소스 검색

Add a readme and a way to get argument overflow.

Sam Jaffe 4 년 전
부모
커밋
d26633dd30
5개의 변경된 파일107개의 추가작업 그리고 2개의 파일을 삭제
  1. 56 0
      README.md
  2. 6 1
      include/program_args/arguments.h
  3. 1 1
      include/program_args/arguments_impl.hpp
  4. 6 0
      program_args.xcodeproj/project.pbxproj
  5. 38 0
      test/argument_test.cpp

+ 56 - 0
README.md

@@ -0,0 +1,56 @@
+#  Fluent Argument Parser
+A tool for the processing of command-line arguments to a C++ program using a fluent interface. No need for writing a constructor or having to create a long list of bindings in main.
+
+## Installation
+
+## Usage
+
+
+### Arguments
+Positional arguments are provided with their zero-based indices. Arguments can be declared as optional, but you cannot include required arguments with a higher index than any optional positional arg.
+
+Arguments always have an arity of 1, and so cannot store information into a container. Arguments past the end requested will be accessible in a public member function called `args()`.
+
+### Options
+Options are provided as a long-form string name, and input into the command line in the form `--name value`. Options can map to either singular values (e.g. std::string, int) or containers (e.g. std::vector<T>). Options mapped to containers are repeatable, so you can provide the flag more than one time. Those mapped to singular values will generate an error if repeated.  
+In either case, options with an arity greater than one are not allowed.
+
+Abbreviated options support key-value concatenation, such as how you can do `-I/path/to/my/include/dir` in gcc/clang.
+
+#### Snippets
+
+Singular option storage cannot be repeated
+```c++
+std::string directory = option("logdir");
+
+$ ./a.out --logdir /var/log --logdir /usr/local/var/log
+Error in program argument handling: Repeated option not allowed for argument logdir
+```
+
+Abbreviations are not automatically generated
+```c++
+std::string directory = option("logdir");
+
+$ ./a.out -l /var/log
+Error in program argument handling: Unknown argument provided: -l
+```
+
+Pairs/Tuples don't get to use increased arity
+```c++
+std::pair<int, int> bounds = options("bounds");
+std::cout << args() << std::endl;
+
+$ ./a.out --bounds 1920 1080
+[ "1080" ]
+```
+
+### Flags
+Flags are a sub-class of options who do not have a follow-on value, but instead change the state of the given object through being called.
+
+Flags are supported for the types of `bool` and `int` only.  
+With boolean flags, in addition to the `--name` argument created and the abbreviated form, a `--no-name` argument will be registered that sets the option value to false (in case the default is set to true).  
+Integer flags cannot have default values, and do not have inverted forms. Instead, it is possible to repeat an integer flag endlessly, incrementing it by one with each appearance.
+
+Abbreviated flags have the additional feature that they can be concatenated, and integer flags can be number-repeated.  
+For example, suppose that both `-v` and `-k` are valid flags, representing `int verbosity` and `bool dry_run` respectively. Then the following are all valid input tokens: `-vk`, `-vv`, `-vvvk`, and `-v5`. The argument `-v5k` will still generate a parse error, however.
+

+ 6 - 1
include/program_args/arguments.h

@@ -19,6 +19,10 @@ public:
   Arguments() = default;
   Arguments(int argc, char const * const * const argv);
 
+  std::vector<std::string> args() const {
+    return {arguments.begin() + argument_names.size(), arguments.end()};
+  }
+
 protected:
   auto argument(size_t index, std::string const & name,
                 std::string const & description = "");
@@ -52,7 +56,8 @@ private:
 
   // Data/Output variables
   std::string program;
-  size_t optional_from{std::numeric_limits<size_t>::max()};
+  constexpr static size_t const no_optional_args{~0ul};
+  size_t optional_from{no_optional_args};
   std::vector<std::string> arguments;
   std::map<std::string, std::vector<std::string>> options;
   std::map<std::string, int> flags;

+ 1 - 1
include/program_args/arguments_impl.hpp

@@ -152,7 +152,7 @@ template <typename Impl> void Arguments<Impl>::usage() const {
   for (auto & [index, name] : argument_names) {
     std::cout << " " << (index == optional_from ? "[" : "") << name;
   }
-  if (optional_from != std::numeric_limits<size_t>::max()) { std::cout << "]"; }
+  if (optional_from != no_optional_args) { std::cout << "]"; }
   std::cout << "\nArgument Arguments:\n";
   for (auto & [name, desc] : argument_descriptions) {
     std::cout << "  " << name << ": " << desc << "\n";

+ 6 - 0
program_args.xcodeproj/project.pbxproj

@@ -10,6 +10,7 @@
 		CD8C5A8925D057900004A6D9 /* GoogleMock.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CDE4F79B25CF316A009E4EC1 /* GoogleMock.framework */; };
 		CD8C5A8B25D057AA0004A6D9 /* options_test.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CD8C5A8A25D057AA0004A6D9 /* options_test.cpp */; };
 		CD8C5AA025D06D0B0004A6D9 /* flag_test.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CD8C5A9F25D06D0B0004A6D9 /* flag_test.cpp */; };
+		CD8C5AA425D072F50004A6D9 /* argument_test.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CD8C5AA325D072F50004A6D9 /* argument_test.cpp */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXContainerItemProxy section */
@@ -60,6 +61,8 @@
 		CD8C5A9325D057C00004A6D9 /* exception.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = exception.h; sourceTree = "<group>"; };
 		CD8C5A9625D058470004A6D9 /* xcode_gtest_helper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = xcode_gtest_helper.h; sourceTree = "<group>"; };
 		CD8C5A9F25D06D0B0004A6D9 /* flag_test.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = flag_test.cpp; sourceTree = "<group>"; };
+		CD8C5AA325D072F50004A6D9 /* argument_test.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = argument_test.cpp; sourceTree = "<group>"; };
+		CDD334A025D200AB008540EE /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
 		CDE4F78A25CF309E009E4EC1 /* libprogram_args.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libprogram_args.a; sourceTree = BUILT_PRODUCTS_DIR; };
 		CDE4F79325CF316A009E4EC1 /* GoogleMock.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = GoogleMock.xcodeproj; path = "../../../gmock-xcode-master/GoogleMock.xcodeproj"; sourceTree = "<group>"; };
 		CDE4F7A225CF317C009E4EC1 /* program_args */ = {isa = PBXFileReference; lastKnownFileType = folder; name = program_args; path = include/program_args; sourceTree = "<group>"; };
@@ -121,6 +124,7 @@
 		CDE4F78125CF309E009E4EC1 = {
 			isa = PBXGroup;
 			children = (
+				CDD334A025D200AB008540EE /* README.md */,
 				CDE4F79325CF316A009E4EC1 /* GoogleMock.xcodeproj */,
 				CD8C5A8E25D057C00004A6D9 /* include */,
 				CDE4F7A225CF317C009E4EC1 /* program_args */,
@@ -144,6 +148,7 @@
 			isa = PBXGroup;
 			children = (
 				CD8C5A9625D058470004A6D9 /* xcode_gtest_helper.h */,
+				CD8C5AA325D072F50004A6D9 /* argument_test.cpp */,
 				CD8C5A8A25D057AA0004A6D9 /* options_test.cpp */,
 				CD8C5A9F25D06D0B0004A6D9 /* flag_test.cpp */,
 			);
@@ -298,6 +303,7 @@
 			files = (
 				CD8C5A8B25D057AA0004A6D9 /* options_test.cpp in Sources */,
 				CD8C5AA025D06D0B0004A6D9 /* flag_test.cpp in Sources */,
+				CD8C5AA425D072F50004A6D9 /* argument_test.cpp in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};

+ 38 - 0
test/argument_test.cpp

@@ -0,0 +1,38 @@
+//
+//  argument_test.cpp
+//  program_args-test
+//
+//  Created by Sam Jaffe on 2/7/21.
+//
+
+#include "program_args/arguments.h"
+
+#include "xcode_gtest_helper.h"
+
+using testing::ElementsAre;
+using testing::Eq;
+
+template <typename T, size_t N> static T parse(char const * const (&argv)[N]) {
+  return T(N, argv);
+}
+
+struct ArgumentTest : program::Arguments<ArgumentTest> {
+  using program::Arguments<ArgumentTest>::Arguments;
+
+  std::string arg0 = argument(0, "arg0");
+};
+
+TEST(ArgumentTest, ThrowsIfMissingArg) {
+  EXPECT_THROW(parse<ArgumentTest>({""}), program::IllegalPositionError);
+}
+
+TEST(ArgumentTest, ProcessesArgument) {
+  auto const options = parse<ArgumentTest>({"", "value"});
+  EXPECT_THAT(options.arg0, Eq("value"));
+}
+
+TEST(ArgumentTest, HasNoIssueWithOverflowArg) {
+  auto const options = parse<ArgumentTest>({"", "value", "extra"});
+  EXPECT_THAT(options.arg0, Eq("value"));
+  EXPECT_THAT(options.args(), ElementsAre("extra"));
+}