@@ -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+
200301void 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