eBay / tsv-utils
1
/**
2
Command line tool that executes a command while preserving header lines.
3

4
Copyright (c) 2018-2020, eBay Inc.
5
Initially written by Jon Degenhardt
6

7
License: Boost License 1.0 (http://boost.org/LICENSE_1_0.txt)
8
*/
9
module tsv_utils.keep_header;
10

11
auto helpText = q"EOS
12
Execute a command against one or more files in a header aware fashion.
13
The first line of each file is assumed to be a header. The first header
14
is output unchanged. Remaining lines are sent to the given command via
15
standard input, excluding the header lines of subsequent files. Output
16
from the command is appended to the initial header line.
17

18
A double dash (--) delimits the command, similar to how the pipe
19
operator (|) delimits commands. Examples:
20

21
    $ keep-header file1.txt -- sort
22
    $ keep-header file1.txt file2.txt -- sort -k1,1nr
23

24
These sort the files as usual, but preserve the header as the first line
25
output. Data can also be read from from standard input. Example:
26

27
    $ cat file1.txt | keep-header -- grep red
28

29
Options:
30

31
-V      --version   Print version information and exit.
32
-h         --help   This help information.
33
EOS";
34

35
static if (__VERSION__ >= 2085) extern(C) __gshared string[] rt_options = [ "gcopt=cleanup:none" ];
36

37
/** keep-header is a simple program, it is implemented entirely in main.
38
 */
39
int main(string[] args)
40
{
41
    import std.algorithm : findSplit, joiner;
42
    import std.path : baseName, stripExtension;
43
    import std.process : pipeProcess, ProcessPipes, Redirect, wait;
44
    import std.range;
45
    import std.stdio;
46
    import std.typecons : tuple;
47

48
    /* When running in DMD code coverage mode, turn on report merging. */
49
    version(D_Coverage) version(DigitalMars)
50
    {
51
        import core.runtime : dmd_coverSetMerge;
52 1
        dmd_coverSetMerge(true);
53
    }
54

55 1
    auto programName = (args.length > 0) ? args[0].stripExtension.baseName : "Unknown_program_name";
56 1
    auto splitArgs = findSplit(args, ["--"]);
57

58 1
    if (splitArgs[1].length == 0 || splitArgs[2].length == 0)
59
    {
60 1
        auto cmdArgs = splitArgs[0][1 .. $];
61 1
        stderr.writefln("Synopsis: %s [file...] -- program [args]", programName);
62 1
        if (cmdArgs.length > 0 &&
63 1
            (cmdArgs[0] == "-h" || cmdArgs[0] == "--help" || cmdArgs[0] == "--help-verbose"))
64
        {
65 1
            stderr.writeln();
66 1
            stderr.writeln(helpText);
67
        }
68 1
        else if (cmdArgs.length > 0 &&
69 1
                 (cmdArgs[0] == "-V" || cmdArgs[0] == "--V" ||  cmdArgs[0] == "--version"))
70
        {
71
            import tsv_utils.common.tsvutils_version;
72 1
            stderr.writeln();
73 1
            stderr.writeln(tsvutilsVersionNotice("keep-header"));
74
        }
75 1
        return 0;
76
    }
77

78 1
    ProcessPipes pipe;
79 1
    try pipe = pipeProcess(splitArgs[2], Redirect.stdin);
80
    catch (Exception exc)
81
    {
82 1
        stderr.writefln("[%s] Command failed: '%s'", programName, splitArgs[2].joiner(" "));
83 1
        stderr.writeln(exc.msg);
84 1
        return 1;
85
    }
86

87 1
    int status = 0;
88
    {
89
        scope(exit)
90
        {
91 1
            auto pipeStatus = wait(pipe.pid);
92 1
            if (pipeStatus != 0) status = pipeStatus;
93
        }
94

95 1
        bool headerWritten = false;
96 1
        foreach (filename; splitArgs[0].length > 1 ? splitArgs[0][1..$] : ["-"])
97
        {
98 1
            bool isStdin = (filename == "-");
99 1
            File inputStream;
100

101 1
            if (isStdin) inputStream = stdin;
102
            else
103
            {
104 1
                try inputStream = filename.File();
105
                catch (Exception exc)
106
                {
107 1
                    stderr.writefln("[%s] Unable to open file: '%s'", programName, filename);
108 1
                    stderr.writeln(exc.msg);
109 1
                    status = 1;
110 1
                    break;
111
                }
112
            }
113

114 1
            auto firstLine = inputStream.readln();
115

116 1
            if (inputStream.eof && firstLine.length == 0) continue;
117

118 1
            if (!headerWritten)
119
            {
120 1
                write(firstLine);
121 1
                stdout.flush;
122 1
                headerWritten = true;
123
            }
124

125 1
            if (isStdin)
126
            {
127 1
                foreach (line; inputStream.byLine(KeepTerminator.yes))
128
                {
129 1
                    pipe.stdin.write(line);
130
                }
131
            }
132
            else
133
            {
134 1
                ubyte[1024 * 128] readBuffer;
135 1
                foreach (ubyte[] chunk; inputStream.byChunk(readBuffer))
136
                {
137 1
                    pipe.stdin.write(cast(char[])chunk);
138
                }
139
            }
140 1
            pipe.stdin.flush;
141
        }
142 1
        pipe.stdin.close;
143
    }
144 1
    return status;
145
}

Read our documentation on viewing source code .

Loading