ALib C++ Framework
by
Library Version: 2511 R0
Documentation generated by doxygen
Loading...
Searching...
No Matches
cliutil.cpp
1//##################################################################################################
2// ALib C++ Framework
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 "Configuration MACRO 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, Paragraphs& text ) {
84 String topics= NULL_STRING;
85 if( helpCmd->ParametersOptional.IsNotEmpty() )
86 topics= helpCmd->ParametersOptional.front()->Args.front();
87 else if( cmdLine.ArgCount() > helpCmd->Position + 1 ) {
88 topics= cmdLine.GetArg(helpCmd->Position + 1);
89 cmdLine.RemoveArg( helpCmd->Position + 1 );
90 ++helpCmd->ConsumedArguments;
91 }
92 return GetHelp(cmdLine, topics, text);
93}
94
95bool CLIUtil::GetHelp( CommandLine& cmdLine, Option* helpOpt, Paragraphs& text ) {
96 String topics= NULL_STRING;
97 if( helpOpt->Args.IsNotEmpty() )
98 topics= helpOpt->Args.front();
99 else if( cmdLine.ArgCount() > helpOpt->Position + 1 ) {
100 topics= cmdLine.GetArg(helpOpt->Position + 1);
101 cmdLine.RemoveArg( helpOpt->Position + 1 );
102 ++helpOpt->ConsumedArguments;
103 helpOpt->Args.push_back( topics );
104 }
105
106 return GetHelp(cmdLine, topics, text);
107}
108
109
110bool CLIUtil::GetHelp( CommandLine& cmdLine, const String& topics, Paragraphs& text )
112 integer oldTextLength= text.Buffer.Length();
113 text.AddMarked( cmdLine.AppInfo );
114
115 if( topics.IsNotEmpty() ) {
116 int cntArgsRecognized= 0;
117
118 Tokenizer args( topics, ',' );
119 int cntTokens= 0;
120 while( args.HasNext() ) {
121 ++cntTokens;
122
123 String arg= args.Next();
124 int cntArgsRecognizedSoFar= cntArgsRecognized;
125
126 // command
127 {
128 CommandDecl* cmdDecl= CLIUtil::GetCommandDecl( cmdLine, arg );
129 if( cmdDecl ) {
130 ++cntArgsRecognized;
131
132 if( cmdLine.DryRun == DryRunModes::Off ) {
133 text.Add( cmdLine.GetResource( "HlpHdlTopic" ), "command", cmdDecl->Identifier() )
134 .PushIndent( 2 )
135 .Add( cmdLine.GetResource( "HlpHdlUsage" ), " ", GetCommandUsageFormat( cmdLine, *cmdDecl ) )
136
137 .Add( NEW_LINE, cmdLine.GetResource( "HlpHdlDscr" ) )
138 .PushIndent( 2 )
139 .AddMarked( cmdDecl->HelpTextLong(), NEW_LINE )
140 .PopIndent()
141
142 .Add( NEW_LINE, cmdLine.GetResource( "HlpHdlPDscr" ) )
143 .PushIndent( 2 );
144 for( auto* param : cmdDecl->Parameters ) {
145 text.Add( "* ", param->Name() )
146 .PushIndent( 2 )
147 .AddMarked( param->GetHelpTextShort() )
148 .PopIndent()
149 .Add( NEW_LINE );
150 }
151 text.PopIndent()
152 .PopIndent();
153 } } }
154
155 // option?
156 if( cntArgsRecognizedSoFar == cntArgsRecognized ) {
157 OptionDecl* optDecl= CLIUtil::GetOptionDecl( cmdLine, arg );
158 if( optDecl ) {
159 ++cntArgsRecognized;
160 if( cmdLine.DryRun == DryRunModes::Off ) {
161 text.Add( cmdLine.GetResource( "HlpHdlTopic" ), "option", optDecl->Identifier() )
162 .PushIndent( 2 )
163 .Add( cmdLine.GetResource( "HlpHdlUsage" ), " ", optDecl->HelpUsageLine() )
164 .Add( NEW_LINE, cmdLine.GetResource( "HlpHdlDscr" ) )
165 .PushIndent( 2 )
166 .AddMarked( optDecl->HelpText(), NEW_LINE )
167 .PopIndent()
168
169 .PopIndent();
170 } } }
171
172 // parameter?
173 if( cntArgsRecognizedSoFar == cntArgsRecognized ) {
174 ParameterDecl* paramDecl= CLIUtil::GetParameterDecl( cmdLine, arg );
175 if( paramDecl ) {
176 ++cntArgsRecognized;
177 if( cmdLine.DryRun == DryRunModes::Off ) {
178 text.Add( cmdLine.GetResource( "HlpHdlTopic" ), "parameter", paramDecl->Name() )
179 .PushIndent( 2 )
180 .AddMarked( paramDecl->GetHelpTextLong() )
181 .PopIndent();
182 } } }
183
184 // special help topic?
185 if( cntArgsRecognizedSoFar == cntArgsRecognized ) {
186 auto& additionalHelpTopics= cmdLine.TryResource("HlpAddnlTopics");
187 if( additionalHelpTopics.IsNotEmpty() ) {
188 Tokenizer topicsTknzr(additionalHelpTopics, ',');
189 while(topicsTknzr.Next().IsNotEmpty()) {
190 if(topicsTknzr.Actual.StartsWith<CHK,lang::Case::Ignore>( arg ) ) {
191 ++cntArgsRecognized;
192 if( cmdLine.DryRun == DryRunModes::Off )
193 text.AddMarked( cmdLine.GetResource( NString64("HlpAddnl")._( topicsTknzr.Actual ) ) );
194
195 break;
196 } } } } }
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 ( ( cntArgsRecognized == 0 )
202 || ( cntTokens > 1 && cntArgsRecognized < cntTokens ) ) {
203 text.Buffer.ShortenTo(oldTextLength);
204 return false;
205 }
206
207 // consumed arg?
208 if( cntArgsRecognized > 0 )
209 return true;
210 }
211
212 // general help
213 text.AddMarked( cmdLine.GetResource( "HlpGeneral" ) );
214
215 text.Add( cmdLine.GetResource( "HlpHdlExtCds") )
216 .PushIndent( 2 );
217 {
218 // sort the exit-codes by their number
219 auto snapshot= cmdLine.allocator.TakeSnapshot();
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 + 1 ) {
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 cmdLine.DryRun= DryRunModes::Off;
290 return false;
291 } }
292
293 return true;
294}
295
296
298 dump.Add( "COMMANDS:")
299 .PushIndent( 2 );
300 for( auto* decl : cmdLine.CommandDecls ) {
301 dump.Add( "- ({}) {}", decl->Element(), decl->Identifier())
302 .PushIndent( 2 );
303 String256 paramIDs;
304 for( auto& param : decl->Parameters )
305 paramIDs << param->Name() << ", ";
306 if( paramIDs.IsEmpty() )
307 paramIDs << "none";
308 else
309 paramIDs.DeleteEnd( 2 );
310 dump.Add( "Associated parameters: ", paramIDs )
311 .Add( decl->HelpTextShort())
312 .PopIndent()
313 .Add( NEW_LINE );
314 }
315 dump.PopIndent()
316
317
318 .Add( NEW_LINE )
319 .Add( "OPTIONS:")
320 .PushIndent( 2 );
321 for( auto* decl : cmdLine.OptionDecls ) {
322 dump.Add( decl->HelpUsageLine() )
323 .Add( decl->HelpText() )
324 .Add( NEW_LINE );
325 }
326 dump.PopIndent();
327
328
329
330 dump.Add( NEW_LINE )
331 .Add( "PARAMETERS:")
332 .PushIndent( 2 );
333 for( auto* decl : cmdLine.ParameterDecls ) {
334 dump.Add( "- ({}) {} Optional: {} Multi-Separator: {}" ,
335 decl->Element(),
336 decl->Name(),
337 decl->IsOptional(),
338 (decl->ValueListSeparator() ? Box(decl->ValueListSeparator()) : Box("-/-") ))
339 .Add( decl->GetHelpTextShort())
340 .Add( NEW_LINE );
341 }
342 dump.PopIndent()
343
344 .Add( NEW_LINE )
345 .Add( "EXIT-CODES:")
346 .PushIndent( 2 );
347 for( auto& declIt : cmdLine.ExitCodeDecls )
348 dump.Add( "{:>5} : {}", declIt.first, declIt.second->FormatString() );
349
350 dump.PopIndent();
351
352 return dump.Buffer;
353}
354
355
356//! @cond NO_DOX
357namespace
358{
359void dumpParsedOptions( CommandLine& app, ListMA<Option*>& optionsOriginal,
360 Paragraphs& dump ) {
361 std::vector<Option*> options;
362 std::vector<Option*> optionsOfActType;
363 auto overallSize= optionsOriginal.size();
364 options .reserve( size_t(overallSize) );
365 optionsOfActType.reserve( size_t(overallSize) );
366 for( auto* optionOrig : optionsOriginal )
367 options.push_back( optionOrig );
368
369 dump.PushIndent( 2 );
370 while( options.size() ) {
371 // collect all options of the same type in optionsOfActType
372 auto* decl= options.front()->Declaration;
373 optionsOfActType.clear();
374 size_t actIdx= 0;
375 while( actIdx < options.size() ) {
376 if( options[actIdx]->Declaration == decl ) {
377 optionsOfActType.push_back( options[actIdx] );
378 options.erase( options.begin() + integer(actIdx) );
379 }
380 else
381 ++actIdx;
382 }
383
384
385 dump.Add( "- \"-{},--{}\" ({}x)",
386 decl->IdentifierChar(), decl->Identifier(), optionsOfActType.size() )
387 .PushIndent( 2 );
388
389 for( actIdx= 0; actIdx < optionsOfActType.size() ; ++actIdx ) {
390 Option* actOption= optionsOfActType[actIdx];
391 dump.Add( "{}/{}: ArgStrings{!Q[]}= {!Q}, #arguments parsed: {}",
392 actIdx + 1, optionsOfActType.size(),
393 actOption->Position,
394 app.GetArg(actOption->Position),
395 actOption->Args.size() )
396 .PushIndent(5);
397
398 uinteger argNo= 0;
399 for( auto& arg : actOption->Args )
400 dump.Add( "Argument {}: {!Q}", ++argNo, arg );
401 dump.PopIndent();
402
403 }
404 dump.PopIndent()
405 .Add( NEW_LINE );
406 }
407 dump.PopIndent();
408}
409} // anon namespace
410//! @endcond
411
413 dump.Add( NEW_LINE )
414 .Add( "OPTIONS:");
415 dumpParsedOptions( cmdLine, cmdLine.Options, dump );
416
417 dump.Add( NEW_LINE )
418 .Add( "OPTION ARGUMENTS IGNORED (Usable with other libs):")
419 .PushIndent( 2 );
420 int cnt= 0;
421 for( auto& it : cmdLine.OptionArgsIgnored )
422 dump.Add( "{}: {!Q}", cnt++ + 1, it );
423 if (cnt == 0 )
424 dump.Add( "None" );
425 dump.PopIndent();
426
427 dump.Add( NEW_LINE )
428 .Add( "COMMANDS PARSED:")
429 .PushIndent( 2 );
430 cnt= 0;
431 for( auto& cmd : cmdLine.CommandsParsed ) {
432 ++cnt;
433 dump.Add( "- {:8}with argument #{}", cmd->Declaration->Identifier(), cmd->Position )
434 .PushIndent( 2 );
435 for( auto* param : cmd->ParametersMandatory ) {
436 dump.Add( "Parameter: {}", param->Declaration->Name() )
437 .PushIndent( 2 );
438 uinteger argNo= 0;
439 for( auto& arg : param->Args )
440 dump.Add( "Parameter argument {}: {!Q}", ++argNo, arg );
441 dump.PopIndent();
442
443 }
444 for( auto* param : cmd->ParametersOptional ) {
445 dump.Add( "Parameter: {}", param->Declaration->Name() )
446 .PushIndent( 2 );
447 uinteger argNo= 0;
448 for( auto& arg : param->Args )
449 dump.Add( "Parameter argument {}: {!Q}", ++argNo, arg );
450 dump.PopIndent();
451
452 }
453 dump.PopIndent()
454 .Add( NEW_LINE );
455 }
456 if (cnt == 0 )
457 dump.Add( "None" );
458 dump.PopIndent();
459
460
461
462 dump.Add( NEW_LINE )
463 .Add( "UNRECOGNIZED CLI ARGUMENTS:")
464 .PushIndent( 2 );
465 for( auto& it : cmdLine.ArgsLeft )
466 dump.Add( "{}: {!Q}", it, cmdLine.GetArg(it));
467
468 if (cmdLine.ArgsLeft.size() == 0 )
469 dump.Add( "None" );
470 dump.PopIndent();
471
472 return dump.Buffer;
473}
474
475} // namespace alib::cli
476#include "ALib.Lang.CIMethods.H"
#define ALIB_LOCK_RECURSIVE_WITH(lock)
Definition alib.inl:1414
static ParameterDecl * GetParameterDecl(CommandLine &cmdLine, const String &identString)
Definition cliutil.cpp:53
static bool GetDryOpt(CommandLine &cmdLine, Option &dryOpt)
Definition cliutil.cpp:264
static OptionDecl * GetOptionDecl(CommandLine &cmdLine, const String &identString)
Definition cliutil.cpp:34
static AString & DumpParseResults(CommandLine &cmdLine, Paragraphs &text)
Definition cliutil.cpp:412
static AString & DumpDeclarations(CommandLine &cmdLine, Paragraphs &text)
Definition cliutil.cpp:297
static bool GetHelp(CommandLine &cmdLine, const String &topics, Paragraphs &text)
Definition cliutil.cpp:110
static CommandDecl * GetCommandDecl(CommandLine &cmdLine, const String &identString)
Definition cliutil.cpp:45
static AString GetCommandUsageFormat(CommandLine &cmdLine, CommandDecl &commandDecl)
Definition cliutil.cpp:62
ListMA< ParameterDecl * > Parameters
Command parameters.
const String & Identifier()
const String & HelpTextLong()
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)
const String & HelpUsageLine()
const String & HelpText()
const String & Identifier()
const String & GetHelpTextLong()
const String & Name()
integer size() const
Definition list.inl:396
static threads::RecursiveLock DEFAULT_LOCK
Paragraphs & PushIndent(uinteger qty, character fillChar=' ')
void Add(boxing::TBoxes< TAllocatorArgs > &args)
void AddMarked(boxing::TBoxes< TAllocatorArgs > &args)
Paragraphs & PopIndent()
void Reset(Snapshot snapshot=Snapshot())
TAString & DeleteEnd(integer regionLength)
TAString & ShortenTo(integer newLength)
Definition tastring.inl:747
void EnsureRemainingCapacity(integer spaceNeeded)
Definition tastring.inl:561
constexpr integer Length() const
Definition string.inl:304
constexpr bool IsEmpty() const
Definition string.inl:353
TChar CharAtStart() const
Definition string.inl:421
constexpr bool IsNotEmpty() const
Definition string.inl:357
bool StartsWith(const TString &needle) const
Definition string.inl:739
size_type size() const
Definition string.inl:1984
TSubstring< TChar > Actual
Definition tokenizer.inl:67
TSubstring< TChar > & Next(lang::Whitespaces trimming=lang::Whitespaces::Trim, TChar newDelim='\0')
Definition tokenizer.cpp:26
DryRunModes
Dry run modes.
Definition clicamp.inl:86
bool Parse(strings::TSubstring< TChar > &input, TEnum &result)
constexpr String NULL_STRING
A nulled string of the default character type.
Definition string.inl:2254
strings::util::TTokenizer< character > Tokenizer
Type alias in namespace alib.
containers::List< T, MonoAllocator, TRecycling > ListMA
Type alias in namespace alib.
Definition list.inl:697
constexpr CString NEW_LINE
A zero-terminated string containing the new-line character sequence.
Definition cstring.inl:540
lang::integer integer
Type alias in namespace alib.
Definition integers.inl:149
boxing::Box Box
Type alias in namespace alib.
Definition box.inl:1135
strings::TString< character > String
Type alias in namespace alib.
Definition string.inl:2172
strings::TSubstring< character > Substring
Type alias in namespace alib.
LocalString< 256 > String256
Type alias name for #"TLocalString;TLocalString<character,256>".
strings::TAString< character, lang::HeapAllocator > AString
Type alias in namespace alib.
format::Paragraphs Paragraphs
Type alias in namespace alib.
lang::uinteger uinteger
Type alias in namespace alib.
Definition integers.inl:152
variables::Declaration Declaration
Type alias in namespace alib.
std::vector< T, StdMA< T > > StdVectorMA
Type alias in namespace alib.
NLocalString< 64 > NString64
Type alias name for #"TLocalString;TLocalString<nchar,64>".
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