-
Notifications
You must be signed in to change notification settings - Fork 0
Motivation
We need to use commands for our projects on a day-to-day basis; Whether those projects are command line interface applications, bots or games such as Minecraft.
Back when I first started working on this project, I programmed many Discord bots, both for fun and for commissions. Discord bots are a classic example of applications that require some sort of command handling, since they are supposed to operated on by users in chat.
I mainly used JDA to write my bots, which does not have a built-in command framework. Naturally, I started writing my own little framework. It worked fine for the most part and even had a couple of other users. But after a rather short time, I stopped maintaining and updating it, since I had decided to switch to a more stable framework with broader support in the community, JDA-Utilities. This still wasn't really satisfying to me though. I came to realise that all of those command frameworks actually don't solve the fundamental issues of parsing commands and arguments and translating them to actions. On the contrary: they tend to leave you to them and instead eliminate some superficial inconveniences.
As an example, let's say you're programming a Discord bot application for moderation purposes. That is, it has commands to ban, kick and mute users.
All of these commands have in common that you need to check whether the bot and/or the executor even have the permission to ban/kick/mute users and that you somehow need to extract a user from the arguments.
Fortunately, JDA-Utilities provides a built-in annotation for permission checks. This is nice, since we now don't have to do it in our command methods anymore. But it poses a different problem: what if we want similar mechanisms for different conditions? Having one or two built-in annotations for JDA specifically is not a problem, but what if we want to extend it or make it general purpose? We cannot possibly implement all possible user requirements in our framework. And without a proper interface to do so, the framework eventually becomes insufficient.
Concerning the argument parsing, neither my old framework nor JDA-Utilities help you directly. All your command typically knows are the arguments in String form. The first "solution" that comes to mind for that matter is extracting the algorithm used to parse Users to a different class. JDA-Utilities does that with its FinderUtil
, for example. Or, alternatively, you could even go one step further and write some sort of extensible mapper that you can pass generic arguments. That does reduce boilerplate, but you are still required to do this in every command in the same manner and it is usually not provided by those frameworks.
And finally, an additional problem emerges that is not so easily solved: error handling. Every time you parse anything, every time you check for any condition, you have to keep in mind to handle possible errors and respond to them accordingly. There is almost no way to effectively move that logic away from your command code in traditional command frameworks.
Only after having accounted for every single one of those steps, we're able to write our actual command logic. More often than not, this logic is smaller than the boilerplate sorrounding it. Additionally, these problems all lead to one thing, sooner or later: duplicate code. And as we know, that is not good. It makes code harder to maintain, more error prone and just less fun, honestly.
The situation above boils down to four different stages of command execution:
-
Requirements and code that runs before anything else (e.g. context-based condition checks such as permissions)
-
Argument parsing and mapping
-
Execution of the actual command logic
-
Error handling (needs to be available during any of the previous stages)
Everything except 3. pollutes our command methods and we want to be able to move it to a different place without losing extensibility. The ideal result would be commands that only contain the actual command logic and nothing else.
mela-command, at its core, is a more sophisticated and simple, yet still considerably traditional, general purpose variant of the many other command frameworks out there. The difference lies in what it provides on top of this core.
The bind framework is a precise implementation of the solution described above. In an aspect-oriented way, command methods only contain the logic and every other stage is moved to reusable classes and interfaces that are handled by the framework through Inversion of Control.
As hinted at in the readme, the two major frameworks that have tried similar things as mela-command are sk89q's Intake and aikar's commands.
Intake was originally supposed to be the basis of mela-command, until it was decided to be rewritten, for a few reasons:
-
Intake is quite old and its last commits were made a long time ago.
-
The framework imitates Guice's API, but doesn't actually have a proper Guice integration, which makes it annoying to use in combination with Guice.
-
Despite a number of Intake's features and mechanisms made it into the bind framework, they are rather limited overall. Many of the features that are completely customisable and part of the standard library in mela-command are either built-in for Intake, i.e. unchangeable, or missing entirely.
-
Although Intake's code base is around the size of mela-command's or even smaller, it's hard to see through, in my opinion and has made our attempts to modify it significantly harder.
When I was made aware of aikar/commands (ACF), the development process of mela-command had already begun.
While that framework does some things better, it lacks some other qualities that I am looking for.
-
It is the opposite of "simple and concise", one of mela-command's main principles. Its code base is large, there are many things that could be simplified and it has some bloated features built-in that mela-command leaves up to extensions, such as Command Completions or I18N.
-
It has an own Dependency Injection framework that is part of its very core. While that doesn't make the framework incompatible with Guice, I think the general lack of modularisation does more harm than good.
-
While it is more flexible than Intake, it still doesn't get to mela-command's flexibility and extensibility. It predefines its way of argument parsing as well as most of the things that are part of mela-command's bind framework standard library. And although it has validation mechanisms for arguments, it lacks the amount of possibilities that mela-command provides via its interceptor interfaces.
So, although ACF can be used for many of mela-command's purposes, it goes in a very different direction in regards to project structure and mentality.
If you prefer other frameworks or work on one of the mentioned ones yourself, please note that those criticisms are not made with malicious intents! Open source development, in my eyes, is not about competition or what's better than what, but about inspiration, cooperation and improvement for the common good.
- Command Implementation
- Command Groups
- Dispatching Commands
- Command Contexts
- Argument Parsing
- Compile API
- Overview
- Mapper Bindings
- Mapping Interceptor Bindings