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