Browse Source

feat: enable recursive actions using boost

Sam Jaffe 2 years ago
parent
commit
1e017de306

+ 26 - 6
include/program_args/arguments.h

@@ -39,7 +39,7 @@ public:
     return {arguments.begin() + argument_names.size(), arguments.end()};
   }
 
-  // protected:
+protected:
   template <typename T> Arguments(T const * parent) : parent_(parent) {}
   template <typename T>
   Arguments(int argc, char const * const * const argv, T const * parent);
@@ -147,8 +147,15 @@ private:
   // A helper for what limited introspection is required for handling actions in
   // test
   friend struct ArgumentTestHelper;
+  template <typename> friend class Arguments;
   friend int main(int, char const * const *);
 
+  operator bool() const { return primed_; }
+
+  template <typename... Parents> int invoke(Parents const &... parents) const {
+    return typed_main(parents..., static_cast<Impl const &>(*this));
+  }
+
   template <typename T>
   std::shared_ptr<T> make_action(std::string const & name) const {
     if (action_name_ != name) { return nullptr; }
@@ -157,9 +164,8 @@ private:
   }
 
 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 =
+  using action_hook_t =
       std::function<make_action_t(size_t, char const * const *)>;
   // Metadata variables
   bool primed_{false};
@@ -170,14 +176,13 @@ private:
   std::map<option_id, std::string> option_descriptions;
   std::map<std::string, option_id> option_names;
   std::set<option_id> flag_names;
-  std::map<std::string, make_hook_t> actions;
+  std::map<std::string, action_hook_t> actions;
   std::string action_name_{""};
   make_action_t make_action_{nullptr};
   std::map<std::string, std::string> action_descriptions;
 
   // Data/Output variables
   std::string program;
-  main_callback_t main_callback{nullptr};
   constexpr static size_t const no_optional_args{~0ul};
   size_t optional_from{no_optional_args};
   std::vector<std::string> arguments;
@@ -250,9 +255,24 @@ struct Arguments<Impl>::WithDefault {
 template <typename Args, typename... Actions>
 int typed_main(Args const &, Actions const &...);
 
+#include <boost/preprocessor.hpp>
+
+#define TRY_INVOKE(r, data, elem)                                              \
+  if (self.elem) { return self.elem.invoke(parents..., self); }
+
+#define PROGRAM_ARGS_INVOKE(tname, ...)                                        \
+  template <>                                                                  \
+  template <typename... Parents>                                               \
+  int program::Arguments<tname>::invoke(Parents const &... parents) const {    \
+    tname const & self = static_cast<tname const &>(*this);                    \
+    BOOST_PP_SEQ_FOR_EACH(TRY_INVOKE, _,                                       \
+                          BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__))               \
+    throw program::ProgramArgumentsError("no valid action provided");          \
+  }
+
 #define PROGRAM_ARGS_MAIN(tname)                                               \
   int main(int argc, char const * const * const argv) try {                    \
-    tname(argc, argv).invoke();                                                \
+    return tname(argc, argv).invoke();                                         \
   } catch (program::ProgramArgumentsError const & pae) {                       \
     std::cerr << "Error in program argument handling: " << pae.what() << "\n"; \
   }

+ 1 - 4
include/program_args/arguments_impl.hpp

@@ -23,13 +23,10 @@ Arguments<Impl>::Action::operator T() const {
   if (!primed()) {
     self->actions.emplace(name, [](int argc, char const * const * argv) {
       return [=](Impl const & args) {
-        return std::make_shared<T>(argc, argv, &args);
+        return std::make_shared<T>(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();

+ 2 - 0
program_args.xcodeproj/project.pbxproj

@@ -510,6 +510,7 @@
 				SYSTEM_HEADER_SEARCH_PATHS = (
 					"external/string-utils/include/",
 					external/scoped_buffer_capture/include/,
+					/opt/local/include/,
 				);
 				USER_HEADER_SEARCH_PATHS = include/;
 			};
@@ -567,6 +568,7 @@
 				SYSTEM_HEADER_SEARCH_PATHS = (
 					"external/string-utils/include/",
 					external/scoped_buffer_capture/include/,
+					/opt/local/include/,
 				);
 				USER_HEADER_SEARCH_PATHS = include/;
 			};

+ 10 - 5
test/action_test.cpp

@@ -13,11 +13,8 @@ using testing::NotNull;
 
 namespace program {
 struct ArgumentTestHelper {
-  template <typename Impl> bool has_main(Impl const & args) const {
-    return static_cast<bool>(args.main_callback);
-  }
-  template <typename Impl> int main(Impl const & args) const {
-    return args.main_callback(args);
+  template <typename Impl> static int main(Impl const & args) {
+    return args.invoke();
   }
 };
 }
@@ -78,6 +75,8 @@ struct Git : program::Arguments<Git> {
   Push push = action("push");
 };
 
+PROGRAM_ARGS_INVOKE(Git, commit, checkout, push)
+
 std::string Push::_remote() const {
   if (Git const * git = Arguments::parent<Git>()) { return git->pwd; }
   return "";
@@ -95,6 +94,12 @@ TEST(ActionTest, ActionIsRouted) {
   EXPECT_THAT(git.commit.message, "this is a message");
 }
 
+TEST(ActionTest, CanDiveIntoTypedMain) {
+  Git git = parse<Git>({"", "-v", "commit", "-m", "this is a message"});
+  program::ArgumentTestHelper::main(git);
+  EXPECT_THAT(g_action.get<Commit>(), NotNull());
+}
+
 TEST(ActionTest, CanFetchParentInfo) {
   Git git = parse<Git>({"", "-C", "./submodules/X", "push"});
   EXPECT_THAT(git.push.remote, "./submodules/X");