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