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