Stroika Library 3.0d20
 
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 */
253 nonvirtual String GetAppName (bool onlyBaseName = true) const;
254
255 public:
256 /*
257 * return get<bool> true iff arg is present in command line.
258 * Either way, get<Sequence<String>>> returns same as GetArguments ();
259 *
260 * \see often simpler GetArgument ()
261 */
262 nonvirtual tuple<bool, Sequence<String>> Get (const Option& o) const;
263
264 public:
265 /*
266 * 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).
267 */
268 nonvirtual bool Has (const Option& o) const;
269
270 public:
271 /**
272 * if the option o.fSupportsArgument, this returns the NAMED argument --name=X for example.
273 * if the option not o.fSupportsArgument, then this returns the first (if any) unnamed argument (positional argument)
274 *
275 * \par Example Usage
276 * \code
277 * constexpr CommandLine::Option kOutFileOption_ = CommandLine::Option{.fSingleCharName = 'o', .fSupportsArgument = true };
278 * CommandLine cmdLine {argc, argv};
279 * String file2Use = cmdLine.GetArgument (kOutFileOption_).value_or ("default-file-name.xml");
280 * \endcode
281 */
282 nonvirtual optional<String> GetArgument (const Option& o) const;
283
284 public:
285 /**
286 * overload with no arguments /0 - returns all commandline arguments.
287 * \pre o.fSupportsArgument
288 */
289 nonvirtual Sequence<String> GetArguments () const;
290 nonvirtual Sequence<String> GetArguments (const Option& o) const;
291
292 public:
293 /**
294 * Strategy/rules used when converting between a list of arguments to a single string.
295 */
297 eWindowsCMD,
298 eBash
299 };
300
301 public:
302 /**
303 */
304 nonvirtual optional<StringShellQuoting> GetStringShellQuoting () const;
305 nonvirtual void SetStringShellQuoting (const optional<StringShellQuoting>& s);
306
307 public:
308 /**
309 * \par Example Usage (use default StringShellQuoting)
310 * \code
311 * String cmdLineText = cmdLine.As<String> ();
312 * \endcode
313 *
314 * \par Example Usage (use no StringShellQuoting)
315 * \code
316 * String cmdLineText = cmdLine.As<String> (nullopt);
317 * \endcode
318 *
319 * \par Example Usage (use bash style quoting)
320 * \code
321 * String cmdLineText = cmdLine.As<String> (StringShellQuoting::eBash);
322 * \endcode
323 */
324 template <Common::IAnyOf<String> T, typename... ARGS>
325 nonvirtual T As (ARGS... args) const;
326
327 public:
328 /**
329 *
330 */
331 nonvirtual String ToString () const;
332
333 private:
334 /**
335 * This may throw, but NOT for not finding option o, just for finding o, but ill-formed.
336 * Returns nullopt if Option 'o' not found at this point in sequence, or the result if it is found.
337 *
338 * \notes
339 * o outer optional refers to if the option was found.
340 * o inner optional refers to if the option had an argument
341 */
344
345 private:
346 optional<StringShellQuoting> fShellStyleQuoting_;
347 Sequence<String> fArgs_;
348 };
349 template <>
350 String CommandLine::As<String> () const;
351 template <>
352 String CommandLine::As<String> (optional<CommandLine::StringShellQuoting> shellStyle) const;
353
354 namespace StandardCommandLineOptions {
355 static inline const CommandLine::Option kHelp{.fSingleCharName = 'h', .fLongName = "help"sv, .fHelpOptionText = "Print out this help."sv};
356 static inline const CommandLine::Option kVersion{.fSingleCharName = 'v', .fLongName = "version"sv, .fHelpOptionText = "Print this application's version."sv};
357 }
358
359}
360
361/*
362 ********************************************************************************
363 ***************************** Implementation Details ***************************
364 ********************************************************************************
365 */
366#include "CommandLine.inl"
367
368#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