Last active
July 8, 2020 20:09
-
-
Save caiorss/1b6826b9722ba07667eefed2f82d667e to your computer and use it in GitHub Desktop.
Sample multi-command CLI - command line application
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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