From cd3758bb328ff425bb06f18c81d3c353b508a336 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Tue, 9 Nov 2021 13:01:13 +0200 Subject: Add --ascii-tree for translating UTF-8 tree(1) output to ASCII --- cli/cli/bootstrap/cli/options.cxx | 10 ++++++++ cli/cli/bootstrap/cli/options.hxx | 10 ++++++++ cli/cli/bootstrap/cli/options.ixx | 18 +++++++++++++ cli/cli/context.cxx | 53 +++++++++++++++++++++++++++++++++++++++ cli/cli/context.hxx | 9 ++++--- cli/cli/html.cxx | 10 +++++++- cli/cli/man.cxx | 10 +++++++- cli/cli/options.cli | 7 ++++++ cli/cli/source.cxx | 10 ++++++++ cli/cli/txt.cxx | 5 +++- cli/doc/bootstrap/cli.1 | 4 +++ cli/doc/bootstrap/cli.xhtml | 6 +++++ cli/tests/ascii-tree/buildfile | 8 ++++++ cli/tests/ascii-tree/testscript | 43 +++++++++++++++++++++++++++++++ 14 files changed, 197 insertions(+), 6 deletions(-) create mode 100644 cli/tests/ascii-tree/buildfile create mode 100644 cli/tests/ascii-tree/testscript (limited to 'cli') diff --git a/cli/cli/bootstrap/cli/options.cxx b/cli/cli/bootstrap/cli/options.cxx index 56dd24f..e66ae63 100644 --- a/cli/cli/bootstrap/cli/options.cxx +++ b/cli/cli/bootstrap/cli/options.cxx @@ -715,6 +715,7 @@ options () page_usage_specified_ (false), option_length_ (0), option_length_specified_ (false), + ascii_tree_ (), ansi_color_ (), exclude_base_ (), include_base_last_ (), @@ -856,6 +857,7 @@ options (int& argc, page_usage_specified_ (false), option_length_ (0), option_length_specified_ (false), + ascii_tree_ (), ansi_color_ (), exclude_base_ (), include_base_last_ (), @@ -1000,6 +1002,7 @@ options (int start, page_usage_specified_ (false), option_length_ (0), option_length_specified_ (false), + ascii_tree_ (), ansi_color_ (), exclude_base_ (), include_base_last_ (), @@ -1144,6 +1147,7 @@ options (int& argc, page_usage_specified_ (false), option_length_ (0), option_length_specified_ (false), + ascii_tree_ (), ansi_color_ (), exclude_base_ (), include_base_last_ (), @@ -1290,6 +1294,7 @@ options (int start, page_usage_specified_ (false), option_length_ (0), option_length_specified_ (false), + ascii_tree_ (), ansi_color_ (), exclude_base_ (), include_base_last_ (), @@ -1432,6 +1437,7 @@ options (::cli::scanner& s, page_usage_specified_ (false), option_length_ (0), option_length_specified_ (false), + ascii_tree_ (), ansi_color_ (), exclude_base_ (), include_base_last_ (), @@ -1613,6 +1619,8 @@ print_usage (::std::ostream& os, ::cli::usage_para p) os << "--option-length Indent option descriptions characters when" << ::std::endl << " printing usage." << ::std::endl; + os << "--ascii-tree Convert UTF-8 tree(1) output to ASCII." << ::std::endl; + os << "--ansi-color Use ANSI color escape sequences when printing" << ::std::endl << " usage." << ::std::endl; @@ -1859,6 +1867,8 @@ struct _cli_options_map_init _cli_options_map_["--option-length"] = &::cli::thunk< options, std::size_t, &options::option_length_, &options::option_length_specified_ >; + _cli_options_map_["--ascii-tree"] = + &::cli::thunk< options, bool, &options::ascii_tree_ >; _cli_options_map_["--ansi-color"] = &::cli::thunk< options, bool, &options::ansi_color_ >; _cli_options_map_["--exclude-base"] = diff --git a/cli/cli/bootstrap/cli/options.hxx b/cli/cli/bootstrap/cli/options.hxx index 35108fa..3ae86c6 100644 --- a/cli/cli/bootstrap/cli/options.hxx +++ b/cli/cli/bootstrap/cli/options.hxx @@ -785,6 +785,15 @@ class options option_length_specified (bool); const bool& + ascii_tree () const; + + bool& + ascii_tree (); + + void + ascii_tree (const bool&); + + const bool& ansi_color () const; bool& @@ -1566,6 +1575,7 @@ class options bool page_usage_specified_; std::size_t option_length_; bool option_length_specified_; + bool ascii_tree_; bool ansi_color_; bool exclude_base_; bool include_base_last_; diff --git a/cli/cli/bootstrap/cli/options.ixx b/cli/cli/bootstrap/cli/options.ixx index ee4cbdb..8c22d51 100644 --- a/cli/cli/bootstrap/cli/options.ixx +++ b/cli/cli/bootstrap/cli/options.ixx @@ -895,6 +895,24 @@ option_length_specified (bool x) } inline const bool& options:: +ascii_tree () const +{ + return this->ascii_tree_; +} + +inline bool& options:: +ascii_tree () +{ + return this->ascii_tree_; +} + +inline void options:: +ascii_tree (const bool& x) +{ + this->ascii_tree_ = x; +} + +inline const bool& options:: ansi_color () const { return this->ansi_color_; diff --git a/cli/cli/context.cxx b/cli/cli/context.cxx index 54bb988..2833861 100644 --- a/cli/cli/context.cxx +++ b/cli/cli/context.cxx @@ -296,6 +296,59 @@ process_link_target (const string& tg) return found ? r : tg; } +void context:: +preprocess_ascii_tree (string& s) +{ + // tree --charset=UTF-8 uses the following box-drawing characters (see + // color.c): + // + // CHAR UTF-8 ASCII + //---------------------------- + // + // | E29482 | + // + // -- E29480 - + // + // |- E2949C | + // + // |_ E29494 ` + // + // C2A0 + // + // Note that here we rely on the fact that neither E2 nor C2 can appear as + // continuation bytes. + // + for (size_t i (0); i != s.size (); ++i) + { + i = s.find_first_of ("\xE2\xC2", i); + + if (i == string::npos) + break; + + if (s[i] == '\xE2') + { + if (s[i + 1] == '\x94') + { + const char* r; + switch (s[i + 2]) + { + case '\x80': r = "-"; break; + case '\x82': + case '\x9c': r = "|"; break; + case '\x94': r = "`"; break; + default: continue; + } + + s.replace (i, 3, r); + } + } + else + { + if (s[i + 1] == '\xA0') + s.replace (i, 2, " "); + } + } +} string context:: translate_arg (string const& s, std::set& set) diff --git a/cli/cli/context.hxx b/cli/cli/context.hxx index 66dcb24..19bfa51 100644 --- a/cli/cli/context.hxx +++ b/cli/cli/context.hxx @@ -153,10 +153,13 @@ public: string process_link_target (const string&); - // Translate and format the documentation string. Translate converts - // the -style constructs to \i{arg}. Format converts the string - // to the output format. + // Preprocess, translate, and format the documentation string. Translate + // converts the -style constructs to \i{arg}. Format converts the + // string to the output format. // + static void + preprocess_ascii_tree (string&); + static string translate_arg (string const&, std::set&); diff --git a/cli/cli/html.cxx b/cli/cli/html.cxx index b374b91..48eb5e3 100644 --- a/cli/cli/html.cxx +++ b/cli/cli/html.cxx @@ -127,13 +127,16 @@ namespace // n > 2 - arg string, short string, long string // size_t n (ds.size ()); - const string& d ( + string d ( n == 1 ? (cd_ == cd_short ? first_sentence (ds[0]) : ds[0]) : (n == 2 ? (cd_ == cd_short ? first_sentence (ds[1]) : ds[1]) : ds[cd_ == cd_short ? 1 : 2])); + if (options.ascii_tree ()) + preprocess_ascii_tree (d); + std::set arg_set; if (n > 1) translate_arg (ds[0], arg_set); @@ -211,6 +214,8 @@ namespace if (type != "bool" || doc.size () >= 3) { + // Note: we naturally assume this doesn't need --ascii-tree treatment. + // string s ( translate_arg ( doc.size () > 0 ? doc[0] : string (""), arg_set)); @@ -236,6 +241,9 @@ namespace d = (cd_ == cd_short ? first_sentence (doc[1]) : doc[1]); } + if (options.ascii_tree ()) + preprocess_ascii_tree (d); + // Format the documentation string. // d = format (o.scope (), escape_html (translate (d, arg_set)), false); diff --git a/cli/cli/man.cxx b/cli/cli/man.cxx index df703e8..d446b2a 100644 --- a/cli/cli/man.cxx +++ b/cli/cli/man.cxx @@ -91,13 +91,16 @@ namespace // n > 2 - arg string, short string, long string // size_t n (ds.size ()); - const string& d ( + string d ( n == 1 ? (cd_ == cd_short ? first_sentence (ds[0]) : ds[0]) : (n == 2 ? (cd_ == cd_short ? first_sentence (ds[1]) : ds[1]) : ds[cd_ == cd_short ? 1 : 2])); + if (options.ascii_tree ()) + preprocess_ascii_tree (d); + std::set arg_set; if (n > 1) translate_arg (ds[0], arg_set); @@ -149,6 +152,8 @@ namespace if (type != "bool" || doc.size () >= 3) { + // Note: we naturally assume this doesn't need --ascii-tree treatment. + // string s ( translate_arg ( doc.size () > 0 ? doc[0] : string (""), arg_set)); @@ -174,6 +179,9 @@ namespace d = (cd_ == cd_short ? first_sentence (doc[1]) : doc[1]); } + if (options.ascii_tree ()) + preprocess_ascii_tree (d); + // Format the documentation string. // d = format (o.scope (), translate (d, arg_set), false); diff --git a/cli/cli/options.cli b/cli/cli/options.cli index 9273845..3a3089b 100644 --- a/cli/cli/options.cli +++ b/cli/cli/options.cli @@ -257,6 +257,13 @@ class options files, and would like their usage to have the same indentation level." }; + bool --ascii-tree + { + "Convert UTF-8 \cb{tree(1)} output to ASCII. Specifically, box-drawing + characters used in the \cb{--charset=UTF-8} output are replaced with + ASCII characters used in the \cb{--charset=ASCII} output." + }; + bool --ansi-color { "Use ANSI color escape sequences when printing usage. By \"color\" we diff --git a/cli/cli/source.cxx b/cli/cli/source.cxx index b6df839..1b9e832 100644 --- a/cli/cli/source.cxx +++ b/cli/cli/source.cxx @@ -278,6 +278,9 @@ namespace : (n == 1 ? ds[0] : ds[1]); // Else, use common (no first sentence). } + if (options.ascii_tree ()) + preprocess_ascii_tree (d); + std::set arg_set; if (n > 1 && options.ansi_color ()) translate_arg (ds[0], arg_set); @@ -345,6 +348,8 @@ namespace { l++; // ' ' seperator + // Note: we naturally assume this doesn't need --ascii-tree treatment. + // string s (doc.size () > 0 ? doc[0] : string ("")); if (options.ansi_color ()) @@ -439,6 +444,8 @@ namespace os << ' '; l++; + // Note: we naturally assume this doesn't need --ascii-tree treatment. + // string s (doc.size () > 0 ? doc[0] : string ("")); if (color) @@ -475,6 +482,9 @@ namespace } } + if (options.ascii_tree ()) + preprocess_ascii_tree (d); + // Format the documentation string. // if (color) diff --git a/cli/cli/txt.cxx b/cli/cli/txt.cxx index 16de45a..1e9094d 100644 --- a/cli/cli/txt.cxx +++ b/cli/cli/txt.cxx @@ -164,13 +164,16 @@ namespace // n > 2 - arg string, short string, long string // size_t n (ds.size ()); - const string& d ( + string d ( n == 1 ? (cd_ == cd_short ? first_sentence (ds[0]) : ds[0]) : (n == 2 ? (cd_ == cd_short ? first_sentence (ds[1]) : ds[1]) : ds[cd_ == cd_short ? 1 : 2])); + if (options.ascii_tree ()) + preprocess_ascii_tree (d); + std::set arg_set; if (n > 1 && options.ansi_color ()) translate_arg (ds[0], arg_set); diff --git a/cli/doc/bootstrap/cli.1 b/cli/doc/bootstrap/cli.1 index d684d30..1cced65 100644 --- a/cli/doc/bootstrap/cli.1 +++ b/cli/doc/bootstrap/cli.1 @@ -206,6 +206,10 @@ the long usage function has the \fB*long_usage()\fR suffix\. 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--ascii-tree\fR" +Convert UTF-8 \fBtree(1)\fR output to ASCII\. Specifically, box-drawing +characters used in the \fB--charset=UTF-8\fR output are replaced with ASCII +characters used in the --charset=ASCII\fR output\. .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 diff --git a/cli/doc/bootstrap/cli.xhtml b/cli/doc/bootstrap/cli.xhtml index b62bf57..9ee5c16 100644 --- a/cli/doc/bootstrap/cli.xhtml +++ b/cli/doc/bootstrap/cli.xhtml @@ -258,6 +258,12 @@ arg+{ --foo } # 'arg+{' ... potentially in separate files, and would like their usage to have the same indentation level. +
--ascii-tree
+
Convert UTF-8 tree(1) output to ASCII. + Specifically, box-drawing characters used in the + --charset=UTF-8 output are replaced with ASCII + characters used in the --charset=ASCII output.
+
--ansi-color
Use ANSI color escape sequences when printing usage. By "color" we really only mean the bold and underline modifiers. Note that Windows diff --git a/cli/tests/ascii-tree/buildfile b/cli/tests/ascii-tree/buildfile new file mode 100644 index 0000000..6450286 --- /dev/null +++ b/cli/tests/ascii-tree/buildfile @@ -0,0 +1,8 @@ +# file : tests/ascii-tree/buildfile +# license : MIT; see accompanying LICENSE file + +import! [metadata] cli = cli%exe{cli} + +./: testscript $cli + +testscript{*}: test = $cli diff --git a/cli/tests/ascii-tree/testscript b/cli/tests/ascii-tree/testscript new file mode 100644 index 0000000..f0546f6 --- /dev/null +++ b/cli/tests/ascii-tree/testscript @@ -0,0 +1,43 @@ +# file : tests/ascii-tree/testscript +# license : MIT; see accompanying LICENSE file + +: basics +: +cat <=test.cli; +" +\ +hello/ +├── build/ +│   ├── bootstrap.build +│   └── root.build +├── hello/ +│   ├── sub/ +│   │   ├── bar +│   │   └── foo +│   ├── buildfile +│   ├── hello.cxx +│   └── testscript +├── buildfile +├── manifest +├── README.md +└── repositories.manifest +\ +" +EOI +$* --generate-txt --ascii-tree --stdout test.cli >>EOO +hello/ +|-- build/ +| |-- bootstrap.build +| `-- root.build +|-- hello/ +| |-- sub/ +| | |-- bar +| | `-- foo +| |-- buildfile +| |-- hello.cxx +| `-- testscript +|-- buildfile +|-- manifest +|-- README.md +`-- repositories.manifest +EOO -- cgit v1.1