From 353438a94953bf4d093af0d84decd5ec7529ed34 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Wed, 20 Jun 2018 09:51:56 +0200 Subject: Add $process.run() and $process.run_regex() functions $process.run([ ...]) Return trimmed stdout. $process.run_regex([ ...], [, ]) Return stdout lines matched and optionally processed with regex. Each line of stdout (including the customary trailing blank) is matched (as a whole) against and, if successful, returned, optionally processed with , as an element of a list. --- build2/functions-process.cxx | 253 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 253 insertions(+) create mode 100644 build2/functions-process.cxx (limited to 'build2/functions-process.cxx') diff --git a/build2/functions-process.cxx b/build2/functions-process.cxx new file mode 100644 index 0000000..ef36828 --- /dev/null +++ b/build2/functions-process.cxx @@ -0,0 +1,253 @@ +// file : build2/functions-process.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2018 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include +#include + +using namespace std; +using namespace butl; + +namespace build2 +{ + // Ideas for potential further improvements: + // + // - Use scope to query environment. + // - Mode to ignore error/suppress diagnostics and return NULL? + // - Similar regex flags to regex.* functions (icase, etc)? + + // Process arguments. + // + static pair + process_args (names&& args, const char* fn) + { + if (args.empty () || args[0].empty ()) + fail << "executable name expected in process." << fn << "()"; + + process_path pp; + try + { + size_t erase; + + // This can be a process_path (pair) or just a path. + // + if (args[0].pair) + { + pp = convert (move (args[0]), move (args[1])); + erase = 2; + } + else + { + pp = run_search (convert (move (args[0]))); + erase = 1; + } + + args.erase (args.begin (), args.begin () + erase); + } + catch (const invalid_argument& e) + { + fail << "invalid process." << fn << "() executable path: " << e.what (); + } + + strings sargs; + try + { + sargs = convert (move (args)); + } + catch (const invalid_argument& e) + { + fail << "invalid process." << fn << "() argument: " << e.what (); + } + + return pair (move (pp), move (sargs)); + } + + static process + start (const scope*, + const process_path& pp, + const strings& args, + cstrings& cargs) + { + cargs.reserve (args.size () + 2); + cargs.push_back (pp.recall_string ()); + transform (args.begin (), + args.end (), + back_inserter (cargs), + [] (const string& s) {return s.c_str ();}); + cargs.push_back (nullptr); + + return run_start (3 /* verbosity */, + pp, + cargs.data (), + 0 /* stdin */, + -1 /* stdout */); + } + + static void + finish (cstrings& args, process& pr, bool io) + { + run_finish (args, pr); + + if (io) + fail << "error reading " << args[0] << " output"; + } + + static value + run (const scope* s, const process_path& pp, const strings& args) + { + cstrings cargs; + process pr (start (s, pp, args, cargs)); + + string v; + bool io (false); + try + { + ifdstream is (move (pr.in_ofd)); + + // Note that getline() will fail if there is no output. + // + if (is.peek () != ifdstream::traits_type::eof ()) + getline (is, v, '\0'); + + is.close (); // Detect errors. + } + catch (const io_error&) + { + // Presumably the child process failed and issued diagnostics so let + // finish() try to deal with that first. + // + io = true; + } + + finish (cargs, pr, io); + + names r; + r.push_back (to_name (move (trim (v)))); + return value (move (r)); + } + + regex + parse_regex (const string&, regex::flag_type); // functions-regex.cxx + + static value + run_regex (const scope* s, + const process_path& pp, + const strings& args, + const string& pat, + const optional& fmt) + { + regex re (parse_regex (pat, regex::ECMAScript)); + + cstrings cargs; + process pr (start (s, pp, args, cargs)); + + names r; + bool io (false); + try + { + ifdstream is (move (pr.in_ofd), ifdstream::badbit); + + for (string l; !eof (getline (is, l)); ) + { + if (fmt) + { + pair p (regex_replace_match (l, re, *fmt)); + + if (p.second) + r.push_back (to_name (move (p.first))); + } + else + { + if (regex_match (l, re)) + r.push_back (to_name (move (l))); + } + } + + is.close (); // Detect errors. + } + catch (const io_error&) + { + // Presumably the child process failed and issued diagnostics so let + // finish() try to deal with that first. + // + io = true; + } + + finish (cargs, pr, io); + + return value (move (r)); + } + + static inline value + run_regex (const scope* s, + names&& args, + const string& pat, + const optional& fmt) + { + pair pa (process_args (move (args), "run_regex")); + return run_regex (s, pa.first, pa.second, pat, fmt); + } + + void + process_functions () + { + function_family f ("process"); + + // $process.run([ ...]) + // + // Return trimmed stdout. + // + f[".run"] = [](const scope* s, names args) + { + pair pa (process_args (move (args), "run")); + return run (s, pa.first, pa.second); + }; + + f["run"] = [](const scope* s, process_path pp) + { + return run (s, pp, strings ()); + }; + + // $process.run_regex([ ...], [, ]) + // + // Return stdout lines matched and optionally processed with regex. + // + // Each line of stdout (including the customary trailing blank) is matched + // (as a whole) against and, if successful, returned, optionally + // processed with , as an element of a list. + // + f[".run_regex"] = [](const scope* s, names a, string p, optional f) + { + return run_regex (s, move (a), p, f); + }; + + f[".run_regex"] = [] (const scope* s, names a, names p, optional f) + { + return run_regex (s, + move (a), + convert (move (p)), + f ? convert (move (*f)) : nullopt_string); + }; + + f["run_regex"] = [](const scope* s, + process_path pp, + string p, + optional f) + { + return run_regex (s, pp, strings (), p, f); + }; + + f["run_regex"] = [](const scope* s, + process_path pp, + names p, + optional f) + { + return run_regex (s, + pp, strings (), + convert (move (p)), + f ? convert (move (*f)) : nullopt_string); + }; + } +} -- cgit v1.1