Stroika Library 3.0d18
 
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"
12#include "Stroika/Foundation/Containers/Sequence.h"
13#include "Stroika/Foundation/Execution/Exceptions.h"
14
15/*
16 * \note Code-Status: <a href="Code-Status.md#Beta">Beta</a>
17 */
18
20
21 using Characters::String;
22 using Containers::Iterable;
23 using Containers::Sequence;
24
25 /**
26 * \todo Perhaps refactor slightly, so easy to tell one kind of issue from another.
27 */
29 public:
31 InvalidCommandLineArgument (const String& message);
32 InvalidCommandLineArgument (const String& message, const String& argument);
33
34 public:
35 String fMessage;
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 ()).AsNarrowSDKString () << endl;
64 * cerr << cmdLine.GenerateUsage (kAllOptions_).AsNarrowSDKString () << endl;
65 * return EXIT_FAILURE;
66 * }
67 * if (cmdLine.Has (StandardCommandLineOptions::kHelp)) {
68 * cerr << cmdLine.GenerateUsage (kAllOptions_).AsNarrowSDKString () << 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.
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 bool fSupportsArgument{false};
153
154 /**
155 * Typically, an option that takes an argument, that argument is required. But rarely - you might want an option that takes
156 * an argument that is optional.
157 */
159
160 /**
161 * If you can have the same option repeated multiple times. The only point of this would be for
162 * things to gather multiple arguments. Note that this can be used with no fSingleCharName and no fLongName, meaning it captures
163 * un-dash-decorated arguments.
164 */
165 bool fRepeatable{false};
166
167 /**
168 * If true, then Get (o) (and GetArgument (i)) will throw if this option isn't found in the commandline; though Has () will not throw.
169 */
170 bool fRequired{false};
171
172 /**
173 * If provided, its the name used in generating help, for the argument to this option.
174 */
175 optional<String> fHelpArgName;
176
177 /**
178 * If provided, its the name used in generating help, for this option.
179 */
180 optional<String> fHelpOptionText;
181
182 bool operator== (const Option&) const = default;
183#if qCompilerAndStdLib_explicitly_defaulted_threeway_warning_Buggy
184 DISABLE_COMPILER_CLANG_WARNING_START ("clang diagnostic ignored \"-Wdefaulted-function-deleted\"")
185#endif
186 auto operator<=> (const Option&) const = default;
187#if qCompilerAndStdLib_explicitly_defaulted_threeway_warning_Buggy
188 DISABLE_COMPILER_CLANG_WARNING_END ("clang diagnostic ignored \"-Wdefaulted-function-deleted\"")
189#endif
190
191 nonvirtual String GetArgumentDescription (bool includeArg = false) const;
192
193 nonvirtual String ToString () const;
194 };
195
196 public:
197 /**
198 * Throw InvalidCommandLineArgument if arguments not fit with options.
199 * This checks for unrecognized arguments.
200 *
201 * \par Example Usage
202 * \code
203 * const initializer_list<Execution::CommandLine::Option> kAllOptions_{StandardCommandLineOptions::kHelp, ...others...};
204 * cmdLine.Validate (kAllOptions_); // throws InvalidCommandLineArgument if bad args so catch/report usage
205 * \endcode
206 */
207 nonvirtual void Validate (Iterable<Option> options) const;
208
209 public:
210 /**
211 * \brief like Validate - but return optional< InvalidCommandLineArgument> instead of throwing
212 *
213 * \par Example Usage
214 * \code
215 * const initializer_list<Execution::CommandLine::Option> kAllOptions_{StandardCommandLineOptions::kHelp, ...others...};
216 * if (auto error = cmdLine.ValidateQuietly (kAllOptions_)) {
217 * cerr << "{}"_f (*error) << endl;
218 * cerr << CommandLine::GenerateUsage ("myApp", kAllOptions_) << endl;
219 * }
220 * \endcode
221 */
222 nonvirtual optional<InvalidCommandLineArgument> ValidateQuietly (Iterable<Option> options) const;
223
224 public:
225 /**
226 */
227 nonvirtual String GenerateUsage (const Iterable<Option>& options) const;
228 static String GenerateUsage (const String& exeName, const Iterable<Option>& options);
229
230 public:
231 /*
232 * return 'argv[0]'
233 *
234 */
235 nonvirtual String GetAppName (bool onlyBaseName = true) const;
236
237 public:
238 /*
239 * return get<bool> true iff arg is present in command line.
240 * Either way, get<Sequence<String>>> returns same as GetArguments ();
241 *
242 * \see often simpler GetArgument ()
243 */
244 nonvirtual tuple<bool, Sequence<String>> Get (const Option& o) const;
245
246 public:
247 /*
248 * 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).
249 */
250 nonvirtual bool Has (const Option& o) const;
251
252 public:
253 /**
254 * \pre o.fSupportsArgument
255 *
256 * \par Example Usage
257 * \code
258 * constexpr CommandLine::Option kOutFileOption_ = CommandLine::Option{.fSingleCharName = 'o', .fSupportsArgument = true };
259 * CommandLine cmdLine {argc, argv};
260 * String file2Use = cmdLine.GetArgument (kOutFileOption_).value_or ("default-file-name.xml");
261 * \endcode
262 */
263 nonvirtual optional<String> GetArgument (const Option& o) const;
264
265 public:
266 /**
267 * overload with no arguments /0 - returns all commandline arguments.
268 * \pre o.fSupportsArgument
269 */
270 nonvirtual Sequence<String> GetArguments () const;
271 nonvirtual Sequence<String> GetArguments (const Option& o) const;
272
273 public:
274 /**
275 * Strategy/rules used when converting between a list of arguments to a single string.
276 */
278 eWindowsCMD,
279 eBash
280 };
281
282 public:
283 /**
284 */
285 nonvirtual optional<StringShellQuoting> GetStringShellQuoting () const;
286 nonvirtual void SetStringShellQuoting (const optional<StringShellQuoting>& s);
287
288 public:
289 /**
290 * \par Example Usage (use default StringShellQuoting)
291 * \code
292 * String cmdLineText = cmdLine.As<String> ();
293 * \endcode
294 *
295 * \par Example Usage (use no StringShellQuoting)
296 * \code
297 * String cmdLineText = cmdLine.As<String> (nullopt);
298 * \endcode
299 *
300 * \par Example Usage (use bash style quoting)
301 * \code
302 * String cmdLineText = cmdLine.As<String> (StringShellQuoting::eBash);
303 * \endcode
304 */
305 template <Common::IAnyOf<String> T, typename... ARGS>
306 nonvirtual T As (ARGS... args) const;
307
308 public:
309 /**
310 *
311 */
312 nonvirtual String ToString () const;
313
314 private:
315 /*
316 * This may throw, but NOT for not finding option o, just for finding o, but ill-formed.
317 * Returns nullopt if Option 'o' not found at this point in sequence, or the result if it is found.
318 */
319 static optional<pair<bool, optional<String>>> ParseOneArg_ (const Option& o, Traversal::Iterator<String>* argi);
320
321 private:
322 optional<StringShellQuoting> fShellStyleQuoting_;
323 Sequence<String> fArgs_;
324 };
325 template <>
326 String CommandLine::As<String> () const;
327 template <>
328 String CommandLine::As<String> (optional<CommandLine::StringShellQuoting> shellStyle) const;
329
330 namespace StandardCommandLineOptions {
331 static inline const CommandLine::Option kHelp{.fSingleCharName = 'h', .fLongName = "help"sv, .fHelpOptionText = "Print out this help."sv};
332 static inline const CommandLine::Option kVersion{.fSingleCharName = 'v', .fLongName = "version"sv, .fHelpOptionText = "Print this application's version."sv};
333 }
334
335}
336
337/*
338 ********************************************************************************
339 ***************************** Implementation Details ***************************
340 ********************************************************************************
341 */
342#include "CommandLine.inl"
343
344#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 Sequence< String > GetArguments() const
nonvirtual T As(ARGS... args) const
nonvirtual optional< String > GetArgument(const Option &o) const
nonvirtual void Validate(Iterable< Option > options) const
nonvirtual optional< InvalidCommandLineArgument > ValidateQuietly(Iterable< Option > options) const
like Validate - but return optional< InvalidCommandLineArgument> instead of throwing
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