├── .clang-format ├── CMakeLists.txt ├── example ├── CMakeLists.txt └── exampleUsage.cpp ├── include └── autoArgParse │ ├── argHandlers.h │ ├── argParser.cpp │ ├── argParser.h │ ├── argParserBase.h │ ├── args.h │ ├── flags.h │ ├── indentedLine.h │ └── parseException.h ├── readme.md └── src └── CMakeLists.txt /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | # We'll use defaults from the google style, but with 4 columns indentation. 3 | BasedOnStyle: google 4 | IndentWidth: 4 5 | --- 6 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | cmake_minimum_required (VERSION 3.6) 3 | 4 | project (AutoArgParse 5 | VERSION 1.0.0 6 | LANGUAGES CXX) 7 | 8 | add_subdirectory (src) 9 | 10 | if(CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR) 11 | add_subdirectory (example) 12 | endif() 13 | -------------------------------------------------------------------------------- /example/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | cmake_minimum_required (VERSION 3.6) 3 | set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -Wextra -Wall") 4 | 5 | add_executable (exampleUsage exampleUsage.cpp) 6 | target_link_libraries (exampleUsage PRIVATE autoArgParse) 7 | -------------------------------------------------------------------------------- /example/exampleUsage.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "autoArgParse/argParser.h" 4 | using namespace std; 5 | using namespace AutoArgParse; 6 | 7 | ArgParser argParser; 8 | 9 | // Let's add an optional flag -p for power 10 | // also attach a trigger which prints a message if -p is detected 11 | auto& powerFlag = argParser.add( 12 | "-p", Policy::OPTIONAL, "Specify power output.", 13 | [](const std::string&) { std::cout << "Triggered power flag\n"; }); 14 | 15 | // give -p a mandatory argument, an integer. Typesafe conversion will be used 16 | auto& powerArg = powerFlag.add>( 17 | "number_watts", Policy::MANDATORY, 18 | "An integer representing the number of watts.", 19 | chain(Converter(), IntRange(0, 50, true, true))); 20 | 21 | // Let's Add a mandatory flag speed 22 | auto& speedFlag = argParser.add("--speed", Policy::MANDATORY, 23 | "Specify the speed."); 24 | 25 | auto& exclusiveSpeed = speedFlag.makeExclusiveGroup(Policy::MANDATORY); 26 | // Add three exclusive flags, fast, medium slow 27 | // no need to provide descriptions, these are self explanatory 28 | auto& slow = exclusiveSpeed.add("slow", ""); 29 | auto& medium = exclusiveSpeed.add("medium", ""); 30 | auto& fast = exclusiveSpeed.add("fast", ""); 31 | 32 | // first demonstrating automatic parsing of file 33 | auto& file1Flag = argParser.add("--file", Policy::OPTIONAL, 34 | "Read the specified file."); 35 | auto& file1 = file1Flag.add>("file_path", Policy::MANDATORY, 36 | "Path to an existing file."); 37 | 38 | // now with custm checkso 39 | auto& file2Flag = 40 | argParser.add("--file2", Policy::OPTIONAL, 41 | "Read the specified file with custom checking."); 42 | 43 | auto& file2 = file2Flag.add>( 44 | "file_path", Policy::MANDATORY, "Path to an existing file.", 45 | [](const std::string& arg) { 46 | std::fstream stream; 47 | stream.open(arg); 48 | // custom checks, here we will just do good() 49 | if (!stream.good()) { 50 | throw ErrorMessage("custom checks of file failed."); 51 | } 52 | return stream; 53 | }); 54 | 55 | int main(const int argc, const char** argv) { 56 | argParser.validateArgs(argc, argv); 57 | // the above will print an error and exit if not all arguments were 58 | // successfully parsed 59 | // otherwise, testing flags is easy 60 | 61 | if (powerFlag) { 62 | int power = powerArg.get(); 63 | // already converted to integer 64 | cout << "Accepted power output of " << power << " W" << endl; 65 | } 66 | if (slow) { 67 | cout << "Running slowly." << endl; 68 | } else if (medium) { 69 | cout << "Running normally." << endl; 70 | } else if (fast) { 71 | cout << "running fast." << endl; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /include/autoArgParse/argHandlers.h: -------------------------------------------------------------------------------- 1 | /**This file contains types required for handling arguments. Including parsing 2 | strings into types and constraints on types.*/ 3 | 4 | #ifndef AUTOARGPARSE_ARGHANDLERS_H_ 5 | #define AUTOARGPARSE_ARGHANDLERS_H_ 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | namespace AutoArgParse { 12 | struct ErrorMessage : public std::exception { 13 | const std::string message; 14 | ErrorMessage(const std::string& message) : message(message) {} 15 | }; 16 | 17 | /** a fake converter object, should never be called, used only to assist with 18 | meta programming. */ 19 | template 20 | struct FakeDoNothingConverter { 21 | template 22 | T operator()(const Args&...) const { 23 | abort(); 24 | } 25 | }; 26 | 27 | /** 28 | * Default object for converting strings to types. Defaults to using 29 | * `istringstream` and the `>>` operator. can be specialised to use custom 30 | * parser. 31 | */ 32 | template 33 | struct Converter { 34 | inline T operator()(const std::string& stringArgToParse) const { 35 | T value; 36 | std::istringstream is(stringArgToParse); 37 | bool success(is >> value); 38 | if (!success) { 39 | throw ErrorMessage(makeErrorMessage()); 40 | } 41 | return value; 42 | } 43 | 44 | // allow custom error messages for integral, float and unsigned types 45 | static inline std::string makeErrorMessage() { 46 | std::ostringstream os; 47 | if (std::is_integral::value) { 48 | os << "Could not interpret argument as integer"; 49 | } else if (std::is_floating_point::value) { 50 | os << "Could not interpret argument as number"; 51 | } else { 52 | os << "Could not parse argument"; 53 | } 54 | if (std::is_unsigned::value) { 55 | os << " greater or equal to 0."; 56 | } else { 57 | os << "."; 58 | } 59 | return os.str(); 60 | } 61 | }; 62 | 63 | template <> 64 | struct Converter { 65 | inline std::string operator()(const std::string& stringArgToParse) const { 66 | return stringArgToParse; 67 | } 68 | }; 69 | 70 | template <> 71 | struct Converter { 72 | inline std::ifstream operator()(const std::string& stringArgToParse) const { 73 | std::ifstream inFile(stringArgToParse); 74 | if (!inFile.good()) { 75 | throw ErrorMessage("Could not open file " + stringArgToParse + 76 | " for reading."); 77 | } 78 | return inFile; 79 | } 80 | }; 81 | 82 | template <> 83 | struct Converter { 84 | inline std::ofstream operator()(const std::string& stringArgToParse) const { 85 | std::ofstream outFile(stringArgToParse); 86 | if (!outFile.good()) { 87 | throw ErrorMessage("Could not open file " + stringArgToParse + 88 | " for writing."); 89 | } 90 | return outFile; 91 | } 92 | }; 93 | 94 | /*helper classes for the chain function, a function that composes multiple 95 | * functions together*/ 96 | namespace detail { 97 | 98 | /** helper functions for ConverterChain class below */ 99 | template 100 | class Composed { 101 | Func1 func1; 102 | Func2 func2; 103 | 104 | public: 105 | Composed(Func1&& func1, Func2&& func2) 106 | : func1(std::forward(func1)), 107 | func2(std::forward(func2)) {} 108 | template 109 | inline auto operator()(T&& arg) 110 | -> decltype(func2(func1(std::forward(arg)))) { 111 | return func2(func1(std::forward(arg))); 112 | } 113 | }; 114 | template 115 | inline Composed composed(Func1&& func1, Func2&& func2) { 116 | return Composed(std::forward(func1), 117 | std::forward(func2)); 118 | } 119 | 120 | template 121 | struct ChainType; 122 | 123 | template 124 | struct ChainType { 125 | typedef Composed type; 126 | }; 127 | 128 | template 129 | struct ChainType { 130 | typedef Composed::type> type; 131 | }; 132 | } // namespace detail 133 | 134 | template 135 | inline auto chain(Func&& func) -> decltype(std::forward(func)) { 136 | return std::forward(func); 137 | } 138 | 139 | template 140 | inline typename detail::ChainType::type chain( 141 | Func&& func, Funcs&&... funcs) { 142 | return detail::composed(std::forward(func), 143 | chain(std::forward(funcs)...)); 144 | } 145 | 146 | /** 147 | * Integer range constraint 148 | */ 149 | class IntRange { 150 | public: 151 | const int min, max; 152 | const bool minInclusive, maxInclusive; 153 | IntRange(int min, int max, bool minInclusive, bool maxInclusive) 154 | : min(min), 155 | max(max), 156 | minInclusive(minInclusive), 157 | maxInclusive(maxInclusive) {} 158 | int operator()(int parsedValue) const { 159 | int testMin = (minInclusive) ? min : min + 1; 160 | int testMax = (maxInclusive) ? max : max - 1; 161 | if (parsedValue < testMin || parsedValue > testMax) { 162 | throw ErrorMessage( 163 | "Expected value to be between " + std::to_string(min) + 164 | ((minInclusive) ? "(inclusive)" : "(exclusive") + " and " + 165 | std::to_string(max) + 166 | ((maxInclusive) ? "(inclusive)" : "(exclusive)") + "."); 167 | } 168 | return parsedValue; 169 | } 170 | }; 171 | } // namespace AutoArgParse 172 | 173 | #endif /* AUTOARGPARSE_ARGHANDLERS_H_ */ 174 | -------------------------------------------------------------------------------- /include/autoArgParse/argParser.cpp: -------------------------------------------------------------------------------- 1 | #ifndef AUTOARGPARSE_ARGPARSER_CPP_ 2 | #define AUTOARGPARSE_ARGPARSER_CPP_ 3 | 4 | #include "argParser.h" 5 | #include 6 | #include "parseException.h" 7 | 8 | #if AUTOARGPARSE_HEADER_ONLY 9 | #define AUTOARGPARSE_INLINE inline 10 | #else 11 | #define AUTOARGPARSE_INLINE /* NO-OP */ 12 | #endif 13 | 14 | namespace AutoArgParse { 15 | AUTOARGPARSE_INLINE void FlagStore::parse(ArgIter& first, ArgIter& last) { 16 | int numberParsedMandatoryFlags = 0; 17 | int numberParsedMandatoryArgs = 0; 18 | while (first != last) { 19 | Policy foundPolicy; 20 | if (tryParseFlag(first, last, foundPolicy)) { 21 | if (foundPolicy == Policy::MANDATORY) { 22 | numberParsedMandatoryFlags++; 23 | } 24 | } else if (tryParseArg(first, last, foundPolicy)) { 25 | if (foundPolicy == Policy::MANDATORY) { 26 | numberParsedMandatoryArgs++; 27 | } 28 | } else { 29 | break; 30 | } 31 | } 32 | if (numberParsedMandatoryFlags != _numberMandatoryFlags) { 33 | if (first == last) { 34 | throw MissingMandatoryFlagException(*this); 35 | } else { 36 | throw UnexpectedArgException(*first, *this); 37 | } 38 | } 39 | if (numberParsedMandatoryArgs != _numberMandatoryArgs) { 40 | if (first == last) { 41 | throw MissingMandatoryArgException(*this); 42 | } else { 43 | throw UnexpectedArgException(*first, *this); 44 | } 45 | } 46 | } 47 | 48 | AUTOARGPARSE_INLINE bool FlagStore::tryParseArg(ArgIter& first, ArgIter& last, 49 | Policy& foundArgPolicy) { 50 | for (auto& argPtr : args) { 51 | if (argPtr->parsed()) { 52 | continue; 53 | } 54 | argPtr->parse(first, last); 55 | if (argPtr->parsed()) { 56 | foundArgPolicy = argPtr->policy; 57 | return true; 58 | } 59 | } 60 | return false; 61 | } 62 | 63 | AUTOARGPARSE_INLINE bool FlagStore::tryParseFlag(ArgIter& first, ArgIter& last, 64 | Policy& foundFlagPolicy) { 65 | auto flagIter = flags.find(*first); 66 | if (flagIter != end(flags)) { 67 | if (flagIter->second->parsed()) { 68 | throw RepeatedFlagException(*first); 69 | } 70 | ++first; 71 | flagIter->second->parse(first, last); 72 | foundFlagPolicy = flagIter->second->policy; 73 | return true; 74 | } else { 75 | return false; 76 | } 77 | } 78 | 79 | typedef std::pair FlagMapping; 80 | 81 | AUTOARGPARSE_INLINE void FlagStore::printUsageSummary(std::ostream& os) const { 82 | for (auto& argPtr : args) { 83 | os << " "; 84 | if (argPtr->policy == Policy::OPTIONAL) { 85 | os << "["; 86 | } 87 | os << argPtr->name; 88 | if (argPtr->policy == Policy::OPTIONAL) { 89 | os << "]"; 90 | } 91 | } 92 | for (const auto& flag : flagInsertionOrder) { 93 | auto& flagObj = flags.at(flag); 94 | os << " "; 95 | 96 | if (flagObj->policy == Policy::OPTIONAL) { 97 | os << "["; 98 | } 99 | if (!flagObj->isExclusiveGroup()) { 100 | os << flag; 101 | } 102 | flagObj->printUsageSummary(os); 103 | if (flagObj->policy == Policy::OPTIONAL) { 104 | os << "]"; 105 | } 106 | } 107 | } 108 | 109 | AUTOARGPARSE_INLINE void printUsageHelp( 110 | const std::deque& flagInsertionOrder, const FlagMap& flags, 111 | std::ostream& os, IndentedLine& lineIndent) { 112 | for (const auto& flag : flagInsertionOrder) { 113 | auto& flagObj = flags.at(flag); 114 | if (flagObj->description.size() > 0) { 115 | os << lineIndent; 116 | os << flag; 117 | flagObj->printUsageSummary(os); 118 | os << lineIndent; 119 | if (flagObj->policy == Policy::OPTIONAL) { 120 | os << "[optional] "; 121 | } 122 | os << flagObj->description; 123 | flagObj->printUsageHelp(os, lineIndent); 124 | } else if (flagObj->isExclusiveGroup()) { 125 | flagObj->printUsageHelp(os, lineIndent); 126 | } 127 | } 128 | } 129 | 130 | AUTOARGPARSE_INLINE void FlagStore::printUsageHelp( 131 | std::ostream& os, IndentedLine& lineIndent) const { 132 | lineIndent.indentLevel++; 133 | for (auto& argPtr : args) { 134 | if (argPtr->description.size() > 0) { 135 | os << lineIndent; 136 | os << argPtr->name; 137 | if (argPtr->policy == Policy::OPTIONAL) { 138 | os << " [optional]"; 139 | } 140 | std::cout << ": " << argPtr->description; 141 | } 142 | } 143 | AutoArgParse::printUsageHelp(flagInsertionOrder, flags, os, lineIndent); 144 | lineIndent.indentLevel--; 145 | } 146 | 147 | AUTOARGPARSE_INLINE void PrintGroup::printUsageHelp(std::ostream& os) const { 148 | IndentedLine lineIndent(0); 149 | if (isDefaultGroup) { 150 | argParser.printUsageHelp(os, lineIndent); 151 | return; 152 | } 153 | for (size_t index : argsToPrint) { 154 | auto& argPtr = argParser.getArgs()[index]; 155 | if (argPtr->description.size() > 0) { 156 | os << lineIndent; 157 | os << argPtr->name; 158 | if (argPtr->policy == Policy::OPTIONAL) { 159 | os << " [optional]"; 160 | } 161 | std::cout << ": " << argPtr->description; 162 | } 163 | } 164 | AutoArgParse::printUsageHelp(flagsToPrint, argParser.store.flags, os, 165 | lineIndent); 166 | } 167 | 168 | AUTOARGPARSE_INLINE void ArgParser::printSuccessfullyParsed( 169 | std::ostream& os, const char** argv, 170 | const int numberSuccessfullyParsed) const { 171 | for (int i = 0; i < numberSuccessfullyParsed; i++) { 172 | os << " " << argv[i]; 173 | } 174 | } 175 | 176 | AUTOARGPARSE_INLINE void ArgParser::validateArgs(const int argc, 177 | const char** argv, 178 | bool handleError) { 179 | stringArgs.assign(argv + 1, argv + argc); 180 | using std::begin; 181 | 182 | using std::end; 183 | auto first = begin(stringArgs); 184 | auto last = end(stringArgs); 185 | try { 186 | parse(first, last); 187 | if (first != last) { 188 | throw UnexpectedArgException(*first, this->getFlagStore()); 189 | } 190 | numberArgsSuccessfullyParsed = 191 | std::distance(begin(stringArgs), first) + 1; 192 | } catch (ParseException& e) { 193 | numberArgsSuccessfullyParsed = 194 | std::distance(begin(stringArgs), first) + 1; 195 | if (!handleError) { 196 | throw; 197 | } 198 | std::cerr << "Error: " << e.what() << std::endl; 199 | std::cerr << "Successfully parsed: "; 200 | printSuccessfullyParsed(std::cerr, argv); 201 | std::cerr << "\n\n"; 202 | printAllUsageInfo(std::cerr, argv[0]); 203 | exit(1); 204 | } catch (HelpFlagTriggeredException& e) { 205 | if (first[-1] == std::string("--help")) { 206 | printAllUsageInfo(std::cout, argv[0]); 207 | } 208 | exit(0); 209 | } 210 | } 211 | 212 | AUTOARGPARSE_INLINE void ArgParser::printAllUsageInfo( 213 | std::ostream& os, const std::string& programName) { 214 | if (helpFlag && firstTimePrinting) { 215 | firstTimePrinting = false; 216 | // help flag would have been the first thing added, move it to the end. 217 | store.rotateLeft(); 218 | } 219 | os << "Usage: " << programName; 220 | printUsageSummary(os); 221 | os << "\n\nArguments:\n"; 222 | if (printGroups.size() == 1) { 223 | printGroups.at(0).printUsageHelp(os); 224 | return; 225 | } 226 | os << "The options are divided into the following groups. Use --help " 227 | "group_name to detail the options under a particular group:\n"; 228 | bool first = true; 229 | for (auto& pg : printGroups) { 230 | if (first) { 231 | first = false; 232 | continue; 233 | } 234 | os << pg.getName() << " -- " << pg.getDescription() << std::endl; 235 | } 236 | if (helpFlag) { 237 | os << "--help prints this message.\n"; 238 | } 239 | } 240 | 241 | AUTOARGPARSE_INLINE void throwFailedArgConversionException( 242 | const std::string& name, const std::string& additionalExpl) { 243 | throw FailedArgConversionException(name, additionalExpl); 244 | } 245 | 246 | AUTOARGPARSE_INLINE void throwMoreThanOneExclusiveArgException( 247 | const std::string& conflictingFlag1, const std::string& conflictingFlag2, 248 | const std::deque& exclusiveFlags) { 249 | throw MoreThanOneExclusiveArgException(conflictingFlag1, conflictingFlag2, 250 | exclusiveFlags); 251 | } 252 | 253 | AUTOARGPARSE_INLINE void FlagStore::rotateLeft() { 254 | if (flagInsertionOrder.empty()) { 255 | return; 256 | } 257 | auto flag = std::move(flagInsertionOrder.front()); 258 | flagInsertionOrder.pop_front(); 259 | flagInsertionOrder.emplace_back(std::move(flag)); 260 | } 261 | } /* namespace AutoArgParse */ 262 | #endif /* AUTOARGPARSE_ARGPARSER_CPP_ */ 263 | -------------------------------------------------------------------------------- /include/autoArgParse/argParser.h: -------------------------------------------------------------------------------- 1 | #ifndef AUTOARGPARSE_ARGPARSER_H_ 2 | #define AUTOARGPARSE_ARGPARSER_H_ 3 | #include "argHandlers.h" 4 | #include "argParserBase.h" 5 | #include "args.h" 6 | #include "flags.h" 7 | #include "indentedLine.h" 8 | 9 | namespace AutoArgParse { 10 | class ArgParser; 11 | class PrintGroup; 12 | 13 | class PrintGroup { 14 | ArgParser& argParser; 15 | std::string name; 16 | std::string description; 17 | std::deque flagsToPrint; 18 | std::vector argsToPrint; 19 | bool isDefaultGroup; 20 | 21 | public: 22 | PrintGroup(ArgParser& argParser, const std::string& name, 23 | const std::string& description, bool isDefaultGroup = false) 24 | : argParser(argParser), 25 | name(name), 26 | description(description), 27 | isDefaultGroup(isDefaultGroup) {} 28 | inline bool active() const { 29 | return !argsToPrint.empty() || !flagsToPrint.empty(); 30 | } 31 | inline const std::string& getName() const { return name; } 32 | 33 | inline const std::string& getDescription() const { return description; } 34 | template