Skip to content

Commit e43ee69

Browse files
Kevin Dohertyfacebook-github-bot
Kevin Doherty
authored andcommitted
Move CommandLineParser from common/settings to folly/settings
Summary: * These files are responsible for parsing folly settings from command line arguments * They're currently part of common but I think make sense to colocate with the folly settings library itself (like how gflags exposes `gflags::ParseCommandLineFlags`) * No code changes except for the namespaces (`facebook::settings` -> `folly::settings`) * This could eventually unblock adding an option to parse folly settings from folly/Init Reviewed By: Gownta Differential Revision: D69604867 Privacy Context Container: L1259632 fbshipit-source-id: 512ee5920c24deb9f9eac3c0a56b7b007dcdcf75
1 parent 5430f4e commit e43ee69

6 files changed

+1278
-0
lines changed

folly/settings/CommandLineParser.cpp

+378
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,378 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#include <folly/settings/CommandLineParser.h>
18+
19+
#include <exception>
20+
#include <iostream>
21+
#include <optional>
22+
#include <string>
23+
24+
#include <fmt/core.h>
25+
#include <fmt/format.h>
26+
#include <folly/ExceptionString.h>
27+
#include <folly/Function.h>
28+
#include <folly/String.h>
29+
#include <folly/logging/xlog.h>
30+
31+
namespace folly::settings {
32+
33+
namespace {
34+
35+
/**
36+
* This class facilitates recursive declaration of a folly::Function which
37+
* returns an instance of itself. This is normally not possible to express in
38+
* C++ without typecast from void*. This class uses type cast operator to
39+
* achieve the same result, so it is transparent for users.
40+
*/
41+
struct RecursiveStateHelper {
42+
using type = Function<RecursiveStateHelper()>;
43+
/* implicit */ RecursiveStateHelper(type f) : func(std::move(f)) {}
44+
explicit operator type() { return std::move(func); }
45+
type func;
46+
};
47+
48+
using State = RecursiveStateHelper::type;
49+
} // namespace
50+
51+
class CommandLineParser::Impl {
52+
public:
53+
explicit Impl(int& argc, char**& argv, SettingsAccessorProxy& flags_info)
54+
: end_(argc), argc_(argc), argv_(argv), flagsInfo_(flags_info) {
55+
end_ =
56+
std::find_if(
57+
argv_ + pos_,
58+
argv_ + end_,
59+
[](const auto& arg) { return std::strcmp(arg, "--") == 0; }) -
60+
argv_;
61+
last_ = end_;
62+
}
63+
64+
ArgParsingResult parse() {
65+
result_ = ArgParsingResult::OK;
66+
67+
State state = parseFlagState();
68+
while (state) {
69+
state = state();
70+
}
71+
72+
return result_;
73+
}
74+
75+
private:
76+
/* BEGIN State machine methods */
77+
/**
78+
* Parses next arg into a flag:value pair. Can transition into:
79+
* - moveBackState
80+
* - endOfArgsState
81+
* - helpFlagState
82+
* - parseFlagValueState
83+
* - setSettingState
84+
*/
85+
State parseFlagState() {
86+
return [this] {
87+
value_in_arg_ = true;
88+
if (!haveMoreArgs()) {
89+
return doneState();
90+
}
91+
92+
auto arg = getNext();
93+
if (!arg.startsWith('-') // positional
94+
|| arg == "-" // stdin
95+
|| arg.empty() // empty?
96+
) {
97+
return moveBackState();
98+
}
99+
100+
// Like gflags allow one or two '-' in front
101+
if (arg.startsWith('-')) {
102+
arg = arg.subpiece(1);
103+
}
104+
if (arg.startsWith('-')) {
105+
arg = arg.subpiece(1);
106+
}
107+
108+
if (arg.empty()) { // reached '--' it is end of args
109+
return endOfArgsState();
110+
}
111+
112+
if (!split<false>('=', arg, flag_, value_)) {
113+
flag_ = arg;
114+
if (isHelpFlag()) {
115+
return helpFlagState();
116+
}
117+
118+
return parseFlagValueState();
119+
}
120+
121+
if (isHelpFlag()) {
122+
return helpFlagState();
123+
}
124+
125+
return setSettingState();
126+
};
127+
}
128+
129+
/**
130+
* Parses value for current flag if flag's arg didn't contain a value. Can
131+
* transition into:
132+
* - errorFlagValueState
133+
* - setSettingState
134+
*/
135+
State parseFlagValueState() {
136+
return [this] {
137+
auto no_value = [this] {
138+
bool is_bool = is_bool_arg(flag_);
139+
if (is_bool) {
140+
value_ = "true";
141+
return setSettingState();
142+
}
143+
return errorFlagValueState();
144+
};
145+
146+
if (!haveMoreArgs()) {
147+
return no_value();
148+
}
149+
150+
value_in_arg_ = false;
151+
auto arg = getNext();
152+
if (arg.startsWith('-')) {
153+
pos_ -= 1;
154+
value_in_arg_ = true;
155+
return no_value();
156+
}
157+
158+
value_ = arg;
159+
return setSettingState();
160+
};
161+
}
162+
163+
/**
164+
* Handles case when there is no value for a flag and flag is not bool flag.
165+
* Can transition into:
166+
* - moveBackState
167+
*/
168+
State errorFlagValueState() {
169+
return [this] {
170+
if (!is_folly_setting(flag_)) {
171+
return moveBackState();
172+
}
173+
174+
fprintf(stderr, "Flag %s requires a value\n", flag_.data());
175+
result_ = ArgParsingResult::ERROR;
176+
return moveBackState();
177+
};
178+
}
179+
180+
/**
181+
* Sets folly::settings setting for current flag. Can transition into:
182+
* - parseFlagState
183+
* - moveBackState
184+
* - errorFlagSetState
185+
*/
186+
State setSettingState() {
187+
return [this] {
188+
auto settingMeta = flagsInfo_.getSettingMetadata(flag_);
189+
if (!settingMeta.has_value() ||
190+
settingMeta->get().commandLine == CommandLine::RejectOverrides) {
191+
return moveBackState();
192+
}
193+
194+
std::optional<std::string> errorMessage;
195+
try {
196+
auto result = flagsInfo_.setFromString(flag_, value_, "cli");
197+
if (result.hasError()) {
198+
errorMessage = toString(result.error());
199+
}
200+
} catch (...) {
201+
errorMessage = std::string(exceptionStr(std::current_exception()));
202+
}
203+
204+
if (errorMessage.has_value()) {
205+
// move unparsed flags
206+
return errorFlagSetState(std::move(errorMessage).value());
207+
}
208+
209+
return parseFlagState();
210+
};
211+
}
212+
213+
/**
214+
* Handles --help flag. Can transition into:
215+
* - moveBackState
216+
*/
217+
State helpFlagState() {
218+
if (result_ == ArgParsingResult::OK) { // Do not reset error
219+
result_ = ArgParsingResult::HELP;
220+
}
221+
return moveBackState();
222+
}
223+
224+
/**
225+
* Handles case when folly::setting cannot be set. Can transition into:
226+
* - moveBackState
227+
*/
228+
State errorFlagSetState(std::string&& errorMessage) {
229+
return [this, errorMessage = std::move(errorMessage)] {
230+
XLOG(ERR) << fmt::format(
231+
"Failed setting '{}' with '{}' due to '{}'",
232+
flag_,
233+
value_,
234+
errorMessage);
235+
result_ = ArgParsingResult::ERROR;
236+
return moveBackState();
237+
};
238+
}
239+
240+
/**
241+
* Moves arguments we couldn't recognize as folly::settings arguments into the
242+
* back of the argv. This includes move of "argumen value", if next argument
243+
* doesn't start with '--'. Can transition into:
244+
* - parseFlagState
245+
*/
246+
State moveBackState() {
247+
return [this] {
248+
// If value_in_arg_ == true we need to move both flag and value
249+
move_args(value_in_arg_ ? 1 : 2);
250+
251+
return parseFlagState();
252+
};
253+
}
254+
255+
/**
256+
* Handles case when we encounter '--' stops parsing. Can transition into:
257+
* - doneState
258+
*/
259+
State endOfArgsState() {
260+
return [this] { return doneState(); };
261+
}
262+
263+
/**
264+
* Stops argument parsing state machine.
265+
*/
266+
State doneState() {
267+
return [this] {
268+
argv_[end_ - 1] = argv_[0];
269+
argv_ += end_ - 1;
270+
argc_ -= end_ - 1;
271+
return State();
272+
};
273+
}
274+
/* END State machine methods */
275+
276+
/**
277+
* Actual function which moves argument at pos_ to the end of argv
278+
*/
279+
void move_args(int num_positions) {
280+
std::rotate(argv_ + pos_ - num_positions, argv_ + pos_, argv_ + last_);
281+
end_ -= num_positions;
282+
pos_ -= num_positions;
283+
}
284+
285+
/**
286+
* Returns true if a flag is folly::settings flag and it is of type bool
287+
*
288+
* @param arg flag name
289+
* @return is bool folly::settings
290+
*/
291+
bool is_bool_arg(StringPiece arg) { return flagsInfo_.isBooleanFlag(arg); }
292+
293+
bool is_folly_setting(StringPiece arg) { return flagsInfo_.hasFlag(arg); }
294+
295+
bool isHelpFlag() { return flag_ == kHelpFlag; }
296+
297+
bool haveMoreArgs() { return pos_ < end_; }
298+
299+
/**
300+
* @return next argument from argv
301+
*/
302+
StringPiece getNext() { return argv_[pos_++]; }
303+
304+
ArgParsingResult result_;
305+
bool value_in_arg_{true};
306+
ptrdiff_t pos_{1}, end_, last_;
307+
int& argc_;
308+
char**& argv_;
309+
310+
StringPiece flag_, value_;
311+
312+
SettingsAccessorProxy& flagsInfo_;
313+
};
314+
315+
CommandLineParser::CommandLineParser(
316+
int& argc, char**& argv, SettingsAccessorProxy& flags_info)
317+
: impl_(std::make_unique<Impl>(argc, argv, flags_info)) {}
318+
319+
CommandLineParser::CommandLineParser(CommandLineParser&&) noexcept = default;
320+
CommandLineParser& CommandLineParser::operator=(CommandLineParser&&) noexcept =
321+
default;
322+
CommandLineParser::~CommandLineParser() = default;
323+
324+
ArgParsingResult CommandLineParser::parse() {
325+
return impl_->parse();
326+
}
327+
328+
void printHelpIfNeeded(StringPiece app, bool exit_on_help) {
329+
std::cerr << fmt::format("{}:\n", app);
330+
Snapshot snapshot;
331+
for (const auto& kv : SettingsAccessorProxy(snapshot).getSettingsMetadata()) {
332+
auto s = fmt::format("--{} {}", kv.first, kv.second.description.str());
333+
std::cerr << fmt::format("\t{}\n", s);
334+
335+
auto type = kv.second.typeStr;
336+
auto def = kv.second.defaultStr;
337+
338+
std::cerr << fmt::format("\t type: {}", type);
339+
if (type != "bool") {
340+
std::cerr << fmt::format(" default: {}", def);
341+
}
342+
std::cerr << "\n";
343+
}
344+
345+
if (exit_on_help) {
346+
exit(0);
347+
}
348+
}
349+
350+
ArgParsingResult parseCommandLineArguments(
351+
int& argc,
352+
char**& argv,
353+
StringPiece project,
354+
Snapshot& snapshot,
355+
const SettingsAccessorProxy::SettingAliases& aliases) {
356+
SettingsAccessorProxy flags_info(snapshot, project, aliases);
357+
auto result = CommandLineParser(argc, argv, flags_info).parse();
358+
if (result == ArgParsingResult::OK) {
359+
snapshot.publish();
360+
}
361+
return result;
362+
}
363+
364+
ArgParsingResult parseCommandLineArguments(
365+
int& argc,
366+
char**& argv,
367+
StringPiece project,
368+
Snapshot* snapshot,
369+
const SettingsAccessorProxy::SettingAliases& aliases) {
370+
if (snapshot) {
371+
return parseCommandLineArguments(argc, argv, project, *snapshot, aliases);
372+
}
373+
374+
Snapshot snap;
375+
return parseCommandLineArguments(argc, argv, project, snap, aliases);
376+
}
377+
378+
} // namespace folly::settings

0 commit comments

Comments
 (0)