Shellminator V3.0.1
Simple Terminal
|
Until now, we’ve focused on how to configure the terminal, but we haven’t yet talked about what to actually do with the commands that are entered.
Of course, you could take a simple approach and use a massive if-else
chain in the execution function to check if the received text matches certain strings. However, once you have more than 10–15 commands, this becomes inefficient and hard to maintain. And let’s not even get started on the complications that arise if the command includes arguments.
While it’s certainly possible to handle this manually, we’re happy to tell you there’s a much better solution. We’ve developed a command processor called the Commander API. This API is a standalone project, designed to be highly optimized while remaining user-friendly. It took quite a bit of effort to get it to this level, but the end result is something we think you’ll really appreciate.
In the following examples, we’ll show you how to connect Shellminator with the Commander API to take your command-line functionality to the next level. Let’s get started!
First, we need to install the Commander API, just like we did with Shellminator. If you need extra guidance, the Commander API website provides detailed instructions for installation.
Next, we include the necessary libraries:
Just like Shellminator, the Commander API operates by deriving an object that will handle tasks for us:
Now comes the design phase. We need to decide how many commands the system will support. The great news is that the system can be expanded or reduced at any time (as long as backward compatibility is not an issue). For this example, let’s define three commands: cat
, dog
, and sum
.
cat
and dog
will simply print text. sum
, however, will perform a calculation—it will add two numbers if valid arguments are provided.To implement these commands, we need to create callback functions. Similar to Shellminator, the Commander API is designed with a non-blocking architecture, so you’ll use callbacks to define external hooks for each command:
The structure of callback functions in Commander API is fixed:
bool
char *args, CommandCaller* caller
You’re free to name your callback functions however you like. These callbacks will form the foundation for handling your commands efficiently.
The next step is crucial for ensuring everything works properly. We need to tell our Commander
object which commands it should recognize. The easiest way to do this is by creating a command tree. This "tree" is essentially an array where each element is a structure that contains:
At first, this might sound a bit abstract, but don’t worry—we’ve created a macro to make this process much simpler:
The command tree must always be of type Commander::systemCommand_t
. Each element should be defined using the systemCommand
macro in the following format:
Once the command tree is set up, it’s time to connect it to Shellminator
. In the previous examples, we derived a class from the Shellminator
class to create a terminal interface. However, when using Commander, we need to start from the ShellminatorCommanderInterface
class instead. This class is built on top of Shellminator
and comes pre-configured to work seamlessly with Commander. The usage is almost identical, so there’s no need to worry about any steep learning curve.
Here’s how to create the terminal object with Commander integration:
For Commander to function correctly, it must be initialized. Under the hood, Commander performs some abstract but essential tasks during this initialization phase. During this time, it temporarily uses a fair amount of stack memory to create an optimized internal structure. This structure allows Commander to efficiently process commands later on.
To avoid issues with memory, it’s best to initialize Commander as early as possible in your program. Additionally, you can assign a debug channel for easier troubleshooting in case something doesn’t work as expected. Here’s how to initialize Commander:
The final step to complete the setup is implementing the callback functions we declared earlier. Let’s focus on the callback for the sum
command, as it’s the most complex of the three:
args
: This is a simple C-style string containing the arguments passed to the command (if any). For the sum
command, it should contain two integers separated by a space.caller
: This is a pointer to the Commander object that invoked this function. It acts as an interface for communicating back to the user. For example, you can print messages or errors using its print
and println
methods.sscanf
to extract two integers (a
and b
) from the args
string. caller->print
and then returns false
to indicate failure.caller->print
and caller->println
to display the result in the following format: a + b = sum
true
to indicate successful execution.caller->print()
and caller->println()
: When writing output in Commander callbacks, always use these methods. They ensure the output is correctly routed to the response channel.true
, when the command was executed without problem. When any problem occurs, it must return with false
.
Here are a few things you might want to try out in the demo below:
cat
→ Executes the cat
command, and you'll see the response from the cat_func
callback. Cat
→ You'll get a Command "Cat" not found!
error because the system is case-sensitive. dog
→ Executes the dog
command, and you'll see the response from the dog_func
callback. help
→ The help
command still works as expected. ?
→ Does the same thing as help
, just a shortcut. sum
→ Error message: _"Executes the cat command, and you'll see the response from the cat_func callback."_ sum
command. sum?
→ Displays information on how to use the sum
command. sum 10 20
→ Adds the two numbers and prints the result: 30.