1 | // tCommand.h  |
2 | //  |
3 | // Parses a command line. A command line takes the form:  |
4 | // program.exe [arg1 arg2 arg3 ...]  |
5 | //  |
6 | // Arguments are separated by spaces. An argument must be enclosed in quotes  |
7 | // (single or double) if it has a space or hyphen in it. Use escape sequences to  |
8 | // put either type of quote inside. If you need to specify paths, it is suggested  |
9 | // to use forward slashes, although backslashes will work so long as the filename  |
10 | // does not have a single or double quote next.  |
11 | //  |
12 | // An argument may be an 'option' or a 'parameter'.  |
13 | //  |
14 | // Options:  |
15 | // An option has a short syntax and a long syntax. Short syntax is a - followed by  |
16 | // a single non-hyphen character. The long form is -- followed by a word. All  |
17 | // options support either long, short, or both forms. Options may have 0 or more  |
18 | // arguments. If an option takes zero arguments it is called a flag and you can  |
19 | // only test for its presence or lack of. Options can be specified in any order.  |
20 | // Short form options may be combined: Eg. -al expands to -a -l  |
21 | //  |
22 | // Parameters:  |
23 | // A parameter is simply an argument that does not start with a - or --. It can be  |
24 | // read as a string and parsed arbitrarily (converted to an integer or float etc.)  |
25 | // Order is important when specifying parameters.  |
26 | //  |
27 | // Example:  |
28 | // mycopy.exe -R --overwrite fileA.txt -pat fileB.txt --log log.txt  |
29 | //  |
30 | // The fileA.txt and fileB.txt in the above example are parameters (assuming  |
31 | // the overwrite option is a flag). fileA.txt is the first parameter and  |
32 | // fileB.txt is the second.  |
33 | //  |
34 | // The '--log log.txt' is an option with a single argument, log.txt. Flags may be  |
35 | // combined. The -pat in the example expands to -p -a -t. It is suggested only to  |
36 | // combine flag options as only the last option would get any arguments.  |
37 | //  |
38 | // If you wish to interpret a hyphen directly instead of as an option specifier  |
39 | // this will happen automatically if there are no options matching what comes  |
40 | // after the hyphen. Eg. 'tool.exe -.85 --add 33 -87.98 -notpresent' works just  |
41 | // fine as long as there are no options that have a short form with digits or a  |
42 | // decimal. In this example the -.85 will be the first parameter, --notpresent  |
43 | // will be the second, and the --add takes in two number arguments.  |
44 | //  |
45 | // Variable argument options are not supported due to the extra syntax that would  |
46 | // be needed. The same result is achieved by entering the same option more than  |
47 | // once. Eg. tool.exe -I /patha/include/ -I /pathb/include  |
48 | //  |
49 | // A powerful feature of the design of this parsing system is separation of concerns. In a typical system the knowledge  |
50 | // of all the different command line parameters and options is needed in a single place, often in main() where argc and  |
51 | // argv are passed in. These values need to somehow be passed all over the place in a large system. With tCommand you  |
52 | // specify which options and parameters you care about only in the cpp file you are working in.  |
53 | //  |
54 | // To use the command line class, you start by registering your options and parameters. This is done using the tOption  |
55 | // and tParam types to create static objects. After main calls the parse function, your objects get populated  |
56 | // appropriately. For example,  |
57 | //  |
58 | // FileA.cpp:  |
59 | // tParam FromFile(1, "FromFile"); // The 1 means this is the first parameter. The description is optional.  |
60 | // tParam ToFile(2, "ToFile"); // The 2 means this is the second parameter. The description is optional.  |
61 | // tOption("log", 'l', 1, "Specify log file"); // The 1 means there is one option argument to --log or -l.  |
62 | //  |
63 | // FileB.cpp:  |
64 | // tOption ProgramOption('p', 0, "Program mode.");  |
65 | // tOption AllOption('a', "ALL", 0, "Include all widgets.");  |
66 | // tOption TimeOption("time", 't', 0, "Print timestamp.");  |
67 | //  |
68 | // Main.cpp:  |
69 | // tParse(argc, argv);  |
70 | //  |
71 | // Internal Processing. The first step is the expansion of combined single hyphen options. Next the parameters and  |
72 | // options are parsed out. For each registered tOption and tParam object, its members are set to reflect the current  |
73 | // command line when the tParse call is made. You may have more than one tOption that responds to the same option name.  |
74 | // You may have more than one tParam that responds to the same parameter number.  |
75 | //  |
76 | // Copyright (c) 2017, 2020 Tristan Grimmer.  |
77 | // Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby  |
78 | // granted, provided that the above copyright notice and this permission notice appear in all copies.  |
79 | //  |
80 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL  |
81 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,  |
82 | // INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN  |
83 | // AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR  |
84 | // PERFORMANCE OF THIS SOFTWARE.  |
85 |   |
86 | #pragma once  |
87 | #include <Foundation/tList.h>  |
88 | #include <Foundation/tString.h>  |
89 |   |
90 |   |
91 | namespace tCommand  |
92 | {  |
93 | struct tParam : public tLink<tParam>  |
94 | {  |
95 | tParam(int paramNumber, const char* name = nullptr, const char* description = nullptr);  |
96 | tParam(const char* description, const char* paramName, int paramNumber);  |
97 | tString Get() const { return Param; }  |
98 | bool IsPresent() const { return !Param.IsEmpty(); }  |
99 | operator bool() const { return IsPresent(); }  |
100 |   |
101 | int ParamNumber; // 1 based.  |
102 | tString Param;  |
103 | tString Name;  |
104 | tString Description;  |
105 | };  |
106 |   |
107 | struct tOption : public tLink<tOption>  |
108 | {  |
109 | tOption(const char* description, char shortName, const char* longName, int numArgs = 0);  |
110 | tOption(const char* description, const char* longName, char shortName, int numArgs = 0);  |
111 | tOption(const char* description, char shortName, int numArgs = 0);  |
112 | tOption(const char* description, const char* longName, int numArgs = 0);  |
113 |   |
114 | // These validity checking functions only return true if the option was found in the command line and all  |
115 | // arguments were successfully parsed.  |
116 | bool IsPresent() const { return Present; }  |
117 | operator bool() const { return IsPresent(); }  |
118 |   |
119 | // These argument accessors all return a reference to a static empty string if 'n' is out of range or the  |
120 | // option is invalid. GetArgs will return false on invalid.  |
121 | const tString& Arg1() const { return ArgN(1); }  |
122 | const tString& Arg2() const { return ArgN(2); }  |
123 | const tString& Arg3() const { return ArgN(3); }  |
124 | const tString& Arg4() const { return ArgN(4); }  |
125 | const tString& ArgN(int n) const; // n must be >= 1.  |
126 | bool GetArgs(tList<tStringItem>& args) const;  |
127 | int GetNumArgs() const { return Args.Count(); }  |
128 | int GetNumFlagArgs() const { return NumFlagArgs; }  |
129 |   |
130 | tString ShortName;  |
131 | tString LongName;  |
132 | tString Description;  |
133 |   |
134 | // This is _not_ the number of args that necessarily gets collected in the Args list. It is the number of args  |
135 | // for each instance of the flag in the command line.  |
136 | int NumFlagArgs;  |
137 |   |
138 | // Important note here. If you have an option that takes 1 argument and it is listed in the command line  |
139 | // multiple times like "-i fileA -i fileB", then they will collect in the Args list in multiples  |
140 | // of 1. In general the arguments collect in multiples of NumFlagArgs.  |
141 | tList<tStringItem> Args;  |
142 | bool Present;  |
143 | };  |
144 |   |
145 | void tParse(int argc, char** argv);  |
146 | void tParse(const char* commandLine, bool fullCommandLine = false);  |
147 | void tPrintUsage(int versionMajor, int versionMinor = -1, int revision = -1);  |
148 | void tPrintUsage(const char* author, int versionMajor, int versionMinor = -1, int revision = -1);  |
149 | void tPrintUsage(const char* author, const char* desc, int versionMajor, int versionMinor = -1, int revision = -1);  |
150 | void tPrintUsage(const char* versionAuthor = nullptr, const char* desc = nullptr);  |
151 | void tPrintSyntax();  |
152 |   |
153 | // Returns the program name assuming you have already called tParse.  |
154 | tString tGetProgram();  |
155 | }  |
156 |   |
157 |   |
158 | // Implementation below this line.  |
159 |   |
160 |   |
161 | inline tCommand::tParam::tParam(const char* description, const char* paramName, int paramNumber) :  |
162 | tParam(paramNumber, paramName, description)  |
163 | {  |
164 | }  |
165 |   |
166 |   |
167 | inline bool tCommand::tOption::GetArgs(tList<tStringItem>& args) const  |
168 | {  |
169 | if (!IsPresent())  |
170 | return false;  |
171 |   |
172 | for (tStringItem* srcArg = Args.First(); srcArg; srcArg = srcArg->Next())  |
173 | args.Append(new tStringItem(*srcArg));  |
174 |   |
175 | return true;  |
176 | }  |
177 | |