Browse Source

refactor: allow filling out Action elements of main args, instead of using them as stubs

Sam Jaffe 2 years ago
parent
commit
bd4d14a565
3 changed files with 33 additions and 27 deletions
  1. 20 9
      include/program_args/arguments.h
  2. 10 4
      include/program_args/arguments_impl.hpp
  3. 3 14
      test/action_test.cpp

+ 20 - 9
include/program_args/arguments.h

@@ -149,10 +149,18 @@ private:
   friend struct ArgumentTestHelper;
   friend int main(int, char const * const *);
 
+  template <typename T>
+  std::shared_ptr<T> make_action(std::string const & name) const {
+    if (action_name_ != name) { return nullptr; }
+    auto ptr = make_action_(static_cast<Impl const &>(*this));
+    return std::static_pointer_cast<T>(ptr);
+  }
+
 private:
   using main_callback_t = std::function<int(Impl const &)>;
+  using make_action_t = std::function<std::shared_ptr<void>(Impl const &)>;
   using make_hook_t =
-      std::function<main_callback_t(size_t, char const * const *)>;
+      std::function<make_action_t(size_t, char const * const *)>;
   // Metadata variables
   bool primed_{false};
   detail::Any parent_;
@@ -163,6 +171,8 @@ private:
   std::map<std::string, option_id> option_names;
   std::set<option_id> flag_names;
   std::map<std::string, make_hook_t> actions;
+  std::string action_name_{""};
+  make_action_t make_action_{nullptr};
   std::map<std::string, std::string> action_descriptions;
 
   // Data/Output variables
@@ -237,17 +247,18 @@ struct Arguments<Impl>::WithDefault {
 
 #include "arguments_impl.hpp"
 
-template <typename Args, typename Action>
-int typed_main(Args const &, Action const &);
+template <typename Args, typename... Actions>
+int typed_main(Args const &, Actions const &...);
 
-#define TYPED_MAIN(tname)                                                      \
-  int typed_main(tname const & args);                                          \
+#define PROGRAM_ARGS_MAIN(tname)                                               \
   int main(int argc, char const * const * const argv) try {                    \
-    tname args(argc, argv);                                                    \
-    return (args.main_callback) ? args.main_callback(args) : typed_main(args); \
+    tname(argc, argv).invoke();                                                \
   } catch (program::ProgramArgumentsError const & pae) {                       \
     std::cerr << "Error in program argument handling: " << pae.what() << "\n"; \
-  }                                                                            \
-  int typed_main(tname const & args)
+  }
+
+#define TYPED_MAIN(tname)                                                      \
+  PROGRAM_ARGS_MAIN(tname)                                                     \
+  template <> int typed_main(tname const & args)
 
 #define PROGRAM_DEFER(...) [this]() { return __VA_ARGS__; }

+ 10 - 4
include/program_args/arguments_impl.hpp

@@ -22,11 +22,15 @@ Arguments<Impl>::Action::operator T() const {
                 "Action can only bind to Arguments");
   if (!primed()) {
     self->actions.emplace(name, [](int argc, char const * const * argv) {
-      (void)T(argc, argv);
       return [=](Impl const & args) {
-        return typed_main(args, T(argc, argv, &args));
+        return std::make_shared<T>(argc, argv, &args);
       };
     });
+  } else if (auto ptr = self->make_action<T>(name)) {
+    self->main_callback = [ptr](Impl const & impl) {
+      return typed_main(impl, *ptr);
+    };
+    return *ptr;
   }
   return T();
 }
@@ -204,11 +208,13 @@ Arguments<Impl>::Arguments(int argc, char const * const * const argv,
       arguments.insert(arguments.end(), &next, &argv[argc]);
       break;
     } else if (auto hook = actions[arg]) {
-      main_callback = hook(argc - i, argv + i);
+      action_name_ = arg;
+      make_action_ = hook(argc - i, argv + i);
       return;
     } else if (arg == "help" && next && actions[next]) {
       char const * const help[] = {next, "--help"};
-      main_callback = actions[next](2, help);
+      action_name_ = next;
+      make_action_ = hook(2, help);
       return;
     } else if (arg[0] != '-') {
       arguments.emplace_back(arg);

+ 3 - 14
test/action_test.cpp

@@ -67,14 +67,12 @@ struct Bad2 : program::Arguments<Bad2> {
   Commit commit = action("commit");
 };
 
-class Git : program::Arguments<Git> {
-public:
+struct Git : program::Arguments<Git> {
   using Arguments::Arguments;
 
   std::string pwd = option('C');
   bool verbose = flag("verbose", 'v');
 
-private:
   Commit commit = action("commit");
   Checkout checkout = action("checkout");
   Push push = action("push");
@@ -94,19 +92,10 @@ TEST(ActionTest, CanRunWithMultipleActions) { EXPECT_NO_THROW(Git()); }
 
 TEST(ActionTest, ActionIsRouted) {
   Git git = parse<Git>({"", "-v", "commit", "-m", "this is a message"});
-  program::ArgumentTestHelper helper;
-  EXPECT_TRUE(helper.has_main(git));
-  helper.main(git);
-  EXPECT_THAT(g_action.get<Commit>(), NotNull());
-  EXPECT_THAT(g_action.get<Commit>()->message, "this is a message");
+  EXPECT_THAT(git.commit.message, "this is a message");
 }
 
 TEST(ActionTest, CanFetchParentInfo) {
   Git git = parse<Git>({"", "-C", "./submodules/X", "push"});
-  program::ArgumentTestHelper helper;
-  EXPECT_TRUE(helper.has_main(git));
-  helper.main(git);
-
-  EXPECT_THAT(g_action.get<Push>(), NotNull());
-  EXPECT_THAT(g_action.get<Push>()->remote, "./submodules/X");
+  EXPECT_THAT(git.push.remote, "./submodules/X");
 }