aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--libbutl/command.cxx150
-rw-r--r--libbutl/command.mxx34
-rw-r--r--tests/command/testscript6
3 files changed, 124 insertions, 66 deletions
diff --git a/libbutl/command.cxx b/libbutl/command.cxx
index fadd617..c23dfd5 100644
--- a/libbutl/command.cxx
+++ b/libbutl/command.cxx
@@ -48,6 +48,82 @@ using namespace std;
namespace butl
{
+ string
+ command_substitute (const string& s, size_t sp,
+ const function<command_substitution_callback>& sc,
+ char open, char close)
+ {
+ string r;
+ size_t p (0); // Current parsing position.
+
+ for (size_t n (s.size ()); sp != string::npos; sp = s.find (open, ++p))
+ {
+ // Append the source string fraction preceding this substitution.
+ //
+ r.append (s, p, sp - p);
+
+ // See if this is an escape of the opening substitution character
+ // (adjacent characters).
+ //
+ if (++sp != n && s[sp] == open)
+ {
+ p = sp;
+ r += open;
+ continue;
+ }
+
+ // In sp we now have the start of the variable name. Find its end.
+ //
+ p = s.find (close, sp);
+
+ // Verify that the variable name is properly terminated, not empty, and
+ // doesn't contain whitespaces.
+ //
+ if (p == string::npos)
+ throw invalid_argument (string ("unmatched substitution character '") +
+ open + "'");
+
+ if (p == sp)
+ throw invalid_argument ("empty substitution variable");
+
+ string vn (s, sp, p - sp);
+
+ if (vn.find_first_of (" \t") != string::npos)
+ throw invalid_argument ("whitespace in substitution variable '" +
+ vn + "'");
+
+ // Find the variable and append its value or fail if it's unknown.
+ //
+ if (!sc (vn, r))
+ throw invalid_argument ("unknown substitution variable '" + vn + "'");
+ }
+
+ // Append the source string tail following the last substitution.
+ //
+ r.append (s.begin () + p, s.end ());
+
+ return r;
+ }
+
+ string
+ command_substitute (const string& s, size_t sp,
+ const command_substitution_map& sm,
+ char o, char c)
+ {
+ return command_substitute (
+ s,
+ sp,
+ [&sm] (const string& vn, string& r)
+ {
+ auto i (sm.find (vn));
+ if (i == sm.end ())
+ return false;
+ r += i->second;
+ return true;
+ },
+ o, c);
+ }
+
process_exit
command_run (const string& cmd_str,
const optional<process_env>& env,
@@ -63,72 +139,19 @@ namespace butl
vector<string> cmd (
string_parser::parse_quoted (cmd_str, true /* unquote */));
- auto bad_arg = [] (const string& d) {throw invalid_argument (d);};
-
if (cmd.empty ())
- bad_arg ("no program path specified");
+ throw invalid_argument ("no program path specified");
// Perform substitutions in a string. Throw invalid_argument for a
- // malformed substitution or an unknown variable.
+ // malformed substitution or an unknown variable name.
//
- auto substitute = [&substitutions, subst, &bad_arg] (string&& s)
+ auto substitute = [&substitutions, subst] (string&& s)
{
- if (!substitutions)
- return move (s);
-
- string r;
- size_t p (0); // Current parsing position.
-
- for (size_t sp; (sp = s.find (subst, p)) != string::npos; ++p)
- {
- // Append the source string fraction preceding this substitution.
- //
- r.append (s, p, sp - p);
-
- ++sp; // Start of the variable name.
- p = s.find (subst, sp); // End of the variable name.
-
- // Unescape the substitution character (adjacent substitution
- // characters).
- //
- if (p == sp)
- {
- r += subst;
- continue;
- }
-
- // Verify that the variable name is properly terminated and doesn't
- // contain whitespaces.
- //
- if (p == string::npos)
- bad_arg (string ("unmatched substitution character '") + subst +
- "' in '" + s + "'");
-
- string vn (s, sp, p - sp);
-
- assert (!vn.empty ()); // Otherwise it would be an escape sequence.
-
- if (vn.find_first_of (" \t") != string::npos)
- bad_arg ("whitespace in variable name '" + vn + "'");
-
- // Find the variable and append its value or fail if it's unknown.
- //
- auto i (substitutions->find (vn));
-
- if (i == substitutions->end ())
- bad_arg ("unknown variable '" + vn + "'");
-
- r += i->second;
- }
-
- // Append the source string tail, following the last substitution, and
- // optimizing for the no-substitutions case.
- //
- if (p == 0)
- return move (s);
+ size_t sp;
+ if (substitutions && (sp = s.find (subst)) != string::npos)
+ return command_substitute (s, sp, *substitutions, subst, subst);
- r.append (s.begin () + p, s.end ());
- return r;
+ return move (s);
};
// Perform substitutions in the program path.
@@ -163,7 +186,7 @@ namespace butl
else // Take the space-separated file path from the next element.
{
if (++i == e)
- bad_arg ("no stdout redirect file specified");
+ throw invalid_argument ("no stdout redirect file specified");
a = move (*i);
}
@@ -174,11 +197,12 @@ namespace butl
}
catch (const invalid_path& e)
{
- bad_arg ("invalid stdout redirect file path '" + e.path + "'");
+ throw invalid_argument ("invalid stdout redirect file path '" +
+ e.path + "'");
}
if (redir->empty ())
- bad_arg ("empty stdout redirect file path");
+ throw invalid_argument ("empty stdout redirect file path");
if (redir->relative () && !cwd.empty ())
redir = cwd / *redir;
diff --git a/libbutl/command.mxx b/libbutl/command.mxx
index 02c8eb8..143d406 100644
--- a/libbutl/command.mxx
+++ b/libbutl/command.mxx
@@ -85,4 +85,38 @@ LIBBUTL_MODEXPORT namespace butl
const optional<command_substitution_map>& = nullopt,
char subst = '@',
const std::function<command_callback>& = {});
+
+ // Reusable substitution utility functions.
+ //
+ // Unlike command_run(), these support different opening and closing
+ // substitution characters (e.g., <name>). Note that unmatched closing
+ // characters are treated literally and there is no support for their
+ // escaping (which would only be necessary if we needed to support variable
+ // names containing the closing character).
+
+ // Perform substitutions in a string. The second argument should be the
+ // position of the openning substitution character in the passed string.
+ // Throw invalid_argument for a malformed substitution or an unknown
+ // variable name.
+ //
+ LIBBUTL_SYMEXPORT std::string
+ command_substitute (const std::string&, std::size_t,
+ const command_substitution_map&,
+ char open, char close);
+
+ // As above but using a callback instead of a map.
+ //
+ // Specifically, on success, the callback should substitute the specified
+ // variable in out by appending its value and returning true. On failure,
+ // the callback can either throw invalid_argument or return false, in which
+ // case the standard "unknown substitution variable ..." exception will be
+ // thrown.
+ //
+ using command_substitution_callback =
+ bool (const std::string& var, std::string& out);
+
+ LIBBUTL_SYMEXPORT std::string
+ command_substitute (const std::string&, std::size_t,
+ const std::function<command_substitution_callback>&,
+ char open, char close);
}
diff --git a/tests/command/testscript b/tests/command/testscript
index 40af276..f2b046a 100644
--- a/tests/command/testscript
+++ b/tests/command/testscript
@@ -114,9 +114,9 @@ end
{
test.options += -s v=a
- $* 'p @a b@' 2>"unmatched substitution character '@' in '@a'" != 0 : unterm-var
- $* "p '@a b@'" 2>"whitespace in variable name 'a b'" != 0 : ws-var
- $* 'p @x@' 2>"unknown variable 'x'" != 0 : unknown-var
+ $* 'p @a b@' 2>"unmatched substitution character '@'" != 0 : unterm-var
+ $* "p '@a b@'" 2>"whitespace in substitution variable 'a b'" != 0 : ws-var
+ $* 'p @x@' 2>"unknown substitution variable 'x'" != 0 : unknown-var
}
}