/*
* Copyright (C) 2016 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see .
*
* Authored by: Thomas Voß
*
*/
#include
#include
#include "anbox/cli.h"
namespace cli = anbox::cli;
namespace po = boost::program_options;
namespace {
namespace pattern {
static constexpr const char* help_for_command_with_subcommands =
"NAME:\n"
" %1% - %2%\n"
"\n"
"USAGE:\n"
" %3% [command options] [arguments...]";
static constexpr const char* commands = "COMMANDS:";
static constexpr const char* command = " %1% %2%";
static constexpr const char* options = "OPTIONS:";
static constexpr const char* option = " --%1% %2%";
}
void add_to_desc_for_flags(po::options_description& desc,
const std::set& flags) {
for (auto flag : flags) {
po::value_semantic *spec = nullptr;
flag->specify_option(spec);
if (!spec) continue;
desc.add_options()(flag->name().as_string().c_str(), spec,
flag->description().as_string().c_str());
}
}
}
std::vector cli::args(int argc, char** argv) {
std::vector result;
for (int i = 1; i < argc; i++) result.push_back(argv[i]);
return result;
}
const cli::Name& cli::Flag::name() const { return name_; }
const cli::Description& cli::Flag::description() const { return description_; }
cli::Flag::Flag(const Name& name, const Description& description)
: name_{name}, description_{description} {}
cli::Command::FlagsWithInvalidValue::FlagsWithInvalidValue()
: std::runtime_error{"Flags with invalid value"} {}
cli::Command::FlagsMissing::FlagsMissing()
: std::runtime_error{"Flags are missing in command invocation"} {}
cli::Name cli::Command::name() const { return name_; }
cli::Usage cli::Command::usage() const { return usage_; }
cli::Description cli::Command::description() const { return description_; }
bool cli::Command::hidden() const { return hidden_; }
cli::Command::Command(const cli::Name& name, const cli::Usage& usage,
const cli::Description& description, bool hidden)
: name_(name), usage_(usage), description_(description), hidden_(hidden) {}
cli::CommandWithSubcommands::CommandWithSubcommands(
const Name& name, const Usage& usage, const Description& description)
: Command{name, usage, description} {
command(std::make_shared(*this));
}
cli::CommandWithSubcommands& cli::CommandWithSubcommands::command(
const Command::Ptr& command) {
commands_[command->name().as_string()] = command;
return *this;
}
cli::CommandWithSubcommands& cli::CommandWithSubcommands::flag(
const Flag::Ptr& flag) {
flags_.insert(flag);
return *this;
}
void cli::CommandWithSubcommands::help(std::ostream& out) {
out << boost::format(pattern::help_for_command_with_subcommands) %
name().as_string() % usage().as_string() % name().as_string()
<< std::endl;
if (flags_.size() > 0) {
out << std::endl
<< pattern::options << std::endl;
for (const auto& flag : flags_)
out << boost::format(pattern::option) % flag->name() % flag->description()
<< std::endl;
}
if (commands_.size() > 0) {
out << std::endl
<< pattern::commands << std::endl;
for (const auto& cmd : commands_) {
if (cmd.second && !cmd.second->hidden())
out << boost::format(pattern::command) % cmd.second->name() %
cmd.second->description()
<< std::endl;
}
}
}
int cli::CommandWithSubcommands::run(const cli::Command::Context& ctxt) {
po::positional_options_description pdesc;
pdesc.add("command", 1);
po::options_description desc("Options");
desc.add_options()("command", po::value()->required(),
"the command to be executed");
add_to_desc_for_flags(desc, flags_);
try {
po::variables_map vm;
auto parsed = po::command_line_parser(ctxt.args)
.options(desc)
.positional(pdesc)
.style(po::command_line_style::unix_style)
.allow_unregistered()
.run();
po::store(parsed, vm);
po::notify(vm);
auto cmd = commands_[vm["command"].as()];
if (!cmd) {
ctxt.cout << "Unknown command '" << vm["command"].as() << "'"
<< std::endl;
help(ctxt.cout);
return EXIT_FAILURE;
}
return cmd->run(cli::Command::Context{
ctxt.cin, ctxt.cout,
po::collect_unrecognized(parsed.options, po::include_positional)});
} catch (const po::error& e) {
ctxt.cout << e.what() << std::endl;
help(ctxt.cout);
return EXIT_FAILURE;
}
return EXIT_FAILURE;
}
cli::CommandWithFlagsAndAction::CommandWithFlagsAndAction(
const Name& name, const Usage& usage, const Description& description, bool hidden)
: Command{name, usage, description, hidden} {}
cli::CommandWithFlagsAndAction& cli::CommandWithFlagsAndAction::flag(
const Flag::Ptr& flag) {
flags_.insert(flag);
return *this;
}
cli::CommandWithFlagsAndAction& cli::CommandWithFlagsAndAction::action(
const Action& action) {
action_ = action;
return *this;
}
int cli::CommandWithFlagsAndAction::run(const Context& ctxt) {
po::options_description cd(name().as_string());
bool help_requested{false};
cd.add_options()("help", po::bool_switch(&help_requested),
"produces a help message");
add_to_desc_for_flags(cd, flags_);
try {
po::variables_map vm;
auto parsed = po::command_line_parser(ctxt.args)
.options(cd)
.style(po::command_line_style::unix_style)
.allow_unregistered()
.run();
po::store(parsed, vm);
po::notify(vm);
if (help_requested) {
help(ctxt.cout);
return EXIT_SUCCESS;
}
return action_(cli::Command::Context{
ctxt.cin, ctxt.cout,
po::collect_unrecognized(parsed.options, po::include_positional)});
} catch (const po::error& e) {
ctxt.cout << e.what() << std::endl;
help(ctxt.cout);
return EXIT_FAILURE;
}
return EXIT_FAILURE;
}
void cli::CommandWithFlagsAndAction::help(std::ostream& out) {
out << boost::format(pattern::help_for_command_with_subcommands) %
name().as_string() % description().as_string() % name().as_string()
<< std::endl;
if (flags_.size() > 0) {
out << std::endl
<< boost::format(pattern::options) << std::endl;
for (const auto& flag : flags_)
out << boost::format(pattern::option) % flag->name() % flag->description()
<< std::endl;
}
}
cli::cmd::Help::Help(Command& cmd)
: Command{cli::Name{"help"}, cli::Usage{"Print a short help message"},
cli::Description{"Print a short help message"}},
command{cmd} {}
// From Command
int cli::cmd::Help::run(const Context& context) {
command.help(context.cout);
return EXIT_FAILURE;
}
void cli::cmd::Help::help(std::ostream& out) { command.help(out); }