From 6b228ee6a85ebdeeae2d10a74cf8076ae3381e0e Mon Sep 17 00:00:00 2001 From: q66 Date: Mon, 8 May 2017 21:23:15 +0200 Subject: [PATCH] documentation for subprocess --- ostd/process.hh | 175 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 165 insertions(+), 10 deletions(-) diff --git a/ostd/process.hh b/ostd/process.hh index f707c65..d26d794 100644 --- a/ostd/process.hh +++ b/ostd/process.hh @@ -32,6 +32,7 @@ namespace ostd { * @{ */ +/** @brief Thrown on errors in ostd::split_args(). */ struct word_error: std::runtime_error { using std::runtime_error::runtime_error; }; @@ -44,8 +45,8 @@ namespace detail { /** @brief Splits command line argument string into individual arguments. * - * The split is done in a platform specific manner, using wordexp on POSIX - * systems and CommandLineToArgvW on Windows. On Windows, the input string + * The split is done in a platform specific manner, using `wordexp` on POSIX + * systems and `CommandLineToArgvW` on Windows. On Windows, the input string * is assumed to be UTF-8 and the output strings are always UTF-8, on POSIX * the wordexp implementation is followed (so it's locale specific). * @@ -76,44 +77,137 @@ OutputRange &&split_args(OutputRange &&out, string_range str) { return std::forward(out); } +/** @brief Thrown on errors in ostd::subprocess. */ struct process_error: std::system_error { using std::system_error::system_error; }; +/** @brief The mode used for standard streams in ostd::subprocess. + * + * This way you can turn stdin, stdout or stderr of any subprocess into + * a stadnard ostd::file_stream or keep them as they are. You can also + * redirect stderr into stdout, if stdout itself is redirected then + * stderr will point to the newly redirected stdout. + * + * Only use the `STDOUT` value for stderr stream. + */ enum class process_stream { - DEFAULT = 0, - PIPE, - STDOUT + DEFAULT = 0, ///< Do not perform any redirection. + PIPE, ///< Capture the stream as an ostd::file_stream. + STDOUT ///< Writes to stderr will be written to stdout. }; +/** @brief Implements portable subprocess handling. + * + * This is a universal API with which you can freely manipulate standard + * streams of the child process (therefore manipulate its I/O) as well as + * get the return code of the child process. It portably covers the role + * of `popen` (but it is also more powerful, as it can be bidirectional) + * as well as the `exec` family of functions. + */ struct OSTD_EXPORT subprocess { - process_stream use_in = process_stream::DEFAULT; + /** @brief The standard input redirection mode. + * + * The value is one of ostd::process_stream. Set this before opening + * the subprocess. If it's set to `PIPE`, you will be able to write + * into the standard input of the child process using the `in` member, + * which is a standard ostd::file_stream. Never set it to `STDOUT` + * as that will make process opening throw an error. By default + * no redirection is done. + * + * @see ostd::subprocess::in, ostd::subprocess::use_out, + * ostd::subprocess::use_err + */ + process_stream use_in = process_stream::DEFAULT; + + /** @brief The standard output redirection mode. + * + * The value is one of ostd::process_stream. Set this before opening + * the subprocess. If it's set to `PIPE`, you will be able to read + * from the standard output of the child process using the `out` member, + * which is a standard ostd::file_stream. Setting this to `STDOUT` has + * the same effect as `DEFAULT`. + * + * @see ostd::subprocess::out, ostd::subprocess::use_in, + * ostd::subprocess::use_err + */ process_stream use_out = process_stream::DEFAULT; + + /** @brief The standard error redirection mode. + * + * The value is one of ostd::process_stream. Set this before opening + * the subprocess. If it's set to `PIPE`, you will be able to read + * from the standard error of the child process using the `err` member, + * which is a standard ostd::file_stream. Setting this to `STDOUT` + * redirects the child process standard error into its stanard output, + * no matter what the redirection mode of the standard output is, so + * they will be effectively the same stream - if you redirect the + * standard output you will also read standard error using `err`. + * + * @see ostd::subprocess::err, ostd::subprocess::use_in, + * ostd::subprocess::use_out + */ process_stream use_err = process_stream::DEFAULT; - file_stream in = file_stream{}; + + /** @brief The standard input stream when redirected. + * + * If no redirection is done (see ostd::subprocess::use_in) then + * this stream will not be opened. + * + * @see ostd::subprocess::out, ostd::subprocess::err + */ + file_stream in = file_stream{}; + + /** @brief The standard output stream when redirected. + * + * If no redirection is done (see ostd::subprocess::use_out) then + * this stream will not be opened. + * + * @see ostd::subprocess::in, ostd::subprocess::err + */ file_stream out = file_stream{}; + + /** @brief The standard error stream when redirected. + * + * If no redirection is done (see ostd::subprocess::use_err) then + * this stream will not be opened. + * + * @see ostd::subprocess::in, ostd::subprocess::out + */ file_stream err = file_stream{}; + /** @brief Initializes a default subprocess. + * + * No streams will be set to redirect. + */ subprocess() {} + + /** @brief Initializes a subprocess with the given stream redirections.*/ subprocess( process_stream in_use, process_stream out_use, process_stream err_use ): use_in(in_use), use_out(out_use), use_err(err_use) {} - subprocess(subprocess &&i): + /** @brief Moves the subprocess data. */ + subprocess(subprocess &&i) noexcept: use_in(i.use_in), use_out(i.use_out), use_err(i.use_err), in(std::move(i.in)), out(std::move(i.out)), err(std::move(i.err)) { move_data(i); } - subprocess &operator=(subprocess &&i) { + /** @brief Move assigns the subprocess data. + * + * Effectively equivalent to swap(). + */ + subprocess &operator=(subprocess &&i) noexcept { swap(i); return *this; } - void swap(subprocess &i) { + /** @brief Swaps the data of two subprocess structures. */ + void swap(subprocess &i) noexcept { using std::swap; swap(use_in, i.use_in); swap(use_out, i.use_out); @@ -124,25 +218,86 @@ struct OSTD_EXPORT subprocess { swap_data(i); } + /** @brief Destroys the subprocess. + * + * If a child process assigned to this structure currently + * exists, it will wait for it to finish first by calling close(). + */ ~subprocess(); + /** @brief Waits for a currently running child process to be done. + * + * If there isn't any child process assigned to this, it will throw + * ostd::process_error. It will also throw the same exception if some + * other error has occured. It will not throw if the command has + * executed but exited with a non-zero code. This code will be + * returned instead. + * + * @returns The child process return code on success. + * + * @throws ostd::process_error on failure of any kind. + * + * @see open_path(), open_command() + */ int close(); + /** @brief Opens a subprocess using the given arguments. + * + * The `path` is an actual absolute or relative path to an executable + * file (as in POSIX `execv` or Windows `CreateProcess`) and `args` + * is a range of string-like types (any works as long as a string can + * be constructed from it). The first element of `args` is `argv[0]`. + * + * If `path` is empty, the first element of `args` is used. + * + * If this fails, ostd::process_error will be thrown. + * + * On success, a new subprocess will be created and this will return + * without waiting for it to finish. Use close() to wait and get the + * return code. + * + * @throws ostd::process_error on failure of any kind. + * + * @see open_command(), close() + */ template void open_path(string_range path, InputRange &&args) { open_full(path, std::forward(args), false); } + /** @brief Like open_path() with an empty first argument. */ template void open_path(InputRange &&args) { open_path(nullptr, std::forward(args)); } + /** @brief Opens a subprocess using the given arguments. + * + * The `cmd` is a command name looked up in the `PATH` environment + * variable when it contains no slash and an ordinary executable + * path when it contains one (as in POSIX `execvp` or Windows + * `CreateProcess`) and `args` is a range of string-like types + * (any works as long as a string can be constructed from it). + * The first element of `args` is `argv[0]`. + * + * If `cmd` is empty, the first element of `args` is used. + * + * If this fails, ostd::process_error will be thrown. + * + * On success, a new subprocess will be created and this will return + * without waiting for it to finish. Use close() to wait and get the + * return code. + * + * @throws ostd::process_error on failure of any kind. + * + * @see open_path(), close() + */ template void open_command(string_range cmd, InputRange &&args) { open_full(cmd, std::forward(args), true); } + /** @brief Like open_command() with an empty first argument. */ template void open_command(InputRange &&args) { open_command(nullptr, std::forward(args));