[ru] remove deprecated scripts

[ru] remove deprecated scripts


add duration, but it does not activate yet

BEGIN {
    interesting = 1
}

{
    sub(/<title>Haskell: Functional Programming, Solid Code, Big Data<\/title>/, "<title>Intro \\“Functional Programming\\”</title>")
    sub(/<meta name="date" content="2011-09-18" \/>/, "<meta name='date' content='2011-10-19' />")
    sub(/<meta name="author" content="Bryan O'Sullivan" \/>/, "<meta name='author' content='Gabor Greif' />\n  <meta name='duration' content='45' />")
}

/^<body>/ {
    interesting = 0
    print
    next
}

interesting {
    print
    next
}

BEGIN {
    interesting = 1
}

{
    sub(/<title>Haskell: Functional Programming, Solid Code, Big Data<\/title>/, "<title>Intro \\“Functional Programming\\”</title>")
    sub(/<meta name="date" content="2011-09-18" \/>/, "<meta name='date' content='2011-10-19' />")
    sub(/<meta name="author" content="Bryan O'Sullivan" \/>/, "<meta name='author' content='Gabor Greif' />")
}

/^<body>/ {
    interesting = 0
    print
    next
}

interesting {
    print
    next
}


Add date.

Add date.

#!/usr/bin/awk -f
# tdbout -t publisher,plugin,publisher:info[contract],year,publisher:info[tester],status,publisher:info[back],publisher:info[poller] *.tdb

BEGIN {
  #After July 1 of this year, start reporting on this year. Before that, ignore this year.
  str = "date +%m-%d-%Y";
  str | getline date;
  str = "date +%Y";
  str | getline current_year;
  #close str;
  str = "date +%W";
  str | getline week;
  #close str;
  if (week<26) {
  	current_year=current_year-1
  }
  #printf "%s %s", current_year, week;
  FS="\t"
  pn = 0
}
$3>date

{
  # add a loop to add line only if either status is (wanted or testing) or ending year is gt or eq to contract year

  #current_year=2018

  #current_year=`date +%Y`
  #week=`date +%W`
  #if [ $week -lt 26 ]
  #  then
  #  ((current_year--))
    #echo current_year $current_year
  #fi
  #echo current_year $current_year

  # end_year is the AU year, or the second half of a range, ie 2014 in 2013-2014
  end_year = 0
  incontract = 0
  # test_year is the contract year, or the back year if there is one.
  test_year = ""
  if ($7 == "") {
    test_year = $3
  } else {
    test_year = $7
  }
  if (length($4) > 3) {
    end_year = substr($4,length($4)-3,4)
  }
  #printf "%s\t%s\t%s\t%s\t%s\t%s\t%s\n", $1,$2,$3,$4,$7,end_year,test_year
  # Is the AU year >= the contract year or the back year. And then decide what to do with the current year.
  if ($6 == "wanted" || $6 == "testing") {
      incontract = 1;
  } else if ((end_year >= test_year) && ((end_year < current_year) || (end_year == current_year && incl_cur == 1))) {
      incontract = 1;
  }

  if (incontract == 1) {
      nn = split($2,na,/\./)
      lp2 = na[nn]
    if (!(($1,lp2,$4) in b)) {
      p[pn] = $1
      n[pn] = lp2
  #    n[pn] = $2
      t[pn] = $3
      if ($7 == "") {
        k[pn] = ".."
      } else {
        k[pn] = $7
      }
      d[pn] = $4
      r1[pn] = $5
      r2[pn] = $8
      pn++
    }
    b[$1,lp2,$4]++
    c[$1,lp2,$4,$6]++
    x[$6]++
    tt++
  }
}

END {
  s[0] = "expected"
  s[1] = "exists"
  s[2] = "manifest"
  s[3] = "wanted"
  s[4] = "testing"
  s[5] = "notReady"
  s[6] = "ready"
  s[7] = "crawling"
  s[8] = "deepCrawl"
  s[9] = "frozen"
  s[10] = "ingNotReady"
  s[11] = "finished"
  s[12] = "down"
  s[13] = "superseded"
  s[14] = "zapped"
  s[15] = "doNotProcess"
  s[16] = "readySource"
  sn = 17

  sc[0] = "expe"
  sc[1] = "exis"
  sc[2] = "mani"
  sc[3] = "want"
  sc[4] = "test"
  sc[5] = "notR"
  sc[6] = "read"
  sc[7] = "craw"
  sc[8] = "deep"
  sc[9] = "froz"
  sc[10] = "ingN"
  sc[11] = "fini"
  sc[12] = "down"
  sc[13] = "supe"
  sc[14] = "zapp"
  sc[15] = "doNP"
  sc[16] = "rdSc"
  scn = 17

  #print out header
  printf "Publisher\tPlugin\tContr\tBack\tYear\tT\tP\tTotal"
  for (j = 0 ; j < scn ; j++) {
    if (x[s[j]] > 0) {
    printf "\t%s", sc[j]
    }
  }
  printf "\n"

  #print out publisher, plugin, contract year, back, year, tester, poller, total aus
  for (i = 0 ; i < pn ; i++) {
    printf "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%d", p[i], n[i], t[i], k[i], d[i], r1[i], r2[i], b[p[i],n[i],d[i]]
    for (j = 0 ; j < sn ; j++) {
      if (x[s[j]] > 0){
      if (c[p[i],n[i],d[i],s[j]] == 0) {
        printf "\t.."
      } else {
        printf "\t%d", c[p[i],n[i],d[i],s[j]]
      }
    }
    }
    printf "\n"
  }
    #print out bottom line sums
    printf "Publisher\tPlugin\tContr\tBack\tYear\tT\tP\t%d", tt
    for (j = 0 ; j < sn ; j++) {
      if (x[s[j]] > 0) {
        printf "\t%d", x[s[j]]
      }
    }

  printf "\n"
  printf "%s", date
  printf "\n"

}

#!/usr/bin/awk -f
# tdbout -t publisher,plugin,publisher:info[contract],year,publisher:info[tester],status,publisher:info[back],publisher:info[poller] *.tdb

BEGIN {
  #After July 1 of this year, start reporting on this year. Before that, ignore this year.
  str = "date +%Y";
  str | getline current_year;
  #close str;
  str = "date +%W";
  str | getline week;
  #close str;
  if (week<26) {
  	current_year=current_year-1
  }
  #printf "%s %s", current_year, week;
  FS="\t"
  pn = 0
}

{
  # add a loop to add line only if either status is (wanted or testing) or ending year is gt or eq to contract year

  #current_year=2018

  #current_year=`date +%Y`
  #week=`date +%W`
  #if [ $week -lt 26 ]
  #  then
  #  ((current_year--))
    #echo current_year $current_year
  #fi
  #echo current_year $current_year

  # end_year is the AU year, or the second half of a range, ie 2014 in 2013-2014
  end_year = 0
  incontract = 0
  # test_year is the contract year, or the back year if there is one.
  test_year = ""
  if ($7 == "") {
    test_year = $3
  } else {
    test_year = $7
  }
  if (length($4) > 3) {
    end_year = substr($4,length($4)-3,4)
  }
  #printf "%s\t%s\t%s\t%s\t%s\t%s\t%s\n", $1,$2,$3,$4,$7,end_year,test_year
  # Is the AU year >= the contract year or the back year. And then decide what to do with the current year.
  if ($6 == "wanted" || $6 == "testing") {
      incontract = 1;
  } else if ((end_year >= test_year) && ((end_year < current_year) || (end_year == current_year && incl_cur == 1))) {
      incontract = 1;
  }

  if (incontract == 1) {
      nn = split($2,na,/\./)
      lp2 = na[nn]
    if (!(($1,lp2,$4) in b)) {
      p[pn] = $1
      n[pn] = lp2
  #    n[pn] = $2
      t[pn] = $3
      if ($7 == "") {
        k[pn] = ".."
      } else {
        k[pn] = $7
      }
      d[pn] = $4
      r1[pn] = $5
      r2[pn] = $8
      pn++
    }
    b[$1,lp2,$4]++
    c[$1,lp2,$4,$6]++
    x[$6]++
    tt++
  }
}

END {
  s[0] = "expected"
  s[1] = "exists"
  s[2] = "manifest"
  s[3] = "wanted"
  s[4] = "testing"
  s[5] = "notReady"
  s[6] = "ready"
  s[7] = "crawling"
  s[8] = "deepCrawl"
  s[9] = "frozen"
  s[10] = "ingNotReady"
  s[11] = "finished"
  s[12] = "down"
  s[13] = "superseded"
  s[14] = "zapped"
  s[15] = "doNotProcess"
  s[16] = "readySource"
  sn = 17

  sc[0] = "expe"
  sc[1] = "exis"
  sc[2] = "mani"
  sc[3] = "want"
  sc[4] = "test"
  sc[5] = "notR"
  sc[6] = "read"
  sc[7] = "craw"
  sc[8] = "deep"
  sc[9] = "froz"
  sc[10] = "ingN"
  sc[11] = "fini"
  sc[12] = "down"
  sc[13] = "supe"
  sc[14] = "zapp"
  sc[15] = "doNP"
  sc[16] = "rdSc"
  scn = 17

  #print out header
  printf "Publisher\tPlugin\tContr\tBack\tYear\tT\tP\tTotal"
  for (j = 0 ; j < scn ; j++) {
    if (x[s[j]] > 0) {
    printf "\t%s", sc[j]
    }
  }
  printf "\n"

  #print out publisher, plugin, contract year, back, year, tester, poller, total aus
  for (i = 0 ; i < pn ; i++) {
    printf "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%d", p[i], n[i], t[i], k[i], d[i], r1[i], r2[i], b[p[i],n[i],d[i]]
    for (j = 0 ; j < sn ; j++) {
      if (x[s[j]] > 0){
      if (c[p[i],n[i],d[i],s[j]] == 0) {
        printf "\t.."
      } else {
        printf "\t%d", c[p[i],n[i],d[i],s[j]]
      }
    }
    }
    printf "\n"
  }
    #print out bottom line sums
    printf "Publisher\tPlugin\tContr\tBack\tYear\tT\tP\t%d", tt
    for (j = 0 ; j < sn ; j++) {
      if (x[s[j]] > 0) {
        printf "\t%d", x[s[j]]
      }
    }

  printf "\n"
  #printf "%s", date
  #printf "\n"
}


droid

droid

BEGIN {
   print "@prefix dcterms: <http://purl.org/dc/terms/> ."
   print
}

{
   if( $0 ~ /^.*:.*!.*,.*$/) {

      container=$0
      sub(/!\/.*$/,"", container)
      container_format=container
      sub(/:\/.*$/,"", container_format)
      sub(/^.*:\//,"", container)

      contained=$0
      sub(/^.*!\//,"", contained)
      sub(/,[^,].*$/,"",contained)

      format=$0
      sub(/^.*,/,"", format)
      sub(/-/,   "/",format)

      print "<"container">"
      print "   dcterms:hasPart <"container"/"contained">;"
      print "."
      print "<"container"/"contained"> dcterms:format <http://provenanceweb.org/formats/pronom/"format"> ;"
      print ""

   } else {

      file=$0
      sub(/,[^,]*$/,"",file)

      format=$0
      sub(/^.*,/,"", format)
      sub(/-/,   "/",format)

      if( tolower(format) != "unknown" ) {
         print "<"file"> dcterms:format <http://provenanceweb.org/formats/pronom/"format"> ."
         print ""
      } else {
         # SIO-Qualify the unknown and say that DROID doesn't know.
      }
   }
}


Commons: handle Unicode '=' correctly, fix #420

####################################################################
# Commons.awk                                                      #
####################################################################

# Initialize constants.
function initConst() {
    NULLSTR = ""
    NONE = "\0"
    TRUE = 1

    STDIN  = "/dev/stdin"
    STDOUT = "/dev/stdout"
    STDERR = "/dev/stderr"

    SUPOUT = " > /dev/null "  # suppress output
    SUPERR = " 2> /dev/null " # suppress error
    PIPE = " | "
}

## Arrays:

# Return 1 if the array contains anything; otherwise return 0.
function anything(array,
                  ####
                  i) {
    for (i in array)
        if (array[i]) return 1
    return 0
}

# Return 1 if the value is non-empty or an array that contains anything;
# Otherwise, return 0.
function exists(value) {
    if (isarray(value))
        return anything(value)
    else
        return value ? 1 : 0
}

# Return an element if it belongs to the array;
# Otherwise, return a null string.
function belongsTo(element, array,
                   ####
                   i) {
    for (i in array)
        if (element == array[i]) return element
    return NULLSTR
}

# Return non-zero if two values are identical;
# Otherwise, return 0.
function identical(x, y,
                   ####
                   i) {
    if (!isarray(x) && !isarray(y))
        return x == y
    else if (isarray(x) && isarray(y)) {
        if (length(x) != length(y)) return 0
        for (i in x)
            if (!identical(x[i], y[i])) return 0
        return 1
    } else
        return 0
}

# Append an element into an array (zero-based).
function append(array, element) {
    array[anything(array) ? length(array) : 0] = element
}

# Comparator function used for controlling array scanning order.
# Like @ind_num_asc, but compare on index fields separated by SUBSEP.
function compareByIndexFields(i1, v1, i2, v2,
                              ####
                              t1, t2, tl, j) {
    split(i1, t1, SUBSEP)
    split(i2, t2, SUBSEP)
    tl = length(t1) < length(t2) ? length(t1) : length(t2)
    for (j = 1; j <= tl; j++) {
        if (t1[j] < t2[j])
            return -1
        else if (t1[j] > t2[j])
            return 1
    }
    return 0
}

## Strings:

# Return non-zero if the string represents a numeral;
# Otherwise, return 0.
function isnum(string) {
    return string == string + 0
}

# Return one of the substrings if the string starts with it;
# Otherwise, return a null string.
function startsWithAny(string, substrings,
                       ####
                       i) {
    for (i in substrings)
        if (index(string, substrings[i]) == 1) return substrings[i]
    return NULLSTR
}

# Return one of the patterns if the string matches this pattern at the beginning;
# Otherwise, return a null string.
function matchesAny(string, patterns,
                    ####
                    i) {
    for (i in patterns)
        if (string ~ "^" patterns[i]) return patterns[i]
    return NULLSTR
}

# Make the first character of string upper-case.
function ucfirst(string) {
    if (length(string) >= 2)
        return toupper(substr(string, 1, 1)) substr(string, 2)
    else if (length(string) == 1)
        return toupper(string)

    return NULLSTR
}

# Replicate a string.
function replicate(string, len,
                   ####
                   i, temp) {
    temp = NULLSTR
    for (i = 0; i < len; i++)
        temp = temp string
    return temp
}

# Reverse a string.
function reverse(string,
                 ####
                 i, temp) {
    temp = NULLSTR
    for (i = length(string); i > 0; i--)
        temp = temp substr(string, i, 1);
    return temp
}

# Join an array into one string;
# Return the string.
function join(array, separator, sortedIn, preserveNull,
              ####
              i, j, saveSortedIn, temp) {
    # Default parameter
    if (!sortedIn)
        sortedIn = "compareByIndexFields"

    temp = NULLSTR
    j = 0
    if (isarray(array)) {
        saveSortedIn = PROCINFO["sorted_in"]
        PROCINFO["sorted_in"] = sortedIn
        for (i in array)
            if (preserveNull || array[i] != NULLSTR)
                temp = j++ ? temp separator array[i] : array[i]
        PROCINFO["sorted_in"] = saveSortedIn
    } else
        temp = array

    return temp
}

# Split a string into characters.
function explode(string, array) {
    split(string, array, NULLSTR)
}

# Return the real character represented by an escape sequence.
# Example: escapeChar("n") returns "\n".
# See: <https://en.wikipedia.org/wiki/Escape_character>
#      <https://en.wikipedia.org/wiki/Escape_sequences_in_C>
function escapeChar(char) {
    switch (char) {
    case "b":
        return "\b" # Backspace
    case "f":
        return "\f" # Formfeed
    case "n":
        return "\n" # Newline (Line Feed)
    case "r":
        return "\r" # Carriage Return
    case "t":
        return "\t" # Horizontal Tab
    case "v":
        return "\v" # Vertical Tab
    case "u0026":
        return "&" # Unicode Character 'AMPERSAND'
    case "u003c":
        return "<" # Unicode Character 'LESS-THAN SIGN'
    case "u003d":
        return "=" # Unicode Character 'EQUALS SIGN'
    case "u003e":
        return ">" # Unicode Character 'GREATER-THAN SIGN'
    case "u200b":
        return "" # Unicode Character 'ZERO WIDTH SPACE'
    default:
        return char
    }
}

# Convert a literal-formatted string into its original string.
function literal(string,
                 ####
                 c, cc, escaping, i, s) {
    if (string !~ /^".*"$/)
        return string

    explode(string, s)
    string = NULLSTR
    escaping = 0
    for (i = 2; i < length(s); i++) {
        c = s[i]
        if (escaping) {
            if (cc) {
                cc = cc c
                if (length(cc) == 5) {
                    string = string escapeChar(cc)
                    escaping = 0 # escape ends
                    cc = NULLSTR
                }
            } else if (c == "u") {
                cc = c
            } else {
                string = string escapeChar(c)
                escaping = 0 # escape ends
            }
        } else {
            if (c == "\\")
                escaping = 1 # escape begins
            else
                string = string c
        }
    }
    return string
}

# Return the escaped string.
function escape(string) {
    gsub(/\\/, "\\\\", string) # substitute backslashes first
    gsub(/"/, "\\\"", string)

    return string
}

# Reverse of escape(string).
function unescape(string) {
    gsub(/\\"/, "\"", string)
    gsub(/\\\\/, "\\", string) # substitute backslashes last

    return string
}

# Return the escaped, quoted string.
function parameterize(string, quotationMark) {
    if (!quotationMark)
        quotationMark = "'"

    if (quotationMark == "'") {
        gsub(/'/, "'\\''", string)
        return "'" string "'"
    } else {
        return "\"" escape(string) "\""
    }
}

# Reverse of parameterize(string, quotationMark).
function unparameterize(string,    temp) {
    match(string, /^'(.*)'$/, temp)
    if (temp[0]) { # use temp[0] (there IS a match for quoted empty string)
        string = temp[1]
        gsub(/'\\''/, "'", string)
        return string
    }
    match(string, /^"(.*)"$/, temp)
    if (temp[0]) {
        string = temp[1]
        return unescape(string)
    }
    return string
}

# Convert any value to human-readable string.
function toString(value, inline, heredoc, valOnly, numSub, level, sortedIn,
                  ####
                  i, items, j, k, p, saveSortedIn, temp, v) {
    if (!level) level = 0
    if (!sortedIn)
        sortedIn = "compareByIndexFields"

    if (isarray(value)) {
        saveSortedIn = PROCINFO["sorted_in"]
        PROCINFO["sorted_in"] = sortedIn
        p = 0
        for (i in value) {
            split(i, j, SUBSEP); k = join(j, ",")
            if (!numSub || !isnum(k)) k = parameterize(k, "\"")
            v = toString(value[i], inline, heredoc, valOnly, numSub, level + 1, sortedIn)
            if (!isarray(value[i])) v = parameterize(v, "\"")
            if (valOnly)
                items[p++] = inline ? v : (replicate("\t", level) v)
            else
                items[p++] = inline ? (k ": " v) :
                    (replicate("\t", level) k "\t" v)
        }
        PROCINFO["sorted_in"] = saveSortedIn
        temp = inline ? join(items, ", ") :
            ("\n" join(items, "\n") "\n" replicate("\t", level))
        temp = valOnly ? ("[" temp "]") : ("{" temp "}")
        return temp
    } else {
        if (heredoc)
            return "'''\n" value "\n'''"
        else
            return value
    }
}

# Squeeze a source line of AWK code.
function squeeze(line, preserveIndent) {
    # Remove preceding spaces if indentation not preserved
    if (!preserveIndent)
        gsub(/^[[:space:]]+/, NULLSTR, line)
    # Remove comment
    gsub(/^[[:space:]]*#.*$/, NULLSTR, line)
    # Remove in-line comment
    gsub(/#[^"/]*$/, NULLSTR, line)
    # Remove trailing spaces
    gsub(/[[:space:]]+$/, NULLSTR, line)
    gsub(/[[:space:]]+\\$/, "\\", line)

    return line
}

# Return 0 if the string starts with '0', 'f', 'n' or 'off';
# Otherwise, return 1.
function yn(string) {
    return (tolower(string) ~ /^([0fn]|off)/) ? 0 : 1
}

## Display & Debugging:

# Initialize ANSI escape codes for SGR (Select Graphic Rendition).
# (ANSI X3.64 Standard Control Sequences)
# See: <https://en.wikipedia.org/wiki/ANSI_escape_code>
function initAnsiCode() {
    # Dumb terminal: no ANSI escape code whatsoever
    if (ENVIRON["TERM"] == "dumb") return

    AnsiCode["reset"]         = AnsiCode[0] = "\33[0m"

    AnsiCode["bold"]          = "\33[1m"
    AnsiCode["underline"]     = "\33[4m"
    AnsiCode["negative"]      = "\33[7m"
    AnsiCode["no bold"]       = "\33[22m" # SGR code 21 (bold off) not widely supported
    AnsiCode["no underline"]  = "\33[24m"
    AnsiCode["positive"]      = "\33[27m"

    AnsiCode["black"]         = "\33[30m"
    AnsiCode["red"]           = "\33[31m"
    AnsiCode["green"]         = "\33[32m"
    AnsiCode["yellow"]        = "\33[33m"
    AnsiCode["blue"]          = "\33[34m"
    AnsiCode["magenta"]       = "\33[35m"
    AnsiCode["cyan"]          = "\33[36m"
    AnsiCode["gray"]          = "\33[37m"

    AnsiCode["default"]       = "\33[39m"

    AnsiCode["dark gray"]     = "\33[90m"
    AnsiCode["light red"]     = "\33[91m"
    AnsiCode["light green"]   = "\33[92m"
    AnsiCode["light yellow"]  = "\33[93m"
    AnsiCode["light blue"]    = "\33[94m"
    AnsiCode["light magenta"] = "\33[95m"
    AnsiCode["light cyan"]    = "\33[96m"
    AnsiCode["white"]         = "\33[97m"
}

# Return ANSI escaped string.
function ansi(code, text) {
    switch (code) {
    case "bold":
        return AnsiCode[code] text AnsiCode["no bold"]
    case "underline":
        return AnsiCode[code] text AnsiCode["no underline"]
    case "negative":
        return AnsiCode[code] text AnsiCode["positive"]
    default:
        return AnsiCode[code] text AnsiCode[0]
    }
}

# Print warning message.
function w(text) {
    print ansi("yellow", text) > STDERR
}

# Print error message.
function e(text) {
    print ansi("bold", ansi("yellow", text)) > STDERR
}

# What a terrible failure.
function wtf(text) {
    print ansi("bold", ansi("red", text)) > STDERR
}

# Print debugging message.
function d(text) {
    print ansi("gray", text) > STDERR
}

# Debug any value.
function da(value, name, inline, heredoc, valOnly, numSub, sortedIn,
            ####
            i, j, saveSortedIn) {
    # Default parameters
    if (!name)
        name = "_"
    if (!sortedIn)
        sortedIn = "compareByIndexFields"

    d(name " = " toString(value, inline, heredoc, valOnly, numSub, 0, sortedIn))
    #if (isarray(value)) {
    #    saveSortedIn = PROCINFO["sorted_in"]
    #    PROCINFO["sorted_in"] = sortedIn
    #    for (i in value) {
    #        split(i, j, SUBSEP)
    #        da(value[i], sprintf(name "[%s]", join(j, ",")), sortedIn)
    #    }
    #    PROCINFO["sorted_in"] = saveSortedIn
    #} else
    #    d(name " = " value)
}

# Naive assertion.
function assert(x, message) {
    if (!message)
        message = "[ERROR] Assertion failed."

    if (x)
        return x
    else
        e(message)
}

## URLs:

# Initialize `UrlEncoding`.
# See: <https://en.wikipedia.org/wiki/Percent-encoding>
function initUrlEncoding() {
    UrlEncoding["\t"] = "%09"
    UrlEncoding["\n"] = "%0A"
    UrlEncoding[" "]  = "%20"
    UrlEncoding["!"]  = "%21"
    UrlEncoding["\""] = "%22"
    UrlEncoding["#"]  = "%23"
    UrlEncoding["$"]  = "%24"
    UrlEncoding["%"]  = "%25"
    UrlEncoding["&"]  = "%26"
    UrlEncoding["'"]  = "%27"
    UrlEncoding["("]  = "%28"
    UrlEncoding[")"]  = "%29"
    UrlEncoding["*"]  = "%2A"
    UrlEncoding["+"]  = "%2B"
    UrlEncoding[","]  = "%2C"
    UrlEncoding["-"]  = "%2D"
    UrlEncoding["."]  = "%2E"
    UrlEncoding["/"]  = "%2F"
    UrlEncoding[":"]  = "%3A"
    UrlEncoding[";"]  = "%3B"
    UrlEncoding["<"]  = "%3C"
    UrlEncoding["="]  = "%3D"
    UrlEncoding[">"]  = "%3E"
    UrlEncoding["?"]  = "%3F"
    UrlEncoding["@"]  = "%40"
    UrlEncoding["["]  = "%5B"
    UrlEncoding["\\"] = "%5C"
    UrlEncoding["]"]  = "%5D"
    UrlEncoding["^"]  = "%5E"
    UrlEncoding["_"]  = "%5F"
    UrlEncoding["`"]  = "%60"
    UrlEncoding["{"]  = "%7B"
    UrlEncoding["|"]  = "%7C"
    UrlEncoding["}"]  = "%7D"
    UrlEncoding["~"]  = "%7E"
}

# Return the URL-encoded string.
function quote(string,    i, r, s) {
    r = NULLSTR
    explode(string, s)
    for (i = 1; i <= length(s); i++)
        r = r (s[i] in UrlEncoding ? UrlEncoding[s[i]] : s[i])
    return r
}

# Return the URL-decoded string.
function unquote(string,    i, k, r, s, temp) {
    r = NULLSTR
    explode(string, s)
    temp = NULLSTR
    for (i = 1; i <= length(s); i++)
        if (temp) {
            temp = temp s[i]
            if (length(temp) > 2) {
                for (k in UrlEncoding)
                    if (temp == UrlEncoding[k]) {
                        r = r k
                        temp = NULLSTR
                        break
                    }
                if (temp) {
                    r = r temp
                    temp = NULLSTR
                }
            }
        } else {
            if (s[i] != "%")
                r = r s[i]
            else
                temp = s[i]
        }
    if (temp)
        r = r temp
    return r
}

# Initialize `UriSchemes`.
function initUriSchemes() {
    UriSchemes[0] = "file://"
    UriSchemes[1] = "http://"
    UriSchemes[2] = "https://"
}

## System:

# Read from a file and return its content.
function readFrom(file,    line, text) {
    if (!file) file = "/dev/stdin"
    text = NULLSTR
    while (getline line < file)
        text = (text ? text "\n" : NULLSTR) line
    return text
}

# Write text to file.
function writeTo(text, file) {
    if (!file) file = "/dev/stdout"
    print text > file
}

# Return the output of a command.
function getOutput(command,    content, line) {
    content = NULLSTR
    while ((command |& getline line) > 0)
        content = (content ? content "\n" : NULLSTR) line
    close(command)
    return content
}

# Return non-zero if file exists; otherwise return 0.
function fileExists(file) {
    return !system("test -f " parameterize(file))
}

# Return non-zero if file exists and is a directory; otherwise return 0.
function dirExists(file) {
    return !system("test -d " parameterize(file))
}

# Detect whether a program exists in path.
# Return the name (or output) if the program call writes anything to stdout;
# Otherwise, return a null string.
function detectProgram(prog, arg, returnOutput,    command, temp) {
    command = prog " " arg SUPERR
    command | getline temp
    close(command)
    if (returnOutput)
        return temp
    if (temp)
        return prog
    return NULLSTR
}

# Return the HEAD revision if the current directory is a git repo;
# Otherwise return a null string.
function getGitHead(    line, group) {
    if (fileExists(".git/HEAD")) {
        getline line < ".git/HEAD"
        match(line, /^ref: (.*)$/, group)
        if (fileExists(".git/" group[1])) {
            getline line < (".git/" group[1])
            return substr(line, 1, 7)
        } else
            return NULLSTR
    } else
        return NULLSTR
}

BEGIN {
    initConst()
    initAnsiCode()
    initUrlEncoding()
    initUriSchemes()
}

####################################################################
# Commons.awk                                                      #
####################################################################

# Initialize constants.
function initConst() {
    NULLSTR = ""
    NONE = "\0"
    TRUE = 1

    STDIN  = "/dev/stdin"
    STDOUT = "/dev/stdout"
    STDERR = "/dev/stderr"

    SUPOUT = " > /dev/null "  # suppress output
    SUPERR = " 2> /dev/null " # suppress error
    PIPE = " | "
}

## Arrays:

# Return 1 if the array contains anything; otherwise return 0.
function anything(array,
                  ####
                  i) {
    for (i in array)
        if (array[i]) return 1
    return 0
}

# Return 1 if the value is non-empty or an array that contains anything;
# Otherwise, return 0.
function exists(value) {
    if (isarray(value))
        return anything(value)
    else
        return value ? 1 : 0
}

# Return an element if it belongs to the array;
# Otherwise, return a null string.
function belongsTo(element, array,
                   ####
                   i) {
    for (i in array)
        if (element == array[i]) return element
    return NULLSTR
}

# Return non-zero if two values are identical;
# Otherwise, return 0.
function identical(x, y,
                   ####
                   i) {
    if (!isarray(x) && !isarray(y))
        return x == y
    else if (isarray(x) && isarray(y)) {
        if (length(x) != length(y)) return 0
        for (i in x)
            if (!identical(x[i], y[i])) return 0
        return 1
    } else
        return 0
}

# Append an element into an array (zero-based).
function append(array, element) {
    array[anything(array) ? length(array) : 0] = element
}

# Comparator function used for controlling array scanning order.
# Like @ind_num_asc, but compare on index fields separated by SUBSEP.
function compareByIndexFields(i1, v1, i2, v2,
                              ####
                              t1, t2, tl, j) {
    split(i1, t1, SUBSEP)
    split(i2, t2, SUBSEP)
    tl = length(t1) < length(t2) ? length(t1) : length(t2)
    for (j = 1; j <= tl; j++) {
        if (t1[j] < t2[j])
            return -1
        else if (t1[j] > t2[j])
            return 1
    }
    return 0
}

## Strings:

# Return non-zero if the string represents a numeral;
# Otherwise, return 0.
function isnum(string) {
    return string == string + 0
}

# Return one of the substrings if the string starts with it;
# Otherwise, return a null string.
function startsWithAny(string, substrings,
                       ####
                       i) {
    for (i in substrings)
        if (index(string, substrings[i]) == 1) return substrings[i]
    return NULLSTR
}

# Return one of the patterns if the string matches this pattern at the beginning;
# Otherwise, return a null string.
function matchesAny(string, patterns,
                    ####
                    i) {
    for (i in patterns)
        if (string ~ "^" patterns[i]) return patterns[i]
    return NULLSTR
}

# Make the first character of string upper-case.
function ucfirst(string) {
    if (length(string) >= 2)
        return toupper(substr(string, 1, 1)) substr(string, 2)
    else if (length(string) == 1)
        return toupper(string)

    return NULLSTR
}

# Replicate a string.
function replicate(string, len,
                   ####
                   i, temp) {
    temp = NULLSTR
    for (i = 0; i < len; i++)
        temp = temp string
    return temp
}

# Reverse a string.
function reverse(string,
                 ####
                 i, temp) {
    temp = NULLSTR
    for (i = length(string); i > 0; i--)
        temp = temp substr(string, i, 1);
    return temp
}

# Join an array into one string;
# Return the string.
function join(array, separator, sortedIn, preserveNull,
              ####
              i, j, saveSortedIn, temp) {
    # Default parameter
    if (!sortedIn)
        sortedIn = "compareByIndexFields"

    temp = NULLSTR
    j = 0
    if (isarray(array)) {
        saveSortedIn = PROCINFO["sorted_in"]
        PROCINFO["sorted_in"] = sortedIn
        for (i in array)
            if (preserveNull || array[i] != NULLSTR)
                temp = j++ ? temp separator array[i] : array[i]
        PROCINFO["sorted_in"] = saveSortedIn
    } else
        temp = array

    return temp
}

# Split a string into characters.
function explode(string, array) {
    split(string, array, NULLSTR)
}

# Return the real character represented by an escape sequence.
# Example: escapeChar("n") returns "\n".
# See: <https://en.wikipedia.org/wiki/Escape_character>
#      <https://en.wikipedia.org/wiki/Escape_sequences_in_C>
function escapeChar(char) {
    switch (char) {
    case "b":
        return "\b" # Backspace
    case "f":
        return "\f" # Formfeed
    case "n":
        return "\n" # Newline (Line Feed)
    case "r":
        return "\r" # Carriage Return
    case "t":
        return "\t" # Horizontal Tab
    case "v":
        return "\v" # Vertical Tab
    case "u0026":
        return "&" # Unicode Character 'AMPERSAND'
    case "u003c":
        return "<" # Unicode Character 'LESS-THAN SIGN'
    case "u003e":
        return ">" # Unicode Character 'GREATER-THAN SIGN'
    case "u200b":
        return "" # Unicode Character 'ZERO WIDTH SPACE'
    default:
        return char
    }
}

# Convert a literal-formatted string into its original string.
function literal(string,
                 ####
                 c, cc, escaping, i, s) {
    if (string !~ /^".*"$/)
        return string

    explode(string, s)
    string = NULLSTR
    escaping = 0
    for (i = 2; i < length(s); i++) {
        c = s[i]
        if (escaping) {
            if (cc) {
                cc = cc c
                if (length(cc) == 5) {
                    string = string escapeChar(cc)
                    escaping = 0 # escape ends
                    cc = NULLSTR
                }
            } else if (c == "u") {
                cc = c
            } else {
                string = string escapeChar(c)
                escaping = 0 # escape ends
            }
        } else {
            if (c == "\\")
                escaping = 1 # escape begins
            else
                string = string c
        }
    }
    return string
}

# Return the escaped string.
function escape(string) {
    gsub(/\\/, "\\\\", string) # substitute backslashes first
    gsub(/"/, "\\\"", string)

    return string
}

# Reverse of escape(string).
function unescape(string) {
    gsub(/\\"/, "\"", string)
    gsub(/\\\\/, "\\", string) # substitute backslashes last

    return string
}

# Return the escaped, quoted string.
function parameterize(string, quotationMark) {
    if (!quotationMark)
        quotationMark = "'"

    if (quotationMark == "'") {
        gsub(/'/, "'\\''", string)
        return "'" string "'"
    } else {
        return "\"" escape(string) "\""
    }
}

# Reverse of parameterize(string, quotationMark).
function unparameterize(string,    temp) {
    match(string, /^'(.*)'$/, temp)
    if (temp[0]) { # use temp[0] (there IS a match for quoted empty string)
        string = temp[1]
        gsub(/'\\''/, "'", string)
        return string
    }
    match(string, /^"(.*)"$/, temp)
    if (temp[0]) {
        string = temp[1]
        return unescape(string)
    }
    return string
}

# Convert any value to human-readable string.
function toString(value, inline, heredoc, valOnly, numSub, level, sortedIn,
                  ####
                  i, items, j, k, p, saveSortedIn, temp, v) {
    if (!level) level = 0
    if (!sortedIn)
        sortedIn = "compareByIndexFields"

    if (isarray(value)) {
        saveSortedIn = PROCINFO["sorted_in"]
        PROCINFO["sorted_in"] = sortedIn
        p = 0
        for (i in value) {
            split(i, j, SUBSEP); k = join(j, ",")
            if (!numSub || !isnum(k)) k = parameterize(k, "\"")
            v = toString(value[i], inline, heredoc, valOnly, numSub, level + 1, sortedIn)
            if (!isarray(value[i])) v = parameterize(v, "\"")
            if (valOnly)
                items[p++] = inline ? v : (replicate("\t", level) v)
            else
                items[p++] = inline ? (k ": " v) :
                    (replicate("\t", level) k "\t" v)
        }
        PROCINFO["sorted_in"] = saveSortedIn
        temp = inline ? join(items, ", ") :
            ("\n" join(items, "\n") "\n" replicate("\t", level))
        temp = valOnly ? ("[" temp "]") : ("{" temp "}")
        return temp
    } else {
        if (heredoc)
            return "'''\n" value "\n'''"
        else
            return value
    }
}

# Squeeze a source line of AWK code.
function squeeze(line, preserveIndent) {
    # Remove preceding spaces if indentation not preserved
    if (!preserveIndent)
        gsub(/^[[:space:]]+/, NULLSTR, line)
    # Remove comment
    gsub(/^[[:space:]]*#.*$/, NULLSTR, line)
    # Remove in-line comment
    gsub(/#[^"/]*$/, NULLSTR, line)
    # Remove trailing spaces
    gsub(/[[:space:]]+$/, NULLSTR, line)
    gsub(/[[:space:]]+\\$/, "\\", line)

    return line
}

# Return 0 if the string starts with '0', 'f', 'n' or 'off';
# Otherwise, return 1.
function yn(string) {
    return (tolower(string) ~ /^([0fn]|off)/) ? 0 : 1
}

## Display & Debugging:

# Initialize ANSI escape codes for SGR (Select Graphic Rendition).
# (ANSI X3.64 Standard Control Sequences)
# See: <https://en.wikipedia.org/wiki/ANSI_escape_code>
function initAnsiCode() {
    # Dumb terminal: no ANSI escape code whatsoever
    if (ENVIRON["TERM"] == "dumb") return

    AnsiCode["reset"]         = AnsiCode[0] = "\33[0m"

    AnsiCode["bold"]          = "\33[1m"
    AnsiCode["underline"]     = "\33[4m"
    AnsiCode["negative"]      = "\33[7m"
    AnsiCode["no bold"]       = "\33[22m" # SGR code 21 (bold off) not widely supported
    AnsiCode["no underline"]  = "\33[24m"
    AnsiCode["positive"]      = "\33[27m"

    AnsiCode["black"]         = "\33[30m"
    AnsiCode["red"]           = "\33[31m"
    AnsiCode["green"]         = "\33[32m"
    AnsiCode["yellow"]        = "\33[33m"
    AnsiCode["blue"]          = "\33[34m"
    AnsiCode["magenta"]       = "\33[35m"
    AnsiCode["cyan"]          = "\33[36m"
    AnsiCode["gray"]          = "\33[37m"

    AnsiCode["default"]       = "\33[39m"

    AnsiCode["dark gray"]     = "\33[90m"
    AnsiCode["light red"]     = "\33[91m"
    AnsiCode["light green"]   = "\33[92m"
    AnsiCode["light yellow"]  = "\33[93m"
    AnsiCode["light blue"]    = "\33[94m"
    AnsiCode["light magenta"] = "\33[95m"
    AnsiCode["light cyan"]    = "\33[96m"
    AnsiCode["white"]         = "\33[97m"
}

# Return ANSI escaped string.
function ansi(code, text) {
    switch (code) {
    case "bold":
        return AnsiCode[code] text AnsiCode["no bold"]
    case "underline":
        return AnsiCode[code] text AnsiCode["no underline"]
    case "negative":
        return AnsiCode[code] text AnsiCode["positive"]
    default:
        return AnsiCode[code] text AnsiCode[0]
    }
}

# Print warning message.
function w(text) {
    print ansi("yellow", text) > STDERR
}

# Print error message.
function e(text) {
    print ansi("bold", ansi("yellow", text)) > STDERR
}

# What a terrible failure.
function wtf(text) {
    print ansi("bold", ansi("red", text)) > STDERR
}

# Print debugging message.
function d(text) {
    print ansi("gray", text) > STDERR
}

# Debug any value.
function da(value, name, inline, heredoc, valOnly, numSub, sortedIn,
            ####
            i, j, saveSortedIn) {
    # Default parameters
    if (!name)
        name = "_"
    if (!sortedIn)
        sortedIn = "compareByIndexFields"

    d(name " = " toString(value, inline, heredoc, valOnly, numSub, 0, sortedIn))
    #if (isarray(value)) {
    #    saveSortedIn = PROCINFO["sorted_in"]
    #    PROCINFO["sorted_in"] = sortedIn
    #    for (i in value) {
    #        split(i, j, SUBSEP)
    #        da(value[i], sprintf(name "[%s]", join(j, ",")), sortedIn)
    #    }
    #    PROCINFO["sorted_in"] = saveSortedIn
    #} else
    #    d(name " = " value)
}

# Naive assertion.
function assert(x, message) {
    if (!message)
        message = "[ERROR] Assertion failed."

    if (x)
        return x
    else
        e(message)
}

## URLs:

# Initialize `UrlEncoding`.
# See: <https://en.wikipedia.org/wiki/Percent-encoding>
function initUrlEncoding() {
    UrlEncoding["\t"] = "%09"
    UrlEncoding["\n"] = "%0A"
    UrlEncoding[" "]  = "%20"
    UrlEncoding["!"]  = "%21"
    UrlEncoding["\""] = "%22"
    UrlEncoding["#"]  = "%23"
    UrlEncoding["$"]  = "%24"
    UrlEncoding["%"]  = "%25"
    UrlEncoding["&"]  = "%26"
    UrlEncoding["'"]  = "%27"
    UrlEncoding["("]  = "%28"
    UrlEncoding[")"]  = "%29"
    UrlEncoding["*"]  = "%2A"
    UrlEncoding["+"]  = "%2B"
    UrlEncoding[","]  = "%2C"
    UrlEncoding["-"]  = "%2D"
    UrlEncoding["."]  = "%2E"
    UrlEncoding["/"]  = "%2F"
    UrlEncoding[":"]  = "%3A"
    UrlEncoding[";"]  = "%3B"
    UrlEncoding["<"]  = "%3C"
    UrlEncoding["="]  = "%3D"
    UrlEncoding[">"]  = "%3E"
    UrlEncoding["?"]  = "%3F"
    UrlEncoding["@"]  = "%40"
    UrlEncoding["["]  = "%5B"
    UrlEncoding["\\"] = "%5C"
    UrlEncoding["]"]  = "%5D"
    UrlEncoding["^"]  = "%5E"
    UrlEncoding["_"]  = "%5F"
    UrlEncoding["`"]  = "%60"
    UrlEncoding["{"]  = "%7B"
    UrlEncoding["|"]  = "%7C"
    UrlEncoding["}"]  = "%7D"
    UrlEncoding["~"]  = "%7E"
}

# Return the URL-encoded string.
function quote(string,    i, r, s) {
    r = NULLSTR
    explode(string, s)
    for (i = 1; i <= length(s); i++)
        r = r (s[i] in UrlEncoding ? UrlEncoding[s[i]] : s[i])
    return r
}

# Return the URL-decoded string.
function unquote(string,    i, k, r, s, temp) {
    r = NULLSTR
    explode(string, s)
    temp = NULLSTR
    for (i = 1; i <= length(s); i++)
        if (temp) {
            temp = temp s[i]
            if (length(temp) > 2) {
                for (k in UrlEncoding)
                    if (temp == UrlEncoding[k]) {
                        r = r k
                        temp = NULLSTR
                        break
                    }
                if (temp) {
                    r = r temp
                    temp = NULLSTR
                }
            }
        } else {
            if (s[i] != "%")
                r = r s[i]
            else
                temp = s[i]
        }
    if (temp)
        r = r temp
    return r
}

# Initialize `UriSchemes`.
function initUriSchemes() {
    UriSchemes[0] = "file://"
    UriSchemes[1] = "http://"
    UriSchemes[2] = "https://"
}

## System:

# Read from a file and return its content.
function readFrom(file,    line, text) {
    if (!file) file = "/dev/stdin"
    text = NULLSTR
    while (getline line < file)
        text = (text ? text "\n" : NULLSTR) line
    return text
}

# Write text to file.
function writeTo(text, file) {
    if (!file) file = "/dev/stdout"
    print text > file
}

# Return the output of a command.
function getOutput(command,    content, line) {
    content = NULLSTR
    while ((command |& getline line) > 0)
        content = (content ? content "\n" : NULLSTR) line
    close(command)
    return content
}

# Return non-zero if file exists; otherwise return 0.
function fileExists(file) {
    return !system("test -f " parameterize(file))
}

# Return non-zero if file exists and is a directory; otherwise return 0.
function dirExists(file) {
    return !system("test -d " parameterize(file))
}

# Detect whether a program exists in path.
# Return the name (or output) if the program call writes anything to stdout;
# Otherwise, return a null string.
function detectProgram(prog, arg, returnOutput,    command, temp) {
    command = prog " " arg SUPERR
    command | getline temp
    close(command)
    if (returnOutput)
        return temp
    if (temp)
        return prog
    return NULLSTR
}

# Return the HEAD revision if the current directory is a git repo;
# Otherwise return a null string.
function getGitHead(    line, group) {
    if (fileExists(".git/HEAD")) {
        getline line < ".git/HEAD"
        match(line, /^ref: (.*)$/, group)
        if (fileExists(".git/" group[1])) {
            getline line < (".git/" group[1])
            return substr(line, 1, 7)
        } else
            return NULLSTR
    } else
        return NULLSTR
}

BEGIN {
    initConst()
    initAnsiCode()
    initUrlEncoding()
    initUriSchemes()
}


LanguageHelper: getDescription()

LanguageHelper: getDescription()

####################################################################
# LanguageHelper.awk                                               #
####################################################################

# Get locale key by language code or alias.
function getCode(code,    group) {
    if (code == "auto" || code in Locale)
        return code
    else if (code in LocaleAlias)
        return LocaleAlias[code]
    else if (tolower(code) in LocaleAlias)
        return LocaleAlias[tolower(code)]

    # Remove unidentified region or script code
    match(code, /^([[:alpha:]][[:alpha:]][[:alpha:]]?)-(.*)$/, group)
    if (group[1])
        return group[1]

    return # return nothing if not found
}

# Return the name of a language.
function getName(code) {
    return Locale[getCode(code)]["name"]
}

# Return all the names of a language, separated by "/"
function getNames(code) {
    if ("name2" in Locale[getCode(code)])
        return Locale[getCode(code)]["name"] " / " Locale[getCode(code)]["name2"]
    else
        return Locale[getCode(code)]["name"]
}

# Return the endonym of a language.
function getEndonym(code) {
    return Locale[getCode(code)]["endonym"]
}

# Return the string for displaying the endonym of a language.
function getDisplay(code) {
    return Locale[getCode(code)]["display"]
}

# Return formatted text for "translations of".
function showTranslationsOf(code, text,    fmt) {
    fmt = Locale[getCode(code)]["translations-of"]
    if (!fmt) fmt = Locale["en"]["translations-of"]
    return sprintf(fmt, text)
}

# Return formatted text for "definitions of".
function showDefinitionsOf(code, text,    fmt) {
    fmt = Locale[getCode(code)]["definitions-of"]
    if (!fmt) fmt = Locale["en"]["definitions-of"]
    return sprintf(fmt, text)
}

# Return a string of "synonyms".
function showSynonyms(code,    tmp) {
    tmp = Locale[getCode(code)]["synonyms"]
    if (!tmp) tmp = Locale["en"]["synonyms"]
    return tmp
}

# Return a string of "examples".
function showExamples(code,    tmp) {
    tmp = Locale[getCode(code)]["examples"]
    if (!tmp) tmp = Locale["en"]["examples"]
    return tmp
}

# Return a string of "see also".
function showSeeAlso(code,    tmp) {
    tmp = Locale[getCode(code)]["see-also"]
    if (!tmp) tmp = Locale["en"]["see-also"]
    return tmp
}

# Return the family of a language.
function getFamily(code) {
    return Locale[getCode(code)]["family"]
}

# Return the branch of a language.
function getBranch(code) {
    return Locale[getCode(code)]["branch"]
}

# Return the ISO 639-3 code of a language.
function getISO(code) {
    return Locale[getCode(code)]["iso"]
}

# Return the Glottocode of a language.
function getGlotto(code) {
    return Locale[getCode(code)]["glotto"]
}

# Return the ISO 15924 script code of a language.
function getScript(code) {
    return Locale[getCode(code)]["script"]
}

# Return 1 if a language is R-to-L; otherwise return 0.
function isRTL(code) {
    return Locale[getCode(code)]["rtl"] ? 1 : 0
}

# Return 1 if Google provides dictionary data for a language; otherwise return 0.
function hasDictionary(code) {
    return Locale[getCode(code)]["dictionary"] ? 1 : 0
}

# Comparator using getName().
function compName(i1, v1, i2, v2) {
    if (getName(i1) < getName(i2))
        return -1
    else
        return (getName(i1) != getName(i2))
}

# Return the name of script (writing system).
# See: <https://en.wikipedia.org/wiki/ISO_15924#List_of_codes>
#      <http://unicode.org/iso15924/iso15924-codes.html>
function scriptName(code) {
    switch (code) {
    case "Arab": return "Arabic"
    case "Armn": return "Armenian"
    case "Beng": return "Bengali"
    case "Cher": return "Cherokee"
    case "Cyrl": return "Cyrillic"
    case "Deva": return "Devanagari"
    case "Ethi": return "Ethiopic (Geʻez)"
    case "Geor": return "Georgian (Mkhedruli)"
    case "Grek": return "Greek"
    case "Gujr": return "Gujarati"
    case "Guru": return "Gurmukhi"
    case "Hani": return "Han"
    case "Hans": return "Han (Simplified)"
    case "Hant": return "Han (Traditional)"
    case "Hebr": return "Hebrew"
    case "Jpan": return "Japanese (Han + Hiragana + Katakana)"
    case "Khmr": return "Khmer"
    case "Knda": return "Kannada"
    case "Kore": return "Korean (Hangul + Han)"
    case "Laoo": return "Lao"
    case "Latn": return "Latin"
    case "Mtei": return "Meitei Mayek"
    case "Mlym": return "Malayalam"
    case "Mymr": return "Myanmar"
    case "Orya": return "Oriya"
    case "Piqd": return "Klingon (pIqaD)"
    case "Sinh": return "Sinhala"
    case "Taml": return "Tamil"
    case "Telu": return "Telugu"
    case "Thaa": return "Thaana"
    case "Thai": return "Thai"
    case "Tibt": return "Tibetan"
    default: return "Unknown"
    }
}

# Return the regions that a language is spoken in, as an English string.
function getSpokenIn(code,    i, j, r, regions, str) {
    r = NULLSTR
    str = Locale[getCode(code)]["spoken-in"]
    if (str) {
        split(str, regions, /\s?;\s?/)
        j = 0
        for (i in regions) {
            r = r regions[i]
            j++
            if (j < length(regions) - 1)
                r = r ", "
            else if (j == length(regions) - 1)
                r = r " and "
        }
    }
    return r
}

# Return the regions that a script is written in, as an English string.
function getWrittenIn(code,    i, j, r, regions, str) {
    r = NULLSTR
    str = Locale[getCode(code)]["written-in"]
    if (str) {
        split(str, regions, /\s?;\s?/)
        j = 0
        for (i in regions) {
            r = r regions[i]
            j++
            if (j < length(regions) - 1)
                r = r ", "
            else if (j == length(regions) - 1)
                r = r " and "
        }
    }
    return r
}

# Return the extra description of a language.
function getDescription(code) {
    return Locale[getCode(code)]["description"]
}

# Return 1 if a language is supported by Google; otherwise return 0.
function isSupportedByGoogle(code,    engines, i, str) {
    str = Locale[getCode(code)]["supported-by"]
    if (str) {
        split(str, engines, /\s?;\s?/)
        for (i in engines)
            if (engines[i] == "google") return 1
    }
    return 0
}

# Return 1 if a language is supported by Bing; otherwise return 0.
function isSupportedByBing(code,    engines, i, str) {
    str = Locale[getCode(code)]["supported-by"]
    if (str) {
        split(str, engines, /\s?;\s?/)
        for (i in engines)
            if (engines[i] == "bing") return 1
    }
    return 0
}

# Return detailed information of a language as a string.
function getDetails(code,    article, desc, group, iso, name, names, script, writing) {
    if (code == "auto" || !getCode(code)) {
        e("[ERROR] Language not found: " code "\n"                      \
          "        Run '-reference / -R' to see a list of available languages.")
        exit 1
    }

    script = scriptName(getScript(code))
    if (isRTL(code)) script = script " (R-to-L)"

    split(getISO(code), group, "-")
    iso = group[1]

    name = getName(code)
    names = getNames(code)
    if (match(name, /\(.*\)/)) {
        writing = substr(name, match(name, /\(.*\)/) + 1)
        writing = substr(writing, 1, length(writing) - 1)
        name = substr(name, 1, match(name, /\(.*\)/) - 2)
    }

    if (getBranch(code)) {
        article = match(tolower(getBranch(code)), /^[aeiou]/) ? "an" : "a"
        if (iso == "eng")
            desc = sprintf("%s is %s %s language spoken %s.",
                           names, article, getBranch(code), getSpokenIn(code))
        else
            desc = sprintf("%s is %s %s language spoken mainly in %s.",
                           names, article, getBranch(code), getSpokenIn(code))
    } else if (getFamily(code) == NULLSTR || tolower(getFamily(code)) == "language isolate")
        desc = sprintf("%s is a language spoken mainly in %s.", names, getSpokenIn(code))
    else if (tolower(getFamily(code)) == "constructed language")
        desc = sprintf("%s is %s.", names, getDescription(code))
    else
        desc = sprintf("%s is a language of the %s family, spoken mainly in %s.",
                       names, getFamily(code), getSpokenIn(code))
    if (writing && getWrittenIn(code))
        desc = desc sprintf(" The %s writing system is officially used in %s.",
                            tolower(writing), getWrittenIn(code))

    return ansi("bold", sprintf("%s\n", getDisplay(code)))              \
        sprintf("%-22s%s\n", "Name", ansi("bold", names))               \
        sprintf("%-22s%s\n", "Family", ansi("bold", getFamily(code)))   \
        sprintf("%-22s%s\n", "Writing system", ansi("bold", script))    \
        sprintf("%-22s%s\n", "Code", ansi("bold", getCode(code)))       \
        sprintf("%-22s%s\n", "ISO 639-3", ansi("bold", iso))            \
        sprintf("%-22s%s\n", "SIL", ansi("bold", "https://iso639-3.sil.org/code/" iso)) \
        sprintf("%-22s%s\n", "Glottolog", getGlotto(code) ?
                ansi("bold", "https://glottolog.org/resource/languoid/id/" getGlotto(code)) : "") \
        sprintf("%-22s%s\n", "Wikipedia", ansi("bold", "https://en.wikipedia.org/wiki/ISO_639:" iso)) \
        (Locale[getCode(code)]["supported-by"] ? # FIXME
        sprintf("%-22s%s\n", "Translator support", sprintf("Google [%s]    Bing [%s]",
                isSupportedByGoogle(code) ? "✔" : "✘", isSupportedByBing(code) ? "✔" : "✘")) : "") \
        (Locale[getCode(code)]["spoken-in"] ? # FIXME
        ansi("bold", sprintf("\n%s", desc)) : "")
}

# Add /slashes/ for IPA phonemic notations and (parentheses) for others.
# Parameters:
#     code
function showPhonetics(phonetics, code) {
    if (code && getCode(code) == "en")
        return "/" phonetics "/"
    else
        return "(" phonetics ")"
}

# Convert a logical string to visual; don't right justify RTL lines.
# Parameters:
#     code: ignore to apply bidirectional algorithm on every string
function show(text, code,    command, temp) {
    if (!code || isRTL(code)) {
        if (Cache[text][0])
            return Cache[text][0]
        else {
            if ((FriBidi || (code && isRTL(code))) && BiDiNoPad) {
                command = "echo " parameterize(text) PIPE BiDiNoPad
                command | getline temp
                close(command)
            } else # non-RTL language, or FriBidi not installed
                temp = text
            return Cache[text][0] = temp
        }
    } else
        return text
}

# Convert a logical string to visual and right justify RTL lines.
# Parameters:
#     code: ignore to apply bidirectional algorithm on every string
#     width: ignore to use default width for padding
function s(text, code, width,    command, temp) {
    if (!code || isRTL(code)) {
        if (!width) width = Option["width"]
        if (Cache[text][width])
            return Cache[text][width]
        else {
            if ((FriBidi || (code && isRTL(code))) && BiDi) {
                command = "echo " parameterize(text) PIPE sprintf(BiDi, width)
                command | getline temp
                close(command)
            } else # non-RTL language, or FriBidi not installed
                temp = text
            return Cache[text][width] = temp
        }
    } else
        return text
}

# Convert a logical string to visual with a certain level of indentation.
# Parameters:
#     level: level of indentation
#     code: ignore to apply left indentation
#     width: ignore to use default width for padding
function ins(level, text, code, width,    i, temp) {
    if (code && isRTL(code)) {
        if (!width) width = Option["width"]
        return s(text, code, width - Option["indent"] * level)
    } else
        return replicate(" ", Option["indent"] * level) text
}

# Parse a POSIX locale identifier and return the language code;
# Identified by both language identifier and region identifier.
# Parameters:
#     lang = [language[_territory][.codeset][@modifier]]
# See: <https://en.wikipedia.org/wiki/Locale>
function parseLang(lang,    code, group) {
    match(lang, /^([a-z][a-z][a-z]?)(_|$)/, group)
    code = getCode(group[1])

    # Detect region identifier
    ## Regions using Chinese Simplified: China, Singapore
    if (lang ~ /^zh_(CN|SG)/) code = "zh-CN"
    ## Regions using Chinese Traditional: Taiwan, Hong Kong
    else if (lang ~ /^zh_(TW|HK)/) code = "zh-TW"

    # FIXME: handle unrecognized language code
    if (!code) code = "en"

    return code
}

# Initialize `UserLang`.
function initUserLang(    lang, utf) {
    if (lang = ENVIRON["LC_ALL"]) {
        if (!UserLocale) UserLocale = lang
        utf = utf || tolower(lang) ~ /utf-?8$/
    }
    if (lang = ENVIRON["LANG"]) {
        if (!UserLocale) UserLocale = lang
        utf = utf || tolower(lang) ~ /utf-?8$/
    }
    if (!UserLocale) {
        UserLocale = "en_US.UTF-8"
        utf = 1
    }
    if (!utf)
        w("[WARNING] Your locale codeset (" UserLocale ") is not UTF-8.")

    UserLang = parseLang(UserLocale)
}

####################################################################
# LanguageHelper.awk                                               #
####################################################################

# Get locale key by language code or alias.
function getCode(code,    group) {
    if (code == "auto" || code in Locale)
        return code
    else if (code in LocaleAlias)
        return LocaleAlias[code]
    else if (tolower(code) in LocaleAlias)
        return LocaleAlias[tolower(code)]

    # Remove unidentified region or script code
    match(code, /^([[:alpha:]][[:alpha:]][[:alpha:]]?)-(.*)$/, group)
    if (group[1])
        return group[1]

    return # return nothing if not found
}

# Return the name of a language.
function getName(code) {
    return Locale[getCode(code)]["name"]
}

# Return all the names of a language, separated by "/"
function getNames(code) {
    if ("name2" in Locale[getCode(code)])
        return Locale[getCode(code)]["name"] " / " Locale[getCode(code)]["name2"]
    else
        return Locale[getCode(code)]["name"]
}

# Return the endonym of a language.
function getEndonym(code) {
    return Locale[getCode(code)]["endonym"]
}

# Return the string for displaying the endonym of a language.
function getDisplay(code) {
    return Locale[getCode(code)]["display"]
}

# Return formatted text for "translations of".
function showTranslationsOf(code, text,    fmt) {
    fmt = Locale[getCode(code)]["translations-of"]
    if (!fmt) fmt = Locale["en"]["translations-of"]
    return sprintf(fmt, text)
}

# Return formatted text for "definitions of".
function showDefinitionsOf(code, text,    fmt) {
    fmt = Locale[getCode(code)]["definitions-of"]
    if (!fmt) fmt = Locale["en"]["definitions-of"]
    return sprintf(fmt, text)
}

# Return a string of "synonyms".
function showSynonyms(code,    tmp) {
    tmp = Locale[getCode(code)]["synonyms"]
    if (!tmp) tmp = Locale["en"]["synonyms"]
    return tmp
}

# Return a string of "examples".
function showExamples(code,    tmp) {
    tmp = Locale[getCode(code)]["examples"]
    if (!tmp) tmp = Locale["en"]["examples"]
    return tmp
}

# Return a string of "see also".
function showSeeAlso(code,    tmp) {
    tmp = Locale[getCode(code)]["see-also"]
    if (!tmp) tmp = Locale["en"]["see-also"]
    return tmp
}

# Return the family of a language.
function getFamily(code) {
    return Locale[getCode(code)]["family"]
}

# Return the branch of a language.
function getBranch(code) {
    return Locale[getCode(code)]["branch"]
}

# Return the ISO 639-3 code of a language.
function getISO(code) {
    return Locale[getCode(code)]["iso"]
}

# Return the Glottocode of a language.
function getGlotto(code) {
    return Locale[getCode(code)]["glotto"]
}

# Return the ISO 15924 script code of a language.
function getScript(code) {
    return Locale[getCode(code)]["script"]
}

# Return 1 if a language is R-to-L; otherwise return 0.
function isRTL(code) {
    return Locale[getCode(code)]["rtl"] ? 1 : 0
}

# Return 1 if Google provides dictionary data for a language; otherwise return 0.
function hasDictionary(code) {
    return Locale[getCode(code)]["dictionary"] ? 1 : 0
}

# Comparator using getName().
function compName(i1, v1, i2, v2) {
    if (getName(i1) < getName(i2))
        return -1
    else
        return (getName(i1) != getName(i2))
}

# Return the name of script (writing system).
# See: <https://en.wikipedia.org/wiki/ISO_15924#List_of_codes>
#      <http://unicode.org/iso15924/iso15924-codes.html>
function scriptName(code) {
    switch (code) {
    case "Arab": return "Arabic"
    case "Armn": return "Armenian"
    case "Beng": return "Bengali"
    case "Cher": return "Cherokee"
    case "Cyrl": return "Cyrillic"
    case "Deva": return "Devanagari"
    case "Ethi": return "Ethiopic (Geʻez)"
    case "Geor": return "Georgian (Mkhedruli)"
    case "Grek": return "Greek"
    case "Gujr": return "Gujarati"
    case "Guru": return "Gurmukhi"
    case "Hani": return "Han"
    case "Hans": return "Han (Simplified)"
    case "Hant": return "Han (Traditional)"
    case "Hebr": return "Hebrew"
    case "Jpan": return "Japanese (Han + Hiragana + Katakana)"
    case "Khmr": return "Khmer"
    case "Knda": return "Kannada"
    case "Kore": return "Korean (Hangul + Han)"
    case "Laoo": return "Lao"
    case "Latn": return "Latin"
    case "Mtei": return "Meitei Mayek"
    case "Mlym": return "Malayalam"
    case "Mymr": return "Myanmar"
    case "Orya": return "Oriya"
    case "Piqd": return "Klingon (pIqaD)"
    case "Sinh": return "Sinhala"
    case "Taml": return "Tamil"
    case "Telu": return "Telugu"
    case "Thaa": return "Thaana"
    case "Thai": return "Thai"
    case "Tibt": return "Tibetan"
    default: return "Unknown"
    }
}

# Return the regions that a language is spoken in, as an English string.
function getSpokenIn(code,    i, j, r, regions, str) {
    r = NULLSTR
    str = Locale[getCode(code)]["spoken-in"]
    if (str) {
        split(str, regions, /\s?;\s?/)
        j = 0
        for (i in regions) {
            r = r regions[i]
            j++
            if (j < length(regions) - 1)
                r = r ", "
            else if (j == length(regions) - 1)
                r = r " and "
        }
    }
    return r
}

# Return the regions that a script is written in, as an English string.
function getWrittenIn(code,    i, j, r, regions, str) {
    r = NULLSTR
    str = Locale[getCode(code)]["written-in"]
    if (str) {
        split(str, regions, /\s?;\s?/)
        j = 0
        for (i in regions) {
            r = r regions[i]
            j++
            if (j < length(regions) - 1)
                r = r ", "
            else if (j == length(regions) - 1)
                r = r " and "
        }
    }
    return r
}

# Return 1 if a language is supported by Google; otherwise return 0.
function isSupportedByGoogle(code,    engines, i, str) {
    str = Locale[getCode(code)]["supported-by"]
    if (str) {
        split(str, engines, /\s?;\s?/)
        for (i in engines)
            if (engines[i] == "google") return 1
    }
    return 0
}

# Return 1 if a language is supported by Bing; otherwise return 0.
function isSupportedByBing(code,    engines, i, str) {
    str = Locale[getCode(code)]["supported-by"]
    if (str) {
        split(str, engines, /\s?;\s?/)
        for (i in engines)
            if (engines[i] == "bing") return 1
    }
    return 0
}

# Return detailed information of a language as a string.
function getDetails(code,    article, desc, group, iso, name, names, script, writing) {
    if (code == "auto" || !getCode(code)) {
        e("[ERROR] Language not found: " code "\n"                      \
          "        Run '-reference / -R' to see a list of available languages.")
        exit 1
    }

    script = scriptName(getScript(code))
    if (isRTL(code)) script = script " (R-to-L)"

    split(getISO(code), group, "-")
    iso = group[1]

    name = getName(code)
    names = getNames(code)
    if (match(name, /\(.*\)/)) {
        writing = substr(name, match(name, /\(.*\)/) + 1)
        writing = substr(writing, 1, length(writing) - 1)
        name = substr(name, 1, match(name, /\(.*\)/) - 2)
    }

    if (getBranch(code)) {
        article = match(tolower(getBranch(code)), /^[aeiou]/) ? "an" : "a"
        desc = sprintf("%s is %s %s language spoken mainly in %s.",
                       names, article, getBranch(code), getSpokenIn(code))
    } else if (getFamily(code) == NULLSTR || tolower(getFamily(code)) == "language isolate") {
        desc = sprintf("%s is a language spoken mainly in %s.",
                       names, getSpokenIn(code))
    } else
        desc = sprintf("%s is a language of the %s family, spoken mainly in %s.",
                       names, getFamily(code), getSpokenIn(code))
    if (writing && getWrittenIn(code))
        desc = desc sprintf(" The %s writing system is officially used in %s.",
                            tolower(writing), getWrittenIn(code))

    return ansi("bold", sprintf("%s\n", getDisplay(code)))              \
        sprintf("%-22s%s\n", "Name", ansi("bold", names))               \
        sprintf("%-22s%s\n", "Family", ansi("bold", getFamily(code)))   \
        sprintf("%-22s%s\n", "Writing system", ansi("bold", script))    \
        sprintf("%-22s%s\n", "Code", ansi("bold", getCode(code)))       \
        sprintf("%-22s%s\n", "ISO 639-3", ansi("bold", iso))            \
        sprintf("%-22s%s\n", "SIL", ansi("bold", "https://iso639-3.sil.org/code/" iso)) \
        sprintf("%-22s%s\n", "Glottolog", getGlotto(code) ?
                ansi("bold", "https://glottolog.org/resource/languoid/id/" getGlotto(code)) : "") \
        sprintf("%-22s%s\n", "Wikipedia", ansi("bold", "https://en.wikipedia.org/wiki/ISO_639:" iso)) \
        (Locale[getCode(code)]["supported-by"] ? # FIXME
        sprintf("%-22s%s\n", "Translator support", sprintf("Google [%s]    Bing [%s]",
                isSupportedByGoogle(code) ? "✔" : "✘", isSupportedByBing(code) ? "✔" : "✘")) : "") \
        (Locale[getCode(code)]["spoken-in"] ? # FIXME
        ansi("bold", sprintf("\n%s", desc)) : "")
}

# Add /slashes/ for IPA phonemic notations and (parentheses) for others.
# Parameters:
#     code
function showPhonetics(phonetics, code) {
    if (code && getCode(code) == "en")
        return "/" phonetics "/"
    else
        return "(" phonetics ")"
}

# Convert a logical string to visual; don't right justify RTL lines.
# Parameters:
#     code: ignore to apply bidirectional algorithm on every string
function show(text, code,    command, temp) {
    if (!code || isRTL(code)) {
        if (Cache[text][0])
            return Cache[text][0]
        else {
            if ((FriBidi || (code && isRTL(code))) && BiDiNoPad) {
                command = "echo " parameterize(text) PIPE BiDiNoPad
                command | getline temp
                close(command)
            } else # non-RTL language, or FriBidi not installed
                temp = text
            return Cache[text][0] = temp
        }
    } else
        return text
}

# Convert a logical string to visual and right justify RTL lines.
# Parameters:
#     code: ignore to apply bidirectional algorithm on every string
#     width: ignore to use default width for padding
function s(text, code, width,    command, temp) {
    if (!code || isRTL(code)) {
        if (!width) width = Option["width"]
        if (Cache[text][width])
            return Cache[text][width]
        else {
            if ((FriBidi || (code && isRTL(code))) && BiDi) {
                command = "echo " parameterize(text) PIPE sprintf(BiDi, width)
                command | getline temp
                close(command)
            } else # non-RTL language, or FriBidi not installed
                temp = text
            return Cache[text][width] = temp
        }
    } else
        return text
}

# Convert a logical string to visual with a certain level of indentation.
# Parameters:
#     level: level of indentation
#     code: ignore to apply left indentation
#     width: ignore to use default width for padding
function ins(level, text, code, width,    i, temp) {
    if (code && isRTL(code)) {
        if (!width) width = Option["width"]
        return s(text, code, width - Option["indent"] * level)
    } else
        return replicate(" ", Option["indent"] * level) text
}

# Parse a POSIX locale identifier and return the language code;
# Identified by both language identifier and region identifier.
# Parameters:
#     lang = [language[_territory][.codeset][@modifier]]
# See: <https://en.wikipedia.org/wiki/Locale>
function parseLang(lang,    code, group) {
    match(lang, /^([a-z][a-z][a-z]?)(_|$)/, group)
    code = getCode(group[1])

    # Detect region identifier
    ## Regions using Chinese Simplified: China, Singapore
    if (lang ~ /^zh_(CN|SG)/) code = "zh-CN"
    ## Regions using Chinese Traditional: Taiwan, Hong Kong
    else if (lang ~ /^zh_(TW|HK)/) code = "zh-TW"

    # FIXME: handle unrecognized language code
    if (!code) code = "en"

    return code
}

# Initialize `UserLang`.
function initUserLang(    lang, utf) {
    if (lang = ENVIRON["LC_ALL"]) {
        if (!UserLocale) UserLocale = lang
        utf = utf || tolower(lang) ~ /utf-?8$/
    }
    if (lang = ENVIRON["LANG"]) {
        if (!UserLocale) UserLocale = lang
        utf = utf || tolower(lang) ~ /utf-?8$/
    }
    if (!UserLocale) {
        UserLocale = "en_US.UTF-8"
        utf = 1
    }
    if (!utf)
        w("[WARNING] Your locale codeset (" UserLocale ") is not UTF-8.")

    UserLang = parseLang(UserLocale)
}


Theme: 3 predefined themes (default, none, random)

####################################################################
# Theme.awk                                                        #
####################################################################

function prettify(name, string,    i, temp) {
    temp = string
    if ("sgr-" name in Option)
        if (isarray(Option["sgr-" name]))
            for (i in Option["sgr-" name])
                temp = ansi(Option["sgr-" name][i], temp)
        else
            temp = ansi(Option["sgr-" name], temp)
    return temp
}

function randomColor(    i) {
    i = int(5 * rand())
    switch (i) {
    case 0: return "green"
    case 1: return "yellow"
    case 2: return "blue"
    case 3: return "magenta"
    case 4: return "cyan"
    default: return "default"
    }
}

function setRandomTheme(    i, n, temp) {
    srand(systime())
    for (i = 0; i < 3; i++) {
        do temp = randomColor(); while (belongsTo(temp, n))
        n[i] = temp
    }

    Option["sgr-prompt-message"] = Option["sgr-languages"] = n[0]
    Option["sgr-original-dictionary-detailed-word-class"][1] = n[0]
    Option["sgr-original-dictionary-detailed-word-class"][2] = "bold"
    Option["sgr-original-dictionary-synonyms"] = n[0]
    Option["sgr-original-dictionary-synonyms-word-class"][1] = n[0]
    Option["sgr-original-dictionary-synonyms-word-class"][2] = "bold"
    Option["sgr-original-dictionary-examples"] = n[0]
    Option["sgr-original-dictionary-see-also"] = n[0]
    Option["sgr-dictionary-word-class"][1] = n[0]
    Option["sgr-dictionary-word-class"][2] = "bold"

    Option["sgr-original"][1] = Option["sgr-original-phonetics"][1] = n[1]
    Option["sgr-original"][2] = Option["sgr-original-phonetics"][2] = "bold"
    Option["sgr-prompt-message-original"][1] = n[1]
    Option["sgr-prompt-message-original"][2] = "bold"
    Option["sgr-languages-sl"] = n[1]
    Option["sgr-original-dictionary-detailed-explanation"][1] = n[1]
    Option["sgr-original-dictionary-detailed-explanation"][2] = "bold"
    Option["sgr-original-dictionary-detailed-example"] = n[1]
    Option["sgr-original-dictionary-detailed-synonyms"] = n[1]
    Option["sgr-original-dictionary-detailed-synonyms-item"][1] = n[1]
    Option["sgr-original-dictionary-detailed-synonyms-item"][2] = "bold"
    Option["sgr-original-dictionary-synonyms-synonyms"] = n[1]
    Option["sgr-original-dictionary-synonyms-synonyms-item"][1] = n[1]
    Option["sgr-original-dictionary-synonyms-synonyms-item"][2] = "bold"
    Option["sgr-original-dictionary-examples-example"] = n[1]
    Option["sgr-original-dictionary-examples-original"][1] = n[1]
    Option["sgr-original-dictionary-examples-original"][2] = "bold"
    Option["sgr-original-dictionary-examples-original"][3] = "underline"
    Option["sgr-original-dictionary-see-also-phrases"] = n[1]
    Option["sgr-original-dictionary-see-also-phrases-item"][1] = n[1]
    Option["sgr-original-dictionary-see-also-phrases-item"][2] = "bold"
    Option["sgr-dictionary-explanation"] = n[1]
    Option["sgr-dictionary-explanation-item"][1] = n[1]
    Option["sgr-dictionary-explanation-item"][2] = "bold"
    Option["sgr-alternatives-original"][1] = n[1]
    Option["sgr-alternatives-original"][2] = "bold"

    Option["sgr-translation"][1] = Option["sgr-translation-phonetics"][1] = n[2]
    Option["sgr-translation"][2] = Option["sgr-translation-phonetics"][2] = "bold"
    Option["sgr-languages-tl"] = n[2]
    Option["sgr-dictionary-word"][1] = n[2]
    Option["sgr-dictionary-word"][2] = "bold"
    Option["sgr-alternatives-translations"] = n[2]
    Option["sgr-alternatives-translations-item"][1] = n[2]
    Option["sgr-alternatives-translations-item"][2] = "bold"
}

function setDefaultTheme() {
    Option["sgr-translation"] = Option["sgr-translation-phonetics"] = "bold"
    Option["sgr-prompt-message-original"] = "underline"
    Option["sgr-languages-sl"] = "underline"
    Option["sgr-languages-tl"] = "bold"
    Option["sgr-original-dictionary-detailed-explanation"] = "bold"
    Option["sgr-original-dictionary-detailed-synonyms-item"] = "bold"
    Option["sgr-original-dictionary-synonyms-synonyms-item"] = "bold"
    Option["sgr-original-dictionary-examples-original"][1] = "bold"
    Option["sgr-original-dictionary-examples-original"][2] = "underline"
    Option["sgr-original-dictionary-see-also-phrases-item"] = "bold"
    Option["sgr-dictionary-word"] = "bold"
    Option["sgr-alternatives-original"] = "underline"
    Option["sgr-alternatives-translations-item"] = "bold"
}

function setTheme(    file, line, script) {
    if (Option["theme"] && Option["theme"] != "default") {
        file = Option["theme"]
        if (!fileExists(file))
            file = ENVIRON["HOME"] "/.translate-shell/" Option["theme"]
    }

    if (file && fileExists(file)) {
        # Read from theme file
        script = NULLSTR
        while (getline line < file)
            script = script "\n" line
        loadOptions(script)
    } else if (Option["theme"] == "none")
        ;# skip
    else if (Option["theme"] == "random")
        setRandomTheme()
    else
        setDefaultTheme()
}

####################################################################
# Theme.awk                                                        #
####################################################################

function prettify(name, string,    i, temp) {
    temp = string
    if ("sgr-" name in Option)
        if (isarray(Option["sgr-" name]))
            for (i in Option["sgr-" name])
                temp = ansi(Option["sgr-" name][i], temp)
        else
            temp = ansi(Option["sgr-" name], temp)
    return temp
}

function setTheme(    file, line, script) {
    if (Option["theme"] && Option["theme"] != "default") {
        file = Option["theme"]
        if (!fileExists(file))
            file = ENVIRON["HOME"] "/.translate-shell/" Option["theme"]
    }

    if (file && fileExists(file)) {
        # Read from theme file
        script = NULLSTR
        while (getline line < file)
            script = script "\n" line
        loadOptions(script)
    } else {
        # Default theme
        Option["sgr-translation"] = "bold"
        Option["sgr-translation-phonetics"] = "bold"
        Option["sgr-prompt-message-original"] = "underline"
        Option["sgr-languages-sl"] = "underline"
        Option["sgr-languages-tl"] = "bold"
        Option["fmt-languages"] = "[ %s -> %t ]"
        Option["sgr-original-dictionary-detailed-explanation"] = "bold"
        Option["sgr-original-dictionary-detailed-synonyms-item"] = "bold"
        Option["sgr-original-dictionary-synonyms-synonyms-item"] = "bold"
        Option["sgr-original-dictionary-examples-original"][1] = "bold"
        Option["sgr-original-dictionary-examples-original"][2] = "underline"
        Option["sgr-original-dictionary-see-also-phrases-item"] = "bold"
        Option["sgr-dictionary-word"] = "bold"
        Option["sgr-alternatives-original"] = "underline"
        Option["sgr-alternatives-translations-item"] = "bold"
    }
}


Use a file name prefix for generated labels

This ensures labels are unique within the wider context of the doxygen
documentation.

#!/usr/bin/awk -f
#
# Adds github compatible labels to level 1 and 2 markdown headings
# in underline style.
#

#
# Get file name prefix for references.
#
filename != FILENAME {
	filename = FILENAME
	# strip path
	"pwd" | getline path
	path = path "/"
	prefix = substr(filename, 1, length(path)) == path \
	         ? substr(filename, length(path) + 1) \
	         : filename
	sub(/\.[^.]*$/, "", prefix)
	gsub(/[^_a-z0-9]+/, "-", prefix)
	prefix = "md_" prefix "_"
}

#
# Substitute github references with doxygen references.
#
/\(#[_a-z0-9-]*\)/ {
	gsub(/\(#[_a-z0-9-]*\)/, "<__REF__&__REF__>")
	gsub(/<__REF__\(#/, "(@ref " prefix)
	gsub(/\)__REF__>/, ")")
}

#
# If this line is underlining a heading, add a label to the previous
# line.
#
line && (/^===*$/ || /^---*$/) {
	id = tolower(line)
	gsub(/[^_a-z0-9]+/, "-", id)
	line = line " {#" prefix id "}"
}

#
# Print the previous line, after a label might have been added.
#
{print line}

#
# Remember this line when scanning the next one for underlining.
#
{line = $0}

#
# Print the last line.
#
END { print }

#!/usr/bin/awk -f
#
# Adds github compatible labels to level 1 and 2 markdown headings
# in underline style.
#

#
# Substitute github references with doxygen references.
#
/\(#[_a-z0-9-]*\)/ {
	gsub(/\(#[_a-z0-9-]*\)/, "<__REF__&__REF__>")
	gsub(/<__REF__\(#/, "(@ref ")
	gsub(/\)__REF__>/, ")")
}

#
# If this line is underlining a heading, add a label to the previous
# line.
#
line && (/^===*$/ || /^---*$/) {
	id = tolower(line)
	gsub(/[^_a-z0-9]+/, "-", id)
	line = line " {#" id "}"
}

#
# Print the previous line, after a label might have been added.
#
{print line}

#
# Remember this line when scanning the next one for underlining.
#
{line = $0}

#
# Print the last line.
#
END { print }


make-barcodes.awk: Allow 0 TAG_LEN or INVARIANT values, remove semicolons.

# The awk code that transforms the one-line fastq record pair into the output that can be sorted
# by barcode.
# Input columns (the 4 FASTQ lines for both reads in a read pair):
#   1: read1 name
#   2: read2 name
#   3: read1 sequence
#   4: read2 sequence
#   5: read1 + line
#   6: read2 + line
#   7: read1 quality
#   8: read2 quality
# Output columns:
#   1: the barcode, put into a canonical form
#   2: the order of the barcode halves ("ab" or "ba")
#   3: read1 name
#   4: sequence of read 1, minus the 12bp barcode and 5bp invariant sequence
#   5: read1 quality scores, minus the same first 17bp
#   6: read2 name
#   7: sequence of read 2, minus the first 17bp
#   8: read2 quality scores, minus the first 17bp
# The canonical form of the barcode is composed of two concatenated tags, one from each read.
# By default, each tag is the first 12bp of the read. The tag from the first read is the "alpha" and
# the tag from the second is the "beta". The barcode is formed by concatenating them in an order
# determined by a string comparison of the two. The greater tag is first (if they are equal, the
# beta is first, but then you have bigger problems).

BEGIN {
  FS = "\t"
  OFS = "\t"
  # The number of bases from the start of each read that form the two halves of the barcode.
  # (this should be half the size of the full, canonical barcode).
  if (TAG_LEN == "") {
    TAG_LEN = 12
  }
  # The number of bases in the read that are between the barcode and the start of the actual sample
  # sequence (the restriction site in the Loeb 2014 protocol).
  if (INVARIANT == "") {
    INVARIANT = 5
  }
}

$3 && $4 {
  alpha = substr($3, 1, TAG_LEN)
  beta = substr($4, 1, TAG_LEN)
  if (alpha > beta) {
    barcode = alpha beta
    order = "ab"
  } else {
    barcode = beta alpha
    order = "ba"
  }
  name1 = $1
  name2 = $2
  seq1 = substr($3, TAG_LEN + INVARIANT + 1)
  seq2 = substr($4, TAG_LEN + INVARIANT + 1)
  qual1 = substr($7, TAG_LEN + INVARIANT + 1)
  qual2 = substr($8, TAG_LEN + INVARIANT + 1)
  print barcode, order, name1, seq1, qual1, name2, seq2, qual2
}

# The awk code that transforms the one-line fastq record pair into the output that can be sorted
# by barcode.
# Input columns (the 4 FASTQ lines for both reads in a read pair):
#   1: read1 name
#   2: read2 name
#   3: read1 sequence
#   4: read2 sequence
#   5: read1 + line
#   6: read2 + line
#   7: read1 quality
#   8: read2 quality
# Output columns:
#   1: the barcode, put into a canonical form
#   2: the order of the barcode halves ("ab" or "ba")
#   3: read1 name
#   4: sequence of read 1, minus the 12bp barcode and 5bp invariant sequence
#   5: read1 quality scores, minus the same first 17bp
#   6: read2 name
#   7: sequence of read 2, minus the first 17bp
#   8: read2 quality scores, minus the first 17bp
# The canonical form of the barcode is composed of two concatenated tags, one from each read.
# By default, each tag is the first 12bp of the read. The tag from the first read is the "alpha" and
# the tag from the second is the "beta". The barcode is formed by concatenating them in an order
# determined by a string comparison of the two. The greater tag is first (if they are equal, the
# beta is first, but then you have bigger problems).

BEGIN {
  FS = "\t"; OFS = "\t";
  # The number of bases from the start of each read that form the two halves of the barcode.
  # (this should be half the size of the full, canonical barcode).
  if (! TAG_LEN) {
    TAG_LEN = 12;
  }
  # The number of bases in the read that are between the barcode and the start of the actual sample
  # sequence (the restriction site in the Loeb 2014 protocol).
  if (! INVARIANT) {
    INVARIANT = 5;
  }
}

$3 && $4 {
  alpha = substr($3, 1, TAG_LEN);
  beta = substr($4, 1, TAG_LEN);
  if (alpha > beta) {
    barcode = alpha beta;
    order = "ab";
  } else {
    barcode = beta alpha;
    order = "ba";
  }
  name1 = $1;
  name2 = $2;
  seq1 = substr($3, TAG_LEN + INVARIANT + 1);
  seq2 = substr($4, TAG_LEN + INVARIANT + 1);
  qual1 = substr($7, TAG_LEN + INVARIANT + 1);
  qual2 = substr($8, TAG_LEN + INVARIANT + 1);
  print barcode, order, name1, seq1, qual1, name2, seq2, qual2;
}