Shellminator V3.0.1
Simple Terminal
Loading...
Searching...
No Matches
Example 204 Commander Position Independent Arguments

Handling Position-Independent Arguments

Now, let’s talk about position-independent arguments.

Have you ever compiled a program using GCC from the command line? If so, you might already be familiar with this concept! Position-independent arguments always come in key-value pairs.

In the past, argument names were often a single letter preceded by a dash, like -h for help. However, modern applications allow more human-friendly argument names that can be full words, as long as they don’t contain spaces. These longer names are usually prefixed with two dashes (--).

For example:

  • The old-school way: -h
  • The modern way: --help

The argument value follows the argument name, separated by at least one space.


Example: Implementing a "random" Command

Let's say we need to implement a command called random. As the name suggests, its job is to generate a random number.

In the syntax used by GCC, the order of function parameters is fixed and cannot be changed, as swapping them could alter the result. In contrast, higher-level languages like Python and Matlab allow parameters to be specified using name-value pairs. This makes it clear which argument is assigned to which value, reducing potential confusion—although this approach requires a bit more typing.

Additionally, modern programming languages often implement random in two ways:

  1. One argument → This is treated as the upper limit, with 0 as the default lower limit.
  2. Two arguments → Both a lower and an upper limit are specified.

We'll build a command that supports both approaches.

Defining Arguments

Each argument needs a short name (required) and an optional long name.
To keep things consistent, either use short names for all arguments or enable long names support for all arguments.

For simplicity:

  • The lower limit argument will have a short name -l and a long name --low.
  • The upper limit argument will have a short name -h and a long name --high.

Here’s how we define these arguments:

Argument min_number( args, 'l', "low" );
Argument max_number( args, 'h', "high" );

Handling Different Cases

We need to check four scenarios:

  1. Both arguments are provided → ✅ Perfect! We can generate a random number within the range.
  2. Only the high argument is provided → ✅ No problem! We assume the lower limit is 0.
  3. Only the low argument is provided → ❌ This doesn't make sense—we should inform the user that something went wrong.
  4. Neither argument is provided → ❌ The user probably doesn’t understand how the command works, so we should display usage instructions.

Optimizing Performance

Previously, we used parseInt() to check if an argument was parsed correctly. However, if you call it multiple times, your command might run hundreds of times slower than necessary!

Instead, once parseInt(), parseFloat(), or parseString() has been successfully called, the argument object remembers its parsed value.

To quickly check if an argument was successfully parsed, you can cast the argument object to a bool.

Example:

min_number.parseInt();
max_number.parseInt();
// Check if both parameters are found
if ((bool)min_number && (bool)max_number) {
caller->print(random((int)min_number, (int)max_number));
return true;
}

To make things a bit more visually appealing, we can also add colors to error messages! This helps draw the user’s attention to the important part of the message—which might even be the solution itself.

// Format the error message to guide the user in the right direction.
caller->print("Argument error! Please check ");
caller->print("random?");
caller->println(" for help!");
@ UNDERLINE
Underline text style.
@ REGULAR
Regular text style.
@ WHITE
White text color.
@ RED
Red text color.

The shell.format() method works seamlessly with the caller argument, so you can mix and match them without any issues!

Live Demo

Here are a few suggested commands you might want to try in the demo above:

âś… Basic Usage

  • random? – Displays a help message explaining how to use the command.
  • random -h 10 – Generates a random number between 0 and 10.
  • random --high 10 – Does the same as the previous command.

âś… Custom Range

  • random -h 10 -l 5 – Generates a random number between 5 and 10.
  • random -h 10 --low 5 – Same as above.
  • random -l 5 -h 10 – Again, generates a number between 5 and 10. This shows that argument order doesn’t matter!

❌ Error Cases

  • random – Error: No arguments provided.
  • random -l 5 – Error: Only the lower limit is given; missing the upper limit.
  • random -l tz5 -h 10 – This is an interesting case! 🤔

You won't get an error, but the lower limit won't be parsed because it's not a valid number. The code will treat it as if only the upper limit was provided, meaning the lower limit defaults to 0.

Give them a try and see what happens!

Whole Code

/*
* Created on Aug 10 2020
*
* Copyright (c) 2023 - Daniel Hajnal
* hajnal.daniel96@gmail.com
* This file is part of the Shellminator project.
* Modified 2023.05.13
*/
#include "Shellminator.hpp"
#include "Commander-API.hpp"
#include "Commander-Arguments.hpp"
// We have to create an object from Commander class.
Commander commander;
bool random_func( char *args, CommandCaller* caller );
Commander::systemCommand_t API_tree[] = {
systemCommand( "random", "Generates a random number.\r\n\targs: --low:Minimum number\r\n\t --high: Maximum number", random_func )
};
// Create a ShellminatorCommanderInterface object, and initialize it to use Serial
// System init section.
void setup(){
Serial.begin(115200);
// Clear the terminal
shell.clear();
commander.attachDebugChannel( &Serial );
commander.attachTree( API_tree );
commander.init();
shell.attachCommander( &commander );
// Initialize shell object.
shell.begin( "arnold" );
}
// Infinite loop.
void loop(){
// Process the new data.
shell.update();
}
bool random_func(char *args, CommandCaller* caller ){
Argument min_number( args, 'l', "low" );
Argument max_number( args, 'h', "high" );
min_number.parseInt();
max_number.parseInt();
// Check if both parameters are found
if( (bool)min_number && (bool)max_number ){
caller -> print( random( (int)min_number, (int)max_number ) );
return true;
}
// Check if only high bound is given
else if( (bool)max_number && !(bool)min_number ){
caller -> print( random( (int)max_number ) );
return true;
}
// Check if only low bound is given. Because this doesn't
// makes much sense generate an error.
else if( !(bool)max_number && (bool)min_number ){
caller -> println( "Only low bound is given. Low bound is also neccesary!" );
return false;
}
// Create some formatting on the error message to navigate
// the user to the right direction.
caller -> print( "Argument error! Please check " );
shell.format( caller, Shellminator::UNDERLINE, Shellminator::RED );
caller -> print( "random?");
shell.format( caller, Shellminator::REGULAR, Shellminator::WHITE );
caller -> println( " for help!" );
return false;
}