Browse Source

feat: allow fallback action to be self for e.g. plugin dispatch

Sam Jaffe 2 years ago
parent
commit
c3c88f3284
3 changed files with 29 additions and 15 deletions
  1. 3 2
      include/program_args/arguments.h
  2. 8 4
      include/program_args/arguments_impl.hpp
  3. 18 9
      test/action_test.cpp

+ 3 - 2
include/program_args/arguments.h

@@ -159,8 +159,9 @@ private:
 
   operator bool() const { return primed_; }
 
+  auto const & self() const { return static_cast<Impl const &>(*this); }
   template <typename... Parents> int invoke(Parents const &... parents) const {
-    return typed_main(parents..., static_cast<Impl const &>(*this));
+    return typed_main(parents..., self());
   }
 
   template <typename T>
@@ -266,7 +267,7 @@ int typed_main(Args const &, Actions const &...);
   template <typename... Parents>                                               \
   int invoke(Parents const &... parents) const {                               \
     return invoke_action(default, program::actions(__VA_ARGS__),               \
-                         program::count(__VA_ARGS__), parents..., *this);      \
+                         program::count(__VA_ARGS__), parents...);             \
   }                                                                            \
   using Arguments::Arguments
 

+ 8 - 4
include/program_args/arguments_impl.hpp

@@ -352,9 +352,9 @@ int Arguments<Impl>::invoke_action(DA const & default_action, T const & tuple,
   // Only actions that were reached in the constructor will ever have the
   // primed_ flag set to true. Therefore, there is a guarantee that this
   // function will only call invoke() on a child one or zero times.
-  auto invoke_one = [&ps...](auto const & action) {
+  auto invoke_one = [this, &ps...](auto const & action) {
     if (!action.primed_) { return std::make_pair(false, 0); }
-    return std::make_pair(true, action.invoke(parents...));
+    return std::make_pair(true, action.invoke(ps..., self()));
   };
 
   // Because of that, it is safe to use a map from bool => status
@@ -363,11 +363,15 @@ int Arguments<Impl>::invoke_action(DA const & default_action, T const & tuple,
   if (rval.count(true)) { return rval.at(true); }
 
   // If no action was invoked, then we need to handle the fallthrough case
-  if constexpr (std::is_same_v<Default, usage_on_error_t>) {
+  // For certain circumstances, such as how git allows custom actions with
+  // a plugin system, we allow you to specify a default action of self.
+  if constexpr (std::is_same_v<DA, require_action_t>) {
     usage();
     return 1;
+  } else if constexpr (std::is_same_v<DA, Impl>) {
+    return typed_main(ps..., self());
   } else {
-    return default_action.invoke(parents...);
+    return default_action.invoke(ps..., self());
   }
 }
 

+ 18 - 9
test/action_test.cpp

@@ -25,15 +25,10 @@ template <typename T, size_t N> static T parse(char const * const (&argv)[N]) {
 
 program::detail::Any g_action;
 
-template <typename Args, typename Action>
-int typed_main(Args const &, Action const & action) {
-  g_action = program::detail::Any(&action);
-  return 0;
-}
-
-template <typename Args, typename Action, typename SubAction>
-int typed_main(Args const &, Action const &, SubAction const & action) {
-  g_action = program::detail::Any(&action);
+template <typename Arg, typename... Args>
+int typed_main(Arg const &arg, Args const &...args) {
+  auto const &last = std::get<sizeof...(Args)>(std::forward_as_tuple(arg, args...));
+  g_action = program::detail::Any(&last);
   return 0;
 }
 
@@ -103,6 +98,13 @@ struct Git : program::Arguments<Git> {
   Remote remote = action("remote");
 };
 
+struct GitPlug : program::Arguments<GitPlug> {
+  PROGRAM_ARGS_INVOKE_WITH_DEFAULT(*this, commit, checkout);
+
+  Commit commit = action("commit");
+  Checkout checkout = action("checkout");
+};
+
 std::string Push::_remote() const {
   if (Git const * git = Arguments::parent<Git>()) { return git->pwd; }
   return "";
@@ -152,3 +154,10 @@ TEST(ActionTest, CanStoreDefaultAction) {
   program::ArgumentTestHelper::main(git);
   EXPECT_THAT(g_action.get<Show>(), NotNull());
 }
+
+TEST(ActionTest, DefaultActionCanBeSelf) {
+  GitPlug git = parse<GitPlug>({""});
+
+  program::ArgumentTestHelper::main(git);
+  EXPECT_THAT(g_action.get<GitPlug>(), NotNull());
+}