Skip to content

hassaanmaqsood/clicore

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

2 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

CliCore

License: MIT C++ JavaScript Python

A flexible, cross-platform command-line processor with extension support for C++, JavaScript, and Python.

🌟 Features

  • πŸ”Œ 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

πŸš€ Quick Start

C++ (Arduino/ESP32)

#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);
    }
}

JavaScript

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!"

Python

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!"

πŸ“š Command Syntax

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"

πŸ”Œ Creating Extensions

Extensions allow you to add domain-specific commands without modifying the core library.

C++ Extension

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);

JavaScript Extension

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);

Python Extension

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)

🎨 Examples

Accessing Command Data

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)

Interactive CLI

JavaScript

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();
});

Python

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):
        break

πŸ“– API Reference

Core Methods

All 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()

Command Handler Signature

C++: std::function<CmdString(const ParsedCommand&)>
JavaScript: (cmd: ParsedCommand) => string
Python: Callable[[ParsedCommand], str]

πŸ—οΈ Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚         CommandProcessor                β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚   Command Parser & Tokenizer      β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚   Command Registry (Map)          β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚   Extension Manager               β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
           β”‚
           β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
           β”‚                             β”‚
    β”Œβ”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”              β”Œβ”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”
    β”‚  Extension 1β”‚              β”‚  Extension 2β”‚
    β”‚  (WiFi)     β”‚              β”‚  (Logging)  β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜              β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

🎯 Use Cases

  • 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

πŸ”§ Configuration

C++ String Type Selection

// 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"

Platform Detection

The library automatically detects the platform using #ifdef ARDUINO and adjusts accordingly.

πŸ§ͺ Testing

C++ (with Catch2)

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");
}

JavaScript (with Jest)

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');
});

Python (with pytest)

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'

🀝 Contributing

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.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/AmazingFeature)
  3. Commit your changes (git commit -m 'Add some AmazingFeature')
  4. Push to the branch (git push origin feature/AmazingFeature)
  5. Open a Pull Request

πŸ“„ License

This project is licensed under the MIT License - see the LICENSE file for details.

πŸ™ Acknowledgments

  • Inspired by Unix command-line parsing conventions
  • Built for real-world embedded systems and IoT applications
  • Community feedback and contributions

πŸ“¬ Contact

πŸ—ΊοΈ Roadmap

  • 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.

About

A command-line processor with extension support for C++, JavaScript, and Python

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors