Make print-results.awk compatible with mawk

#!/usr/bin/awk -f

# Awk script for parsing test protocols that is based on TAP
#
# The implementation uses four arrays:
#   planned - array of test ids that have been declared with a "x..y" plan
#   passed - array of test ids that passed
#   failed - array of test ids that failed
#   skipped - array of test ids that were skipped
#
# These output specifiers can be added when calling the program
#   verbose - Print test and description on separate lines
#   color - Add terminal color escape codes
#   info - Print additional information to output, such as skipped tests

# Functions to add terminal color escape codes surrounding strings
# The global variable "color" needs to have a truthy value
function tcol(str, c)	{ return color ? c str COL_NORMAL : str }
function okcol(str)	{ return tcol(str, COL_OK) }
function warncol(str)	{ return tcol(str, COL_WARN) }
function failcol(str)	{ return tcol(str, COL_FAIL) }
function noticecol(str)	{ return tcol(str, COL_NOTICE) }

# Directive parsing
function is_skip(directive) { return directive ~ /^[Ss][Kk][Ii][Pp]/ }
function is_todo(directive) { return directive ~ /^[Tt][Oo][Dd][Oo]/ }

function is_defined(id) {
	return id in passed || id in failed || id in skipped
}

function print_test(label, id, text, is_first) {
	if (is_first) printf "%s", label

	if (verbose) printf "\n\t%d %s", id, text[id]
	else printf "%s %d", (is_first ? "" : ","), id

	system("") # Flush output buffer
}

function arrlen(arr,   elem, n) {
	n = 0
	for (elem in arr) n++
	return n
}

function print_table(msg, table, text, first) {
	if (arrlen(table)) {
		first = 1
		for (id in table) {
			print_test(msg, id, text, first)
			first = 0
		}
		printf "\n"
	}
}

BEGIN {
	# Id counter, if not in feed
	counter = 1

	# Highlight colors, when enabled
	COL_NORMAL="\033[1;000m"	# Default
	COL_OK="\033[0;32m"		# Green
	COL_NOTICE="\033[1;34m"		# Blue
	COL_WARN="\033[1;33m"		# Yellow
	COL_FAIL="\033[1;31m"		# Red

	# Only use tab as field separator
	FS="\t"
}

# Plan record, $2=from, $3=to, $4=directive (there can be more than one)
$1 == "plan" {
	if ($2 == 1 && $3 == 0 && is_skip($4)) {
		# Special case for skipping the whole test
		skip_all = 1
	} else for (i = $2; i <= $3; i++) {
		# Declare new test
		planned[i] = 1

		if ($4 ~ /^[Ss][Kk][Ii][Pp]/) {
			# Skip range of tests
			if (is_defined(i)) {
				# TODO: Test already defined, signal warning
			} else {
				skipped[i] = 1
				message[i] = $5
				explain[i] = $5
			}
		}
	}
}

# Test line, $1=status, $2=id, $3=description, $4=directive, $5=explanation
$1 ~ /(not )?ok/ {
	id = $2

	# Make sure the test has an id
	if (!id) id = counter++
	else counter = id + 1

	# Have we seen this test result already somewhere?
	if (is_defined(id)) {
		repeat[id]++
		repeat_text[id] = id " repeated " repeat[id] " times"
	}

	# The test has passed
	message[id] = $3
	explain[id] = $5
	fail = $1 ~ /^not/

	if (is_todo($4)) {
		# TODO directive. These tests count as passed no matter what
		passed[id] = 1
		# If it was successful, then notify user at the end
		if (!fail) done[id] = 1
	} else if (is_skip($4)) {
		# SKIP directive
		skipped[id] = 1
		skip_msg[id] = message[id] "-- " explain[id]
	} else if (fail) {
		# Test failed
		failed[id] = 1
		print_test(failcol("FAILED"), id, message, arrlen(failed) == 1)
	} else {
		passed[id] = 1
	}
}

/Bail out!/ {
	# Bail out
	bail_out = 1
	bail_out_message = $0
	sub(/^[^!]*![ 	]*/, "", bail_out_message)
	exit
}

/^#/ {
	# Diagnostic
}

END {
	# Mark planned tests that failed to run as failed
	for (id in planned) {
		if (id in passed || id in skipped || id in failed) continue;
		failed[id] = 1
		print_test(failcol("FAILED"), id, message, arrlen(failed) == 1)
	}

	# Check for tests that aren't part of any plan
	for (id in passed) {
		if (!(id in planned)) { noplan[i] = 1 }
	}

	# Print test summary (finish failed tests)
	no_plan = arrlen(planned) == 0 && !skip_all
	nfail = arrlen(failed)
	npass = arrlen(passed) + arrlen(skipped)
	n = npass + nfail
	if (n > 0) {
		if (nfail) {
			printf (verbose ? "\n\t" : " ")
		} else {
			if (arrlen(done) || arrlen(repeat) || arrlen(noplan)) {
				printf warncol("OK")
			} else if (arrlen(skipped)) {
				printf noticecol("OK")
			} else {
				printf okcol("OK")
			}
			printf " "
		}
		printf "(%d/%s)\n", npass, (no_plan ? failcol("???") : n "")
	} else if (skip_all) {
		print noticecol("OK") " (" noticecol("skip") ")"
	} else {
		print warncol("NO TESTS")
	}

	# Print additional information (possibly one line each)
	if (info) {
		print_table(noticecol("Skipped"), skipped, skip_msg);
		print_table(warncol("Repeated"), repeat, repeat_text);
		print_table(warncol("Passed"), done, explain);
		print_table(warncol("Not planned"), noplan, explain);
	}

	# Did the test intentionally exit early?
	if (bail_out) {
		print failcol("BAILED") " " bail_out_message
	}

	# Report the number of failed tests in the exit code
	exit nfail
}

#!/usr/bin/awk -f

# Awk script for parsing test protocols that is based on TAP
#
# The implementation uses four arrays:
#   planned - array of test ids that have been declared with a "x..y" plan
#   passed - array of test ids that passed
#   failed - array of test ids that failed
#   skipped - array of test ids that were skipped
#
# These output specifiers can be added when calling the program
#   verbose - Print test and description on separate lines
#   color - Add terminal color escape codes
#   info - Print additional information to output, such as skipped tests

# Functions to add terminal color escape codes surrounding strings
# The global variable "color" needs to have a truthy value
function tcol(str, c)	{ return color ? c str COL_NORMAL : str }
function okcol(str)	{ return tcol(str, COL_OK) }
function warncol(str)	{ return tcol(str, COL_WARN) }
function failcol(str)	{ return tcol(str, COL_FAIL) }
function noticecol(str)	{ return tcol(str, COL_NOTICE) }

# Directive parsing
function is_skip(directive) { return directive ~ /^[Ss][Kk][Ii][Pp]/ }
function is_todo(directive) { return directive ~ /^[Tt][Oo][Dd][Oo]/ }

function is_defined(id) {
	return id in passed || id in failed || id in skipped
}

function print_test(label, id, text, is_first) {
	if (is_first) printf "%s", label

	if (verbose) printf "\n\t%d %s", id, text[id]
	else printf "%s %d", (is_first ? "" : ","), id

	system("") # Flush output buffer
}

function print_table(msg, table, text, first) {
	if (length(table)) {
		first = 1
		for (id in table) {
			print_test(msg, id, text, first)
			first = 0
		}
		printf "\n"
	}
}

BEGIN {
	# Id counter, if not in feed
	counter = 1

	# Highlight colors, when enabled
	COL_NORMAL="\033[1;000m"	# Default
	COL_OK="\033[0;32m"		# Green
	COL_NOTICE="\033[1;34m"		# Blue
	COL_WARN="\033[1;33m"		# Yellow
	COL_FAIL="\033[1;31m"		# Red

	# Only use tab as field separator
	FS="\t"
}

# Plan record, $2=from, $3=to, $4=directive (there can be more than one)
$1 == "plan" {
	if ($2 == 1 && $3 == 0 && is_skip($4)) {
		# Special case for skipping the whole test
		skip_all = 1
	} else for (i = $2; i <= $3; i++) {
		# Declare new test
		planned[i] = 1

		if ($4 ~ /^[Ss][Kk][Ii][Pp]/) {
			# Skip range of tests
			if (is_defined(i)) {
				# TODO: Test already defined, signal warning
			} else {
				skipped[i] = 1
				message[i] = $5
				explain[i] = $5
			}
		}
	}
}

# Test line, $1=status, $2=id, $3=description, $4=directive, $5=explanation
$1 ~ /(not )?ok/ {
	id = $2

	# Make sure the test has an id
	if (!id) id = counter++
	else counter = id + 1

	# Have we seen this test result already somewhere?
	if (is_defined(id)) {
		repeat[id]++
		repeat_text[id] = id " repeated " repeat[id] " times"
	}

	# The test has passed
	message[id] = $3
	explain[id] = $5
	fail = $1 ~ /^not/

	if (is_todo($4)) {
		# TODO directive. These tests count as passed no matter what
		passed[id] = 1
		# If it was successful, then notify user at the end
		if (!fail) done[id] = 1
	} else if (is_skip($4)) {
		# SKIP directive
		skipped[id] = 1
		skip_msg[id] = message[id] "-- " explain[id]
	} else if (fail) {
		# Test failed
		failed[id] = 1
		print_test(failcol("FAILED"), id, message, length(failed) == 1)
	} else {
		passed[id] = 1
	}
}

/Bail out!/ {
	# Bail out
	bail_out = 1
	bail_out_message = $0
	sub(/^[^!]*![ 	]*/, "", bail_out_message)
	exit
}

/^#/ {
	# Diagnostic
}

END {
	# Mark planned tests that failed to run as failed
	for (id in planned) {
		if (id in passed || id in skipped || id in failed) continue;
		failed[id] = 1
		print_test(failcol("FAILED"), id, message, length(failed) == 1)
	}

	# Check for tests that aren't part of any plan
	for (id in passed) {
		if (!(id in planned)) { noplan[i] = 1 }
	}

	# Print test summary (finish failed tests)
	no_plan = length(planned) == 0 && !skip_all
	nfail = length(failed)
	npass = length(passed) + length(skipped)
	n = npass + nfail
	if (n > 0) {
		if (nfail) {
			printf (verbose ? "\n\t" : " ")
		} else {
			if (length(done) || length(repeat) || length(noplan)) {
				printf warncol("OK")
			} else if (length (skipped)) {
				printf noticecol("OK")
			} else {
				printf okcol("OK")
			}
			printf " "
		}
		printf "(%d/%s)\n", npass, (no_plan ? failcol("???") : n "")
	} else if (skip_all) {
		print noticecol("OK") " (" noticecol("skip") ")"
	} else {
		print warncol("NO TESTS")
	}

	# Print additional information (possibly one line each)
	if (info) {
		print_table(noticecol("Skipped"), skipped, skip_msg);
		print_table(warncol("Repeated"), repeat, repeat_text);
		print_table(warncol("Passed"), done, explain);
		print_table(warncol("Not planned"), noplan, explain);
	}

	# Did the test intentionally exit early?
	if (bail_out) {
		print failcol("BAILED") " " bail_out_message
	}

	# Report the number of failed tests in the exit code
	exit nfail
}


Add "in numSeasons seasons" to info about shows

# Verify that every show in BBoxPrograms is also in BBoxEpisodes

# INVOCATION:
#   awk -v EPISODES_SORTED_FILE=$EPISODES_SORTED_FILE -v SEASONS_SORTED_FILE=$SEASONS_SORTED_FILE \
#       -v TEMP_FILE=$TEMP_FILE -f verifyBBoxDownloadsFrom-webscraper.awk $PROGRAMS_SORTED_FILE \
#       >>$EPISODE_INFO_FILE

# Field numbers
#    1 web-scraper-start-url    2 URL    3 Program_Title    4 Sn_Years    5 Seasons    9  Description

BEGIN {
    FS="\t"
}

/\/us\// {
    programURL = $1
    numSeasons = $4

    seaStr = ""
    if (numSeasons != "null") {
        sub (/ Seasons?/,"",numSeasons)
        numSeasons == 1 ? seaStr = " in 1 season" : seaStr = " in " numSeasons " seasons"
    }

nflds = split (programURL,fld,"/")
showType = fld[3]
showType == "show" ? spacer = "  " : spacer = " "
# Must have single quotes to handle special charaters in targets
target = sprintf ("'%s'",fld[nflds])
unquotedTarget = substr(target,2,length(target)-2)
#
cmd = "grep -c " target " " EPISODES_SORTED_FILE
if ((cmd | getline numEpisodes) > 0) {
    numEpisodes == 1 ? epiStr = " episode" : epiStr = " episodes"
    print "==> " showType spacer target " has " numEpisodes epiStr seaStr
}
close (cmd)
if (numEpisodes == 0) {
    badEpisodes += 1
    print "    " unquotedTarget >> TEMP_FILE
}
#
cmd = "grep " target " " SEASONS_SORTED_FILE
while ((cmd | getline seasonLine) > 0) {
    nfields = split (seasonLine, fields, "\t")
    seasonField = fields[nfields-4]
    episodeField = fields[nfields-2]
    if (seasonField == "") {
        print "==> " target " (blank Seasons field in " SEASONS_SORTED_FILE ")" \
            > "/dev/stderr"
        print "    " unquotedTarget " (blank Seasons field in " SEASONS_SORTED_FILE ")" \
            >> TEMP_FILE
        continue
    }
    print "          " target " " seasonField " has " episodeField

}
close (cmd)
}

END {
    if (badEpisodes > 0 ) {
        badEpisodes == 1 ? field = "URL" : field = "URLs"
        printf ("==> %2d extra /program/ %s in %s\n", badEpisodes, field, FILENAME) > "/dev/stderr"
    }
}

# Verify that every show in BBoxPrograms is also in BBoxEpisodes

# INVOCATION:
#   awk -v EPISODES_SORTED_FILE=$EPISODES_SORTED_FILE -v SEASONS_SORTED_FILE=$SEASONS_SORTED_FILE \
#       -v TEMP_FILE=$TEMP_FILE -f verifyBBoxDownloadsFrom-webscraper.awk $PROGRAMS_SORTED_FILE \
#       >>$EPISODE_INFO_FILE

# Field numbers
#    1 web-scraper-start-url    2 URL    3 Program_Title    4 Sn_Years    5 Seasons    9  Description

BEGIN {
    FS="\t"
}

/\/us\// {
    programURL = $1
    nflds = split (programURL,fld,"/")
    showType = fld[3]
    showType == "show" ? spacer = "  " : spacer = " "
    # Must have single quotes to handle special charaters in targets
    target = sprintf ("'%s'",fld[nflds])
    unquotedTarget = substr(target,2,length(target)-2)
    #
    cmd = "grep -c " target " " EPISODES_SORTED_FILE
    if ((cmd | getline NumEpisodes) > 0) {
        NumEpisodes == 1 ? epiStr = " episode" : epiStr = " episodes"
        print "==> " showType spacer target " has " NumEpisodes epiStr
    }
    close (cmd)
    if (NumEpisodes == 0) {
        badEpisodes += 1
        print "    " unquotedTarget >> TEMP_FILE
    }
    #
    cmd = "grep " target " " SEASONS_SORTED_FILE
    while ((cmd | getline seasonLine) > 0) {
        nfields = split (seasonLine, fields, "\t")
        seasonField = fields[nfields-4]
        episodeField = fields[nfields-2]
        if (seasonField == "") {
            print "==> " target " (blank Seasons field in " SEASONS_SORTED_FILE ")" \
                > "/dev/stderr"
            print "    " unquotedTarget " (blank Seasons field in " SEASONS_SORTED_FILE ")" \
                >> TEMP_FILE
            continue
        }
        print "          " target " " seasonField " has " episodeField

    }
    close (cmd)
}

END {
    if (badEpisodes > 0 ) {
        badEpisodes == 1 ? field = "URL" : field = "URLs"
        printf ("==> %2d extra /program/ %s in %s\n", badEpisodes, field, FILENAME) > "/dev/stderr"
    }
}


build.awk: update

build.awk: update

#!/usr/bin/gawk -f

# Not all 4.x versions of gawk can handle @include without ".awk" extension
# But the build.awk script and the single build should support gawk 4.0+.
@include "include/Commons.awk"
@include "include/Utils.awk"
@include "include/Languages.awk"
@include "metainfo.awk"

function readFrom(file,    line, text) {
    if (!file) file = "/dev/stdin"
    text = NULLSTR
    while (getline line < file)
        text = (text ? text "\n" : NULLSTR) line
    return text
}

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

function getOutput(command,    content, line) {
    content = NULLSTR
    while ((command |& getline line) > 0)
        content = (content ? content "\n" : NULLSTR) line
    return content
}

function init() {
    BuildPath            = "build/"
    Trans                = BuildPath Command
    TransAwk             = Trans ".awk"

    ManPath              = "man/"
    Man                  = ManPath Command ".1"
    ManSource            = Man ".md"
    ManTemplate          = Man ".template.html"
    ManHtml              = Man ".html"

    PagesPath            = "gh-pages/"
    BadgeDownload        = PagesPath "images/badge-download"
    BadgeRelease         = PagesPath "images/badge-release"
    HomebrewFormula      = PagesPath "translate-shell.rb"
    Index                = PagesPath "index.md"

    ReadmePath           = "./"
    ReadmeTemplate       = ReadmePath "README.template.md"
    Readme               = ReadmePath "README.md"

    WikiPath             = "wiki/"
    WikiHome             = WikiPath "Home.md"
    WikiLanguages        = WikiPath "Languages.md"
    WikiLanguagesHtml    = WikiLanguages ".html"

    RegistryPath         = "registry/"
    MainRegistryTemplate = RegistryPath "index.template.trans"
    MainRegistry         = RegistryPath "index.trans"
}

function man() {
    if (fileExists(ManTemplate))
        system("pandoc -s -t html --toc --toc-depth 1 --template " ManTemplate " " ManSource " -o " ManHtml)
    return system("pandoc -s -t man " ManSource " -o " Man)
}

function readme(    code, col, cols, content, group, i, j, language, r, rows, text) {
    text = readFrom(ReadmeTemplate)

    content = getOutput("gawk -f translate.awk -- -no-ansi -h")
    gsub(/\$usage\$/, content, text)

    initBiDi(); initLocale()
    rows = int(length(Locale) / 3) + 1
    cols[0][0] = cols[1][0] = cols[2][0] = NULLSTR
    i = 0
    saveSortedIn = PROCINFO["sorted_in"]
    PROCINFO["sorted_in"] = "compName"
    for (code in Locale) {
        col = int(i / rows)
        append(cols[col], code)
        i++
    }
    PROCINFO["sorted_in"] = saveSortedIn
    r = "| Language | Code | Language | Code | Language | Code |" RS \
        "| :------: | :--: | :------: | :--: | :------: | :--: |" RS
    for (i = 0; i < rows; i++) {
        r = r "| "
        for (j = 0; j < 3; j++)
            if (cols[j][i]) {
                split(getName(cols[j][i]), group, " ")
                language = group[1]
                r = r "**[" getName(cols[j][i]) "](" "http://en.wikipedia.org/wiki/" language "_language" ")**
 **" getEndonym(cols[j][i]) "** | **`" cols[j][i] "`** | "
            }
        r = r RS
    }
    gsub(/\$code-list\$/, r, text)

    content = readFrom(WikiHome)
    gsub(/\$wiki-home\$/, content, text)

    writeTo(text, Readme)
    return 0
}

function wiki(    code, group, iso, language, saveSortedIn) {
    initBiDi(); initLocale()

    print "***" length(Locale) "*** *languages in total. "              \
        "Generated from the source code of Translate Shell " Version ".*\n" > WikiLanguages
    print "| Code | Name | Family | [Writing system](https://github.com/soimort/translate-shell/wiki/Writing-Systems-and-Fonts) | Is [RTL](http://en.wikipedia.org/wiki/Right-to-left)? | Has dictionary? |" > WikiLanguages
    print "| :--: | ---: | -----: | :------------: | :---------------------------------------------------: | :-------------: |" > WikiLanguages
    saveSortedIn = PROCINFO["sorted_in"]
    PROCINFO["sorted_in"] = "@ind_num_asc"
    for (code in Locale) {
        split(getISO(code), group, "-")
        iso = group[1]
        split(getName(code), group, " ")
        language = group[1]
        print sprintf("| **`%s`**
 [`%s`](%s) | **[%s](%s)**
 **%s** | %s | `%s` | %s | %s |",
                      getCode(code), iso, "http://www.ethnologue.com/language/" iso,
                      getName(code), "http://en.wikipedia.org/wiki/" language "_language", getEndonym(code),
                      getFamily(code), getScript(code),
                      isRTL(code) ? "✓" : NULLSTR,
                      hasDictionary(code) ? "✓" : NULLSTR) > WikiLanguages
    }
    PROCINFO["sorted_in"] = saveSortedIn

    return system("pandoc -s -t html " WikiLanguages " -o " WikiLanguagesHtml)
}

function doc() {
    man()
    readme()
    wiki()
    return 0
}

function build(target, type,    group, inline, line, temp) {
    # Default target: bash
    if (!target) target = "bash"

    ("mkdir -p " parameterize(BuildPath)) | getline

    if (target == "bash" || target == "zsh") {

        print "#!/usr/bin/env " target > Trans
        print > Trans

        print "read -r -d '' TRANS_PROGRAM << 'EOF'" > Trans
        if (fileExists(EntryPoint))
            while (getline line < EntryPoint) {
                match(line, /^[[:space:]]*@include[[:space:]]*"(.*)"$/, group)
                if (RSTART) {
                    # Include file
                    if (fileExists(group[1] ".awk"))
                        while (getline inline < (group[1] ".awk"))
                            if (inline = squeeze(inline))
                                print inline > Trans # effective LOC
                } else {
                    if (line && line !~ /^[[:space:]]*#!/) {
                        # Remove preceding spaces
                        gsub(/^[[:space:]]+/, "", line)
                        print line > Trans
                    }
                }
            }
        print "EOF" > Trans

        print "read -r -d '' TRANS_MANPAGE << 'EOF'" > Trans
        if (fileExists(Man))
            while (getline line < Man)
                print line > Trans
        print "EOF" > Trans
        print "export TRANS_MANPAGE" > Trans

        if (type == "release")
            print "export TRANS_BUILD=release" temp > Trans
        else {
            temp = getGitHead()
            if (temp)
                print "export TRANS_BUILD=git:" temp > Trans
        }

        print "export TRANS_ABSPATH=$(cd \"$(dirname \"$0\")\" && pwd)/$(basename \"$0\")" > Trans

        print "export COLUMNS=$(tput cols)" > Trans

        print "gawk -f <(echo -E \"$TRANS_PROGRAM\") - \"$@\"" > Trans

        ("chmod +x " parameterize(Trans)) | getline
        return 0

    } else if (target == "awk" || target == "gawk") {

        "uname -s" | getline temp

        if (fileExists(EntryPoint))
            while (getline line < EntryPoint) {
                match(line, /^[[:space:]]*@include[[:space:]]*"(.*)"$/, group)
                if (RSTART) {
                    # Include file
                    if (fileExists(group[1] ".awk"))
                        while (getline inline < (group[1] ".awk"))
                            if (inline = squeeze(inline, 1))
                                print inline > TransAwk
                } else {
                    if (temp == "Darwin" && line == "#!/usr/bin/gawk -f")
                        # OS X: gawk not in /usr/bin, use a better shebang
                        print "#!/usr/bin/env gawk -f" > TransAwk
                    else
                        print line > TransAwk
                }
            }

        ("chmod +x " parameterize(TransAwk)) | getline
        return 0

    } else {

        w("[FAILED] Unknown target: " ansi("underline", target))
        w("         Supported targets: "                                \
          ansi("underline", "bash") ", "                                \
          ansi("underline", "zsh") ", "                                 \
          ansi("underline", "gawk"))
        return 1

    }
}

function clean() {
    ("rm -f " BuildPath Command "*") | getline
    return 0
}

function release(    content, group, sha1, size, temp, text) {
    ("sha1sum " Trans) | getline temp
    split(temp, group)
    sha1 = group[1]

    d("Updating registry ...")
    # Update registry
    text = readFrom(MainRegistryTemplate)
    gsub(/\$Version\$/, Version, text)
    writeTo(text, MainRegistry)

    d("Updating gh-pages/images ...")
    # Update gh-pages/images/badge-release
    text = readFrom(BadgeRelease ".temp")
    gsub(/\$Version\$/, Version, text)
    writeTo(text, BadgeRelease)
    system("save-to-png " BadgeRelease)
    # Update gh-pages/images/badge-download
    ("wc -c " Trans) | getline temp
    split(temp, group)
    size = int(group[1] / 1000)
    text = readFrom(BadgeDownload ".temp")
    gsub(/\$Size\$/, size, text)
    writeTo(text, BadgeDownload)
    system("save-to-png " BadgeDownload)

    d("Updating gh-pages/translate-shell.rb ...")
    # Update gh-pages/translate-shell.rb
    text = readFrom(HomebrewFormula ".temp")
    gsub(/\$sha1\$/, sha1, text)
    gsub(/\$Version\$/, Version, text)
    writeTo(text, HomebrewFormula)

    d("Updating gh-pages/index.md ...")
    # Update gh-pages/index.md
    content = readFrom(Readme)
    text = readFrom(Index ".temp")
    gsub(/\$sha1\$/, sha1, text)
    gsub(/\$Version\$/, Version, text)
    gsub(/\$readme\$/, content, text)
    writeTo(text, Index)

    return 0
}

function test() {
    return 0
}

BEGIN {
    init()

    pos = 0
    while (ARGV[++pos]) {
        # -target TARGET
        match(ARGV[pos], /^--?target(=(.*)?)?$/, group)
        if (RSTART) {
            target = tolower(group[2] ? group[2] : ARGV[++pos])
            continue
        }

        # -type TYPE
        match(ARGV[pos], /^--?type(=(.*)?)?$/, group)
        if (RSTART) {
            type = tolower(group[2] ? group[2] : ARGV[++pos])
            continue
        }

        # TASK
        match(ARGV[pos], /^[^\-]/, group)
        if (RSTART) {
            append(tasks, ARGV[pos])
            continue
        }
    }

    # Default task: build
    if (!anything(tasks)) tasks[0] = "build"

    for (i = 0; i < length(tasks); i++) {
        task = tasks[i]
        status = 0
        switch (task) {

        case "man":
            status = man()
            break

        case "readme":
            status = readme()
            break

        case "wiki":
            status = wiki()
            break

        case "doc":
            status = doc()
            break

        case "build":
            status = build(target, type)
            break

        case "clean":
            status = clean()
            break

        case "release":
            status = release()
            break

        case "test":
            status = test()
            break

        default: # unknown task
            status = -1
        }

        if (status == 0) {
            d("[OK] Task " ansi("bold", task) " completed.")
        } else if (status < 0) {
            w("[FAILED] Unknown task: " ansi("bold", task))
            exit 1
        } else {
            w("[FAILED] Task " ansi("bold", task) " failed.")
            exit 1
        }
    }
}

#!/usr/bin/gawk -f

# Not all 4.x versions of gawk can handle @include without ".awk" extension
# But the build.awk script and the single build should support gawk 4.0+.
@include "include/Commons.awk"
@include "include/Utils.awk"
@include "include/Languages.awk"
@include "metainfo.awk"

function readFrom(file,    line, text) {
    if (!file) file = "/dev/stdin"
    text = NULLSTR
    while (getline line < file)
        text = (text ? text "\n" : NULLSTR) line
    return text
}

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

function getOutput(command,    content, line) {
    content = NULLSTR
    while ((command |& getline line) > 0)
        content = (content ? content "\n" : NULLSTR) line
    return content
}

function init() {
    BuildPath            = "build/"
    Trans                = BuildPath Command
    TransAwk             = Trans ".awk"

    ManPath              = "man/"
    Man                  = ManPath Command ".1"
    ManSource            = Man ".md"
    ManTemplate          = Man ".template.html"
    ManHtml              = Man ".html"

    PagesPath            = "gh-pages/"
    BadgeDownload        = PagesPath "images/badge-download"
    BadgeRelease         = PagesPath "images/badge-release"
    HomebrewFormula      = PagesPath "translate-shell.rb"
    Index                = PagesPath "index.md"

    ReadmePath           = "./"
    ReadmeTemplate       = ReadmePath "README.template.md"
    Readme               = ReadmePath "README.md"

    WikiPath             = "wiki/"
    WikiHome             = WikiPath "Home.md"
    WikiLanguages        = WikiPath "Languages.md"
    WikiLanguagesHtml    = WikiLanguages ".html"

    RegistryPath         = "registry/"
    MainRegistryTemplate = RegistryPath "index.template.trans"
    MainRegistry         = RegistryPath "index.trans"
}

function man() {
    if (fileExists(ManTemplate))
        system("pandoc -s -t html --toc --toc-depth 1 --template " ManTemplate " " ManSource " -o " ManHtml)
    return system("pandoc -s -t man " ManSource " -o " Man)
}

function readme(    code, col, cols, content, group, i, j, language, r, rows, text) {
    text = readFrom(ReadmeTemplate)

    content = getOutput("gawk -f translate.awk -- -no-ansi -h")
    gsub(/\$usage\$/, content, text)

    initBiDi(); initLocale()
    rows = int(length(Locale) / 3) + 1
    cols[0][0] = cols[1][0] = cols[2][0] = NULLSTR
    i = 0
    saveSortedIn = PROCINFO["sorted_in"]
    PROCINFO["sorted_in"] = "compName"
    for (code in Locale) {
        col = int(i / rows)
        append(cols[col], code)
        i++
    }
    PROCINFO["sorted_in"] = saveSortedIn
    r = "| Language | Code | Language | Code | Language | Code |" RS \
        "| :------: | :--: | :------: | :--: | :------: | :--: |" RS
    for (i = 0; i < rows; i++) {
        r = r "| "
        for (j = 0; j < 3; j++)
            if (cols[j][i]) {
                split(getName(cols[j][i]), group, " ")
                language = group[1]
                r = r "**[" getName(cols[j][i]) "](" "http://en.wikipedia.org/wiki/" language "_language" ")**
 **" getEndonym(cols[j][i]) "** | **`" cols[j][i] "`** | "
            }
        r = r RS
    }
    gsub(/\$code-list\$/, r, text)

    content = readFrom(WikiHome)
    gsub(/\$wiki-home\$/, content, text)

    writeTo(text, Readme)
    return 0
}

function wiki(    code, group, iso, language, saveSortedIn) {
    initBiDi(); initLocale()

    print "***" length(Locale) "*** *languages in total. "              \
        "Generated from the source code of Translate Shell " Version ".*\n" > WikiLanguages
    print "| Code | Name | Family | [Writing system](https://github.com/soimort/translate-shell/wiki/Writing-Systems-and-Fonts) | Is [RTL](http://en.wikipedia.org/wiki/Right-to-left)? | Has dictionary? |" > WikiLanguages
    print "| :--: | ---: | -----: | :------------: | :---------------------------------------------------: | :-------------: |" > WikiLanguages
    saveSortedIn = PROCINFO["sorted_in"]
    PROCINFO["sorted_in"] = "@ind_num_asc"
    for (code in Locale) {
        split(getISO(code), group, "-")
        iso = group[1]
        split(getName(code), group, " ")
        language = group[1]
        print sprintf("| **`%s`**
 [`%s`](%s) | **[%s](%s)**
 **%s** | %s | `%s` | %s | %s |",
                      getCode(code), iso, "http://www.ethnologue.com/language/" iso,
                      getName(code), "http://en.wikipedia.org/wiki/" language "_language", getEndonym(code),
                      getFamily(code), getScript(code),
                      isRTL(code) ? "✓" : NULLSTR,
                      hasDictionary(code) ? "✓" : NULLSTR) > WikiLanguages
    }
    PROCINFO["sorted_in"] = saveSortedIn

    return system("pandoc -s -t html " WikiLanguages " -o " WikiLanguagesHtml)
}

function doc() {
    man()
    readme()
    wiki()
    return 0
}

function build(target, type,    group, inline, line, temp) {
    # Default target: bash
    if (!target) target = "bash"

    ("mkdir -p " parameterize(BuildPath)) | getline

    if (target == "bash" || target == "zsh") {

        print "#!/usr/bin/env " target > Trans
        print > Trans

        print "read -r -d '' TRANS_PROGRAM << 'EOF'" > Trans
        if (fileExists(EntryPoint))
            while (getline line < EntryPoint) {
                match(line, /^[[:space:]]*@include[[:space:]]*"(.*)"$/, group)
                if (RSTART) {
                    # Include file
                    if (fileExists(group[1] ".awk"))
                        while (getline inline < (group[1] ".awk"))
                            if (inline = squeeze(inline))
                                print inline > Trans # effective LOC
                } else {
                    if (line && line !~ /^[[:space:]]*#!/) {
                        # Remove preceding spaces
                        gsub(/^[[:space:]]+/, "", line)
                        print line > Trans
                    }
                }
            }
        print "EOF" > Trans

        print "read -r -d '' TRANS_MANPAGE << 'EOF'" > Trans
        if (fileExists(Man))
            while (getline line < Man)
                print line > Trans
        print "EOF" > Trans
        print "export TRANS_MANPAGE" > Trans

        if (type == "release")
            print "export TRANS_BUILD=release" temp > Trans
        else {
            temp = getGitHead()
            if (temp)
                print "export TRANS_BUILD=git:" temp > Trans
        }

        print "export TRANS_ABSPATH=$(cd \"$(dirname \"$0\")\" && pwd)/$(basename \"$0\")" > Trans

        print "export COLUMNS=$(tput cols)" > Trans

        print "gawk -f <(echo -E \"$TRANS_PROGRAM\") - \"$@\"" > Trans

        ("chmod +x " parameterize(Trans)) | getline
        return 0

    } else if (target == "awk" || target == "gawk") {

        "uname -s" | getline temp

        if (fileExists(EntryPoint))
            while (getline line < EntryPoint) {
                match(line, /^[[:space:]]*@include[[:space:]]*"(.*)"$/, group)
                if (RSTART) {
                    # Include file
                    if (fileExists(group[1] ".awk"))
                        while (getline inline < (group[1] ".awk"))
                            if (inline = squeeze(inline, 1))
                                print inline > TransAwk
                } else {
                    if (temp == "Darwin" && line == "#!/usr/bin/gawk -f")
                        # OS X: gawk not in /usr/bin, use a better shebang
                        print "#!/usr/bin/env gawk -f" > TransAwk
                    else
                        print line > TransAwk
                }
            }

        ("chmod +x " parameterize(TransAwk)) | getline
        return 0

    } else {

        w("[FAILED] Unknown target: " ansi("underline", target))
        w("         Supported targets: "                                \
          ansi("underline", "bash") ", "                                \
          ansi("underline", "zsh") ", "                                 \
          ansi("underline", "gawk"))
        return 1

    }
}

function clean() {
    ("rm -f " BuildPath Command "*") | getline
    return 0
}

function release() {
    ("sha1sum " Trans) | getline temp
    split(temp, group)
    sha1 = group[1]

    d("Updating registry ...")
    # Update registry
    text = readFrom(MainRegistryTemplate)
    gsub(/\$Version\$/, Version, text)
    writeTo(text, MainRegistry)

    d("Updating gh-pages/images ...")
    # Update gh-pages/images/badge-release
    text = readFrom(BadgeRelease ".temp")
    gsub(/\$Version\$/, Version, text)
    writeTo(text, BadgeRelease)
    system("save-to-png " BadgeRelease)
    # Update gh-pages/images/badge-download
    ("wc -c " Trans) | getline temp
    split(temp, group)
    size = int(group[1] / 1000)
    text = readFrom(BadgeDownload ".temp")
    gsub(/\$Size\$/, size, text)
    writeTo(text, BadgeDownload)
    system("save-to-png " BadgeDownload)

    d("Updating gh-pages/translate-shell.rb ...")
    # Update gh-pages/translate-shell.rb
    text = readFrom(HomebrewFormula ".temp")
    gsub(/\$sha1\$/, sha1, text)
    gsub(/\$Version\$/, Version, text)
    writeTo(text, HomebrewFormula)

    d("Updating gh-pages/index.md ...")
    # Update gh-pages/index.md
    content = readFrom(Readme)
    text = readFrom(Index ".temp")
    gsub(/\$sha1\$/, sha1, text)
    gsub(/\$Version\$/, Version, text)
    gsub(/\$readme\$/, content, text)
    writeTo(text, Index)

    return 0
}

function test() {
    return 0
}

BEGIN {
    init()

    pos = 0
    while (ARGV[++pos]) {
        # -target TARGET
        match(ARGV[pos], /^--?target(=(.*)?)?$/, group)
        if (RSTART) {
            target = tolower(group[2] ? group[2] : ARGV[++pos])
            continue
        }

        # -type TYPE
        match(ARGV[pos], /^--?type(=(.*)?)?$/, group)
        if (RSTART) {
            type = tolower(group[2] ? group[2] : ARGV[++pos])
            continue
        }

        # TASK
        match(ARGV[pos], /^[^\-]/, group)
        if (RSTART) {
            append(tasks, ARGV[pos])
            continue
        }
    }

    # Default task: build
    if (!anything(tasks)) tasks[0] = "build"

    for (i = 0; i < length(tasks); i++) {
        task = tasks[i]
        status = 0
        switch (task) {

        case "man":
            status = man()
            break

        case "readme":
            status = readme()
            break

        case "wiki":
            status = wiki()
            break

        case "doc":
            status = doc()
            break

        case "build":
            status = build(target, type)
            break

        case "clean":
            status = clean()
            break

        case "release":
            status = release()
            break

        case "test":
            status = test()
            break

        default: # unknown task
            status = -1
        }

        if (status == 0) {
            d("[OK] Task " ansi("bold", task) " completed.")
        } else if (status < 0) {
            w("[FAILED] Unknown task: " ansi("bold", task))
            exit 1
        } else {
            w("[FAILED] Task " ansi("bold", task) " failed.")
            exit 1
        }
    }
}


taking care of two hads

taking care of two hads

#! /bin/awk -f

#check back to back double occurrence of a word

BEGIN {
}

#body
FILENAME != prevfile {
  NR = 1
  prevfile = FILENAME
}
NF > 0{
  if ($1 == lastword && $1 != "had")
     printf "double %s, file %s, line %d\n", $1, FILENAME, NR
  for (i=2; i <= NF; i++)
      if ($i == $(i-1) && $i != "had")
         printf "double %s, file %s, line %d\n", $i, FILENAME, NR
  if (NF > 0){
     lastword = $NF
  }
}
END {
}

#! /bin/awk -f

#check back to back double occurrence of a word

BEGIN {
}

#body
FILENAME != prevfile {
  NR = 1
  prevfile = FILENAME
}
NF > 0{
  if ($1 == lastword)
     printf "double %s, file %s, line %d\n", $1, FILENAME, NR
  for (i=2; i <= NF; i++)
      if ($i == $(i-1))
         printf "double %s, file %s, line %d\n", $i, FILENAME, NR
  if (NF > 0){
     lastword = $NF
  }
}
END {
}


Make season number match that in the URL

Created shortSeasonURL

# Produce a raw data spreadsheet from URLs found in $SHOW_URLS
#
# Note that IN_CANADA affects processing.
#
# INVOCATION:
#    while read -r line; do
#        curl -sS "$line" |
#            awk -v ERRORS=$ERRORS -v RAW_TITLES=$RAW_TITLES v LONG_SPREADSHEET=$LONG_SPREADSHEET \
#            -v EPISODE_URLS=$EPISODE_URLS -f getAcornFrom-showPages.awk >>$UNSORTED
#        ((lastRow++))
#    done <"$SHOW_URLS"

#   Field Names
#        1 Title (link)    2 Seasons    3 Episodes    4 Duration    5 Description

# Extract the show title
/meta property="og:title/ {
    split ($0,fld,"\"")
    showTitle = fld[4]
    sub (/^Watch /,"",showTitle)
    sub (/ on Acorn TV$/,"",showTitle)
    gsub (/&/,"\\&",showTitle)
    gsub (/"/,"\"\"",showTitle)
    gsub (/'/,"'",showTitle)
    if (match (showTitle, /^The /)) {
        showTitle = substr(showTitle, 5) ", The"
    }
    print showTitle >> RAW_TITLES
    # print "==> showTitle = " showTitle > "/dev/stderr"
    next
}

# Extract the show URL
/meta property="og:url/ {
    split ($0,fld,"\"")
    showURL = fld[4]
    # print "==> showURL = " showURL > "/dev/stderr"
    # Create shorter URL by removing https://
    shortURL = showURL
    sub (/.*acorn\.tv/,"acorn.tv",shortURL)
    next
}

# Extract the number of episodes in the show
/itemprop="numberOfEpisodes"/ {
    episodeLinesFound += 1
    split ($0,fld,"\"")
    if (episodeLinesFound == 1) {
        showEpisodes = fld[4]
        if (showEpisodes == "") {
            printf ("==> Blank showEpisodes in numberOfEpisodes: %s\t%s\n", shortURL,
                    showTitle) >> ERRORS
        }
        # print "==> showEpisodes = " showEpisodes " " shortURL > "/dev/stderr"
    }
    if (episodeLinesFound != 1) {
        seasonEpisodes = seasonEpisodes "+" fld[4]
        if (seasonEpisodes == "") {
            printf ("==> Blank seasonEpisodes in numberOfEpisodes: %s\t%s\n", shortURL,
                    showTitle) >> ERRORS
        }
        # print "==> seasonEpisodes = " seasonEpisodes " " shortURL > "/dev/stderr"
    }
    next
}

# Extract the number of seasons in the show
/itemprop="numberOfSeasons"/ {
    seasonLinesFound += 1
    split ($0,fld,"\"")
    showSeasons = fld[4]
    if (showSeasons == "") {
        printf ("==> Blank showSeasons in numberOfSeasons: %s\t%s\n", shortURL, showTitle) >> ERRORS
    }
    # print "==> showSeasons = " showSeasons " " shortURL > "/dev/stderr"
    next
}

# Extract the show description
/id="franchise-description"/ {
    descriptionLinesFound += 1
    # get rid of boilerplate
    split ($0,fld,"[<>]")
    showDescription = fld[3]
    # fix sloppy input spacing
    gsub (/ \./,".",showDescription)
    gsub (/  */," ",showDescription)
    sub (/^ */,"",showDescription)
    sub (/ *$/,"",showDescription)
    # fix funky HTML characters
    gsub (/&/,"\\&",showDescription)
    gsub (/"/,"\"\"",showDescription)
    gsub (/'/,"'",showDescription)
    # fix unmatched quotes
    numQuotes = gsub(/"/,"\"",showDescription)
    if ((numQuotes % 2) == 1) {
        printf ("==> Changed unmatched quote (%d): %d\t%s\t%s\t%s\n", numQuotes, shortURL,
                showTitle) >> ERRORS
        showDescription = showDescription " \""
    }
    # print "==> showDescription = " showDescription " " shortURL > "/dev/stderr"
    next
}

# Extract the seasonNumber
# Note that Acorn "season" numbers may not correspond to actual season numbers.
# They simply refer to the number of seasons available on Acorn.
/<meta itemprop="seasonNumber" content="/ {
    split ($0,fld,"\"")
    seasonNumber = fld[4]
    # print "==> seasonNumber = " seasonNumber " " shortURL > "/dev/stderr"
    next
}

# Extract the episode URL
/ episodeURL = " episodeURL > "/dev/stderr"
    print episodeURL >> EPISODE_URLS
    shortEpisodeURL = episodeURL
    sub (/.*acorn\.tv/,"acorn.tv",shortEpisodeURL)
    split (episodeURL, part, "/")
    shortSeasonURL = "acorn.tv/" part[4] "/" part[5]
    next
}

# Extract the episode duration
/<meta itemprop="timeRequired"/ {
    durationLinesFound += 1
    split ($0,fld,"\"")
    split (fld[4],tm,/[TMS]/)
    secs = tm[3]
    mins = tm[2] + int(secs / 60)
    hrs =  int(mins / 60)
    secs %= 60; mins %= 60
    #
    showSecs += secs
    showMins += mins + int(showSecs / 60)
    showHrs += hrs + int(showMins / 60)
    showSecs %= 60; showMins %= 60
    #
    episodeDuration = sprintf ("%02d:%02d:%02d", hrs, mins, secs)
    # print "==> episodeDuration = " episodeDuration " " shortEpisodeURL > "/dev/stderr"
    if (episodeDuration == "00:00:00")
        printf ("==> Blank episode duration: %s  %s\n", shortEpisodeURL, showTitle) >> ERRORS
    next
}

# Extract the episode title
/
/ {
    split ($0,fld,"[<>]")
    episodeTitle = fld[3]
    gsub (/&/,"\\&", episodeTitle)
    gsub (/'/,"'",episodeTitle)
    # print "==> episodeTitle = " episodeTitle " " shortEpisodeURL > "/dev/stderr"
    next
}

# Extract episode number
/
.*span itemprop="episodeNumber">/ {
    split ($0,fld,"[<>]")
    episodeNumber = fld[5]
    # print "==> episodeNumber = " episodeNumber " " shortEpisodeURL > "/dev/stderr"
    #
    # Setup showType so shows group/sort properly
    # Default showType to "S"
    showType = "S"
    # If show is a Prequel, use "P" e.g. Doc Martin
    if (episodeURL ~ /\/prequel/)
        showType = "P"
    #
    # Setup episodeType so episodes group/sort properly
    # Default episodeType to "E"
    episodeType = "E"
    # Default bonus and christmas specials episodeType to "X"
    if (episodeURL ~ /\/bonus|christmas[-]?special/)
        episodeType = "X"
    # If episode is a Trailer, set episodeType to "T" - even though not all these have episodes
    if (episodeURL ~ /_cs\//)
        episodeType = "T"
    #
    # cryptoftears, newworlds & newtonslaw bonus seasonNumber should be 1
    if (episodeURL ~ \
        /\/cryptoftears\/bonus\/|\/newworlds\/bonus\/|\/newtonslaw\/bonus\// \
        && seasonNumber != 1) {
            printf ("==> Changed S%02d to S01: %s\n", seasonNumber, shortSeasonURL) >> ERRORS
        seasonNumber = 1
    }
    # Plain christmasspecial, seriesfinale don't increment seasonNumber
    if (episodeURL ~ /\/christmasspecial\/|\/seriesfinale\//) {
        printf ("==> Changed S%02d to S%02d: %s\n", seasonNumber, seasonNumber-1,
                shortSeasonURL) >> ERRORS
        seasonNumber -= 1
    }
    #
    # The season number should match that in the URL
    # Birds of a Feather, Doc Martin, Murdoch, Poirot, Rebus, Vera, and others have problems
    if (episodeURL ~ \
           /\/series[0-9]{1,2}\/|[0-9]{1,2}bonus\/|\/murdoch\/season[0-9]{1,2}/) {
        split (episodeURL, part, "/")
        URLseasonNumber = part[5]
        sub (/bonus/,"",URLseasonNumber)
        # Foyle's War has sets: foyleswar/series9set8bonus/
        sub (/set[0-9]/,"",URLseasonNumber)
        # Midsomer Murders has two parts: midsomermurders/series19parttwo/
        sub (/parttwo/,"",URLseasonNumber)
        sub (/christmasspecial/,"",URLseasonNumber)
        sub (/[[:alpha:]]*/,"",URLseasonNumber)
        if (URLseasonNumber != seasonNumber) {
            printf ("==> Changed S%02d to S%02d: %s\n", seasonNumber, URLseasonNumber,
                    shortSeasonURL) >> ERRORS
            seasonNumber = URLseasonNumber
        }
    }
    #
    # Wrap up this episode
    # =HYPERLINK("https://acorn.tv/1900island/series1/week-one";"1900 Island, S01E01, Week One")
    episodeLink = sprintf ("=HYPERLINK(\"%s\";\"%s, %s%02d%s%02d, %s\"\)", episodeURL, showTitle,
                    showType, seasonNumber, episodeType, episodeNumber, episodeTitle)
    # Print "episode" line
    # =HYPERLINK("https://acorn.tv/1900island/series1/week-one";"1900 Island, S01E01, Week One") \
    # \t\t\t 00:59:17 \t As they arrive
        printf ("%s\t\t\t%s\t%s\n", episodeLink, episodeDuration, episodeDescription) >> LONG_SPREADSHEET
    # Make sure there is no carryover
    episodeURL = ""
    shortEpisodeURL = ""
    shortSeasonURL = ""
    episodeType = ""
    episodeTitle = ""
    episodeNumber = ""
    episodeDuration = ""
    next
}

# Wrap up this show
// {
    if (episodeLinesFound == 0) {
        printf ("==> No numberOfEpisodes: %s\t%s\n", shortURL, showTitle) >> ERRORS
    }
    if (seasonLinesFound == 0) {
        printf ("==> No numberOfSeasons: %s\t%s\n", shortURL, showTitle) >> ERRORS
    }
    if (descriptionLinesFound == 0) {
        printf ("==> No franchise-description: %s\t%s\n", shortURL, showTitle) >> ERRORS
    }
    if (durationLinesFound == 0) {
        printf ("==> No durations: %s\t%s\n", shortURL, showTitle) >> ERRORS
    }
    showLink = "=HYPERLINK(\"" showURL "\";\"" showTitle "\")"
    showDuration = sprintf ("%02d:%02d:%02d", showHrs, showMins, showSecs)
    # Title	Seasons	Episodes	Duration	Description
    printf ("%s\t%s\t=%s\t%s\t%s\n", showLink, showSeasons, seasonEpisodes, showDuration, showDescription)
    # printf ("%s\t%s\t=%s\t%s\t%s\n", showLink, showSeasons, showEpisodes, showDuration, showDescription)
    # Make sure there is no carryover
    showTitle = ""
    showURL = ""
    shortURL = ""
    showLink = ""
    showSeasons = ""
    showEpisodes = ""
    seasonEpisodes = ""
    showSecs = 0
    showMins = 0
    showHrs = 0
    showDuration = ""
    showDescription = ""
    #
    episodeLinesFound = 0
    seasonLinesFound = 0
    descriptionLinesFound  = 0
}

# Produce a raw data spreadsheet from URLs found in $SHOW_URLS
#
# Note that IN_CANADA affects processing.
#
# INVOCATION:
#    while read -r line; do
#        curl -sS "$line" |
#            awk -v ERRORS=$ERRORS -v RAW_TITLES=$RAW_TITLES v LONG_SPREADSHEET=$LONG_SPREADSHEET \
#            -v EPISODE_URLS=$EPISODE_URLS -f getAcornFrom-showPages.awk >>$UNSORTED
#        ((lastRow++))
#    done <"$SHOW_URLS"

#   Field Names
#        1 Title (link)    2 Seasons    3 Episodes    4 Duration    5 Description

# Extract the show title
/meta property="og:title/ {
    split ($0,fld,"\"")
    showTitle = fld[4]
    sub (/^Watch /,"",showTitle)
    sub (/ on Acorn TV$/,"",showTitle)
    gsub (/&/,"\\&",showTitle)
    gsub (/"/,"\"\"",showTitle)
    gsub (/'/,"'",showTitle)
    if (match (showTitle, /^The /)) {
        showTitle = substr(showTitle, 5) ", The"
    }
    print showTitle >> RAW_TITLES
    # print "==> showTitle = " showTitle > "/dev/stderr"
    next
}

# Extract the show URL
/meta property="og:url/ {
    split ($0,fld,"\"")
    showURL = fld[4]
    # print "==> showURL = " showURL > "/dev/stderr"
    # Create shorter URL by removing https://
    shortURL = showURL
    sub (/.*acorn\.tv/,"acorn.tv",shortURL)
    next
}

# Extract the number of episodes in the show
/itemprop="numberOfEpisodes"/ {
    episodeLinesFound += 1
    split ($0,fld,"\"")
    if (episodeLinesFound == 1) {
        showEpisodes = fld[4]
        if (showEpisodes == "") {
            printf ("==> Blank showEpisodes in numberOfEpisodes: %s\t%s\n", shortURL, showTitle) >> ERRORS
        }
        # print "==> showEpisodes = " showEpisodes " " shortURL > "/dev/stderr"
    }
    if (episodeLinesFound != 1) {
        seasonEpisodes = seasonEpisodes "+" fld[4]
        if (seasonEpisodes == "") {
            printf ("==> Blank seasonEpisodes in numberOfEpisodes: %s\t%s\n", shortURL, showTitle) >> ERRORS
        }
        # print "==> seasonEpisodes = " seasonEpisodes " " shortURL > "/dev/stderr"
    }
    next
}

# Extract the number of seasons in the show
/itemprop="numberOfSeasons"/ {
    seasonLinesFound += 1
    split ($0,fld,"\"")
    showSeasons = fld[4]
    if (showSeasons == "") {
        printf ("==> Blank showSeasons in numberOfSeasons: %s\t%s\n", shortURL, showTitle) >> ERRORS
    }
    # print "==> showSeasons = " showSeasons " " shortURL > "/dev/stderr"
    next
}

# Extract the show description
/id="franchise-description"/ {
    descriptionLinesFound += 1
    # get rid of boilerplate
    split ($0,fld,"[<>]")
    showDescription = fld[3]
    # fix sloppy input spacing
    gsub (/ \./,".",showDescription)
    gsub (/  */," ",showDescription)
    sub (/^ */,"",showDescription)
    sub (/ *$/,"",showDescription)
    # fix funky HTML characters
    gsub (/&/,"\\&",showDescription)
    gsub (/"/,"\"\"",showDescription)
    gsub (/'/,"'",showDescription)
    # fix unmatched quotes
    numQuotes = gsub(/"/,"\"",showDescription)
    if ((numQuotes % 2) == 1) {
        printf ("==> Changed unmatched quote (%d): %d\t%s\t%s\t%s\n", numQuotes, shortURL,
                showTitle) >> ERRORS
        showDescription = showDescription " \""
    }
    # print "==> showDescription = " showDescription " " shortURL > "/dev/stderr"
    next
}

# Extract the seasonNumber
# Note that Acorn "season" numbers may not correspond to actual season numbers.
# They simply refer to the number of seasons available on Acorn.
/<meta itemprop="seasonNumber" content="/ {
    split ($0,fld,"\"")
    seasonNumber = fld[4]
    # print "==> seasonNumber = " seasonNumber " " shortURL > "/dev/stderr"
    next
}

# Extract the episode URL
/ episodeURL = " episodeURL > "/dev/stderr"
    print episodeURL >> EPISODE_URLS
    shortEpisodeURL = episodeURL
    sub (/.*acorn\.tv/,"acorn.tv",shortEpisodeURL)
    next
}

# Extract the episode duration
/<meta itemprop="timeRequired"/ {
    durationLinesFound += 1
    split ($0,fld,"\"")
    split (fld[4],tm,/[TMS]/)
    secs = tm[3]
    mins = tm[2] + int(secs / 60)
    hrs =  int(mins / 60)
    secs %= 60; mins %= 60
    #
    showSecs += secs
    showMins += mins + int(showSecs / 60)
    showHrs += hrs + int(showMins / 60)
    showSecs %= 60; showMins %= 60
    #
    episodeDuration = sprintf ("%02d:%02d:%02d", hrs, mins, secs)
    # print "==> episodeDuration = " episodeDuration " " shortEpisodeURL > "/dev/stderr"
    if (episodeDuration == "00:00:00")
        printf ("==> Blank episode duration: %s  %s\n", shortEpisodeURL, showTitle) >> ERRORS
    next
}

# Extract the episode title
/
/ {
    split ($0,fld,"[<>]")
    episodeTitle = fld[3]
    gsub (/&/,"\\&", episodeTitle)
    gsub (/'/,"'",episodeTitle)
    # print "==> episodeTitle = " episodeTitle " " shortEpisodeURL > "/dev/stderr"
    next
}

# Extract episode number
/
.*span itemprop="episodeNumber">/ {
    split ($0,fld,"[<>]")
    episodeNumber = fld[5]
    # print "==> episodeNumber = " episodeNumber " " shortEpisodeURL > "/dev/stderr"
    #
    # Setup showType so shows group/sort properly
    # Default showType to "S"
    showType = "S"
    # If show is a Prequel, use "P" e.g. Doc Martin
    if (episodeURL ~ /\/prequel/)
        showType = "P"
    #
    # Setup episodeType so episodes group/sort properly
    # Default episodeType to "E"
    episodeType = "E"
    # Default bonus and christmas specials episodeType to "X"
    if (episodeURL ~ /\/bonus|christmas[-]?special/)
        episodeType = "X"
    # If episode is a Trailer, set episodeType to "T" - even though not all these have episodes
    if (episodeURL ~ /_cs\//)
        episodeType = "T"
    #
    # cryptoftears, newworlds & newtonslaw bonus seasonNumber should be 1
    if (episodeURL ~ \
        /\/cryptoftears\/bonus\/|\/newworlds\/bonus\/|\/newtonslaw\/bonus\// \
        && seasonNumber != 1) {
            printf ("==> Changed S%02d to S01: %s\n", seasonNumber, shortEpisodeURL) >> ERRORS
        seasonNumber = 1
    }
    # Plain christmasspecial, seriesfinale don't increment seasonNumber
    if (episodeURL ~ /\/christmasspecial\/|\/seriesfinale\//) {
        printf ("==> Changed S%02d to S%02d: %s\n", seasonNumber, seasonNumber-1,
                shortEpisodeURL) >> ERRORS
        seasonNumber -= 1
    }
    # Wrap up this episode
    # =HYPERLINK("https://acorn.tv/1900island/series1/week-one";"1900 Island, S01E01, Week One")
    episodeLink = sprintf ("=HYPERLINK(\"%s\";\"%s, %s%02d%s%02d, %s\"\)", episodeURL, showTitle,
                    showType, seasonNumber, episodeType, episodeNumber, episodeTitle)
    # Print "episode" line
    # =HYPERLINK("https://acorn.tv/1900island/series1/week-one";"1900 Island, S01E01, Week One") \
    # \t\t\t 00:59:17 \t As they arrive
        printf ("%s\t\t\t%s\t%s\n", episodeLink, episodeDuration, episodeDescription) >> LONG_SPREADSHEET
    episodeURL = ""
    episodeType = ""
    episodeTitle = ""
    episodeNumber = ""
    episodeDuration = ""
    next
}

# Wrap up this show
// {
    if (episodeLinesFound == 0) {
        printf ("==> No numberOfEpisodes: %s\t%s\n", shortURL, showTitle) >> ERRORS
    }
    if (seasonLinesFound == 0) {
        printf ("==> No numberOfSeasons: %s\t%s\n", shortURL, showTitle) >> ERRORS
    }
    if (descriptionLinesFound == 0) {
        printf ("==> No franchise-description: %s\t%s\n", shortURL, showTitle) >> ERRORS
    }
    if (durationLinesFound == 0) {
        printf ("==> No durations: %s\t%s\n", shortURL, showTitle) >> ERRORS
    }
    showLink = "=HYPERLINK(\"" showURL "\";\"" showTitle "\")"
    showDuration = sprintf ("%02d:%02d:%02d", showHrs, showMins, showSecs)
    # Title	Seasons	Episodes	Duration	Description
    printf ("%s\t%s\t=%s\t%s\t%s\n", showLink, showSeasons, seasonEpisodes, showDuration, showDescription)
    # printf ("%s\t%s\t=%s\t%s\t%s\n", showLink, showSeasons, showEpisodes, showDuration, showDescription)
    # Make sure there is no carryover
    showTitle = ""
    showURL = ""
    shortURL = ""
    showLink = ""
    showSeasons = ""
    showEpisodes = ""
    seasonEpisodes = ""
    showSecs = 0
    showMins = 0
    showHrs = 0
    showDuration = ""
    showDescription = ""
    #
    episodeLinesFound = 0
    seasonLinesFound = 0
    descriptionLinesFound  = 0
}


Account for two spaces in MHz header

Account for two spaces in MHz header

Two spaces in "|  TV" caused header to be skipped and included it in the description instead

# Generate series Marquees, Descriptions, Headers from MHz series pages
#       (Headers include Genre, Country, Language, Rating)
# and return the list of Episode URLs for further processing
#
# INVOCATION:
#       curl -s https://watch.mhzchoice.com/a-french-village/ \
#           | awk -v MARQUEE_FILE=$MARQUEE_FILE -v DESCRIPTION_FILE=$DESCRIPTION_FILE \
#           -v HEADER_FILE=$HEADER_FILE -v EPISODE_URL_FILE=$EPISODE_URL_FILE \
#           -v ERROR_FILE=$ERROR_FILE -f getMHzFrom-seriesPages.awk
#
# INPUT:
#       <meta name="apple-itunes-app" content="app-id=1096194231, \
#           app-argument=https://mhzchoice.vhx.tv/agatha-christie-s-criminal-games">
#       <title>A French Village - MHz Choice</title>
#       <meta name="description" content="DRAMA | FRANCE | FRENCH WITH ENGLISH SUBTITLES | TV-MA^M
#       This acclaimed drama is about the German...all in a day's work... its inhabitants.">
# ---
#       <select class="js-switch-season btn-dropdown-black margin-right-medium" data-switch-season >
#           <option value="https://watch.mhzchoice.com/a-french-village/season:1" selected>
#             Season 1
#           </option>
#           <option value="https://watch.mhzchoice.com/a-french-village/season:2">
#             Season 2
#           </option>
#       </select>
# ---
#       Show More
#
# OUTPUT:
#       $MARQUEE_FILE, $DESCRIPTION_FILE, $HEADER_FILE,
#       list of Episode URLs
#

/meta name="apple-itunes-app" content=/,/>/ {
    seriesURL = $0
    sub (/.*app-argument=/,"",seriesURL)
    sub (/">/,"",seriesURL)
    sub (/mhzchoice.vhx.tv/,"watch.mhzchoice.com",seriesURL)
    shortURL = seriesURL
    sub (/.*watch/,"watch",shortURL)
}

/title>/ {
    seriesTitle = $0
    sub (/.*<title>/,"",seriesTitle)
    sub (/ - MHz Choice<.*/,"",seriesTitle)
    gsub (/'/,"'",seriesTitle)
    if (match (seriesTitle, /^The /)) {
        seriesTitle = substr(seriesTitle, 5) ", The"
    }
    print seriesTitle >> MARQUEE_FILE
    next
}

/meta name="description" content=/,/>/ {
    # if we're on the first line of this block ...
    if ($0 ~ /name="description" content="/) {
        headerPrinted = "no"
        sub (/.*name="description" content="/,"")
    }
    # If we find a header, clean it up and print it
    if ($0 ~ /\| {1,2}TV/) {
        sub (/WITH ENGLISH SUBTITLES /,"")
        sub (/SCANDINAVIAN CRIME FICTION/,"Sweden")
        sub (/NONFICTION - DOCUMENTARY/,"Documentary")
        gsub (/ \| {1,2}/,"|")
        sub (/\r/,"")
        # Split out header fields
        numFields = split ($0,fld,"\|")
        # lowercase everything but the first character in all fields but the last
        for (i = 1; i < numFields; ++i) {
            fld[i] = substr(fld[i],1,1) tolower(substr(fld[i],2))
            # uppercase the first character of any second word
            if (match (fld[i],/ /) > 0)
                fld[i] = substr(fld[i],1,RSTART) toupper(substr(fld[i],RSTART+1,1)) \
                (substr(fld[i],RSTART+2))
        }
        # print the finalized header
        if (numFields > 3 ) {
            print fld[1] "\t" fld[2] "\t" fld[(numFields-1)] "\t" fld[(numFields)] >> HEADER_FILE
            headerPrinted = "yes"
        }
    } else {
    # We found a description, clean it up and print it
    gsub (/  */," ")
    gsub (/'/,"'")
    gsub (/"/,"\"")
    gsub (/&/,"\\&")
    gsub (/‘/,"’")
    gsub (/’/,"’")
    gsub (/“/,"“")
    gsub (/”/,"”")
    gsub (/\r/," ")
    # if it's not the last line of the description, print it without a newline
    if ($0 !~ /"\>/) {
        printf ("%s", $0) >> DESCRIPTION_FILE
    } else {
    # if it's the last line of the description, print it with a newline
    sub (/"\>/,"")
    print $0 >> DESCRIPTION_FILE
    # if we didn't find a header in this block, print a blank one
    if (headerPrinted == "no") {
        print "==> No genre/country line: " shortURL "  " seriesTitle >> ERROR_FILE
        print "\t\t\t" >> HEADER_FILE
    }
}
    }
}

# Return the list of Episode URLs for further processing
/<select class="js-switch-season/,/<\/select>/ {
    sub (/^ */,"")
    if ($0 ~ /^<option value="/) {
        split ($0,fld,"\"")
        print fld[2] >> EPISODE_URL_FILE
    }
}

# If there is more to load ...
/class="js-load-more-link/ {
    sub (/.*href="/,"https://watch.mhzchoice.com")
    sub (/">.*/,"")
    print $0 >> EPISODE_URL_FILE
}

# Generate series Marquees, Descriptions, Headers from MHz series pages
#       (Headers include Genre, Country, Language, Rating)
# and return the list of Episode URLs for further processing
#
# INVOCATION:
#       curl -s https://watch.mhzchoice.com/a-french-village/ \
#           | awk -v MARQUEE_FILE=$MARQUEE_FILE -v DESCRIPTION_FILE=$DESCRIPTION_FILE \
#           -v HEADER_FILE=$HEADER_FILE -v EPISODE_URL_FILE=$EPISODE_URL_FILE \
#           -v ERROR_FILE=$ERROR_FILE -f getMHzFrom-seriesPages.awk
#
# INPUT:
#       <meta name="apple-itunes-app" content="app-id=1096194231, \
#           app-argument=https://mhzchoice.vhx.tv/agatha-christie-s-criminal-games">
#       <title>A French Village - MHz Choice</title>
#       <meta name="description" content="DRAMA | FRANCE | FRENCH WITH ENGLISH SUBTITLES | TV-MA^M
#       This acclaimed drama is about the German...all in a day's work... its inhabitants.">
# ---
#       <select class="js-switch-season btn-dropdown-black margin-right-medium" data-switch-season >
#           <option value="https://watch.mhzchoice.com/a-french-village/season:1" selected>
#             Season 1
#           </option>
#           <option value="https://watch.mhzchoice.com/a-french-village/season:2">
#             Season 2
#           </option>
#       </select>
# ---
#       Show More
#
# OUTPUT:
#       $MARQUEE_FILE, $DESCRIPTION_FILE, $HEADER_FILE,
#       list of Episode URLs
#

/meta name="apple-itunes-app" content=/,/>/ {
    seriesURL = $0
    sub (/.*app-argument=/,"",seriesURL)
    sub (/">/,"",seriesURL)
    sub (/mhzchoice.vhx.tv/,"watch.mhzchoice.com",seriesURL)
    shortURL = seriesURL
    sub (/.*watch/,"watch",shortURL)
}

/title>/ {
    seriesTitle = $0
    sub (/.*<title>/,"",seriesTitle)
    sub (/ - MHz Choice<.*/,"",seriesTitle)
    gsub (/'/,"'",seriesTitle)
    if (match (seriesTitle, /^The /)) {
        seriesTitle = substr(seriesTitle, 5) ", The"
    }
    print seriesTitle >> MARQUEE_FILE
    next
}

/meta name="description" content=/,/>/ {
    # if we're on the first line of this block ...
    if ($0 ~ /name="description" content="/) {
        headerPrinted = "no"
        sub (/.*name="description" content="/,"")
    }
    # If we find a header, clean it up and print it
    if ($0 ~ /\| TV/) {
        sub (/WITH ENGLISH SUBTITLES /,"")
        sub (/SCANDINAVIAN CRIME FICTION/,"Sweden")
        sub (/NONFICTION - DOCUMENTARY/,"Documentary")
        gsub (/ \| /,"|")
        sub (/\r/,"")
        # Split out header fields
        numFields = split ($0,fld,"\|")
        # lowercase everything but the first character in all fields but the last
        for (i = 1; i < numFields; ++i) {
            fld[i] = substr(fld[i],1,1) tolower(substr(fld[i],2))
            # uppercase the first character of any second word
            if (match (fld[i],/ /) > 0)
                fld[i] = substr(fld[i],1,RSTART) toupper(substr(fld[i],RSTART+1,1)) \
                (substr(fld[i],RSTART+2))
        }
        # print the finalized header
        if (numFields > 3 ) {
            print fld[1] "\t" fld[2] "\t" fld[(numFields-1)] "\t" fld[(numFields)] >> HEADER_FILE
            headerPrinted = "yes"
        }
    } else {
    # We found a description, clean it up and print it
    gsub (/  */," ")
    gsub (/'/,"'")
    gsub (/"/,"\"")
    gsub (/&/,"\\&")
    gsub (/‘/,"’")
    gsub (/’/,"’")
    gsub (/“/,"“")
    gsub (/”/,"”")
    gsub (/\r/," ")
    # if it's not the last line of the description, print it without a newline
    if ($0 !~ /"\>/) {
        printf ("%s", $0) >> DESCRIPTION_FILE
    } else {
    # if it's the last line of the description, print it with a newline
    sub (/"\>/,"")
    print $0 >> DESCRIPTION_FILE
    # if we didn't find a header in this block, print a blank one
    if (headerPrinted == "no") {
        print "==> No genre/country line: " shortURL "  " seriesTitle >> ERROR_FILE
        print "\t\t\t" >> HEADER_FILE
    }
}
    }
}

# Return the list of Episode URLs for further processing
/<select class="js-switch-season/,/<\/select>/ {
    sub (/^ */,"")
    if ($0 ~ /^<option value="/) {
        split ($0,fld,"\"")
        print fld[2] >> EPISODE_URL_FILE
    }
}

# If there is more to load ...
/class="js-load-more-link/ {
    sub (/.*href="/,"https://watch.mhzchoice.com")
    sub (/">.*/,"")
    print $0 >> EPISODE_URL_FILE
}


Allow KMOD with hypens and dots.

Allow KMOD with hypens and dots.

MFC after:	1 week

#!/usr/bin/awk -f

#-
# Copyright (c) 2006 Max Laier.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS `AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
#
# $FreeBSD$

#
# Script to generate module .c file from a list of firmware images
#

function usage ()
{
	print "usage: fw_stub <firmware:name>* [-l name] [-m modname] [-c outfile]";
	exit 1;
}

#   These are just for convenience ...
function printc(s)
{
	if (opt_c)
		print s > ctmpfilename;
	else
		print s > "/dev/stdout";
}

BEGIN {

#
#   Process the command line.
#

num_files = 0;

for (i = 1; i < ARGC; i++) {
	if (ARGV[i] ~ /^-/) {
		#
		#   awk doesn't have getopt(), so we have to do it ourselves.
		#   This is a bit clumsy, but it works.
		#
		for (j = 2; j <= length(ARGV[i]); j++) {
			o = substr(ARGV[i], j, 1);
			if (o == "c") {
				if (length(ARGV[i]) > j) {
					opt_c = substr(ARGV[i], j + 1);
					break;
				}
				else {
					if (++i < ARGC)
						opt_c = ARGV[i];
					else
						usage();
				}
			} else if (o == "m") {
				if (length(ARGV[i]) > j) {
					opt_m = substr(ARGV[i], j + 1);
					break;
				}
				else {
					if (++i < ARGC)
						opt_m = ARGV[i];
					else
						usage();
				}
			} else if (o == "l") {
				if (length(ARGV[i]) > j) {
					opt_l = substr(ARGV[i], j + 1);
					break;
				}
				else {
					if (++i < ARGC)
						opt_l = ARGV[i];
					else
						usage();
				}
			} else
				usage();
		}
	} else {
		split(ARGV[i], curr, ":");
		filenames[num_files] = curr[1];
		if (length(curr[2]) > 0)
			shortnames[num_files] = curr[2];
		else
			shortnames[num_files] = curr[1];
		if (length(curr[3]) > 0)
			versions[num_files] = int(curr[3]);
		else
			versions[num_files] = 0;
		num_files++;
	}
}

if (!num_files || !opt_m)
	usage();

cfilename = opt_c;
ctmpfilename = cfilename ".tmp";
modname = opt_m;
gsub(/[-\.]/, "_", modname);

printc("#include <sys/param.h>\
#include <sys/errno.h>\
#include <sys/kernel.h>\
#include <sys/module.h>\
#include <sys/linker.h>\
#include <sys/firmware.h>\
#include <sys/systm.h>\n");

if (opt_l) {
	printc("static long " opt_l "_license_ack = 0;");
}

for (file_i = 0; file_i < num_files; file_i++) {
	symb = filenames[file_i];
	# '-', '.' and '/' are converted to '_' by ld/objcopy
	gsub(/-|\.|\//, "_", symb);
	printc("extern char _binary_" symb "_start[], _binary_" symb "_end[];");
}

printc("\nstatic int\n"\
modname "_fw_modevent(module_t mod, int type, void *unused)\
{\
	const struct firmware *fp, *parent;\
	int error;\
	switch (type) {\
	case MOD_LOAD:\n");

if (opt_l) {
		printc("\
		TUNABLE_LONG_FETCH(\"legal." opt_l ".license_ack\", &" opt_l "_license_ack);\
		if (!" opt_l "_license_ack) {\
			printf(\"" opt_m ": You need to read the LICENSE file in /usr/share/doc/legal/" opt_l "/.\\n\");\
			printf(\"" opt_m ": If you agree with the license, set legal." opt_l ".license_ack=1 in /boot/loader.conf.\\n\");\
			return(EPERM);\
		}\n");
}

for (file_i = 0; file_i < num_files; file_i++) {
	short = shortnames[file_i];
	symb = filenames[file_i];
	version = versions[file_i];
	# '-', '.' and '/' are converted to '_' by ld/objcopy
	gsub(/-|\.|\//, "_", symb);

	reg = "\t\tfp = ";
	reg = reg "firmware_register(\"" short "\", _binary_" symb "_start , ";
	reg = reg "(size_t)(_binary_" symb "_end - _binary_" symb "_start), ";
	reg = reg version ", ";

	if (file_i == 0)
		reg = reg "NULL);";
	else
		reg = reg "parent);";

	printc(reg);

	printc("\t\tif (fp == NULL)");
	printc("\t\t\tgoto fail_" file_i ";");
	if (file_i == 0)
		printc("\t\tparent = fp;");
}

printc("\t\treturn (0);");

for (file_i = num_files - 1; file_i > 0; file_i--) {
	printc("fail_" file_i ":")
	printc("\t\t(void)firmware_unregister(\"" shortnames[file_i - 1] "\");");
}

printc("\tfail_0:");
printc("\t\treturn (ENXIO);");

printc("\tcase MOD_UNLOAD:");

for (file_i = 1; file_i < num_files; file_i++) {
	printc("\t\terror = firmware_unregister(\"" shortnames[file_i] "\");");
	printc("\t\tif (error)");
	printc("\t\t\treturn (error);");
}

printc("\t\terror = firmware_unregister(\"" shortnames[0] "\");");

printc("\t\treturn (error);\
	}\
	return (EINVAL);\
}\
\
static moduledata_t " modname "_fw_mod = {\
        \"" modname "_fw\",\
        " modname "_fw_modevent,\
        0\
};\
DECLARE_MODULE(" modname "_fw, " modname "_fw_mod, SI_SUB_DRIVERS, SI_ORDER_FIRST);\
MODULE_VERSION(" modname "_fw, 1);\
MODULE_DEPEND(" modname "_fw, firmware, 1, 1, 1);\
");

if (opt_c)
	if ((rc = system("mv -f " ctmpfilename " " cfilename))) {
		print "'mv -f " ctmpfilename " " cfilename "' failed: " rc \
		    > "/dev/stderr";
		exit 1;
	}

exit 0;

}

#!/usr/bin/awk -f

#-
# Copyright (c) 2006 Max Laier.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS `AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
#
# $FreeBSD$

#
# Script to generate module .c file from a list of firmware images
#

function usage ()
{
	print "usage: fw_stub <firmware:name>* [-l name] [-m modname] [-c outfile]";
	exit 1;
}

#   These are just for convenience ...
function printc(s)
{
	if (opt_c)
		print s > ctmpfilename;
	else
		print s > "/dev/stdout";
}

BEGIN {

#
#   Process the command line.
#

num_files = 0;

for (i = 1; i < ARGC; i++) {
	if (ARGV[i] ~ /^-/) {
		#
		#   awk doesn't have getopt(), so we have to do it ourselves.
		#   This is a bit clumsy, but it works.
		#
		for (j = 2; j <= length(ARGV[i]); j++) {
			o = substr(ARGV[i], j, 1);
			if (o == "c") {
				if (length(ARGV[i]) > j) {
					opt_c = substr(ARGV[i], j + 1);
					break;
				}
				else {
					if (++i < ARGC)
						opt_c = ARGV[i];
					else
						usage();
				}
			} else if (o == "m") {
				if (length(ARGV[i]) > j) {
					opt_m = substr(ARGV[i], j + 1);
					break;
				}
				else {
					if (++i < ARGC)
						opt_m = ARGV[i];
					else
						usage();
				}
			} else if (o == "l") {
				if (length(ARGV[i]) > j) {
					opt_l = substr(ARGV[i], j + 1);
					break;
				}
				else {
					if (++i < ARGC)
						opt_l = ARGV[i];
					else
						usage();
				}
			} else
				usage();
		}
	} else {
		split(ARGV[i], curr, ":");
		filenames[num_files] = curr[1];
		if (length(curr[2]) > 0)
			shortnames[num_files] = curr[2];
		else
			shortnames[num_files] = curr[1];
		if (length(curr[3]) > 0)
			versions[num_files] = int(curr[3]);
		else
			versions[num_files] = 0;
		num_files++;
	}
}

if (!num_files || !opt_m)
	usage();

cfilename = opt_c;
ctmpfilename = cfilename ".tmp";

printc("#include <sys/param.h>\
#include <sys/errno.h>\
#include <sys/kernel.h>\
#include <sys/module.h>\
#include <sys/linker.h>\
#include <sys/firmware.h>\
#include <sys/systm.h>\n");

if (opt_l) {
	printc("static long " opt_l "_license_ack = 0;");
}

for (file_i = 0; file_i < num_files; file_i++) {
	symb = filenames[file_i];
	# '-', '.' and '/' are converted to '_' by ld/objcopy
	gsub(/-|\.|\//, "_", symb);
	printc("extern char _binary_" symb "_start[], _binary_" symb "_end[];");
}

printc("\nstatic int\n"\
opt_m "_fw_modevent(module_t mod, int type, void *unused)\
{\
	const struct firmware *fp, *parent;\
	int error;\
	switch (type) {\
	case MOD_LOAD:\n");

if (opt_l) {
		printc("\
		TUNABLE_LONG_FETCH(\"legal." opt_l ".license_ack\", &" opt_l "_license_ack);\
		if (!" opt_l "_license_ack) {\
			printf(\"" opt_m ": You need to read the LICENSE file in /usr/share/doc/legal/" opt_l "/.\\n\");\
			printf(\"" opt_m ": If you agree with the license, set legal." opt_l ".license_ack=1 in /boot/loader.conf.\\n\");\
			return(EPERM);\
		}\n");
}

for (file_i = 0; file_i < num_files; file_i++) {
	short = shortnames[file_i];
	symb = filenames[file_i];
	version = versions[file_i];
	# '-', '.' and '/' are converted to '_' by ld/objcopy
	gsub(/-|\.|\//, "_", symb);

	reg = "\t\tfp = ";
	reg = reg "firmware_register(\"" short "\", _binary_" symb "_start , ";
	reg = reg "(size_t)(_binary_" symb "_end - _binary_" symb "_start), ";
	reg = reg version ", ";

	if (file_i == 0)
		reg = reg "NULL);";
	else
		reg = reg "parent);";

	printc(reg);

	printc("\t\tif (fp == NULL)");
	printc("\t\t\tgoto fail_" file_i ";");
	if (file_i == 0)
		printc("\t\tparent = fp;");
}

printc("\t\treturn (0);");

for (file_i = num_files - 1; file_i > 0; file_i--) {
	printc("fail_" file_i ":")
	printc("\t\t(void)firmware_unregister(\"" shortnames[file_i - 1] "\");");
}

printc("\tfail_0:");
printc("\t\treturn (ENXIO);");

printc("\tcase MOD_UNLOAD:");

for (file_i = 1; file_i < num_files; file_i++) {
	printc("\t\terror = firmware_unregister(\"" shortnames[file_i] "\");");
	printc("\t\tif (error)");
	printc("\t\t\treturn (error);");
}

printc("\t\terror = firmware_unregister(\"" shortnames[0] "\");");

printc("\t\treturn (error);\
	}\
	return (EINVAL);\
}\
\
static moduledata_t " opt_m "_fw_mod = {\
        \"" opt_m "_fw\",\
        " opt_m "_fw_modevent,\
        0\
};\
DECLARE_MODULE(" opt_m "_fw, " opt_m "_fw_mod, SI_SUB_DRIVERS, SI_ORDER_FIRST);\
MODULE_VERSION(" opt_m "_fw, 1);\
MODULE_DEPEND(" opt_m "_fw, firmware, 1, 1, 1);\
");

if (opt_c)
	if ((rc = system("mv -f " ctmpfilename " " cfilename))) {
		print "'mv -f " ctmpfilename " " cfilename "' failed: " rc \
		    > "/dev/stderr";
		exit 1;
	}

exit 0;

}