1 /*
2 * Copyright (c) 2023 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "cl_option.h"
17 #include "cl_parser.h"
18
19 #include "mpl_logging.h"
20
21 #include <cstddef>
22 #include <deque>
23 #include <iostream>
24 #include <string>
25 #include <string_view>
26 #include <set>
27
28 using namespace maplecl;
29
GetCommandLine()30 CommandLine &CommandLine::GetCommandLine()
31 {
32 static CommandLine cl;
33 return cl;
34 }
35
CheckJoinedOption(KeyArg & keyArg,OptionCategory & optCategory)36 OptionInterface *CommandLine::CheckJoinedOption(KeyArg &keyArg, OptionCategory &optCategory)
37 {
38 auto &str = keyArg.rawArg;
39
40 for (auto joinedOption : optCategory.joinedOptions) {
41 /* Joined Option (like -DMACRO) can be detected as substring (-D) in the option string */
42 if (str.find(joinedOption.first) == 0) {
43 size_t keySize = joinedOption.first.size();
44
45 keyArg.val = str.substr(keySize);
46 keyArg.key = str.substr(0, keySize);
47 keyArg.isJoinedOpt = true;
48 return joinedOption.second;
49 }
50 }
51
52 return nullptr;
53 }
54
ParseJoinedOption(size_t & argsIndex,const std::deque<std::string_view> & args,KeyArg & keyArg,OptionCategory & optCategory)55 RetCode CommandLine::ParseJoinedOption(size_t &argsIndex, const std::deque<std::string_view> &args, KeyArg &keyArg,
56 OptionCategory &optCategory)
57 {
58 OptionInterface *option = CheckJoinedOption(keyArg, optCategory);
59 if (option != nullptr) {
60 RetCode err = option->Parse(argsIndex, args, keyArg);
61 if (err != RetCode::noError) {
62 return err;
63 }
64
65 /* Set Option in all categories registering for this option */
66 for (auto &category : option->optCategories) {
67 category->AddEnabledOption(option);
68 }
69 } else {
70 return RetCode::notRegistered;
71 }
72
73 return RetCode::noError;
74 }
75
ParseOption(size_t & argsIndex,const std::deque<std::string_view> & args,KeyArg & keyArg,OptionCategory & optCategory,OptionInterface * opt)76 RetCode CommandLine::ParseOption(size_t &argsIndex, const std::deque<std::string_view> &args, KeyArg &keyArg,
77 OptionCategory &optCategory, OptionInterface *opt)
78 {
79 RetCode err = opt->Parse(argsIndex, args, keyArg);
80 if (err != RetCode::noError) {
81 return err;
82 }
83
84 /* Set Option in all categories registering for this option */
85 for (auto &category : opt->optCategories) {
86 category->AddEnabledOption(opt);
87 }
88
89 return RetCode::noError;
90 }
91
ParseEqualOption(size_t & argsIndex,const std::deque<std::string_view> & args,KeyArg & keyArg,OptionCategory & optCategory,const OptionsMapType & optMap,size_t pos)92 RetCode CommandLine::ParseEqualOption(size_t &argsIndex, const std::deque<std::string_view> &args, KeyArg &keyArg,
93 OptionCategory &optCategory, const OptionsMapType &optMap, size_t pos)
94 {
95 keyArg.isEqualOpt = true;
96 auto &arg = args[argsIndex];
97
98 /* To handle joined option, we must have full (not splitted key),
99 * because joined option splitting is different:
100 * As example for -Dkey=value: default splitting key="Dkey" value="value",
101 * Joined option splitting key="D" value="key=value"
102 */
103 auto item = optMap.find(std::string(arg.substr(0, pos)));
104 if (item != optMap.end()) {
105 /* equal option, like --key=value */
106 keyArg.key = arg.substr(0, pos);
107 keyArg.val = arg.substr(pos + 1);
108 return ParseOption(argsIndex, args, keyArg, optCategory, item->second);
109 } else {
110 /* It can be joined option, like: -DMACRO=VALUE */
111 return ParseJoinedOption(argsIndex, args, keyArg, optCategory);
112 }
113 }
114
ParseSimpleOption(size_t & argsIndex,const std::deque<std::string_view> & args,KeyArg & keyArg,OptionCategory & optCategory,const OptionsMapType & optMap)115 RetCode CommandLine::ParseSimpleOption(size_t &argsIndex, const std::deque<std::string_view> &args, KeyArg &keyArg,
116 OptionCategory &optCategory, const OptionsMapType &optMap)
117 {
118 keyArg.isEqualOpt = false;
119 auto &arg = args[argsIndex];
120
121 auto item = optMap.find(std::string(arg));
122 if (item != optMap.end()) {
123 /* --key or --key value */
124 return ParseOption(argsIndex, args, keyArg, optCategory, item->second);
125 } else {
126 /* It can be joined option, like: -DMACRO */
127 return ParseJoinedOption(argsIndex, args, keyArg, optCategory);
128 }
129 }
130
HandleInputArgs(const std::deque<std::string_view> & args,OptionCategory & optCategory)131 RetCode CommandLine::HandleInputArgs(const std::deque<std::string_view> &args, OptionCategory &optCategory)
132 {
133 RetCode err = RetCode::noError;
134
135 /* badCLArgs contains option parsing errors for each incorrect option.
136 * We should clear old badCLArgs results. */
137 badCLArgs.clear();
138
139 bool wasError = false;
140 for (size_t argsIndex = 0; argsIndex < args.size();) {
141 auto &arg = args[argsIndex];
142 if (arg == "") {
143 ++argsIndex;
144 continue;
145 }
146
147 KeyArg keyArg(arg);
148
149 auto pos = arg.find('=');
150 /* option like --key=value */
151 if (pos != std::string::npos) {
152 DEBUG_ASSERT(pos > 0, "CG internal error, composite unit with less than 2 unit elements.");
153 err = ParseEqualOption(argsIndex, args, keyArg, optCategory, optCategory.options, pos);
154 if (err != RetCode::noError) {
155 badCLArgs.emplace_back(args[argsIndex], err);
156 ++argsIndex;
157 wasError = true;
158 }
159 continue;
160 } else {
161 /* option like "--key value" or "--key" */
162 err = ParseSimpleOption(argsIndex, args, keyArg, optCategory, optCategory.options);
163 if (err != RetCode::noError) {
164 badCLArgs.emplace_back(args[argsIndex], err);
165 ++argsIndex;
166 wasError = true;
167 }
168 continue;
169 }
170
171 ++argsIndex;
172 continue;
173 }
174
175 if (wasError == true) {
176 return RetCode::parsingErr;
177 }
178
179 return err;
180 }
181
Parse(int argc,char ** argv,OptionCategory & optCategory)182 RetCode CommandLine::Parse(int argc, char **argv, OptionCategory &optCategory)
183 {
184 if (argc > 0) {
185 --argc;
186 ++argv; // skip program name argv[0] if present
187 }
188
189 if (argc == 0 || *argv == nullptr) {
190 return RetCode::noError;
191 }
192
193 std::deque<std::string_view> args;
194 while (argc != 0 && *argv != nullptr) {
195 args.emplace_back(*argv);
196 ++argv;
197 --argc;
198 }
199
200 return HandleInputArgs(args, optCategory);
201 }
202
Parse(std::vector<std::string> argvs,OptionCategory & optCategory)203 RetCode CommandLine::Parse(std::vector<std::string> argvs, OptionCategory &optCategory)
204 {
205 if (argvs.size() == 0) {
206 return RetCode::noError;
207 }
208 std::deque<std::string_view> args;
209 for (size_t i = 0; i < argvs.size(); i++) {
210 args.emplace_back(argvs[i]);
211 }
212 return HandleInputArgs(args, optCategory);
213 }
214
Register(const std::vector<std::string> & optNames,OptionInterface & opt,OptionCategory & optCategory)215 void CommandLine::Register(const std::vector<std::string> &optNames, OptionInterface &opt, OptionCategory &optCategory)
216 {
217 for (auto &optName : optNames) {
218 if (optName.empty()) {
219 continue;
220 }
221
222 DEBUG_ASSERT(optCategory.options.count(optName) == 0, "Duplicated options name %s", optName.data());
223 optCategory.options.emplace(optName, &opt);
224
225 if (opt.IsJoinedValPermitted()) {
226 optCategory.joinedOptions.emplace(optName, &opt);
227 }
228 }
229
230 auto &disabledWith = opt.GetDisabledName();
231 if (!disabledWith.empty()) {
232 for (auto &disabledName : disabledWith) {
233 DEBUG_ASSERT(optCategory.options.count(disabledName) == 0, "Duplicated options name %s",
234 disabledName.data());
235 optCategory.options.emplace(disabledName, &opt);
236 }
237 }
238
239 optCategory.registredOptions.push_back(&opt);
240 opt.optCategories.push_back(&optCategory);
241 }
242
Clear(OptionCategory & optCategory)243 void CommandLine::Clear(OptionCategory &optCategory)
244 {
245 for (auto &opt : optCategory.registredOptions) {
246 opt->Clear();
247 }
248 }
249
BashCompletionPrinter(const OptionCategory & optCategory) const250 void CommandLine::BashCompletionPrinter(const OptionCategory &optCategory) const
251 {
252 for (auto &opt : optCategory.options) {
253 maple::LogInfo::MapleLogger() << opt.first << '\n';
254 }
255 }
256
HelpPrinter(const OptionCategory & optCategory) const257 void CommandLine::HelpPrinter(const OptionCategory &optCategory) const
258 {
259 for (auto &opt : optCategory.registredOptions) {
260 if (opt->IsVisibleOption()) {
261 maple::LogInfo::MapleLogger() << opt->GetDescription() << '\n';
262 }
263 }
264 }
265