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 +++++++++++++++++++++++++++++++++++++++++++++------------ 6 files changed, 135 insertions(+), 23 deletions(-) (limited to 'cli') 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]; -- cgit v1.1