From ca0fa738650ab546b1422e0b2dbfdc89ba8eb5a3 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Mon, 6 Apr 2020 16:19:38 +0200 Subject: Add ability to print process_env environment --- libbutl/process-io.cxx | 106 +++++++++++++++++++++++++++++++++++++++++++++++ libbutl/process-io.mxx | 24 +++++++++++ libbutl/process.mxx | 11 ++++- tests/process/driver.cxx | 36 ++++++++++++++++ 4 files changed, 176 insertions(+), 1 deletion(-) create mode 100644 libbutl/process-io.cxx diff --git a/libbutl/process-io.cxx b/libbutl/process-io.cxx new file mode 100644 index 0000000..c29bbc0 --- /dev/null +++ b/libbutl/process-io.cxx @@ -0,0 +1,106 @@ +// file : libbutl/process-io.cxx -*- C++ -*- +// license : MIT; see accompanying LICENSE file + +#ifndef __cpp_modules_ts +#include +#endif + +// C includes. + +#ifndef __cpp_lib_modules_ts +#include + +#include // strchr() +#endif + +// Other includes. + +#ifdef __cpp_modules_ts +module butl.process_io; + +// Only imports additional to interface. +#ifdef __clang__ +#ifdef __cpp_lib_modules_ts +import std.core; +import std.io; +#endif +import butl.process; +#endif + +import butl.path-io; +#else +#include +#endif + +using namespace std; + +namespace butl +{ + // process_env + // + ostream& + operator<< (ostream& o, const process_env& env) + { + bool first (true); + const dir_path* cwd (env.cwd); + + if (cwd != nullptr && !cwd->empty ()) + { + if (cwd->string ().find (' ') != string::npos) + o << "PWD=\"" << *cwd << '"'; + else + o << "PWD=" << *cwd; + + first = false; + } + + if (env.vars != nullptr) + { + for (const char* const* ev (env.vars); *ev != nullptr; ++ev) + { + if (first) + first = false; + else + o << ' '; + + const char* v (*ev); + + // If there is no `=` in the string, then this is just a name + // (variable unset) and we print it as the empty string assignment. + // + const char* eq (strchr (v, '=')); + + // If there is the space character in the string, then we quote the + // variable value, unless this is the variable name that contains the + // space character in which case we quote the whole (potentially + // broken) assignment. + // + const char* sp (strchr (v, ' ')); + + if (eq != nullptr) // Variable assignment? + { + if (sp == nullptr) // No space? + { + o << v; + } + else if (eq < sp) // Space in the value? + { + o.write (v, eq - v + 1); // Name and '='. + o << '"' << eq + 1 << '"'; // Quoted value. + } + else // Space in the name. + o << '"' << v << '"'; + } + else // Variable unset. + { + if (sp == nullptr) // No space? + o << v << '='; + else // Space in the name. + o << '"' << v << "=\""; + } + } + } + + return o; + } +} diff --git a/libbutl/process-io.mxx b/libbutl/process-io.mxx index 5471846..d07a212 100644 --- a/libbutl/process-io.mxx +++ b/libbutl/process-io.mxx @@ -40,4 +40,28 @@ LIBBUTL_MODEXPORT namespace butl process::print (o, a.argv, a.argc); return o; } + + // Print the environment variables and the current working directory (if + // specified) in a POSIX shell command line notation. The process path + // itself is not printed. For example: + // + // LC_ALL=C + // + // If an environment variable is in the `name` rather than in the + // `name=value` form, then it is considered unset. Since there is no POSIX + // way to unset a variable on the command line, this information is printed + // as `name=` (ambiguous with assigning an empty value but the two cases are + // normally handled in the same way). For example: + // + // PATH= LC_ALL=C + // + // Note that since there is no POSIX way to change the current working + // directory of a command to be executed, this information is printed in a + // pseudo-notation by assigning to PWD (which, according POSIX, would result + // in the undefined behavior of the cwd utility). For example: + // + // PWD=/tmp LC_ALL=C + // + LIBBUTL_SYMEXPORT std::ostream& + operator<< (std::ostream&, const process_env&); } diff --git a/libbutl/process.mxx b/libbutl/process.mxx index 1f378c4..b8c6054 100644 --- a/libbutl/process.mxx +++ b/libbutl/process.mxx @@ -494,7 +494,6 @@ LIBBUTL_MODEXPORT namespace butl quote_argument (const char*, std::string& buffer); #endif - public: id_type id () const; @@ -548,6 +547,16 @@ LIBBUTL_MODEXPORT namespace butl const dir_path* cwd = nullptr; const char* const* vars = nullptr; + // Return true if there is an "environment", that is, either the current + // working directory or environment variables. + // + bool + env () const + { + return (cwd != nullptr && !cwd->empty ()) || + (vars != nullptr && *vars != nullptr); + } + process_env (const process_path& p, const dir_path& c = dir_path (), const char* const* v = nullptr) diff --git a/tests/process/driver.cxx b/tests/process/driver.cxx index 7a0f38b..3be4154 100644 --- a/tests/process/driver.cxx +++ b/tests/process/driver.cxx @@ -7,6 +7,7 @@ #include #include #include +#include #include // istreambuf_iterator, ostream_iterator #include // copy() #include @@ -28,6 +29,7 @@ import butl.fdstream; #include #include #include +#include #include #include #endif @@ -474,4 +476,38 @@ main (int argc, const char* argv[]) assert (!exec (path ("testX.bat"))); } #endif + + // Test printing process_env to stream. + // + { + auto str = [] (const process_env& env) + { + ostringstream os; + os << env; + return os.str (); + }; + + process_path p; + + assert (str (process_env (p)) == ""); + + { + dir_path d ("dir"); + dir_path ds ("d ir"); + assert (str (process_env (p, d)) == "PWD=dir"); + assert (str (process_env (p, ds)) == "PWD=\"d ir\""); + } + + { + dir_path ed; // Empty. + const char* vars[] = {nullptr}; + assert (str (process_env (p, ed, vars)) == ""); + } + + { + const char* vars[] = {"A=B", "A=B C", "A B=C", "A", "A B", nullptr}; + assert (str (process_env (p, vars)) == + "A=B A=\"B C\" \"A B=C\" A= \"A B=\""); + } + } } -- cgit v1.1