aboutsummaryrefslogtreecommitdiff
path: root/libbuild2/functions-process.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'libbuild2/functions-process.cxx')
-rw-r--r--libbuild2/functions-process.cxx253
1 files changed, 253 insertions, 0 deletions
diff --git a/libbuild2/functions-process.cxx b/libbuild2/functions-process.cxx
new file mode 100644
index 0000000..83188d3
--- /dev/null
+++ b/libbuild2/functions-process.cxx
@@ -0,0 +1,253 @@
+// file : libbuild2/functions-process.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <libbutl/regex.mxx>
+
+#include <libbuild2/function.hxx>
+#include <libbuild2/variable.hxx>
+
+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_path, strings>
+ 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<process_path> (move (args[0]), move (args[1]));
+ erase = 2;
+ }
+ else
+ {
+ pp = run_search (convert<path> (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<strings> (move (args));
+ }
+ catch (const invalid_argument& e)
+ {
+ fail << "invalid process." << fn << "() argument: " << e.what ();
+ }
+
+ return pair<process_path, strings> (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<string>& 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<string, bool> 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<string>& fmt)
+ {
+ pair<process_path, strings> 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(<prog>[ <args>...])
+ //
+ // Return trimmed stdout.
+ //
+ f[".run"] = [](const scope* s, names args)
+ {
+ pair<process_path, strings> 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(<prog>[ <args>...], <pat> [, <fmt>])
+ //
+ // Return stdout lines matched and optionally processed with regex.
+ //
+ // Each line of stdout (including the customary trailing blank) is matched
+ // (as a whole) against <pat> and, if successful, returned, optionally
+ // processed with <fmt>, as an element of a list.
+ //
+ f[".run_regex"] = [](const scope* s, names a, string p, optional<string> f)
+ {
+ return run_regex (s, move (a), p, f);
+ };
+
+ f[".run_regex"] = [] (const scope* s, names a, names p, optional<names> f)
+ {
+ return run_regex (s,
+ move (a),
+ convert<string> (move (p)),
+ f ? convert<string> (move (*f)) : nullopt_string);
+ };
+
+ f["run_regex"] = [](const scope* s,
+ process_path pp,
+ string p,
+ optional<string> f)
+ {
+ return run_regex (s, pp, strings (), p, f);
+ };
+
+ f["run_regex"] = [](const scope* s,
+ process_path pp,
+ names p,
+ optional<names> f)
+ {
+ return run_regex (s,
+ pp, strings (),
+ convert<string> (move (p)),
+ f ? convert<string> (move (*f)) : nullopt_string);
+ };
+ }
+}