Utils: curlPost()

Utils: curlPost()

####################################################################
# Utils.awk                                                        #
####################################################################

# Detect gawk version.
function initGawk(    group) {
    Gawk = "gawk"
    GawkVersion = PROCINFO["version"]

    split(PROCINFO["version"], group, ".")
    if (group[1] < 4) {
        e("[ERROR] Oops! Your gawk (version " GawkVersion ") "          \
          "appears to be too old.\n"                                    \
          "        You need at least gawk 4.0.0 to run this program.")
        exit 1
    }
}

# Detect external bidirectional algorithm utility (fribidi);
# Fallback to Unix `rev` if not found.
function initBiDi() {
    FriBidi = detectProgram("fribidi", "--version", 1)
    BiDiNoPad = FriBidi ? "fribidi --nopad" : "rev" SUPERR
    BiDi = FriBidi ? "fribidi --width %s" :
        "rev" SUPERR "| sed \"s/'/\\\\\\'/\" | xargs printf '%%s '"
}

# Detect external readline wrapper (rlwrap).
function initRlwrap() {
    Rlwrap = detectProgram("rlwrap", "--version")
}

# Detect emacs.
function initEmacs() {
    Emacs = detectProgram("emacs", "--version")
}

# Detect curl.
function initCurl() {
    Curl = detectProgram("curl", "--version")
}

# Log any value if debugging is enabled.
function l(value, name, inline, heredoc, valOnly, numSub, sortedIn) {
    if (Option["debug"]) {
        if (name)
            da(value, name, inline, heredoc, valOnly, numSub, sortedIn)
        else
            d(value)
    }
}

# Return a log message if debugging is enabled.
function m(string) {
    if (Option["debug"])
        return ansi("cyan", string) RS
}

# Return 1 if the first version is newer than the second; otherwise return 0.
function newerVersion(ver1, ver2,    i, group1, group2) {
    split(ver1, group1, ".")
    split(ver2, group2, ".")
    for (i = 1; i <= 4; i++) {
        if (group1[i] + 0 > group2[i] + 0)
            return 1
        else if (group1[i] + 0 < group2[i] + 0)
            return 0
    }
    return 0
}

# Fork a rlwrap process as the wrapper. Return non-zero if failed,
function rlwrapMe(    i, command) {
    initRlwrap()

    if (!Rlwrap) {
        l(">> not found: rlwrap")
        return 1
    }

    if (ENVIRON["TRANS_ENTRY"]) {
        command = Rlwrap " " ENVIRON["TRANS_ENTRY"] " "                 \
            parameterize("-no-rlwrap") # never fork rlwrap again!
    } else if (fileExists(ENVIRON["TRANS_DIR"] "/" EntryScript)) {
        command = Rlwrap " sh "                                         \
            parameterize(ENVIRON["TRANS_DIR"] "/" EntryScript)          \
            " - " parameterize("-no-rlwrap") # never fork rlwrap again!
    } else {
        l(">> not found: $TRANS_ENTRY or EntryPoint")
        return 1
    }
    for (i = 1; i < length(ARGV); i++)
        if (ARGV[i])
            command = command " " parameterize(ARGV[i])

    l(">> forking: " command)
    if (!system(command)) {
        l(">> process exited with code 0")
        exit ExitCode
    } else {
        l(">> process exited with non-zero return code")
        return 1
    }
}

# Fork an emacs process as the front-end. Return non-zero if failed,
function emacsMe(    i, params, el, command) {
    initEmacs()

    if (!Emacs) {
        l(">> not found: emacs")
        return 1
    }

    params = ""
    for (i = 1; i < length(ARGV); i++)
        if (ARGV[i])
            params = params " " parameterize(ARGV[i], "\"")
    if (ENVIRON["TRANS_ENTRY"]) {
        el = "(progn (setq explicit-shell-file-name \"" ENVIRON["TRANS_ENTRY"] "\") " \
            "(setq explicit-" Command "-args '(\"-I\" \"-no-rlwrap\"" params ")) " \
            "(command-execute 'shell) (rename-buffer \"" Name "\"))"
    } else if (fileExists(ENVIRON["TRANS_DIR"] "/" EntryScript)) {
        el = "(progn (setq explicit-shell-file-name \"" "sh" "\") "     \
            "(setq explicit-" "sh" "-args '(\"" ENVIRON["TRANS_DIR"] "/" EntryScript "\" \"-I\" \"-no-rlwrap\"" params ")) " \
            "(command-execute 'shell) (rename-buffer \"" Name "\"))"
    } else {
        l(">> not found: $TRANS_ENTRY or EntryPoint")
        return 1
    }
    command = Emacs " --eval " parameterize(el)

    l(">> forking: " command)
    if (!system(command)) {
        l(">> process exited with code 0")
        exit ExitCode
    } else {
        l(">> process exited with non-zero return code")
        return 1
    }
}

# Fetch the content of a URL. Return a null string if failed.
function curl(url, output,    command, content, line) {
    initCurl()

    if (!Curl) {
        l(">> not found: curl")
        w("[WARNING] curl is not found.")
        return NULLSTR
    }

    command = Curl " --location --silent"
    if (Option["user-agent"])
        command = command " --user-agent " parameterize(Option["user-agent"])
    command = command " " parameterize(url)
    if (output) {
        command = command " --output " parameterize(output)
        system(command)
        return NULLSTR
    }
    content = NULLSTR
    while ((command |& getline line) > 0)
        content = (content ? content "\n" : NULLSTR) line
    return content
}

# Fetch the content of a URL. Return a null string if failed.
function curlPost(url, data, output,    command, content, line) {
    initCurl()

    if (!Curl) {
        l(">> not found: curl")
        w("[WARNING] curl is not found.")
        return NULLSTR
    }

    command = Curl " --location --silent"
    if (Option["user-agent"])
        command = command " --user-agent " parameterize(Option["user-agent"])
    command = command " --request POST --data " data
    command = command " " parameterize(url)
    if (output) {
        command = command " --output " parameterize(output)
        system(command)
        return NULLSTR
    }
    content = NULLSTR
    while ((command |& getline line) > 0)
        content = (content ? content "\n" : NULLSTR) line
    return content
}

# Dump a Unicode string into a byte array. Return the length of the array.
# NOTE: can only be ran once for each text! Build a cache.
function dump(text, group,    command, temp) {
    # hexdump tricks:
    # (1) use -v (--no-squeezing)
    # (2) use "%u" (unsigned integers)
    command = "hexdump" " -v -e'1/1 \"%03u\" \" \"'"
    ("echo " parameterize(text) PIPE command) | getline temp
    split(temp, group, " ")
    return length(group) - 1
}

# Base64 encode a string.
# NOTE: can only be ran once for each text! Build a cache.
function base64(text,    command, temp) {
    command = "base64"
    ("echo -n " parameterize(text) PIPE command) | getline temp
    return temp
}

# Print a Unicode-escaped string. (GNU only)
# FIXME: find a BSD/macOS equivalent
# NOTE: can only be ran once for each text! Build a cache.
function uprintf(text,    command, temp) {
    command = "printf"
    (command " -- " parameterize(text) | getline temp)
    return temp
}

####################################################################
# Utils.awk                                                        #
####################################################################

# Detect gawk version.
function initGawk(    group) {
    Gawk = "gawk"
    GawkVersion = PROCINFO["version"]

    split(PROCINFO["version"], group, ".")
    if (group[1] < 4) {
        e("[ERROR] Oops! Your gawk (version " GawkVersion ") "          \
          "appears to be too old.\n"                                    \
          "        You need at least gawk 4.0.0 to run this program.")
        exit 1
    }
}

# Detect external bidirectional algorithm utility (fribidi);
# Fallback to Unix `rev` if not found.
function initBiDi() {
    FriBidi = detectProgram("fribidi", "--version", 1)
    BiDiNoPad = FriBidi ? "fribidi --nopad" : "rev" SUPERR
    BiDi = FriBidi ? "fribidi --width %s" :
        "rev" SUPERR "| sed \"s/'/\\\\\\'/\" | xargs printf '%%s '"
}

# Detect external readline wrapper (rlwrap).
function initRlwrap() {
    Rlwrap = detectProgram("rlwrap", "--version")
}

# Detect emacs.
function initEmacs() {
    Emacs = detectProgram("emacs", "--version")
}

# Detect curl.
function initCurl() {
    Curl = detectProgram("curl", "--version")
}

# Log any value if debugging is enabled.
function l(value, name, inline, heredoc, valOnly, numSub, sortedIn) {
    if (Option["debug"]) {
        if (name)
            da(value, name, inline, heredoc, valOnly, numSub, sortedIn)
        else
            d(value)
    }
}

# Return a log message if debugging is enabled.
function m(string) {
    if (Option["debug"])
        return ansi("cyan", string) RS
}

# Return 1 if the first version is newer than the second; otherwise return 0.
function newerVersion(ver1, ver2,    i, group1, group2) {
    split(ver1, group1, ".")
    split(ver2, group2, ".")
    for (i = 1; i <= 4; i++) {
        if (group1[i] + 0 > group2[i] + 0)
            return 1
        else if (group1[i] + 0 < group2[i] + 0)
            return 0
    }
    return 0
}

# Fork a rlwrap process as the wrapper. Return non-zero if failed,
function rlwrapMe(    i, command) {
    initRlwrap()

    if (!Rlwrap) {
        l(">> not found: rlwrap")
        return 1
    }

    if (ENVIRON["TRANS_ENTRY"]) {
        command = Rlwrap " " ENVIRON["TRANS_ENTRY"] " "                 \
            parameterize("-no-rlwrap") # never fork rlwrap again!
    } else if (fileExists(ENVIRON["TRANS_DIR"] "/" EntryScript)) {
        command = Rlwrap " sh "                                         \
            parameterize(ENVIRON["TRANS_DIR"] "/" EntryScript)          \
            " - " parameterize("-no-rlwrap") # never fork rlwrap again!
    } else {
        l(">> not found: $TRANS_ENTRY or EntryPoint")
        return 1
    }
    for (i = 1; i < length(ARGV); i++)
        if (ARGV[i])
            command = command " " parameterize(ARGV[i])

    l(">> forking: " command)
    if (!system(command)) {
        l(">> process exited with code 0")
        exit ExitCode
    } else {
        l(">> process exited with non-zero return code")
        return 1
    }
}

# Fork an emacs process as the front-end. Return non-zero if failed,
function emacsMe(    i, params, el, command) {
    initEmacs()

    if (!Emacs) {
        l(">> not found: emacs")
        return 1
    }

    params = ""
    for (i = 1; i < length(ARGV); i++)
        if (ARGV[i])
            params = params " " parameterize(ARGV[i], "\"")
    if (ENVIRON["TRANS_ENTRY"]) {
        el = "(progn (setq explicit-shell-file-name \"" ENVIRON["TRANS_ENTRY"] "\") " \
            "(setq explicit-" Command "-args '(\"-I\" \"-no-rlwrap\"" params ")) " \
            "(command-execute 'shell) (rename-buffer \"" Name "\"))"
    } else if (fileExists(ENVIRON["TRANS_DIR"] "/" EntryScript)) {
        el = "(progn (setq explicit-shell-file-name \"" "sh" "\") "     \
            "(setq explicit-" "sh" "-args '(\"" ENVIRON["TRANS_DIR"] "/" EntryScript "\" \"-I\" \"-no-rlwrap\"" params ")) " \
            "(command-execute 'shell) (rename-buffer \"" Name "\"))"
    } else {
        l(">> not found: $TRANS_ENTRY or EntryPoint")
        return 1
    }
    command = Emacs " --eval " parameterize(el)

    l(">> forking: " command)
    if (!system(command)) {
        l(">> process exited with code 0")
        exit ExitCode
    } else {
        l(">> process exited with non-zero return code")
        return 1
    }
}

# Fetch the content of a URL. Return a null string if failed.
function curl(url, output,    command, content, line) {
    initCurl()

    if (!Curl) {
        l(">> not found: curl")
        w("[WARNING] curl is not found.")
        return NULLSTR
    }

    command = Curl " --location --silent"
    if (Option["user-agent"])
        command = command " --user-agent " parameterize(Option["user-agent"])
    command = command " " parameterize(url)
    if (output) {
        command = command " --output " parameterize(output)
        system(command)
        return NULLSTR
    }
    content = NULLSTR
    while ((command |& getline line) > 0)
        content = (content ? content "\n" : NULLSTR) line
    return content
}

# Dump a Unicode string into a byte array. Return the length of the array.
# NOTE: can only be ran once for each text! Build a cache.
function dump(text, group,    command, temp) {
    # hexdump tricks:
    # (1) use -v (--no-squeezing)
    # (2) use "%u" (unsigned integers)
    command = "hexdump" " -v -e'1/1 \"%03u\" \" \"'"
    ("echo " parameterize(text) PIPE command) | getline temp
    split(temp, group, " ")
    return length(group) - 1
}

# Base64 encode a string.
# NOTE: can only be ran once for each text! Build a cache.
function base64(text,    command, temp) {
    command = "base64"
    ("echo -n " parameterize(text) PIPE command) | getline temp
    return temp
}

# Print a Unicode-escaped string. (GNU only)
# FIXME: find a BSD/macOS equivalent
# NOTE: can only be ran once for each text! Build a cache.
function uprintf(text,    command, temp) {
    command = "printf"
    (command " -- " parameterize(text) | getline temp)
    return temp
}


relpath.awk shouldn't be executable. (dm)

git-svn-id: 30a5f035a20f1bc647618dbad7eea2a951b61b7c@2297 91a5dbb7-01b9-0310-9b5f-b28072856b6e

function normalizePath(path) {
  if (length(path) == 0) path = "."
  path = path pathDelimiter
  gsub((pathDelimiter "+"), pathDelimiter, path)
  return path
}

BEGIN {
  pathDelimiter = "/"

  pathLength = length(path = normalizePath(path))
  referenceLength = length(reference = normalizePath(reference))

  minimumLength = pathLength
  if (referenceLength < minimumLength) minimumLength = referenceLength

  prefixLength = 0
  for (i=1; i<=minimumLength; ++i) {
    character = substr(reference, i, 1)
    if (substr(path, i, 1) != character) break
    if (character == pathDelimiter) prefixLength = i
  }

  if ((prefixLength > 0) || ((substr(path, 1, 1) == pathDelimiter) == (substr(reference, 1, 1) == pathDelimiter))) {
    suffix = substr(path, prefixLength+1)
    prefix = substr(reference, prefixLength+1)
    gsub(("[^" pathDelimiter "]+"), "..", prefix)
    path = normalizePath(prefix suffix)
  }

  path = normalizePath(path)
  sub((pathDelimiter "$"), "", path)

  printf "%s", path
  exit 0
}


Apply regress because of errors.

Apply regress because of errors.

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

BEGIN {
  FS="\t"
  pn = 0
    #str = "date +%m-%d-%Y";
    #str | getline date;
    #close str;
}
#$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
  # increased to 2015 July 1 2015.
  current_year = 2015
  # 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
      r[pn] = $5
      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] = "frozen"
  s[9] = "ingNotReady"
  s[10] = "finished"
  s[11] = "down"
  s[12] = "superseded"
  s[13] = "zapped"
  s[14] = "doNotProcess"
  sn = 15

  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] = "froz"
  sc[9] = "ingN"
  sc[10] = "fini"
  sc[11] = "down"
  sc[12] = "supe"
  sc[13] = "zapp"
  sc[14] = "doNP"
  scn = 15

  #print out header
  printf "Publisher\tPlugin\tContr\tBack\tYear\tT\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, total aus
  for (i = 0 ; i < pn ; i++) {
    printf "%s\t%s\t%s\t%s\t%s\t%s\t%d", p[i], n[i], t[i], k[i], d[i], r[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\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] *.tdb

BEGIN {
  FS="\t"
  pn = 0
    #str = "date +%m-%d-%Y";
    #str | getline date;
    #close str;
}
$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
  # increased to 2015 July 1 2015.
  current_year = 2015
  # 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
      r[pn] = $5
      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] = "frozen"
  s[9] = "ingNotReady"
  s[10] = "finished"
  s[11] = "down"
  s[12] = "superseded"
  s[13] = "zapped"
  s[14] = "doNotProcess"
  sn = 15

  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] = "froz"
  sc[9] = "ingN"
  sc[10] = "fini"
  sc[11] = "down"
  sc[12] = "supe"
  sc[13] = "zapp"
  sc[14] = "doNP"
  scn = 15

  #print out header
  printf "Publisher\tPlugin\tContr\tBack\tYear\tT\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, total aus
  for (i = 0 ; i < pn ; i++) {
    printf "%s\t%s\t%s\t%s\t%s\t%s\t%d", p[i], n[i], t[i], k[i], d[i], r[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\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"
}


Utils: fix newerVersion()

Utils: fix newerVersion()

####################################################################
# Utils.awk                                                        #
####################################################################

# Detect gawk version.
function initGawk(    group) {
    Gawk = "gawk"
    GawkVersion = PROCINFO["version"]

    split(PROCINFO["version"], group, ".")
    if (group[1] < 4) {
        e("[ERROR] Oops! Your gawk (version " GawkVersion ") "          \
          "appears to be too old.\n"                                    \
          "        You need at least gawk 4.0.0 to run this program.")
        exit 1
    }
}

# Detect external bidirectional algorithm utility (fribidi);
# Fallback to Unix `rev` if not found.
function initBiDi() {
    FriBidi = detectProgram("fribidi", "--version", 1)
    BiDiNoPad = FriBidi ? "fribidi --nopad" : "rev"
    BiDi = FriBidi ? "fribidi --width %s" :
        "rev | sed \"s/'/\\\\\\'/\" | xargs printf '%%s '"
}

# Detect external readline wrapper (rlwrap).
function initRlwrap() {
    Rlwrap = detectProgram("rlwrap", "--version")
}

# Detect emacs.
function initEmacs() {
    Emacs = detectProgram("emacs", "--version")
}

# Detect curl.
function initCurl() {
    Curl = detectProgram("curl", "--version")
}

# Log any value if debugging is enabled.
function l(value, name, inline, heredoc, valOnly, numSub, sortedIn) {
    if (Option["debug"]) {
        if (name)
            da(value, name, inline, heredoc, valOnly, numSub, sortedIn)
        else
            d(value)
    }
}

# Return a log message if debugging is enabled.
function m(string) {
    if (Option["debug"])
        return ansi("cyan", string) RS
}

# Return 1 if the first version is newer than the second; otherwise return 0.
function newerVersion(ver1, ver2,    i, group1, group2) {
    split(ver1, group1, ".")
    split(ver2, group2, ".")
    for (i = 1; i <= 4; i++) {
        if (group1[i] + 0 > group2[i] + 0)
            return 1
        else if (group1[i] + 0 < group2[i] + 0)
            return 0
    }
    return 0
}

# Fork a rlwrap process as the wrapper. Return non-zero if failed,
function rlwrapMe(    i, command) {
    initRlwrap()

    if (!Rlwrap) {
        l(">> not found: rlwrap")
        return 1
    } else if (!(ENVIRON["TRANS_PROGRAM"] || fileExists(EntryPoint))) {
        l(">> not found: $TRANS_PROGRAM or EntryPoint")
        return 1
    } else {
        command = Rlwrap " " Gawk " " (ENVIRON["TRANS_PROGRAM"] ?
                                       "\"${TRANS_PROGRAM}\"" :
                                       "-f " EntryPoint)                \
            " - " parameterize("-no-rlwrap") # never fork rlwrap again!
        for (i = 1; i < length(ARGV); i++)
            if (ARGV[i])
                command = command " " parameterize(ARGV[i])

        l(">> forking: " command)
        if (!system(command)) {
            l(">> process exited with code 0")
            exit ExitCode
        } else {
            l(">> process exited with non-zero return code")
            return 1
        }
    }
}

# Fork an emacs process as the front-end. Return non-zero if failed,
function emacsMe(    i, params, el, command) {
    initEmacs()

    if (!Emacs) {
        l(">> not found: emacs")
        return 1
    } else if (!(ENVIRON["TRANS_PROGRAM"] || fileExists(EntryPoint))) {
        l(">> not found: $TRANS_PROGRAM or EntryPoint")
        return 1
    } else {
        params = ""
        for (i = 1; i < length(ARGV); i++)
            if (ARGV[i])
                params = params " " parameterize(ARGV[i], "\"")
        if (ENVIRON["TRANS_PROGRAM"]) {
            el = "(progn (setq trans-program (getenv \"TRANS_PROGRAM\")) " \
                "(setq explicit-shell-file-name \"" Gawk "\") "         \
                "(setq explicit-" Gawk "-args (cons trans-program '(\"-\" \"-I\" \"-no-rlwrap\"" params "))) " \
                "(command-execute 'shell) (rename-buffer \"" Name "\"))"
        } else {
            el = "(progn (setq explicit-shell-file-name \"" Gawk "\") " \
                "(setq explicit-" Gawk "-args '(\"-f\" \"" EntryPoint "\" \"--\" \"-I\" \"-no-rlwrap\"" params ")) " \
                "(command-execute 'shell) (rename-buffer \"" Name "\"))"
        }
        command = Emacs " --eval " parameterize(el)

        l(">> forking: " command)
        if (!system(command)) {
            l(">> process exited with code 0")
            exit ExitCode
        } else {
            l(">> process exited with non-zero return code")
            return 1
        }
    }
}

# Fetch the content of a URL. Return a null string if failed.
function curl(url,    command, content, line) {
    initCurl()

    if (!Curl) {
        l(">> not found: curl")
        w("[WARNING] curl is not found.")
        return NULLSTR
    } else {
        command = Curl " --location --silent " url
        content = NULLSTR
        while ((command |& getline line) > 0)
            content = (content ? content "\n" : NULLSTR) line
        return content
    }
}

####################################################################
# Utils.awk                                                        #
####################################################################

# Detect gawk version.
function initGawk(    group) {
    Gawk = "gawk"
    GawkVersion = PROCINFO["version"]

    split(PROCINFO["version"], group, ".")
    if (group[1] < 4) {
        e("[ERROR] Oops! Your gawk (version " GawkVersion ") "          \
          "appears to be too old.\n"                                    \
          "        You need at least gawk 4.0.0 to run this program.")
        exit 1
    }
}

# Detect external bidirectional algorithm utility (fribidi);
# Fallback to Unix `rev` if not found.
function initBiDi() {
    FriBidi = detectProgram("fribidi", "--version", 1)
    BiDiNoPad = FriBidi ? "fribidi --nopad" : "rev"
    BiDi = FriBidi ? "fribidi --width %s" :
        "rev | sed \"s/'/\\\\\\'/\" | xargs printf '%%s '"
}

# Detect external readline wrapper (rlwrap).
function initRlwrap() {
    Rlwrap = detectProgram("rlwrap", "--version")
}

# Detect emacs.
function initEmacs() {
    Emacs = detectProgram("emacs", "--version")
}

# Detect curl.
function initCurl() {
    Curl = detectProgram("curl", "--version")
}

# Log any value if debugging is enabled.
function l(value, name, inline, heredoc, valOnly, numSub, sortedIn) {
    if (Option["debug"]) {
        if (name)
            da(value, name, inline, heredoc, valOnly, numSub, sortedIn)
        else
            d(value)
    }
}

# Return a log message if debugging is enabled.
function m(string) {
    if (Option["debug"])
        return ansi("cyan", string) RS
}

# Return 1 if the first version is newer than the second; otherwise return 0.
function newerVersion(ver1, ver2,    group1, group2) {
    split(ver1, group1, ".")
    split(ver2, group2, ".")
    if (group1[1] + 0 > group2[1] + 0)
        return 1
    else if (group1[1] + 0 < group2[1] + 0)
        return 0
    if (group1[2] + 0 > group2[2] + 0)
        return 1
    else if (group1[2] + 0 < group2[2] + 0)
        return 0
    if (group1[3] + 0 > group2[3] + 0)
        return 1
    else if (group1[3] + 0 < group2[3] + 0)
        return 0
    return 0
}

# Fork a rlwrap process as the wrapper. Return non-zero if failed,
function rlwrapMe(    i, command) {
    initRlwrap()

    if (!Rlwrap) {
        l(">> not found: rlwrap")
        return 1
    } else if (!(ENVIRON["TRANS_PROGRAM"] || fileExists(EntryPoint))) {
        l(">> not found: $TRANS_PROGRAM or EntryPoint")
        return 1
    } else {
        command = Rlwrap " " Gawk " " (ENVIRON["TRANS_PROGRAM"] ?
                                       "\"${TRANS_PROGRAM}\"" :
                                       "-f " EntryPoint)                \
            " - " parameterize("-no-rlwrap") # never fork rlwrap again!
        for (i = 1; i < length(ARGV); i++)
            if (ARGV[i])
                command = command " " parameterize(ARGV[i])

        l(">> forking: " command)
        if (!system(command)) {
            l(">> process exited with code 0")
            exit ExitCode
        } else {
            l(">> process exited with non-zero return code")
            return 1
        }
    }
}

# Fork an emacs process as the front-end. Return non-zero if failed,
function emacsMe(    i, params, el, command) {
    initEmacs()

    if (!Emacs) {
        l(">> not found: emacs")
        return 1
    } else if (!(ENVIRON["TRANS_PROGRAM"] || fileExists(EntryPoint))) {
        l(">> not found: $TRANS_PROGRAM or EntryPoint")
        return 1
    } else {
        params = ""
        for (i = 1; i < length(ARGV); i++)
            if (ARGV[i])
                params = params " " parameterize(ARGV[i], "\"")
        if (ENVIRON["TRANS_PROGRAM"]) {
            el = "(progn (setq trans-program (getenv \"TRANS_PROGRAM\")) " \
                "(setq explicit-shell-file-name \"" Gawk "\") "         \
                "(setq explicit-" Gawk "-args (cons trans-program '(\"-\" \"-I\" \"-no-rlwrap\"" params "))) " \
                "(command-execute 'shell) (rename-buffer \"" Name "\"))"
        } else {
            el = "(progn (setq explicit-shell-file-name \"" Gawk "\") " \
                "(setq explicit-" Gawk "-args '(\"-f\" \"" EntryPoint "\" \"--\" \"-I\" \"-no-rlwrap\"" params ")) " \
                "(command-execute 'shell) (rename-buffer \"" Name "\"))"
        }
        command = Emacs " --eval " parameterize(el)

        l(">> forking: " command)
        if (!system(command)) {
            l(">> process exited with code 0")
            exit ExitCode
        } else {
            l(">> process exited with non-zero return code")
            return 1
        }
    }
}

# Fetch the content of a URL. Return a null string if failed.
function curl(url,    command, content, line) {
    initCurl()

    if (!Curl) {
        l(">> not found: curl")
        w("[WARNING] curl is not found.")
        return NULLSTR
    } else {
        command = Curl " --location --silent " url
        content = NULLSTR
        while ((command |& getline line) > 0)
            content = (content ? content "\n" : NULLSTR) line
        return content
    }
}


neaten up file generation

neaten up file generation

# See LICENSE file for copyright and license details.

BEGIN {
	FS = ";"
	# set up hexadecimal lookup table
	for(i = 0; i < 16; i++)
		hex[sprintf("%X",i)] = i;
}

$3  ~ /^L/ { alphav[alphac++] = $1; }
($3  ~ /^Z/) || ($5 == "WS") || ($5 == "S") || ($5 == "B") { spacev[spacec++] = $1; }
$3 == "Cc" { cntrlv[cntrlc++] = $1; }
$3 == "Lu" { upperv[upperc++] = $1; tolowerv[uppercc++] = ($14 == "") ? $1 : $14; }
$3 == "Ll" { lowerv[lowerc++] = $1; toupperv[lowercc++] = ($13 == "") ? $1 : $13; }
$3 == "Lt" { titlev[titlec++] = $1; }
$3 == "Nd" { digitv[digitc++] = $1; }

END {
	mkis("alpha", alphav, alphac, "src/isalpharune.c", q, "");
	mkis("space", spacev, spacec, "src/isspacerune.c", q, "");
	mkis("cntrl", cntrlv, cntrlc, "src/iscntrlrune.c", q, "");
	mkis("upper", upperv, upperc, "src/upperrune.c",   tolowerv, "lower");
	mkis("lower", lowerv, lowerc, "src/lowerrune.c",   toupperv, "upper");
	mkis("title", titlev, titlec, "src/istitlerune.c", q, "");
	mkis("digit", digitv, digitc, "src/isdigitrune.c", q, "");
}

# parse hexadecimal rune index to int
function code(s) {
	x = 0;
	for(i = 1; i <= length(s); i++) {
		c = substr(s, i, 1);
		x = (x*16) + hex[c];
	}
	return x;
}

# generate 'is<name>rune' unicode lookup function
function mkis(name, runev, runec, file, casev, casename) {
	rune1c = 0;
	rune2c = 0;
	rune3c = 0;
	rune4c = 0;
	mode = 1;

	print "/* Automatically generated by mkrunetype.awk */" > file
	print "#include <stdlib.h>" > file
	print "#include \"utf.h\"" > file
	print "#include \"runetype.h\"" > file

	#sort rune groups into singletons, ranges and laces
	for(j = 0; j < runec; j++) {
		# range
		if(code(runev[j+1]) == code(runev[j])+1 && ((length(casev) == 0) ||
		   code(casev[j+1]) == code(casev[j])+1) && j+1 < runec) {
			if (mode == 2) {
				continue;
			} else if (mode == 3) {
				rune3v1[rune3c] = runev[j];
				rune3c++;
			} else if (mode == 4) {
				rune4v1[rune4c] = runev[j];
				rune4c++;
			}
			mode = 2;
			rune2v0[rune2c] = runev[j];
			if(length(casev) > 0) {
				case2v[rune2c] = casev[j];
			}
			continue;
		}
		# lace 1
		if(code(runev[j+1]) == code(runev[j])+2 && ((length(casev) == 0) ||
		   (code(casev[j+1]) == code(runev[j+1])+1 && code(casev[j]) == code(runev[j])+1)) &&
		   j+1 < runec) {
			if (mode == 3) {
				continue;
			} else if (mode == 2) {
				rune2v1[rune2c] = runev[j];
				rune2c++;
			} else if (mode == 4) {
				rune4v1[rune2c] = runev[j];
				rune4c++;
			}
			mode = 3;
			rune3v0[rune3c] = runev[j];
			continue;
		}
		# lace 2
		if(code(runev[j+1]) == code(runev[j])+2 && ((length(casev) == 0) ||
		   (code(casev[j+1]) == code(runev[j+1])-1 && code(casev[j]) == code(runev[j])-1)) &&
		   j+1 < runec) {
			if (mode == 4) {
				continue;
			} else if (mode == 2) {
				rune2v1[rune2c] = runev[j];
				rune2c++;
			} else if (mode == 3) {
				rune3v1[rune2c] = runev[j];
				rune3c++;
			}
			mode = 4;
			rune4v0[rune4c] = runev[j];
			continue;
		}
		# terminating case
		if (mode == 1) {
			rune1v[rune1c] = runev[j];
			if (length(casev) > 0) {
				case1v[rune1c] = casev[j];
			}
			rune1c++;
		} else if (mode == 2) {
			rune2v1[rune2c] = runev[j];
			rune2c++;
		} else if (mode == 3) {
			rune3v1[rune3c] = runev[j];
			rune3c++;
		} else { #lace 2
			rune4v1[rune4c] = runev[j];
			rune4c++;
		}
		mode = 1;
	}
	print HEADER > file;

	#generate list of laces 1
	if(rune3c > 0) {
		print "static Rune "name"3[][2] = {" > file;
		for(j = 0; j < rune3c; j++) {
			print "\t{ 0x"rune3v0[j]", 0x"rune3v1[j]" }," > file;
		}
		print "};\n" > file;
	}

	#generate list of laces 2
	if(rune4c > 0) {
		print "static Rune "name"4[][2] = {" > file;
		for(j = 0; j < rune4c; j++) {
			print "\t{ 0x"rune4v0[j]", 0x"rune4v1[j]" }," > file;
		}
		print "};\n" > file;
	}

	# generate list of ranges
	if(rune2c > 0) {
		if(length(casev) > 0) {
			print "static Rune "name"2[][3] = {" > file;
			for(j = 0; j < rune2c; j++) {
				print "\t{ 0x"rune2v0[j]", 0x"rune2v1[j]", 0x"case2v[j]" }," > file;
			}
		} else {
			print "static Rune "name"2[][2] = {" > file
			for(j = 0; j < rune2c; j++) {
				print "\t{ 0x"rune2v0[j]", 0x"rune2v1[j]" }," > file;
			}
		}
		print "};\n" > file;
	}

	# generate list of singletons
	if(rune1c > 0) {
		if(length(casev) > 0) {
			print "static Rune "name"1[][2] = {" > file;
			for(j = 0; j < rune1c; j++) {
				print "\t{ 0x"rune1v[j]", 0x"case1v[j]" }," > file;
			}
		} else {
			print "static Rune "name"1[] = {" > file;
			for(j = 0; j < rune1c; j++) {
				print "\t0x"rune1v[j]"," > file;
			}
		}
		print "};\n" > file;
	}

	# generate lookup function
	print "int\nis"name"rune(Rune r)\n{" > file;
	if(rune4c > 0 || rune3c > 0)
		print "\tRune *match;\n" > file;
	if(rune4c > 0) {
		print "\tif((match = bsearch(&r, "name"4, nelem("name"4), sizeof *"name"4, &rune2cmp)))" > file;
		print "\t\treturn !((r - match[0]) % 2);" > file;
	}
	if(rune3c > 0) {
		print "\tif((match = bsearch(&r, "name"3, nelem("name"3), sizeof *"name"3, &rune2cmp)))" > file;
		print "\t\treturn !((r - match[0]) % 2);" > file;
	}
	if(rune2c > 0) {
		print "\tif(bsearch(&r, "name"2, nelem("name"2), sizeof *"name"2, &rune2cmp))\n\t\treturn 1;" > file;
	}
	if(rune1c > 0) {
		print "\tif(bsearch(&r, "name"1, nelem("name"1), sizeof *"name"1, &rune1cmp))\n\t\treturn 1;" > file;
	}
	print "\treturn 0;\n}" > file;

	# generate case conversion function
	if(length(casev) > 0) {
		print "\nint\nto"casename"rune(Rune r)\n{\n\tRune *match;\n" > file;
		if(rune4c > 0) {
			print "\tmatch = bsearch(&r, "name"4, nelem("name"4), sizeof *"name"4, &rune2cmp);" > file;
			print "\tif(match)" > file;
			print "\t\treturn ((r - match[0]) % 2) ? r : r - 1;" > file;
		}
		if(rune3c > 0) {
			print "\tmatch = bsearch(&r, "name"3, nelem("name"3), sizeof *"name"3, &rune2cmp);" > file;
			print "\tif(match)" > file;
			print "\t\treturn ((r - match[0]) % 2) ? r : r + 1;" > file;
		}
		if(rune2c > 0) {
			print "\tmatch = bsearch(&r, "name"2, nelem("name"2), sizeof *"name"2, &rune2cmp);" > file;
			print "\tif(match)" > file;
			print "\t\treturn match[2] + (r - match[0]);" > file;
		}
		if(rune1c > 0) {
			print "\tmatch = bsearch(&r, "name"1, nelem("name"1), sizeof *"name"1, &rune1cmp);" > file;
			print "\tif(match)" > file;
			print "\t\treturn match[1];" > file;
		}
		print "\treturn r;\n}" > file;
	}
}

# See LICENSE file for copyright and license details.

BEGIN {
	FS = ";"
	# set up hexadecimal lookup table
	for(i = 0; i < 16; i++)
		hex[sprintf("%X",i)] = i;
	HEADER = "/* Automatically generated by mkrunetype.awk */\n#include <stdlib.h>\n\n#include \"utf.h\"\n#include \"runetype.h\"\n"
}

$3  ~ /^L/ { alphav[alphac++] = $1; }
($3  ~ /^Z/) || ($5 == "WS") || ($5 == "S") || ($5 == "B") { spacev[spacec++] = $1; }
$3 == "Cc" { cntrlv[cntrlc++] = $1; }
$3 == "Lu" { upperv[upperc++] = $1; tolowerv[uppercc++] = ($14 == "") ? $1 : $14; }
$3 == "Ll" { lowerv[lowerc++] = $1; toupperv[lowercc++] = ($13 == "") ? $1 : $13; }
$3 == "Lt" { titlev[titlec++] = $1; }
$3 == "Nd" { digitv[digitc++] = $1; }

END {
	mkis("alpha", alphav, alphac, "src/isalpharune.c", q, "");
	mkis("space", spacev, spacec, "src/isspacerune.c", q, "");
	mkis("cntrl", cntrlv, cntrlc, "src/iscntrlrune.c", q, "");
	mkis("upper", upperv, upperc, "src/upperrune.c",   tolowerv, "lower");
	mkis("lower", lowerv, lowerc, "src/lowerrune.c",   toupperv, "upper");
	mkis("title", titlev, titlec, "src/istitlerune.c", q, "");
	mkis("digit", digitv, digitc, "src/isdigitrune.c", q, "");
}

# parse hexadecimal rune index to int
function code(s) {
	x = 0;
	for(i = 1; i <= length(s); i++) {
		c = substr(s, i, 1);
		x = (x*16) + hex[c];
	}
	return x;
}

# generate 'is<name>rune' unicode lookup function
function mkis(name, runev, runec, file, casev, casename) {
	rune1c = 0;
	rune2c = 0;
	rune3c = 0;
	rune4c = 0;
	mode = 1;

	#sort rune groups into singletons, ranges and laces
	for(j = 0; j < runec; j++) {
		# range
		if(code(runev[j+1]) == code(runev[j])+1 && ((length(casev) == 0) ||
		   code(casev[j+1]) == code(casev[j])+1) && j+1 < runec) {
			if (mode == 2) {
				continue;
			} else if (mode == 3) {
				rune3v1[rune3c] = runev[j];
				rune3c++;
			} else if (mode == 4) {
				rune4v1[rune4c] = runev[j];
				rune4c++;
			}
			mode = 2;
			rune2v0[rune2c] = runev[j];
			if(length(casev) > 0) {
				case2v[rune2c] = casev[j];
			}
			continue;
		}
		# lace 1
		if(code(runev[j+1]) == code(runev[j])+2 && ((length(casev) == 0) ||
		   (code(casev[j+1]) == code(runev[j+1])+1 && code(casev[j]) == code(runev[j])+1)) &&
		   j+1 < runec) {
			if (mode == 3) {
				continue;
			} else if (mode == 2) {
				rune2v1[rune2c] = runev[j];
				rune2c++;
			} else if (mode == 4) {
				rune4v1[rune2c] = runev[j];
				rune4c++;
			}
			mode = 3;
			rune3v0[rune3c] = runev[j];
			continue;
		}
		# lace 2
		if(code(runev[j+1]) == code(runev[j])+2 && ((length(casev) == 0) ||
		   (code(casev[j+1]) == code(runev[j+1])-1 && code(casev[j]) == code(runev[j])-1)) &&
		   j+1 < runec) {
			if (mode == 4) {
				continue;
			} else if (mode == 2) {
				rune2v1[rune2c] = runev[j];
				rune2c++;
			} else if (mode == 3) {
				rune3v1[rune2c] = runev[j];
				rune3c++;
			}
			mode = 4;
			rune4v0[rune4c] = runev[j];
			continue;
		}
		# terminating case
		if (mode == 1) {
			rune1v[rune1c] = runev[j];
			if (length(casev) > 0) {
				case1v[rune1c] = casev[j];
			}
			rune1c++;
		} else if (mode == 2) {
			rune2v1[rune2c] = runev[j];
			rune2c++;
		} else if (mode == 3) {
			rune3v1[rune3c] = runev[j];
			rune3c++;
		} else { #lace 2
			rune4v1[rune4c] = runev[j];
			rune4c++;
		}
		mode = 1;
	}
	print HEADER > file;

	#generate list of laces 1
	if(rune3c > 0) {
		print "static Rune "name"3[][2] = {" > file;
		for(j = 0; j < rune3c; j++) {
			print "\t{ 0x"rune3v0[j]", 0x"rune3v1[j]" }," > file;
		}
		print "};\n" > file;
	}

	#generate list of laces 2
	if(rune4c > 0) {
		print "static Rune "name"4[][2] = {" > file;
		for(j = 0; j < rune4c; j++) {
			print "\t{ 0x"rune4v0[j]", 0x"rune4v1[j]" }," > file;
		}
		print "};\n" > file;
	}

	# generate list of ranges
	if(rune2c > 0) {
		if(length(casev) > 0) {
			print "static Rune "name"2[][3] = {" > file;
			for(j = 0; j < rune2c; j++) {
				print "\t{ 0x"rune2v0[j]", 0x"rune2v1[j]", 0x"case2v[j]" }," > file;
			}
		} else {
			print "static Rune "name"2[][2] = {" > file
			for(j = 0; j < rune2c; j++) {
				print "\t{ 0x"rune2v0[j]", 0x"rune2v1[j]" }," > file;
			}
		}
		print "};\n" > file;
	}

	# generate list of singletons
	if(rune1c > 0) {
		if(length(casev) > 0) {
			print "static Rune "name"1[][2] = {" > file;
			for(j = 0; j < rune1c; j++) {
				print "\t{ 0x"rune1v[j]", 0x"case1v[j]" }," > file;
			}
		} else {
			print "static Rune "name"1[] = {" > file;
			for(j = 0; j < rune1c; j++) {
				print "\t0x"rune1v[j]"," > file;
			}
		}
		print "};\n" > file;
	}
	# generate lookup function
	print "int\nis"name"rune(Rune r)\n{" > file;
	if(rune4c > 0 || rune3c > 0)
		print "\tRune *match;\n" > file;
	if(rune4c > 0) {
		print "\tif((match = bsearch(&r, "name"4, nelem("name"4), sizeof *"name"4, &rune2cmp)))" > file;
		print "\t\treturn !((r - match[0]) % 2);" > file;
	}
	if(rune3c > 0) {
		print "\tif((match = bsearch(&r, "name"3, nelem("name"3), sizeof *"name"3, &rune2cmp)))" > file;
		print "\t\treturn !((r - match[0]) % 2);" > file;
	}
	if(rune2c > 0) {
		print "\tif(bsearch(&r, "name"2, nelem("name"2), sizeof *"name"2, &rune2cmp))\n\t\treturn 1;" > file;
	}
	if(rune1c > 0) {
		print "\tif(bsearch(&r, "name"1, nelem("name"1), sizeof *"name"1, &rune1cmp))\n\t\treturn 1;" > file;
	}
	print "\treturn 0;\n}" > file;

	# generate case conversion function
	if(length(casev) > 0) {
		print "\nint\nto"casename"rune(Rune r)\n{\n\tRune *match;\n" > file;
		if(rune4c > 0) {
			print "\tmatch = bsearch(&r, "name"4, nelem("name"4), sizeof *"name"4, &rune2cmp);" > file;
			print "\tif (match)" > file;
			print "\t\treturn ((r - match[0]) % 2) ? r : r - 1;" > file;
		}
		if(rune3c > 0) {
			print "\tmatch = bsearch(&r, "name"3, nelem("name"3), sizeof *"name"3, &rune2cmp);" > file;
			print "\tif (match)" > file;
			print "\t\treturn ((r - match[0]) % 2) ? r : r + 1;" > file;
		}
		if(rune2c > 0) {
			print "\tmatch = bsearch(&r, "name"2, nelem("name"2), sizeof *"name"2, &rune2cmp);" > file;
			print "\tif (match)" > file;
			print "\t\treturn match[2] + (r - match[0]);" > file;
		}
		if(rune1c > 0) {
			print "\tmatch = bsearch(&r, "name"1, nelem("name"1), sizeof *"name"1, &rune1cmp);" > file;
			print "\tif (match)" > file;
			print "\t\treturn match[1];" > file;
		}
		print "\treturn r;\n}" > file;
	}
}


Parameter by which to tilt loops.

Parameter by which to tilt loops.

#!/usr/bin/awk

# TODO:
# cat nfa.dot | dot -Tplain > /tmp/a.mp \
# && mpost -s 'outputformat="svg"' -s 'outputtemplate="%j-%c.%o"' /tmp/a.mp

# Only intended to consume Dot generated from libfsm's .dot output,
# and not graphviz diagrams in general.

function sname(s)
{
	return s;
}

# TODO: convert label from graphviz's html to a utf8
# This is either <xyz> or abc (with no quotes)
function slabel(s)
{
	# XXX: placeholder
	if (s == "<>") {
		return "";
	}
	if (s == "\"\"") {
		return "";
	}

# XXX: placeholder
gsub(/ε/, "e", s); # XXX: why not?
gsub(/"/, "", s)
gsub(/</, "", s)
gsub(/>/, "", s)

	return s;
}

BEGIN {

	# TODO: common stuff for multiple graphs

	loopangle = 0; # angle by which to tilt loops. 0 is vertical

	print "path e;"

	print "input fsm.mp;"

	# TODO: can use 'scantokens' to eval a string, .: don't need to pass diam etc

	print "vardef fsmgvedge(suffix tail, head)(expr pp, lab) expr e =";
	print "	draw e withpen pencircle scaled 0.25bp withcolor red;";

	# TODO: better explain this
	# TODO: explain reversed for q, else we'd find p again, where head == tail
	print "	pair p; p = e           intersectionpoint head.perim;\n";
	print "	pair q; q = (reverse e) intersectionpoint tail.perim;\n";

	print "	if head = tail:"
	print "		numeric t; t = arctime (arclength e / 2) of e;"
	print "		path m; m = head -- point t of e;"
	print "		numeric ta; (ta, whatever) = head.perim intersectiontimes m;"
#	print "		draw point ta of head.perim withpen pencircle scaled 4bp withcolor red;"
	printf "		fsmloop(head)(ta - (length fullcircle * %f / 360), lab);\n", loopangle
	print "	else:"
	print "		path b; b = tail .. q .. p .. head;"
# TODO: explain this. we permit a 2% lengthening threshold relative to graphviz's b-spline
# this affects NFA especially
# XXX: or (arclength b > 6in), but wigglyness would be a better metric
	print "		r := arclength b / arclength e;";
	print "		if (r > 1.02) or (r < 0.93):";
	print "			b := e;"
	print "		fi;"
	print "		t0 := -1;"
	print "		if lab <> \"\":"
# TODO: explain we find what time (lx,ly) is along graphviz's edge, e
# then pass the time and use that for placing our label on our edge
# use intersectiontimes to find the time of (lx,ly) along e
# or find by e cutbefore (lx,ly) and use arctime to find the time wrt e
# XXX: but lx,ly isn't even on the line. could mirror it about the line head--tail

	print "			pair tx; tx = pp intersectiontimes b;"
	print "			t0 := ypart tx;" # XXX: cheesy
	print "		fi;"
	print "		fsmedge(tail, head)(t0, lab) b;"
	print "	fi;"

	print "enddef;";
}

/^graph / {
	printf "beginfig(%s)\n", $2
}

# node S6 0.97222 0.41667 0.30556 0.30556 "" solid circle black lightgrey
# node name x y width height label style shape color fillcolor
/^node / {
	name   = sname($2);
	x      = $3;
	y      = $4;
	width  = $5;
	height = $6;
	label  = slabel($7);
	style  = $8;
	shape  = $9;
	# TODO: label delimited by <>, may contain spaces

	# TODO: quantise node positions

# TODO: when plotting in graphviz, assume all nodes have the same width (not zero!). enough for "SXX" perhaps
# that should align them all on the same centre line. and pass fixedsize=true

	# TODO: explain this is diameter
	# TODO: store 8bp as a constant somewhere
# TODO: explain we take diameter from label presence and endstateness.
# ignore graphviz height and width, because it scales with label width and that looks bad for S1 vs S5
	diam = 0.3 + length(label) * 0.05;

	isend = shape == "doublecircle";

	if ($2 != "start") {
		printf "\tfsmstate(%s)(%fin, %s, \"%s\") (%fin, %fin);\n", name, diam, isend? "true" : "false", label, x, y
	}
}

# edge tail head n x1 y1 .. xn yn [label xl yl] style color
# edge S3 S6 10 3.5244 0.20119 3.2727 0.19014 2.7322 0.17358 2.2778 0.20833 1.873 0.23929 1.7755 0.28067 1.375 0.34722 1.3389 0.35322 1.3009 0.35974 1.2635 0.3663 <ε> 2.3264 0.3125 solid black
/^edge / {
	tail = sname($2);
	head = sname($3);
	n    = $4;

# TODO: pretty sure i have head, tail the wrong way around...
if (tail == "start") {
	printf "\tfsmstart(%s);\n", head
} else {

	v = split($0, a, " ");
	# TODO: assert n < v - whatever
	# TODO: assert n >= 4 (knot, control, control); mind trailing stuff... label etc

	# TODO: note we assume -Etailclip=false -Eheadclip=false -Edir=none
	# TODO: explain that requirement

	# things i wish the graphviz documentation said about -Tplain:
	# graphviz always outputs cubic b-splines
	# 3i+1 points total. format is { knot, control, control }* knot
	# bezier is ordered tail to head
	# first two points (knot, control) are ontop of each other, in the tail node
	# last two points (control, knot) are ontop of each other, in the head node
	# coordinates are in inches

	# We connect graphviz's path to endpoints at each node's centre, to ensure
	# a path between nodes does intersect with each node. This is just to be on
	# the safe side; the graphviz endpoints ought to be inside the node anyway
	# due to -Etailclip=false and -Eheadclip=false.

	printf "\te := %s .. \n", tail;
	for (i = 1; i <= n - 1; i += 3) {
		# knot
		ox = a[(i + 0) * 2 + 3];
		oy = a[(i + 0) * 2 + 4];

		# control
		c1x = a[(i + 1) * 2 + 3];
		c1y = a[(i + 1) * 2 + 4];

		# control
		c2x = a[(i + 2) * 2 + 3];
		c2y = a[(i + 2) * 2 + 4];

		printf "\t(%fin, %fin) .. controls (%fin, %fin) and (%fin, %fin) ..\n", ox, oy, c1x, c1y, c2x, c2y;
	}
	# TODO: knot at end, which i haven't drawn
	# last point:
	{
		# knot
		ox = a[(n + 0) * 2 + 3];
		oy = a[(n + 0) * 2 + 4];
		printf "\t(%fin, %fin) .. %s;\n", ox, oy, head;
	}

	# XXX: assuming no space in label
label = slabel(a[n * 2 + 5]);
lx    = (a[n * 2 + 6]);
ly    = (a[n * 2 + 7]);

	if (n * 2 == v - 4 - 2) {
		label = "";
	}

	print "path pp;"

	if (head != tail && label != "") {
#		printf "draw (%fin, %fin) withpen pencircle scaled 3bp withcolor red;", lx, ly
#		printf "draw (%fin, %fin) reflectedabout(%s, %s) withpen pencircle scaled 2bp withcolor red;", lx, ly, tail, head

		printf "pair lxy; lxy = (%fin, %fin);\n", lx, ly
		printf "pair llxy; llxy = lxy reflectedabout(%s, %s);\n", tail, head
		print "path lpp; lpp = llxy -- lxy;"

		# XXX: i dislike this indeed! but at least the extension is constant
		# TODO: instead of extending like this, i'd rather use the line's direction,
		# and find a lxy--whatever point on e
		print "pair ja; ja = lpp projectby +2in;"
		print "pair jb; jb = lpp projectby -2in;"
		print "pp = ja -- jb;"

#		printf "draw pp withpen pencircle scaled 0.5bp withcolor green;\n"
#		printf "draw llxy -- lxy withpen pencircle scaled 1bp withcolor red;\n"
	}

	printf "fsmgvedge(%s, %s)(pp, \"%s\") e;\n", tail, head, label;
}
}

/^stop$/ {
	# XXX: ought to be per graph; stop is per file
	print "endfig;"
}

END {
	print "end."
}

#!/usr/bin/awk

# TODO:
# cat nfa.dot | dot -Tplain > /tmp/a.mp \
# && mpost -s 'outputformat="svg"' -s 'outputtemplate="%j-%c.%o"' /tmp/a.mp

# Only intended to consume Dot generated from libfsm's .dot output,
# and not graphviz diagrams in general.

function sname(s)
{
	return s;
}

# TODO: convert label from graphviz's html to a utf8
# This is either <xyz> or abc (with no quotes)
function slabel(s)
{
	# XXX: placeholder
	if (s == "<>") {
		return "";
	}
	if (s == "\"\"") {
		return "";
	}

# XXX: placeholder
gsub(/ε/, "e", s); # XXX: why not?
gsub(/"/, "", s)
gsub(/</, "", s)
gsub(/>/, "", s)

	return s;
}

BEGIN {

	# TODO: common stuff for multiple graphs

	print "path e;"

	print "input fsm.mp;"

	# TODO: can use 'scantokens' to eval a string, .: don't need to pass diam etc

	print "vardef fsmgvedge(suffix tail, head)(expr pp, lab) expr e =";
	print "	draw e withpen pencircle scaled 0.25bp withcolor red;";

	# TODO: better explain this
	# TODO: explain reversed for q, else we'd find p again, where head == tail
	print "	pair p; p = e           intersectionpoint head.perim;\n";
	print "	pair q; q = (reverse e) intersectionpoint tail.perim;\n";

	print "	if head = tail:"
	print "		numeric t; t = arctime (arclength e / 2) of e;"
	print "		path m; m = head -- point t of e;"
	print "		numeric ta; (ta, whatever) = head.perim intersectiontimes m;"
#	print "		draw point ta of head.perim withpen pencircle scaled 4bp withcolor red;"
	print "		fsmloop(head)(ta, lab);"
	print "	else:"
	print "		path b; b = tail .. q .. p .. head;"
# TODO: explain this. we permit a 2% lengthening threshold relative to graphviz's b-spline
# this affects NFA especially
# XXX: or (arclength b > 6in), but wigglyness would be a better metric
	print "		r := arclength b / arclength e;";
	print "		if (r > 1.02) or (r < 0.93):";
	print "			b := e;"
	print "		fi;"
	print "		t0 := -1;"
	print "		if lab <> \"\":"
# TODO: explain we find what time (lx,ly) is along graphviz's edge, e
# then pass the time and use that for placing our label on our edge
# use intersectiontimes to find the time of (lx,ly) along e
# or find by e cutbefore (lx,ly) and use arctime to find the time wrt e
# XXX: but lx,ly isn't even on the line. could mirror it about the line head--tail

	print "			pair tx; tx = pp intersectiontimes b;"
	print "			t0 := ypart tx;" # XXX: cheesy
	print "		fi;"
	print "		fsmedge(tail, head)(t0, lab) b;"
	print "	fi;"

	print "enddef;";
}

/^graph / {
	printf "beginfig(%s)\n", $2
}

# node S6 0.97222 0.41667 0.30556 0.30556 "" solid circle black lightgrey
# node name x y width height label style shape color fillcolor
/^node / {
	name   = sname($2);
	x      = $3;
	y      = $4;
	width  = $5;
	height = $6;
	label  = slabel($7);
	style  = $8;
	shape  = $9;
	# TODO: label delimited by <>, may contain spaces

	# TODO: quantise node positions

# TODO: when plotting in graphviz, assume all nodes have the same width (not zero!). enough for "SXX" perhaps
# that should align them all on the same centre line. and pass fixedsize=true

	# TODO: explain this is diameter
	# TODO: store 8bp as a constant somewhere
# TODO: explain we take diameter from label presence and endstateness.
# ignore graphviz height and width, because it scales with label width and that looks bad for S1 vs S5
	diam = 0.3 + length(label) * 0.05;

	isend = shape == "doublecircle";

	if ($2 != "start") {
		printf "\tfsmstate(%s)(%fin, %s, \"%s\") (%fin, %fin);\n", name, diam, isend? "true" : "false", label, x, y
	}
}

# edge tail head n x1 y1 .. xn yn [label xl yl] style color
# edge S3 S6 10 3.5244 0.20119 3.2727 0.19014 2.7322 0.17358 2.2778 0.20833 1.873 0.23929 1.7755 0.28067 1.375 0.34722 1.3389 0.35322 1.3009 0.35974 1.2635 0.3663 <ε> 2.3264 0.3125 solid black
/^edge / {
	tail = sname($2);
	head = sname($3);
	n    = $4;

# TODO: pretty sure i have head, tail the wrong way around...
if (tail == "start") {
	printf "\tfsmstart(%s);\n", head
} else {

	v = split($0, a, " ");
	# TODO: assert n < v - whatever
	# TODO: assert n >= 4 (knot, control, control); mind trailing stuff... label etc

	# TODO: note we assume -Etailclip=false -Eheadclip=false -Edir=none
	# TODO: explain that requirement

	# things i wish the graphviz documentation said about -Tplain:
	# graphviz always outputs cubic b-splines
	# 3i+1 points total. format is { knot, control, control }* knot
	# bezier is ordered tail to head
	# first two points (knot, control) are ontop of each other, in the tail node
	# last two points (control, knot) are ontop of each other, in the head node
	# coordinates are in inches

	# We connect graphviz's path to endpoints at each node's centre, to ensure
	# a path between nodes does intersect with each node. This is just to be on
	# the safe side; the graphviz endpoints ought to be inside the node anyway
	# due to -Etailclip=false and -Eheadclip=false.

	printf "\te := %s .. \n", tail;
	for (i = 1; i <= n - 1; i += 3) {
		# knot
		ox = a[(i + 0) * 2 + 3];
		oy = a[(i + 0) * 2 + 4];

		# control
		c1x = a[(i + 1) * 2 + 3];
		c1y = a[(i + 1) * 2 + 4];

		# control
		c2x = a[(i + 2) * 2 + 3];
		c2y = a[(i + 2) * 2 + 4];

		printf "\t(%fin, %fin) .. controls (%fin, %fin) and (%fin, %fin) ..\n", ox, oy, c1x, c1y, c2x, c2y;
	}
	# TODO: knot at end, which i haven't drawn
	# last point:
	{
		# knot
		ox = a[(n + 0) * 2 + 3];
		oy = a[(n + 0) * 2 + 4];
		printf "\t(%fin, %fin) .. %s;\n", ox, oy, head;
	}

	# XXX: assuming no space in label
label = slabel(a[n * 2 + 5]);
lx    = (a[n * 2 + 6]);
ly    = (a[n * 2 + 7]);

	if (n * 2 == v - 4 - 2) {
		label = "";
	}

	print "path pp;"

	if (head != tail && label != "") {
#		printf "draw (%fin, %fin) withpen pencircle scaled 3bp withcolor red;", lx, ly
#		printf "draw (%fin, %fin) reflectedabout(%s, %s) withpen pencircle scaled 2bp withcolor red;", lx, ly, tail, head

		printf "pair lxy; lxy = (%fin, %fin);\n", lx, ly
		printf "pair llxy; llxy = lxy reflectedabout(%s, %s);\n", tail, head
		print "path lpp; lpp = llxy -- lxy;"

		# XXX: i dislike this indeed! but at least the extension is constant
		# TODO: instead of extending like this, i'd rather use the line's direction,
		# and find a lxy--whatever point on e
		print "pair ja; ja = lpp projectby +2in;"
		print "pair jb; jb = lpp projectby -2in;"
		print "pp = ja -- jb;"

#		printf "draw pp withpen pencircle scaled 0.5bp withcolor green;\n"
#		printf "draw llxy -- lxy withpen pencircle scaled 1bp withcolor red;\n"
	}

	printf "fsmgvedge(%s, %s)(pp, \"%s\") e;\n", tail, head, label;
}
}

/^stop$/ {
	# XXX: ought to be per graph; stop is per file
	print "endfig;"
}

END {
	print "end."
}