Skip to content

Commit 919a050

Browse files
committed
Adding colors to help text
1 parent 5986b20 commit 919a050

File tree

1 file changed

+141
-23
lines changed

1 file changed

+141
-23
lines changed

src/command_line.cpp

Lines changed: 141 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,107 @@ static auto setChrootDir(CommandLine & cmdline, std::string_view val) {
197197
cmdline.chrootDir.emplace(std::move(value));
198198
}
199199

200+
static std::string colorTagged(std::string_view str) {
201+
202+
static constexpr std::pair<std::string_view, std::string_view> replacements[] = {
203+
{"norm", makeColor<Color::normal>()},
204+
{"bold", makeColor<Color::bold>()},
205+
{"longopt", defaultColorScheme().longOptionInUsage},
206+
{"arg", defaultColorScheme().optionArgInUsage},
207+
};
208+
209+
static const std::regex patterns_re = []() {
210+
std::string patterns = "\\{(?:";
211+
patterns += "(";
212+
patterns += replacements[0].first;
213+
patterns += ")";
214+
for (size_t i = 1; i < std::size(replacements); ++i) {
215+
patterns += "|(";
216+
patterns += replacements[i].first;
217+
patterns += ")";
218+
}
219+
patterns += ")\\}";
220+
221+
return std::regex(patterns, std::regex_constants::ECMAScript);
222+
}();
223+
224+
std::string ret;
225+
for (auto start = str.cbegin(); ; ) {
226+
std::cmatch m;
227+
if (!std::regex_search(start, str.cend(), m, patterns_re)) {
228+
ret.append(start, str.cend());
229+
break;
230+
}
231+
size_t found_idx = std::find_if(m.begin() + 1, m.end(), [](auto & sm) {
232+
return sm.length() > 0;
233+
}) - m.begin();
234+
if (found_idx < m.size() && found_idx - 1 < std::size(replacements)) {
235+
ret.append(start, m[0].first);
236+
ret.append(replacements[found_idx - 1].second);
237+
} else {
238+
ret.append(start, m[0].second);
239+
}
240+
start = m[0].second;
241+
}
242+
return ret;
243+
}
244+
245+
static void removeColors(std::string & str) {
246+
enum {
247+
stateNormal,
248+
stateEsc,
249+
stateControlStart,
250+
stateControlIntermediate
251+
} state = stateNormal;
252+
size_t putIdx = 0;
253+
for (char c: str) {
254+
switch(state) {
255+
break; case stateNormal: restart:
256+
if (c == '\x1b') {
257+
state = stateEsc;
258+
continue;
259+
}
260+
str[putIdx++] = c;
261+
262+
break; case stateEsc:
263+
if (c == '[') {
264+
state = stateControlStart;
265+
continue;
266+
}
267+
state = stateNormal;
268+
goto restart;
269+
270+
break; case stateControlStart:
271+
if (c >= 0x30 && c <= 0x3F) {
272+
continue;
273+
}
274+
if (c >= 0x20 && c <= 0x2F) {
275+
state = stateControlIntermediate;
276+
continue;
277+
}
278+
if (c >= 0x40 && c <= 0x7E) {
279+
state = stateNormal;
280+
continue;
281+
}
282+
state = stateNormal;
283+
goto restart;
284+
285+
break; case stateControlIntermediate:
286+
if (c >= 0x20 && c <= 0x2F) {
287+
state = stateControlIntermediate;
288+
continue;
289+
}
290+
if (c >= 0x40 && c <= 0x7E) {
291+
state = stateNormal;
292+
continue;
293+
}
294+
state = stateNormal;
295+
goto restart;
296+
}
297+
}
298+
str.resize(putIdx);
299+
}
300+
200301
void CommandLine::parse(int argc, char * argv[], ColorStatus envColorStatus) {
201302

202303
const char * const progname = (argc ? argv[0] : WSDDN_PROGNAME);
@@ -207,8 +308,12 @@ void CommandLine::parse(int argc, char * argv[], ColorStatus envColorStatus) {
207308
help("show this help message and exit").
208309
handler([&]() {
209310

311+
auto useColor = shouldUseColor(envColorStatus, stdout);
210312
auto colorizer = colorizerForFile(envColorStatus, stdout);
211-
fmt::print("{}", parser.formatHelp(progname, terminalWidth(stdout), colorizer));
313+
auto help = parser.formatHelp(progname, terminalWidth(stdout), colorizer);
314+
if (!useColor)
315+
removeColors(help);
316+
fmt::print("{}", help);
212317
exit(EXIT_SUCCESS);
213318
}));
214319
parser.add(Option("--version", "-v").
@@ -266,7 +371,7 @@ void CommandLine::parse(int argc, char * argv[], ColorStatus envColorStatus) {
266371
"--ipv4only and --ipv6only cannot be used together");
267372
parser.add(Option("--hoplimit").
268373
argName("NUMBER").
269-
help("hop limit for multicast packets (default = 1)").
374+
help(colorTagged("hop limit for multicast packets (default = {bold}1{norm})")).
270375
occurs(Argum::neverOrOnce).
271376
handler([this](std::string_view val){
272377
this->hoplimit = Argum::parseIntegral<unsigned>(val);
@@ -287,28 +392,31 @@ void CommandLine::parse(int argc, char * argv[], ColorStatus envColorStatus) {
287392
}));
288393
parser.add(Option("--hostname", "-H").
289394
argName("NAME").
290-
help("override hostname to be reported to Windows machines. "
291-
"If you set the value to \":NETBIOS:\" then Netbios hostname will be used. "
395+
help(colorTagged(
396+
"override hostname to be reported to Windows machines. "
397+
"If you set the value to {bold}\":NETBIOS:\"{norm} then Netbios hostname will be used. "
292398
"The Netbios hostname is either detected from SMB configuration, if found, or produced "
293-
"by capitalizing normal machine hostname.").
399+
"by capitalizing normal machine hostname.")).
294400
occurs(Argum::neverOrOnce).
295401
handler([this](std::string_view val) {
296402
setHostname(*this, sys_string(val).trim());
297403
}));
298404
parser.add(Option("--domain", "-D").
299405
argName("NAME").
300-
help("report this computer as a member of Windows domain NAME. "
301-
"--domain and --workgroup are mutually exclusive. "
302-
"If neither is specified domain/workgroup membership is auto-detected").
406+
help(colorTagged(
407+
"report this computer as a member of Windows domain {arg}NAME{norm}.\n"
408+
"{longopt}--domain{norm} and {longopt}--workgroup{norm} are mutually exclusive. "
409+
"If neither is specified domain/workgroup membership is auto-detected")).
303410
occurs(Argum::neverOrOnce).
304411
handler([this](std::string_view val){
305412
setDomain(*this, sys_string(val).trim());
306413
}));
307414
parser.add(Option("--workgroup", "-W").
308415
argName("NAME").
309-
help("report this computer as a member of Windows workgroup NAME. "
310-
"--domain and --workgroup are mutually exclusive. "
311-
"If neither is specified domain/workgroup membership is auto-detected").
416+
help(colorTagged(
417+
"report this computer as a member of Windows workgroup {arg}NAME{norm}.\n"
418+
"{longopt}--domain{norm} and {longopt}--workgroup{norm} are mutually exclusive. "
419+
"If neither is specified domain/workgroup membership is auto-detected")).
312420
occurs(Argum::neverOrOnce).
313421
handler([this](std::string_view val){
314422
setWorkgroup(*this, sys_string(val).trim());
@@ -318,8 +426,9 @@ void CommandLine::parse(int argc, char * argv[], ColorStatus envColorStatus) {
318426
#if CAN_HAVE_SAMBA
319427
parser.add(Option("--smb-conf").
320428
argName("PATH").
321-
help("location of smb.conf (a.k.a. samba.conf) to get the workgroup etc. information from. "
322-
"Normally its location is auto-detected. Use this option if auto-detection fails or picks wrong samba instance. ").
429+
help(colorTagged(
430+
"location of {bold}smb.conf{norm} (a.k.a. {bold}samba.conf{norm}) to get the workgroup etc. information from. "
431+
"Normally its location is auto-detected. Use this option if auto-detection fails or picks wrong samba instance.")).
323432
occurs(Argum::neverOrOnce).
324433
handler([this](std::string_view val){
325434
setSmbConf(*this, val);
@@ -337,42 +446,51 @@ void CommandLine::parse(int argc, char * argv[], ColorStatus envColorStatus) {
337446
//Behavior options
338447
parser.add(Option("--log-level").
339448
argName("LEVEL").
340-
help("set log level (default = 4). Log levels range from 0 (disable logging) to 6 (detailed trace).\n"
341-
"Passing values bigger than 6 is equivalent to 6").
449+
help(colorTagged(
450+
"set log level (default = {bold}4{norm}). Log levels range from "
451+
"{bold}0{norm} (disable logging) to {bold}6{norm} (detailed trace).\n"
452+
"Passing values bigger than {bold}6{norm} is equivalent to {bold}6{norm}")).
342453
occurs(Argum::neverOrOnce).
343454
handler([this](std::string_view val){
344455
auto decrease = Argum::parseIntegral<unsigned>(val);
345456
setLogLevel(*this, decrease);
346457
}));
347458
parser.add(Option("--log-file").
348459
argName("PATH").
349-
help("the file to write the log output to.\n"
350-
"If --user option is used, the directory of the logfile must allow the specified user to create and delete files").
460+
help(colorTagged(
461+
"the file to write the log output to.\n"
462+
"If {longopt}--user{norm} option is used, the directory of the logfile must allow the specified "
463+
"user to create and delete files")).
351464
occurs(Argum::neverOrOnce).
352465
handler([this](std::string_view val){
353466
setLogFile(*this, val);
354467
}));
355468
#if HAVE_OS_LOG
356469
parser.add(Option("--log-os-log").
357-
help("log to system log.\n"
358-
"This option is mutually exclusive with --log-file").
470+
help(colorTagged(
471+
"log to system log.\n"
472+
"This option is mutually exclusive with {longopt}--log-file{norm}")).
359473
occurs(Argum::neverOrOnce).
360474
handler([this](){
361475
setLogToOsLog(*this, true);
362476
}));
363477
#endif
364478
parser.add(Option("--pid-file").
365479
argName("PATH").
366-
help("PID file to create.\n"
367-
"If --user option is used, the directory of the pidfile must allow the specified user to create and delete files").
480+
help(colorTagged(
481+
"PID file to create.\n"
482+
"If {longopt}--user{norm} option is used, the directory of the pidfile must "
483+
"allow the specified user to create and delete files")).
368484
occurs(Argum::neverOrOnce).
369485
handler([this](std::string_view val){
370486
setPidFile(*this, val);
371487
}));
372488
parser.add(Option("--user", "-U").
373489
argName("USERNAME").
374-
help("the account to run networking code under. "
375-
"USERNAME can be either a plain user or in a groupname:username form").
490+
help(colorTagged(
491+
"the account to run networking code under. "
492+
"{arg}USERNAME{norm} can be either a plain user or in "
493+
"a {bold}groupname:username{norm} form")).
376494
occurs(Argum::neverOrOnce).
377495
handler([this](std::string_view val){
378496
setRunAs(*this, val);

0 commit comments

Comments
 (0)