SlideShare a Scribd company logo
1 of 63
Add an interactive command
line to your applications
Daniele Pallastrelli, daniele77.github.io
13.01.2022, Italian C++
My typical projects
• Run for a long period of time
• No classic interaction with the user (i.e., no GUI)
• Run in a server, custom board, almost never desktop applications
• They're not CPU bound applications
Why an interactive console in your apps?
• Need to have some sort of console to interact with my
applications (e.g., embedded systems running around the clock)
to:
• monitor,
• configure,
• manage the system
• E.g., CISCO routers
• Traditional systems: SSH connections + logs
Why an interactive console in your apps?
• Debugging
• Poke around internal state
• Dump internal structures
• Change the log level at runtime
• Change the working mode
• Enable / disable modules
• Load / unload plugins
• Early stage of development
Reinventing the wheel?
Existing solutions in open source domain:
• Linux only
• They're applications where you hook external programs to commands
• No remote sessions
• Few in C++
• None of them in "modern" C++
Enter the shell (cli?)
• My own library in C++14
• Production code quality
• Used in several industrial projects
• Demo time 
• C++14
• Cross-platform (Linux and windows tested)
• Menus and submenus
• Command history (navigation with arrow keys)
• Autocompletion (with TAB key)
• Async interface
• Colors
Something missing…
• Good when you start the app from a console (e.g. desktop
applications or development stage)
• What about processes that run in background (e.g., embedded,
servers & c)?
Try #1
Try #1
Try #2
Features summary
• C++14
• Header only
• Cross-platform (linux and windows)
• Menus and submenus
• Remote sessions (telnet)
• Persistent history (navigation with arrow keys)
• Autocompletion (with TAB key)
• Async interface
• Colors
auto rootMenu = make_unique<Menu>("cli");
rootMenu->Insert(
"hello",
[](std::ostream& out){ out << "Hello, worldn"; },
"Print hello world" );
rootMenu->Insert(
"hello_everysession",
[](std::ostream&){ Cli::cout() << "Hello, everybody" << std::endl; },
"Print hello everybody on all open sessions" );
rootMenu->Insert(
"reverse", {"string_to_revert"},
[](std::ostream& out, const string& arg)
{
string copy(arg);
std::reverse(copy.begin(), copy.end());
out << copy << "n";
},
"Print the reverse string" );
rootMenu->Insert(
"add", {"first_term", "second_term"},
[](std::ostream& out, int x, int y)
{
out << x << " + " << y << " = " << (x+y) << "n";
},
"Print the sum of the two numbers" );
rootMenu->Insert(
"sort", {"list of strings separated by space"},
[](std::ostream& out, std::vector<std::string> data)
{
std::sort(data.begin(), data.end());
out << "sorted list: ";
std::copy(data.begin(), data.end(), std::ostream_iterator<std::string>(out, " "));
out << "n";
},
"Alphabetically sort a list of words" );
auto subMenu = make_unique<Menu>("sub");
subMenu->Insert(
"demo",
[](std::ostream& out){ out << "This is a sample!n"; },
"Print a demo string" );
rootMenu->Insert( std::move(subMenu) );
// create a cli with the given root menu and a persistent storage
Cli cli( std::move(rootMenu), std::make_unique<FileHistoryStorage>(".cli") );
// global exit action
cli.ExitAction( [](auto& out){ out << "Goodbye and thanks for all the fish.n"; } );
// std exception custom handler
cli.StdExceptionHandler(
[](std::ostream& out, const std::string& cmd, const std::exception& e)
{
out << "Exception caught in cli handler: "
<< e.what()
<< " handling command: "
<< cmd
<< ".n";
}
);
LoopScheduler scheduler;
CliLocalTerminalSession localSession(cli, scheduler, std::cout, 200);
localSession.ExitAction(
[&scheduler](auto& out) // session exit action
{
out << "Closing App...n";
scheduler.Stop();
}
);
scheduler.Run();
StandaloneAsioScheduler scheduler;
CliLocalTerminalSession localSession(cli, scheduler, std::cout, 200);
localSession.ExitAction(
[&scheduler](auto& out) // session exit action
{
out << "Closing App...n";
scheduler.Stop();
}
);
// setup server
StandaloneAsioCliTelnetServer server(cli, scheduler, 5000);
// exit action for all the connections
server.ExitAction( [](auto& out) { out << "Terminating this session...n"; } );
scheduler.Run();
How does it work
Core idea
• Command list
• The user enters a string: command and parameters
• Iteration over the command list:
• Command name
• Parameter number and type
• The handler is called with the typed parameters
cli
hello hello_everysession sub
hello
demo
cli
hello
hello_everysession
sub
hello
demo
class Command {};
class Menu : public Command
{
private:
Menu* parent{ nullptr };
std::vector<Command*> cmds;
};
template <typename F, typename ... Args>
class VariadicFunctionCommand : public Command {};
Menu is-a Command, because when you start the CLI, every Menu shows as a command
you can digit at the prompt (e.g., if you define a Menu "foo", you get the command "foo" in
the Cli to enter the submenu).
auto rootMenu = make_unique<Menu>("cli");
rootMenu->Insert(
"hello",
[](std::ostream& out)
{
out << "Hello, worldn";
}
);
rootMenu->Insert(
"hello_everysession",
[](std::ostream&)
{
Cli::cout() << "Hello, everybodyn";
}
);
auto subMenu = make_unique<Menu>("sub");
subMenu->Insert(
"hello",
[](std::ostream& out)
{
out << "Hello, submenu worldn";
}
);
subMenu->Insert(
"demo",
[](std::ostream&)
{
out << "Demon";
}
);
rootMenu->Insert( std::move(subMenu) );
auto rootMenu = make_unique<Menu>("cli");
...
Cli cli( std::move(rootMenu) );
auto rootMenu = make_unique<Menu>("cli");
...
Cli cli( std::move(rootMenu) );
CliLocalTerminalSession localSession(cli,
scheduler, std::cout, 200);
CliTelnetServer server(cli, scheduler, 5000);
Cli cli( std::move(rootMenu) );
CliLocalTerminalSession localSession(cli,
scheduler, std::cout, 200);
CliTelnetServer server(cli, scheduler, 5000);
CliFileSession fileSession(cli, infile, outfile);
CliLocalTerminalSession localSession(
scheduler, std::cout, 200);
CliTelnetServer server(scheduler, 5000);
CliFileSession fileSession(infile, outfile);
Cli cli( std::move(rootMenu) );
cli.SetLocalSession( localSession );
cli.SetFileSession( fileSession );
cli.SetTelnetServer( server );
rootMenu->Insert(
"add", {"first_term", "second_term"},
[](std::ostream& out, int x, int y)
{
out << x << " + " << y << " = " << (x+y) << "n";
},
"Print the sum of two numbers"
);
rootMenu->Insert(
"add", {"first_term", "second_term"},
[](std::ostream& out, int x, int y)
{
out << x << " + " << y << " = " << (x+y) << "n";
},
"Print the sum of two numbers"
);
Interlude – How do you get the type of a
lambda argument?
template <typename F>
void foo(F f)
{
// what's the type of "f" first parameter?
// something like:
// using T = F::first_parameter_type
)
foo( [](int){} );
Interlude – How do you get the type of a
lambda argument?
template<typename F, typename Ret, typename A, typename... Rest>
A helper(Ret (F::*)(A, Rest...) const);
template <typename F>
void foo(F f)
{
using T = decltype( helper(&F::operator()) );
}
foo( [](int){} );
class Menu : public Command
{
public:
template <typename F>
CmdHandler Insert(const string& cmdName, F f)
{
return CreateCmd(cmdName, f, &F::operator());
}
private:
template <typename F, typename R, typename ... Args>
CmdHandler CreateCmd(const string& cmdName, F& f, R (F::*)(Args...) const)
{
auto cmd = make_unique< VariadicFunctionCommand< F, Args ... > >(cmdName, f);
// insert cmd into this menu commands
...
}
};
Works with lambdas
and std::function
class Menu : public Command
{
public:
...
template <typename R, typename ... Args>
CmdHandler Insert(const std::string& cmdName, R (*f)(Args...))
{
using F = R (*)(Args...);
auto cmd = make_unique<VariadicFunctionCommand<F, Args ...>>(cmdName, f);
// insert cmd into this menu commands
...
}
...
};
Overload for free-
functions
template <typename F, typename ... Args>
class VariadicFunctionCommand : public Command
{
public:
VariadicFunctionCommand(const std::string& _name, F fun) :
Command(_name), func(std::move(fun))
{}
bool Exec(const vector<string>& cmdLine) override
{
...
try
{
Select<Args...>::Exec(func, std::next(cmdLine.begin()), cmdLine.end());
}
catch (std::bad_cast&)
{
return false;
}
return true;
...
}
};
template <typename ... Args>
struct Select;
template <typename P, typename ... Args>
struct Select<P, Args...>
{
template <typename F, typename InputIt>
static void Exec(const F& f, InputIt first, InputIt last)
{
assert( first != last );
assert( std::distance(first, last) == 1+sizeof...(Args) );
const P firstPar = detail::from_string<typename std::decay<P>::type>(*first);
auto g = [&](auto ... pars){ f(firstPar, pars...); };
Select<Args...>::Exec(g, std::next(first), last);
}
};
template <>
struct Select<>
{
template <typename F, typename InputIt>
static void Exec(const F& f, InputIt first, InputIt last)
{
assert(first == last);
f();
}
};
Interlude
How do you manage concurrency?
Concurrency in my projects
• Single thread (when possible), using Proactor pattern.
• When I must: multiple threads, using Proactor pattern 
By the way…
…what's the PROACTOR pattern?
The proactor pattern: Concurrency Without Threads
The Proactor solution
Split every application service into:
• Long-duration operations. execute asynchronously
• Completion handlers. processes the results of the associated
asynchronous operations (potentially invoking additional
asynchronous operations).
Waiting for
completion events
Unblocking
Concurrency in my projects
• asio for asynchonous I/O (timers, too)
• asio::io_context available
• When I/O is ready, asio puts handler in the asio::io_context
• If more threads are needed (e.g., time consuming computations or
blocking I/O), the result is put in asio::io_context
• => everything runs in (my) single thread of execution
Back to the library 
• Input coming from:
• Keyboard, using blocking primitives (std::getchar() and _getch())
• Sockets
• Commands callbacks (in which thread?)
• => proactor (asio::io_context)
Consequences
• The user must instantiate a boost::asio::io_context object
• The whole library depends on boost::asio
Concurrency summary (v. 1.0)
• The library depends on boost::asio, even when you don't need the
telnet server
• Library users ask to use standalone asio instead of boost::asio
• The truth is that the whole library depends on boost::asio because:
• Telnet server needs it
• Keyboard handler needs an event handler
• The two must use the same event manager, i.e. boost::asio::io_context
Release 2.0 – Goals:
• Optionally use standalone asio instead of boost::asio for the telnet
server
• Remove all dependencies if the telnet server is not needed
Release 2.0 – The solution
• New abstraction: "Scheduler"
• schedules async event
• The library provides three kind of schedulers:
• StandaloneAsioScheduler (based on asio::io_context)
• BoostAsioScheduler (based on boost::asio::io_context)
• LoopScheduler (hand made sync queue)
Release 2.0 – The solution
• Cli library can use all the schedulers, but if you need the telnet server
you must use *AsioScheduler
• Bottom line:
• If ( you need telnet server OR your app already uses [boost::]asio::io_context
=> StandaloneAsioScheduler or BoostAsioScheduler
• Else
=> LoopScheduler (no external dependencies)
Release 2.0 – Standalone Asio
#include <cli/standaloneasioscheduler.h>
#include <cli/standaloneasioremotecli.h>
...
StandaloneAsioScheduler scheduler;
// setup local session
CliLocalTerminalSession localSession(cli, scheduler, std::cout, 200);
// setup server
StandaloneAsioCliTelnetServer server(cli, scheduler, 5000);
// start event loop
scheduler.Run();
Release 2.0 – Boost Asio
#include <cli/boostasioscheduler.h>
#include <cli/boostasioremotecli.h>
...
BoostAsioScheduler scheduler;
// setup local session
CliLocalTerminalSession localSession(cli, scheduler, std::cout, 200);
// setup server
BoostAsioCliTelnetServer server(cli, scheduler, 5000);
// start event loop
scheduler.Run();
Release 2.0 – No Asio
#include <cli/loopscheduler.h>
...
LoopScheduler scheduler;
// setup local session
CliLocalTerminalSession localSession(cli, scheduler, std::cout, 200);
// start event loop
scheduler.Run();
Conclusions
Miles to go…
• User-defined arguments (and completion for them)
• More (bash-like) shortcuts
• Conan support
• Rename the library (?)
Library core: design decisions
• Reflection (?)
• Preprocessors (???)
• "Composite" Design Pattern VS Template metaprogramming
Intentional architecture VS emergent design
"Many times, thinking things out in advance saved us serious development headaches later on. ... [on
making a particular specification change] ... Making this change in the spec took an hour or two. If we
had made this change in code, it would have added weeks to the schedule. I can’t tell you how
strongly I believe in Big Design Up Front, which the proponents of Extreme Programming consider
anathema. I have consistently saved time and made better products by using BDUF and I’m proud to
use it, no matter what the XP fanatics claim. They’re just wrong on this point and I can’t be any clearer
than that."
-- Joel Spolsky "The project Aardwark Spec" – Joel On Software
Take away
• Use the right technique
• Use Cli library  (when you need it)
• Use Proactor pattern (when you need it)
• Use your brain (always)
References
Me: daniele.pallastrelli@gmail.com
Me: @DPallastrelli
Github: http://github.com/daniele77
Web: daniele77.github.io
63

More Related Content

What's hot

Interactive Music II ProcessingとSuperColliderの連携1
Interactive Music II ProcessingとSuperColliderの連携1Interactive Music II ProcessingとSuperColliderの連携1
Interactive Music II ProcessingとSuperColliderの連携1
Atsushi Tadokoro
 
Linux 4.x Tracing Tools: Using BPF Superpowers
Linux 4.x Tracing Tools: Using BPF SuperpowersLinux 4.x Tracing Tools: Using BPF Superpowers
Linux 4.x Tracing Tools: Using BPF Superpowers
Brendan Gregg
 

What's hot (20)

Introducing Async/Await
Introducing Async/AwaitIntroducing Async/Await
Introducing Async/Await
 
Why Rust? - Matthias Endler - Codemotion Amsterdam 2016
Why Rust? - Matthias Endler - Codemotion Amsterdam 2016Why Rust? - Matthias Endler - Codemotion Amsterdam 2016
Why Rust? - Matthias Endler - Codemotion Amsterdam 2016
 
Rust system programming language
Rust system programming languageRust system programming language
Rust system programming language
 
Rxjs ngvikings
Rxjs ngvikingsRxjs ngvikings
Rxjs ngvikings
 
TypeScript Introduction
TypeScript IntroductionTypeScript Introduction
TypeScript Introduction
 
Goでかんたんソースコードの静的解析
Goでかんたんソースコードの静的解析Goでかんたんソースコードの静的解析
Goでかんたんソースコードの静的解析
 
Monitoring IO performance with iostat and pt-diskstats
Monitoring IO performance with iostat and pt-diskstatsMonitoring IO performance with iostat and pt-diskstats
Monitoring IO performance with iostat and pt-diskstats
 
The Rust Programming Language
The Rust Programming LanguageThe Rust Programming Language
The Rust Programming Language
 
Building fast interpreters in Rust
Building fast interpreters in RustBuilding fast interpreters in Rust
Building fast interpreters in Rust
 
Interactive Music II ProcessingとSuperColliderの連携1
Interactive Music II ProcessingとSuperColliderの連携1Interactive Music II ProcessingとSuperColliderの連携1
Interactive Music II ProcessingとSuperColliderの連携1
 
REST API 설계
REST API 설계REST API 설계
REST API 설계
 
JVM code reading -- C2
JVM code reading -- C2JVM code reading -- C2
JVM code reading -- C2
 
The Rust Programming Language: an Overview
The Rust Programming Language: an OverviewThe Rust Programming Language: an Overview
The Rust Programming Language: an Overview
 
Golang 高性能实战
Golang 高性能实战Golang 高性能实战
Golang 高性能实战
 
Rust
RustRust
Rust
 
Linux tuning to improve PostgreSQL performance
Linux tuning to improve PostgreSQL performanceLinux tuning to improve PostgreSQL performance
Linux tuning to improve PostgreSQL performance
 
with NATS with Kubernetesの世界へ
with NATS with Kubernetesの世界へwith NATS with Kubernetesの世界へ
with NATS with Kubernetesの世界へ
 
Linux 4.x Tracing Tools: Using BPF Superpowers
Linux 4.x Tracing Tools: Using BPF SuperpowersLinux 4.x Tracing Tools: Using BPF Superpowers
Linux 4.x Tracing Tools: Using BPF Superpowers
 
Adding a BOLT pass
Adding a BOLT passAdding a BOLT pass
Adding a BOLT pass
 
Unified JVM Logging
Unified JVM LoggingUnified JVM Logging
Unified JVM Logging
 

Similar to Add an interactive command line to your C++ application

System Calls.pptxnsjsnssbhsbbebdbdbshshsbshsbbs
System Calls.pptxnsjsnssbhsbbebdbdbshshsbshsbbsSystem Calls.pptxnsjsnssbhsbbebdbdbshshsbshsbbs
System Calls.pptxnsjsnssbhsbbebdbdbshshsbshsbbs
ashukiller7
 
Степан Кольцов — Rust — лучше, чем C++
Степан Кольцов — Rust — лучше, чем C++Степан Кольцов — Rust — лучше, чем C++
Степан Кольцов — Rust — лучше, чем C++
Yandex
 
Fantom - Programming Language for JVM, CLR, and Javascript
Fantom - Programming Language for JVM, CLR, and JavascriptFantom - Programming Language for JVM, CLR, and Javascript
Fantom - Programming Language for JVM, CLR, and Javascript
Kamil Toman
 
Rust: код может быть одновременно безопасным и быстрым, Степан Кольцов
Rust: код может быть одновременно безопасным и быстрым, Степан КольцовRust: код может быть одновременно безопасным и быстрым, Степан Кольцов
Rust: код может быть одновременно безопасным и быстрым, Степан Кольцов
Yandex
 
please help finish sorting methods- import java-util-Arrays- import ja.pdf
please help finish sorting methods- import java-util-Arrays- import ja.pdfplease help finish sorting methods- import java-util-Arrays- import ja.pdf
please help finish sorting methods- import java-util-Arrays- import ja.pdf
anfenterprises
 

Similar to Add an interactive command line to your C++ application (20)

2 BytesC++ course_2014_c3_ function basics&parameters and overloading
2 BytesC++ course_2014_c3_ function basics&parameters and overloading2 BytesC++ course_2014_c3_ function basics&parameters and overloading
2 BytesC++ course_2014_c3_ function basics&parameters and overloading
 
Getting started cpp full
Getting started cpp   fullGetting started cpp   full
Getting started cpp full
 
Introduction to typescript
Introduction to typescriptIntroduction to typescript
Introduction to typescript
 
Hands on Session on Python
Hands on Session on PythonHands on Session on Python
Hands on Session on Python
 
Modern C++ Concurrency API
Modern C++ Concurrency APIModern C++ Concurrency API
Modern C++ Concurrency API
 
System Calls.pptxnsjsnssbhsbbebdbdbshshsbshsbbs
System Calls.pptxnsjsnssbhsbbebdbdbshshsbshsbbsSystem Calls.pptxnsjsnssbhsbbebdbdbshshsbshsbbs
System Calls.pptxnsjsnssbhsbbebdbdbshshsbshsbbs
 
Степан Кольцов — Rust — лучше, чем C++
Степан Кольцов — Rust — лучше, чем C++Степан Кольцов — Rust — лучше, чем C++
Степан Кольцов — Rust — лучше, чем C++
 
Developer Experience i TypeScript. Najbardziej ikoniczne duo
Developer Experience i TypeScript. Najbardziej ikoniczne duoDeveloper Experience i TypeScript. Najbardziej ikoniczne duo
Developer Experience i TypeScript. Najbardziej ikoniczne duo
 
C++11
C++11C++11
C++11
 
Summary of C++17 features
Summary of C++17 featuresSummary of C++17 features
Summary of C++17 features
 
The Challenge of Bringing FEZ to PlayStation Platforms
The Challenge of Bringing FEZ to PlayStation PlatformsThe Challenge of Bringing FEZ to PlayStation Platforms
The Challenge of Bringing FEZ to PlayStation Platforms
 
TechTalk - Dotnet
TechTalk - DotnetTechTalk - Dotnet
TechTalk - Dotnet
 
CP 04.pptx
CP 04.pptxCP 04.pptx
CP 04.pptx
 
Fantom - Programming Language for JVM, CLR, and Javascript
Fantom - Programming Language for JVM, CLR, and JavascriptFantom - Programming Language for JVM, CLR, and Javascript
Fantom - Programming Language for JVM, CLR, and Javascript
 
Rust: код может быть одновременно безопасным и быстрым, Степан Кольцов
Rust: код может быть одновременно безопасным и быстрым, Степан КольцовRust: код может быть одновременно безопасным и быстрым, Степан Кольцов
Rust: код может быть одновременно безопасным и быстрым, Степан Кольцов
 
New Functional Features of Java 8
New Functional Features of Java 8New Functional Features of Java 8
New Functional Features of Java 8
 
Function
FunctionFunction
Function
 
Oop object oriented programing topics
Oop object oriented programing topicsOop object oriented programing topics
Oop object oriented programing topics
 
please help finish sorting methods- import java-util-Arrays- import ja.pdf
please help finish sorting methods- import java-util-Arrays- import ja.pdfplease help finish sorting methods- import java-util-Arrays- import ja.pdf
please help finish sorting methods- import java-util-Arrays- import ja.pdf
 
PyCon 2010 SQLAlchemy tutorial
PyCon 2010 SQLAlchemy tutorialPyCon 2010 SQLAlchemy tutorial
PyCon 2010 SQLAlchemy tutorial
 

Recently uploaded

%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...
%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...
%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...
masabamasaba
 
%+27788225528 love spells in Knoxville Psychic Readings, Attraction spells,Br...
%+27788225528 love spells in Knoxville Psychic Readings, Attraction spells,Br...%+27788225528 love spells in Knoxville Psychic Readings, Attraction spells,Br...
%+27788225528 love spells in Knoxville Psychic Readings, Attraction spells,Br...
masabamasaba
 

Recently uploaded (20)

WSO2CON 2024 - Building the API First Enterprise – Running an API Program, fr...
WSO2CON 2024 - Building the API First Enterprise – Running an API Program, fr...WSO2CON 2024 - Building the API First Enterprise – Running an API Program, fr...
WSO2CON 2024 - Building the API First Enterprise – Running an API Program, fr...
 
WSO2Con2024 - Hello Choreo Presentation - Kanchana
WSO2Con2024 - Hello Choreo Presentation - KanchanaWSO2Con2024 - Hello Choreo Presentation - Kanchana
WSO2Con2024 - Hello Choreo Presentation - Kanchana
 
WSO2CON 2024 - Navigating API Complexity: REST, GraphQL, gRPC, Websocket, Web...
WSO2CON 2024 - Navigating API Complexity: REST, GraphQL, gRPC, Websocket, Web...WSO2CON 2024 - Navigating API Complexity: REST, GraphQL, gRPC, Websocket, Web...
WSO2CON 2024 - Navigating API Complexity: REST, GraphQL, gRPC, Websocket, Web...
 
%in kempton park+277-882-255-28 abortion pills for sale in kempton park
%in kempton park+277-882-255-28 abortion pills for sale in kempton park %in kempton park+277-882-255-28 abortion pills for sale in kempton park
%in kempton park+277-882-255-28 abortion pills for sale in kempton park
 
%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...
%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...
%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...
 
%in tembisa+277-882-255-28 abortion pills for sale in tembisa
%in tembisa+277-882-255-28 abortion pills for sale in tembisa%in tembisa+277-882-255-28 abortion pills for sale in tembisa
%in tembisa+277-882-255-28 abortion pills for sale in tembisa
 
WSO2CON 2024 - How to Run a Security Program
WSO2CON 2024 - How to Run a Security ProgramWSO2CON 2024 - How to Run a Security Program
WSO2CON 2024 - How to Run a Security Program
 
AI & Machine Learning Presentation Template
AI & Machine Learning Presentation TemplateAI & Machine Learning Presentation Template
AI & Machine Learning Presentation Template
 
%+27788225528 love spells in Knoxville Psychic Readings, Attraction spells,Br...
%+27788225528 love spells in Knoxville Psychic Readings, Attraction spells,Br...%+27788225528 love spells in Knoxville Psychic Readings, Attraction spells,Br...
%+27788225528 love spells in Knoxville Psychic Readings, Attraction spells,Br...
 
Devoxx UK 2024 - Going serverless with Quarkus, GraalVM native images and AWS...
Devoxx UK 2024 - Going serverless with Quarkus, GraalVM native images and AWS...Devoxx UK 2024 - Going serverless with Quarkus, GraalVM native images and AWS...
Devoxx UK 2024 - Going serverless with Quarkus, GraalVM native images and AWS...
 
WSO2CON 2024 - API Management Usage at La Poste and Its Impact on Business an...
WSO2CON 2024 - API Management Usage at La Poste and Its Impact on Business an...WSO2CON 2024 - API Management Usage at La Poste and Its Impact on Business an...
WSO2CON 2024 - API Management Usage at La Poste and Its Impact on Business an...
 
WSO2CON 2024 Slides - Unlocking Value with AI
WSO2CON 2024 Slides - Unlocking Value with AIWSO2CON 2024 Slides - Unlocking Value with AI
WSO2CON 2024 Slides - Unlocking Value with AI
 
What Goes Wrong with Language Definitions and How to Improve the Situation
What Goes Wrong with Language Definitions and How to Improve the SituationWhat Goes Wrong with Language Definitions and How to Improve the Situation
What Goes Wrong with Language Definitions and How to Improve the Situation
 
WSO2CON2024 - It's time to go Platformless
WSO2CON2024 - It's time to go PlatformlessWSO2CON2024 - It's time to go Platformless
WSO2CON2024 - It's time to go Platformless
 
MarTech Trend 2024 Book : Marketing Technology Trends (2024 Edition) How Data...
MarTech Trend 2024 Book : Marketing Technology Trends (2024 Edition) How Data...MarTech Trend 2024 Book : Marketing Technology Trends (2024 Edition) How Data...
MarTech Trend 2024 Book : Marketing Technology Trends (2024 Edition) How Data...
 
tonesoftg
tonesoftgtonesoftg
tonesoftg
 
WSO2Con204 - Hard Rock Presentation - Keynote
WSO2Con204 - Hard Rock Presentation - KeynoteWSO2Con204 - Hard Rock Presentation - Keynote
WSO2Con204 - Hard Rock Presentation - Keynote
 
WSO2CON 2024 - WSO2's Digital Transformation Journey with Choreo: A Platforml...
WSO2CON 2024 - WSO2's Digital Transformation Journey with Choreo: A Platforml...WSO2CON 2024 - WSO2's Digital Transformation Journey with Choreo: A Platforml...
WSO2CON 2024 - WSO2's Digital Transformation Journey with Choreo: A Platforml...
 
Direct Style Effect Systems - The Print[A] Example - A Comprehension Aid
Direct Style Effect Systems -The Print[A] Example- A Comprehension AidDirect Style Effect Systems -The Print[A] Example- A Comprehension Aid
Direct Style Effect Systems - The Print[A] Example - A Comprehension Aid
 
WSO2Con2024 - From Blueprint to Brilliance: WSO2's Guide to API-First Enginee...
WSO2Con2024 - From Blueprint to Brilliance: WSO2's Guide to API-First Enginee...WSO2Con2024 - From Blueprint to Brilliance: WSO2's Guide to API-First Enginee...
WSO2Con2024 - From Blueprint to Brilliance: WSO2's Guide to API-First Enginee...
 

Add an interactive command line to your C++ application

  • 1. Add an interactive command line to your applications Daniele Pallastrelli, daniele77.github.io 13.01.2022, Italian C++
  • 2. My typical projects • Run for a long period of time • No classic interaction with the user (i.e., no GUI) • Run in a server, custom board, almost never desktop applications • They're not CPU bound applications
  • 3. Why an interactive console in your apps? • Need to have some sort of console to interact with my applications (e.g., embedded systems running around the clock) to: • monitor, • configure, • manage the system • E.g., CISCO routers • Traditional systems: SSH connections + logs
  • 4. Why an interactive console in your apps? • Debugging • Poke around internal state • Dump internal structures • Change the log level at runtime • Change the working mode • Enable / disable modules • Load / unload plugins • Early stage of development
  • 5. Reinventing the wheel? Existing solutions in open source domain: • Linux only • They're applications where you hook external programs to commands • No remote sessions • Few in C++ • None of them in "modern" C++
  • 6. Enter the shell (cli?) • My own library in C++14 • Production code quality • Used in several industrial projects • Demo time  • C++14 • Cross-platform (Linux and windows tested) • Menus and submenus • Command history (navigation with arrow keys) • Autocompletion (with TAB key) • Async interface • Colors
  • 7. Something missing… • Good when you start the app from a console (e.g. desktop applications or development stage) • What about processes that run in background (e.g., embedded, servers & c)?
  • 11.
  • 12. Features summary • C++14 • Header only • Cross-platform (linux and windows) • Menus and submenus • Remote sessions (telnet) • Persistent history (navigation with arrow keys) • Autocompletion (with TAB key) • Async interface • Colors
  • 13. auto rootMenu = make_unique<Menu>("cli"); rootMenu->Insert( "hello", [](std::ostream& out){ out << "Hello, worldn"; }, "Print hello world" ); rootMenu->Insert( "hello_everysession", [](std::ostream&){ Cli::cout() << "Hello, everybody" << std::endl; }, "Print hello everybody on all open sessions" ); rootMenu->Insert( "reverse", {"string_to_revert"}, [](std::ostream& out, const string& arg) { string copy(arg); std::reverse(copy.begin(), copy.end()); out << copy << "n"; }, "Print the reverse string" );
  • 14. rootMenu->Insert( "add", {"first_term", "second_term"}, [](std::ostream& out, int x, int y) { out << x << " + " << y << " = " << (x+y) << "n"; }, "Print the sum of the two numbers" ); rootMenu->Insert( "sort", {"list of strings separated by space"}, [](std::ostream& out, std::vector<std::string> data) { std::sort(data.begin(), data.end()); out << "sorted list: "; std::copy(data.begin(), data.end(), std::ostream_iterator<std::string>(out, " ")); out << "n"; }, "Alphabetically sort a list of words" );
  • 15. auto subMenu = make_unique<Menu>("sub"); subMenu->Insert( "demo", [](std::ostream& out){ out << "This is a sample!n"; }, "Print a demo string" ); rootMenu->Insert( std::move(subMenu) );
  • 16. // create a cli with the given root menu and a persistent storage Cli cli( std::move(rootMenu), std::make_unique<FileHistoryStorage>(".cli") ); // global exit action cli.ExitAction( [](auto& out){ out << "Goodbye and thanks for all the fish.n"; } ); // std exception custom handler cli.StdExceptionHandler( [](std::ostream& out, const std::string& cmd, const std::exception& e) { out << "Exception caught in cli handler: " << e.what() << " handling command: " << cmd << ".n"; } );
  • 17. LoopScheduler scheduler; CliLocalTerminalSession localSession(cli, scheduler, std::cout, 200); localSession.ExitAction( [&scheduler](auto& out) // session exit action { out << "Closing App...n"; scheduler.Stop(); } ); scheduler.Run();
  • 18. StandaloneAsioScheduler scheduler; CliLocalTerminalSession localSession(cli, scheduler, std::cout, 200); localSession.ExitAction( [&scheduler](auto& out) // session exit action { out << "Closing App...n"; scheduler.Stop(); } ); // setup server StandaloneAsioCliTelnetServer server(cli, scheduler, 5000); // exit action for all the connections server.ExitAction( [](auto& out) { out << "Terminating this session...n"; } ); scheduler.Run();
  • 19. How does it work
  • 20. Core idea • Command list • The user enters a string: command and parameters • Iteration over the command list: • Command name • Parameter number and type • The handler is called with the typed parameters
  • 22. class Command {}; class Menu : public Command { private: Menu* parent{ nullptr }; std::vector<Command*> cmds; }; template <typename F, typename ... Args> class VariadicFunctionCommand : public Command {};
  • 23. Menu is-a Command, because when you start the CLI, every Menu shows as a command you can digit at the prompt (e.g., if you define a Menu "foo", you get the command "foo" in the Cli to enter the submenu).
  • 24. auto rootMenu = make_unique<Menu>("cli"); rootMenu->Insert( "hello", [](std::ostream& out) { out << "Hello, worldn"; } ); rootMenu->Insert( "hello_everysession", [](std::ostream&) { Cli::cout() << "Hello, everybodyn"; } );
  • 25. auto subMenu = make_unique<Menu>("sub"); subMenu->Insert( "hello", [](std::ostream& out) { out << "Hello, submenu worldn"; } ); subMenu->Insert( "demo", [](std::ostream&) { out << "Demon"; } ); rootMenu->Insert( std::move(subMenu) );
  • 26. auto rootMenu = make_unique<Menu>("cli"); ... Cli cli( std::move(rootMenu) );
  • 27. auto rootMenu = make_unique<Menu>("cli"); ... Cli cli( std::move(rootMenu) ); CliLocalTerminalSession localSession(cli, scheduler, std::cout, 200); CliTelnetServer server(cli, scheduler, 5000);
  • 28. Cli cli( std::move(rootMenu) ); CliLocalTerminalSession localSession(cli, scheduler, std::cout, 200); CliTelnetServer server(cli, scheduler, 5000); CliFileSession fileSession(cli, infile, outfile); CliLocalTerminalSession localSession( scheduler, std::cout, 200); CliTelnetServer server(scheduler, 5000); CliFileSession fileSession(infile, outfile); Cli cli( std::move(rootMenu) ); cli.SetLocalSession( localSession ); cli.SetFileSession( fileSession ); cli.SetTelnetServer( server );
  • 29.
  • 30. rootMenu->Insert( "add", {"first_term", "second_term"}, [](std::ostream& out, int x, int y) { out << x << " + " << y << " = " << (x+y) << "n"; }, "Print the sum of two numbers" );
  • 31. rootMenu->Insert( "add", {"first_term", "second_term"}, [](std::ostream& out, int x, int y) { out << x << " + " << y << " = " << (x+y) << "n"; }, "Print the sum of two numbers" );
  • 32. Interlude – How do you get the type of a lambda argument? template <typename F> void foo(F f) { // what's the type of "f" first parameter? // something like: // using T = F::first_parameter_type ) foo( [](int){} );
  • 33. Interlude – How do you get the type of a lambda argument? template<typename F, typename Ret, typename A, typename... Rest> A helper(Ret (F::*)(A, Rest...) const); template <typename F> void foo(F f) { using T = decltype( helper(&F::operator()) ); } foo( [](int){} );
  • 34. class Menu : public Command { public: template <typename F> CmdHandler Insert(const string& cmdName, F f) { return CreateCmd(cmdName, f, &F::operator()); } private: template <typename F, typename R, typename ... Args> CmdHandler CreateCmd(const string& cmdName, F& f, R (F::*)(Args...) const) { auto cmd = make_unique< VariadicFunctionCommand< F, Args ... > >(cmdName, f); // insert cmd into this menu commands ... } }; Works with lambdas and std::function
  • 35. class Menu : public Command { public: ... template <typename R, typename ... Args> CmdHandler Insert(const std::string& cmdName, R (*f)(Args...)) { using F = R (*)(Args...); auto cmd = make_unique<VariadicFunctionCommand<F, Args ...>>(cmdName, f); // insert cmd into this menu commands ... } ... }; Overload for free- functions
  • 36. template <typename F, typename ... Args> class VariadicFunctionCommand : public Command { public: VariadicFunctionCommand(const std::string& _name, F fun) : Command(_name), func(std::move(fun)) {} bool Exec(const vector<string>& cmdLine) override { ... try { Select<Args...>::Exec(func, std::next(cmdLine.begin()), cmdLine.end()); } catch (std::bad_cast&) { return false; } return true; ... } };
  • 37. template <typename ... Args> struct Select; template <typename P, typename ... Args> struct Select<P, Args...> { template <typename F, typename InputIt> static void Exec(const F& f, InputIt first, InputIt last) { assert( first != last ); assert( std::distance(first, last) == 1+sizeof...(Args) ); const P firstPar = detail::from_string<typename std::decay<P>::type>(*first); auto g = [&](auto ... pars){ f(firstPar, pars...); }; Select<Args...>::Exec(g, std::next(first), last); } }; template <> struct Select<> { template <typename F, typename InputIt> static void Exec(const F& f, InputIt first, InputIt last) { assert(first == last); f(); } };
  • 38. Interlude How do you manage concurrency?
  • 39. Concurrency in my projects • Single thread (when possible), using Proactor pattern. • When I must: multiple threads, using Proactor pattern 
  • 40. By the way… …what's the PROACTOR pattern?
  • 41. The proactor pattern: Concurrency Without Threads
  • 42. The Proactor solution Split every application service into: • Long-duration operations. execute asynchronously • Completion handlers. processes the results of the associated asynchronous operations (potentially invoking additional asynchronous operations).
  • 43.
  • 44.
  • 47.
  • 48. Concurrency in my projects • asio for asynchonous I/O (timers, too) • asio::io_context available • When I/O is ready, asio puts handler in the asio::io_context • If more threads are needed (e.g., time consuming computations or blocking I/O), the result is put in asio::io_context • => everything runs in (my) single thread of execution
  • 49. Back to the library  • Input coming from: • Keyboard, using blocking primitives (std::getchar() and _getch()) • Sockets • Commands callbacks (in which thread?) • => proactor (asio::io_context)
  • 50. Consequences • The user must instantiate a boost::asio::io_context object • The whole library depends on boost::asio
  • 51. Concurrency summary (v. 1.0) • The library depends on boost::asio, even when you don't need the telnet server • Library users ask to use standalone asio instead of boost::asio • The truth is that the whole library depends on boost::asio because: • Telnet server needs it • Keyboard handler needs an event handler • The two must use the same event manager, i.e. boost::asio::io_context
  • 52. Release 2.0 – Goals: • Optionally use standalone asio instead of boost::asio for the telnet server • Remove all dependencies if the telnet server is not needed
  • 53. Release 2.0 – The solution • New abstraction: "Scheduler" • schedules async event • The library provides three kind of schedulers: • StandaloneAsioScheduler (based on asio::io_context) • BoostAsioScheduler (based on boost::asio::io_context) • LoopScheduler (hand made sync queue)
  • 54. Release 2.0 – The solution • Cli library can use all the schedulers, but if you need the telnet server you must use *AsioScheduler • Bottom line: • If ( you need telnet server OR your app already uses [boost::]asio::io_context => StandaloneAsioScheduler or BoostAsioScheduler • Else => LoopScheduler (no external dependencies)
  • 55. Release 2.0 – Standalone Asio #include <cli/standaloneasioscheduler.h> #include <cli/standaloneasioremotecli.h> ... StandaloneAsioScheduler scheduler; // setup local session CliLocalTerminalSession localSession(cli, scheduler, std::cout, 200); // setup server StandaloneAsioCliTelnetServer server(cli, scheduler, 5000); // start event loop scheduler.Run();
  • 56. Release 2.0 – Boost Asio #include <cli/boostasioscheduler.h> #include <cli/boostasioremotecli.h> ... BoostAsioScheduler scheduler; // setup local session CliLocalTerminalSession localSession(cli, scheduler, std::cout, 200); // setup server BoostAsioCliTelnetServer server(cli, scheduler, 5000); // start event loop scheduler.Run();
  • 57. Release 2.0 – No Asio #include <cli/loopscheduler.h> ... LoopScheduler scheduler; // setup local session CliLocalTerminalSession localSession(cli, scheduler, std::cout, 200); // start event loop scheduler.Run();
  • 59. Miles to go… • User-defined arguments (and completion for them) • More (bash-like) shortcuts • Conan support • Rename the library (?)
  • 60. Library core: design decisions • Reflection (?) • Preprocessors (???) • "Composite" Design Pattern VS Template metaprogramming
  • 61. Intentional architecture VS emergent design "Many times, thinking things out in advance saved us serious development headaches later on. ... [on making a particular specification change] ... Making this change in the spec took an hour or two. If we had made this change in code, it would have added weeks to the schedule. I can’t tell you how strongly I believe in Big Design Up Front, which the proponents of Extreme Programming consider anathema. I have consistently saved time and made better products by using BDUF and I’m proud to use it, no matter what the XP fanatics claim. They’re just wrong on this point and I can’t be any clearer than that." -- Joel Spolsky "The project Aardwark Spec" – Joel On Software
  • 62. Take away • Use the right technique • Use Cli library  (when you need it) • Use Proactor pattern (when you need it) • Use your brain (always)
  • 63. References Me: daniele.pallastrelli@gmail.com Me: @DPallastrelli Github: http://github.com/daniele77 Web: daniele77.github.io 63