Skip to content

Instantly share code, notes, and snippets.

@caiorss
Last active July 8, 2020 20:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save caiorss/1b6826b9722ba07667eefed2f82d667e to your computer and use it in GitHub Desktop.
Save caiorss/1b6826b9722ba07667eefed2f82d667e to your computer and use it in GitHub Desktop.
Sample multi-command CLI - command line application
#include <iostream>
#include <string>
#include <vector>
#include <functional>
#include <filesystem>
#include <functional>
#include <CLI/CLI.hpp>
using namespace std::string_literals;
namespace fs = std::filesystem;
struct Command_list
{
std::string m_path = ".";
bool m_filter_file = false;
bool m_filter_dir = false;
bool m_recursive = false;
Command_list(CLI::App& app, std::string name)
{
this->install(app, name);
}
void install(CLI::App& app, std::string name)
{
CLI::App* cmd = app.add_subcommand(
name, "List directory"
);
cmd->callback([this](){
try {
this->list_directory();
// Status code 0 => OK finished successfuly
return true;
} catch(std::exception const& ex) {
std::cout << " Error: " << ex.what() << '\n';
return false;
}
});
cmd->add_option("<PATH>", m_path, "Directory to be listed." )->required();
cmd->add_flag("-f,--file", m_filter_file, "Only list files");
cmd->add_flag("-d,--directory", m_filter_dir, "Only list directories");
cmd->add_flag("-r,--recursive", m_recursive, "List direcotyr in recursive way");
}
private:
void list_directory()
{
if(m_filter_file && m_filter_dir)
throw std::runtime_error("Error: --file and --dir flags cannot be simultaneously true.");
using Predfun = std::function<bool (fs::path const&)>;
auto predicate = Predfun{};
auto iterate_dir = [&predicate](auto&& iterable){
for(auto const& path : iterable )
{
if(predicate(path)) std::printf(" => %s\n", path.path().c_str());
}
};
using ptr = bool (*) (fs::path const&);
if(!m_filter_file && !m_filter_dir)
predicate = [](fs::path const& p){ return true; };
if(m_filter_file)
predicate = (ptr) &fs::is_regular_file;
if(m_filter_dir)
predicate = (ptr) &fs::is_directory;
if(!m_recursive)
iterate_dir( fs::directory_iterator(m_path) );
else
iterate_dir( fs::recursive_directory_iterator(m_path) );
}
};
class MiniWebServer
{
int m_port = 8080;
std::string m_host = "0.0.0.0";
std::string m_path = ".";
bool m_auth = false;
public:
// Set server TCP Port
void set_port(int port ){ m_port = port; }
// Hostnames that server will listen to
void set_host(std::string host ){ m_host = host; }
// Set path containing server data
void set_path(std::string path ){ m_path = path; }
void set_auth(bool flag ){ m_auth = flag; }
int get_port(){ return m_port; }
void run_server()
{
std::printf(" [INFO] Running web server => port = %d ; host = %s; path = %s \n"
, m_port, m_host.c_str(), m_path.c_str());
std::printf(" [INFO] Server has authentication => %s \n", m_auth ? "TRUE" : "FALSE" );
}
};
template<typename T, typename Klass>
auto bind_setter(Klass& cls, void (Klass::* setter) (T&&) )
{
return [&](T&& q){
(cls.*setter)( std::forward(q) );
};
}
template<typename T, typename Klass>
auto bind_setter(std::shared_ptr<Klass> const& cls, void (Klass::* setter) (T) )
{
return [cls, setter](T q){ ((*cls).*setter)(q); };
}
void command_server( CLI::App& app)
{
CLI::App* cmd = app.add_subcommand(
"server", "Run HTTP server for sharing files from some folder."
);
cmd->footer("Run local file sharing web server");
// Created with shared_ptr in order to the object survive this scope
// and avoid reference to destroyed object when it is referenced from callback.
auto server = std::make_shared<MiniWebServer>();
cmd->callback([=]
{
std::cout << " [TRACE] ----- Callback invoked OK ------ \n";
if(server->get_port() < 0 || server->get_port() > 65535)
{
std::cerr << " [ERROR] " << " Invalid TCP port range. " << '\n';
// Returns non-zero status code
return false;
}
server->run_server();
return true;
});
cmd->add_option_function<std::string>(
"<PATH>"
, bind_setter<std::string>(server, &MiniWebServer::set_path)
, "Directory to be shared in the web server."
)->required();
cmd->add_option_function<int>(
"-p,--port"
, [=](auto q){ server->set_port(q); }
, "Server TCP port, default: 8080"
);
cmd->add_option_function<std::string>(
"--host"
, [=](auto q){ server->set_host(q); }
, "Hostname that server will listen to (default: 0.0.0.0)"
);
cmd->add_flag(
"-a,--auth"
// , [](auto q){ server->set_auth(q); }
, bind_setter<bool>(server, &MiniWebServer::set_auth)
, "Shows the command line parameters passed to QEMU."
);
}
void command_version( CLI::App& app, std::string name)
{
auto cmd = app.add_subcommand(name, "Show application version.");
cmd->callback([=]{
std::printf("cliapp version 0.1 - Your favorite U-NIX swiss army knife \n");
// Return true for idnicating successful status code
return true;
});
}
int main(int argc, char** argv)
{
CLI::App app("cliapp");
app.footer("\n Command line demo toolbox.");
command_version(app, "version");
command_version(app, "v");
Command_list cmd_list1(app, "ls");
Command_list cmd_list2(app, "list");
command_server(app);
// ----------- Parse Arguments ---------------//
try
{
if(argc == 1){
std::cout << app.help() << "\n";
return 0;
}
app.require_subcommand();
app.validate_positionals();
app.parse(argc, argv);
} catch(const CLI::ParseError &e)
{
return app.exit(e);
} catch (std::exception& ex)
{
std::cout << ex.what() << std::endl;
return EXIT_FAILURE;
}
return 0;
}
cmake_minimum_required(VERSION 3.9)
project(cliapp)
#========== Global Configurations =============#
#----------------------------------------------#
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_VERBOSE_MAKEFILE ON)
set(CMAKE_CXX_EXTENSIONS OFF)
# ------------ Download CPM CMake Script ----------------#
## Automatically donwload and use module CPM.cmake
file(DOWNLOAD https://raw.githubusercontent.com/TheLartians/CPM.cmake/v0.26.2/cmake/CPM.cmake
"${CMAKE_BINARY_DIR}/CPM.cmake")
include("${CMAKE_BINARY_DIR}/CPM.cmake")
#----------- Add dependencies --------------------------#
CPMAddPackage(
NAME cli11
URL https://github.com/CLIUtils/CLI11/archive/v1.9.0.zip
DOWNLOAD_ONLY YES
)
include_directories( ${cli11_SOURCE_DIR}/include )
message([TRACE] " cli11_SOURCE_DIR = ${cli11_SOURCE_DIR} ")
#----------- Set targets -------------------------------#
add_executable(cliapp cliapp.cpp)
target_link_libraries(cliapp stdc++fs)
install( TARGETS cliapp
RUNTIME DESTINATION bin)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment