Introduced a Subcommand abstraction in stout. Review: https://reviews.apache.org/r/22764/
Project: http://git-wip-us.apache.org/repos/asf/mesos/repo Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/3279c406 Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/3279c406 Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/3279c406 Branch: refs/heads/master Commit: 3279c406c7ff12f869189f7b20dbef17310bce49 Parents: 7f1774b Author: Jie Yu <yujie....@gmail.com> Authored: Wed Jun 25 14:33:16 2014 -0700 Committer: Jie Yu <yujie....@gmail.com> Committed: Wed Jun 25 14:33:21 2014 -0700 ---------------------------------------------------------------------- 3rdparty/libprocess/3rdparty/stout/Makefile.am | 2 + .../3rdparty/stout/include/stout/subcommand.hpp | 191 +++++++++++++++++++ .../3rdparty/stout/tests/subcommand_tests.cpp | 181 ++++++++++++++++++ 3 files changed, 374 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/mesos/blob/3279c406/3rdparty/libprocess/3rdparty/stout/Makefile.am ---------------------------------------------------------------------- diff --git a/3rdparty/libprocess/3rdparty/stout/Makefile.am b/3rdparty/libprocess/3rdparty/stout/Makefile.am index eac7ab5..b562e87 100644 --- a/3rdparty/libprocess/3rdparty/stout/Makefile.am +++ b/3rdparty/libprocess/3rdparty/stout/Makefile.am @@ -68,6 +68,7 @@ EXTRA_DIST = \ include/stout/stopwatch.hpp \ include/stout/stringify.hpp \ include/stout/strings.hpp \ + include/stout/subcommand.hpp \ include/stout/tests/utils.hpp \ include/stout/thread.hpp \ include/stout/try.hpp \ @@ -105,5 +106,6 @@ EXTRA_DIST = \ tests/set_tests.cpp \ tests/some_tests.cpp \ tests/strings_tests.cpp \ + tests/subcommand_tests.cpp \ tests/thread_tests.cpp \ tests/uuid_tests.cpp http://git-wip-us.apache.org/repos/asf/mesos/blob/3279c406/3rdparty/libprocess/3rdparty/stout/include/stout/subcommand.hpp ---------------------------------------------------------------------- diff --git a/3rdparty/libprocess/3rdparty/stout/include/stout/subcommand.hpp b/3rdparty/libprocess/3rdparty/stout/include/stout/subcommand.hpp new file mode 100644 index 0000000..b121836 --- /dev/null +++ b/3rdparty/libprocess/3rdparty/stout/include/stout/subcommand.hpp @@ -0,0 +1,191 @@ +/** + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __STOUT_SUBCOMMAND_HPP__ +#define __STOUT_SUBCOMMAND_HPP__ + +#include <iostream> +#include <sstream> +#include <string> +#include <vector> + +#include <stout/flags.hpp> +#include <stout/foreach.hpp> +#include <stout/hashset.hpp> +#include <stout/option.hpp> +#include <stout/preprocessor.hpp> + +// Subcommand is an abstraction for creating command binaries that +// encompass many subcommands. For example: +// +// $ ./runner start --arg1=1 --arg2=2 +// $ ./runner stop --arg3=3 --arg4=4 +// +// Here, the 'runner' command contains two subcommand implementations: +// StartCommand and StopCommand. Each subcommand needs to define a +// name, implement an 'execute' function, and provide the address of a +// flags where the command line arguments will be parsed to. To +// simplify creating command binaries that encompass many subcommands, +// we provide a 'dispatch' function which will look at argv[1] to +// decide which subcommand to execute (based on its name) and then +// parse the command line flags for you. +class Subcommand +{ +public: + // This function is supposed to be called by the main function of + // the command binary. A user needs to register at least one + // subcommand. Here is a typical example of the main function of the + // command binary: + // + // int main(int argc, char** argv) + // { + // return Subcommand::dispatch( + // None(), + // argc, + // argv, + // new Subcommand1(), + // new Subcommand2(), + // new Subcommand3()); + // } +#define INSERT(z, N, _) subcommands.push_back( c ## N ); +#define TEMPLATE(Z, N, DATA) \ + static int dispatch( \ + const Option<std::string>& prefix, \ + int argc, \ + char** argv, \ + ENUM_PARAMS(N, Subcommand* c)) \ + { \ + std::vector<Subcommand*> subcommands; \ + REPEAT_FROM_TO(0, N, INSERT, _) \ + return dispatch(prefix, argc, argv, subcommands); \ + } + + REPEAT_FROM_TO(1, 11, TEMPLATE, _) // Args C1 -> C11. +#undef TEMPLATE +#undef INSERT + + explicit Subcommand(const std::string& _name) : name_(_name) {} + virtual ~Subcommand() {} + + std::string name() const { return name_; } + +protected: + // Defines the main function of this subcommand. The return value + // will be used as the exit code. + // TODO(jieyu): Consider passing in argc and argv as some users + // might want to access the remaining command line arguments. + virtual int execute() = 0; + + // Returns the pointer to the flags that will be used for this + // subcommand. If the user does not provide an override, the default + // empty flags will be used. + virtual flags::FlagsBase* getFlags() { return &flags_; } + +private: + // Returns the usage by listing all the registered subcommands. + static std::string usage( + const std::string& argv0, + const std::vector<Subcommand*>& subcommands); + + static int dispatch( + const Option<std::string>& prefix, + int argc, + char** argv, + const std::vector<Subcommand*>& subcommands); + + // The name of this subcommand. + std::string name_; + + // The default flags which is empty. + flags::FlagsBase flags_; +}; + + +inline std::string Subcommand::usage( + const std::string& argv0, + const std::vector<Subcommand*>& subcommands) +{ + std::ostringstream stream; + + stream << "Usage: " << argv0 << " <subcommand> [OPTIONS]\n\n" + << "Available subcommands:\n" + << " help\n"; + + // Get a list of available subcommands. + foreach (Subcommand* subcommand, subcommands) { + stream << " " << subcommand->name() << "\n"; + } + + return stream.str(); +} + + +inline int Subcommand::dispatch( + const Option<std::string>& prefix, + int argc, + char** argv, + const std::vector<Subcommand*>& subcommands) +{ + if (subcommands.empty()) { + std::cerr << "No subcommand is found" << std::endl; + return 1; + } + + // Check for duplicated subcommand names. + hashset<std::string> names; + foreach (Subcommand* subcommand, subcommands) { + if (names.contains(subcommand->name())) { + std::cerr << "Multiple subcommands have name '" + << subcommand->name() << "'" << std::endl; + return 1; + } + names.insert(subcommand->name()); + } + + if (argc < 2) { + std::cerr << usage(argv[0], subcommands) << std::endl; + return 1; + } + + if (std::string(argv[1]) == "help") { + if (argc == 2) { + std::cout << usage(argv[0], subcommands) << std::endl; + return 0; + } + + // 'argv[0] help subcommand' => 'argv[0] subcommand --help' + argv[1] = argv[2]; + argv[2] = (char*) "--help"; + } + + foreach (Subcommand* subcommand, subcommands) { + if (subcommand->name() == argv[1]) { + flags::FlagsBase* flags = subcommand->getFlags(); + + Try<Nothing> load = flags->load(prefix, argc - 1, argv + 1); + if (load.isError()) { + std::cerr << "Failed to parse the flags: " << load.error() << std::endl; + return 1; + } + + return subcommand->execute(); + } + } + + std::cerr << "Subcommand '" << argv[1] << "' is not available\n" + << usage(argv[0], subcommands) << std::endl; + return 1; +} + +#endif // __STOUT_SUBCOMMAND_HPP__ http://git-wip-us.apache.org/repos/asf/mesos/blob/3279c406/3rdparty/libprocess/3rdparty/stout/tests/subcommand_tests.cpp ---------------------------------------------------------------------- diff --git a/3rdparty/libprocess/3rdparty/stout/tests/subcommand_tests.cpp b/3rdparty/libprocess/3rdparty/stout/tests/subcommand_tests.cpp new file mode 100644 index 0000000..c40bba4 --- /dev/null +++ b/3rdparty/libprocess/3rdparty/stout/tests/subcommand_tests.cpp @@ -0,0 +1,181 @@ +/** + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <string.h> +#include <stdlib.h> + +#include <string> +#include <vector> + +#include <gtest/gtest.h> + +#include <stout/flags.hpp> +#include <stout/foreach.hpp> +#include <stout/subcommand.hpp> + +using std::string; +using std::vector; + + +class TestSubcommand : public Subcommand +{ +public: + struct Flags : public flags::FlagsBase + { + Flags() + { + add(&b, "b", "bool"); + add(&i, "i", "int"); + add(&s, "s", "string"); + add(&s2, "s2", "string with single quote"); + add(&s3, "s3", "string with double quote"); + add(&d, "d", "Duration"); + add(&y, "y", "Bytes"); + add(&j, "j", "JSON::Object"); + } + + void populate() + { + b = true; + i = 42; + s = "hello"; + s2 = "we're"; + s3 = "\"geek\""; + d = Seconds(10); + y = Bytes(100); + + JSON::Object object; + object.values["strings"] = "string"; + object.values["integer1"] = 1; + object.values["integer2"] = -1; + object.values["double1"] = 1; + object.values["double2"] = -1; + object.values["double3"] = -1.42; + + JSON::Object nested; + nested.values["string"] = "string"; + object.values["nested"] = nested; + + JSON::Array array; + array.values.push_back(nested); + object.values["array"] = array; + + j = object; + } + + Option<bool> b; + Option<int> i; + Option<string> s; + Option<string> s2; + Option<string> s3; + Option<Duration> d; + Option<Bytes> y; + Option<JSON::Object> j; + }; + + explicit TestSubcommand(const string& name) : Subcommand(name) {} + + Flags flags; + +protected: + virtual int execute() { return 0; } + virtual flags::FlagsBase* getFlags() { return &flags; } +}; + + +// Generates a vector of arguments from flags. +static vector<string> getArgv(const flags::FlagsBase& flags) +{ + vector<string> argv; + foreachpair (const string& name, const flags::Flag& flag, flags) { + Option<string> value = flag.stringify(flags); + if (value.isSome()) { + argv.push_back("--" + name + "=" + value.get()); + } + } + return argv; +} + + +TEST(SubcommandTest, Flags) +{ + TestSubcommand::Flags flags; + flags.populate(); + + // Construct the command line arguments. + vector<string> _argv = getArgv(flags); + int argc = _argv.size() + 2; + char** argv = new char*[argc]; + argv[0] = (char*) "command"; + argv[1] = (char*) "subcommand"; + for (int i = 2; i < argc; i++) { + argv[i] = ::strdup(_argv[i - 2].c_str()); + } + + TestSubcommand subcommand("subcommand"); + + ASSERT_EQ(0, Subcommand::dispatch( + None(), + argc, + argv, + &subcommand)); + + EXPECT_EQ(flags.b, subcommand.flags.b); + EXPECT_EQ(flags.i, subcommand.flags.i); + EXPECT_EQ(flags.s, subcommand.flags.s); + EXPECT_EQ(flags.s2, subcommand.flags.s2); + EXPECT_EQ(flags.s3, subcommand.flags.s3); + EXPECT_EQ(flags.d, subcommand.flags.d); + EXPECT_EQ(flags.y, subcommand.flags.y); + EXPECT_EQ(flags.j, subcommand.flags.j); + + for (int i = 2; i < argc; i++) { + ::free(argv[i]); + } + delete argv; +} + + +TEST(SubcommandTest, Dispatch) +{ + TestSubcommand subcommand("subcommand"); + TestSubcommand subcommand2("subcommand2"); + + int argc = 2; + char* argv[] = { + (char*) "command", + (char*) "subcommand" + }; + + EXPECT_EQ(1, Subcommand::dispatch( + None(), + argc, + argv, + &subcommand2)); + + // Duplicated subcommand names. + EXPECT_EQ(1, Subcommand::dispatch( + None(), + argc, + argv, + &subcommand, + &subcommand)); + + EXPECT_EQ(0, Subcommand::dispatch( + None(), + argc, + argv, + &subcommand, + &subcommand2)); +}