// 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_map& m) { function_family f (m, "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); }; } }