ALib C++ Library
Library Version: 2511 R0
Documentation generated by doxygen
Loading...
Searching...
No Matches
cliutil.cpp
1//##################################################################################################
2// ALib C++ Library
3//
4// Copyright 2013-2025 A-Worx GmbH, Germany
5// Published under 'Boost Software License' (a free software license, see LICENSE.txt)
6//##################################################################################################
7#include "alib_precompile.hpp"
8#if !defined(ALIB_C20_MODULES) || ((ALIB_C20_MODULES != 0) && (ALIB_C20_MODULES != 1))
9# error "Symbol ALIB_C20_MODULES has to be given to the compiler as either 0 or 1"
10#endif
11#if ALIB_C20_MODULES
12 module;
13#endif
14//========================================= Global Fragment ========================================
15#include <algorithm>
17//============================================== Module ============================================
18#if ALIB_C20_MODULES
19 module ALib.CLI;
20 import ALib.Characters.Functions;
21 import ALib.Strings;
22 import ALib.Strings.Tokenizer;
23 import ALib.Format;
24#else
26# include "ALib.Strings.H"
28# include "ALib.CLI.H"
29#endif
30//========================================== Implementation ========================================
31
32namespace alib::cli {
33
34OptionDecl* CLIUtil::GetOptionDecl( CommandLine& cmdLine, const String& identString ) {
35 for( auto* decl : cmdLine.OptionDecls )
36 if ( ( identString.Length() == 1
37 && identString.CharAtStart<NC>() == decl->IdentifierChar() )
38 || ( identString.Length() >= decl->MinimumRecognitionLength()
39 && decl->Identifier().StartsWith<CHK,lang::Case::Ignore>( identString ) ) )
40 return decl;
41 return nullptr;
42}
43
44
45CommandDecl* CLIUtil::GetCommandDecl( CommandLine& cmdLine, const String& identString ) {
46 for( auto* decl : cmdLine.CommandDecls )
47 if ( identString.Length() >= decl->MinimumRecognitionLength()
48 && decl->Identifier().StartsWith<CHK,lang::Case::Ignore>( identString ) )
49 return decl;
50 return nullptr;
51}
52
54 for( auto* decl : cmdLine.ParameterDecls )
55 if ( decl->Name().StartsWith<CHK,lang::Case::Ignore>( identString ) )
56 return decl;
57 return nullptr;
58}
59
60
61
63 AString result;
64 result.EnsureRemainingCapacity(2048);
65 result << cmdLine.GetResource( "HlpCLIAppName" ) << ' ' << cmd.Identifier();
66
67 for( auto* param : cmd.Parameters ) {
68 result << ' ';
69 if( param->IsOptional() ) result << '[';
70
71 result << param->Name();
72 if ( param->ValueListSeparator() != '\0' ) {
73 result << '[' << param->ValueListSeparator() << param->Name() << "...]";
74 }
75
76 if( param->IsOptional() ) result << ']';
77
78 }
79 return result;
80}
81
83bool CLIUtil::GetHelp( CommandLine& cmdLine, Command* helpCmd, Option* helpOpt, Paragraphs& text )
85 text.AddMarked( cmdLine.AppInfo );
86
87 String argList= NULL_STRING;
88 bool argWasPeeked= false;
89 if( helpCmd ) {
90 if( helpCmd->ParametersOptional.IsNotEmpty() )
91 argList= helpCmd->ParametersOptional.front()->Args.front();
92 else if( cmdLine.ArgCount() > helpCmd->Position + 1 ) {
93 argWasPeeked= true;
94 argList= cmdLine.GetArg(helpCmd->Position + 1);
95 }
96 } else {
97 if( helpOpt->Args.IsNotEmpty() )
98 argList= helpOpt->Args.front();
99 else if( cmdLine.ArgCount() > helpOpt->Position + 1 ) {
100 argWasPeeked= true;
101 argList= cmdLine.GetArg(helpOpt->Position + 1);
102 } }
103
104 if( argList.IsNotEmpty() ) {
105 int cntArgsRecognized= 0;
106
107 Tokenizer args( argList, ',' );
108 int cntTokens= 0;
109 while( args.HasNext() ) {
110 ++cntTokens;
111
112 String arg= args.Next();
113 int cntArgsRecognizedSoFar= cntArgsRecognized;
114
115 // command
116 {
117 CommandDecl* cmdDecl= CLIUtil::GetCommandDecl( cmdLine, arg );
118 if( cmdDecl ) {
119 ++cntArgsRecognized;
120
121 if( cmdLine.DryRun == DryRunModes::Off ) {
122 text.Add( cmdLine.GetResource( "HlpHdlTopic" ), "command", cmdDecl->Identifier() )
123 .PushIndent( 2 )
124 .Add( cmdLine.GetResource( "HlpHdlUsage" ), " ", GetCommandUsageFormat( cmdLine, *cmdDecl ) )
125
126 .Add( NEW_LINE, cmdLine.GetResource( "HlpHdlDscr" ) )
127 .PushIndent( 2 )
128 .AddMarked( cmdDecl->HelpTextLong(), NEW_LINE )
129 .PopIndent()
130
131 .Add( NEW_LINE, cmdLine.GetResource( "HlpHdlPDscr" ) )
132 .PushIndent( 2 );
133 for( auto* param : cmdDecl->Parameters ) {
134 text.Add( "* ", param->Name() )
135 .PushIndent( 2 )
136 .AddMarked( param->GetHelpTextShort() )
137 .PopIndent()
138 .Add( NEW_LINE );
139 }
140 text.PopIndent()
141 .PopIndent();
142 } } }
143
144 // option?
145 if( cntArgsRecognizedSoFar == cntArgsRecognized ) {
146 OptionDecl* optDecl= CLIUtil::GetOptionDecl( cmdLine, arg );
147 if( optDecl ) {
148 ++cntArgsRecognized;
149 if( cmdLine.DryRun == DryRunModes::Off ) {
150 text.Add( cmdLine.GetResource( "HlpHdlTopic" ), "option", optDecl->Identifier() )
151 .PushIndent( 2 )
152 .Add( cmdLine.GetResource( "HlpHdlUsage" ), " ", optDecl->HelpUsageLine() )
153 .Add( NEW_LINE, cmdLine.GetResource( "HlpHdlDscr" ) )
154 .PushIndent( 2 )
155 .AddMarked( optDecl->HelpText(), NEW_LINE )
156 .PopIndent()
157
158 .PopIndent();
159 } } }
160
161 // parameter?
162 if( cntArgsRecognizedSoFar == cntArgsRecognized ) {
163 ParameterDecl* paramDecl= CLIUtil::GetParameterDecl( cmdLine, arg );
164 if( paramDecl ) {
165 ++cntArgsRecognized;
166 if( cmdLine.DryRun == DryRunModes::Off ) {
167 text.Add( cmdLine.GetResource( "HlpHdlTopic" ), "parameter", paramDecl->Name() )
168 .PushIndent( 2 )
169 .AddMarked( paramDecl->GetHelpTextLong() )
170 .PopIndent();
171 } } }
172
173 // special help topic?
174 if( cntArgsRecognizedSoFar == cntArgsRecognized ) {
175 auto& additionalHelpTopics= cmdLine.TryResource("HlpAddnlTopics");
176 if( additionalHelpTopics.IsNotEmpty() ) {
177 Tokenizer topics(additionalHelpTopics, ',');
178 while(topics.Next().IsNotEmpty()) {
179 if(topics.Actual.StartsWith<CHK,lang::Case::Ignore>( arg ) ) {
180 ++cntArgsRecognized;
181 if( cmdLine.DryRun == DryRunModes::Off )
182 text.AddMarked( cmdLine.GetResource( NString64("HlpAddnl")._( topics.Actual ) ) );
183
184 break;
185 } } } } }
186
187
188 // Add arg to command object if at least one token matched
189 if( cntArgsRecognized > 0 && argWasPeeked ) {
190 if( helpCmd ) {
191 cmdLine.RemoveArg( helpCmd->Position + 1 );
192 ++helpCmd->ConsumedArguments;
193 } else {
194 cmdLine.RemoveArg( helpOpt->Position + 1 );
195 ++helpOpt->ConsumedArguments;
196 helpOpt->Args.push_back( argList );
197 } }
198
199 // not peeked means "--help=arg" was given. In this case, the argument has to be recognized.
200 // Also in the case that more than one token was read but the recognized qty is smaller.
201 if ( ( !argWasPeeked && cntArgsRecognized == 0 )
202 || ( cntTokens > 1 && cntArgsRecognized < cntTokens ) )
203 return false;
204
205 // consumed arg?
206 if( cntArgsRecognized > 0 )
207 return true;
208 }
209
210 // general help
211 text.AddMarked( cmdLine.GetResource( "HlpGeneral" ) );
212
213 text.Add( cmdLine.GetResource( "HlpHdlExtCds") )
214 .PushIndent( 2 );
215
216 {
217 // sort the exit codes by their number
218 auto snapshot= cmdLine.allocator.TakeSnapshot();
220 sortedExitCodes(cmdLine.allocator);
221 for( auto& declIt : cmdLine.ExitCodeDecls )
222 sortedExitCodes.emplace_back(declIt);
223 std::sort( sortedExitCodes.begin(), sortedExitCodes.end(),
224 []( std::pair<Enum, ExitCodeDecl *>& lhs,
225 std::pair<Enum, ExitCodeDecl *>& rhs)
226 {
227 return lhs.first.Integral() < rhs.first.Integral();
228 }
229 );
230
231 for( auto& declIt : sortedExitCodes )
232 text.Add( " {:>3}: {}\n {}", declIt.first.Integral(),
233 declIt.second->Name(),
234 declIt.second->FormatString() );
235 cmdLine.allocator.Reset(snapshot);
236 }
237 text.PopIndent();
238
239 text.Add( cmdLine.GetResource( "HlpHdlUsage" ) )
240 .PushIndent( 2 )
241 .Add( cmdLine.GetResource( "HlpUsage" ) )
242 .PopIndent()
243 .Add( NEW_LINE, cmdLine.GetResource( "HlpHdlOpts" ) )
244 .PushIndent( 2 );
245 for( auto* decl : cmdLine.OptionDecls )
246 text.Add( decl->HelpUsageLine() );
247
248 text.PopIndent();
249
250 text.Add( NEW_LINE, cmdLine.GetResource( "HlpHdlCmds" ) )
251 .PushIndent( 2 );
252 for( auto* decl : cmdLine.CommandDecls ) {
253 text.Add( "* ", GetCommandUsageFormat( cmdLine, *decl ), NEW_LINE )
254 .PushIndent( 2 )
255 .Add( decl->HelpTextShort(), NEW_LINE )
256 .PopIndent();
257 }
258 text.PopIndent();
259
260
261 return true;
262}
263
264bool CLIUtil::GetDryOpt( CommandLine& cmdLine, Option& dryOpt) {
266
267 // try to fetch argument
269 bool argWasPeeked= false;
270 if( dryOpt.Args.IsNotEmpty() )
271 arg= dryOpt.Args.front();
272 else if( cmdLine.ArgCount() > dryOpt.Position ) {
273 argWasPeeked= true;
274 arg= cmdLine.GetArg(dryOpt.Position + 1);
275 }
276
277 if( arg.IsNotEmpty() ) {
278 DryRunModes dryRunMode;
279 if( enumrecords::Parse( arg, dryRunMode ) && arg.IsEmpty() ) {
280 cmdLine.DryRun= dryRunMode;
281 if( argWasPeeked ) {
282 cmdLine.RemoveArg( dryOpt.Position + 1 );
283 ++dryOpt.ConsumedArguments;
284 dryOpt.Args.push_back( cmdLine.GetArg(dryOpt.Position + 1) );
285 } }
286
287 // not peeked means "--dryrun=arg" was given. In this case, the argument has to be recognized.
288 else if ( !argWasPeeked )
289 return false;
290 }
291
292 return true;
293}
294
295
297 dump.Add( "COMMANDS:")
298 .PushIndent( 2 );
299 for( auto* decl : cmdLine.CommandDecls ) {
300 dump.Add( "- ({}) {}", decl->Element(), decl->Identifier())
301 .PushIndent( 2 );
302 String256 paramIDs;
303 for( auto& param : decl->Parameters )
304 paramIDs << param->Name() << ", ";
305 if( paramIDs.IsEmpty() )
306 paramIDs << "none";
307 else
308 paramIDs.DeleteEnd( 2 );
309 dump.Add( "Associated parameters: ", paramIDs )
310 .Add( decl->HelpTextShort())
311 .PopIndent()
312 .Add( NEW_LINE );
313 }
314 dump.PopIndent()
315
316
317 .Add( NEW_LINE )
318 .Add( "OPTIONS:")
319 .PushIndent( 2 );
320 for( auto* decl : cmdLine.OptionDecls ) {
321 dump.Add( decl->HelpUsageLine() )
322 .Add( decl->HelpText() )
323 .Add( NEW_LINE );
324 }
325 dump.PopIndent();
326
327
328
329 dump.Add( NEW_LINE )
330 .Add( "PARAMETERS:")
331 .PushIndent( 2 );
332 for( auto* decl : cmdLine.ParameterDecls ) {
333 dump.Add( "- ({}) {} Optional: {} Multi-Separator: {}" ,
334 decl->Element(),
335 decl->Name(),
336 decl->IsOptional(),
337 (decl->ValueListSeparator() ? Box(decl->ValueListSeparator()) : Box("-/-") ))
338 .Add( decl->GetHelpTextShort())
339 .Add( NEW_LINE );
340 }
341 dump.PopIndent()
342
343 .Add( NEW_LINE )
344 .Add( "EXIT CODES:")
345 .PushIndent( 2 );
346 for( auto& declIt : cmdLine.ExitCodeDecls )
347 dump.Add( "{:>5} : {}", declIt.first, declIt.second->FormatString() );
348
349 dump.PopIndent();
350
351 return dump.Buffer;
352}
353
354
355//! @cond NO_DOX
356namespace
357{
358void dumpParsedOptions( CommandLine& app, ListMA<Option*>& optionsOriginal,
359 Paragraphs& dump ) {
360 std::vector<Option*> options;
361 std::vector<Option*> optionsOfActType;
362 auto overallSize= optionsOriginal.size();
363 options .reserve( size_t(overallSize) );
364 optionsOfActType.reserve( size_t(overallSize) );
365 for( auto* optionOrig : optionsOriginal )
366 options.push_back( optionOrig );
367
368 dump.PushIndent( 2 );
369 while( options.size() ) {
370 // collect all options of the same type in optionsOfActType
371 auto* decl= options.front()->Declaration;
372 optionsOfActType.clear();
373 size_t actIdx= 0;
374 while( actIdx < options.size() ) {
375 if( options[actIdx]->Declaration == decl ) {
376 optionsOfActType.push_back( options[actIdx] );
377 options.erase( options.begin() + integer(actIdx) );
378 }
379 else
380 ++actIdx;
381 }
382
383
384 dump.Add( "- \"-{},--{}\" ({}x)",
385 decl->IdentifierChar(), decl->Identifier(), optionsOfActType.size() )
386 .PushIndent( 2 );
387
388 for( actIdx= 0; actIdx < optionsOfActType.size() ; ++actIdx ) {
389 Option* actOption= optionsOfActType[actIdx];
390 dump.Add( "{}/{}: ArgStrings{!Q[]}= {!Q}, #arguments parsed: {}",
391 actIdx + 1, optionsOfActType.size(),
392 actOption->Position,
393 app.GetArg(actOption->Position),
394 actOption->Args.size() )
395 .PushIndent(5);
396
397 uinteger argNo= 0;
398 for( auto& arg : actOption->Args )
399 dump.Add( "Argument {}: {!Q}", ++argNo, arg );
400 dump.PopIndent();
401
402 }
403 dump.PopIndent()
404 .Add( NEW_LINE );
405 }
406 dump.PopIndent();
407}
408} // anon namespace
409//! @endcond
410
412 dump.Add( NEW_LINE )
413 .Add( "OPTIONS:");
414 dumpParsedOptions( cmdLine, cmdLine.Options, dump );
415
416 dump.Add( NEW_LINE )
417 .Add( "OPTION ARGUMENTS IGNORED (Usable with other libs):")
418 .PushIndent( 2 );
419 int cnt= 0;
420 for( auto& it : cmdLine.OptionArgsIgnored )
421 dump.Add( "{}: {!Q}", cnt++ + 1, it );
422 if (cnt == 0 )
423 dump.Add( "None" );
424 dump.PopIndent();
425
426 dump.Add( NEW_LINE )
427 .Add( "COMMANDS PARSED:")
428 .PushIndent( 2 );
429 cnt= 0;
430 for( auto& cmd : cmdLine.CommandsParsed ) {
431 ++cnt;
432 dump.Add( "- {:8}with argument #{}", cmd->Declaration->Identifier(), cmd->Position )
433 .PushIndent( 2 );
434 for( auto* param : cmd->ParametersMandatory ) {
435 dump.Add( "Parameter: {}", param->Declaration->Name() )
436 .PushIndent( 2 );
437 uinteger argNo= 0;
438 for( auto& arg : param->Args )
439 dump.Add( "Parameter argument {}: {!Q}", ++argNo, arg );
440 dump.PopIndent();
441
442 }
443 for( auto* param : cmd->ParametersOptional ) {
444 dump.Add( "Parameter: {}", param->Declaration->Name() )
445 .PushIndent( 2 );
446 uinteger argNo= 0;
447 for( auto& arg : param->Args )
448 dump.Add( "Parameter argument {}: {!Q}", ++argNo, arg );
449 dump.PopIndent();
450
451 }
452 dump.PopIndent()
453 .Add( NEW_LINE );
454 }
455 if (cnt == 0 )
456 dump.Add( "None" );
457 dump.PopIndent();
458
459
460
461 dump.Add( NEW_LINE )
462 .Add( "UNRECOGNIZED CLI ARGUMENTS:")
463 .PushIndent( 2 );
464 for( auto& it : cmdLine.ArgsLeft )
465 dump.Add( "{}: {!Q}", it, cmdLine.GetArg(it));
466
467 if (cmdLine.ArgsLeft.size() == 0 )
468 dump.Add( "None" );
469 dump.PopIndent();
470
471 return dump.Buffer;
472}
473
474} // namespace alib::cli
475#include "ALib.Lang.CIMethods.H"
static ALIB_DLL ParameterDecl * GetParameterDecl(CommandLine &cmdLine, const String &identString)
Definition cliutil.cpp:53
static ALIB_DLL bool GetDryOpt(CommandLine &cmdLine, Option &dryOpt)
Definition cliutil.cpp:264
static ALIB_DLL OptionDecl * GetOptionDecl(CommandLine &cmdLine, const String &identString)
Definition cliutil.cpp:34
static ALIB_DLL AString & DumpParseResults(CommandLine &cmdLine, Paragraphs &text)
Definition cliutil.cpp:411
static ALIB_DLL AString & DumpDeclarations(CommandLine &cmdLine, Paragraphs &text)
Definition cliutil.cpp:296
static ALIB_DLL CommandDecl * GetCommandDecl(CommandLine &cmdLine, const String &identString)
Definition cliutil.cpp:45
static ALIB_DLL bool GetHelp(CommandLine &cmdLine, Command *helpCmd, Option *helpOpt, Paragraphs &text)
Definition cliutil.cpp:83
static ALIB_DLL AString GetCommandUsageFormat(CommandLine &cmdLine, CommandDecl &commandDecl)
Definition cliutil.cpp:62
ListMA< ParameterDecl * > Parameters
Command parameters.
const String & Identifier()
const String & HelpTextLong()
ALIB_DLL void RemoveArg(integer argNo)
const String & TryResource(const NString &name)
StdVectorMA< integer > ArgsLeft
ListMA< String, Recycling::Shared > OptionArgsIgnored
MonoAllocator allocator
ListMA< CommandDecl * > CommandDecls
Commands defined.
ListMA< Command * > CommandsParsed
A list of commands actually parsed. Filled with method ReadNextCommands.
const String & GetResource(const NString &name)
ListMA< Option * > Options
The options parsed in the order of their appearance.
ListMA< ParameterDecl * > ParameterDecls
Possible Parameters.
ListMA< OptionDecl * > OptionDecls
Possible Options.
HashMap< MonoAllocator, Enum, ExitCodeDecl * > ExitCodeDecls
Possible Errors.
virtual String GetArg(integer idx)
virtual integer ArgCount()
const String & HelpUsageLine()
const String & HelpText()
const String & Identifier()
const String & GetHelpTextLong()
const String & Name()
integer size() const
Definition list.inl:392
static ALIB_DLL threads::RecursiveLock DefaultLock
ALIB_DLL Paragraphs & PushIndent(uinteger qty, character fillChar=' ')
void Add(boxing::TBoxes< TAllocatorArgs > &args)
void AddMarked(boxing::TBoxes< TAllocatorArgs > &args)
ALIB_DLL Paragraphs & PopIndent()
ALIB_DLL void Reset(Snapshot snapshot=Snapshot())
TAString & DeleteEnd(integer regionLength)
void EnsureRemainingCapacity(integer spaceNeeded)
Definition tastring.inl:601
constexpr integer Length() const
Definition string.inl:316
constexpr bool IsEmpty() const
Definition string.inl:365
TChar CharAtStart() const
Definition string.inl:433
constexpr bool IsNotEmpty() const
Definition string.inl:369
bool StartsWith(const TString &needle) const
Definition string.inl:751
size_type size() const
Definition string.inl:1999
ALIB_DLL TSubstring< TChar > & Next(lang::Whitespaces trimming=lang::Whitespaces::Trim, TChar newDelim='\0')
Definition tokenizer.cpp:26
TSubstring< TChar > Actual
Definition tokenizer.inl:67
#define ALIB_LOCK_RECURSIVE_WITH(lock)
Definition alib.inl:1340
DryRunModes
Dry run modes.
Definition clicamp.inl:71
bool Parse(strings::TSubstring< TChar > &input, TEnum &result)
variables::Declaration Declaration
Type alias in namespace alib.
constexpr String NULL_STRING
A nulled string of the default character type.
Definition string.inl:2271
NLocalString< 64 > NString64
Type alias name for TLocalString<nchar,64>.
LocalString< 256 > String256
Type alias name for TLocalString<character,256>.
strings::TAString< character, lang::HeapAllocator > AString
Type alias in namespace alib.
strings::util::TTokenizer< character > Tokenizer
Type alias in namespace alib.
std::vector< T, StdMA< T > > StdVectorMA
Type alias in namespace alib.
containers::List< T, MonoAllocator, TRecycling > ListMA
Type alias in namespace alib.
Definition list.inl:693
constexpr CString NEW_LINE
A zero-terminated string containing the new-line character sequence.
Definition cstring.inl:616
lang::integer integer
Type alias in namespace alib.
Definition integers.inl:149
format::Paragraphs Paragraphs
Type alias in namespace alib.
boxing::Box Box
Type alias in namespace alib.
Definition box.inl:1149
strings::TString< character > String
Type alias in namespace alib.
Definition string.inl:2189
lang::uinteger uinteger
Type alias in namespace alib.
Definition integers.inl:152
strings::TSubstring< character > Substring
Type alias in namespace alib.
See sibling type NC.
Definition chk_nc.inl:33
A command of a ALib CLI command line.
ListMA< Parameter *, Recycling::Shared > ParametersOptional
Optional parameters parsed.
ListMA< String, Recycling::Shared > Args
Arguments belonging to this option.
integer ConsumedArguments
Definition arguments.inl:35