A flexible, cross-platform command-line processor with extension support for C++, JavaScript, and Python.
- π Extension-Based Architecture - Add functionality through modular extensions
- π― Unix-Style Command Parsing - Supports flags (
-f), long options (--option=value), and arguments - π Cross-Platform - Works on Arduino, ESP32, Node.js, Python, desktop C++, and embedded Linux
- π§© Language Agnostic - Same design pattern across C++, JavaScript, and Python
- π¦ Zero Dependencies - Core library has no external dependencies
- π§ͺ Easily Testable - Clean separation of concerns makes unit testing simple
- π Production Ready - Used in real embedded systems and CLI tools
#include "command_processor.h"
CommandProcessor processor;
void setup() {
Serial.begin(115200);
processor.enableBuiltinCommands();
// Register custom command
processor.registerCommand("led",
[](const ParsedCommand& cmd) -> CmdString {
if (cmd.args.empty()) return "Usage: led <on|off>\n";
if (cmd.args[0] == "on") {
digitalWrite(LED_BUILTIN, HIGH);
return "LED turned on\n";
} else {
digitalWrite(LED_BUILTIN, LOW);
return "LED turned off\n";
}
},
"Control LED",
"led <on|off>");
}
void loop() {
if (Serial.available()) {
String input = Serial.readStringUntil('\n');
String result = processor.processCommand(input);
Serial.print(result);
}
}const { CommandProcessor } = require('clicore');
const processor = new CommandProcessor();
processor.enableBuiltinCommands();
// Register custom command
processor.registerCommand('greet',
(cmd) => {
const name = cmd.args[0] || 'World';
return `Hello, ${name}!\n`;
},
'Greet someone',
'greet [name]');
// Process command
const result = processor.processCommand('greet Alice');
console.log(result); // "Hello, Alice!"from clicore import CommandProcessor, ParsedCommand
processor = CommandProcessor()
processor.enable_builtin_commands()
# Register custom command
def greet_handler(cmd: ParsedCommand) -> str:
name = cmd.args[0] if cmd.args else 'World'
return f"Hello, {name}!\n"
processor.register_command('greet', greet_handler,
'Greet someone', 'greet [name]')
# Process command
result = processor.process_command('greet Alice')
print(result) # "Hello, Alice!"CliCore supports Unix-style command syntax:
# Basic command
command
# With arguments
command arg1 arg2 arg3
# Short flags
command -a -b -c
command -abc # equivalent
# Long flags
command --verbose --debug
# Options with values
command --output=file.txt
command --output file.txt # equivalent
# Combined
command arg1 arg2 --flag --option=value -v "quoted argument"Extensions allow you to add domain-specific commands without modifying the core library.
template<typename WiFiManagerType>
class WiFiExtension : public ICommandExtension {
private:
WiFiManagerType* _wifi;
public:
WiFiExtension(WiFiManagerType* wifi) : _wifi(wifi) {}
void registerCommands(CommandProcessor& processor) override {
processor.registerCommand("wifi-connect",
[this](const ParsedCommand& cmd) -> CmdString {
if (cmd.args.size() < 2)
return "Usage: wifi-connect <ssid> <password>\n";
bool success = _wifi->connect(cmd.args[0], cmd.args[1]);
return success ? "Connected!\n" : "Failed!\n";
},
"Connect to WiFi",
"wifi-connect <ssid> <password>");
}
};
// Usage
WiFiManager wifi;
auto ext = std::make_shared<WiFiExtension<WiFiManager>>(&wifi);
processor.addExtension(ext);class FileSystemExtension extends CommandExtension {
constructor(fs) {
super();
this._fs = fs;
}
registerCommands(processor) {
processor.registerCommand('read',
(cmd) => {
if (cmd.args.length === 0)
return 'Usage: read <filename>\n';
try {
return this._fs.readFileSync(cmd.args[0], 'utf8');
} catch (error) {
return `Error: ${error.message}\n`;
}
},
'Read file contents',
'read <filename>');
}
}
// Usage
const fs = require('fs');
const fsExt = new FileSystemExtension(fs);
processor.addExtension(fsExt);class DatabaseExtension(CommandExtension):
def __init__(self, db_connection):
self._db = db_connection
def register_commands(self, processor: CommandProcessor) -> None:
def query_handler(cmd: ParsedCommand) -> str:
if not cmd.args:
return "Usage: query <sql>\n"
sql = ' '.join(cmd.args)
results = self._db.execute(sql)
return str(results) + '\n'
processor.register_command('query', query_handler,
'Execute SQL query',
'query <sql>')
# Usage
db = get_database_connection()
db_ext = DatabaseExtension(db)
processor.add_extension(db_ext)All three languages provide the same parsed command structure:
// C++
processor.registerCommand("demo", [](const ParsedCommand& cmd) {
// Command name
CmdString name = cmd.command;
// Arguments (positional)
for (const auto& arg : cmd.args) {
// Use arg...
}
// Flags (boolean)
bool verbose = cmd.flags.find("v") != cmd.flags.end();
// Options (key=value)
if (cmd.options.find("output") != cmd.options.end()) {
CmdString output = cmd.options.at("output");
}
return "OK\n";
});// JavaScript
processor.registerCommand('demo', (cmd) => {
const name = cmd.command;
const args = cmd.args;
const verbose = cmd.flags.has('v');
const output = cmd.options.get('output') || 'default.txt';
return 'OK\n';
});# Python
def demo_handler(cmd: ParsedCommand) -> str:
name = cmd.command
args = cmd.args
verbose = 'v' in cmd.flags
output = cmd.options.get('output', 'default.txt')
return 'OK\n'
processor.register_command('demo', demo_handler)const readline = require('readline');
const { CommandProcessor } = require('./path/to/clicore.js');
const processor = new CommandProcessor();
processor.enableBuiltinCommands();
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
prompt: '> '
});
console.log("Type 'help' for commands, 'quit' to exit");
rl.prompt();
rl.on('line', (line) => {
const input = line.trim();
if (input === 'quit') process.exit(0);
if (input) {
const result = processor.processCommand(input);
console.log(result);
}
rl.prompt();
});from clicore import CommandProcessor
processor = CommandProcessor()
processor.enable_builtin_commands()
print("Type 'help' for commands, 'quit' to exit")
while True:
try:
user_input = input('> ').strip()
if user_input in ('quit', 'exit'):
break
if user_input:
result = processor.process_command(user_input)
print(result, end='')
except (KeyboardInterrupt, EOFError):
breakAll three languages share similar APIs:
| Method | C++ | JavaScript | Python |
|---|---|---|---|
| Enable help command | enableBuiltinCommands() |
enableBuiltinCommands() |
enable_builtin_commands() |
| Register command | registerCommand(...) |
registerCommand(...) |
register_command(...) |
| Process input | processCommand(input) |
processCommand(input) |
process_command(input) |
| Add extension | addExtension(ext) |
addExtension(ext) |
add_extension(ext) |
| Get commands | getRegisteredCommands() |
getRegisteredCommands() |
get_registered_commands() |
C++: std::function<CmdString(const ParsedCommand&)>
JavaScript: (cmd: ParsedCommand) => string
Python: Callable[[ParsedCommand], str]
βββββββββββββββββββββββββββββββββββββββββββ
β CommandProcessor β
β βββββββββββββββββββββββββββββββββββββ β
β β Command Parser & Tokenizer β β
β βββββββββββββββββββββββββββββββββββββ β
β βββββββββββββββββββββββββββββββββββββ β
β β Command Registry (Map) β β
β βββββββββββββββββββββββββββββββββββββ β
β βββββββββββββββββββββββββββββββββββββ β
β β Extension Manager β β
β βββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββββββ
β β
ββββββββΌβββββββ ββββββββΌβββββββ
β Extension 1β β Extension 2β
β (WiFi) β β (Logging) β
βββββββββββββββ βββββββββββββββ
- IoT Devices - Control ESP32/Arduino via serial commands
- CLI Tools - Build interactive command-line applications
- Embedded Systems - Add command interface to embedded Linux devices
- Test Fixtures - Control test equipment via command interface
- Debug Consoles - Runtime debugging and configuration
- Automation Scripts - Scriptable device control
- REPL Interfaces - Interactive programming environments
// Use Arduino String
#define CMD_STRING_TYPE String
#include "command_processor.h"
// Use std::string
#define CMD_STRING_TYPE std::string
#include "command_processor.h"The library automatically detects the platform using #ifdef ARDUINO and adjusts accordingly.
TEST_CASE("Command parsing") {
CommandProcessor processor;
processor.registerCommand("test", [](const ParsedCommand& cmd) {
return cmd.args.size() > 0 ? cmd.args[0] : "empty";
});
REQUIRE(processor.processCommand("test hello") == "hello");
}test('command parsing', () => {
const processor = new CommandProcessor();
processor.registerCommand('test', (cmd) => {
return cmd.args.length > 0 ? cmd.args[0] : 'empty';
});
expect(processor.processCommand('test hello')).toBe('hello');
});def test_command_parsing():
processor = CommandProcessor()
processor.register_command('test', lambda cmd:
cmd.args[0] if cmd.args else 'empty')
assert processor.process_command('test hello') == 'hello'Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
- Fork the repository
- Create your feature branch (
git checkout -b feature/AmazingFeature) - Commit your changes (
git commit -m 'Add some AmazingFeature') - Push to the branch (
git push origin feature/AmazingFeature) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
- Inspired by Unix command-line parsing conventions
- Built for real-world embedded systems and IoT applications
- Community feedback and contributions
- GitHub Issues: https://github.com/hassaanmaqsood/clicore/issues
- Command aliases support
- Command history and autocomplete
- Shell script execution mode
- Rust implementation
- Go implementation
Built with β€οΈ for developers who need flexible command interfaces across platforms.