2015-09-06 20:19:09 +02:00
# include "./argumentparser.h"
2016-12-23 09:55:12 +01:00
# include "./argumentparserprivate.h"
2015-09-06 20:19:09 +02:00
# include "./commandlineutils.h"
2015-05-08 23:20:47 +02:00
2017-01-27 18:51:54 +01:00
# include "../conversion/stringbuilder.h"
2017-05-01 03:13:11 +02:00
# include "../conversion/stringconversion.h"
2016-07-31 23:20:31 +02:00
# include "../io/ansiescapecodes.h"
2017-05-01 03:13:11 +02:00
# include "../io/path.h"
2018-05-07 21:59:23 +02:00
# include "../misc/levenshtein.h"
2019-06-10 16:03:27 +02:00
# include "../misc/parseerror.h"
2015-04-22 18:36:40 +02:00
# include <algorithm>
2017-05-01 03:13:11 +02:00
# include <cstdlib>
# include <cstring>
2015-04-22 18:36:40 +02:00
# include <iostream>
2018-05-07 21:59:23 +02:00
# include <set>
2015-04-22 18:36:40 +02:00
# include <sstream>
2017-05-01 03:13:11 +02:00
# include <string>
2015-04-22 18:36:40 +02:00
2019-07-02 18:40:25 +02:00
# ifdef CPP_UTILITIES_USE_STANDARD_FILESYSTEM
# include <filesystem>
# endif
2015-04-22 18:36:40 +02:00
using namespace std ;
using namespace std : : placeholders ;
2019-06-05 23:49:35 +02:00
using namespace std : : literals ;
2019-06-10 21:56:46 +02:00
using namespace CppUtilities : : EscapeCodes ;
2015-04-22 18:36:40 +02:00
/*!
2019-06-10 21:56:46 +02:00
* \ namespace CppUtilities
* \ brief Contains all utilities provides by the c + + utilities library .
2016-06-12 01:56:57 +02:00
*/
2019-06-10 21:56:46 +02:00
namespace CppUtilities {
2015-04-22 18:36:40 +02:00
2021-05-28 19:15:09 +02:00
/*!
* \ brief Returns whether the specified env variable is set to a non - zero and non - white - space - only value .
*/
std : : optional < bool > isEnvVariableSet ( const char * variableName )
{
const char * envValue = std : : getenv ( variableName ) ;
if ( ! envValue ) {
return std : : nullopt ;
}
for ( ; * envValue ; + + envValue ) {
switch ( * envValue ) {
case ' 0 ' :
case ' ' :
break ;
default :
return true ;
}
}
return false ;
}
2016-12-23 09:55:12 +01:00
/*!
* \ brief The ArgumentDenotationType enum specifies the type of a given argument denotation .
*/
enum ArgumentDenotationType : unsigned char {
Value = 0 , /**< parameter value */
Abbreviation = 1 , /**< argument abbreviation */
FullName = 2 /**< full argument name */
} ;
2018-05-07 21:59:23 +02:00
/*!
* \ brief The ArgumentCompletionInfo struct holds information internally used for shell completion and suggestions .
*/
struct ArgumentCompletionInfo {
ArgumentCompletionInfo ( const ArgumentReader & reader ) ;
const Argument * const lastDetectedArg ;
size_t lastDetectedArgIndex = 0 ;
vector < Argument * > lastDetectedArgPath ;
list < const Argument * > relevantArgs ;
list < const Argument * > relevantPreDefinedValues ;
const char * const * lastSpecifiedArg = nullptr ;
unsigned int lastSpecifiedArgIndex = 0 ;
bool nextArgumentOrValue = false ;
bool completeFiles = false , completeDirs = false ;
} ;
/*!
* \ brief Constructs a new completion info for the specified \ a reader .
* \ remarks Only assigns some defaults . Use ArgumentParser : : determineCompletionInfo ( ) to populate the struct with actual data .
*/
ArgumentCompletionInfo : : ArgumentCompletionInfo ( const ArgumentReader & reader )
: lastDetectedArg ( reader . lastArg )
{
}
2019-09-04 18:51:17 +02:00
/// \cond
2018-05-07 21:59:23 +02:00
struct ArgumentSuggestion {
2018-05-11 16:15:02 +02:00
ArgumentSuggestion ( const char * unknownArg , size_t unknownArgSize , const char * suggestion , bool hasDashPrefix ) ;
ArgumentSuggestion ( const char * unknownArg , size_t unknownArgSize , const char * suggestion , size_t suggestionSize , bool hasDashPrefix ) ;
2018-05-07 21:59:23 +02:00
bool operator < ( const ArgumentSuggestion & other ) const ;
bool operator = = ( const ArgumentSuggestion & other ) const ;
void addTo ( multiset < ArgumentSuggestion > & suggestions , size_t limit ) const ;
const char * const suggestion ;
const size_t suggestionSize ;
const size_t editingDistance ;
2018-05-11 16:15:02 +02:00
const bool hasDashPrefix ;
2018-05-07 21:59:23 +02:00
} ;
2018-05-11 16:15:02 +02:00
ArgumentSuggestion : : ArgumentSuggestion ( const char * unknownArg , size_t unknownArgSize , const char * suggestion , size_t suggestionSize , bool isOperation )
2018-05-07 21:59:23 +02:00
: suggestion ( suggestion )
, suggestionSize ( suggestionSize )
2019-06-10 21:56:46 +02:00
, editingDistance ( computeDamerauLevenshteinDistance ( unknownArg , unknownArgSize , suggestion , suggestionSize ) )
2018-05-11 16:15:02 +02:00
, hasDashPrefix ( isOperation )
2018-05-07 21:59:23 +02:00
{
}
2018-05-11 16:15:02 +02:00
ArgumentSuggestion : : ArgumentSuggestion ( const char * unknownArg , size_t unknownArgSize , const char * suggestion , bool isOperation )
: ArgumentSuggestion ( unknownArg , unknownArgSize , suggestion , strlen ( suggestion ) , isOperation )
2018-05-07 21:59:23 +02:00
{
}
bool ArgumentSuggestion : : operator < ( const ArgumentSuggestion & other ) const
{
return editingDistance < other . editingDistance ;
}
void ArgumentSuggestion : : addTo ( multiset < ArgumentSuggestion > & suggestions , size_t limit ) const
{
if ( suggestions . size ( ) > = limit & & ! ( * this < * - - suggestions . end ( ) ) ) {
return ;
}
suggestions . emplace ( * this ) ;
while ( suggestions . size ( ) > limit ) {
suggestions . erase ( - - suggestions . end ( ) ) ;
}
}
2019-09-04 18:51:17 +02:00
/// \endcond
2018-05-07 21:59:23 +02:00
2016-12-23 09:55:12 +01:00
/*!
2017-09-30 18:42:34 +02:00
* \ class ArgumentReader
* \ brief The ArgumentReader class internally encapsulates the process of reading command line arguments .
2016-12-23 09:55:12 +01:00
* \ remarks
2021-03-30 20:48:05 +02:00
* - For meaning of parameters see documentation of corresponding member variables .
2016-12-23 09:55:12 +01:00
* - Results are stored in specified \ a args and assigned sub arguments .
2021-07-03 19:07:49 +02:00
* - This class is explicitly * not * part of the public API .
2017-09-30 18:42:34 +02:00
*/
/*!
* \ brief Initializes the internal reader for the specified \ a parser and arguments .
2016-12-23 09:55:12 +01:00
*/
2017-05-01 03:13:11 +02:00
ArgumentReader : : ArgumentReader ( ArgumentParser & parser , const char * const * argv , const char * const * end , bool completionMode )
: parser ( parser )
, args ( parser . m_mainArgs )
, index ( 0 )
, argv ( argv )
, end ( end )
, lastArg ( nullptr )
, argDenotation ( nullptr )
, completionMode ( completionMode )
{
}
2016-12-23 09:55:12 +01:00
2016-12-23 22:41:06 +01:00
/*!
* \ brief Resets the ArgumentReader to continue reading new \ a argv .
*/
2016-12-23 09:55:12 +01:00
ArgumentReader & ArgumentReader : : reset ( const char * const * argv , const char * const * end )
{
this - > argv = argv ;
this - > end = end ;
index = 0 ;
lastArg = nullptr ;
argDenotation = nullptr ;
return * this ;
}
/*!
* \ brief Reads the commands line arguments specified when constructing the object .
2016-12-23 22:41:06 +01:00
* \ remarks Reads on main - argument - level .
2016-12-23 09:55:12 +01:00
*/
2018-05-07 20:04:30 +02:00
bool ArgumentReader : : read ( )
2016-12-23 09:55:12 +01:00
{
2018-05-07 20:04:30 +02:00
return read ( args ) ;
2016-12-23 09:55:12 +01:00
}
2017-11-29 21:52:16 +01:00
/*!
* \ brief Returns whether the \ a denotation with the specified \ a denotationLength matches the argument ' s \ a name .
*/
bool Argument : : matchesDenotation ( const char * denotation , size_t denotationLength ) const
{
return m_name & & ! strncmp ( m_name , denotation , denotationLength ) & & * ( m_name + denotationLength ) = = ' \0 ' ;
}
2016-12-23 09:55:12 +01:00
/*!
* \ brief Reads the commands line arguments specified when constructing the object .
2018-05-07 20:04:30 +02:00
* \ remarks The argument definitions to look for are specified via \ a args . The method calls itself recursively
* to check for nested arguments as well .
* \ returns Returns true if all arguments have been processed . Returns false on early exit because some argument
* is unknown and behavior for this case is set to UnknownArgumentBehavior : : Fail .
2016-12-23 09:55:12 +01:00
*/
2018-05-07 20:04:30 +02:00
bool ArgumentReader : : read ( ArgumentVector & args )
2016-12-23 09:55:12 +01:00
{
// method is called recursively for sub args to the last argument (which is nullptr in the initial call) is the current parent argument
Argument * const parentArg = lastArg ;
// determine the current path
const vector < Argument * > & parentPath = parentArg ? parentArg - > path ( parentArg - > occurrences ( ) - 1 ) : vector < Argument * > ( ) ;
Argument * lastArgInLevel = nullptr ;
vector < const char * > * values = nullptr ;
// iterate through all argument denotations; loop might exit earlier when an denotation is unknown
2017-05-01 03:13:11 +02:00
while ( argv ! = end ) {
2017-10-19 00:48:05 +02:00
// check whether there are still values to read
if ( values & & lastArgInLevel - > requiredValueCount ( ) ! = Argument : : varValueCount & & values - > size ( ) < lastArgInLevel - > requiredValueCount ( ) ) {
// read arg as value and continue with next arg
2016-12-23 09:55:12 +01:00
values - > emplace_back ( argDenotation ? argDenotation : * argv ) ;
2019-05-04 21:38:23 +02:00
+ + index ;
+ + argv ;
argDenotation = nullptr ;
2017-10-19 00:48:05 +02:00
continue ;
}
// determine how denotation must be processed
bool abbreviationFound = false ;
if ( argDenotation ) {
2019-12-30 22:52:23 +01:00
// continue reading children for abbreviation denotation already detected
2017-10-19 00:48:05 +02:00
abbreviationFound = false ;
argDenotationType = Abbreviation ;
2016-12-23 09:55:12 +01:00
} else {
2017-10-19 00:48:05 +02:00
// determine denotation type
argDenotation = * argv ;
if ( ! * argDenotation & & ( ! lastArgInLevel | | values - > size ( ) > = lastArgInLevel - > requiredValueCount ( ) ) ) {
// skip empty arguments
2019-05-04 21:38:23 +02:00
+ + index ;
+ + argv ;
argDenotation = nullptr ;
2017-10-19 00:48:05 +02:00
continue ;
2016-12-23 09:55:12 +01:00
}
2017-10-19 00:48:05 +02:00
abbreviationFound = false ;
argDenotationType = Value ;
2019-05-04 21:38:23 +02:00
if ( * argDenotation = = ' - ' ) {
+ + argDenotation ;
+ + argDenotationType ;
if ( * argDenotation = = ' - ' ) {
+ + argDenotation ;
+ + argDenotationType ;
}
}
2017-10-19 00:48:05 +02:00
}
2016-12-23 09:55:12 +01:00
2017-10-19 00:48:05 +02:00
// try to find matching Argument instance
Argument * matchingArg = nullptr ;
if ( argDenotationType ! = Value ) {
2017-11-29 21:52:16 +01:00
// determine actual denotation length (everything before equation sign)
2017-10-19 00:48:05 +02:00
const char * const equationPos = strchr ( argDenotation , ' = ' ) ;
2017-11-29 21:52:16 +01:00
const auto argDenotationLength = equationPos ? static_cast < size_t > ( equationPos - argDenotation ) : strlen ( argDenotation ) ;
// loop through each "part" of the denotation
// names are read at once, but for abbreviations each character is considered individually
for ( ; argDenotationLength ; matchingArg = nullptr ) {
2017-10-19 00:48:05 +02:00
// search for arguments by abbreviation or name depending on the previously determined denotation type
if ( argDenotationType = = Abbreviation ) {
2018-05-07 20:04:30 +02:00
for ( Argument * const arg : args ) {
2017-10-19 00:48:05 +02:00
if ( arg - > abbreviation ( ) & & arg - > abbreviation ( ) = = * argDenotation ) {
matchingArg = arg ;
abbreviationFound = true ;
break ;
2016-12-23 09:55:12 +01:00
}
2017-10-19 00:48:05 +02:00
}
} else {
2018-05-07 20:04:30 +02:00
for ( Argument * const arg : args ) {
2017-11-29 21:52:16 +01:00
if ( arg - > matchesDenotation ( argDenotation , argDenotationLength ) ) {
2017-10-19 00:48:05 +02:00
matchingArg = arg ;
break ;
2016-12-23 09:55:12 +01:00
}
}
2017-10-19 00:48:05 +02:00
}
2017-11-29 21:52:16 +01:00
if ( ! matchingArg ) {
break ;
}
2016-12-23 09:55:12 +01:00
2017-11-29 21:52:16 +01:00
// an argument matched the specified denotation so add an occurrence
matchingArg - > m_occurrences . emplace_back ( index , parentPath , parentArg ) ;
2016-12-23 09:55:12 +01:00
2017-11-29 21:52:16 +01:00
// prepare reading parameter values
values = & matchingArg - > m_occurrences . back ( ) . values ;
2016-12-23 09:55:12 +01:00
2018-05-07 20:04:30 +02:00
// read value after equation sign
2017-11-29 21:52:16 +01:00
if ( ( argDenotationType ! = Abbreviation & & equationPos ) | | ( + + argDenotation = = equationPos ) ) {
values - > push_back ( equationPos + 1 ) ;
argDenotation = nullptr ;
}
// read sub arguments, distinguish whether further abbreviations follow
2019-05-04 21:38:23 +02:00
+ + index ;
+ + parser . m_actualArgc ;
lastArg = lastArgInLevel = matchingArg ;
lastArgDenotation = argv ;
2017-11-29 21:52:16 +01:00
if ( argDenotationType ! = Abbreviation | | ! argDenotation | | ! * argDenotation ) {
// no further abbreviations follow -> read sub args for next argv
2019-05-04 21:38:23 +02:00
+ + argv ;
argDenotation = nullptr ;
2017-11-29 21:52:16 +01:00
read ( lastArg - > m_subArgs ) ;
argDenotation = nullptr ;
2017-10-19 00:48:05 +02:00
break ;
2017-11-29 21:52:16 +01:00
} else {
2017-11-29 22:52:50 +01:00
// further abbreviations follow -> remember current arg value
2018-05-07 20:04:30 +02:00
const char * const * const currentArgValue = argv ;
2017-11-29 22:52:50 +01:00
// don't increment argv, keep processing outstanding chars of argDenotation
2017-11-29 21:52:16 +01:00
read ( lastArg - > m_subArgs ) ;
2017-11-29 22:52:50 +01:00
// stop further processing if the denotation has been consumed or even the next value has already been loaded
if ( ! argDenotation | | currentArgValue ! = argv ) {
argDenotation = nullptr ;
2017-11-29 21:52:16 +01:00
break ;
}
2016-12-23 09:55:12 +01:00
}
}
2017-11-29 21:52:16 +01:00
// continue with next arg if we've got a match already
if ( matchingArg ) {
continue ;
}
2017-10-19 00:48:05 +02:00
2017-11-29 21:52:16 +01:00
// unknown argument might be a sibling of the parent element
2017-10-19 00:48:05 +02:00
for ( auto parentArgument = parentPath . crbegin ( ) , pathEnd = parentPath . crend ( ) ; ; + + parentArgument ) {
2018-05-07 20:04:30 +02:00
for ( Argument * const sibling : ( parentArgument ! = pathEnd ? ( * parentArgument ) - > subArguments ( ) : parser . m_mainArgs ) ) {
2017-10-19 00:48:05 +02:00
if ( sibling - > occurrences ( ) < sibling - > maxOccurrences ( ) ) {
2017-11-27 10:30:14 +01:00
// check whether the denoted abbreviation matches the sibling's abbreviatiopn
if ( argDenotationType = = Abbreviation & & ( sibling - > abbreviation ( ) & & sibling - > abbreviation ( ) = = * argDenotation ) ) {
2018-05-07 20:04:30 +02:00
return false ;
2017-11-27 10:30:14 +01:00
}
// check whether the denoted name matches the sibling's name
2017-11-29 21:52:16 +01:00
if ( sibling - > matchesDenotation ( argDenotation , argDenotationLength ) ) {
2018-05-07 20:04:30 +02:00
return false ;
2016-12-23 09:55:12 +01:00
}
2017-10-19 00:48:05 +02:00
}
2016-12-23 09:55:12 +01:00
}
2017-10-19 00:48:05 +02:00
if ( parentArgument = = pathEnd ) {
break ;
2016-12-23 09:55:12 +01:00
}
2019-05-04 21:38:23 +02:00
}
2017-10-19 00:48:05 +02:00
}
2016-12-23 09:55:12 +01:00
2017-10-19 00:48:05 +02:00
// unknown argument might just be a parameter value of the last argument
if ( lastArgInLevel & & values - > size ( ) < lastArgInLevel - > requiredValueCount ( ) ) {
values - > emplace_back ( abbreviationFound ? argDenotation : * argv ) ;
2019-05-04 21:38:23 +02:00
+ + index ;
+ + argv ;
argDenotation = nullptr ;
2017-10-19 00:48:05 +02:00
continue ;
}
2016-12-23 09:55:12 +01:00
2017-10-19 00:48:05 +02:00
// first value might denote "operation"
2018-05-07 20:04:30 +02:00
for ( Argument * const arg : args ) {
2017-10-19 00:48:05 +02:00
if ( arg - > denotesOperation ( ) & & arg - > name ( ) & & ! strcmp ( arg - > name ( ) , * argv ) ) {
( matchingArg = arg ) - > m_occurrences . emplace_back ( index , parentPath , parentArg ) ;
lastArgDenotation = argv ;
2019-05-04 21:38:23 +02:00
+ + index ;
+ + argv ;
2017-10-19 00:48:05 +02:00
break ;
}
}
// use the first default argument which is not already present if there is still no match
if ( ! matchingArg & & ( ! completionMode | | ( argv + 1 ! = end ) ) ) {
const bool uncombinableMainArgPresent = parentArg ? false : parser . isUncombinableMainArgPresent ( ) ;
2018-05-07 20:04:30 +02:00
for ( Argument * const arg : args ) {
2017-10-19 00:48:05 +02:00
if ( arg - > isImplicit ( ) & & ! arg - > isPresent ( ) & & ! arg - > wouldConflictWithArgument ( )
& & ( ! uncombinableMainArgPresent | | ! arg - > isMainArgument ( ) ) ) {
( matchingArg = arg ) - > m_occurrences . emplace_back ( index , parentPath , parentArg ) ;
break ;
2016-12-23 09:55:12 +01:00
}
2017-10-19 00:48:05 +02:00
}
}
2016-12-23 09:55:12 +01:00
2017-10-19 00:48:05 +02:00
if ( matchingArg ) {
// an argument matched the specified denotation
if ( lastArgInLevel = = matchingArg ) {
break ; // break required? -> TODO: add test for this condition
}
2016-12-23 09:55:12 +01:00
2017-10-19 00:48:05 +02:00
// prepare reading parameter values
values = & matchingArg - > m_occurrences . back ( ) . values ;
2016-12-23 09:55:12 +01:00
2017-10-19 00:48:05 +02:00
// read sub arguments
2019-05-04 21:38:23 +02:00
+ + parser . m_actualArgc ;
lastArg = lastArgInLevel = matchingArg ;
argDenotation = nullptr ;
2017-10-19 00:48:05 +02:00
read ( lastArg - > m_subArgs ) ;
argDenotation = nullptr ;
continue ;
}
2016-12-23 09:55:12 +01:00
2017-10-19 00:48:05 +02:00
// argument denotation is unknown -> handle error
if ( parentArg ) {
// continue with parent level
2018-05-07 20:04:30 +02:00
return false ;
2017-10-19 00:48:05 +02:00
}
if ( completionMode ) {
// ignore unknown denotation
2019-05-04 21:38:23 +02:00
+ + index ;
+ + argv ;
argDenotation = nullptr ;
2017-10-19 00:48:05 +02:00
} else {
switch ( parser . m_unknownArgBehavior ) {
case UnknownArgumentBehavior : : Warn :
cerr < < Phrases : : Warning < < " The specified argument \" " < < * argv < < " \" is unknown and will be ignored. " < < Phrases : : EndFlush ;
2019-06-12 20:33:43 +02:00
[[fallthrough]] ;
2017-10-19 00:48:05 +02:00
case UnknownArgumentBehavior : : Ignore :
// ignore unknown denotation
2019-05-04 21:38:23 +02:00
+ + index ;
+ + argv ;
argDenotation = nullptr ;
2017-10-19 00:48:05 +02:00
break ;
case UnknownArgumentBehavior : : Fail :
2018-05-07 20:04:30 +02:00
return false ;
2017-10-19 00:48:05 +02:00
}
}
2016-12-23 09:55:12 +01:00
} // while(argv != end)
2018-05-07 20:04:30 +02:00
return true ;
2016-12-23 09:55:12 +01:00
}
2017-09-30 18:42:34 +02:00
/*!
* \ class Wrapper
* \ brief The Wrapper class is internally used print text which might needs to be wrapped preserving the indentation .
2021-07-03 19:07:49 +02:00
* \ remarks This class is explicitly * not * part of the public API .
2017-09-30 18:42:34 +02:00
*/
2017-09-29 20:58:58 +02:00
ostream & operator < < ( ostream & os , const Wrapper & wrapper )
{
// determine max. number of columns
static const TerminalSize termSize ( determineTerminalSize ( ) ) ;
const auto maxColumns = termSize . columns ? termSize . columns : numeric_limits < unsigned short > : : max ( ) ;
// print wrapped string considering indentation
unsigned short currentCol = wrapper . m_indentation . level ;
for ( const char * currentChar = wrapper . m_str ; * currentChar ; + + currentChar ) {
const bool wrappingRequired = currentCol > = maxColumns ;
if ( wrappingRequired | | * currentChar = = ' \n ' ) {
// insert newline (TODO: wrap only at end of a word)
os < < ' \n ' ;
// print indentation (if enough space)
if ( wrapper . m_indentation . level < maxColumns ) {
os < < wrapper . m_indentation ;
currentCol = wrapper . m_indentation . level ;
} else {
currentCol = 0 ;
}
}
if ( * currentChar ! = ' \n ' & & ( ! wrappingRequired | | * currentChar ! = ' ' ) ) {
os < < * currentChar ;
+ + currentCol ;
}
}
return os ;
}
2019-05-04 22:49:57 +02:00
CPP_UTILITIES_EXPORT ApplicationInfo applicationInfo ;
2015-08-25 19:12:05 +02:00
2016-10-30 00:30:54 +02:00
/// \cond
2016-05-25 01:24:17 +02:00
inline bool notEmpty ( const char * str )
{
return str & & * str ;
}
2016-06-12 01:56:57 +02:00
/// \endcond
2015-04-22 18:36:40 +02:00
/*!
2019-09-04 18:45:28 +02:00
* \ class Argument
2015-04-22 18:36:40 +02:00
* \ brief The Argument class is a wrapper for command line argument information .
*
2021-07-03 19:07:49 +02:00
* Instances of the Argument class are used as definition when parsing command line
2015-04-22 18:36:40 +02:00
* arguments . Arguments can be assigned to an ArgumentParser using
* ArgumentParser : : setMainArguments ( ) and to another Argument instance using
* Argument : : setSecondaryArguments ( ) .
*/
/*!
2016-02-27 01:19:16 +01:00
* \ brief Constructs an Argument with the given \ a name , \ a abbreviation and \ a description .
2015-04-22 18:36:40 +02:00
*
* The \ a name and the abbreviation mustn ' t contain any whitespaces .
* The \ a name mustn ' t be empty . The \ a abbreviation and the \ a description might be empty .
*/
2017-05-01 03:13:11 +02:00
Argument : : Argument ( const char * name , char abbreviation , const char * description , const char * example )
: m_name ( name )
, m_abbreviation ( abbreviation )
, m_environmentVar ( nullptr )
, m_description ( description )
, m_example ( example )
, m_minOccurrences ( 0 )
, m_maxOccurrences ( 1 )
, m_requiredValueCount ( 0 )
2019-03-24 21:50:24 +01:00
, m_flags ( Flags : : None )
2019-05-04 23:14:43 +02:00
, m_deprecatedBy ( nullptr )
2017-05-01 03:13:11 +02:00
, m_isMainArg ( false )
, m_valueCompletionBehavior ( ValueCompletionBehavior : : PreDefinedValues | ValueCompletionBehavior : : Files | ValueCompletionBehavior : : Directories
| ValueCompletionBehavior : : FileSystemIfNoPreDefinedValues )
, m_preDefinedCompletionValues ( nullptr )
{
}
2015-04-22 18:36:40 +02:00
/*!
2016-02-27 01:19:16 +01:00
* \ brief Destroys the Argument .
2015-04-22 18:36:40 +02:00
*/
Argument : : ~ Argument ( )
2017-05-01 03:13:11 +02:00
{
}
2015-04-22 18:36:40 +02:00
/*!
2016-07-31 23:20:31 +02:00
* \ brief Returns the first parameter value of the first occurrence of the argument .
2016-07-17 01:26:34 +02:00
* \ remarks
* - If the argument is not present and the an environment variable has been set
* using setEnvironmentVariable ( ) the value of the specified variable will be returned .
* - Returns nullptr if no value is available though .
*/
const char * Argument : : firstValue ( ) const
{
2017-05-01 03:13:11 +02:00
if ( ! m_occurrences . empty ( ) & & ! m_occurrences . front ( ) . values . empty ( ) ) {
2016-07-31 23:20:31 +02:00
return m_occurrences . front ( ) . values . front ( ) ;
2017-05-01 03:13:11 +02:00
} else if ( m_environmentVar ) {
2016-07-17 01:26:34 +02:00
return getenv ( m_environmentVar ) ;
} else {
return nullptr ;
}
}
2021-01-01 18:30:17 +01:00
/*!
* \ brief Returns the first value like Argument : : firstValue ( ) but returns \ a fallback instead of nullptr if there ' s no value .
*/
const char * Argument : : firstValueOr ( const char * fallback ) const
{
if ( const auto * const v = firstValue ( ) ) {
return v ;
} else {
return fallback ;
}
}
2016-07-17 01:26:34 +02:00
/*!
* \ brief Writes the name , the abbreviation and other information about the Argument to the give ostream .
2015-04-22 18:36:40 +02:00
*/
2016-10-02 21:53:58 +02:00
void Argument : : printInfo ( ostream & os , unsigned char indentation ) const
2015-04-22 18:36:40 +02:00
{
2019-05-04 23:14:43 +02:00
if ( isDeprecated ( ) ) {
return ;
}
2017-09-29 20:58:58 +02:00
Indentation ident ( indentation ) ;
os < < ident ;
2016-07-31 23:20:31 +02:00
EscapeCodes : : setStyle ( os , EscapeCodes : : TextAttribute : : Bold ) ;
2017-05-01 03:13:11 +02:00
if ( notEmpty ( name ( ) ) ) {
2017-09-17 21:37:24 +02:00
if ( ! denotesOperation ( ) ) {
os < < ' - ' < < ' - ' ;
}
os < < name ( ) ;
2015-04-22 18:36:40 +02:00
}
2017-05-01 03:13:11 +02:00
if ( notEmpty ( name ( ) ) & & abbreviation ( ) ) {
2016-06-12 01:56:57 +02:00
os < < ' , ' < < ' ' ;
2015-04-22 18:36:40 +02:00
}
2017-05-01 03:13:11 +02:00
if ( abbreviation ( ) ) {
2016-06-12 01:56:57 +02:00
os < < ' - ' < < abbreviation ( ) ;
2015-04-22 18:36:40 +02:00
}
2016-07-31 23:20:31 +02:00
EscapeCodes : : setStyle ( os ) ;
2017-05-01 03:13:11 +02:00
if ( requiredValueCount ( ) ) {
2016-06-12 01:56:57 +02:00
unsigned int valueNamesPrint = 0 ;
2017-05-01 03:13:11 +02:00
for ( auto i = valueNames ( ) . cbegin ( ) , end = valueNames ( ) . cend ( ) ; i ! = end & & valueNamesPrint < requiredValueCount ( ) ; + + i ) {
2016-06-12 01:56:57 +02:00
os < < ' ' < < ' [ ' < < * i < < ' ] ' ;
2015-04-22 18:36:40 +02:00
+ + valueNamesPrint ;
}
2017-09-29 20:58:58 +02:00
if ( requiredValueCount ( ) = = Argument : : varValueCount ) {
2016-06-12 01:56:57 +02:00
os < < " ... " ;
} else {
2017-05-01 03:13:11 +02:00
for ( ; valueNamesPrint < requiredValueCount ( ) ; + + valueNamesPrint ) {
2016-06-12 01:56:57 +02:00
os < < " [value " < < ( valueNamesPrint + 1 ) < < ' ] ' ;
}
2015-04-22 18:36:40 +02:00
}
}
2017-09-29 20:58:58 +02:00
ident . level + = 2 ;
2017-05-01 03:13:11 +02:00
if ( notEmpty ( description ( ) ) ) {
2017-09-29 20:58:58 +02:00
os < < ' \n ' < < ident < < Wrapper ( description ( ) , ident ) ;
2015-04-22 18:36:40 +02:00
}
2017-05-01 03:13:11 +02:00
if ( isRequired ( ) ) {
2017-09-29 20:58:58 +02:00
os < < ' \n ' < < ident < < " particularities: mandatory " ;
2017-05-01 03:13:11 +02:00
if ( ! isMainArgument ( ) ) {
2016-10-02 21:53:58 +02:00
os < < " if parent argument is present " ;
}
}
2017-05-01 03:13:11 +02:00
if ( environmentVariable ( ) ) {
2017-09-29 20:58:58 +02:00
os < < ' \n ' < < ident < < " default environment variable: " < < Wrapper ( environmentVariable ( ) , ident + 30 ) ;
2015-04-22 18:36:40 +02:00
}
2016-10-02 21:53:58 +02:00
os < < ' \n ' ;
2019-05-04 23:14:43 +02:00
bool hasSubArgs = false ;
for ( const auto * const arg : subArguments ( ) ) {
if ( arg - > isDeprecated ( ) ) {
continue ;
}
hasSubArgs = true ;
2017-09-29 20:58:58 +02:00
arg - > printInfo ( os , ident . level ) ;
2015-04-22 18:36:40 +02:00
}
2017-09-17 21:37:24 +02:00
if ( notEmpty ( example ( ) ) ) {
2019-05-04 23:14:43 +02:00
if ( ident . level = = 2 & & hasSubArgs ) {
2017-09-17 21:37:24 +02:00
os < < ' \n ' ;
}
2017-09-29 20:58:58 +02:00
os < < ident < < " example: " < < Wrapper ( example ( ) , ident + 9 ) ;
2017-09-17 21:37:24 +02:00
os < < ' \n ' ;
}
2015-04-22 18:36:40 +02:00
}
/*!
2016-02-27 01:19:16 +01:00
* \ brief This function return the first present and uncombinable argument of the given list of arguments .
*
2015-04-22 18:36:40 +02:00
* The Argument \ a except will be ignored .
*/
Argument * firstPresentUncombinableArg ( const ArgumentVector & args , const Argument * except )
{
2017-05-01 03:13:11 +02:00
for ( Argument * arg : args ) {
if ( arg ! = except & & arg - > isPresent ( ) & & ! arg - > isCombinable ( ) ) {
2015-04-22 18:36:40 +02:00
return arg ;
}
}
return nullptr ;
}
/*!
2016-02-27 01:19:16 +01:00
* \ brief Sets the secondary arguments for this arguments .
*
* The given arguments will be considered as secondary arguments of this argument by the argument parser .
* This means that the parser will complain if these arguments are given , but not this argument .
* If secondary arguments are labeled as mandatory their parent is also mandatory .
2015-04-22 18:36:40 +02:00
*
* The Argument does not take ownership . Do not destroy the given arguments as long as they are
* used as secondary arguments .
*
* \ sa secondaryArguments ( )
2015-07-27 23:19:10 +02:00
* \ sa addSecondaryArgument ( )
2015-04-22 18:36:40 +02:00
* \ sa hasSecondaryArguments ( )
*/
2016-06-11 19:41:40 +02:00
void Argument : : setSubArguments ( const ArgumentInitializerList & secondaryArguments )
2015-04-22 18:36:40 +02:00
{
// remove this argument from the parents list of the previous secondary arguments
2017-05-01 03:13:11 +02:00
for ( Argument * arg : m_subArgs ) {
2015-04-22 18:36:40 +02:00
arg - > m_parents . erase ( remove ( arg - > m_parents . begin ( ) , arg - > m_parents . end ( ) , this ) , arg - > m_parents . end ( ) ) ;
}
// assign secondary arguments
2016-06-11 19:41:40 +02:00
m_subArgs . assign ( secondaryArguments ) ;
2015-04-22 18:36:40 +02:00
// add this argument to the parents list of the assigned secondary arguments
// and set the parser
2017-05-01 03:13:11 +02:00
for ( Argument * arg : m_subArgs ) {
if ( find ( arg - > m_parents . cbegin ( ) , arg - > m_parents . cend ( ) , this ) = = arg - > m_parents . cend ( ) ) {
2015-04-22 18:36:40 +02:00
arg - > m_parents . push_back ( this ) ;
}
}
}
2015-07-27 23:19:10 +02:00
/*!
2016-02-27 01:19:16 +01:00
* \ brief Adds \ a arg as a secondary argument for this argument .
2015-07-27 23:19:10 +02:00
*
* \ sa secondaryArguments ( )
* \ sa setSecondaryArguments ( )
* \ sa hasSecondaryArguments ( )
*/
2016-06-11 19:41:40 +02:00
void Argument : : addSubArgument ( Argument * arg )
2015-07-27 23:19:10 +02:00
{
2019-05-04 22:35:00 +02:00
if ( find ( m_subArgs . cbegin ( ) , m_subArgs . cend ( ) , arg ) ! = m_subArgs . cend ( ) ) {
return ;
}
m_subArgs . push_back ( arg ) ;
if ( find ( arg - > m_parents . cbegin ( ) , arg - > m_parents . cend ( ) , this ) = = arg - > m_parents . cend ( ) ) {
arg - > m_parents . push_back ( this ) ;
2015-07-27 23:19:10 +02:00
}
}
2015-04-22 18:36:40 +02:00
/*!
2016-06-12 01:56:57 +02:00
* \ brief Returns whether at least one parent argument is present .
* \ remarks Returns always true for main arguments .
2015-04-22 18:36:40 +02:00
*/
bool Argument : : isParentPresent ( ) const
{
2017-05-01 03:13:11 +02:00
if ( isMainArgument ( ) ) {
2016-06-12 01:56:57 +02:00
return true ;
}
2017-05-01 03:13:11 +02:00
for ( const Argument * parent : m_parents ) {
if ( parent - > isPresent ( ) ) {
2015-04-22 18:36:40 +02:00
return true ;
}
}
return false ;
}
/*!
2016-02-27 01:19:16 +01:00
* \ brief Checks if this arguments conflicts with other arguments .
*
* If the argument is in conflict with an other argument this argument will be returned .
* Otherwise nullptr will be returned .
2016-11-15 21:44:23 +01:00
*
* \ remarks Conflicts with main arguments aren ' t considered by this method !
2015-04-22 18:36:40 +02:00
*/
Argument * Argument : : conflictsWithArgument ( ) const
{
2016-11-15 21:44:23 +01:00
return isPresent ( ) ? wouldConflictWithArgument ( ) : nullptr ;
}
/*!
* \ brief Checks if this argument would conflict with other arguments if it was present .
*
* If the argument is in conflict with an other argument this argument will be returned .
* Otherwise nullptr will be returned .
*
* \ remarks Conflicts with main arguments aren ' t considered by this method !
*/
Argument * Argument : : wouldConflictWithArgument ( ) const
{
2019-05-04 22:35:00 +02:00
if ( isCombinable ( ) ) {
return nullptr ;
}
for ( Argument * parent : m_parents ) {
for ( Argument * sibling : parent - > subArguments ( ) ) {
if ( sibling ! = this & & sibling - > isPresent ( ) & & ! sibling - > isCombinable ( ) ) {
return sibling ;
2015-04-22 18:36:40 +02:00
}
}
}
return nullptr ;
}
2017-09-26 16:46:17 +02:00
/*!
* \ brief Returns the first operation argument specified by the user or nullptr if no operation has been specified .
* \ remarks Only direct sub arguments of this argument are considered .
*/
Argument * Argument : : specifiedOperation ( ) const
{
for ( Argument * arg : m_subArgs ) {
if ( arg - > denotesOperation ( ) & & arg - > isPresent ( ) ) {
return arg ;
}
}
return nullptr ;
}
2016-11-26 00:11:48 +01:00
/*!
* \ brief Resets this argument and all sub arguments recursively .
* \ sa Argument : : reset ( )
*/
void Argument : : resetRecursively ( )
{
2017-05-01 03:13:11 +02:00
for ( Argument * arg : m_subArgs ) {
2016-11-26 00:11:48 +01:00
arg - > resetRecursively ( ) ;
}
reset ( ) ;
}
2015-04-22 18:36:40 +02:00
/*!
2019-09-04 18:45:28 +02:00
* \ class ArgumentParser
2015-04-22 18:36:40 +02:00
* \ brief The ArgumentParser class provides a means for handling command line arguments .
*
* To setup the parser create instances of ApplicationUtilities : : Argument to define a
* set of known arguments and assign these to the parser using setMainArguments ( ) .
*
* To invoke parsing call parseArgs ( ) . The parser will verify the previously
* assigned definitions ( and might throw std : : invalid_argument ) and then parse the
* given command line arguments according the definitions ( and might throw
2019-09-04 18:45:28 +02:00
* CppUtilities : : Failure ) .
2015-04-22 18:36:40 +02:00
*/
/*!
2016-02-27 01:19:16 +01:00
* \ brief Constructs a new ArgumentParser .
2015-04-22 18:36:40 +02:00
*/
2017-05-01 03:13:11 +02:00
ArgumentParser : : ArgumentParser ( )
: m_actualArgc ( 0 )
, m_executable ( nullptr )
, m_unknownArgBehavior ( UnknownArgumentBehavior : : Fail )
, m_defaultArg ( nullptr )
2017-10-19 00:51:16 +02:00
, m_helpArg ( * this )
2017-05-01 03:13:11 +02:00
{
}
2015-04-22 18:36:40 +02:00
/*!
2016-02-27 01:19:16 +01:00
* \ brief Sets the main arguments for the parser . The parser will use these argument definitions
* to when parsing the command line arguments and when printing help information .
* \ remarks
2016-06-14 00:43:32 +02:00
* - The parser does not take ownership . Do not destroy the arguments as long as they are used as
* main arguments .
2016-08-05 01:43:46 +02:00
* - Sets the first specified argument as default argument if none has been assigned yet and the
* first argument does not require any values or has no mandatory sub arguments .
2015-04-22 18:36:40 +02:00
*/
void ArgumentParser : : setMainArguments ( const ArgumentInitializerList & mainArguments )
{
2019-05-04 22:35:00 +02:00
if ( ! mainArguments . size ( ) ) {
2016-06-14 00:43:32 +02:00
m_mainArgs . clear ( ) ;
2019-05-04 23:14:43 +02:00
return ;
2015-04-22 18:36:40 +02:00
}
2019-05-04 22:35:00 +02:00
for ( Argument * arg : mainArguments ) {
arg - > m_isMainArg = true ;
}
m_mainArgs . assign ( mainArguments ) ;
if ( m_defaultArg | | ( * mainArguments . begin ( ) ) - > requiredValueCount ( ) ) {
return ;
}
bool subArgsRequired = false ;
for ( const Argument * subArg : ( * mainArguments . begin ( ) ) - > subArguments ( ) ) {
if ( subArg - > isRequired ( ) ) {
subArgsRequired = true ;
break ;
}
}
if ( ! subArgsRequired ) {
m_defaultArg = * mainArguments . begin ( ) ;
}
2015-04-22 18:36:40 +02:00
}
/*!
2016-02-27 01:19:16 +01:00
* \ brief Adds the specified \ a argument to the main argument .
* \ remarks
* The parser does not take ownership . Do not destroy the argument as long as it is used as
* main argument .
*/
void ArgumentParser : : addMainArgument ( Argument * argument )
{
argument - > m_isMainArg = true ;
m_mainArgs . push_back ( argument ) ;
}
/*!
2016-07-17 01:26:34 +02:00
* \ brief Prints help text for all assigned arguments .
2015-04-22 18:36:40 +02:00
*/
void ArgumentParser : : printHelp ( ostream & os ) const
{
2016-07-31 23:20:31 +02:00
EscapeCodes : : setStyle ( os , EscapeCodes : : TextAttribute : : Bold ) ;
2019-07-22 18:17:39 +02:00
bool wroteLine = false ;
2019-05-04 22:49:57 +02:00
if ( applicationInfo . name & & * applicationInfo . name ) {
os < < applicationInfo . name ;
if ( applicationInfo . version & & * applicationInfo . version ) {
2015-08-25 19:12:05 +02:00
os < < ' , ' < < ' ' ;
}
2019-07-22 18:17:39 +02:00
wroteLine = true ;
2015-08-25 19:12:05 +02:00
}
2019-05-04 22:49:57 +02:00
if ( applicationInfo . version & & * applicationInfo . version ) {
os < < " version " < < applicationInfo . version ;
2019-07-22 18:17:39 +02:00
wroteLine = true ;
2015-08-25 19:12:05 +02:00
}
2019-07-22 18:17:39 +02:00
if ( wroteLine ) {
2015-08-25 19:12:05 +02:00
os < < ' \n ' < < ' \n ' ;
}
2016-07-31 23:20:31 +02:00
EscapeCodes : : setStyle ( os ) ;
2019-07-22 18:17:39 +02:00
if ( applicationInfo . description & & * applicationInfo . description ) {
os < < applicationInfo . description ;
wroteLine = true ;
}
if ( wroteLine ) {
os < < ' \n ' < < ' \n ' ;
}
2017-05-01 03:13:11 +02:00
if ( ! m_mainArgs . empty ( ) ) {
2019-09-14 20:52:35 +02:00
bool hasOperations = false , hasTopLevelOptions = false ;
2019-05-04 22:49:57 +02:00
for ( const Argument * const arg : m_mainArgs ) {
2018-01-28 00:38:05 +01:00
if ( arg - > denotesOperation ( ) ) {
hasOperations = true ;
2019-09-14 20:52:35 +02:00
} else if ( strcmp ( arg - > name ( ) , " help " ) ) {
hasTopLevelOptions = true ;
}
if ( hasOperations & & hasTopLevelOptions ) {
2018-01-28 00:38:05 +01:00
break ;
}
}
// check whether operations are available
if ( hasOperations ) {
// split top-level operations and other configurations
os < < " Available operations: " ;
2019-05-04 22:49:57 +02:00
for ( const Argument * const arg : m_mainArgs ) {
2019-05-04 23:14:43 +02:00
if ( ! arg - > denotesOperation ( ) | | arg - > isDeprecated ( ) | | ! strcmp ( arg - > name ( ) , " help " ) ) {
continue ;
2018-01-28 00:38:05 +01:00
}
2019-05-04 23:14:43 +02:00
os < < ' \n ' ;
arg - > printInfo ( os ) ;
2018-01-28 00:38:05 +01:00
}
2019-09-14 20:52:35 +02:00
if ( hasTopLevelOptions ) {
os < < " \n Available top-level options: " ;
for ( const Argument * const arg : m_mainArgs ) {
if ( arg - > denotesOperation ( ) | | arg - > isDeprecated ( ) | | ! strcmp ( arg - > name ( ) , " help " ) ) {
continue ;
}
os < < ' \n ' ;
arg - > printInfo ( os ) ;
2018-01-28 00:38:05 +01:00
}
}
} else {
// just show all args if no operations are available
os < < " Available arguments: " ;
2019-05-04 22:49:57 +02:00
for ( const Argument * const arg : m_mainArgs ) {
2019-05-04 23:14:43 +02:00
if ( arg - > isDeprecated ( ) | | ! strcmp ( arg - > name ( ) , " help " ) ) {
continue ;
2018-01-28 00:43:03 +01:00
}
2019-05-04 23:14:43 +02:00
os < < ' \n ' ;
arg - > printInfo ( os ) ;
2018-01-28 00:38:05 +01:00
}
2015-08-25 19:12:05 +02:00
}
2015-04-22 18:36:40 +02:00
}
2019-07-22 18:17:39 +02:00
if ( ! applicationInfo . dependencyVersions . empty ( ) ) {
os < < ' \n ' ;
auto i = applicationInfo . dependencyVersions . begin ( ) , end = applicationInfo . dependencyVersions . end ( ) ;
os < < " Linked against: " < < * i ;
for ( + + i ; i ! = end ; + + i ) {
os < < ' , ' < < ' ' < < * i ;
}
os < < ' \n ' ;
}
2019-05-04 22:49:57 +02:00
if ( applicationInfo . url & & * applicationInfo . url ) {
os < < " \n Project website: " < < applicationInfo . url < < endl ;
2015-04-22 18:36:40 +02:00
}
}
2017-11-27 10:23:38 +01:00
/*!
* \ brief Parses the specified command line arguments .
*
* The behavior is configurable by specifying the \ a behavior argument . See ParseArgumentBehavior for
* the options . By default , all options are present .
*
* \ remarks
* - The results are stored in the Argument instances assigned as main arguments and sub arguments .
* - This method will not return in the error case if the ParseArgumentBehavior : : ExitOnFailure is present
* ( default ) .
* - This method will not return in case shell completion is requested . This behavior can be altered
2019-09-14 20:53:25 +02:00
* by overriding the exit function via ArgumentParser : : setExitFunction ( ) which defaults to & std : : exit .
2017-11-27 10:23:38 +01:00
* \ throws Throws Failure if the specified arguments are invalid and the ParseArgumentBehavior : : ExitOnFailure
2019-06-10 16:03:27 +02:00
* flag is * not * present .
2019-09-14 20:53:25 +02:00
* \ sa readArgs ( )
2017-11-27 10:23:38 +01:00
*/
2019-06-10 16:03:27 +02:00
void ArgumentParser : : parseArgs ( int argc , const char * const * argv , ParseArgumentBehavior behavior )
2017-09-29 17:14:57 +02:00
{
try {
2017-11-27 10:23:38 +01:00
readArgs ( argc , argv ) ;
if ( ! argc ) {
return ;
}
if ( behavior & ParseArgumentBehavior : : CheckConstraints ) {
checkConstraints ( m_mainArgs ) ;
}
if ( behavior & ParseArgumentBehavior : : InvokeCallbacks ) {
invokeCallbacks ( m_mainArgs ) ;
}
2019-06-10 16:03:27 +02:00
} catch ( const ParseError & failure ) {
2017-11-27 10:23:38 +01:00
if ( behavior & ParseArgumentBehavior : : ExitOnFailure ) {
CMD_UTILS_START_CONSOLE ;
cerr < < failure ;
2019-03-24 21:52:10 +01:00
invokeExit ( 1 ) ;
2017-11-27 10:23:38 +01:00
}
throw ;
2017-09-29 17:14:57 +02:00
}
}
2016-10-02 21:53:58 +02:00
/*!
* \ brief Parses the specified command line arguments .
* \ remarks
* - The results are stored in the Argument instances assigned as main arguments and sub arguments .
* - In contrast to parseArgs ( ) this method does not check whether constraints are violated and it
* does not call any callbacks .
2017-09-29 17:15:39 +02:00
* - This method will not return in case shell completion is requested . This behavior can be altered
2019-09-14 20:53:25 +02:00
* by overriding the exit function via ArgumentParser : : setExitFunction ( ) which defaults to & std : : exit .
2016-10-02 21:53:58 +02:00
* \ throws Throws Failure if the specified arguments are invalid .
2019-09-14 20:53:25 +02:00
* \ sa parseArgs ( )
2017-11-27 10:23:38 +01:00
* \ deprecated In next major release , this method will be private . parseArgs ( ) can serve the same
* purpose then .
2016-10-02 21:53:58 +02:00
*/
2017-05-01 03:13:11 +02:00
void ArgumentParser : : readArgs ( int argc , const char * const * argv )
2016-10-02 21:53:58 +02:00
{
2019-06-12 20:34:25 +02:00
CPP_UTILITIES_IF_DEBUG_BUILD ( verifyArgs ( m_mainArgs ) ; )
2016-10-02 21:53:58 +02:00
m_actualArgc = 0 ;
2016-07-03 22:36:48 +02:00
2018-01-29 16:21:19 +01:00
// the first argument is the executable name
if ( ! argc ) {
m_executable = nullptr ;
return ;
}
m_executable = * argv ;
2016-07-03 22:36:48 +02:00
2018-01-29 16:21:19 +01:00
// check for further arguments
if ( ! - - argc ) {
// no arguments specified -> flag default argument as present if one is assigned
if ( m_defaultArg ) {
m_defaultArg - > m_occurrences . emplace_back ( 0 ) ;
}
return ;
}
2018-05-11 15:51:30 +02:00
// check for completion mode: if first arg (after executable name) is "--bash-completion-for", bash completion for the following arguments is requested
const bool completionMode = ! strcmp ( * + + argv , " --bash-completion-for " ) ;
// determine the index of the current word for completion and the number of arguments to be passed to ArgumentReader
2019-05-04 21:38:23 +02:00
unsigned int currentWordIndex = 0 , argcForReader ;
2018-01-29 16:21:19 +01:00
if ( completionMode ) {
// the first argument after "--bash-completion-for" is the index of the current word
try {
currentWordIndex = ( - - argc ? stringToNumber < unsigned int , string > ( * ( + + argv ) ) : 0 ) ;
if ( argc ) {
2019-05-04 21:38:23 +02:00
+ + argv ;
- - argc ;
2016-06-14 00:43:32 +02:00
}
2018-01-29 16:21:19 +01:00
} catch ( const ConversionException & ) {
currentWordIndex = static_cast < unsigned int > ( argc - 1 ) ;
2016-06-14 00:43:32 +02:00
}
2018-05-11 15:51:30 +02:00
argcForReader = min ( static_cast < unsigned int > ( argc ) , currentWordIndex + 1 ) ;
} else {
argcForReader = static_cast < unsigned int > ( argc ) ;
2018-01-29 16:21:19 +01:00
}
// read specified arguments
2018-05-11 15:51:30 +02:00
ArgumentReader reader ( * this , argv , argv + argcForReader , completionMode ) ;
2018-05-07 20:04:30 +02:00
const bool allArgsProcessed ( reader . read ( ) ) ;
2017-10-19 00:51:16 +02:00
m_noColorArg . apply ( ) ;
2018-05-11 15:51:30 +02:00
// fail when not all arguments could be processed, except when in completion mode
2018-05-07 20:04:30 +02:00
if ( ! completionMode & & ! allArgsProcessed ) {
2018-05-11 15:51:30 +02:00
const auto suggestions ( findSuggestions ( argc , argv , static_cast < unsigned int > ( argc - 1 ) , reader ) ) ;
2019-06-10 16:03:27 +02:00
throw ParseError ( argsToString ( " The specified argument \" " , * reader . argv , " \" is unknown. " , suggestions ) ) ;
2018-01-29 16:21:19 +01:00
}
2021-07-03 19:07:49 +02:00
// print Bash completion and prevent the application to continue with the regular execution
2018-01-29 16:21:19 +01:00
if ( completionMode ) {
printBashCompletion ( argc , argv , currentWordIndex , reader ) ;
2019-03-24 21:52:10 +01:00
invokeExit ( 0 ) ;
2016-06-12 01:56:57 +02:00
}
}
2016-11-26 00:11:48 +01:00
/*!
* \ brief Resets all Argument instances assigned as mainArguments ( ) and sub arguments .
* \ sa Argument : : reset ( )
*/
void ArgumentParser : : resetArgs ( )
{
2017-05-01 03:13:11 +02:00
for ( Argument * arg : m_mainArgs ) {
2016-11-26 00:11:48 +01:00
arg - > resetRecursively ( ) ;
}
2016-12-23 22:41:06 +01:00
m_actualArgc = 0 ;
2016-11-26 00:11:48 +01:00
}
2017-09-26 16:46:17 +02:00
/*!
* \ brief Returns the first operation argument specified by the user or nullptr if no operation has been specified .
* \ remarks Only main arguments are considered . See Argument : : specifiedOperation ( ) to check sub arguments of a specific
* argument .
*/
Argument * ArgumentParser : : specifiedOperation ( ) const
{
for ( Argument * arg : m_mainArgs ) {
if ( arg - > denotesOperation ( ) & & arg - > isPresent ( ) ) {
return arg ;
}
}
return nullptr ;
}
2016-11-15 21:44:23 +01:00
/*!
* \ brief Checks whether at least one uncombinable main argument is present .
*/
bool ArgumentParser : : isUncombinableMainArgPresent ( ) const
{
2017-05-01 03:13:11 +02:00
for ( const Argument * arg : m_mainArgs ) {
if ( ! arg - > isCombinable ( ) & & arg - > isPresent ( ) ) {
2016-11-15 21:44:23 +01:00
return true ;
}
}
return false ;
}
2019-06-12 20:34:25 +02:00
# ifdef CPP_UTILITIES_DEBUG_BUILD
2015-04-22 18:36:40 +02:00
/*!
2016-06-12 01:56:57 +02:00
* \ brief Verifies the specified \ a argument definitions .
2015-04-22 18:36:40 +02:00
*
2016-06-12 01:56:57 +02:00
* Asserts that
* - The same argument has not been added twice to the same parent .
* - Only one argument within a parent is default or implicit .
* - Only main arguments denote operations .
2017-04-06 00:01:06 +02:00
* - Argument abbreviations are unique within the same level .
* - Argument names are unique within within the same level .
2015-04-22 18:36:40 +02:00
*
2016-06-12 01:56:57 +02:00
* \ remarks
* - Verifies the sub arguments , too .
2018-03-07 19:40:29 +01:00
* - For debugging purposes only ; hence only used in debug builds .
2015-04-22 18:36:40 +02:00
*/
2019-06-10 21:56:46 +02:00
void ArgumentParser : : verifyArgs ( const ArgumentVector & args )
2015-04-22 18:36:40 +02:00
{
2016-06-12 01:56:57 +02:00
vector < const Argument * > verifiedArgs ;
verifiedArgs . reserve ( args . size ( ) ) ;
2017-04-05 23:56:57 +02:00
vector < char > abbreviations ;
2016-11-15 22:02:40 +01:00
abbreviations . reserve ( abbreviations . size ( ) + args . size ( ) ) ;
2017-04-05 23:56:57 +02:00
vector < const char * > names ;
2016-11-15 22:02:40 +01:00
names . reserve ( names . size ( ) + args . size ( ) ) ;
2016-06-14 00:43:32 +02:00
bool hasImplicit = false ;
2017-05-01 03:13:11 +02:00
for ( const Argument * arg : args ) {
2016-06-12 01:56:57 +02:00
assert ( find ( verifiedArgs . cbegin ( ) , verifiedArgs . cend ( ) , arg ) = = verifiedArgs . cend ( ) ) ;
verifiedArgs . push_back ( arg ) ;
2016-06-14 00:43:32 +02:00
assert ( ! arg - > isImplicit ( ) | | ! hasImplicit ) ;
hasImplicit | = arg - > isImplicit ( ) ;
2016-06-12 01:56:57 +02:00
assert ( ! arg - > abbreviation ( ) | | find ( abbreviations . cbegin ( ) , abbreviations . cend ( ) , arg - > abbreviation ( ) ) = = abbreviations . cend ( ) ) ;
abbreviations . push_back ( arg - > abbreviation ( ) ) ;
2017-05-01 03:13:11 +02:00
assert ( ! arg - > name ( ) | | find_if ( names . cbegin ( ) , names . cend ( ) , [ arg ] ( const char * name ) { return ! strcmp ( arg - > name ( ) , name ) ; } ) = = names . cend ( ) ) ;
2016-06-12 01:56:57 +02:00
assert ( arg - > requiredValueCount ( ) = = 0 | | arg - > subArguments ( ) . size ( ) = = 0 ) ;
names . emplace_back ( arg - > name ( ) ) ;
2016-11-15 22:02:40 +01:00
}
2017-05-01 03:13:11 +02:00
for ( const Argument * arg : args ) {
2018-03-07 19:40:29 +01:00
verifyArgs ( arg - > subArguments ( ) ) ;
2016-06-12 01:56:57 +02:00
}
2015-04-22 18:36:40 +02:00
}
2016-05-25 01:24:17 +02:00
# endif
2015-04-22 18:36:40 +02:00
2016-07-03 22:36:48 +02:00
/*!
* \ brief Returns whether \ a arg1 should be listed before \ a arg2 when
* printing completion .
*
* Arguments are sorted by name ( ascending order ) . However , all arguments
* denoting an operation are listed before all other arguments .
*/
bool compareArgs ( const Argument * arg1 , const Argument * arg2 )
{
2017-05-01 03:13:11 +02:00
if ( arg1 - > denotesOperation ( ) & & ! arg2 - > denotesOperation ( ) ) {
2016-07-03 22:36:48 +02:00
return true ;
2017-05-01 03:13:11 +02:00
} else if ( ! arg1 - > denotesOperation ( ) & & arg2 - > denotesOperation ( ) ) {
2016-07-03 22:36:48 +02:00
return false ;
} else {
return strcmp ( arg1 - > name ( ) , arg2 - > name ( ) ) < 0 ;
}
}
/*!
* \ brief Inserts the specified \ a siblings in the \ a target list .
* \ remarks Only inserts siblings which could still occur at least once more .
*/
void insertSiblings ( const ArgumentVector & siblings , list < const Argument * > & target )
{
bool onlyCombinable = false ;
2017-05-01 03:13:11 +02:00
for ( const Argument * sibling : siblings ) {
if ( sibling - > isPresent ( ) & & ! sibling - > isCombinable ( ) ) {
2016-07-03 22:36:48 +02:00
onlyCombinable = true ;
break ;
}
}
2017-05-01 03:13:11 +02:00
for ( const Argument * sibling : siblings ) {
if ( ( ! onlyCombinable | | sibling - > isCombinable ( ) ) & & sibling - > occurrences ( ) < sibling - > maxOccurrences ( ) ) {
2016-07-03 22:36:48 +02:00
target . push_back ( sibling ) ;
}
}
}
/*!
2018-05-06 00:26:42 +02:00
* \ brief Determines arguments relevant for Bash completion or suggestions in case of typo .
2016-07-03 22:36:48 +02:00
*/
2018-05-06 00:26:42 +02:00
ArgumentCompletionInfo ArgumentParser : : determineCompletionInfo (
2018-05-07 21:59:23 +02:00
int argc , const char * const * argv , unsigned int currentWordIndex , const ArgumentReader & reader ) const
2016-07-03 22:36:48 +02:00
{
2018-05-06 00:26:42 +02:00
ArgumentCompletionInfo completion ( reader ) ;
2016-07-03 22:36:48 +02:00
2018-05-06 00:26:42 +02:00
// determine last detected arg
if ( completion . lastDetectedArg ) {
2019-05-04 21:38:23 +02:00
completion . lastDetectedArgIndex = static_cast < size_t > ( reader . lastArgDenotation - argv ) ;
2018-05-06 00:26:42 +02:00
completion . lastDetectedArgPath = completion . lastDetectedArg - > path ( completion . lastDetectedArg - > occurrences ( ) - 1 ) ;
2016-07-03 22:36:48 +02:00
}
2016-10-22 19:32:16 +02:00
// determine last arg, omitting trailing empty args
2017-05-01 03:13:11 +02:00
if ( argc ) {
2018-05-06 00:26:42 +02:00
completion . lastSpecifiedArgIndex = static_cast < unsigned int > ( argc ) - 1 ;
completion . lastSpecifiedArg = argv + completion . lastSpecifiedArgIndex ;
for ( ; completion . lastSpecifiedArg > = argv & & * * completion . lastSpecifiedArg = = ' \0 ' ;
- - completion . lastSpecifiedArg , - - completion . lastSpecifiedArgIndex )
2017-05-01 03:13:11 +02:00
;
2016-07-03 22:36:48 +02:00
}
2018-05-06 00:26:42 +02:00
// just return main arguments if no args detected
if ( ! completion . lastDetectedArg | | ! completion . lastDetectedArg - > isPresent ( ) ) {
completion . nextArgumentOrValue = true ;
insertSiblings ( m_mainArgs , completion . relevantArgs ) ;
completion . relevantArgs . sort ( compareArgs ) ;
return completion ;
}
2017-07-28 17:32:07 +02:00
2018-05-06 00:26:42 +02:00
completion . nextArgumentOrValue = currentWordIndex > completion . lastDetectedArgIndex ;
if ( ! completion . nextArgumentOrValue ) {
// since the argument could be detected (hopefully unambiguously?) just return it for "final completion"
completion . relevantArgs . push_back ( completion . lastDetectedArg ) ;
completion . relevantArgs . sort ( compareArgs ) ;
return completion ;
}
2017-07-28 17:32:07 +02:00
2018-05-06 00:26:42 +02:00
// define function to add parameter values of argument as possible completions
const auto addValueCompletionsForArg = [ & completion ] ( const Argument * arg ) {
if ( arg - > valueCompletionBehaviour ( ) & ValueCompletionBehavior : : PreDefinedValues ) {
completion . relevantPreDefinedValues . push_back ( arg ) ;
}
if ( ! ( arg - > valueCompletionBehaviour ( ) & ValueCompletionBehavior : : FileSystemIfNoPreDefinedValues ) | | ! arg - > preDefinedCompletionValues ( ) ) {
completion . completeFiles = completion . completeFiles | | arg - > valueCompletionBehaviour ( ) & ValueCompletionBehavior : : Files ;
completion . completeDirs = completion . completeDirs | | arg - > valueCompletionBehaviour ( ) & ValueCompletionBehavior : : Directories ;
}
} ;
// detect number of specified values
auto currentValueCount = completion . lastDetectedArg - > values ( completion . lastDetectedArg - > occurrences ( ) - 1 ) . size ( ) ;
// ignore values which are specified after the current word
if ( currentValueCount ) {
const auto currentWordIndexRelativeToLastDetectedArg = currentWordIndex - completion . lastDetectedArgIndex ;
if ( currentValueCount > currentWordIndexRelativeToLastDetectedArg ) {
currentValueCount - = currentWordIndexRelativeToLastDetectedArg ;
} else {
currentValueCount = 0 ;
}
}
2016-07-03 22:36:48 +02:00
2018-05-06 00:26:42 +02:00
// add value completions for implicit child if there are no value specified and there are no values required by the
// last detected argument itself
if ( ! currentValueCount & & ! completion . lastDetectedArg - > requiredValueCount ( ) ) {
for ( const Argument * child : completion . lastDetectedArg - > subArguments ( ) ) {
if ( child - > isImplicit ( ) & & child - > requiredValueCount ( ) ) {
addValueCompletionsForArg ( child ) ;
break ;
2017-07-28 17:32:07 +02:00
}
2018-05-06 00:26:42 +02:00
}
}
2017-07-28 17:32:07 +02:00
2018-05-06 00:26:42 +02:00
// add value completions for last argument if there are further values required
if ( completion . lastDetectedArg - > requiredValueCount ( ) = = Argument : : varValueCount
| | ( currentValueCount < completion . lastDetectedArg - > requiredValueCount ( ) ) ) {
addValueCompletionsForArg ( completion . lastDetectedArg ) ;
}
2016-07-03 22:36:48 +02:00
2018-05-06 00:26:42 +02:00
if ( completion . lastDetectedArg - > requiredValueCount ( ) = = Argument : : varValueCount
| | completion . lastDetectedArg - > values ( completion . lastDetectedArg - > occurrences ( ) - 1 ) . size ( )
> = completion . lastDetectedArg - > requiredValueCount ( ) ) {
// sub arguments of the last arg are possible completions
for ( const Argument * subArg : completion . lastDetectedArg - > subArguments ( ) ) {
if ( subArg - > occurrences ( ) < subArg - > maxOccurrences ( ) ) {
completion . relevantArgs . push_back ( subArg ) ;
2016-07-03 22:36:48 +02:00
}
}
2018-05-06 00:26:42 +02:00
// siblings of parents are possible completions as well
for ( auto parentArgument = completion . lastDetectedArgPath . crbegin ( ) , end = completion . lastDetectedArgPath . crend ( ) ; ; + + parentArgument ) {
insertSiblings ( parentArgument ! = end ? ( * parentArgument ) - > subArguments ( ) : m_mainArgs , completion . relevantArgs ) ;
if ( parentArgument = = end ) {
break ;
}
}
2016-07-03 22:36:48 +02:00
}
2018-05-06 00:26:42 +02:00
return completion ;
}
2018-05-07 21:59:23 +02:00
/*!
* \ brief Returns the suggestion string printed in error case due to unknown arguments .
*/
string ArgumentParser : : findSuggestions ( int argc , const char * const * argv , unsigned int cursorPos , const ArgumentReader & reader ) const
{
// determine completion info
const auto completionInfo ( determineCompletionInfo ( argc , argv , cursorPos , reader ) ) ;
2018-05-11 16:15:02 +02:00
// determine the unknown/misspelled argument
const auto * unknownArg ( * reader . argv ) ;
auto unknownArgSize ( strlen ( unknownArg ) ) ;
2018-05-11 16:15:16 +02:00
// -> refuse suggestions for long args to prevent huge memory allocation for Damerau-Levenshtein algo
if ( unknownArgSize > 16 ) {
return string ( ) ;
}
2018-05-11 16:15:02 +02:00
// -> remove dashes since argument names internally don't have them
if ( unknownArgSize > = 2 & & unknownArg [ 0 ] = = ' - ' & & unknownArg [ 1 ] = = ' - ' ) {
unknownArg + = 2 ;
unknownArgSize - = 2 ;
}
2018-05-07 21:59:23 +02:00
// find best suggestions limiting the results to 2
multiset < ArgumentSuggestion > bestSuggestions ;
// -> consider relevant arguments
for ( const Argument * const arg : completionInfo . relevantArgs ) {
2018-05-11 16:15:02 +02:00
ArgumentSuggestion ( unknownArg , unknownArgSize , arg - > name ( ) , ! arg - > denotesOperation ( ) ) . addTo ( bestSuggestions , 2 ) ;
2018-05-07 21:59:23 +02:00
}
// -> consider relevant values
for ( const Argument * const arg : completionInfo . relevantPreDefinedValues ) {
2018-05-11 18:12:52 +02:00
if ( ! arg - > preDefinedCompletionValues ( ) ) {
continue ;
}
2018-05-07 21:59:23 +02:00
for ( const char * i = arg - > preDefinedCompletionValues ( ) ; * i ; + + i ) {
const char * const wordStart ( i ) ;
const char * wordEnd ( wordStart + 1 ) ;
for ( ; * wordEnd & & * wordEnd ! = ' ' ; + + wordEnd )
;
2018-05-11 16:15:02 +02:00
ArgumentSuggestion ( unknownArg , unknownArgSize , wordStart , static_cast < size_t > ( wordEnd - wordStart ) , false ) . addTo ( bestSuggestions , 2 ) ;
2018-05-07 21:59:23 +02:00
i = wordEnd ;
}
}
// format suggestion
string suggestionStr ;
if ( const auto suggestionCount = bestSuggestions . size ( ) ) {
// allocate memory
size_t requiredSize = 15 ;
for ( const auto & suggestion : bestSuggestions ) {
requiredSize + = suggestion . suggestionSize + 2 ;
2018-05-11 16:15:02 +02:00
if ( suggestion . hasDashPrefix ) {
requiredSize + = 2 ;
}
2018-05-07 21:59:23 +02:00
}
suggestionStr . reserve ( requiredSize ) ;
// add each suggestion to end up with something like "Did you mean status (1), pause (3), cat (4), edit (5) or rescan-all (8)?"
suggestionStr + = " \n Did you mean " ;
size_t i = 0 ;
for ( const auto & suggestion : bestSuggestions ) {
2018-05-11 15:29:10 +02:00
if ( + + i = = suggestionCount & & suggestionCount ! = 1 ) {
2018-05-07 21:59:23 +02:00
suggestionStr + = " or " ;
} else if ( i > 1 ) {
suggestionStr + = " , " ;
}
2018-05-11 16:15:02 +02:00
if ( suggestion . hasDashPrefix ) {
suggestionStr + = " -- " ;
}
2018-05-07 21:59:23 +02:00
suggestionStr . append ( suggestion . suggestion , suggestion . suggestionSize ) ;
}
suggestionStr + = ' ? ' ;
}
return suggestionStr ;
}
2018-05-06 00:26:42 +02:00
/*!
* \ brief Prints the bash completion for the specified arguments and the specified \ a lastPath .
* \ remarks Arguments must have been parsed before with readSpecifiedArgs ( ) . When calling this method , completionMode must
* be set to true .
*/
2018-05-07 21:59:23 +02:00
void ArgumentParser : : printBashCompletion ( int argc , const char * const * argv , unsigned int currentWordIndex , const ArgumentReader & reader ) const
2018-05-06 00:26:42 +02:00
{
2018-05-07 21:59:23 +02:00
// determine completion info and sort relevant arguments
const auto completionInfo ( [ & ] {
auto clutteredCompletionInfo ( determineCompletionInfo ( argc , argv , currentWordIndex , reader ) ) ;
clutteredCompletionInfo . relevantArgs . sort ( compareArgs ) ;
return clutteredCompletionInfo ;
} ( ) ) ;
2018-05-06 00:26:42 +02:00
2016-07-03 22:36:48 +02:00
// read the "opening" (started but not finished argument denotation)
const char * opening = nullptr ;
2016-07-31 23:20:31 +02:00
string compoundOpening ;
2018-05-06 00:33:33 +02:00
size_t openingLen = 0 , compoundOpeningStartLen = 0 ;
2016-07-03 22:36:48 +02:00
unsigned char openingDenotationType = Value ;
2018-05-06 00:26:42 +02:00
if ( argc & & completionInfo . nextArgumentOrValue ) {
2017-05-01 03:13:11 +02:00
if ( currentWordIndex < static_cast < unsigned int > ( argc ) ) {
2016-07-31 23:20:31 +02:00
opening = argv [ currentWordIndex ] ;
2021-07-03 19:07:49 +02:00
// For some reason completions for eg. "set --values disk=1 tag=a" are split so the
2016-07-31 23:20:31 +02:00
// equation sign is an own argument ("set --values disk = 1 tag = a").
// This is not how values are treated by the argument parser. Hence the opening
// must be joined again. In this case only the part after the equation sign needs to be
// provided for completion so compoundOpeningStartLen is set to number of characters to skip.
2018-05-06 00:33:33 +02:00
const size_t minCurrentWordIndex = ( completionInfo . lastDetectedArg ? completionInfo . lastDetectedArgIndex : 0 ) ;
2017-05-01 03:13:11 +02:00
if ( currentWordIndex > minCurrentWordIndex & & ! strcmp ( opening , " = " ) ) {
2016-07-31 23:20:31 +02:00
compoundOpening . reserve ( compoundOpeningStartLen = strlen ( argv [ - - currentWordIndex ] ) + 1 ) ;
compoundOpening = argv [ currentWordIndex ] ;
compoundOpening + = ' = ' ;
2017-05-01 03:13:11 +02:00
} else if ( currentWordIndex > ( minCurrentWordIndex + 1 ) & & ! strcmp ( argv [ currentWordIndex - 1 ] , " = " ) ) {
2016-07-31 23:20:31 +02:00
compoundOpening . reserve ( ( compoundOpeningStartLen = strlen ( argv [ currentWordIndex - = 2 ] ) + 1 ) + strlen ( opening ) ) ;
compoundOpening = argv [ currentWordIndex ] ;
compoundOpening + = ' = ' ;
compoundOpening + = opening ;
}
2017-05-01 03:13:11 +02:00
if ( ! compoundOpening . empty ( ) ) {
2016-07-31 23:20:31 +02:00
opening = compoundOpening . data ( ) ;
}
} else {
2018-05-06 00:26:42 +02:00
opening = * completionInfo . lastSpecifiedArg ;
2016-07-31 23:20:31 +02:00
}
2019-05-04 21:38:23 +02:00
if ( * opening = = ' - ' ) {
+ + opening ;
+ + openingDenotationType ;
if ( * opening = = ' - ' ) {
+ + opening ;
+ + openingDenotationType ;
}
}
2016-07-03 22:36:48 +02:00
openingLen = strlen ( opening ) ;
}
// print "COMPREPLY" bash array
cout < < " COMPREPLY=( " ;
// -> completions for parameter values
2018-05-06 00:26:42 +02:00
bool noWhitespace = false ;
2018-05-06 00:33:33 +02:00
for ( const Argument * const arg : completionInfo . relevantPreDefinedValues ) {
2017-09-30 18:42:21 +02:00
if ( arg - > valueCompletionBehaviour ( ) & ValueCompletionBehavior : : InvokeCallback & & arg - > m_callbackFunction ) {
arg - > m_callbackFunction ( arg - > isPresent ( ) ? arg - > m_occurrences . front ( ) : ArgumentOccurrence ( Argument : : varValueCount ) ) ;
}
2018-05-06 00:33:33 +02:00
if ( ! arg - > preDefinedCompletionValues ( ) ) {
continue ;
}
const bool appendEquationSign = arg - > valueCompletionBehaviour ( ) & ValueCompletionBehavior : : AppendEquationSign ;
if ( argc & & currentWordIndex < = completionInfo . lastSpecifiedArgIndex & & opening ) {
if ( openingDenotationType ! = Value ) {
continue ;
}
bool wordStart = true , ok = false , equationSignAlreadyPresent = false ;
size_t wordIndex = 0 ;
for ( const char * i = arg - > preDefinedCompletionValues ( ) , * end = opening + openingLen ; * i ; ) {
if ( wordStart ) {
const char * i1 = i , * i2 = opening ;
for ( ; * i1 & & i2 ! = end & & * i1 = = * i2 ; + + i1 , + + i2 )
;
if ( ( ok = ( i2 = = end ) ) ) {
cout < < ' \' ' ;
}
wordStart = false ;
wordIndex = 0 ;
} else if ( ( wordStart = ( * i = = ' ' ) | | ( * i = = ' \n ' ) ) ) {
equationSignAlreadyPresent = false ;
if ( ok ) {
cout < < ' \' ' < < ' ' ;
2016-07-03 22:36:48 +02:00
}
2018-05-06 00:33:33 +02:00
+ + i ;
continue ;
} else if ( * i = = ' = ' ) {
equationSignAlreadyPresent = true ;
2016-07-03 22:36:48 +02:00
}
2018-05-06 00:33:33 +02:00
if ( ! ok ) {
+ + i ;
continue ;
}
if ( ! compoundOpeningStartLen | | wordIndex > = compoundOpeningStartLen ) {
2017-05-01 03:13:11 +02:00
if ( * i = = ' \' ' ) {
2016-10-22 19:32:16 +02:00
cout < < " ' \" ' \" ' " ;
} else {
cout < < * i ;
}
2018-05-06 00:33:33 +02:00
}
2019-05-04 21:38:23 +02:00
+ + i ;
+ + wordIndex ;
2018-05-06 00:33:33 +02:00
switch ( * i ) {
case ' ' :
case ' \n ' :
case ' \0 ' :
if ( appendEquationSign & & ! equationSignAlreadyPresent ) {
cout < < ' = ' ;
noWhitespace = true ;
equationSignAlreadyPresent = false ;
}
if ( * i = = ' \0 ' ) {
cout < < ' \' ' ;
}
}
}
cout < < ' ' ;
} else if ( const char * i = arg - > preDefinedCompletionValues ( ) ) {
bool equationSignAlreadyPresent = false ;
cout < < ' \' ' ;
while ( * i ) {
if ( * i = = ' \' ' ) {
cout < < " ' \" ' \" ' " ;
} else {
cout < < * i ;
}
switch ( * ( + + i ) ) {
case ' = ' :
equationSignAlreadyPresent = true ;
break ;
case ' ' :
case ' \n ' :
case ' \0 ' :
if ( appendEquationSign & & ! equationSignAlreadyPresent ) {
cout < < ' = ' ;
equationSignAlreadyPresent = false ;
}
if ( * i ! = ' \0 ' ) {
cout < < ' \' ' ;
if ( * ( + + i ) ) {
cout < < ' ' < < ' \' ' ;
2016-10-22 19:32:16 +02:00
}
2016-07-03 22:36:48 +02:00
}
}
}
2018-05-06 00:33:33 +02:00
cout < < ' \' ' < < ' ' ;
2016-07-03 22:36:48 +02:00
}
}
// -> completions for further arguments
2018-05-06 00:33:33 +02:00
for ( const Argument * const arg : completionInfo . relevantArgs ) {
2018-05-06 00:26:42 +02:00
if ( argc & & currentWordIndex < = completionInfo . lastSpecifiedArgIndex & & opening ) {
2017-05-01 03:13:11 +02:00
switch ( openingDenotationType ) {
2016-07-03 22:36:48 +02:00
case Value :
2017-05-01 03:13:11 +02:00
if ( ! arg - > denotesOperation ( ) | | strncmp ( arg - > name ( ) , opening , openingLen ) ) {
2016-07-03 22:36:48 +02:00
continue ;
}
break ;
case Abbreviation :
break ;
case FullName :
2017-05-01 03:13:11 +02:00
if ( strncmp ( arg - > name ( ) , opening , openingLen ) ) {
2016-07-03 22:36:48 +02:00
continue ;
}
}
}
2018-05-06 00:26:42 +02:00
if ( opening & & openingDenotationType = = Abbreviation & & ! completionInfo . nextArgumentOrValue ) {
2018-01-29 16:23:10 +01:00
// TODO: add test for this case
2016-10-22 19:32:16 +02:00
cout < < ' \' ' < < ' - ' < < opening < < arg - > abbreviation ( ) < < ' \' ' < < ' ' ;
2018-05-06 00:26:42 +02:00
} else if ( completionInfo . lastDetectedArg & & reader . argDenotationType = = Abbreviation & & ! completionInfo . nextArgumentOrValue ) {
2017-05-01 03:13:11 +02:00
if ( reader . argv = = reader . end ) {
2016-12-23 22:41:06 +01:00
cout < < ' \' ' < < * ( reader . argv - 1 ) < < ' \' ' < < ' ' ;
}
2017-05-01 03:13:11 +02:00
} else if ( arg - > denotesOperation ( ) ) {
2016-10-22 19:32:16 +02:00
cout < < ' \' ' < < arg - > name ( ) < < ' \' ' < < ' ' ;
2016-07-03 22:36:48 +02:00
} else {
2016-10-22 19:32:16 +02:00
cout < < ' \' ' < < ' - ' < < ' - ' < < arg - > name ( ) < < ' \' ' < < ' ' ;
2016-07-03 22:36:48 +02:00
}
}
// -> completions for files and dirs
// -> if there's already an "opening", determine the dir part and the file part
string actualDir , actualFile ;
bool haveFileOrDirCompletions = false ;
2018-05-06 00:26:42 +02:00
if ( argc & & currentWordIndex = = completionInfo . lastSpecifiedArgIndex & & opening ) {
2016-10-22 19:32:16 +02:00
// the "opening" might contain escaped characters which need to be unescaped first (let's hope this covers all possible escapings)
2016-07-03 22:36:48 +02:00
string unescapedOpening ( opening ) ;
findAndReplace < string > ( unescapedOpening , " \\ " , " " ) ;
findAndReplace < string > ( unescapedOpening , " \\ , " , " , " ) ;
findAndReplace < string > ( unescapedOpening , " \\ [ " , " [ " ) ;
findAndReplace < string > ( unescapedOpening , " \\ ] " , " ] " ) ;
findAndReplace < string > ( unescapedOpening , " \\ ! " , " ! " ) ;
findAndReplace < string > ( unescapedOpening , " \\ # " , " # " ) ;
findAndReplace < string > ( unescapedOpening , " \\ $ " , " $ " ) ;
2016-10-22 19:32:16 +02:00
findAndReplace < string > ( unescapedOpening , " \\ ' " , " ' " ) ;
findAndReplace < string > ( unescapedOpening , " \\ \" " , " \" " ) ;
findAndReplace < string > ( unescapedOpening , " \\ \\ " , " \\ " ) ;
2016-07-03 22:36:48 +02:00
// determine the "directory" part
string dir = directory ( unescapedOpening ) ;
2017-05-01 03:13:11 +02:00
if ( dir . empty ( ) ) {
2016-07-03 22:36:48 +02:00
actualDir = " . " ;
} else {
2017-05-01 03:13:11 +02:00
if ( dir [ 0 ] = = ' \" ' | | dir [ 0 ] = = ' \' ' ) {
2016-07-03 22:36:48 +02:00
dir . erase ( 0 , 1 ) ;
}
2017-05-01 03:13:11 +02:00
if ( dir . size ( ) > 1 & & ( dir [ dir . size ( ) - 2 ] = = ' \" ' | | dir [ dir . size ( ) - 2 ] = = ' \' ' ) ) {
2016-07-03 22:36:48 +02:00
dir . erase ( dir . size ( ) - 2 , 1 ) ;
}
actualDir = move ( dir ) ;
}
// determine the "file" part
string file = fileName ( unescapedOpening ) ;
2017-05-01 03:13:11 +02:00
if ( file [ 0 ] = = ' \" ' | | file [ 0 ] = = ' \' ' ) {
2016-07-03 22:36:48 +02:00
file . erase ( 0 , 1 ) ;
}
2021-05-12 17:52:36 +02:00
if ( file . size ( ) > 1 & & ( file [ file . size ( ) - 2 ] = = ' \" ' | | file [ file . size ( ) - 2 ] = = ' \' ' ) ) {
2016-07-03 22:36:48 +02:00
file . erase ( file . size ( ) - 2 , 1 ) ;
}
actualFile = move ( file ) ;
}
2016-10-22 19:32:16 +02:00
// -> completion for files and dirs
2019-07-02 18:40:25 +02:00
# ifdef CPP_UTILITIES_USE_STANDARD_FILESYSTEM
2019-06-05 23:49:35 +02:00
if ( completionInfo . completeFiles | | completionInfo . completeDirs ) {
2020-01-29 19:26:49 +01:00
try {
const auto replace = " ' " s , with = " ' \" ' \" ' " s ;
const auto useActualDir = argc & & currentWordIndex < = completionInfo . lastSpecifiedArgIndex & & opening ;
const auto dirEntries = [ & ] {
filesystem : : directory_iterator i ;
if ( useActualDir ) {
i = filesystem : : directory_iterator ( actualDir ) ;
findAndReplace ( actualDir , replace , with ) ;
} else {
i = filesystem : : directory_iterator ( " . " ) ;
}
return i ;
} ( ) ;
for ( const auto & dirEntry : dirEntries ) {
if ( ! completionInfo . completeDirs & & dirEntry . is_directory ( ) ) {
2018-05-06 00:33:33 +02:00
continue ;
2016-07-03 22:36:48 +02:00
}
2020-01-29 19:26:49 +01:00
if ( ! completionInfo . completeFiles & & ! dirEntry . is_directory ( ) ) {
continue ;
2018-05-06 00:33:33 +02:00
}
2020-01-29 19:26:49 +01:00
auto dirEntryName = dirEntry . path ( ) . filename ( ) . string ( ) ;
auto hasStartingQuote = false ;
if ( useActualDir ) {
if ( ! startsWith ( dirEntryName , actualFile ) ) {
continue ;
}
cout < < ' \' ' ;
hasStartingQuote = true ;
if ( actualDir ! = " . " ) {
cout < < actualDir ;
}
}
findAndReplace ( dirEntryName , replace , with ) ;
if ( ! hasStartingQuote ) {
cout < < ' \' ' ;
}
cout < < dirEntryName < < ' \' ' < < ' ' ;
haveFileOrDirCompletions = true ;
2016-07-03 22:36:48 +02:00
}
2020-01-29 19:26:49 +01:00
} catch ( const filesystem : : filesystem_error & ) {
// ignore filesystem errors; there's no good way to report errors when printing bash completion
2016-07-03 22:36:48 +02:00
}
}
2019-07-02 18:40:25 +02:00
# endif
2016-07-03 22:36:48 +02:00
cout < < ' ) ' ;
// ensure file or dir completions are formatted appropriately
2017-05-01 03:13:11 +02:00
if ( haveFileOrDirCompletions ) {
2016-07-03 22:36:48 +02:00
cout < < " ; compopt -o filenames " ;
}
2021-07-03 19:07:49 +02:00
// ensure trailing whitespace is omitted
2017-05-01 03:13:11 +02:00
if ( noWhitespace ) {
2016-07-03 22:36:48 +02:00
cout < < " ; compopt -o nospace " ;
}
cout < < endl ;
}
2016-06-12 01:56:57 +02:00
/*!
* \ brief Checks the constrains of the specified \ a args .
2021-07-03 19:07:49 +02:00
* \ remarks Checks the constraints of sub arguments , too .
2016-06-12 01:56:57 +02:00
*/
void ArgumentParser : : checkConstraints ( const ArgumentVector & args )
{
2017-05-01 03:13:11 +02:00
for ( const Argument * arg : args ) {
2016-06-12 01:56:57 +02:00
const auto occurrences = arg - > occurrences ( ) ;
2017-05-01 03:13:11 +02:00
if ( arg - > isParentPresent ( ) & & occurrences > arg - > maxOccurrences ( ) ) {
2019-06-10 16:03:27 +02:00
throw ParseError ( argsToString ( " The argument \" " , arg - > name ( ) , " \" mustn't be specified more than " , arg - > maxOccurrences ( ) ,
2017-05-01 03:13:11 +02:00
( arg - > maxOccurrences ( ) = = 1 ? " time. " : " times. " ) ) ) ;
2015-07-27 23:19:10 +02:00
}
2017-05-01 03:13:11 +02:00
if ( arg - > isParentPresent ( ) & & occurrences < arg - > minOccurrences ( ) ) {
2019-06-10 16:03:27 +02:00
throw ParseError ( argsToString ( " The argument \" " , arg - > name ( ) , " \" must be specified at least " , arg - > minOccurrences ( ) ,
2017-05-01 03:13:11 +02:00
( arg - > minOccurrences ( ) = = 1 ? " time. " : " times. " ) ) ) ;
2016-06-12 01:56:57 +02:00
}
Argument * conflictingArgument = nullptr ;
2017-05-01 03:13:11 +02:00
if ( arg - > isMainArgument ( ) ) {
if ( ! arg - > isCombinable ( ) & & arg - > isPresent ( ) ) {
2016-06-12 01:56:57 +02:00
conflictingArgument = firstPresentUncombinableArg ( m_mainArgs , arg ) ;
2015-04-22 18:36:40 +02:00
}
2016-06-12 01:56:57 +02:00
} else {
conflictingArgument = arg - > conflictsWithArgument ( ) ;
}
2017-05-01 03:13:11 +02:00
if ( conflictingArgument ) {
2019-06-10 16:03:27 +02:00
throw ParseError ( argsToString ( " The argument \" " , conflictingArgument - > name ( ) , " \" can not be combined with \" " , arg - > name ( ) , " \" . " ) ) ;
2016-06-12 01:56:57 +02:00
}
2017-05-01 03:13:11 +02:00
for ( size_t i = 0 ; i ! = occurrences ; + + i ) {
2019-05-04 22:35:00 +02:00
if ( arg - > allRequiredValuesPresent ( i ) ) {
continue ;
}
stringstream ss ( stringstream : : in | stringstream : : out ) ;
2021-03-30 20:48:05 +02:00
ss < < " Not all parameters for argument \" " < < arg - > name ( ) < < " \" " ;
2019-05-04 22:35:00 +02:00
if ( i ) {
ss < < " ( " < < ( i + 1 ) < < " occurrence) " ;
}
2021-03-30 20:48:05 +02:00
ss < < " provided. You have to provide the following parameters: " ;
2019-05-04 22:35:00 +02:00
size_t valueNamesPrint = 0 ;
for ( const auto & name : arg - > m_valueNames ) {
ss < < ' ' < < name ;
+ + valueNamesPrint ;
}
if ( arg - > m_requiredValueCount ! = Argument : : varValueCount ) {
while ( valueNamesPrint < arg - > m_requiredValueCount ) {
ss < < " \n value " < < ( + + valueNamesPrint ) ;
2015-04-22 18:36:40 +02:00
}
}
2019-06-10 16:03:27 +02:00
throw ParseError ( ss . str ( ) ) ;
2015-04-22 18:36:40 +02:00
}
2016-06-12 01:56:57 +02:00
2021-07-03 19:07:49 +02:00
// check constraints of sub arguments recursively
2016-06-12 01:56:57 +02:00
checkConstraints ( arg - > m_subArgs ) ;
}
}
/*!
* \ brief Invokes the callbacks for the specified \ a args .
* \ remarks
* - Checks the callbacks for sub arguments , too .
2016-07-31 23:20:31 +02:00
* - Invokes the assigned callback methods for each occurrence of
2016-06-12 01:56:57 +02:00
* the argument .
*/
void ArgumentParser : : invokeCallbacks ( const ArgumentVector & args )
{
2017-05-01 03:13:11 +02:00
for ( const Argument * arg : args ) {
2016-07-31 23:20:31 +02:00
// invoke the callback for each occurrence of the argument
2017-05-01 03:13:11 +02:00
if ( arg - > m_callbackFunction ) {
for ( const auto & occurrence : arg - > m_occurrences ) {
2016-07-31 23:20:31 +02:00
arg - > m_callbackFunction ( occurrence ) ;
2015-04-22 18:36:40 +02:00
}
}
2016-06-12 01:56:57 +02:00
// invoke the callbacks for sub arguments recursively
invokeCallbacks ( arg - > m_subArgs ) ;
}
2015-04-22 18:36:40 +02:00
}
2019-03-24 21:52:10 +01:00
/*!
* \ brief Exits using the assigned function or std : : exit ( ) .
*/
void ArgumentParser : : invokeExit ( int code )
{
if ( m_exitFunction ) {
m_exitFunction ( code ) ;
return ;
}
std : : exit ( code ) ;
}
2015-04-22 18:36:40 +02:00
/*!
2016-06-10 22:59:22 +02:00
* \ class HelpArgument
2015-04-22 18:36:40 +02:00
* \ brief The HelpArgument class prints help information for an argument parser
* when present ( - - help , - h ) .
*/
/*!
* \ brief Constructs a new help argument for the specified parser .
*/
2017-05-01 03:13:11 +02:00
HelpArgument : : HelpArgument ( ArgumentParser & parser )
: Argument ( " help " , ' h ' , " shows this information " )
2015-04-22 18:36:40 +02:00
{
2017-05-01 03:13:11 +02:00
setCallback ( [ & parser ] ( const ArgumentOccurrence & ) {
2015-09-01 20:05:38 +02:00
CMD_UTILS_START_CONSOLE ;
parser . printHelp ( cout ) ;
} ) ;
2015-04-22 18:36:40 +02:00
}
2016-10-30 00:30:54 +02:00
/*!
* \ class OperationArgument
* \ brief The OperationArgument class is an Argument where denotesOperation ( ) is true by default .
*/
/*!
* \ class ConfigValueArgument
* \ brief The ConfigValueArgument class is an Argument where setCombinable ( ) is true by default .
* \ sa ConfigValueArgument : : ConfigValueArgument ( )
*/
2017-10-17 00:00:46 +02:00
/*!
* \ class NoColorArgument
* \ brief The NoColorArgument class allows to specify whether use of escape codes or similar technique to provide formatted output
* on the terminal should be enabled / disabled .
*
2021-07-03 19:07:49 +02:00
* This argument will either prevent or explicitly allow the use of escape codes or similar technique to provide formatted output
2017-10-17 00:00:46 +02:00
* on the terminal . More explicitly , the argument will always allow to negate the default value of EscapeCodes : : enabled which can be
* configured at build time by setting the CMake variable ENABLE_ESCAPE_CODES_BY_DEFAULT .
*
* \ remarks
* - Only the first instance is considered for actually altering the value of EscapeCodes : : enabled so it makes no sense to
* instantiate this class multiple times .
* - It is ensure that EscapeCodes : : enabled will be set before any callback functions are invoked and even in the error case ( if
* the error doesn ' t prevent the argument from being detected ) . Hence this feature is implemented via NoColorArgument : : apply ( )
* rather than the usual callback mechanism .
*
* \ sa NoColorArgument : : NoColorArgument ( ) , EscapeCodes : : enabled
*/
/*!
* \ brief Constructs a new NoColorArgument argument .
* \ remarks This will also set EscapeCodes : : enabled to the value of the environment variable ENABLE_ESCAPE_CODES .
*/
NoColorArgument : : NoColorArgument ( )
# ifdef CPP_UTILITIES_ESCAPE_CODES_ENABLED_BY_DEFAULT
: Argument ( " no-color " , ' \0 ' , " disables formatted/colorized output " )
# else
: Argument ( " enable-color " , ' \0 ' , " enables formatted/colorized output " )
# endif
{
setCombinable ( true ) ;
2021-05-28 19:15:09 +02:00
// set the environment variable (not directly used and just assigned for printing help)
2017-10-17 00:00:46 +02:00
setEnvironmentVariable ( " ENABLE_ESCAPE_CODES " ) ;
2021-05-28 19:15:09 +02:00
// initialize EscapeCodes::enabled from environment variable
const auto escapeCodesEnabled = isEnvVariableSet ( environmentVariable ( ) ) ;
if ( escapeCodesEnabled . has_value ( ) ) {
EscapeCodes : : enabled = escapeCodesEnabled . value ( ) ;
2017-10-17 00:00:46 +02:00
}
}
/*!
2021-07-03 19:07:49 +02:00
* \ brief Sets EscapeCodes : : enabled according to the presence of the first instantiation of NoColorArgument .
2017-10-17 00:00:46 +02:00
*/
2017-10-19 00:51:16 +02:00
void NoColorArgument : : apply ( ) const
2017-10-17 00:00:46 +02:00
{
2017-10-19 00:51:16 +02:00
if ( isPresent ( ) ) {
2017-10-17 00:00:46 +02:00
# ifdef CPP_UTILITIES_ESCAPE_CODES_ENABLED_BY_DEFAULT
EscapeCodes : : enabled = false ;
# else
EscapeCodes : : enabled = true ;
# endif
}
}
2018-09-22 17:04:14 +02:00
/*!
* \ brief Throws a Failure for the current instance and the specified \ a argumentPath .
*/
void ValueConversion : : Helper : : ArgumentValueConversionError : : throwFailure ( const std : : vector < Argument * > & argumentPath ) const
{
2019-06-10 16:03:27 +02:00
throw ParseError ( argumentPath . empty ( )
2018-09-22 17:04:14 +02:00
? argsToString ( " Conversion of top-level value \" " , valueToConvert , " \" to type \" " , targetTypeName , " \" failed: " , errorMessage )
: argsToString ( " Conversion of value \" " , valueToConvert , " \" (for argument -- " , argumentPath . back ( ) - > name ( ) , " ) to type \" " ,
2019-05-04 20:58:23 +02:00
targetTypeName , " \" failed: " , errorMessage ) ) ;
2018-09-22 17:04:14 +02:00
}
/*!
* \ brief Throws a Failure for insufficient number of values .
*/
void ArgumentOccurrence : : throwNumberOfValuesNotSufficient ( unsigned long valuesToConvert ) const
{
2019-06-10 16:03:27 +02:00
throw ParseError ( path . empty ( )
2018-09-22 17:04:14 +02:00
? argsToString ( " Expected " , valuesToConvert , " top-level values to be present but only " , values . size ( ) , " have been specified. " )
: argsToString ( " Expected " , valuesToConvert , " values for argument -- " , path . back ( ) - > name ( ) , " to be present but only " , values . size ( ) ,
2019-05-04 20:58:23 +02:00
" have been specified. " ) ) ;
2018-09-22 17:04:14 +02:00
}
2019-06-10 21:56:46 +02:00
} // namespace CppUtilities