From 6280033ac4d3d3646c4c512a1c852c9c8f088f80 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Wed, 18 Nov 2015 15:35:45 +0200 Subject: Add support for ANSI colorization of usage output --- cli/context.cxx | 40 +++++++++++++++++++++++--- cli/options.cli | 9 ++++++ cli/options.cxx | 12 +++++++- cli/options.hxx | 4 +++ cli/options.ixx | 6 ++++ cli/source.cxx | 87 +++++++++++++++++++++++++++++++++++++++++++++------------ doc/cli.1 | 6 ++++ doc/cli.xhtml | 7 +++++ 8 files changed, 148 insertions(+), 23 deletions(-) diff --git a/cli/context.cxx b/cli/context.cxx index d41bb3c..7459406 100644 --- a/cli/context.cxx +++ b/cli/context.cxx @@ -614,8 +614,23 @@ format_line (output_type ot, string& r, const char* s, size_t n) { case ot_plain: { - if (b & code) - r += "'"; + if ((b & link) == 0) + { + if (options.ansi_color ()) + { + if (b & bold) + r += "\033[1m"; + + if (b & itlc) + r += "\033[4m"; + } + else + { + if (b & code) + r += "'"; + } + } + break; } case ot_html: @@ -713,8 +728,25 @@ format_line (output_type ot, string& r, const char* s, size_t n) } else { - if (b & code) - r += "'"; + if (options.ansi_color ()) + { + // While there are codes to turn off bold (22) and + // underline (24), it is not clear how widely they + // are supported. + // + r += "\033[0m"; // Clear all. + + if (eb & bold) + r += "\033[1m"; + + if (eb & itlc) + r += "\033[4m"; + } + else + { + if (b & code) + r += "'"; + } } break; diff --git a/cli/options.cli b/cli/options.cli index 841081d..4972ba9 100644 --- a/cli/options.cli +++ b/cli/options.cli @@ -139,6 +139,15 @@ class options files, and would like their usage to have the same indentation level." }; + bool --ansi-color + { + "Use ANSI color escape sequences when printing usage. By \"color\" we + really only mean the bold and underline modifiers. Note that Windows + console does not recognize ANSI escape sequences and will display + them as garbage. However, if you pipe such output through \c{\b{less}(1)}, + it will display them correctly." + }; + bool --exclude-base { "Exclude base class information from usage and documentation." diff --git a/cli/options.cxx b/cli/options.cxx index c225d49..d6b0e84 100644 --- a/cli/options.cxx +++ b/cli/options.cxx @@ -506,7 +506,6 @@ namespace cli const_cast (o), 0 }; - if (!kstr.empty ()) { av[1] = const_cast (kstr.c_str ()); @@ -565,6 +564,7 @@ options () long_usage_ (), short_usage_ (), option_length_ (0), + ansi_color_ (), exclude_base_ (), class__ (), docvar_ (), @@ -630,6 +630,7 @@ options (int& argc, long_usage_ (), short_usage_ (), option_length_ (0), + ansi_color_ (), exclude_base_ (), class__ (), docvar_ (), @@ -698,6 +699,7 @@ options (int start, long_usage_ (), short_usage_ (), option_length_ (0), + ansi_color_ (), exclude_base_ (), class__ (), docvar_ (), @@ -766,6 +768,7 @@ options (int& argc, long_usage_ (), short_usage_ (), option_length_ (0), + ansi_color_ (), exclude_base_ (), class__ (), docvar_ (), @@ -836,6 +839,7 @@ options (int start, long_usage_ (), short_usage_ (), option_length_ (0), + ansi_color_ (), exclude_base_ (), class__ (), docvar_ (), @@ -902,6 +906,7 @@ options (::cli::scanner& s, long_usage_ (), short_usage_ (), option_length_ (0), + ansi_color_ (), exclude_base_ (), class__ (), docvar_ (), @@ -1000,6 +1005,9 @@ print_usage (::std::ostream& os) os << "--option-length Indent option descriptions characters when" << ::std::endl << " printing usage." << ::std::endl; + os << "--ansi-color Use ANSI color escape sequences when printing" << ::std::endl + << " usage." << ::std::endl; + os << "--exclude-base Exclude base class information from usage and" << ::std::endl << " documentation." << ::std::endl; @@ -1164,6 +1172,8 @@ struct _cli_options_map_init &::cli::thunk< options, bool, &options::short_usage_ >; _cli_options_map_["--option-length"] = &::cli::thunk< options, std::size_t, &options::option_length_ >; + _cli_options_map_["--ansi-color"] = + &::cli::thunk< options, bool, &options::ansi_color_ >; _cli_options_map_["--exclude-base"] = &::cli::thunk< options, bool, &options::exclude_base_ >; _cli_options_map_["--class"] = diff --git a/cli/options.hxx b/cli/options.hxx index ecafb22..933a41a 100644 --- a/cli/options.hxx +++ b/cli/options.hxx @@ -426,6 +426,9 @@ class options option_length () const; const bool& + ansi_color () const; + + const bool& exclude_base () const; const std::vector& @@ -569,6 +572,7 @@ class options bool long_usage_; bool short_usage_; std::size_t option_length_; + bool ansi_color_; bool exclude_base_; std::vector class__; std::map docvar_; diff --git a/cli/options.ixx b/cli/options.ixx index b146a60..c4a3220 100644 --- a/cli/options.ixx +++ b/cli/options.ixx @@ -330,6 +330,12 @@ option_length () const } inline const bool& options:: +ansi_color () const +{ + return this->ansi_color_; +} + +inline const bool& options:: exclude_base () const { return this->exclude_base_; diff --git a/cli/source.cxx b/cli/source.cxx index 971c639..d58f46b 100644 --- a/cli/source.cxx +++ b/cli/source.cxx @@ -7,7 +7,7 @@ #include "source.hxx" -using std::cerr; +using namespace std; namespace { @@ -171,8 +171,36 @@ namespace } }; + + // Return the number of "text characters", ignoring any escape sequences + // (e.g., ANSI color). // - // + static size_t + text_size (const string& s, size_t p = 0, size_t n = string::npos) + { + size_t r (0); + + n = n == string::npos ? s.size () : n + p; + + // The start position (p) might be pointing half-way into the + // escape sequence. So we always have to scan from the start. + // + for (size_t i (0), m (s.size ()); i < n; ++i) + { + if (s[i] == '\033') // ANSI escape: "\033[Nm" + { + i += 3; + assert (i < m && s[i] == 'm'); + continue; + } + + if (i >= p) + ++r; + } + + return r; + } + struct option_length: traversal::option, context { option_length (context& c, size_t& l, type*& o) @@ -190,6 +218,8 @@ namespace if (options.suppress_undocumented () && doc.empty ()) return; + bool color (options.ansi_color ()); + size_t l (0); names& n (o.named ()); @@ -207,10 +237,15 @@ namespace { l++; // ' ' seperator - if (doc.size () > 0) - l += format (ot_plain, doc[0], false).size (); - else - l += 5; // + string s (doc.size () > 0 ? doc[0] : string ("")); + + if (color) + { + std::set arg_set; + s = translate_arg (s, arg_set); + } + + l += text_size (format (ot_plain, s, false)); } if (l > length_) @@ -242,6 +277,8 @@ namespace if (options.suppress_undocumented () && doc.empty ()) return; + bool color (options.ansi_color ()); + size_t l (0); names& n (o.named ()); @@ -255,28 +292,34 @@ namespace l++; } + if (color) + os << "\\033[1m"; // Bold. + os << escape_str (*i); + + if (color) + os << "\\033[0m"; + l += i->size (); } string type (o.type ().name ()); + std::set arg_set; if (type != "bool" || doc.size () >= 3) { os << ' '; l++; - if (doc.size () > 0) - { - string s (format (ot_plain, doc[0], false)); - os << escape_str (s); - l += s.size (); - } - else - { - os << ""; - l += 5; - } + string s (doc.size () > 0 ? doc[0] : string ("")); + + if (color) + s = translate_arg (s, arg_set); + + s = format (ot_plain, s, false); + + os << escape_str (s); + l += text_size (s); } // Figure out which documentation string we should use. @@ -306,6 +349,9 @@ namespace // Format the documentation string. // + if (color) + d = translate (d, arg_set); + d = format (ot_plain, d, false); if (!d.empty ()) @@ -323,7 +369,7 @@ namespace // we get the same output on Windows (which has two characters for // a newline). // - if (d[i] == '\n' || i - b == 78 - length_) + if (d[i] == '\n' || text_size (d, b, i - b) == 78 - length_) { if (b != 0) // Not a first line. { @@ -423,6 +469,11 @@ namespace r += "\\\""; break; } + case '\033': + { + r += "\\033"; + break; + } default: { r += s[i]; diff --git a/doc/cli.1 b/doc/cli.1 index 88f6be0..663fdaf 100644 --- a/doc/cli.1 +++ b/doc/cli.1 @@ -120,6 +120,12 @@ provided\. Indent option descriptions \fIlen\fR characters when printing usage\. This is useful when you have multiple options classes, potentially in separate files, and would like their usage to have the same indentation level\. +.IP "\fB--ansi-color\fR" +Use ANSI color escape sequences when printing usage\. By "color" we really +only mean the bold and underline modifiers\. Note that Windows console does +not recognize ANSI escape sequences and will display them as garbage\. +However, if you pipe such output through \fBless\fR(1)\fR, it will display +them correctly\. .IP "\fB--exclude-base\fR" Exclude base class information from usage and documentation\. .IP "\fB--class\fR \fIfq-name\fR" diff --git a/doc/cli.xhtml b/doc/cli.xhtml index e2baa6b..305087c 100644 --- a/doc/cli.xhtml +++ b/doc/cli.xhtml @@ -165,6 +165,13 @@ separate files, and would like their usage to have the same indentation level. +
--ansi-color
+
Use ANSI color escape sequences when printing usage. By "color" we + really only mean the bold and underline modifiers. Note that Windows + console does not recognize ANSI escape sequences and will display them as + garbage. However, if you pipe such output through + less(1), it will display them correctly.
+
--exclude-base
Exclude base class information from usage and documentation.
-- cgit v1.1