2 * Copyright (C) 2016 Canonical, Ltd.
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation; version 3.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Lesser General Public License for more details.
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 * Authored by: Thomas Voß <thomas.voss@canonical.com>
20 #include <boost/format.hpp>
21 #include <boost/program_options.hpp>
23 #include "anbox/cli.h"
25 namespace cli = anbox::cli;
26 namespace po = boost::program_options;
30 static constexpr const char* help_for_command_with_subcommands =
35 " %3% [command options] [arguments...]";
37 static constexpr const char* commands = "COMMANDS:";
38 static constexpr const char* command = " %1% %2%";
40 static constexpr const char* options = "OPTIONS:";
41 static constexpr const char* option = " --%1% %2%";
44 void add_to_desc_for_flags(po::options_description& desc,
45 const std::set<cli::Flag::Ptr>& flags) {
46 for (auto flag : flags) {
47 po::value_semantic *spec = nullptr;
48 flag->specify_option(spec);
50 desc.add_options()(flag->name().as_string().c_str(), spec,
51 flag->description().as_string().c_str());
56 std::vector<std::string> cli::args(int argc, char** argv) {
57 std::vector<std::string> result;
58 for (int i = 1; i < argc; i++) result.push_back(argv[i]);
62 const cli::Name& cli::Flag::name() const { return name_; }
64 const cli::Description& cli::Flag::description() const { return description_; }
66 cli::Flag::Flag(const Name& name, const Description& description)
67 : name_{name}, description_{description} {}
69 cli::Command::FlagsWithInvalidValue::FlagsWithInvalidValue()
70 : std::runtime_error{"Flags with invalid value"} {}
72 cli::Command::FlagsMissing::FlagsMissing()
73 : std::runtime_error{"Flags are missing in command invocation"} {}
75 cli::Name cli::Command::name() const { return name_; }
77 cli::Usage cli::Command::usage() const { return usage_; }
79 cli::Description cli::Command::description() const { return description_; }
81 bool cli::Command::hidden() const { return hidden_; }
83 cli::Command::Command(const cli::Name& name, const cli::Usage& usage,
84 const cli::Description& description, bool hidden)
85 : name_(name), usage_(usage), description_(description), hidden_(hidden) {}
87 cli::CommandWithSubcommands::CommandWithSubcommands(
88 const Name& name, const Usage& usage, const Description& description)
89 : Command{name, usage, description} {
90 command(std::make_shared<cmd::Help>(*this));
93 cli::CommandWithSubcommands& cli::CommandWithSubcommands::command(
94 const Command::Ptr& command) {
95 commands_[command->name().as_string()] = command;
99 cli::CommandWithSubcommands& cli::CommandWithSubcommands::flag(
100 const Flag::Ptr& flag) {
105 void cli::CommandWithSubcommands::help(std::ostream& out) {
106 out << boost::format(pattern::help_for_command_with_subcommands) %
107 name().as_string() % usage().as_string() % name().as_string()
110 if (flags_.size() > 0) {
112 << pattern::options << std::endl;
113 for (const auto& flag : flags_)
114 out << boost::format(pattern::option) % flag->name() % flag->description()
118 if (commands_.size() > 0) {
120 << pattern::commands << std::endl;
121 for (const auto& cmd : commands_) {
122 if (cmd.second && !cmd.second->hidden())
123 out << boost::format(pattern::command) % cmd.second->name() %
124 cmd.second->description()
130 int cli::CommandWithSubcommands::run(const cli::Command::Context& ctxt) {
131 po::positional_options_description pdesc;
132 pdesc.add("command", 1);
134 po::options_description desc("Options");
135 desc.add_options()("command", po::value<std::string>()->required(),
136 "the command to be executed");
138 add_to_desc_for_flags(desc, flags_);
141 po::variables_map vm;
142 auto parsed = po::command_line_parser(ctxt.args)
145 .style(po::command_line_style::unix_style)
146 .allow_unregistered()
149 po::store(parsed, vm);
152 auto cmd = commands_[vm["command"].as<std::string>()];
154 ctxt.cout << "Unknown command '" << vm["command"].as<std::string>() << "'"
160 return cmd->run(cli::Command::Context{
162 po::collect_unrecognized(parsed.options, po::include_positional)});
163 } catch (const po::error& e) {
164 ctxt.cout << e.what() << std::endl;
172 cli::CommandWithFlagsAndAction::CommandWithFlagsAndAction(
173 const Name& name, const Usage& usage, const Description& description, bool hidden)
174 : Command{name, usage, description, hidden} {}
176 cli::CommandWithFlagsAndAction& cli::CommandWithFlagsAndAction::flag(
177 const Flag::Ptr& flag) {
182 cli::CommandWithFlagsAndAction& cli::CommandWithFlagsAndAction::action(
183 const Action& action) {
188 int cli::CommandWithFlagsAndAction::run(const Context& ctxt) {
189 po::options_description cd(name().as_string());
191 bool help_requested{false};
192 cd.add_options()("help", po::bool_switch(&help_requested),
193 "produces a help message");
195 add_to_desc_for_flags(cd, flags_);
198 po::variables_map vm;
199 auto parsed = po::command_line_parser(ctxt.args)
201 .style(po::command_line_style::unix_style)
202 .allow_unregistered()
204 po::store(parsed, vm);
207 if (help_requested) {
212 return action_(cli::Command::Context{
214 po::collect_unrecognized(parsed.options, po::include_positional)});
215 } catch (const po::error& e) {
216 ctxt.cout << e.what() << std::endl;
224 void cli::CommandWithFlagsAndAction::help(std::ostream& out) {
225 out << boost::format(pattern::help_for_command_with_subcommands) %
226 name().as_string() % description().as_string() % name().as_string()
229 if (flags_.size() > 0) {
231 << boost::format(pattern::options) << std::endl;
232 for (const auto& flag : flags_)
233 out << boost::format(pattern::option) % flag->name() % flag->description()
238 cli::cmd::Help::Help(Command& cmd)
239 : Command{cli::Name{"help"}, cli::Usage{"Print a short help message"},
240 cli::Description{"Print a short help message"}},
244 int cli::cmd::Help::run(const Context& context) {
245 command.help(context.cout);
249 void cli::cmd::Help::help(std::ostream& out) { command.help(out); }