1####################################################################
2# Commons.awk                                                      #
3####################################################################
4
5# Initialize constants.
6function initConst() {
7    NULLSTR = ""
8    NONE = "\0"
9    TRUE = 1
10
11    STDIN  = "/dev/stdin"
12    STDOUT = "/dev/stdout"
13    STDERR = "/dev/stderr"
14
15    SUPOUT = " > /dev/null "  # suppress output
16    SUPERR = " 2> /dev/null " # suppress error
17    PIPE = " | "
18}
19
20
21
22## Arrays:
23
24# Return 1 if the array contains anything; otherwise return 0.
25function anything(array,
26                  ####
27                  i) {
28    for (i in array)
29        if (array[i]) return 1
30    return 0
31}
32
33# Return 1 if the value is non-empty or an array that contains anything;
34# Otherwise, return 0.
35function exists(value) {
36    if (isarray(value))
37        return anything(value)
38    else
39        return value ? 1 : 0
40}
41
42# Return an element if it belongs to the array;
43# Otherwise, return a null string.
44function belongsTo(element, array,
45                   ####
46                   i) {
47    for (i in array)
48        if (element == array[i]) return element
49    return NULLSTR
50}
51
52# Return non-zero if two values are identical;
53# Otherwise, return 0.
54function identical(x, y,
55                   ####
56                   i) {
57    if (!isarray(x) && !isarray(y))
58        return x == y
59    else if (isarray(x) && isarray(y)) {
60        if (length(x) != length(y)) return 0
61        for (i in x)
62            if (!identical(x[i], y[i])) return 0
63        return 1
64    } else
65        return 0
66}
67
68# Append an element into an array (zero-based).
69function append(array, element) {
70    array[anything(array) ? length(array) : 0] = element
71}
72
73# Comparator function used for controlling array scanning order.
74# Like @ind_num_asc, but compare on index fields separated by SUBSEP.
75function compareByIndexFields(i1, v1, i2, v2,
76                              ####
77                              t1, t2, tl, j) {
78    split(i1, t1, SUBSEP)
79    split(i2, t2, SUBSEP)
80    tl = length(t1) < length(t2) ? length(t1) : length(t2)
81    for (j = 1; j <= tl; j++) {
82        if (t1[j] < t2[j])
83            return -1
84        else if (t1[j] > t2[j])
85            return 1
86    }
87    return 0
88}
89
90
91
92## Strings:
93
94# Return non-zero if the string represents a numeral;
95# Otherwise, return 0.
96function isnum(string) {
97    return string == string + 0
98}
99
100# Return one of the substrings if the string starts with it;
101# Otherwise, return a null string.
102function startsWithAny(string, substrings,
103                       ####
104                       i) {
105    for (i in substrings)
106        if (index(string, substrings[i]) == 1) return substrings[i]
107    return NULLSTR
108}
109
110# Return one of the patterns if the string matches this pattern at the beginning;
111# Otherwise, return a null string.
112function matchesAny(string, patterns,
113                    ####
114                    i) {
115    for (i in patterns)
116        if (string ~ "^" patterns[i]) return patterns[i]
117    return NULLSTR
118}
119
120# Replicate a string.
121function replicate(string, len,
122                   ####
123                   i, temp) {
124    temp = NULLSTR
125    for (i = 0; i < len; i++)
126        temp = temp string
127    return temp
128}
129
130# Reverse a string.
131function reverse(string,
132                 ####
133                 i, temp) {
134    temp = NULLSTR
135    for (i = length(string); i > 0; i--)
136        temp = temp substr(string, i, 1);
137    return temp
138}
139
140# Join an array into one string;
141# Return the string.
142function join(array, separator, sortedIn, preserveNull,
143              ####
144              i, j, saveSortedIn, temp) {
145    # Default parameter
146    if (!sortedIn)
147        sortedIn = "compareByIndexFields"
148
149    temp = NULLSTR
150    j = 0
151    if (isarray(array)) {
152        saveSortedIn = PROCINFO["sorted_in"]
153        PROCINFO["sorted_in"] = sortedIn
154        for (i in array)
155            if (preserveNull || array[i] != NULLSTR)
156                temp = j++ ? temp separator array[i] : array[i]
157        PROCINFO["sorted_in"] = saveSortedIn
158    } else
159        temp = array
160
161    return temp
162}
163
164# Split a string into characters.
165function explode(string, array) {
166    split(string, array, NULLSTR)
167}
168
169# Return the real character represented by an escape sequence.
170# Example: escapeChar("n") returns "\n".
171# See: <https://en.wikipedia.org/wiki/Escape_character>
172#      <https://en.wikipedia.org/wiki/Escape_sequences_in_C>
173function escapeChar(char) {
174    switch (char) {
175    case "b":
176        return "\b" # Backspace
177    case "f":
178        return "\f" # Formfeed
179    case "n":
180        return "\n" # Newline (Line Feed)
181    case "r":
182        return "\r" # Carriage Return
183    case "t":
184        return "\t" # Horizontal Tab
185    case "v":
186        return "\v" # Vertical Tab
187    case "u0026":
188        return "&" # Unicode Character 'AMPERSAND'
189    case "u003c":
190        return "<" # Unicode Character 'LESS-THAN SIGN'
191    case "u003e":
192        return ">" # Unicode Character 'GREATER-THAN SIGN'
193    case "u200b":
194        return "" # Unicode Character 'ZERO WIDTH SPACE'
195    default:
196        return char
197    }
198}
199
200# Convert a literal-formatted string into its original string.
201function literal(string,
202                 ####
203                 c, cc, escaping, i, s) {
204    if (string !~ /^".*"$/)
205        return string
206
207    explode(string, s)
208    string = NULLSTR
209    escaping = 0
210    for (i = 2; i < length(s); i++) {
211        c = s[i]
212        if (escaping) {
213            if (cc) {
214                cc = cc c
215                if (length(cc) == 5) {
216                    string = string escapeChar(cc)
217                    escaping = 0 # escape ends
218                    cc = NULLSTR
219                }
220            } else if (c == "u") {
221                cc = c
222            } else {
223                string = string escapeChar(c)
224                escaping = 0 # escape ends
225            }
226        } else {
227            if (c == "\\")
228                escaping = 1 # escape begins
229            else
230                string = string c
231        }
232    }
233    return string
234}
235
236# Return the escaped string.
237function escape(string) {
238    gsub(/\\/, "\\\\", string) # substitute backslashes first
239    gsub(/"/, "\\\"", string)
240
241    return string
242}
243
244# Reverse of escape(string).
245function unescape(string) {
246    gsub(/\\"/, "\"", string)
247    gsub(/\\\\/, "\\", string) # substitute backslashes last
248
249    return string
250}
251
252# Return the escaped, quoted string.
253function parameterize(string, quotationMark) {
254    if (!quotationMark)
255        quotationMark = "'"
256
257    if (quotationMark == "'") {
258        gsub(/'/, "'\\''", string)
259        return "'" string "'"
260    } else {
261        return "\"" escape(string) "\""
262    }
263}
264
265# Reverse of parameterize(string, quotationMark).
266function unparameterize(string,    temp) {
267    match(string, /^'(.*)'$/, temp)
268    if (temp[0]) { # use temp[0] (there IS a match for quoted empty string)
269        string = temp[1]
270        gsub(/'\\''/, "'", string)
271        return string
272    }
273    match(string, /^"(.*)"$/, temp)
274    if (temp[0]) {
275        string = temp[1]
276        return unescape(string)
277    }
278    return string
279}
280
281# Convert any value to human-readable string.
282function toString(value, inline, heredoc, valOnly, numSub, level, sortedIn,
283                  ####
284                  i, items, j, k, p, saveSortedIn, temp, v) {
285    if (!level) level = 0
286    if (!sortedIn)
287        sortedIn = "compareByIndexFields"
288
289    if (isarray(value)) {
290        saveSortedIn = PROCINFO["sorted_in"]
291        PROCINFO["sorted_in"] = sortedIn
292        p = 0
293        for (i in value) {
294            split(i, j, SUBSEP); k = join(j, ",")
295            if (!numSub || !isnum(k)) k = parameterize(k, "\"")
296            v = toString(value[i], inline, heredoc, valOnly, numSub, level + 1, sortedIn)
297            if (!isarray(value[i])) v = parameterize(v, "\"")
298            if (valOnly)
299                items[p++] = inline ? v : (replicate("\t", level) v)
300            else
301                items[p++] = inline ? (k ": " v) :
302                    (replicate("\t", level) k "\t" v)
303        }
304        PROCINFO["sorted_in"] = saveSortedIn
305        temp = inline ? join(items, ", ") :
306            ("\n" join(items, "\n") "\n" replicate("\t", level))
307        temp = valOnly ? ("[" temp "]") : ("{" temp "}")
308        return temp
309    } else {
310        if (heredoc)
311            return "'''\n" value "\n'''"
312        else
313            return value
314    }
315}
316
317# Squeeze a source line of AWK code.
318function squeeze(line, preserveIndent) {
319    # Remove preceding spaces if indentation not preserved
320    if (!preserveIndent)
321        gsub(/^[[:space:]]+/, NULLSTR, line)
322    # Remove comment
323    gsub(/^[[:space:]]*#.*$/, NULLSTR, line)
324    # Remove in-line comment
325    gsub(/#[^"/]*$/, NULLSTR, line)
326    # Remove trailing spaces
327    gsub(/[[:space:]]+$/, NULLSTR, line)
328    gsub(/[[:space:]]+\\$/, "\\", line)
329
330    return line
331}
332
333# Return 0 if the string starts with '0', 'f', 'n' or 'off';
334# Otherwise, return 1.
335function yn(string) {
336    return (tolower(string) ~ /^([0fn]|off)/) ? 0 : 1
337}
338
339
340
341## Display & Debugging:
342
343# Initialize ANSI escape codes for SGR (Select Graphic Rendition).
344# (ANSI X3.64 Standard Control Sequences)
345# See: <https://en.wikipedia.org/wiki/ANSI_escape_code>
346function initAnsiCode() {
347    # Dumb terminal: no ANSI escape code whatsoever
348    if (ENVIRON["TERM"] == "dumb") return
349
350    AnsiCode["reset"]         = AnsiCode[0] = "\33[0m"
351
352    AnsiCode["bold"]          = "\33[1m"
353    AnsiCode["underline"]     = "\33[4m"
354    AnsiCode["negative"]      = "\33[7m"
355    AnsiCode["no bold"]       = "\33[22m" # SGR code 21 (bold off) not widely supported
356    AnsiCode["no underline"]  = "\33[24m"
357    AnsiCode["positive"]      = "\33[27m"
358
359    AnsiCode["black"]         = "\33[30m"
360    AnsiCode["red"]           = "\33[31m"
361    AnsiCode["green"]         = "\33[32m"
362    AnsiCode["yellow"]        = "\33[33m"
363    AnsiCode["blue"]          = "\33[34m"
364    AnsiCode["magenta"]       = "\33[35m"
365    AnsiCode["cyan"]          = "\33[36m"
366    AnsiCode["gray"]          = "\33[37m"
367
368    AnsiCode["default"]       = "\33[39m"
369
370    AnsiCode["dark gray"]     = "\33[90m"
371    AnsiCode["light red"]     = "\33[91m"
372    AnsiCode["light green"]   = "\33[92m"
373    AnsiCode["light yellow"]  = "\33[93m"
374    AnsiCode["light blue"]    = "\33[94m"
375    AnsiCode["light magenta"] = "\33[95m"
376    AnsiCode["light cyan"]    = "\33[96m"
377    AnsiCode["white"]         = "\33[97m"
378}
379
380# Return ANSI escaped string.
381function ansi(code, text) {
382    switch (code) {
383    case "bold":
384        return AnsiCode[code] text AnsiCode["no bold"]
385    case "underline":
386        return AnsiCode[code] text AnsiCode["no underline"]
387    case "negative":
388        return AnsiCode[code] text AnsiCode["positive"]
389    default:
390        return AnsiCode[code] text AnsiCode[0]
391    }
392}
393
394# Print warning message.
395function w(text) {
396    print ansi("yellow", text) > STDERR
397}
398
399# Print error message.
400function e(text) {
401    print ansi("bold", ansi("yellow", text)) > STDERR
402}
403
404# What a terrible failure.
405function wtf(text) {
406    print ansi("bold", ansi("red", text)) > STDERR
407}
408
409# Print debugging message.
410function d(text) {
411    print ansi("gray", text) > STDERR
412}
413
414# Debug any value.
415function da(value, name, inline, heredoc, valOnly, numSub, sortedIn,
416            ####
417            i, j, saveSortedIn) {
418    # Default parameters
419    if (!name)
420        name = "_"
421    if (!sortedIn)
422        sortedIn = "compareByIndexFields"
423
424    d(name " = " toString(value, inline, heredoc, valOnly, numSub, 0, sortedIn))
425    #if (isarray(value)) {
426    #    saveSortedIn = PROCINFO["sorted_in"]
427    #    PROCINFO["sorted_in"] = sortedIn
428    #    for (i in value) {
429    #        split(i, j, SUBSEP)
430    #        da(value[i], sprintf(name "[%s]", join(j, ",")), sortedIn)
431    #    }
432    #    PROCINFO["sorted_in"] = saveSortedIn
433    #} else
434    #    d(name " = " value)
435}
436
437# Naive assertion.
438function assert(x, message) {
439    if (!message)
440        message = "[ERROR] Assertion failed."
441
442    if (x)
443        return x
444    else
445        e(message)
446}
447
448
449
450## URLs:
451
452# Initialize `UrlEncoding`.
453# See: <https://en.wikipedia.org/wiki/Percent-encoding>
454function initUrlEncoding() {
455    UrlEncoding["\t"] = "%09"
456    UrlEncoding["\n"] = "%0A"
457    UrlEncoding[" "]  = "%20"
458    UrlEncoding["!"]  = "%21"
459    UrlEncoding["\""] = "%22"
460    UrlEncoding["#"]  = "%23"
461    UrlEncoding["$"]  = "%24"
462    UrlEncoding["%"]  = "%25"
463    UrlEncoding["&"]  = "%26"
464    UrlEncoding["'"]  = "%27"
465    UrlEncoding["("]  = "%28"
466    UrlEncoding[")"]  = "%29"
467    UrlEncoding["*"]  = "%2A"
468    UrlEncoding["+"]  = "%2B"
469    UrlEncoding[","]  = "%2C"
470    UrlEncoding["-"]  = "%2D"
471    UrlEncoding["."]  = "%2E"
472    UrlEncoding["/"]  = "%2F"
473    UrlEncoding[":"]  = "%3A"
474    UrlEncoding[";"]  = "%3B"
475    UrlEncoding["<"]  = "%3C"
476    UrlEncoding["="]  = "%3D"
477    UrlEncoding[">"]  = "%3E"
478    UrlEncoding["?"]  = "%3F"
479    UrlEncoding["@"]  = "%40"
480    UrlEncoding["["]  = "%5B"
481    UrlEncoding["\\"] = "%5C"
482    UrlEncoding["]"]  = "%5D"
483    UrlEncoding["^"]  = "%5E"
484    UrlEncoding["_"]  = "%5F"
485    UrlEncoding["`"]  = "%60"
486    UrlEncoding["{"]  = "%7B"
487    UrlEncoding["|"]  = "%7C"
488    UrlEncoding["}"]  = "%7D"
489    UrlEncoding["~"]  = "%7E"
490}
491
492# Return the URL-encoded string.
493function quote(string,    i, r, s) {
494    r = NULLSTR
495    explode(string, s)
496    for (i = 1; i <= length(s); i++)
497        r = r (s[i] in UrlEncoding ? UrlEncoding[s[i]] : s[i])
498    return r
499}
500
501# Return the URL-decoded string.
502function unquote(string,    i, k, r, s, temp) {
503    r = NULLSTR
504    explode(string, s)
505    temp = NULLSTR
506    for (i = 1; i <= length(s); i++)
507        if (temp) {
508            temp = temp s[i]
509            if (length(temp) > 2) {
510                for (k in UrlEncoding)
511                    if (temp == UrlEncoding[k]) {
512                        r = r k
513                        temp = NULLSTR
514                        break
515                    }
516                if (temp) {
517                    r = r temp
518                    temp = NULLSTR
519                }
520            }
521        } else {
522            if (s[i] != "%")
523                r = r s[i]
524            else
525                temp = s[i]
526        }
527    if (temp)
528        r = r temp
529    return r
530}
531
532# Initialize `UriSchemes`.
533function initUriSchemes() {
534    UriSchemes[0] = "file://"
535    UriSchemes[1] = "http://"
536    UriSchemes[2] = "https://"
537}
538
539
540
541## System:
542
543# Read from a file and return its content.
544function readFrom(file,    line, text) {
545    if (!file) file = "/dev/stdin"
546    text = NULLSTR
547    while (getline line < file)
548        text = (text ? text "\n" : NULLSTR) line
549    return text
550}
551
552# Write text to file.
553function writeTo(text, file) {
554    if (!file) file = "/dev/stdout"
555    print text > file
556}
557
558# Return the output of a command.
559function getOutput(command,    content, line) {
560    content = NULLSTR
561    while ((command |& getline line) > 0)
562        content = (content ? content "\n" : NULLSTR) line
563    close(command)
564    return content
565}
566
567# Return non-zero if file exists; otherwise return 0.
568function fileExists(file) {
569    return !system("test -f " parameterize(file))
570}
571
572# Return non-zero if file exists and is a directory; otherwise return 0.
573function dirExists(file) {
574    return !system("test -d " parameterize(file))
575}
576
577# Detect whether a program exists in path.
578# Return the name (or output) if the program call writes anything to stdout;
579# Otherwise, return a null string.
580function detectProgram(prog, arg, returnOutput,    command, temp) {
581    command = prog " " arg SUPERR
582    command | getline temp
583    close(command)
584    if (returnOutput)
585        return temp
586    if (temp)
587        return prog
588    return NULLSTR
589}
590
591# Return the HEAD revision if the current directory is a git repo;
592# Otherwise return a null string.
593function getGitHead(    line, group) {
594    if (fileExists(".git/HEAD")) {
595        getline line < ".git/HEAD"
596        match(line, /^ref: (.*)$/, group)
597        if (fileExists(".git/" group[1])) {
598            getline line < (".git/" group[1])
599            return substr(line, 1, 7)
600        } else
601            return NULLSTR
602    } else
603        return NULLSTR
604}
605
606
607
608BEGIN {
609    initConst()
610    initAnsiCode()
611    initUrlEncoding()
612    initUriSchemes()
613}
614