cli C++ Library

repository·master·Indexed 23 days ago

https://github.com/daniele77/cli

A cross-platform, header-only C++14 library for creating interactive, Cisco-style command-line interfaces. It features a hierarchical menu system, autocompletion, persistent history, and an asynchronous architecture. The library supports local terminal sessions and remote telnet sessions via Boost Asio or standalone Asio, utilizing a scheduler-based execution model to manage command handlers.

Tokens
5.6K
Snippets
7
Records
40
Agent score
79%

What's inside cli

  1. Build a command hierarchy with Menus

    master

    The CLI is structured as a tree of Menu objects. You must provide at least one root menu to initialize the Cli instance. You can create a hierarchy by nesting menus using the Menu::Insert method, which takes ownership of the child menu via std::move.

    To create a CLI, initialize a Menu and pass it to the Cli constructor.

    // create a menu (this is the root menu of our cli)
    auto rootMenu = make_unique<Menu>("myprompt");
    
    // ... fills rootMenu with commands or sub-menus
    
    // create the cli with the root menu
    Cli cli(std::move(rootMenu));
  2. How async programming and schedulers work in cli

    master

    The cli library is asynchronous. Command handlers are executed by a scheduler in a specific thread (which can be the main thread). This design allows for single-threaded applications without manual synchronization concerns.

    Key Concepts

    • Scheduler: Manages the execution of command handlers. You must pass a scheduler instance to CliLocalTerminalSession.
    • Scheduler::Run(): Enters the scheduler loop. All CLI callbacks execute in the thread that calls this method.
    • Scheduler::Stop(): Exits the scheduler loop.
    • Scheduler::Post(const std::function<void()>& f): A thread-safe way to submit work to the scheduler from any other thread.

    Available Schedulers

    • LoopScheduler: The simplest option. Use this if you do not need remote (telnet) sessions.
    • BoostAsioScheduler: A wrapper around boost::asio::io_context. Required if using BoostAsioCliTelnetServer or if your application already uses Boost Asio.
    • StandaloneAsioScheduler: A wrapper around standalone asio::io_context. Required if using StandaloneAsioCliTelnetServer or if your application uses standalone Asio.
  3. CLI User Interface and Navigation

    master

    The CLI provides an interactive prompt. Users can navigate through menus and execute commands using the following patterns:

    • Enter a submenu: Type the submenu name.
    • Go back to parent menu: Type the parent menu name or ...
    • Navigate history: Use Up/Down arrow keys to cycle through previous commands.
    • Exit: Type exit or use the exit command.

    Commands

    • help: List available commands and descriptions.
    • history: Show previously entered commands.
    • exit: Terminate the application.
    • Command Execution:
      • Execute a command in the current menu by its name.
      • Execute a command in a submenu using its full path (e.g., submenu command).
      • Re-run a history item using ! followed by its identifier (e.g., !1).

    Features

    • Autocompletion: Press Tab to suggest command or menu names.
    • Screen Clearing: Press Ctrl-L.
    • Parameter Parsing: Supports single quotes (') and double quotes (") to treat strings (including spaces) as single parameters. Use backslash \ to escape quotes within parameters.
  4. Enable Unicode support via custom streams

    master

    By default, cli uses standard std::cin and std::cout, which may not support Unicode effectively on all platforms. To handle Unicode (e.g., UTF-8), you must provide custom Unicode-aware stream objects derived from std::istream or std::ostream when initializing a FileSession.

    A common approach is using boost::nowide to provide UTF-8 aware streams.

  5. Install the cli library

    master

    The cli library is a header-only C++14 library. You can obtain it via GitHub releases, Vcpkg, or Conan. Since it is header-only, you only need to include the header paths in your compilation process.

    To install via CMake to your system:

    mkdir build && cd build
    cmake ..
    sudo make install

    To specify a custom installation path:

    mkdir build && cd build
    cmake .. -DCMAKE_INSTALL_PREFIX:PATH=<cli_install_location>
    make install
    mkdir build && cd build
    cmake ..
    sudo make install
  6. Integrate cli using CMake FetchContent

    master

    You can include the cli library directly in your project using CMake's FetchContent module. This avoids manual installation and manages the dependency automatically.

    Add the following to your CMakeLists.txt:

    include(FetchContent)
    FetchContent_Declare(
      cli
      GIT_REPOSITORY https://github.com/daniele77/cli.git
      GIT_TAG v2.1.0
    )
    FetchContent_MakeAvailable(cli)
    
    add_executable(main-project)
    target_link_libraries(main-project PRIVATE cli::cli)
    include(FetchContent)
    FetchContent_Declare(
      cli
      GIT_REPOSITORY https://github.com/daniele77/cli.git
      GIT_TAG v2.1.0
    )
    FetchContent_MakeAvailable(cli)
    
    add_executable(main-project)
    target_link_libraries(main-project PRIVATE cli::cli)
  7. Compile cli examples

    master

    Examples are located in the examples directory. Compilation requirements depend on whether you need remote (telnet) sessions:

    1. No remote sessions (No Asio/Boost): cmake .. -DCLI_BuildExamples=ON

    2. Using Boost Asio: cmake .. -DCLI_BuildExamples=ON -DCLI_UseBoostAsio=ON (Optionally specify path: -DBOOST_ROOT=<boost_path>)

    3. Using Standalone Asio: cmake .. -DCLI_BuildExamples=ON -DCLI_UseStandaloneAsio=ON (Optionally specify path: -DASIO_INCLUDEDIR=<asio_path>)

    After configuring, run: cmake --build .

    mkdir build && cd build
    cmake .. -DCLI_BuildExamples=ON
    cmake --build .
  8. The Scheduler abstraction

    master
    In the CLI library, a Scheduler acts as a task execution engine. It provides a decoupled way to hand off work to a background execution context. By using the Post method, a consumer can submit work without needing to know the underlying threading model or the specific thread that will eventually run the task. This is useful for asynchronous task submission and thread-safe communication between different parts of an application.
  9. Difference between ExecOne() and PollOne() in LoopScheduler

    master

    When manually driving the LoopScheduler loop, you can choose between two execution modes:

    1. ExecOne() (Blocking): This method uses a condition variable to wait. If the task queue is empty, the calling thread will sleep until either a new task is added via Post() or Stop() is called. This is efficient for dedicated worker threads.
    2. PollOne() (Non-blocking): This method checks the queue immediately. If the queue is empty or the scheduler is stopped, it returns false without waiting. This is useful for integration into existing event loops where you don't want to block the main thread.
  10. Initialize a CLI application with Cli

    master

    To create a CLI application, instantiate the cli::Cli class. You must provide a std::unique_ptr<cli::Menu> representing the root menu (the first level of commands) and an optional std::unique_ptr<cli::HistoryStorage> to define how command history is stored.

    By default, if no history storage is provided, it uses cli::VolatileHistoryStorage (history is lost when the application restarts). For persistent history, use cli::FileHistoryStorage.

  11. Use BoostAsioScheduler with an existing io_context

    master

    If your application already manages a boost::asio::io_context, you can pass it directly to the BoostAsioScheduler to integrate the CLI into your existing event loop.

    // Existing application context
    boost::asio::io_context ioc;
    
    // CLI setup using the existing ioc
    BoostAsioScheduler scheduler(ioc);
    CliLocalTerminalSession localSession(cli, scheduler);
    BoostAsioCliTelnetServer server(cli, scheduler, 5000);
    
    // Run your existing application loop
    ioc.run();
  12. Add commands to a Menu

    master

    Commands can be added to any Menu using the Menu::Insert method. The library supports several types of command handlers:

    • Free functions
    • std::function objects
    • Lambdas

    Command handlers can take an arbitrary number of parameters. Supported types include basic types, std::string, and custom types (provided you overload std::istream::operator>>).

    Special Case: Arbitrary String Arguments If you need to capture an arbitrary number of string arguments, your handler must take exactly one parameter of type std::vector<std::string>.