summaryrefslogtreecommitdiff
path: root/cli
diff options
context:
space:
mode:
Diffstat (limited to 'cli')
-rw-r--r--cli/options.cli18
-rw-r--r--cli/options.cxx218
-rw-r--r--cli/options.hxx20
-rw-r--r--cli/options.ixx36
-rw-r--r--cli/runtime-source.cxx51
-rw-r--r--cli/source.cxx132
6 files changed, 414 insertions, 61 deletions
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<char*> (co.c_str ()),
+ const_cast<char*> (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<char*> (co.c_str ())," << endl
+ << "const_cast<char*> (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;"