From f27f579358d2c12fc3926bfd5bb95ef3e08ca6a7 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Tue, 2 Apr 2019 15:09:44 +0200 Subject: Handle combined option values in argv_file_scanner Turns out we cannot just pass them along as combined because of quoting. While at it, also add support for quoting non-option arguments. --- cli/options.cli | 18 +++---- cli/options.cxx | 80 ++++++++++++++++------------- cli/runtime-source.cxx | 134 +++++++++++++++++++++++++++++++------------------ 3 files changed, 140 insertions(+), 92 deletions(-) (limited to 'cli') diff --git a/cli/options.cli b/cli/options.cli index 8781bc6..9af36cd 100644 --- a/cli/options.cli +++ b/cli/options.cli @@ -633,15 +633,15 @@ class options std::string --options-file { "", - "Read additional options from with each option appearing on a - separate line optionally followed by space and an option value. Empty - lines and lines starting with \cb{#} are ignored. Option values can - be enclosed in double (\cb{\"}) or single (\cb{'}) quotes to preserve - leading and trailing whitespaces as well as to specify empty values. - If the value itself contains trailing or leading quotes, enclose it - with an extra pair of quotes, for example \cb{'\"x\"'}. Non-leading - and non-trailing quotes are interpreted as being part of the option - value. + "Read additional options from . Each option should appear on a + separate line optionally followed by space or equal sign (\cb{=}) and an + option value. Empty lines and lines starting with \cb{#} are ignored. + Option values can be enclosed in double (\cb{\"}) or single (\cb{'}) + quotes to preserve leading and trailing whitespaces as well as to specify + empty values. If the value itself contains trailing or leading quotes, + enclose it with an extra pair of quotes, for example \cb{'\"x\"'}. + Non-leading and non-trailing quotes are interpreted as being part of the + option value. The semantics of providing options in a file is equivalent to providing the same set of options in the same order on the command line at the diff --git a/cli/options.cxx b/cli/options.cxx index 809b588..bb102e0 100644 --- a/cli/options.cxx +++ b/cli/options.cxx @@ -372,45 +372,59 @@ namespace cli if (line.empty () || line[0] == '#') continue; - string::size_type p (line.find (' ')); - - if (p == string::npos) + string::size_type p (string::npos); + if (line.compare (0, 1, "-") == 0) { - if (!skip_) - skip_ = (line == "--"); + p = line.find (' '); - args_.push_back (line); + string::size_type q (line.find ('=')); + if (q != string::npos && q < p) + p = q; } - else + + string s1; + if (p != string::npos) { - string s1 (line, 0, p); + s1.assign (line, 0, p); // Skip leading whitespaces in the argument. // - n = line.size (); - for (++p; p < n; ++p) + if (line[p] == '=') + ++p; + else { - char c (line[p]); - - if (c != ' ' && c != '\t' && c != '\r') - break; + n = line.size (); + for (++p; p < n; ++p) + { + char c (line[p]); + if (c != ' ' && c != '\t' && c != '\r') + break; + } } + } + else if (!skip_) + skip_ = (line == "--"); - string s2 (line, p); + string s2 (line, p != string::npos ? p : 0); - // If the string is wrapped in quotes, remove them. - // - n = s2.size (); - char cf (s2[0]), cl (s2[n - 1]); + // If the string (which is an option value or argument) is + // wrapped in quotes, remove them. + // + n = s2.size (); + char cf (s2[0]), cl (s2[n - 1]); - if (cf == '"' || cf == '\'' || cl == '"' || cl == '\'') - { - if (n == 1 || cf != cl) - throw unmatched_quote (s2); + if (cf == '"' || cf == '\'' || cl == '"' || cl == '\'') + { + if (n == 1 || cf != cl) + throw unmatched_quote (s2); - s2 = string (s2, 1, n - 2); - } + s2 = string (s2, 1, n - 2); + } + if (!s1.empty ()) + { + // See if this is another file option. + // const option_info* oi; if (!skip_ && (oi = find (s1.c_str ()))) { @@ -420,19 +434,19 @@ namespace cli if (oi->search_func != 0) { std::string f (oi->search_func (s2.c_str (), oi->arg)); - if (!f.empty ()) load (f); } else load (s2); + + continue; } - else - { - args_.push_back (s1); - args_.push_back (s2); - } + + args_.push_back (s1); } + + args_.push_back (s2); } } @@ -1659,9 +1673,7 @@ print_usage (::std::ostream& os, ::cli::usage_para p) << " the list of names that should not be used as" << ::std::endl << " identifiers." << ::std::endl; - os << "--options-file Read additional options from with each" << ::std::endl - << " option appearing on a separate line optionally" << ::std::endl - << " followed by space and an option value." << ::std::endl; + os << "--options-file Read additional options from ." << ::std::endl; p = ::cli::usage_para::option; diff --git a/cli/runtime-source.cxx b/cli/runtime-source.cxx index f9248d9..778bed4 100644 --- a/cli/runtime-source.cxx +++ b/cli/runtime-source.cxx @@ -359,7 +359,8 @@ 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 ()); + size_t pfx_n (pfx.size ()); + bool comb_values (pfx_n != 0 && !ctx.options.no_combined_values ()); os << "// argv_file_scanner" << endl << "//" << endl @@ -398,10 +399,8 @@ generate_runtime_source (context& ctx, bool complete) // if (comb_values) { - size_t n (pfx.size ()); - os << "else if (std::strncmp (a, \"" << pfx << "\", " << - n << ") == 0)" // It looks like an option. + pfx_n << ") == 0)" // It looks like an option. << "{" << "if ((ov = std::strchr (a, '=')) != 0)" // Has '='. << "{" @@ -537,67 +536,104 @@ generate_runtime_source (context& ctx, bool complete) << "if (line.empty () || line[0] == '#')" << endl << "continue;" << endl - << "string::size_type p (line.find (' '));" - << endl - << "if (p == string::npos)" - << "{"; - if (sep) - os << "if (!skip_)" << endl - << "skip_ = (line == \"" << ctx.opt_sep << "\");" - << endl; - os << "args_.push_back (line);" - << "}" - << "else" + << "string::size_type p (string::npos);"; + + // If we have the option prefix, then only consider lines that start + // with that as options. + // + if (pfx_n != 0) + os << "if (line.compare (0, " << pfx_n << ", \"" << pfx << "\") == 0)" + << "{"; + + os << "p = line.find (' ');"; + + // Handle the combined option/value (--foo=bar). This is a bit tricky + // since the equal sign can be part of the value (--foo bar=baz). + // + // Note that we cannot just pass it along as combined because of + // quoting (--foo="'bar baz'"). + // + if (comb_values) + { + os << endl + << "string::size_type q (line.find ('='));" + << "if (q != string::npos && q < p)" << endl + << "p = q;"; + } + + if (pfx_n != 0) + os << "}"; + else + os << endl; + + os << "string s1;" + << "if (p != string::npos)" << "{" - << "string s1 (line, 0, p);" + << "s1.assign (line, 0, p);" << endl << "// Skip leading whitespaces in the argument." << endl - << "//" << endl - << "n = line.size ();" - << "for (++p; p < n; ++p)" - << "{" - << "char c (line[p]);" - << endl - << "if (c != ' ' && c != '\\t' && c != '\\r')" << endl - << "break;" - << "}" - << "string s2 (line, p);" + << "//" << endl; + if (comb_values) + os << "if (line[p] == '=')" << endl // Keep whitespaces after '='. + << "++p;" + << "else" + << "{"; + os << "n = line.size ();" + << "for (++p; p < n; ++p)" + << "{" + << "char c (line[p]);" + << "if (c != ' ' && c != '\\t' && c != '\\r')" << endl + << "break;" + << "}"; + if (comb_values) + os << "}"; + os << "}"; + if (sep) + os << "else if (!skip_)" << endl + << "skip_ = (line == \"" << ctx.opt_sep << "\");" + << endl; + + os << "string s2 (line, p != string::npos ? p : 0);" << endl - << "// If the string is wrapped in quotes, remove them." << endl + << "// If the string (which is an option value or argument) is" << endl + << "// wrapped in quotes, remove them." << endl << "//" << endl << "n = s2.size ();" << "char cf (s2[0]), cl (s2[n - 1]);" << endl << "if (cf == '\"' || cf == '\\'' || cl == '\"' || cl == '\\'')" << "{" - << "if (n == 1 || cf != cl)" << endl - << "throw unmatched_quote (s2);" + << "if (n == 1 || cf != cl)" << endl + << "throw unmatched_quote (s2);" << endl - << "s2 = string (s2, 1, n - 2);" - << "}" - << "const option_info* oi;" - << "if (" << (sep ? "!skip_ && " : "") << - "(oi = find (s1.c_str ())))" << endl + << "s2 = string (s2, 1, n - 2);" + << "}"; + + os << "if (!s1.empty ())" << "{" - << "if (s2.empty ())" << endl - << "throw missing_value (oi->option);" + << "// See if this is another file option." << endl + << "//" << endl + << "const option_info* oi;" + << "if (" << (sep ? "!skip_ && " : "") << + "(oi = find (s1.c_str ())))" << endl + << "{" + << "if (s2.empty ())" << endl + << "throw missing_value (oi->option);" << endl - << "if (oi->search_func != 0)" - << "{" - << "std::string f (oi->search_func (s2.c_str (), oi->arg));" + << "if (oi->search_func != 0)" + << "{" + << "std::string f (oi->search_func (s2.c_str (), oi->arg));" + << "if (!f.empty ())" << endl + << "load (f);" + << "}" + << "else" << endl + << "load (s2);" << endl - << "if (!f.empty ())" << endl - << "load (f);" - << "}" - << "else" << endl - << "load (s2);" + << "continue;" + << "}" + << "args_.push_back (s1);" << "}" - << "else" - << "{" - << "args_.push_back (s1);" << "args_.push_back (s2);" - << "}" - << "}" << "}" // while << "}"; } -- cgit v1.1