aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKaren Arutyunov <karen@codesynthesis.com>2017-03-09 15:42:32 +0300
committerKaren Arutyunov <karen@codesynthesis.com>2017-03-15 19:52:39 +0300
commitdeb3ed0a579dadbd6cca7ef2e7fb10148387a1ca (patch)
treecf3f0cb4bc68408f391e9c7436fb317c883f48ea
parent52a29dd64479e9313848941078a2619b47f2c61b (diff)
Add support for in place editing for sed builtin
-rw-r--r--build2/test/script/builtin.cxx157
-rw-r--r--doc/testscript.cli13
-rw-r--r--tests/test/script/builtin/sed.test21
3 files changed, 100 insertions, 91 deletions
diff --git a/build2/test/script/builtin.cxx b/build2/test/script/builtin.cxx
index a971f64..336fa72 100644
--- a/build2/test/script/builtin.cxx
+++ b/build2/test/script/builtin.cxx
@@ -148,11 +148,6 @@ namespace build2
// cat <file>...
//
- // Read files in sequence and write their contents to STDOUT in the same
- // sequence. Read from STDIN if no arguments provided or '-' is specified
- // as a file path. STDIN, STDOUT and file streams are set to binary mode
- // prior to I/O operations.
- //
// Note that POSIX doesn't specify if after I/O operation failure the
// command should proceed with the rest of the arguments. The current
// implementation exits immediatelly in such a case.
@@ -340,12 +335,6 @@ namespace build2
// cp <src-file>... <dst-dir>/
// cp -R|-r <src-path>... <dst-dir>/
//
- // Copy files and/or directories. The first two forms make a copy of a
- // single entity at the specified path. The last two copy one or more
- // entities into the specified directory.
- //
- // For details read the builtin description in the manual.
- //
// Note: can be executed synchronously.
//
static uint8_t
@@ -517,7 +506,9 @@ namespace build2
// false
//
- // Return 1. Failure to close the file descriptors is silently ignored.
+ // Failure to close the file descriptors is silently ignored.
+ //
+ // Note: can be executed synchronously.
//
static future<uint8_t>
false_ (scope&, const strings&, auto_fd, auto_fd, auto_fd)
@@ -527,7 +518,9 @@ namespace build2
// true
//
- // Return 0. Failure to close the file descriptors is silently ignored.
+ // Failure to close the file descriptors is silently ignored.
+ //
+ // Note: can be executed synchronously.
//
static future<uint8_t>
true_ (scope&, const strings&, auto_fd, auto_fd, auto_fd)
@@ -554,10 +547,6 @@ namespace build2
// mkdir [-p] <dir>...
//
- // -p
- // Create any missing intermediate pathname components. Each argument
- // that names an existing directory must be ignored without error.
- //
// Note that POSIX doesn't specify if after a directory creation failure
// the command should proceed with the rest of the arguments. The current
// implementation exits immediatelly in such a case.
@@ -653,19 +642,6 @@ namespace build2
// rm [-r] [-f] <path>...
//
- // Remove a file or directory. A path must not be the test working
- // directory or its parent directory. It also must not be outside the
- // testscript working directory unless the -f option is specified. Note
- // that directories are not removed by default.
- //
- // -r
- // Remove directories recursively. Must be specified to remove even an
- // empty directory.
- //
- // -f
- // Do not fail if path doesn't exist or no paths specified. Removing
- // paths outside the testscript working directory is not an error.
- //
// The implementation deviates from POSIX in a number of ways. It doesn't
// interact with a user and fails immediatelly if unable to process an
// argument. It doesn't check for dots containment in the path, and
@@ -786,14 +762,6 @@ namespace build2
// rmdir [-f] <path>...
//
- // Remove a directory. The directory must be empty and not be the test
- // working directory or its parent directory. It also must not be outside
- // the testscript working directory unless the -f option is specified.
- //
- // -f
- // Do not fail if no directory is specified, the directory does not
- // exist, or is outside the script working directory.
- //
// Note: can be executed synchronously.
//
static uint8_t
@@ -894,34 +862,7 @@ namespace build2
return 1;
}
- // sed [-n] -e <script> [<file>]
- //
- // Read text from file, make editing changes according to script, and
- // write the result to stdout. If file is not specified or is '-', read
- // from stdin.
- //
- // -n
- // Suppress automatic printing of the pattern space at the end of the
- // script execution.
- //
- // -e <script>
- // Editing commands to be executed (required).
- //
- // Currently, only single-command scripts using the following editing
- // commands are supported.
- //
- // s/<regex>/<replacement>/<flags>
- // The supported flags are 'i' (case-insensitive search), 'g'
- // (substitute globally), 'p' (print if a replacement was made). If
- // regex starts with ^, then it only matches at the beginning of the
- // pattern space. Similarly, if it ends with $, then it only matches
- // at the end of the pattern space.
- //
- // In replacement, besides the standard ECMAScript escape sequences a
- // subset of Perl-specific ones is recognized.
- //
- // For more details read the builtin description in 'The build2
- // Testscript Language'.
+ // sed [-n] [-i] -e <script> [<file>]
//
// Note: must be executed asynchronously.
//
@@ -941,6 +882,11 @@ namespace build2
try
{
+ // Automatically remove a temporary file (used for in place editing)
+ // on failure.
+ //
+ auto_rmfile rm;
+
// Do not throw when failbit is set (getline() failed to extract any
// character).
//
@@ -953,6 +899,7 @@ namespace build2
// Process options.
//
bool auto_prn (true);
+ bool in_place (false);
struct substitute
{
@@ -966,9 +913,13 @@ namespace build2
for (; i != e; ++i)
{
- if (*i == "-n")
+ const string& o (*i);
+
+ if (o == "-n")
auto_prn = false;
- else if (*i == "-e")
+ else if (o == "-i")
+ in_place = true;
+ else if (o == "-e")
{
// Only a single script is supported.
//
@@ -1034,7 +985,7 @@ namespace build2
}
else
{
- if (*i == "--")
+ if (o == "--")
++i;
break;
@@ -1058,6 +1009,43 @@ namespace build2
if (i != e)
error () << "unexpected argument";
+ // If we edit file in place make sure that the file path is specified
+ // and obtain a temporary file path. We will be writing to the
+ // temporary file (rather than to stdout) and will move it to the
+ // original file path afterwards.
+ //
+ path tp;
+ if (in_place)
+ {
+ if (p.empty ())
+ error () << "-i option specified while reading from stdin";
+
+ try
+ {
+ tp = path::temp_path ("build2-sed");
+
+ cout.close (); // Flush and close.
+
+ cout.open (
+ fdopen (tp,
+ fdopen_mode::out | fdopen_mode::truncate |
+ fdopen_mode::create,
+ path_permissions (p)));
+ }
+ catch (const io_error& e)
+ {
+ error_record d (error ());
+ d << "unable to open '" << tp << "': " << e;
+ }
+ catch (const system_error& e)
+ {
+ error_record d (error ());
+ d << "unable to obtain temporary file: " << e;
+ }
+
+ rm = auto_rmfile (tp);
+ }
+
// Note that ECMAScript is implied if no grammar flag is specified.
//
regex re (subst->regex,
@@ -1096,6 +1084,16 @@ namespace build2
cin.close ();
cout.close ();
+
+ if (in_place)
+ {
+ mvfile (
+ tp, p,
+ cpflags::overwrite_content | cpflags::overwrite_permissions);
+
+ rm.cancel ();
+ }
+
r = 0;
}
catch (const io_error& e)
@@ -1127,6 +1125,10 @@ namespace build2
{
error (false) << e;
}
+ catch (const system_error& e)
+ {
+ error (false) << e;
+ }
catch (const failed&)
{
// Diagnostics has already been issued.
@@ -1142,19 +1144,6 @@ namespace build2
// test -f|-d <path>
//
- // Test the specified path according to one of the following options.
- // Succeed (0 exit code) if the test passes and fail (1 exit code)
- // otherwise. In case of an error exit with code 2. Providing no
- // arguments is an error (unlike POSIX).
- //
- // -f
- // Path exists and is to a regular file.
- //
- // -d
- // Path exists and is to a directory.
- //
- // Note that tests dereference symbolic links.
- //
// Note: can be executed synchronously.
//
static uint8_t
@@ -1223,10 +1212,6 @@ namespace build2
// touch <file>...
//
- // Change file access and modification times to the current time. Create
- // a file if doesn't exist. Fail if a file system entry other than file
- // exists for the name specified.
- //
// Note that POSIX doesn't specify the behavior for touching an entry
// other than file.
//
diff --git a/doc/testscript.cli b/doc/testscript.cli
index 4c46786..078f674 100644
--- a/doc/testscript.cli
+++ b/doc/testscript.cli
@@ -2392,12 +2392,14 @@ working directory unless the \c{-f} option is specified.
\h#builtins-sed|\c{sed}|
\
-sed [-n] -e <script> [<file>]
+sed [-n] [-i] -e <script> [<file>]
\
Read text from \i{file}, make editing changes according to \i{script}, and
write the result to \c{stdout}. If \i{file} is not specified or is \c{-}, read
-from \c{stdin}.
+from \c{stdin}. If both \i{file} and the \c{-i} option are specified then edit
+the \i{file} in place. Specifying \c{-i} when reading from \c{stdin} is
+illegal.
Note that this builtin implementation deviates significantly from POSIX
\c{sed} (as described next). Most significantly, the regular expression flavor
@@ -2410,6 +2412,10 @@ is ECMAScript (more specifically, ECMA-262-based C++11 regular expressions).
Suppress automatic printing of the pattern space at the end of the script
execution.|
+\li|\n\c{-i}
+
+ Edit \i{file} in place.|
+
\li|\n\c{-e <script>}\n
Editing commands to be executed (required).||
@@ -2417,8 +2423,7 @@ is ECMAScript (more specifically, ECMA-262-based C++11 regular expressions).
To perform the transformation \c{sed} reads each line of input (without the
newline) into the pattern space. It then executes the script commands on the
pattern space. At the end of the script execution, unless the \c{-n} option is
-specified, \c{sed} writes the pattern space to \c{stdout} followed by a
-newline.
+specified, \c{sed} writes the pattern space to output followed by a newline.
Currently, only single-command scripts using the following editing commands
are supported.
diff --git a/tests/test/script/builtin/sed.test b/tests/test/script/builtin/sed.test
index c0a8172..dbd70c4 100644
--- a/tests/test/script/builtin/sed.test
+++ b/tests/test/script/builtin/sed.test
@@ -104,7 +104,6 @@
info: stderr: test/1/stderr
sed: unexpected argument
EOE
-
}
: command
@@ -299,6 +298,26 @@
}
}
+: in-place
+:
+{
+ : no-file
+ :
+ $c <"sed -i -e 's/a/b/'" && $b 2>>/EOE != 0
+ testscript:1:1: error: sed exit status 1 != 0
+ info: stderr: test/1/stderr
+ sed: -i option specified while reading from stdin
+ EOE
+
+ : edit
+ :
+ $c <<EOI && $b
+ cat <'foo' >=f;
+ sed -i -e 's/foo/bar/' f;
+ cat f >'bar'
+ EOI
+}
+
: big
:
: Sed a big file (about 100K) to test that the builtin is asynchronous.