Merge lp:~thomas-voss/biometryd/restructure-cli into lp:biometryd

Proposed by Thomas Voß
Status: Merged
Approved by: Thomas Voß
Approved revision: 10
Merged at revision: 10
Proposed branch: lp:~thomas-voss/biometryd/restructure-cli
Merge into: lp:biometryd
Diff against target: 1263 lines (+554/-432)
19 files modified
src/biometry/CMakeLists.txt (+0/-2)
src/biometry/cmds/enroll.cpp (+8/-16)
src/biometry/cmds/enroll.h (+1/-1)
src/biometry/cmds/help.cpp (+0/-40)
src/biometry/cmds/help.h (+0/-47)
src/biometry/cmds/identify.cpp (+7/-7)
src/biometry/cmds/identify.h (+1/-1)
src/biometry/cmds/list_devices.cpp (+8/-7)
src/biometry/cmds/list_devices.h (+1/-1)
src/biometry/cmds/run.cpp (+4/-6)
src/biometry/cmds/run.h (+1/-1)
src/biometry/cmds/version.cpp (+8/-7)
src/biometry/cmds/version.h (+1/-1)
src/biometry/daemon.cpp (+10/-99)
src/biometry/daemon.h (+1/-4)
src/biometry/daemon_main.cpp (+5/-4)
src/biometry/util/cli.cpp (+240/-25)
src/biometry/util/cli.h (+251/-156)
tests/test_daemon.cpp (+7/-7)
To merge this branch: bzr merge lp:~thomas-voss/biometryd/restructure-cli
Reviewer Review Type Date Requested Status
Ubuntu Phablet Team Pending
Review via email: mp+294229@code.launchpad.net

Commit message

Alter cli::* interface and provide a cli::Command::Context to run invocations.
Restructure setup of default help options and commands.

Description of the change

Alter cli::* interface and provide a cli::Command::Context to run invocations.
Restructure setup of default help options and commands.

To post a comment you must log in.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'src/biometry/CMakeLists.txt'
--- src/biometry/CMakeLists.txt 2016-05-09 14:37:52 +0000
+++ src/biometry/CMakeLists.txt 2016-05-10 11:29:00 +0000
@@ -37,8 +37,6 @@
37 cmds/identify.cpp37 cmds/identify.cpp
38 cmds/list_devices.h38 cmds/list_devices.h
39 cmds/list_devices.cpp39 cmds/list_devices.cpp
40 cmds/help.h
41 cmds/help.cpp
42 cmds/run.h40 cmds/run.h
43 cmds/run.cpp41 cmds/run.cpp
44 cmds/version.h42 cmds/version.h
4543
=== modified file 'src/biometry/cmds/enroll.cpp'
--- src/biometry/cmds/enroll.cpp 2016-05-09 14:37:52 +0000
+++ src/biometry/cmds/enroll.cpp 2016-05-10 11:29:00 +0000
@@ -35,26 +35,18 @@
35namespace cli = biometry::util::cli;35namespace cli = biometry::util::cli;
3636
37biometry::cmds::Enroll::Enroll()37biometry::cmds::Enroll::Enroll()
38 : Command38 : CommandWithFlagsAndAction{cli::Name{"enroll"}, cli::Usage{"enroll"}, cli::Description{"enrolls a new template to a device"}},
39 {
40 {
41 Name{"enroll"},
42 Usage{"enroll"},
43 Description{"enrolls a new template to a device"},
44 {}
45 }
46 },
47 user(biometry::User::current())39 user(biometry::User::current())
48{40{
49 mutable_info().flags.push_back(cli::make_flag(Name{"device"}, Description{"The device to enroll to"}, device));41 flag(cli::make_flag(cli::Name{"device"}, cli::Description{"The device to enroll to"}, device));
50 mutable_info().flags.push_back(cli::make_flag(Name{"device"}, Description{"The device to enroll to"}, device));42 flag(cli::make_flag(cli::Name{"device"}, cli::Description{"The device to enroll to"}, device));
51 mutable_info().flags.push_back(cli::make_flag(Name{"user"}, Description{"The user to enroll for"}, device));43 flag(cli::make_flag(cli::Name{"user"}, cli::Description{"The user to enroll for"}, device));
5244
53 mutable_run() = [this]()45 action([this](const cli::Command::Context& ctxt)
54 {46 {
55 if (device.empty())47 if (device.empty())
56 {48 {
57 std::cout << "You must specify a device for enrolling a template" << std::endl;49 ctxt.cout << "You must specify a device for enrolling a template" << std::endl;
58 return EXIT_FAILURE;50 return EXIT_FAILURE;
59 }51 }
6052
@@ -71,10 +63,10 @@
7163
72 auto op = device->template_store().enroll(biometry::Application::system(), user);64 auto op = device->template_store().enroll(biometry::Application::system(), user);
7365
74 std::cout << "Starting template enrollment for " << user << " to " << descriptor->name() << std::endl;66 ctxt.cout << "Starting template enrollment for " << user << " to " << descriptor->name() << std::endl;
7567
76 op->start_with_observer(std::make_shared<TracingObserver<biometry::TemplateStore::Enrollment>>());68 op->start_with_observer(std::make_shared<TracingObserver<biometry::TemplateStore::Enrollment>>());
7769
78 return 0;70 return 0;
79 };71 });
80}72}
8173
=== modified file 'src/biometry/cmds/enroll.h'
--- src/biometry/cmds/enroll.h 2016-05-09 14:37:52 +0000
+++ src/biometry/cmds/enroll.h 2016-05-10 11:29:00 +0000
@@ -36,7 +36,7 @@
36namespace cmds36namespace cmds
37{37{
38/// @brief Enroll requests enrollment of a new template to a biometric device.38/// @brief Enroll requests enrollment of a new template to a biometric device.
39class Enroll : public util::cli::Command39class Enroll : public util::cli::CommandWithFlagsAndAction
40{40{
41public:41public:
42 /// @brief Enroll creates a new instance, initializing flags to default values.42 /// @brief Enroll creates a new instance, initializing flags to default values.
4343
=== removed file 'src/biometry/cmds/help.cpp'
--- src/biometry/cmds/help.cpp 2016-05-09 14:37:52 +0000
+++ src/biometry/cmds/help.cpp 1970-01-01 00:00:00 +0000
@@ -1,40 +0,0 @@
1/*
2 * Copyright (C) 2016 Canonical, Ltd.
3 *
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.
7 *
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.
12 *
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/>.
15 *
16 * Authored by: Thomas Voß <thomas.voss@canonical.com>
17 *
18 */
19
20#include <biometry/cmds/help.h>
21
22namespace cli = biometry::util::cli;
23
24biometry::cmds::Help::Help(const CommandEnumerator& enumerator)
25 : Command{{Name{"help"}, Usage{"help"}, Description{"print a help message and exit"}, {}}, [this]()
26 {
27 std::cout << "Usage: biometryd [COMMAND] \n"
28 "\n"
29 "biometryd mediates access to biometric devices. \n"
30 "\n"
31 "Commands:\n";
32 Help::enumerator([](const Command::Ptr& command)
33 {
34 std::cout << " " << command->info().name << " " << command->info().description << std::endl;
35 });
36 return EXIT_FAILURE;
37 }},
38 enumerator{enumerator}
39{
40}
410
=== removed file 'src/biometry/cmds/help.h'
--- src/biometry/cmds/help.h 2016-05-09 14:37:52 +0000
+++ src/biometry/cmds/help.h 1970-01-01 00:00:00 +0000
@@ -1,47 +0,0 @@
1/*
2 * Copyright (C) 2016 Canonical, Ltd.
3 *
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.
7 *
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.
12 *
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/>.
15 *
16 * Authored by: Thomas Voß <thomas.voss@canonical.com>
17 *
18 */
19
20#ifndef BIOMETRYD_CMDS_HELP_H_
21#define BIOMETRYD_CMDS_HELP_H_
22
23#include <biometry/util/cli.h>
24
25#include <functional>
26#include <iostream>
27#include <memory>
28
29namespace biometry
30{
31namespace cmds
32{
33class Help : public util::cli::Command
34{
35public:
36 typedef std::function<void(const Command::Ptr&)> Enumerator;
37 typedef std::function<void(const Enumerator&)> CommandEnumerator;
38
39 Help(const CommandEnumerator& enumerator);
40
41private:
42 CommandEnumerator enumerator;
43};
44}
45}
46
47#endif // BIOMETRYD_CMDS_HELP_H_
480
=== modified file 'src/biometry/cmds/identify.cpp'
--- src/biometry/cmds/identify.cpp 2016-05-09 14:37:52 +0000
+++ src/biometry/cmds/identify.cpp 2016-05-10 11:29:00 +0000
@@ -37,15 +37,15 @@
37namespace cli = biometry::util::cli;37namespace cli = biometry::util::cli;
3838
39biometry::cmds::Identify::Identify()39biometry::cmds::Identify::Identify()
40 : Command{{Name{"identify"}, Usage{"identify"}, Description{"tries to identify the user holding the device"}, {}}}40 : CommandWithFlagsAndAction{cli::Name{"identify"}, cli::Usage{"identify"}, cli::Description{"tries to identify the user holding the device"}}
41{41{
42 mutable_info().flags.push_back(cli::make_flag(Name{"device"}, Description{"The device to enroll to"}, device));42 flag(cli::make_flag(cli::Name{"device"}, cli::Description{"The device to enroll to"}, device));
43 mutable_info().flags.push_back(cli::make_flag(Command::Name{"config"}, Command::Description{"The daemon configuration"}, config));43 flag(cli::make_flag(cli::Name{"config"}, cli::Description{"The daemon configuration"}, config));
44 mutable_run() = [this]()44 action([this](const cli::Command::Context& ctxt)
45 {45 {
46 if (device.empty())46 if (device.empty())
47 {47 {
48 std::cout << "You must specify a device for identification" << std::endl;48 ctxt.cout << "You must specify a device for identification" << std::endl;
49 return EXIT_FAILURE;49 return EXIT_FAILURE;
50 }50 }
5151
@@ -62,10 +62,10 @@
6262
63 auto op = device->identifier().identify_user(biometry::Application::system(), biometry::Reason{"requested by cli"});63 auto op = device->identifier().identify_user(biometry::Application::system(), biometry::Reason{"requested by cli"});
6464
65 std::cout << "Starting identification using device " << descriptor->name() << std::endl;65 ctxt.cout << "Starting identification using device " << descriptor->name() << std::endl;
6666
67 op->start_with_observer(std::make_shared<TracingObserver<biometry::Identification>>());67 op->start_with_observer(std::make_shared<TracingObserver<biometry::Identification>>());
6868
69 return 0;69 return 0;
70 };70 });
71}71}
7272
=== modified file 'src/biometry/cmds/identify.h'
--- src/biometry/cmds/identify.h 2016-05-09 14:37:52 +0000
+++ src/biometry/cmds/identify.h 2016-05-10 11:29:00 +0000
@@ -36,7 +36,7 @@
36namespace cmds36namespace cmds
37{37{
38/// @brief Identify requests identification of the user.38/// @brief Identify requests identification of the user.
39class Identify : public util::cli::Command39class Identify : public util::cli::CommandWithFlagsAndAction
40{40{
41public:41public:
42 /// @brief Enroll creates a new instance, initializing flags to default values.42 /// @brief Enroll creates a new instance, initializing flags to default values.
4343
=== modified file 'src/biometry/cmds/list_devices.cpp'
--- src/biometry/cmds/list_devices.cpp 2016-05-09 14:37:52 +0000
+++ src/biometry/cmds/list_devices.cpp 2016-05-10 11:29:00 +0000
@@ -25,12 +25,13 @@
25namespace cli = biometry::util::cli;25namespace cli = biometry::util::cli;
2626
27biometry::cmds::ListDevices::ListDevices()27biometry::cmds::ListDevices::ListDevices()
28 : Command{{Name{"list-devices"}, Usage{"list-devices"}, Description{"lists all known devices"}, {}}, []()28 : CommandWithFlagsAndAction{cli::Name{"list-devices"}, cli::Usage{"list-devices"}, cli::Description{"lists all known devices"}}
29 {
30 std::cout << "Known devices:" << std::endl;
31 for (const auto& pair : biometry::device_registry())
32 std::cout << " - " << pair.first << "\t" << pair.second->description() << std::endl;
33 return 0;
34 }}
35{29{
30 action([](const cli::Command::Context& ctxt)
31 {
32 ctxt.cout << "Known devices:" << std::endl;
33 for (const auto& pair : biometry::device_registry())
34 ctxt.cout << " - " << pair.first << "\t" << pair.second->description() << std::endl;
35 return 0;
36 });
36}37}
3738
=== modified file 'src/biometry/cmds/list_devices.h'
--- src/biometry/cmds/list_devices.h 2016-05-09 14:37:52 +0000
+++ src/biometry/cmds/list_devices.h 2016-05-10 11:29:00 +0000
@@ -26,7 +26,7 @@
26{26{
27namespace cmds27namespace cmds
28{28{
29class ListDevices : public util::cli::Command29class ListDevices : public util::cli::CommandWithFlagsAndAction
30{30{
31public:31public:
32 ListDevices();32 ListDevices();
3333
=== modified file 'src/biometry/cmds/run.cpp'
--- src/biometry/cmds/run.cpp 2016-05-09 14:37:52 +0000
+++ src/biometry/cmds/run.cpp 2016-05-10 11:29:00 +0000
@@ -47,11 +47,11 @@
4747
4848
49biometry::cmds::Run::Run(const BusFactory& bus_factory)49biometry::cmds::Run::Run(const BusFactory& bus_factory)
50 : Command{{Name{"run"}, Usage{"run"}, Description{"run the daemon"}, {}}},50 : CommandWithFlagsAndAction{cli::Name{"run"}, cli::Usage{"run"}, cli::Description{"run the daemon"}},
51 bus_factory{bus_factory}51 bus_factory{bus_factory}
52{52{
53 mutable_info().flags.push_back(cli::make_flag(Command::Name{"config"}, Command::Description{"The daemon configuration"}, config));53 flag(cli::make_flag(cli::Name{"config"}, cli::Description{"The daemon configuration"}, config));
54 mutable_run() = [this]()54 action([this](const cli::Command::Context&)
55 {55 {
56 auto trap = core::posix::trap_signals_for_all_subsequent_threads({core::posix::Signal::sig_term});56 auto trap = core::posix::trap_signals_for_all_subsequent_threads({core::posix::Signal::sig_term});
57 trap->signal_raised().connect([trap](const core::posix::Signal&)57 trap->signal_raised().connect([trap](const core::posix::Signal&)
@@ -59,8 +59,6 @@
59 trap->stop();59 trap->stop();
60 });60 });
6161
62 std::cout << "here " << config.get() << std::endl;
63
64 using StreamingJsonConfigurationBuilder = util::StreamingConfigurationBuilder<util::JsonConfigurationBuilder>;62 using StreamingJsonConfigurationBuilder = util::StreamingConfigurationBuilder<util::JsonConfigurationBuilder>;
65 StreamingJsonConfigurationBuilder builder63 StreamingJsonConfigurationBuilder builder
66 {64 {
@@ -92,5 +90,5 @@
92 runtime->stop();90 runtime->stop();
9391
94 return EXIT_SUCCESS;92 return EXIT_SUCCESS;
95 };93 });
96}94}
9795
=== modified file 'src/biometry/cmds/run.h'
--- src/biometry/cmds/run.h 2016-05-09 14:37:52 +0000
+++ src/biometry/cmds/run.h 2016-05-10 11:29:00 +0000
@@ -36,7 +36,7 @@
36{36{
37namespace cmds37namespace cmds
38{38{
39class Run : public util::cli::Command39class Run : public util::cli::CommandWithFlagsAndAction
40{40{
41public:41public:
42 /// @brief BusFactory models creation of bus instances.42 /// @brief BusFactory models creation of bus instances.
4343
=== modified file 'src/biometry/cmds/version.cpp'
--- src/biometry/cmds/version.cpp 2016-05-09 14:37:52 +0000
+++ src/biometry/cmds/version.cpp 2016-05-10 11:29:00 +0000
@@ -23,12 +23,13 @@
23namespace cli = biometry::util::cli;23namespace cli = biometry::util::cli;
2424
25biometry::cmds::Version::Version()25biometry::cmds::Version::Version()
26 : Command{{Name{"version"}, Usage{"version"}, Description{"print the version of the daemon"}, {}}, []()26 : CommandWithFlagsAndAction{cli::Name{"version"}, cli::Usage{"version"}, cli::Description{"print the version of the daemon"}}
27 {
28 std::uint32_t major, minor, patch;
29 biometry::version(major, minor, patch);
30 std::cout << "biometryd " << major << "." << minor << "." << patch << std::endl;
31 return 0;
32 }}
33{27{
28 action([](const cli::Command::Context& ctxt)
29 {
30 std::uint32_t major, minor, patch;
31 biometry::version(major, minor, patch);
32 ctxt.cout << "biometryd " << major << "." << minor << "." << patch << std::endl;
33 return 0;
34 });
34}35}
3536
=== modified file 'src/biometry/cmds/version.h'
--- src/biometry/cmds/version.h 2016-05-09 14:37:52 +0000
+++ src/biometry/cmds/version.h 2016-05-10 11:29:00 +0000
@@ -30,7 +30,7 @@
30{30{
31namespace cmds31namespace cmds
32{32{
33class Version : public util::cli::Command33class Version : public util::cli::CommandWithFlagsAndAction
34{34{
35public:35public:
36 Version();36 Version();
3737
=== modified file 'src/biometry/daemon.cpp'
--- src/biometry/daemon.cpp 2016-05-09 14:37:52 +0000
+++ src/biometry/daemon.cpp 2016-05-10 11:29:00 +0000
@@ -22,7 +22,6 @@
22#include <biometry/devices/plugin/enumerator.h>22#include <biometry/devices/plugin/enumerator.h>
2323
24#include <biometry/cmds/enroll.h>24#include <biometry/cmds/enroll.h>
25#include <biometry/cmds/help.h>
26#include <biometry/cmds/identify.h>25#include <biometry/cmds/identify.h>
27#include <biometry/cmds/list_devices.h>26#include <biometry/cmds/list_devices.h>
28#include <biometry/cmds/run.h>27#include <biometry/cmds/run.h>
@@ -37,106 +36,18 @@
37namespace cli = biometry::util::cli;36namespace cli = biometry::util::cli;
38namespace po = boost::program_options;37namespace po = boost::program_options;
3938
40namespace39biometry::Daemon::Daemon()
41{40 : device_registrar{biometry::devices::plugin::DirectoryEnumerator{Configuration::default_plugin_directory()}},
42std::multimap<cli::Command::Name, std::function<void(const std::string&)>>& notifiers()41 cmd{cli::Name{"biometryd"}, cli::Usage{"biometryd"}, cli::Description{"biometryd"}}
43{42{
44 static std::multimap<cli::Command::Name, std::function<void(const std::string&)>> instance;43 cmd.command(std::make_shared<cmds::Enroll>())
45 return instance;44 .command(std::make_shared<cmds::Identify>())
46}45 .command(std::make_shared<cmds::ListDevices>())
47}46 .command(std::make_shared<cmds::Run>())
4847 .command(std::make_shared<cmds::Version>());
49cli::Command::Flag::Flag(const Name& name, const Description& description)
50 : name_{name},
51 description_{description}
52{
53}
54
55const cli::Command::Name& cli::Command::Flag::name() const
56{
57 return name_;
58}
59
60const cli::Command::Description& cli::Command::Flag::description() const
61{
62 return description_;
63}
64
65biometry::Daemon::Daemon() : device_registrar{biometry::devices::plugin::DirectoryEnumerator{Configuration::default_plugin_directory()}}
66{
67 install_command(std::make_shared<cmds::Enroll>());
68 install_command(std::make_shared<cmds::Identify>());
69 install_command(std::make_shared<cmds::ListDevices>());
70 install_command(std::make_shared<cmds::Run>());
71 install_command(std::make_shared<cmds::Version>());
72
73 help = std::make_shared<cmds::Help>([this](const cmds::Help::Enumerator& enumerator)
74 {
75 for (const auto& pair : cmds)
76 enumerator(pair.second);
77 });
78}48}
7949
80int biometry::Daemon::run(const std::vector<std::string>& args)50int biometry::Daemon::run(const std::vector<std::string>& args)
81{51{
82 po::positional_options_description pdesc;52 return cmd.run({std::cin, std::cout, args});
83 pdesc.add("command", 1);
84
85 po::options_description desc("Options");
86 desc.add_options()
87 ("command", po::value<std::string>()->default_value(help->info().name), "the command to be executed");
88
89 for (const auto& pair : cmds)
90 {
91 po::options_description cd(pair.first);
92 for (auto flag : pair.second->info().flags)
93 {
94 // Depending on the name of the flag, we only ever install it once
95 // to the options_description to avoid triggering an issue caused
96 // by ambigious options. Instead, we relay the notify method to inform multiple
97 // flags of the same name that a new value is available.
98 if (notifiers().count(flag->name()) == 0)
99 {
100 auto v = po::value<std::string>()->notifier([flag](const std::string& s)
101 {
102 // Iterate over all known identifiers.
103 auto range = notifiers().equal_range(flag->name());
104 while (range.first != range.second)
105 {
106 (range.first->second)(s);
107 ++range.first;
108 }
109 });
110
111 cd.add_options()(flag->name().as_string().c_str(), v, flag->description().as_string().c_str());
112 }
113
114 notifiers().insert(std::make_pair(flag->name(), [flag](const std::string& s) { flag->notify(s); }));
115 }
116 desc.add(cd);
117 }
118
119 try
120 {
121 po::variables_map vm;
122 po::store(po::command_line_parser(args).options(desc).positional(pdesc).run(), vm);
123 po::notify(vm);
124
125 auto command = vm["command"].as<std::string>();
126
127 if (command == "help")
128 return help->run();
129
130 return cmds[command]->run();
131 }
132 catch (const std::exception& e)
133 {
134 std::cout << e.what() << std::endl;
135 return help->run();
136 }
137}
138
139void biometry::Daemon::install_command(const cli::Command::Ptr& command)
140{
141 cmds[command->info().name] = command;
142}53}
14354
=== modified file 'src/biometry/daemon.h'
--- src/biometry/daemon.h 2016-05-09 14:37:52 +0000
+++ src/biometry/daemon.h 2016-05-10 11:29:00 +0000
@@ -54,11 +54,8 @@
54 int run(const std::vector<std::string>& args);54 int run(const std::vector<std::string>& args);
5555
56private:56private:
57 void install_command(const util::cli::Command::Ptr& command);
58
59 DeviceRegistrar device_registrar;57 DeviceRegistrar device_registrar;
60 std::unordered_map<std::string, util::cli::Command::Ptr> cmds;58 util::cli::CommandWithSubcommands cmd;
61 util::cli::Command::Ptr help;
62};59};
6360
6461
6562
=== modified file 'src/biometry/daemon_main.cpp'
--- src/biometry/daemon_main.cpp 2016-05-04 12:17:44 +0000
+++ src/biometry/daemon_main.cpp 2016-05-10 11:29:00 +0000
@@ -19,11 +19,12 @@
1919
20#include <biometry/daemon.h>20#include <biometry/daemon.h>
2121
22#include <biometry/util/cli.h>
23
24namespace cli = biometry::util::cli;
25
22int main(int argc, char** argv)26int main(int argc, char** argv)
23{27{
24 std::vector<std::string> args;
25 for (int i = 1; i < argc; i++) args.emplace_back(argv[i]);
26
27 biometry::Daemon biometryd;28 biometry::Daemon biometryd;
28 return biometryd.run(args);29 return biometryd.run(cli::args(argc, argv));
29}30}
3031
=== modified file 'src/biometry/util/cli.cpp'
--- src/biometry/util/cli.cpp 2016-05-09 14:37:52 +0000
+++ src/biometry/util/cli.cpp 2016-05-10 11:29:00 +0000
@@ -19,30 +19,245 @@
1919
20#include <biometry/util/cli.h>20#include <biometry/util/cli.h>
2121
22#include <boost/format.hpp>
23#include <boost/program_options.hpp>
24
22namespace cli = biometry::util::cli;25namespace cli = biometry::util::cli;
2326namespace po = boost::program_options;
24cli::Command::Info cli::Command::info() const27
25{28namespace
26 return info_;29{
27}30namespace pattern
2831{
29int cli::Command::run()32static constexpr const char* help_for_command_with_subcommands =
30{33"NAME:\n"
31 return run_();34" %1% - %2%\n"
32}35"\n"
3336"USAGE:\n"
34cli::Command::Command(const cli::Command::Info& info, const std::function<int()>& run)37" %3% [command options] [arguments...]";
35 : info_(info),38
36 run_{run}39static constexpr const char* commands = "COMMANDS:";
37{40static constexpr const char* command = " %1% %2%";
38}41
3942static constexpr const char* options = "OPTIONS:";
40cli::Command::Info& cli::Command::mutable_info()43static constexpr const char* option = " --%1% %2%";
41{44}
42 return info_;45
43}46void add_to_desc_for_flags(po::options_description& desc, const std::set<cli::Flag::Ptr>& flags)
4447{
45std::function<int()>& cli::Command::mutable_run()48 for (auto flag : flags)
46{49 {
47 return run_;50 auto v = po::value<std::string>()->notifier([flag](const std::string& s)
51 {
52 flag->notify(s);
53 });
54 desc.add_options()(flag->name().as_string().c_str(), v, flag->description().as_string().c_str());
55 }
56}
57}
58
59std::vector<std::string> cli::args(int argc, char **argv)
60{
61 std::vector<std::string> result;
62 for (int i = 1; i < argc; i++) result.push_back(argv[i]);
63 return result;
64}
65
66const cli::Name& cli::Flag::name() const
67{
68 return name_;
69}
70
71const cli::Description& cli::Flag::description() const
72{
73 return description_;
74}
75
76cli::Flag::Flag(const Name& name, const Description& description)
77 : name_{name},
78 description_{description}
79{
80}
81
82cli::Name cli::Command::name() const
83{
84 return name_;
85}
86
87cli::Usage cli::Command::usage() const
88{
89 return usage_;
90}
91
92cli::Description cli::Command::description() const
93{
94 return description_;
95}
96
97cli::Command::Command(const cli::Name& name, const cli::Usage& usage, const cli::Description& description)
98 : name_(name),
99 usage_(usage),
100 description_(description)
101{
102}
103
104cli::CommandWithSubcommands::CommandWithSubcommands(const Name& name, const Usage& usage, const Description& description)
105 : Command{name, usage, description}
106{
107 command(std::make_shared<cmd::Help>(*this));
108}
109
110cli::CommandWithSubcommands& cli::CommandWithSubcommands::command(const Command::Ptr& command)
111{
112 commands_[command->name().as_string()] = command;
113 return *this;
114}
115
116cli::CommandWithSubcommands& cli::CommandWithSubcommands::flag(const Flag::Ptr& flag)
117{
118 flags_.insert(flag);
119 return *this;
120}
121
122void cli::CommandWithSubcommands::help(std::ostream& out)
123{
124 out << boost::format(pattern::help_for_command_with_subcommands)
125 % name().as_string() % usage().as_string()
126 % name().as_string() << std::endl;
127
128 if (flags_.size() > 0)
129 {
130 out << std::endl << pattern::options << std::endl;
131 for (const auto& flag : flags_)
132 out << boost::format(pattern::option) % flag->name() % flag->description() << std::endl;
133 }
134
135 if (commands_.size() > 0)
136 {
137 out << std::endl << pattern::commands << std::endl;
138 for (const auto& cmd : commands_)
139 out << boost::format(pattern::command) % cmd.second->name() % cmd.second->description() << std::endl;
140 }
141}
142
143int cli::CommandWithSubcommands::run(const cli::Command::Context& ctxt)
144{
145 po::positional_options_description pdesc;
146 pdesc.add("command", 1);
147
148 po::options_description desc("Options");
149 desc.add_options()("command", po::value<std::string>()->required(), "the command to be executed");
150
151 add_to_desc_for_flags(desc, flags_);
152
153 try
154 {
155 po::variables_map vm;
156 auto parsed = po::command_line_parser(ctxt.args).options(desc).positional(pdesc).allow_unregistered().run();
157 po::store(parsed, vm);
158 po::notify(vm);
159
160 return commands_[vm["command"].as<std::string>()]->run(cli::Command::Context
161 {
162 ctxt.cin,
163 ctxt.cout,
164 po::collect_unrecognized(parsed.options, po::include_positional)
165 });
166 }
167 catch (const po::error& e)
168 {
169 ctxt.cout << e.what() << std::endl;
170 help(ctxt.cout);
171 return EXIT_FAILURE;
172 }
173
174 return EXIT_FAILURE;
175}
176
177cli::CommandWithFlagsAndAction::CommandWithFlagsAndAction(const Name& name, const Usage& usage, const Description& description)
178 : Command{name, usage, description}
179{
180}
181
182cli::CommandWithFlagsAndAction& cli::CommandWithFlagsAndAction::flag(const Flag::Ptr& flag)
183{
184 flags_.insert(flag);
185 return *this;
186}
187
188cli::CommandWithFlagsAndAction& cli::CommandWithFlagsAndAction::action(const Action& action)
189{
190 action_ = action;
191 return *this;
192}
193
194int cli::CommandWithFlagsAndAction::run(const Context& ctxt)
195{
196 po::options_description cd(name().as_string());
197
198 bool help_requested{false};
199 cd.add_options()("help", po::bool_switch(&help_requested), "produces a help message");
200
201 add_to_desc_for_flags(cd, flags_);
202
203 try
204 {
205 po::variables_map vm;
206 auto parsed = po::command_line_parser(ctxt.args).options(cd).allow_unregistered().run();
207 po::store(parsed, vm);
208 po::notify(vm);
209
210 if (help_requested)
211 {
212 help(ctxt.cout);
213 return EXIT_SUCCESS;
214 }
215
216 return action_(cli::Command::Context
217 {
218 ctxt.cin,
219 ctxt.cout,
220 po::collect_unrecognized(parsed.options, po::include_positional)
221 });
222 }
223 catch (const po::error& e)
224 {
225 ctxt.cout << e.what() << std::endl;
226 help(ctxt.cout);
227 return EXIT_FAILURE;
228 }
229
230 return EXIT_FAILURE;
231}
232
233void cli::CommandWithFlagsAndAction::help(std::ostream& out)
234{
235 out << boost::format(pattern::help_for_command_with_subcommands)
236 % name().as_string() % description().as_string()
237 % name().as_string() << std::endl;
238
239 if (flags_.size() > 0)
240 {
241 out << std::endl << boost::format(pattern::options) << std::endl;
242 for (const auto& flag : flags_)
243 out << boost::format(pattern::option) % flag->name() % flag->description() << std::endl;
244 }
245}
246
247cli::cmd::Help::Help(Command& cmd)
248 : Command{cli::Name{"help"}, cli::Usage{"prints a short help message"}, cli::Description{"prints a short help message"}},
249 command{cmd}
250{
251}
252
253// From Command
254int cli::cmd::Help::run(const Context &context)
255{
256 command.help(context.cout);
257 return EXIT_FAILURE;
258}
259
260void cli::cmd::Help::help(std::ostream &out)
261{
262 command.help(out);
48}263}
49264
=== modified file 'src/biometry/util/cli.h'
--- src/biometry/util/cli.h 2016-05-09 14:37:52 +0000
+++ src/biometry/util/cli.h 2016-05-10 11:29:00 +0000
@@ -26,9 +26,11 @@
26#include <iomanip>26#include <iomanip>
27#include <iostream>27#include <iostream>
28#include <memory>28#include <memory>
29#include <set>
29#include <sstream>30#include <sstream>
30#include <stdexcept>31#include <stdexcept>
31#include <string>32#include <string>
33#include <unordered_map>
3234
33namespace biometry35namespace biometry
34{36{
@@ -78,170 +80,263 @@
78 return out << std::setw(max) << std::left << scs.as_string();80 return out << std::setw(max) << std::left << scs.as_string();
79}81}
8082
83// We are imposing size constraints to ensure a consistent CLI layout.
84typedef SizeConstrainedString<15> Name;
85typedef SizeConstrainedString<60> Usage;
86typedef SizeConstrainedString<60> Description;
87
88/// @brief Flag models an input parameter to a command.
89class BIOMETRY_DLL_PUBLIC Flag : public DoNotCopyOrMove
90{
91public:
92 // Safe us some typing.
93 typedef std::shared_ptr<Flag> Ptr;
94
95 /// @brief notify announces a new value to the flag.
96 virtual void notify(const std::string& value) = 0;
97 /// @brief name returns the name of the Flag.
98 const Name& name() const;
99 /// @brief description returns a human-readable description of the flag.
100 const Description& description() const;
101
102protected:
103 /// @brief Flag creates a new instance, initializing name and description
104 /// from the given values.
105 Flag(const Name& name, const Description& description);
106
107private:
108 Name name_;
109 Description description_;
110};
111
112/// @brief TypedFlag implements Flag relying on operator<< and operator>> to read/write values to/from strings.
113template<typename T>
114class BIOMETRY_DLL_PUBLIC TypedFlag : public Flag
115{
116public:
117 typedef std::shared_ptr<TypedFlag<T>> Ptr;
118
119 TypedFlag(const Name& name, const Description& description) : Flag{name, description}
120 {
121 }
122
123 /// @brief value installs the given value in the flag.
124 TypedFlag& value(const T& value)
125 {
126 value_ = value;
127 return *this;
128 }
129
130 /// @brief value returns the optional value associated with the flag.
131 const Optional<T>& value() const
132 {
133 return value_;
134 }
135
136 /// @brief notify tries to unwrap a value of type T from value.
137 void notify(const std::string& s) override
138 {
139 std::stringstream ss{s};
140 T value; ss >> value;
141 value_ = value;
142 }
143
144private:
145 Optional<T> value_;
146};
147
148/// @brief TypedReferenceFlag implements Flag, relying on operator<</>> to convert to/from string representations,
149/// updating the given mutable reference to a value of type T.
150template<typename T>
151class BIOMETRY_DLL_PUBLIC TypedReferenceFlag : public Flag
152{
153public:
154 // Safe us some typing.
155 typedef std::shared_ptr<TypedReferenceFlag<T>> Ptr;
156
157 /// @brief TypedReferenceFlag initializes a new instance with name, description and value.
158 TypedReferenceFlag(const Name& name, const Description& description, T& value)
159 : Flag{name, description},
160 value_{value}
161 {
162 }
163
164 /// @brief notify tries to unwrap a value of type T from value,
165 /// relying on operator>> to read from given string s.
166 void notify(const std::string& s) override
167 {
168 std::stringstream ss{s};
169 ss >> value_.get();
170 }
171
172private:
173 std::reference_wrapper<T> value_;
174};
175
176/// @brief OptionalTypedReferenceFlag handles Optional<T> references, making sure that
177/// a value is always read on notify, even if the Optional<T> wasn't initialized previously.
178template<typename T>
179class BIOMETRY_DLL_PUBLIC OptionalTypedReferenceFlag : public Flag
180{
181public:
182 typedef std::shared_ptr<OptionalTypedReferenceFlag<T>> Ptr;
183
184 OptionalTypedReferenceFlag(const Name& name, const Description& description, Optional<T>& value)
185 : Flag{name, description},
186 value_{value}
187 {
188 }
189
190 /// @brief notify tries to unwrap a value of type T from value.
191 void notify(const std::string& s) override
192 {
193 std::stringstream ss{s}; T value; ss >> value;
194 value_.get() = value;
195 }
196
197private:
198 std::reference_wrapper<Optional<T>> value_;
199};
200
81/// @brief Command abstracts an individual command available from the daemon.201/// @brief Command abstracts an individual command available from the daemon.
82class BIOMETRY_DLL_PUBLIC Command : public DoNotCopyOrMove202class BIOMETRY_DLL_PUBLIC Command : public DoNotCopyOrMove
83{203{
84public:204public:
85 // Safe us some typing205 // Safe us some typing
86 typedef std::shared_ptr<Command> Ptr;206 typedef std::shared_ptr<Command> Ptr;
87207
88 // We are imposing size constraints to ensure a consistent CLI layout.208 /// @brief Context bundles information passed to Command::run invocations.
89 typedef SizeConstrainedString<15> Name;209 struct Context
90 typedef SizeConstrainedString<60> Usage;210 {
91 typedef SizeConstrainedString<60> Description;211 std::istream& cin; ///< The std::istream that should be used for reading.
92212 std::ostream& cout; ///< The std::ostream that should be used for writing.
93 /// @brief Flag models an input parameter to a command.213 std::vector<std::string> args; ///< The command line args.
94 class Flag : public DoNotCopyOrMove214 };
95 {215
96 public:216 /// @brief name returns the Name of the command.
97 // Safe us some typing.217 virtual Name name() const;
98 typedef std::shared_ptr<Flag> Ptr;218
99219 /// @brief usage returns a short usage string for the command.
100 /// @brief notify announces a new value to the flag.220 virtual Usage usage() const;
101 virtual void notify(const std::string& value) = 0;221
102 /// @brief name returns the name of the Flag.222 /// @brief description returns a longer string explaining the command.
103 const Name& name() const;223 virtual Description description() const;
104 /// @brief description returns a human-readable description of the flag.224
105 const Description& description() const;
106
107 protected:
108 /// @brief Flag creates a new instance, initializing name and description
109 /// from the given values.
110 Flag(const Name& name, const Description& description);
111
112 private:
113 Name name_;
114 Description description_;
115 };
116
117 /// @brief Info bundles details about a command.
118 struct Info
119 {
120 Name name; ///< The name of the command.
121 Usage usage; ///< Short usage description of the command.
122 Description description; /// More detailed description of the command.
123 std::vector<Flag::Ptr> flags; /// Flags known to the command.
124 };
125
126 template<typename T>
127 class TypedFlag : public Flag
128 {
129 public:
130 typedef std::shared_ptr<TypedFlag<T>> Ptr;
131
132 TypedFlag(const Name& name, const Description& description) : Flag{name, description}
133 {
134 }
135
136 /// @brief value installs the given value in the flag.
137 TypedFlag& value(const T& value)
138 {
139 value_ = value;
140 return *this;
141 }
142
143 /// @brief value returns the optional value associated with the flag.
144 const Optional<T>& value() const
145 {
146 return value_;
147 }
148
149 /// @brief notify tries to unwrap a value of type T from value.
150 void notify(const std::string& s) override
151 {
152 std::stringstream ss{s};
153 T value; ss >> value;
154 value_ = value;
155 }
156
157 private:
158 Optional<T> value_;
159 };
160
161 template<typename T>
162 class TypedReferenceFlag : public Flag
163 {
164 public:
165 typedef std::shared_ptr<TypedReferenceFlag<T>> Ptr;
166
167 TypedReferenceFlag(const Name& name, const Description& description, T& value)
168 : Flag{name, description},
169 value_{value}
170 {
171 }
172
173 /// @brief notify tries to unwrap a value of type T from value.
174 void notify(const std::string& s) override
175 {
176 std::stringstream ss{s};
177 ss >> value_.get();
178 }
179
180 private:
181 std::reference_wrapper<T> value_;
182 };
183
184 /// @brief OptionalTypedReferenceFlag handles Optional<T> references, making sure that
185 /// a value is always read on notify, even if the Optional<T> wasn't initialized previously.
186 template<typename T>
187 class OptionalTypedReferenceFlag : public Flag
188 {
189 public:
190 typedef std::shared_ptr<OptionalTypedReferenceFlag<T>> Ptr;
191
192 OptionalTypedReferenceFlag(const Name& name, const Description& description, Optional<T>& value)
193 : Flag{name, description},
194 value_{value}
195 {
196 }
197
198 /// @brief notify tries to unwrap a value of type T from value.
199 void notify(const std::string& s) override
200 {
201 std::stringstream ss{s}; T value; ss >> value;
202 value_.get() = value;
203 }
204
205 private:
206 std::reference_wrapper<Optional<T>> value_;
207 };
208
209 /// @brief info returns Info about a command.
210 virtual Info info() const;
211 /// @brief run puts the command to execution.225 /// @brief run puts the command to execution.
212 virtual int run();226 virtual int run(const Context& context) = 0;
227
228 /// @brief help prints information about a command to out.
229 virtual void help(std::ostream& out) = 0;
213230
214protected:231protected:
215 /// @brief Command initializes a new instance with the given info and functor.232 /// @brief Command initializes a new instance with the given name, usage and description.
216 Command(const Info& info, const std::function<int()>& run = std::function<int()>());233 Command(const Name& name, const Usage& usage, const Description& description);
217234
218 /// @brief info returns a mutable reference to info_.235 /// @brief name adjusts the name of the command to n.
219 Info& mutable_info();236 // virtual void name(const Name& n);
220237 /// @brief usage adjusts the usage string of the comand to u.
221 /// @brief run returns a mutable reference to run_.238 // virtual void usage(const Usage& u);
222 std::function<int()>& mutable_run();239 /// @brief description adjusts the description string of the command to d.
223240 // virtual void description(const Description& d);
224private:241
225 Info info_;242private:
226 std::function<int()> run_;243 Name name_;
227};244 Usage usage_;
228245 Description description_;
229template<typename T>246};
230BIOMETRY_DLL_PUBLIC typename Command::TypedFlag<T>::Ptr make_flag(const Command::Name& name, const Command::Description& desc)247
231{248/// @brief CommandWithSubcommands implements Command, selecting one of a set of actions.
232 return std::make_shared<Command::TypedFlag<T>>(name, desc);249class BIOMETRY_DLL_PUBLIC CommandWithSubcommands : public Command
233}250{
234251public:
235template<typename T>252 typedef std::shared_ptr<CommandWithSubcommands> Ptr;
236BIOMETRY_DLL_PUBLIC typename Command::TypedReferenceFlag<T>::Ptr make_flag(const Command::Name& name, const Command::Description& desc, T& value)253 typedef std::function<int(const Context&)> Action;
237{254
238 return std::make_shared<Command::TypedReferenceFlag<T>>(name, desc, value);255 /// @brief CommandWithSubcommands initializes a new instance with the given name, usage and description
239}256 CommandWithSubcommands(const Name& name, const Usage& usage, const Description& description);
240257
241template<typename T>258 /// @brief command adds the given command to the set of known commands.
242BIOMETRY_DLL_PUBLIC typename Command::OptionalTypedReferenceFlag<T>::Ptr make_flag(const Command::Name& name, const Command::Description& desc, Optional<T>& value)259 CommandWithSubcommands& command(const Command::Ptr& command);
243{260
244 return std::make_shared<Command::OptionalTypedReferenceFlag<T>>(name, desc, value);261 /// @brief flag adds the given flag to the set of known flags.
262 CommandWithSubcommands& flag(const Flag::Ptr& flag);
263
264 // From Command
265 int run(const Context& context) override;
266 void help(std::ostream &out) override;
267
268private:
269 std::unordered_map<std::string, Command::Ptr> commands_;
270 std::set<Flag::Ptr> flags_;
271};
272
273/// @brief CommandWithFlagsAction implements Command, executing an Action after handling
274class BIOMETRY_DLL_PUBLIC CommandWithFlagsAndAction : public Command
275{
276public:
277 typedef std::shared_ptr<CommandWithFlagsAndAction> Ptr;
278 typedef std::function<int(const Context&)> Action;
279
280 /// @brief CommandWithFlagsAndAction initializes a new instance with the given name, usage and description
281 CommandWithFlagsAndAction(const Name& name, const Usage& usage, const Description& description);
282
283 /// @brief flag adds the given flag to the set of known flags.
284 CommandWithFlagsAndAction& flag(const Flag::Ptr& flag);
285
286 /// @brief action installs the given action.
287 CommandWithFlagsAndAction& action(const Action& action);
288
289 // From Command
290 int run(const Context& context) override;
291 void help(std::ostream &out) override;
292
293private:
294 std::set<Flag::Ptr> flags_;
295 Action action_;
296};
297
298namespace cmd
299{
300/// @brief HelpFor prints a help message for the given command on execution.
301class Help : public Command
302{
303public:
304 /// @brief HelpFor initializes a new instance with the given reference to a cmd.
305 explicit Help(Command& cmd);
306
307 // From Command
308 int run(const Context &context) override;
309 void help(std::ostream &out) override;
310
311private:
312 /// @cond
313 Command& command;
314 /// @endcond
315};
316}
317
318/// @brief args returns a vector of strings assembled from argc and argv.
319BIOMETRY_DLL_PUBLIC std::vector<std::string> args(int argc, char** argv);
320
321/// @brief make_flag returns a flag with the given name and description.
322template<typename T>
323BIOMETRY_DLL_PUBLIC typename TypedFlag<T>::Ptr make_flag(const Name& name, const Description& description)
324{
325 return std::make_shared<TypedFlag<T>>(name, description);
326}
327
328/// @brief make_flag returns a flag with the given name and description, notifying updates to value.
329template<typename T>
330BIOMETRY_DLL_PUBLIC typename TypedReferenceFlag<T>::Ptr make_flag(const Name& name, const Description& desc, T& value)
331{
332 return std::make_shared<TypedReferenceFlag<T>>(name, desc, value);
333}
334
335/// @brief make_flag returns a flag with the given name and description, updating the given optional value.
336template<typename T>
337BIOMETRY_DLL_PUBLIC typename OptionalTypedReferenceFlag<T>::Ptr make_flag(const Name& name, const Description& desc, Optional<T>& value)
338{
339 return std::make_shared<OptionalTypedReferenceFlag<T>>(name, desc, value);
245}340}
246}341}
247}342}
248343
=== modified file 'tests/test_daemon.cpp'
--- tests/test_daemon.cpp 2016-05-09 14:37:52 +0000
+++ tests/test_daemon.cpp 2016-05-10 11:29:00 +0000
@@ -65,18 +65,18 @@
6565
66TEST(TypedFlag, stores_name_and_desc_passed_on_construction)66TEST(TypedFlag, stores_name_and_desc_passed_on_construction)
67{67{
68 cli::Command::Name name{"42"};68 cli::Name name{"42"};
69 cli::Command::Description desc{"43"};69 cli::Description desc{"43"};
70 cli::Command::TypedFlag<int> flag{name, desc};70 cli::TypedFlag<int> flag{name, desc};
71 EXPECT_EQ(name, flag.name());71 EXPECT_EQ(name, flag.name());
72 EXPECT_EQ(desc, flag.description());72 EXPECT_EQ(desc, flag.description());
73}73}
7474
75TEST(TypedFlag, parses_string_on_notify_and_sets_value)75TEST(TypedFlag, parses_string_on_notify_and_sets_value)
76{76{
77 cli::Command::Name name{"42"};77 cli::Name name{"42"};
78 cli::Command::Description desc{"43"};78 cli::Description desc{"43"};
79 cli::Command::TypedFlag<int> flag{name, desc};79 cli::TypedFlag<int> flag{name, desc};
80 EXPECT_FALSE(flag.value().is_initialized());80 EXPECT_FALSE(flag.value().is_initialized());
81 flag.notify("42");81 flag.notify("42");
82 EXPECT_TRUE(flag.value().is_initialized());82 EXPECT_TRUE(flag.value().is_initialized());
@@ -153,7 +153,7 @@
153 biometry::Daemon daemon;153 biometry::Daemon daemon;
154 auto rc = daemon.run(154 auto rc = daemon.run(
155 {155 {
156 "run", "--config", "test.json"156 "run", "--config=test.json"
157 });157 });
158158
159 return rc == EXIT_SUCCESS ? core::posix::exit::Status::success : core::posix::exit::Status::failure;159 return rc == EXIT_SUCCESS ? core::posix::exit::Status::success : core::posix::exit::Status::failure;

Subscribers

People subscribed via source and target branches