diff options
Diffstat (limited to 'skiphead.re')
-rw-r--r-- | skiphead.re | 193 |
1 files changed, 193 insertions, 0 deletions
diff --git a/skiphead.re b/skiphead.re new file mode 100644 index 0000000..f7f48e5 --- /dev/null +++ b/skiphead.re @@ -0,0 +1,193 @@ +// Copyright 2022 Benjamin Barenblat +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +#include <assert.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <algorithm> +#include <array> +#include <ios> +#include <iostream> +#include <string_view> + +#include "goldfishlocale.h" + +namespace { + +constexpr std::string_view kShortUsage = + "Usage: skiphead [OPTION...] COMMAND [ARGS...]\n"; + +constexpr std::u8string_view kHelp = + u8R"( +With no options, copy the first line of standard input to standard output, and +then execute the specified command. This is useful for processing the output of +commands that emit headers; for example, “ps -ef | skiphead grep systemd” will +print the headers from “ps” before grepping for “systemd” in the remaining +output. + +Like head(1), skiphead accepts -NUM, -n, and --lines arguments to control the +number of lines printed before invoking COMMAND. + +If your COMMAND starts with -, you may terminate skiphead’s option processing +with --. + +Options: + -NUM, -n NUM, --lines=NUM print NUM header lines instead of 1 + -- terminate option processing + --help display this help and exit + --version display version information and exit + +Please report bugs to Benjamin Barenblat <bbarenblat@gmail.com>. +)"; + +constexpr std::u8string_view kAskForHelp = + u8"Try “skiphead --help” for more information.\n"; + +constexpr std::string_view kVersionInfo = R"(skiphead 1.0.0 +Copyright 2022 Benjamin Barenblat +Licensed under the Apache License, Version 2.0 +)"; + +int ParseOptionsAndAdvanceArgv(char**& argv) { + int lines_to_skip = 1; + bool expect_bare_number = false; + for (++argv; argv[0] != nullptr; ++argv) { + const char* YYCURSOR = argv[0]; + const char* a; + /*!stags:re2c format = "const char* @@;"; */ + + /*!re2c + re2c:define:YYCTYPE = char; + re2c:flags:tags = 1; + re2c:yyfill:enable = 0; + + ("-" | "-n" | "--lines=") @a [0-9]+ { + lines_to_skip = atoi(a); + continue; + } + + "-n" | "--lines" { + expect_bare_number = true; + continue; + } + + [0-9]+ { + if (expect_bare_number) { + lines_to_skip = atoi(argv[0]); + expect_bare_number = false; + continue; + } + break; + } + + "--help" { + std::cout << kShortUsage << goldfishlocale::ToSystem(kHelp); + exit(0); + } + + "--version" { + std::cout << kVersionInfo; + exit(0); + } + + "--" { + ++argv; + break; + } + + "-" [^\x00]+ { + std::clog << goldfishlocale::ToSystem( + u8"skiphead: Unrecognized option “") + << argv[0] << goldfishlocale::ToSystem(u8"”\n") + << goldfishlocale::ToSystem(kAskForHelp); + exit(1); + } + + * { + break; + } + */ + } + return lines_to_skip; +} + +int Read(int fd, void* buf, size_t count) { + ssize_t bytes_read = read(fd, buf, count); + if (bytes_read < 0) { + if (errno == EINTR) { + return Read(fd, buf, count); // Just try again. + } + std::clog << "skiphead: Read failed: " << strerror(errno) << '\n'; + exit(1); + } + return bytes_read; +} + +void CopyLines(int lines_to_skip) { + std::array<char, 16> buffer; + while (lines_to_skip > 0) { + int bytes_read = + Read(STDIN_FILENO, buffer.data(), + std::min(static_cast<int>(buffer.size()), lines_to_skip)); + if (bytes_read == 0) { + // Standard input got closed. Just move on. + break; + } + + for (int i = 0; i < bytes_read; ++i) { + if (buffer[i] == '\n') { + --lines_to_skip; + } + } + + std::cout.write(buffer.data(), bytes_read); + } +} + +} // namespace + +int main(int argc, char* argv[]) { + goldfishlocale::SetLocaleFromEnvironment(); + + // We're not using the C stdio functions in this program, so enable extra + // userspace buffering to reduce syscall overhead. + std::ios_base::sync_with_stdio(false); + + if (argc < 2) { + std::clog << kShortUsage << goldfishlocale::ToSystem(kAskForHelp); + return 1; + } + + int lines_to_skip = ParseOptionsAndAdvanceArgv(argv); + assert(lines_to_skip >= 0); + if (argv[0] == nullptr) { + std::clog << kShortUsage << goldfishlocale::ToSystem(kAskForHelp); + return 1; + } + + CopyLines(lines_to_skip); + std::cout.flush(); + + execvp(argv[0], argv); + + int r = errno; + std::clog << goldfishlocale::ToSystem( + u8"skiphead: Failed to execute command “") + << argv[0] << goldfishlocale::ToSystem(u8"”: ") << strerror(r) + << '\n'; + return r; +} |