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