From 044e2e1c1460fb060f677a366144b98905522754 Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Tue, 31 Jan 2017 22:08:38 +0300 Subject: Add sed builtin --- build2/test/script/builtin.cxx | 506 ++++++++++++++++++++++++++++++++--------- 1 file changed, 397 insertions(+), 109 deletions(-) (limited to 'build2/test/script/builtin.cxx') diff --git a/build2/test/script/builtin.cxx b/build2/test/script/builtin.cxx index 008ac32..3957adb 100644 --- a/build2/test/script/builtin.cxx +++ b/build2/test/script/builtin.cxx @@ -10,12 +10,17 @@ # include #endif +#include #include +#include +#include #include // use default operator<< implementation #include // fdopen_mode, fdstream_mode #include // mkdir_status +#include + #include // Strictly speaking a builtin which reads/writes from/to standard streams @@ -51,6 +56,74 @@ namespace build2 // struct failed {}; + // Accumulate an error message, print it atomically in dtor to the + // provided stream and throw failed afterwards if requested. Prefixes + // the message with the builtin name. + // + // Move constructible-only, not assignable (based to diag_record). + // + class error_record + { + public: + template + friend const error_record& + operator<< (const error_record& r, const T& x) + { + r.ss_ << x; + return r; + } + + error_record (ostream& o, bool fail, const char* name) + : os_ (o), fail_ (fail), empty_ (false) + { + ss_ << name << ": "; + } + + // Older versions of libstdc++ don't have the ostringstream move + // support. Luckily, GCC doesn't seem to be actually needing move due + // to copy/move elision. + // +#ifdef __GLIBCXX__ + error_record (error_record&&); +#else + error_record (error_record&& r) + : os_ (r.os_), + ss_ (move (r.ss_)), + fail_ (r.fail_), + empty_ (r.empty_) + { + r.empty_ = true; + } +#endif + + ~error_record () noexcept (false) + { + if (!empty_) + { + // The output stream can be in a bad state (for example as a + // result of unsuccessful attempt to report a previous error), so + // we check it. + // + if (os_.good ()) + { + ss_.put ('\n'); + os_ << ss_.str (); + os_.flush (); + } + + if (fail_) + throw failed (); + } + } + + private: + ostream& os_; + mutable ostringstream ss_; + + bool fail_; + bool empty_; + }; + // Parse and normalize a path. Also, unless it is already absolute, make // the path absolute using the specified directory. Throw invalid_path // if the path is empty, and on parsing and normalization failures. @@ -103,6 +176,11 @@ namespace build2 uint8_t r (1); ofdstream cerr (move (err)); + auto error = [&cerr] (bool fail = true) + { + return error_record (cerr, fail, "cat"); + }; + try { ifdstream cin (move (in), fdstream_mode::binary); @@ -154,15 +232,15 @@ namespace build2 } catch (const io_error& e) { - cerr << "cat: unable to print "; + error_record d (error ()); + d << "unable to print "; if (p.empty ()) - cerr << "stdin"; + d << "stdin"; else - cerr << "'" << p << "'"; + d << "'" << p << "'"; - cerr << ": " << e << endl; - throw failed (); + d << ": " << e; } cin.close (); @@ -171,15 +249,13 @@ namespace build2 } catch (const invalid_path& e) { - cerr << "cat: invalid path '" << e.path << "'" << endl; + error (false) << "invalid path '" << e.path << "'"; } - // Can be thrown while closing cin, cout or writing to cerr (that's - // why need to check its state before writing). + // Can be thrown while creating/closing cin, cout or writing to cerr. // catch (const io_error& e) { - if (cerr.good ()) - cerr << "cat: " << e << endl; + error (false) << e; } catch (const failed&) { @@ -215,8 +291,7 @@ namespace build2 for (auto b (args.begin ()), i (b), e (args.end ()); i != e; ++i) cout << (i != b ? " " : "") << *i; - cout << endl; - + cout << '\n'; cout.close (); r = 0; } @@ -291,6 +366,11 @@ namespace build2 uint8_t r (1); ofdstream cerr (move (err)); + auto error = [&cerr] (bool fail = true) + { + return error_record (cerr, fail, "mkdir"); + }; + try { in.close (); @@ -317,10 +397,7 @@ namespace build2 // Create directories. // if (i == args.end ()) - { - cerr << "mkdir: missing directory" << endl; - throw failed (); - } + error () << "missing directory"; for (; i != args.end (); ++i) { @@ -337,9 +414,7 @@ namespace build2 } catch (const system_error& e) { - cerr << "mkdir: unable to create directory '" << p << "': " - << e << endl; - throw failed (); + error () << "unable to create directory '" << p << "': " << e; } } @@ -347,15 +422,13 @@ namespace build2 } catch (const invalid_path& e) { - cerr << "mkdir: invalid path '" << e.path << "'" << endl; + error (false) << "invalid path '" << e.path << "'"; } - // Can be thrown while closing in, out or writing to cerr (that's why - // need to check its state before writing). + // Can be thrown while closing in, out or writing to cerr. // catch (const io_error& e) { - if (cerr.good ()) - cerr << "mkdir: " << e << endl; + error (false) << e; } catch (const failed&) { @@ -403,6 +476,11 @@ namespace build2 uint8_t r (1); ofdstream cerr (move (err)); + auto error = [&cerr] (bool fail = true) + { + return error_record (cerr, fail, "rm"); + }; + try { in.close (); @@ -432,10 +510,7 @@ namespace build2 // Remove entries. // if (i == args.end () && !force) - { - cerr << "rm: missing file" << endl; - throw failed (); - } + error () << "missing file"; const dir_path& wd (sp.wd_path); const dir_path& rwd (sp.root->wd_path); @@ -445,11 +520,8 @@ namespace build2 path p (parse_path (*i, wd)); if (!p.sub (rwd) && !force) - { - cerr << "rm: '" << p << "' is out of working directory '" << rwd - << "'" << endl; - throw failed (); - } + error () << "'" << p << "' is out of working directory '" << rwd + << "'"; try { @@ -458,17 +530,11 @@ namespace build2 if (dir_exists (d)) { if (!dir) - { - cerr << "rm: '" << p << "' is a directory" << endl; - throw failed (); - } + error () << "'" << p << "' is a directory"; if (wd.sub (d)) - { - cerr << "rm: '" << p << "' contains test working directory '" - << wd << "'" << endl; - throw failed (); - } + error () << "'" << p << "' contains test working directory '" + << wd << "'"; // The call can result in rmdir_status::not_exist. That's not // very likelly but there is also nothing bad about it. @@ -480,8 +546,7 @@ namespace build2 } catch (const system_error& e) { - cerr << "rm: unable to remove '" << p << "': " << e << endl; - throw failed (); + error () << "unable to remove '" << p << "': " << e; } } @@ -489,15 +554,13 @@ namespace build2 } catch (const invalid_path& e) { - cerr << "rm: invalid path '" << e.path << "'" << endl; + error (false) << "invalid path '" << e.path << "'"; } - // Can be thrown while closing in, out or writing to cerr (that's why - // need to check its state before writing). + // Can be thrown while closing in, out or writing to cerr. // catch (const io_error& e) { - if (cerr.good ()) - cerr << "rm: " << e << endl; + error (false) << e; } catch (const failed&) { @@ -533,6 +596,11 @@ namespace build2 uint8_t r (1); ofdstream cerr (move (err)); + auto error = [&cerr] (bool fail = true) + { + return error_record (cerr, fail, "rmdir"); + }; + try { in.close (); @@ -559,10 +627,7 @@ namespace build2 // Remove directories. // if (i == args.end () && !force) - { - cerr << "rmdir: missing directory" << endl; - throw failed (); - } + error () << "missing directory"; const dir_path& wd (sp.wd_path); const dir_path& rwd (sp.root->wd_path); @@ -572,18 +637,12 @@ namespace build2 dir_path p (path_cast (parse_path (*i, wd))); if (wd.sub (p)) - { - cerr << "rmdir: '" << p << "' contains test working directory '" - << wd << "'" << endl; - throw failed (); - } + error () << "'" << p << "' contains test working directory '" + << wd << "'"; if (!p.sub (rwd) && !force) - { - cerr << "rmdir: '" << p << "' is out of working directory '" - << rwd << "'" << endl; - throw failed (); - } + error () << "'" << p << "' is out of working directory '" + << rwd << "'"; try { @@ -596,8 +655,7 @@ namespace build2 } catch (const system_error& e) { - cerr << "rmdir: unable to remove '" << p << "': " << e << endl; - throw failed (); + error () << "unable to remove '" << p << "': " << e; } } @@ -605,15 +663,259 @@ namespace build2 } catch (const invalid_path& e) { - cerr << "rmdir: invalid path '" << e.path << "'" << endl; + error (false) << "invalid path '" << e.path << "'"; + } + // Can be thrown while closing in, out or writing to cerr. + // + catch (const io_error& e) + { + error (false) << e; + } + catch (const failed&) + { + // Diagnostics has already been issued. + } + + cerr.close (); + return r; + } + catch (const std::exception&) + { + return 1; + } + + // sed [-n] -e