From bffe74e67f69fb4ad928230e86ca776bd39ae432 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Sun, 1 Apr 2018 18:37:30 +0200 Subject: Implement combined flags (-xyz vs -x -y -z) and values (--foo=bar) support Both are enabled by default but can be disable with --no-combined-flags and --no-combined-values options. --- cli/options.cli | 18 ++++ cli/options.cxx | 218 ++++++++++++++++++++++++++++++++++++++++--------- cli/options.hxx | 20 +++++ cli/options.ixx | 36 ++++++++ cli/runtime-source.cxx | 51 ++++++++++-- cli/source.cxx | 132 ++++++++++++++++++++++++++---- 6 files changed, 414 insertions(+), 61 deletions(-) (limited to 'cli') diff --git a/cli/options.cli b/cli/options.cli index 97b63aa..e2d04ed 100644 --- a/cli/options.cli +++ b/cli/options.cli @@ -581,6 +581,24 @@ class options incremental option parsing." }; + bool --no-combined-flags + { + "Disable support for combining multiple single-character flags into a + single argument (the \cb{-xyz} form that is equivalent to \cb{-x} \cb{-y} + \cb{-z}). An argument is considered a combination of flags if it starts + with a single option prefix (\cb{--option-prefix}) and only contains + letters and digits. Note that an option with a value may not be part of + such a combination, not even if it is specified last." + } + + bool --no-combined-values + { + "Disable support for combining an option and its value into a single + argument with the assignment sign (the \c{\i{option}\b{=}\i{value}} + form). This functionality requires a non-empty option prefix + (\cb{--option-prefix})." + } + bool --include-with-brackets { "Use angle brackets (\cb{<>}) instead of quotes (\cb{\"\"}) in the diff --git a/cli/options.cxx b/cli/options.cxx index 149cc50..b64c26a 100644 --- a/cli/options.cxx +++ b/cli/options.cxx @@ -221,24 +221,45 @@ namespace cli // See if the next argument is the file option. // const char* a (base::peek ()); - const option_info* oi; + const option_info* oi = 0; + const char* ov = 0; - if (!skip_ && (oi = find (a))) + if (!skip_) { - base::next (); + if ((oi = find (a)) != 0) + { + base::next (); + + if (!base::more ()) + throw missing_value (a); - if (!base::more ()) - throw missing_value (oi->option); + ov = base::next (); + } + else if (std::strncmp (a, "-", 1) == 0) + { + if ((ov = std::strchr (a, '=')) != 0) + { + std::string o (a, 0, ov - a); + if ((oi = find (o.c_str ())) != 0) + { + base::next (); + ++ov; + } + } + } + } + if (oi != 0) + { if (oi->search_func != 0) { - std::string f (oi->search_func (base::next (), oi->arg)); + std::string f (oi->search_func (ov, oi->arg)); if (!f.empty ()) load (f); } else - load (base::next ()); + load (ov); if (!args_.empty ()) return true; @@ -681,6 +702,8 @@ options () option_separator_ ("--"), option_separator_specified_ (false), keep_separator_ (), + no_combined_flags_ (), + no_combined_values_ (), include_with_brackets_ (), include_prefix_ (), include_prefix_specified_ (false), @@ -817,6 +840,8 @@ options (int& argc, option_separator_ ("--"), option_separator_specified_ (false), keep_separator_ (), + no_combined_flags_ (), + no_combined_values_ (), include_with_brackets_ (), include_prefix_ (), include_prefix_specified_ (false), @@ -956,6 +981,8 @@ options (int start, option_separator_ ("--"), option_separator_specified_ (false), keep_separator_ (), + no_combined_flags_ (), + no_combined_values_ (), include_with_brackets_ (), include_prefix_ (), include_prefix_specified_ (false), @@ -1095,6 +1122,8 @@ options (int& argc, option_separator_ ("--"), option_separator_specified_ (false), keep_separator_ (), + no_combined_flags_ (), + no_combined_values_ (), include_with_brackets_ (), include_prefix_ (), include_prefix_specified_ (false), @@ -1236,6 +1265,8 @@ options (int start, option_separator_ ("--"), option_separator_specified_ (false), keep_separator_ (), + no_combined_flags_ (), + no_combined_values_ (), include_with_brackets_ (), include_prefix_ (), include_prefix_specified_ (false), @@ -1373,6 +1404,8 @@ options (::cli::scanner& s, option_separator_ ("--"), option_separator_specified_ (false), keep_separator_ (), + no_combined_flags_ (), + no_combined_values_ (), include_with_brackets_ (), include_prefix_ (), include_prefix_specified_ (false), @@ -1602,6 +1635,14 @@ print_usage (::std::ostream& os, ::cli::usage_para p) os << "--keep-separator Leave the option separator in the scanner." << ::std::endl; + os << "--no-combined-flags Disable support for combining multiple" << ::std::endl + << " single-character flags into a single argument (the" << ::std::endl + << " -xyz form that is equivalent to -x -y -z)." << ::std::endl; + + os << "--no-combined-values Disable support for combining an option and its" << ::std::endl + << " value into a single argument with the assignment" << ::std::endl + << " sign (the option=value form)." << ::std::endl; + os << "--include-with-brackets Use angle brackets (<>) instead of quotes (\"\") in" << ::std::endl << " the generated #include directives." << ::std::endl; @@ -1833,6 +1874,10 @@ struct _cli_options_map_init &options::option_separator_specified_ >; _cli_options_map_["--keep-separator"] = &::cli::thunk< options, bool, &options::keep_separator_ >; + _cli_options_map_["--no-combined-flags"] = + &::cli::thunk< options, bool, &options::no_combined_flags_ >; + _cli_options_map_["--no-combined-values"] = + &::cli::thunk< options, bool, &options::no_combined_values_ >; _cli_options_map_["--include-with-brackets"] = &::cli::thunk< options, bool, &options::include_with_brackets_ >; _cli_options_map_["--include-prefix"] = @@ -1871,6 +1916,10 @@ _parse (::cli::scanner& s, ::cli::unknown_mode opt_mode, ::cli::unknown_mode arg_mode) { + // Can't skip combined flags (--no-combined-flags). + // + assert (opt_mode != ::cli::unknown_mode::skip); + bool r = false; bool opt = true; @@ -1886,52 +1935,143 @@ _parse (::cli::scanner& s, continue; } - if (opt && _parse (o, s)) - r = true; - else if (opt && std::strncmp (o, "-", 1) == 0 && o[1] != '\0') + if (opt) { - switch (opt_mode) + if (_parse (o, s)) { - case ::cli::unknown_mode::skip: - { - s.skip (); - r = true; - continue; - } - case ::cli::unknown_mode::stop: - { - break; - } - case ::cli::unknown_mode::fail: - { - throw ::cli::unknown_option (o); - } + r = true; + continue; } - break; - } - else - { - switch (arg_mode) + if (std::strncmp (o, "-", 1) == 0 && o[1] != '\0') { - case ::cli::unknown_mode::skip: + // Handle combined option values. + // + std::string co; + if (const char* v = std::strchr (o, '=')) { - s.skip (); - r = true; - continue; + co.assign (o, 0, v - o); + ++v; + + int ac (2); + char* av[] = + { + const_cast (co.c_str ()), + const_cast (v) + }; + + ::cli::argv_scanner ns (0, ac, av); + + if (_parse (co.c_str (), ns)) + { + // Parsed the option but not its value? + // + if (ns.end () != 2) + throw ::cli::invalid_value (co, v); + + s.next (); + r = true; + continue; + } + else + { + // Set the unknown option and fall through. + // + o = co.c_str (); + } } - case ::cli::unknown_mode::stop: + + // Handle combined flags. + // + char cf[3]; { - break; + const char* p = o + 1; + for (; *p != '\0'; ++p) + { + if (!((*p >= 'a' && *p <= 'z') || + (*p >= 'A' && *p <= 'Z') || + (*p >= '0' && *p <= '9'))) + break; + } + + if (*p == '\0') + { + for (p = o + 1; *p != '\0'; ++p) + { + std::strcpy (cf, "-"); + cf[1] = *p; + cf[2] = '\0'; + + int ac (1); + char* av[] = + { + cf + }; + + ::cli::argv_scanner ns (0, ac, av); + + if (!_parse (cf, ns)) + break; + } + + if (*p == '\0') + { + // All handled. + // + s.next (); + r = true; + continue; + } + else + { + // Set the unknown option and fall through. + // + o = cf; + } + } } - case ::cli::unknown_mode::fail: + + switch (opt_mode) { - throw ::cli::unknown_argument (o); + case ::cli::unknown_mode::skip: + { + s.skip (); + r = true; + continue; + } + case ::cli::unknown_mode::stop: + { + break; + } + case ::cli::unknown_mode::fail: + { + throw ::cli::unknown_option (o); + } } + + break; } + } - break; + switch (arg_mode) + { + case ::cli::unknown_mode::skip: + { + s.skip (); + r = true; + continue; + } + case ::cli::unknown_mode::stop: + { + break; + } + case ::cli::unknown_mode::fail: + { + throw ::cli::unknown_argument (o); + } } + + break; } return r; diff --git a/cli/options.hxx b/cli/options.hxx index 8a93b43..dae58b6 100644 --- a/cli/options.hxx +++ b/cli/options.hxx @@ -1329,6 +1329,24 @@ class options keep_separator (const bool&); const bool& + no_combined_flags () const; + + bool& + no_combined_flags (); + + void + no_combined_flags (const bool&); + + const bool& + no_combined_values () const; + + bool& + no_combined_values (); + + void + no_combined_values (const bool&); + + const bool& include_with_brackets () const; bool& @@ -1534,6 +1552,8 @@ class options std::string option_separator_; bool option_separator_specified_; bool keep_separator_; + bool no_combined_flags_; + bool no_combined_values_; bool include_with_brackets_; std::string include_prefix_; bool include_prefix_specified_; diff --git a/cli/options.ixx b/cli/options.ixx index 634e7b7..5111030 100644 --- a/cli/options.ixx +++ b/cli/options.ixx @@ -2066,6 +2066,42 @@ keep_separator(const bool& x) } inline const bool& options:: +no_combined_flags () const +{ + return this->no_combined_flags_; +} + +inline bool& options:: +no_combined_flags () +{ + return this->no_combined_flags_; +} + +inline void options:: +no_combined_flags(const bool& x) +{ + this->no_combined_flags_ = x; +} + +inline const bool& options:: +no_combined_values () const +{ + return this->no_combined_values_; +} + +inline bool& options:: +no_combined_values () +{ + return this->no_combined_values_; +} + +inline void options:: +no_combined_values(const bool& x) +{ + this->no_combined_values_ = x; +} + +inline const bool& options:: include_with_brackets () const { return this->include_with_brackets_; diff --git a/cli/runtime-source.cxx b/cli/runtime-source.cxx index 7d50f24..b87f933 100644 --- a/cli/runtime-source.cxx +++ b/cli/runtime-source.cxx @@ -355,6 +355,9 @@ generate_runtime_source (context& ctx, bool complete) { bool sep (!ctx.opt_sep.empty ()); + const string& pfx (ctx.opt_prefix); + bool comb_values (!pfx.empty () && !ctx.options.no_combined_values ()); + os << "// argv_file_scanner" << endl << "//" << endl @@ -369,24 +372,60 @@ generate_runtime_source (context& ctx, bool complete) << "// See if the next argument is the file option." << endl << "//" << endl << "const char* a (base::peek ());" - << "const option_info* oi;" - << endl - << "if (" << (sep ? "!skip_ && " : "") << "(oi = find (a)))" + << "const option_info* oi = 0;" + << "const char* ov = 0;" + << endl; + + if (sep) + os << "if (!skip_)" + << "{"; + + os << "if ((oi = find (a)) != 0)" << "{" << "base::next ();" << endl << "if (!base::more ())" << endl - << "throw missing_value (oi->option);" + << "throw missing_value (a);" << endl + << "ov = base::next ();" + << "}"; + + // Handle the combined option/value (--foo=bar). See the option parsing + // implementation for details. + // + if (comb_values) + { + size_t n (pfx.size ()); + + os << "else if (std::strncmp (a, \"" << pfx << "\", " << + n << ") == 0)" // It looks like an option. + << "{" + << "if ((ov = std::strchr (a, '=')) != 0)" // Has '='. + << "{" + << "std::string o (a, 0, ov - a);" + << "if ((oi = find (o.c_str ())) != 0)" + << "{" + << "base::next ();" + << "++ov;" // That's the value. + << "}" + << "}" + << "}"; + } + + if (sep) + os << "}"; + + os << "if (oi != 0)" + << "{" << "if (oi->search_func != 0)" << "{" - << "std::string f (oi->search_func (base::next (), oi->arg));" + << "std::string f (oi->search_func (ov, oi->arg));" << endl << "if (!f.empty ())" << endl << "load (f);" << "}" << "else" << endl - << "load (base::next ());" + << "load (ov);" << endl << "if (!args_.empty ())" << endl << "return true;" diff --git a/cli/source.cxx b/cli/source.cxx index 37f0ef5..282d5e5 100644 --- a/cli/source.cxx +++ b/cli/source.cxx @@ -934,12 +934,22 @@ namespace bool pfx (!opt_prefix.empty ()); bool sep (!opt_sep.empty ()); + bool comb_flags (pfx && !options.no_combined_flags ()); + bool comb_values (pfx && !options.no_combined_values ()); + os << "bool " << name << "::" << endl << "_parse (" << cli << "::scanner& s," << endl << um << (pfx ? " opt_mode" : "") << "," << endl << um << " arg_mode)" - << "{" - << "bool r = false;"; + << "{"; + + if (comb_flags) + os << "// Can't skip combined flags (--no-combined-flags)." << endl + << "//" << endl + << "assert (opt_mode != " << cli << "::unknown_mode::skip);" + << endl; + + os << "bool r = false;"; if (sep) os << "bool opt = true;" // Still recognizing options. @@ -964,24 +974,114 @@ namespace os << "}"; } - os << "if (" << (sep ? "opt && " : "") << "_parse (o, s))" << endl - << "r = true;"; + if (sep) + os << "if (opt)" + << "{"; - // Unknown option. + // First try the argument as is. // + os << "if (_parse (o, s))" + << "{" + << "r = true;" + << "continue;" + << "}"; + if (pfx) { size_t n (opt_prefix.size ()); - os << "else if ("; + os << "if (std::strncmp (o, \"" << opt_prefix << "\", " << + n << ") == 0 && o[" << n << "] != '\\0')" + << "{"; + + if (comb_values) + { + os << "// Handle combined option values." << endl + << "//" << endl + << "std::string co;" // Need to live until next block. + << "if (const char* v = std::strchr (o, '='))" + << "{" + << "co.assign (o, 0, v - o);" + << "++v;" + << endl + << "int ac (2);" + << "char* av[] =" + << "{" + << "const_cast (co.c_str ())," << endl + << "const_cast (v)" + << "};" + << cli << "::argv_scanner ns (0, ac, av);" + << endl + << "if (_parse (co.c_str (), ns))" + << "{" + << "// Parsed the option but not its value?" << endl + << "//" << endl + << "if (ns.end () != 2)" << endl + << "throw " << cli << "::invalid_value (co, v);" + << endl + << "s.next ();" + << "r = true;" + << "continue;" + << "}" + << "else" + << "{" + << "// Set the unknown option and fall through." << endl + << "//" << endl + << "o = co.c_str ();" + << "}" + << "}"; + } - if (sep) - os << "opt && "; + if (comb_flags) + { + os << "// Handle combined flags." << endl + << "//" << endl + << "char cf[" << n + 2 << "];" // Need to live until next block. + << "{" + << "const char* p = o + " << n << ";" + << "for (; *p != '\\0'; ++p)" + << "{" + << "if (!((*p >= 'a' && *p <= 'z') ||" << endl + << "(*p >= 'A' && *p <= 'Z') ||" << endl + << "(*p >= '0' && *p <= '9')))" << endl + << "break;" + << "}" + << "if (*p == '\\0')" + << "{" + << "for (p = o + " << n << "; *p != '\\0'; ++p)" + << "{" + << "std::strcpy (cf, \"" << opt_prefix << "\");" + << "cf[" << n << "] = *p;" + << "cf[" << n + 1 << "] = '\\0';" + << endl + << "int ac (1);" + << "char* av[] = {cf};" + << cli << "::argv_scanner ns (0, ac, av);" + << endl + << "if (!_parse (cf, ns))" << endl + << "break;" + << "}" + << "if (*p == '\\0')" + << "{" + << "// All handled." << endl + << "//" << endl + << "s.next ();" + << "r = true;" + << "continue;" + << "}" + << "else" + << "{" + << "// Set the unknown option and fall through." << endl + << "//" << endl + << "o = cf;" + << "}" + << "}" + << "}"; + } - os << "std::strncmp (o, \"" << opt_prefix << "\", " << - n << ") == 0 && o[" << n << "] != '\\0')" - << "{" - << "switch (opt_mode)" + // Unknown option. + // + os << "switch (opt_mode)" << "{" << "case " << cli << "::unknown_mode::skip:" << endl << "{" @@ -1002,11 +1102,12 @@ namespace << "}"; } + if (sep) + os << "}"; + // Unknown argument. // - os << "else" - << "{" - << "switch (arg_mode)" + os << "switch (arg_mode)" << "{" << "case " << cli << "::unknown_mode::skip:" << endl << "{" @@ -1024,7 +1125,6 @@ namespace << "}" << "}" // switch << "break;" // The stop case. - << "}" << "}" // for << "return r;" -- cgit v1.1