Stroika Library 3.0d21
 
Loading...
Searching...
No Matches
CommandLine.h
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2025. All rights reserved
3 */
4#ifndef _Stroika_Foundation_Execution_CommandLine_h_
5#define _Stroika_Foundation_Execution_CommandLine_h_ 1
6
7#include "Stroika/Foundation/StroikaPreComp.h"
8
9#include "Stroika/Foundation/Characters/SDKChar.h"
11#include "Stroika/Foundation/Common/Common.h"
13#include "Stroika/Foundation/Containers/Sequence.h"
14#include "Stroika/Foundation/Execution/Exceptions.h"
15
16/*
17 * \note Code-Status: <a href="Code-Status.md#Beta">Beta</a>
18 */
19
21
22 using Characters::String;
23 using Containers::Iterable;
24 using Containers::Sequence;
25
26 /**
27 * \todo Perhaps refactor slightly, so easy to tell one kind of issue from another.
28 */
30 public:
32 InvalidCommandLineArgument (const String& message);
33 InvalidCommandLineArgument (const String& message, const String& argument);
34
35 public:
36 String fArgument;
37 };
38
39 /**
40 * Take in a 'command line' specification (typically from 'main', but also used as arguments to ProcessRunner),
41 * and define 'Option' objects and lookup if given arguments
42 * are 'present' in the commandline (and grab associated arguments).
43 *
44 * Supports repeated option elements. Supports -o and --output-file formats
45 * Supports -o ARG and -o=ARG formats
46 *
47 * inspired partly by https://man7.org/linux/man-pages/man3/getopt.3.html
48 *
49 * \par Example Usage (in application main)
50 * \code
51 * uint16_t portNumber = 8080;
52 *
53 * const CommandLine::Option kPortO_{
54 * .fLongName = "port"sv, .fSupportsArgument = true, .fHelpOptionText = "specify webserver listen port (default {})"_f(portNumber)};
55 * const CommandLine::Option kQuitAfterO_{
56 * .fLongName = "quit-after"sv, .fSupportsArgument = true, .fHelpOptionText = "automatically quit after <argument> seconds"sv};
57 * const Sequence<CommandLine::Option> kAllOptions_{StandardCommandLineOptions::kHelp, kPortO_, kQuitAfterO_};
58 *
59 * try {
60 * cmdLine.Validate (kAllOptions_);
61 * }
62 * catch (const InvalidCommandLineArgument&) {
63 * cerr << Characters::ToString (current_exception ()) << endl;
64 * cerr << cmdLine.GenerateUsage (kAllOptions_) << endl;
65 * return EXIT_FAILURE;
66 * }
67 * if (cmdLine.Has (StandardCommandLineOptions::kHelp)) {
68 * cerr << cmdLine.GenerateUsage (kAllOptions_) << endl;
69 * return EXIT_SUCCESS;
70 * }
71 * \endcode
72 *
73 * \par Example Usage (args to ProcessRunner)
74 * \code
75 * ProcessRunner p{CommandLine{WrapInShell::eBash, "echo $USER"}};
76 * \endcode
77 *
78 * TODO:
79 * o \todo find some way to better handle std::filesystem::path arguments (handle quoting, normalizing paths so they work better cross platform if needed/helpful (e.g. /cygrdrive/c/???)
80 *
81 */
83 public:
84 /**
85 * Used as optional CTOR argument, to create a CommandLine with
86 * bash -c "actual string arg"
87 * or
88 * cmd /C "actual string arg"
89 */
90 enum class WrapInShell {
91#if qStroika_Foundation_Common_Platform_Windows
92 eWindowsCMD,
93#endif
94 eBash,
95 };
96
97 public:
98 /**
99 * Unlike most other Stroika APIs, plain 'char' here for char*, is interpreted as being in the SDK code page
100 * (current locale - like SDKChar if narrow).
101 *
102 * CommandLine{STRING} parsing the string into individual components the way a generic shell would (space separation)
103 * and respecting quote characters).
104 *
105 * CommandLine{WrapInShell, STRING} really doesn't parse the string (except to add quotes as needed), but creates
106 * a CommandLine that will allow the argument shell to parse the command (e.g. bash -c "arguments").
107 *
108 * CommandLine{Sequence<String>} just captures that sequence of arguments and does not processing/parsing.
109 *
110 * CommandLine{argc,argv} are meant for being called from main, and also do no processing (besides treating the
111 * char* strings as SDKChar and mapping them from the OS codepage to UNICODE).
112 */
113 CommandLine () = delete;
114 CommandLine (const CommandLine&) = default;
115 CommandLine (const String& cmdLine);
116 CommandLine (WrapInShell wrapInShell, const String& cmdLine);
117 CommandLine (const Sequence<String>& cmdLine);
118 CommandLine (int argc, char* argv[]);
119 CommandLine (int argc, const char* argv[]);
120 CommandLine (int argc, wchar_t* argv[]);
121 CommandLine (int argc, const wchar_t* argv[]);
122
123 public:
124 /**
125 * \par Example Usage
126 * \code
127 * const CommandLine::Option kDashO = CommandLine::Option{.fSingleCharName = 'o', .fSupportsArgument = true };
128 * \endcode
129 *
130 * \note fSingleCharName is optional, and fLongName is also optional. Meaning its totally legal to supply no short name and no long-name (in which case its required to support fSupportsArgument)
131 *
132 * \pre fSingleCharName or fLongName or fSupportsArgument
133 * \pre not fRepeatable or fSupportsArgument
134 *
135 * \todo figure out if I can make this a literal type, so can be defined constexpr when not using fLongName (or maybe even with using stringview)
136 */
137 struct Option {
138 optional<char> fSingleCharName; // for -s
139 optional<String> fLongName; // for --long use
140
141 /**
142 * refers to long-name only - being case sensitive; defaults to eCaseInsensitive (appropriate for long names, less appropriate default for short - one letter - options).
143 */
144 Characters::CompareOptions fLongNameCaseSensitive{Characters::eCaseInsensitive};
145
146 /**
147 * Look for argument after option (note this refers to named arguments, NOT positional arguments - see IsPositionalArgument ())
148 *
149 * if true, and long-form option, look for -OPT=XXX and copy out XXX as the argument
150 * if true, and either form option given, if no =, look for next argi, and if there, use that as argument.
151 *
152 * But either way, this refers to supporting NAMED arguments, not POSITIONAL arguments.
153 */
154 bool fSupportsArgument{false};
155
156 /**
157 * Typically, an option that takes an argument, that argument is required. But rarely - you might want an option that takes
158 * an argument that is optional.
159 */
161
162 /**
163 * If you can have the same option repeated multiple times. The only point of this would be for
164 * things to gather multiple arguments. Note that this can be used with no fSingleCharName and no fLongName, meaning it captures
165 * un-dash-decorated arguments.
166 */
167 bool fRepeatable{false};
168
169 /**
170 * If true, then Get (o) (and GetArgument (i)) will throw if this option isn't found in the commandline; though Has () will not throw.
171 */
172 bool fRequired{false};
173
174 /**
175 * used for positional arguments, where its known what number position (not including -/-- arguments) to find these arguments.
176 *
177 * Causes GetArguments () to skip the given number of file arguments if provided.
178 *
179 * \req IsPositionArgument()
180 */
181 optional<size_t> fSkipFirstNArguments;
182
183 /**
184 * If provided, its the name used in generating help, for the argument to this option.
185 *
186 * This is HELPFUL to use for positional parameters, so they can be shown in help.
187 */
188 optional<String> fHelpArgName;
189
190 /**
191 * If provided, its the name used in generating help, for this option.
192 */
193 optional<String> fHelpOptionText;
194
195 bool operator== (const Option&) const = default;
196#if qCompilerAndStdLib_explicitly_defaulted_threeway_warning_Buggy
197 DISABLE_COMPILER_CLANG_WARNING_START ("clang diagnostic ignored \"-Wdefaulted-function-deleted\"")
198#endif
199 auto operator<=> (const Option&) const = default;
200#if qCompilerAndStdLib_explicitly_defaulted_threeway_warning_Buggy
201 DISABLE_COMPILER_CLANG_WARNING_END ("clang diagnostic ignored \"-Wdefaulted-function-deleted\"")
202#endif
203
204 nonvirtual String GetArgumentDescription (bool includeArgName = false) const;
205
206 /**
207 * Return true iff the argument (option) is not a named option (e..g not -n, or --blah, but positional in the argument list)
208 */
209 constexpr bool IsPositionArgument () const;
210
211 nonvirtual String ToString () const;
212 };
213
214 public:
215 /**
216 * Throw InvalidCommandLineArgument if arguments not fit with options.
217 * This checks for unrecognized arguments.
218 *
219 * \par Example Usage
220 * \code
221 * const initializer_list<Execution::CommandLine::Option> kAllOptions_{StandardCommandLineOptions::kHelp, ...others...};
222 * cmdLine.Validate (kAllOptions_); // throws InvalidCommandLineArgument if bad args so catch/report usage
223 * \endcode
224 */
225 nonvirtual void Validate (const Iterable<Option>& options) const;
226
227 public:
228 /**
229 * \brief like Validate - but return optional<InvalidCommandLineArgument> instead of throwing
230 *
231 * \par Example Usage
232 * \code
233 * const initializer_list<Execution::CommandLine::Option> kAllOptions_{StandardCommandLineOptions::kHelp, ...others...};
234 * if (auto error = cmdLine.ValidateQuietly (kAllOptions_)) {
235 * cerr << "{}"_f (*error) << endl;
236 * cerr << CommandLine::GenerateUsage ("myApp", kAllOptions_) << endl;
237 * }
238 * \endcode
239 */
240 nonvirtual optional<InvalidCommandLineArgument> ValidateQuietly (const Iterable<Option>& options) const;
241
242 public:
243 /**
244 */
245 nonvirtual String GenerateUsage (const Iterable<Option>& options) const;
246 static String GenerateUsage (const String& exeName, const Iterable<Option>& options);
247
248 public:
249 /*
250 * return 'argv[0]'
251 *
252 * if onlyBaseName is true, then return just the base name (e.g. 'myapp' from '/usr/bin/myapp' or 'C:\Program Files\MyApp\myapp.exe').
253 */
254 nonvirtual String GetAppName (bool onlyBaseName = true) const;
255
256 public:
257 /*
258 * return get<bool> true iff arg is present in command line.
259 * Either way, get<Sequence<String>>> returns same as GetArguments ();
260 *
261 * \see often simpler GetArgument ()
262 */
263 nonvirtual tuple<bool, Sequence<String>> Get (const Option& o) const;
264
265 public:
266 /*
267 * Return true iff arguments (in this object) have that option set. Note this will not throw just because option is required and missing (but may if ill formed).
268 */
269 nonvirtual bool Has (const Option& o) const;
270
271 public:
272 /**
273 * if the option o.fSupportsArgument, this returns the NAMED argument --name=X for example.
274 * if the option not o.fSupportsArgument, then this returns the first (if any) unnamed argument (positional argument)
275 *
276 * \par Example Usage
277 * \code
278 * constexpr CommandLine::Option kOutFileOption_ = CommandLine::Option{.fSingleCharName = 'o', .fSupportsArgument = true };
279 * CommandLine cmdLine {argc, argv};
280 * String file2Use = cmdLine.GetArgument (kOutFileOption_).value_or ("default-file-name.xml");
281 * \endcode
282 */
283 nonvirtual optional<String> GetArgument (const Option& o) const;
284
285 public:
286 /**
287 * overload with no arguments /0 - returns all commandline arguments.
288 * \pre o.fSupportsArgument
289 */
290 nonvirtual Sequence<String> GetArguments () const;
291 nonvirtual Sequence<String> GetArguments (const Option& o) const;
292
293 public:
294 /**
295 * Strategy/rules used when converting between a list of arguments to a single string.
296 */
298 eWindowsCMD,
299 eBash
300 };
301
302 public:
303 /**
304 */
305 nonvirtual optional<StringShellQuoting> GetStringShellQuoting () const;
306 nonvirtual void SetStringShellQuoting (const optional<StringShellQuoting>& s);
307
308 public:
309 /**
310 * \par Example Usage (use default StringShellQuoting)
311 * \code
312 * String cmdLineText = cmdLine.As<String> ();
313 * \endcode
314 *
315 * \par Example Usage (use no StringShellQuoting)
316 * \code
317 * String cmdLineText = cmdLine.As<String> (nullopt);
318 * \endcode
319 *
320 * \par Example Usage (use bash style quoting)
321 * \code
322 * String cmdLineText = cmdLine.As<String> (StringShellQuoting::eBash);
323 * \endcode
324 */
325 template <Common::IAnyOf<String> T, typename... ARGS>
326 nonvirtual T As (ARGS... args) const;
327
328 public:
329 /**
330 *
331 */
332 nonvirtual String ToString () const;
333
334 private:
335 /**
336 * This may throw, but NOT for not finding option o, just for finding o, but ill-formed.
337 * Returns nullopt if Option 'o' not found at this point in sequence, or the result if it is found.
338 *
339 * \notes
340 * o outer optional refers to if the option was found.
341 * o inner optional refers to if the option had an argument
342 */
345
346 private:
347 optional<StringShellQuoting> fShellStyleQuoting_;
348 Sequence<String> fArgs_;
349 };
350 template <>
351 String CommandLine::As<String> () const;
352 template <>
353 String CommandLine::As<String> (optional<CommandLine::StringShellQuoting> shellStyle) const;
354
355 namespace StandardCommandLineOptions {
356 static inline const CommandLine::Option kHelp{.fSingleCharName = 'h', .fLongName = "help"sv, .fHelpOptionText = "Print out this help."sv};
357 static inline const CommandLine::Option kVersion{.fSingleCharName = 'v', .fLongName = "version"sv, .fHelpOptionText = "Print this application's version."sv};
358 }
359
360}
361
362/*
363 ********************************************************************************
364 ***************************** Implementation Details ***************************
365 ********************************************************************************
366 */
367#include "CommandLine.inl"
368
369#endif /*_Stroika_Foundation_Execution_CommandLine_h_*/
String is like std::u32string, except it is much easier to use, often much more space efficient,...
Definition String.h:201
A generalization of a vector: a container whose elements are keyed by the natural numbers.
nonvirtual optional< InvalidCommandLineArgument > ValidateQuietly(const Iterable< Option > &options) const
like Validate - but return optional<InvalidCommandLineArgument> instead of throwing
nonvirtual Sequence< String > GetArguments() const
nonvirtual T As(ARGS... args) const
nonvirtual optional< String > GetArgument(const Option &o) const
nonvirtual void Validate(const Iterable< Option > &options) const
Iterable<T> is a base class for containers which easily produce an Iterator<T> to traverse them.
Definition Iterable.h:237
An Iterator<T> is a copyable object which allows traversing the contents of some container....
Definition Iterator.h:225