From 50bd1722d22590222cb353a02c7f234dec0ecbb5 Mon Sep 17 00:00:00 2001 From: Thraix Date: Tue, 12 May 2026 23:31:16 +0200 Subject: [PATCH] Initial commit for ArgParser --- .clang-format | 334 ++++++++++++++++++++++++++++++++++++++++++++++ .gitignore | 3 + LICENSE | 21 +++ makegen.xml | 32 +++++ src/ActionArg.cpp | 54 ++++++++ src/ActionArg.h | 19 +++ src/Arg.cpp | 41 ++++++ src/Arg.h | 26 ++++ src/ArgParser.cpp | 228 +++++++++++++++++++++++++++++++ src/ArgParser.h | 70 ++++++++++ src/FlagArg.cpp | 55 ++++++++ src/FlagArg.h | 17 +++ src/Utils.cpp | 173 ++++++++++++++++++++++++ src/Utils.h | 78 +++++++++++ src/ValueArg.h | 122 +++++++++++++++++ src/main.cpp | 109 +++++++++++++++ 16 files changed, 1382 insertions(+) create mode 100644 .clang-format create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 makegen.xml create mode 100644 src/ActionArg.cpp create mode 100644 src/ActionArg.h create mode 100644 src/Arg.cpp create mode 100644 src/Arg.h create mode 100644 src/ArgParser.cpp create mode 100644 src/ArgParser.h create mode 100644 src/FlagArg.cpp create mode 100644 src/FlagArg.h create mode 100644 src/Utils.cpp create mode 100644 src/Utils.h create mode 100644 src/ValueArg.h create mode 100644 src/main.cpp diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..0dd9657 --- /dev/null +++ b/.clang-format @@ -0,0 +1,334 @@ +--- +Language: Cpp +AccessModifierOffset: -2 +AlignAfterOpenBracket: Align +AlignArrayOfStructures: None +AlignConsecutiveAssignments: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionDeclarations: false + AlignFunctionPointers: false + PadOperators: true +AlignConsecutiveBitFields: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionDeclarations: false + AlignFunctionPointers: false + PadOperators: false +AlignConsecutiveDeclarations: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionDeclarations: true + AlignFunctionPointers: false + PadOperators: false +AlignConsecutiveMacros: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionDeclarations: false + AlignFunctionPointers: false + PadOperators: false +AlignConsecutiveShortCaseStatements: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCaseArrows: false + AlignCaseColons: false +AlignConsecutiveTableGenBreakingDAGArgColons: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionDeclarations: false + AlignFunctionPointers: false + PadOperators: false +AlignConsecutiveTableGenCondOperatorColons: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionDeclarations: false + AlignFunctionPointers: false + PadOperators: false +AlignConsecutiveTableGenDefinitionColons: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionDeclarations: false + AlignFunctionPointers: false + PadOperators: false +AlignEscapedNewlines: Left +AlignOperands: Align +AlignTrailingComments: + Kind: Always + OverEmptyLines: 0 +AllowAllArgumentsOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowBreakBeforeNoexceptSpecifier: Never +AllowShortBlocksOnASingleLine: Never +AllowShortCaseExpressionOnASingleLine: true +AllowShortCaseLabelsOnASingleLine: false +AllowShortCompoundRequirementOnASingleLine: true +AllowShortEnumsOnASingleLine: true +AllowShortFunctionsOnASingleLine: false +AllowShortIfStatementsOnASingleLine: Never +AllowShortLambdasOnASingleLine: All +AllowShortLoopsOnASingleLine: true +AllowShortNamespacesOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakBeforeMultilineStrings: true +AttributeMacros: + - __capability + - absl_nonnull + - absl_nullable + - absl_nullability_unknown +BinPackArguments: false +BinPackLongBracedList: true +BinPackParameters: false +BitFieldColonSpacing: Both +BracedInitializerIndentWidth: -1 +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: Never + AfterEnum: false + AfterExternBlock: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + BeforeCatch: false + BeforeElse: false + BeforeLambdaBody: false + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakAdjacentStringLiterals: true +BreakAfterAttributes: Leave +BreakAfterJavaFieldAnnotations: false +BreakAfterReturnType: None +BreakArrays: true +BreakBeforeBinaryOperators: None +BreakBeforeConceptDeclarations: Always +BreakBeforeBraces: Allman +BreakBeforeInlineASMColon: OnlyMultiline +BreakBeforeTemplateCloser: false +BreakBeforeTernaryOperators: true +BreakBinaryOperations: Never +BreakConstructorInitializers: BeforeColon +BreakFunctionDefinitionParameters: false +BreakInheritanceList: BeforeColon +BreakStringLiterals: true +BreakTemplateDeclarations: Yes +ColumnLimit: 120 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerIndentWidth: 2 +ContinuationIndentWidth: 2 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +EmptyLineAfterAccessModifier: Never +EmptyLineBeforeAccessModifier: LogicalBlock +EnumTrailingComma: Leave +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: false +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IfMacros: + - KJ_IF_MAYBE +IncludeBlocks: Regroup +IncludeCategories: + - Regex: '^' + Priority: 2 + SortPriority: 0 + CaseSensitive: false + - Regex: '^<.*\.h>' + Priority: 1 + SortPriority: 0 + CaseSensitive: false + - Regex: '^<.*' + Priority: 2 + SortPriority: 0 + CaseSensitive: false + - Regex: '.*' + Priority: 3 + SortPriority: 0 + CaseSensitive: false +IncludeIsMainRegex: '([-_](test|unittest))?$' +IncludeIsMainSourceRegex: '' +IndentAccessModifiers: false +IndentCaseBlocks: false +IndentCaseLabels: true +IndentExportBlock: true +IndentExternBlock: AfterExternBlock +IndentGotoLabels: true +IndentPPDirectives: None +IndentRequiresClause: true +IndentWidth: 2 +IndentWrappedFunctionNames: false +InsertBraces: false +InsertNewlineAtEOF: false +InsertTrailingCommas: None +IntegerLiteralSeparator: + Binary: 0 + BinaryMinDigits: 0 + Decimal: 0 + DecimalMinDigits: 0 + Hex: 0 + HexMinDigits: 0 +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLines: + AtEndOfFile: false + AtStartOfBlock: false + AtStartOfFile: true +KeepFormFeed: false +LambdaBodyIndentation: Signature +LineEnding: DeriveLF +MacroBlockBegin: '' +MacroBlockEnd: '' +MainIncludeChar: Quote +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: All +ObjCBinPackProtocolList: Never +ObjCBlockIndentWidth: 2 +ObjCBreakBeforeNestedBlockParam: true +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +OneLineFormatOffRegex: '' +PackConstructorInitializers: Never +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 1 +PenaltyBreakBeforeMemberAccess: 150 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakOpenParenthesis: 0 +PenaltyBreakScopeResolution: 500 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyIndentedWhitespace: 0 +PenaltyReturnTypeOnItsOwnLine: 200 +PointerAlignment: Left +PPIndentWidth: -1 +QualifierAlignment: Leave +RawStringFormats: + - Language: Cpp + Delimiters: + - cc + - CC + - cpp + - Cpp + - CPP + - 'c++' + - 'C++' + CanonicalDelimiter: '' + BasedOnStyle: google + - Language: TextProto + Delimiters: + - pb + - PB + - proto + - PROTO + EnclosingFunctions: + - EqualsProto + - EquivToProto + - PARSE_PARTIAL_TEXT_PROTO + - PARSE_TEST_PROTO + - PARSE_TEXT_PROTO + - ParseTextOrDie + - ParseTextProtoOrDie + - ParseTestProto + - ParsePartialTestProto + CanonicalDelimiter: pb + BasedOnStyle: google +ReferenceAlignment: Pointer +ReflowComments: Always +RemoveBracesLLVM: false +RemoveEmptyLinesInUnwrappedLines: false +RemoveParentheses: Leave +RemoveSemicolon: false +RequiresClausePosition: OwnLine +RequiresExpressionIndentation: OuterScope +SeparateDefinitionBlocks: Always +ShortNamespaceLines: 1 +SkipMacroDefinitionBody: false +SortIncludes: + Enabled: true + IgnoreCase: false +SortJavaStaticImport: Before +SortUsingDeclarations: LexicographicNumeric +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterOperatorKeyword: false +SpaceAfterTemplateKeyword: true +SpaceAroundPointerQualifiers: Default +SpaceBeforeAssignmentOperators: true +SpaceBeforeCaseColon: false +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeJsonColon: false +SpaceBeforeParens: ControlStatements +SpaceBeforeParensOptions: + AfterControlStatements: true + AfterForeachMacros: true + AfterFunctionDefinitionName: false + AfterFunctionDeclarationName: false + AfterIfMacros: true + AfterNot: false + AfterOverloadedOperator: false + AfterPlacementOperator: true + AfterRequiresInClause: false + AfterRequiresInExpression: false + BeforeNonEmptyParentheses: false +SpaceBeforeRangeBasedForLoopColon: true +SpaceBeforeSquareBrackets: false +SpaceInEmptyBlock: false +SpacesBeforeTrailingComments: 2 +SpacesInAngles: Never +SpacesInContainerLiterals: true +SpacesInLineCommentPrefix: + Minimum: 1 + Maximum: -1 +SpacesInParens: Never +SpacesInParensOptions: + ExceptDoubleParentheses: false + InCStyleCasts: false + InConditionalStatements: false + InEmptyParentheses: false + Other: false +SpacesInSquareBrackets: false +Standard: Auto +StatementAttributeLikeMacros: + - Q_EMIT +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TableGenBreakInsideDAGArg: DontBreak +TabWidth: 2 +UseTab: Never +VerilogBreakBetweenInstancePorts: true +WhitespaceSensitiveMacros: + - BOOST_PP_STRINGIZE + - CF_SWIFT_NAME + - NS_SWIFT_NAME + - PP_STRINGIZE + - STRINGIZE +WrapNamespaceBodyWithEmptyLines: Leave +... + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d8ae818 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +bin/ +compile_flags.txt +Makefile diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ff9d431 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Tim HÃ¥kansson + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/makegen.xml b/makegen.xml new file mode 100644 index 0000000..06dd6b2 --- /dev/null +++ b/makegen.xml @@ -0,0 +1,32 @@ + + + false + bin/Test/ + arg_parser + executable + ArgParser + src/ + + + false + bin/Release + libarg_parser.so + sharedlibrary + main.cpp + ArgParser + src/ + + + -g3 + -w + _DEBUG + false + bin/Debug/ + arg_parser + executable + ArgParser + src/ + + Release + v1.3.8 + diff --git a/src/ActionArg.cpp b/src/ActionArg.cpp new file mode 100644 index 0000000..7b176fa --- /dev/null +++ b/src/ActionArg.cpp @@ -0,0 +1,54 @@ +#include "ActionArg.h" + +#include + +namespace ap +{ + ActionArg::ActionArg(const std::string& argument, const std::string& helpText, std::function action) + : Arg{argument, helpText, false, true}, + action{action} + { + } + + bool ActionArg::PartialMatch(const std::string& arg) const + { + for (const auto& argument : arguments) + { + if (argument == arg) + return true; + } + return false; + } + + bool ActionArg::Parse(const std::vector& arguments, size_t& index) + { + action(); + return true; + } + + std::string ActionArg::GetHelpArgument(bool onlyFirst) const + { + std::stringstream ss; + + if (onlyFirst) + { + if (required) + { + ss << arguments.front(); + } + else + { + ss << "[" << arguments.front() << "]"; + } + } + else + { + ss << arguments.front(); + for (int i = 1; i < arguments.size(); i++) + { + ss << ", " << arguments[i]; + } + } + return ss.str(); + } +} diff --git a/src/ActionArg.h b/src/ActionArg.h new file mode 100644 index 0000000..7c4ea3b --- /dev/null +++ b/src/ActionArg.h @@ -0,0 +1,19 @@ +#pragma once + +#include + +#include "Arg.h" + +namespace ap +{ + struct ActionArg : public Arg + { + std::function action; + + ActionArg(const std::string& argument, const std::string& helpText, std::function action); + + bool PartialMatch(const std::string& arg) const override; + bool Parse(const std::vector& arguments, size_t& index) override; + std::string GetHelpArgument(bool onlyFirst) const override; + }; +} diff --git a/src/Arg.cpp b/src/Arg.cpp new file mode 100644 index 0000000..d271cb9 --- /dev/null +++ b/src/Arg.cpp @@ -0,0 +1,41 @@ +#include "Arg.h" + +namespace ap +{ + Arg::Arg(const std::string& argument, const std::string& helpText, bool required, bool overridesMandatory) + : helpText{helpText}, + required{required}, + overridesMandatory{overridesMandatory} + { + size_t lastPos = 0; + size_t pos = argument.find(','); + while (pos != std::string::npos) + { + arguments.emplace_back(argument.substr(lastPos, (pos - lastPos))); + lastPos = pos + 1; + pos = argument.find(',', lastPos); + } + arguments.emplace_back(argument.substr(lastPos)); + } + + bool Arg::Match(const std::string& arg) const + { + for (const auto& argument : arguments) + { + if (argument == arg) + return true; + } + + return MatchAdditional(arg); + } + + bool Arg::PartialMatch(const std::string& arg) const + { + return false; + } + + bool Arg::MatchAdditional(const std::string& arg) const + { + return false; + } +} diff --git a/src/Arg.h b/src/Arg.h new file mode 100644 index 0000000..6b15343 --- /dev/null +++ b/src/Arg.h @@ -0,0 +1,26 @@ +#pragma once + +#include +#include + +namespace ap +{ + struct Arg + { + std::vector arguments; + std::string helpText; + bool required; + bool overridesMandatory; + bool wasSpecified{false}; + + Arg(const std::string& argument, const std::string& helpText, bool required, bool overridesMandatory); + + bool Match(const std::string& arg) const; + virtual bool PartialMatch(const std::string& arg) const; + + virtual bool MatchAdditional(const std::string& arg) const; + + virtual bool Parse(const std::vector& arguments, size_t& index) = 0; + virtual std::string GetHelpArgument(bool onlyFirst) const = 0; + }; +} diff --git a/src/ArgParser.cpp b/src/ArgParser.cpp new file mode 100644 index 0000000..3ceba81 --- /dev/null +++ b/src/ArgParser.cpp @@ -0,0 +1,228 @@ +#include "ArgParser.h" + +#include +#include + +#include "ActionArg.h" +#include "FlagArg.h" + +const int HELP_WIDTH = 25; +const int MAX_WIDTH = 80; + +namespace ap +{ + ArgParser::ArgParser(const std::string& commandName, + const std::string& description, + const std::string& additionalHelp, + const std::vector& arguments) + : commandName{commandName}, + description{description}, + additionalHelp{additionalHelp}, + argumentStrings{arguments} + { + AddAction("--help,-h", "Displays this information", [&]() { PrintHelp(); }); + } + + ArgParser::ArgParser(const std::string& commandName, + const std::string& description, + const std::string& additionalHelp, + int argc, + char** argv) + : commandName{commandName}, + description{description}, + additionalHelp{additionalHelp} + { + AddAction("--help,-h", "Displays this information", [&]() { PrintHelp(); }); + for (int i = 0; i < argc; i++) + { + argumentStrings.emplace_back(argv[i]); + } + } + + void ArgParser::AddAction(const std::string& arg, const std::string& helpText, std::function action) + { + args.emplace_back(std::make_shared(arg, helpText, action)); + } + + void ArgParser::AddFlag(const std::string& arg, bool& flag, const std::string& helpText) + { + args.emplace_back(std::make_shared(flag, arg, helpText)); + } + + void ArgParser::PrintHelp() const + { + size_t pos = 0; + size_t offset = 0; + Utils::PrintMaxWidthSplit(description, pos, offset, MAX_WIDTH, " "); + + std::cout << std::endl; + std::cout << "Usage: " << std::endl; + PrintCommandHelp(false); + PrintCommandHelp(true); + + std::cout << std::endl; + std::cout << "Options: " << std::endl; + + size_t width = 0; + for (auto& arg : args) + { + std::string helpArg = arg->GetHelpArgument(false); + if (helpArg.size() + 4 < HELP_WIDTH) + width = std::max(width, helpArg.size()); + } + width = std::min(size_t(HELP_WIDTH), width + 4); + + for (const auto& arg : args) + { + PrintArgHelp(*arg, width); + } + + std::cout << std::endl; + pos = 0; + offset = 0; + Utils::PrintMaxWidthSplit(additionalHelp, pos, offset, MAX_WIDTH, " "); + } + + void ArgParser::PrintError() const + { + std::cout << "Use --help for more info" << std::endl; + } + + bool ArgParser::Parse() + { + bool anyOverridesMandatory = false; + for (size_t i = 0; i < argumentStrings.size(); i++) + { + auto it = std::find_if(args.begin(), + args.end(), + [&](const std::shared_ptr& arg) + { return arg->overridesMandatory && arg->Match(argumentStrings[i]); }); + if (it != args.end()) + { + (*it)->Parse(argumentStrings, i); + anyOverridesMandatory = true; + } + } + + if (anyOverridesMandatory) + { + return false; + } + + for (size_t i = 0; i < argumentStrings.size(); i++) + { + auto it = std::find_if( + args.begin(), args.end(), [&](const std::shared_ptr& arg) { return arg->Match(argumentStrings[i]); }); + if (it != args.end()) + { + if (!(*it)->Parse(argumentStrings, i)) + { + PrintError(); + return false; + } + continue; + } + + if (Utils::IsMultiFlag(argumentStrings[i])) + { + std::vector>::iterator> iterators; + for (size_t j = 1; j < argumentStrings[i].size(); j++) + { + std::string flag = std::string("-") + argumentStrings[i][j]; + auto it = + std::find_if(args.begin(), args.end(), [&](const std::shared_ptr& arg) { return arg->Match(flag); }); + if (it == args.end()) + { + std::cout << "Unknown flag: \"" << flag << "\" in \"" << argumentStrings[i] << "\"" << std::endl + << std::endl + << std::endl; + PrintError(); + return false; + } + iterators.emplace_back(it); + } + + for (auto it : iterators) + { + if (!(*it)->Parse(argumentStrings, i)) + { + PrintError(); + return false; + } + if ((*it)->overridesMandatory) + { + anyOverridesMandatory = true; + } + } + continue; + } + + std::cout << "Unknown argment: \"" << argumentStrings[i] << "\"" << std::endl << std::endl; + PrintError(); + return false; + } + + if (anyOverridesMandatory) + { + return false; + } + + for (const auto& arg : args) + { + if (arg->required && !arg->wasSpecified) + { + std::cout << "Mandatory argument not specified: " << arg->arguments.front() << std::endl << std::endl; + PrintError(); + return false; + } + } + return true; + } + + void ArgParser::PrintCommandHelp(bool actions) const + { + std::cout << " " << commandName; + size_t offset = 4; + if (commandName.size() - 3 > HELP_WIDTH) + std::cout << std::endl << " "; + else + { + std::cout << " "; + offset = commandName.size() + 3; + } + + size_t pos = offset; + PrintCommandArgsHelp(pos, offset, actions, true); + PrintCommandArgsHelp(pos, offset, actions, false); + std::cout << std::endl; + } + + void ArgParser::PrintCommandArgsHelp(size_t& pos, size_t offset, bool actions, bool required) const + { + for (auto& arg : args) + { + if (arg->overridesMandatory == actions && arg->required == required) + { + Utils::PrintMaxWidth(arg->GetHelpArgument(true), pos, offset, MAX_WIDTH); + } + } + } + + void ArgParser::PrintArgHelp(const Arg& arg, size_t offset) const + { + std::string helpArg = arg.GetHelpArgument(false); + if (helpArg.size() + 4 >= HELP_WIDTH) + { + std::cout << " " << helpArg << std::endl; + std::cout << std::setw(offset) << ""; + size_t pos = offset; + Utils::PrintMaxWidthSplit(arg.helpText, pos, offset, MAX_WIDTH, " "); + } + else + { + std::cout << " " << std::setw(offset - 2) << std::left << helpArg; + size_t pos = offset; + Utils::PrintMaxWidthSplit(arg.helpText, pos, offset, MAX_WIDTH, " "); + } + } +} diff --git a/src/ArgParser.h b/src/ArgParser.h new file mode 100644 index 0000000..6f2884b --- /dev/null +++ b/src/ArgParser.h @@ -0,0 +1,70 @@ +#include + +#include +#include +#include +#include +#include + +#include "Arg.h" +#include "ValueArg.h" + +namespace ap +{ + struct ArgParser + { + std::string commandName; + std::string description; + std::string additionalHelp; + std::vector argumentStrings; + std::vector> args; + + ArgParser(const std::string& commandName, + const std::string& description, + const std::string& additionalHelp, + const std::vector& arguments); + + ArgParser(const std::string& commandName, + const std::string& description, + const std::string& additionalHelp, + int argc, + char** argv); + + void AddAction(const std::string& arg, const std::string& helpText, std::function action); + + template + void Add(const std::string& arg, T& value, const std::string& helpText) + { + args.emplace_back(std::make_shared>(value, arg, helpText, true)); + } + + template + void Add(const std::string& arg, T& value, const std::string& helpText, const T& defaultValue) + { + value = defaultValue; + std::stringstream ss; + ss << helpText << " (default=" << defaultValue << ")"; + args.emplace_back(std::make_shared>(value, arg, ss.str(), false)); + } + + template + void AddOptional(const std::string& arg, std::optional& value, const std::string& helpText) + { + value = std::nullopt; + std::stringstream ss; + ss << helpText << " (default=null)"; + args.emplace_back(std::make_shared>>(value, arg, ss.str(), false)); + } + + void AddFlag(const std::string& arg, bool& flag, const std::string& helpText); + + bool Parse(); + + private: + void PrintCommandHelp(bool actions) const; + void PrintCommandArgsHelp(size_t& pos, size_t offset, bool actions, bool required) const; + void PrintArgHelp(const Arg& arg, size_t offset) const; + void PrintHelp() const; + void PrintError() const; + }; +} diff --git a/src/FlagArg.cpp b/src/FlagArg.cpp new file mode 100644 index 0000000..d5089a0 --- /dev/null +++ b/src/FlagArg.cpp @@ -0,0 +1,55 @@ +#include "FlagArg.h" + +#include + +namespace ap +{ + FlagArg::FlagArg(bool& flag, const std::string& argument, const std::string& helpText) + : Arg{argument, helpText, false, false}, + flag{flag} + { + flag = false; + } + + bool FlagArg::PartialMatch(const std::string& arg) const + { + for (const auto& argument : arguments) + { + if (argument == arg) + return true; + } + return false; + } + + bool FlagArg::Parse(const std::vector& arguments, size_t& index) + { + flag = true; + return true; + } + + std::string FlagArg::GetHelpArgument(bool onlyFirst) const + { + std::stringstream ss; + + if (onlyFirst) + { + if (required) + { + ss << arguments.front(); + } + else + { + ss << "[" << arguments.front() << "]"; + } + } + else + { + ss << arguments.front(); + for (int i = 1; i < arguments.size(); i++) + { + ss << ", " << arguments[i]; + } + } + return ss.str(); + } +} diff --git a/src/FlagArg.h b/src/FlagArg.h new file mode 100644 index 0000000..c8d71d0 --- /dev/null +++ b/src/FlagArg.h @@ -0,0 +1,17 @@ +#pragma once + +#include "Arg.h" + +namespace ap +{ + struct FlagArg : public Arg + { + bool& flag; + + FlagArg(bool& flag, const std::string& argument, const std::string& helpText); + + bool PartialMatch(const std::string& arg) const override; + bool Parse(const std::vector& arguments, size_t& index) override; + std::string GetHelpArgument(bool onlyFirst) const override; + }; +} diff --git a/src/Utils.cpp b/src/Utils.cpp new file mode 100644 index 0000000..a28c073 --- /dev/null +++ b/src/Utils.cpp @@ -0,0 +1,173 @@ +#include "Utils.h" + +#include +#include + +namespace ap +{ + template <> + std::pair ParseValue(const std::string_view& str) + { + try + { + size_t index = 0; + int i = std::stoi(std::string(str), &index); + + if (index != str.size()) + return {false, 0}; + + return {true, i}; + } + catch (...) + { + return {false, 0}; + } + } + + template <> + std::pair ParseValue(const std::string_view& str) + { + try + { + size_t index = 0; + float f = std::stof(std::string(str), &index); + + if (index != str.size()) + return {false, 0}; + + return {true, f}; + } + catch (...) + { + return {false, 0}; + } + } + + template <> + std::pair ParseValue(const std::string_view& str) + { + return {true, std::string(str)}; + } + + template <> + std::pair ParseValue(const std::string_view& str) + { + if (Utils::CaseInsensitiveEqual(str, "true") || Utils::CaseInsensitiveEqual(str, "yes") || str == "y" || str == "Y") + { + return {true, true}; + } + + if (Utils::CaseInsensitiveEqual(str, "false") || Utils::CaseInsensitiveEqual(str, "no") || str == "n" || str == "N") + { + return {true, false}; + } + + return {false, false}; + } + + template <> + std::string GetTypeHelpName() + { + return ""; + } + + template <> + std::string GetTypeHelpName() + { + return ""; + } + + template <> + std::string GetTypeHelpName() + { + return ""; + } + + template <> + std::string GetTypeHelpName() + { + return ""; + } + + namespace Utils + { + bool CaseInsensitiveEqual(const std::string_view& sv, const std::string& str) + { + if (sv.size() != str.size()) + return false; + + for (size_t i = 0; i < sv.size(); i++) + { + if (std::tolower(sv[i]) != str[i]) + { + return false; + } + } + return true; + } + + void PrintMaxWidth( + const std::vector& strings, size_t pos, size_t offset, size_t maxWidth, const std::string& delim) + { + if (strings.empty()) + return; + + for (auto& string : strings) + { + PrintMaxWidth(strings.front(), pos, offset, maxWidth); + } + std::cout << std::endl; + } + + void PrintMaxWidthSplit( + const std::string& str, size_t pos, size_t offset, size_t maxWidth, const std::string& delim) + { + size_t lastPos = 0; + size_t strPos = str.find(delim); + while (strPos != std::string::npos) + { + PrintMaxWidth(std::string_view(str.c_str() + lastPos, strPos - lastPos), pos, offset, maxWidth); + lastPos = strPos + 1; + strPos = str.find(' ', lastPos); + } + PrintMaxWidth(std::string_view(str.c_str() + lastPos, str.size() - lastPos), pos, offset, maxWidth); + std::cout << std::endl; + } + + void PrintMaxWidth(const std::string_view& view, size_t& pos, size_t offset, size_t maxWidth) + { + if (pos + view.size() > maxWidth && pos != offset) + { + std::cout << std::endl << std::setw(offset) << ""; + pos = offset; + } + size_t lastPos = 0; + size_t newline = view.find('\n'); + while (newline != std::string::npos) + { + std::cout << view.substr(lastPos, newline - lastPos) << std::endl << std::setw(offset) << ""; + pos = offset; + lastPos = newline + 1; + newline = view.find('\n', lastPos); + } + std::cout << view.substr(lastPos); + if (pos + view.size() != maxWidth) + std::cout << " "; + pos += view.size() + 1; + } + + bool IsMultiFlag(const std::string& arg) + { + if (arg[0] != '-') + return false; + + for (auto i = 1; i < arg.size(); i++) + { + if (!(arg[i] >= 'a' && arg[i] <= 'z') && !(arg[i] >= 'A' && arg[i] <= 'Z') && !(arg[i] >= '0' && arg[i] <= '9')) + return false; + } + + return true; + } + } +} diff --git a/src/Utils.h b/src/Utils.h new file mode 100644 index 0000000..78cf24e --- /dev/null +++ b/src/Utils.h @@ -0,0 +1,78 @@ +#pragma once + +#include +#include +#include +#include + +namespace ap +{ + template + struct dependent_false + { + enum + { + value = false + }; + }; + + template + struct is_optional : std::false_type + { + }; + + template + struct is_optional> : std::true_type + { + }; + + template + std::string GetTypeHelpName() + { + static_assert(dependent_false::value, "No GetTypeHelpName function created for type T"); + return {false, T{}}; + } + + template + std::pair ParseValue(const std::string_view& str) + { + static_assert(dependent_false::value, "No ParseValue function created for type T"); + return {false, T{}}; + } + + template <> + std::pair ParseValue(const std::string_view& str); + + template <> + std::pair ParseValue(const std::string_view& str); + + template <> + std::pair ParseValue(const std::string_view& str); + + template <> + std::pair ParseValue(const std::string_view& str); + + template <> + std::string GetTypeHelpName(); + + template <> + std::string GetTypeHelpName(); + + template <> + std::string GetTypeHelpName(); + + template <> + std::string GetTypeHelpName(); + + namespace Utils + { + bool CaseInsensitiveEqual(const std::string_view& sv, const std::string& str); + void PrintMaxWidth( + const std::vector& strings, size_t pos, size_t offset, size_t maxWidth, const std::string& delim); + void PrintMaxWidthSplit( + const std::string& view, size_t pos, size_t offset, size_t maxWidth, const std::string& delim); + void PrintMaxWidth(const std::string_view& view, size_t& pos, size_t offset, size_t maxWidth); + + bool IsMultiFlag(const std::string& arg); + } +} diff --git a/src/ValueArg.h b/src/ValueArg.h new file mode 100644 index 0000000..0c6c989 --- /dev/null +++ b/src/ValueArg.h @@ -0,0 +1,122 @@ +#include +#include + +#include "Arg.h" +#include "Utils.h" + +namespace ap +{ + template + struct ValueArg : public Arg + { + T& value; + + ValueArg(T& value, const std::string& argument, const std::string& helpText, bool required) + : Arg{argument, helpText, required, false}, + value{value} + { + } + + bool MatchAdditional(const std::string& arg) const override + { + size_t pos = arg.find('='); + if (pos == std::string::npos) + return false; + + for (const auto& argument : arguments) + { + if (std::string_view(arg.c_str(), pos) == argument) + return true; + } + + return false; + } + + bool Parse(const std::vector& arguments, size_t& index) override + { + wasSpecified = true; + + size_t pos = arguments[index].find('='); + std::string valStr; + std::string argStr; + if (pos == std::string::npos) + { + argStr = arguments[index]; + if (arguments.size() <= index + 1) + { + std::cout << "No argument specified for " << arguments[index] << std::endl; + return false; + } + index++; + + valStr = arguments[index]; + } + else + { + argStr = arguments[index].substr(0, pos); + valStr = arguments[index].substr(pos + 1, arguments[index].size() - pos - 1); + } + + std::pair res; + if constexpr (is_optional::value) + { + res = ParseValue(valStr); + } + else + { + res = ParseValue(valStr); + } + const auto& [result, val] = res; + if (!result) + { + std::cout << "Could not parse value for argument=\"" << argStr << "\"" << " value=\"" << valStr << "\"" + << std::endl + << std::endl; + return false; + } + + value = val; + + return true; + } + + std::string GetHelpArgument(bool onlyFirst) const override + { + std::stringstream ss; + + if (!onlyFirst) + { + ss << arguments.front(); + for (int i = 1; i < arguments.size(); i++) + { + ss << ", " << arguments[i]; + } + OutputTypeHelpName(ss); + return ss.str(); + } + + if (!required) + ss << "["; + + ss << arguments.front(); + OutputTypeHelpName(ss); + + if (!required) + ss << "]"; + return ss.str(); + } + + private: + void OutputTypeHelpName(std::ostream& os) const + { + if constexpr (is_optional::value) + { + os << " " << GetTypeHelpName(); + } + else + { + os << " " << GetTypeHelpName(); + } + } + }; +} diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..9e72c6e --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,109 @@ +#include + +#include "ArgParser.h" + +void PrintVersion() +{ + std::cout << "command v1.2.3" << std::endl; +} + +int main(int argc, char** argv) +{ + ap::ArgParser parser( + "makegen", + "MakeGen is a utility tool to generate and run Makefiles in a simple manner. By default it " + "always compiles code with parallel jobs", + "If no option is given it will run \"make all\"\n\nIf multiple make options are given it will " + "run in the following order:\nclean all install run, rebuild will be translated to \"clean all\"", + argc - 1, + argv + 1); + + struct Args + { + bool make; + bool install; + bool clean; + bool rebuild; + bool run; + bool single; + bool simple; + bool conf; + std::optional target; + int test; + }; + + Args args; + parser.AddAction("--version,-v", "Displays the version of this program", PrintVersion); + parser.AddFlag("make,all,-m,-a", args.make, "Generates a Makefile and runs \"make all\""); + parser.AddFlag("install,-i", args.install, "Generates a Makefile and runs \"make all && make install\""); + parser.AddFlag("clean,-c", args.clean, "Generates a Makefile and runs \"make clean\""); + parser.AddFlag("rebuld,-r", args.rebuild, "Generates a Makefile and runs \"make clean && make all\""); + parser.AddFlag("run,execute,-e", args.run, "Generates a Makefile and runs \"make all && make run\""); + parser.AddFlag("single,-s", args.single, "Runs additional makegen options as single thread\n(no --jobs=X flag)"); + parser.AddFlag("--simple", args.simple, "Creates a simple Makefile without include dependencies"); + parser.AddOptional("--target,-t", args.target, "Run the makegen.xml file with the specified target"); + if (parser.Parse()) + { + if (args.target) + std::cout << args.target.value() << std::endl; + else + std::cout << "null" << std::endl; + } + return 0; +#if 0 + ap::ArgParser parser("command", + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut " + "labore et dolore magna aliqua", + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut " + "labore et dolore magna aliqua", + argc - 1, + argv + 1); + + // ArgParser parser("command", + // "This text explains what the command does", + // "Additional help information", + // {{"--test", "10", "--test2=20", "--test4=tesssda", "--test5", "--test6=Y"}}); + int test; + int test2; + float test3; + std::string test4; + bool flag; + bool b; + bool version; + parser.AddInfo("--version", version, "Displays the version of the program"); + parser.Add( + "--test", + test, + "some1 help2 text3 some4 help5 text6 some7 help8 text9 some10 " + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); + parser.Add("--test2,-t", test2, "some help text 2"); + parser.Add("--test3", test3, "some help text", 0.1f); + parser.Add("--test4", test4, "some help text"); + parser.Add("--test6", b, "some help text"); + parser.Add("--test7", b, "some help text"); + parser.AddFlag("--test5", flag, "some help text"); + parser.AddFlag("--very_very_very_very_long", flag, "some help text"); + parser.AddFlag("--very_very_very_very_longxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + flag, + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore " + "et dolore magna aliqua"); + parser.AddFlag("--very_very_very_very", flag, "some help text"); + parser.AddFlag("--very_very_very_ver", flag, "some help text"); + if (parser.Parse()) + { + std::cout << test << std::endl; + std::cout << test2 << std::endl; + std::cout << test3 << std::endl; + std::cout << test4 << std::endl; + std::cout << b << std::endl; + std::cout << flag << std::endl; + } + + if (version) + { + std::cout << "Command 1.2.3" << std::endl; + return 0; + } + return 1; +#endif +}