更新src/font-data-cpp.awk

更新src/font-data-cpp.awk

BEGIN {
	valid_line_number = 0
	print "{"
}
/^[01]/ {
	valid_line_number++
	printf "0b%s,\n", $0
}
/^[01]/ && valid_line_number % 12 == 0 {
	print "}, {"
}
END {
	print "}"
}


fix formatting so header is in-line with resource

function split_unit(used, output) {
  if ( match(used, /([0-9]+)Ki/, value_split) ) {
    output[0] = int(value_split[1])
    output[1] = "Ki"
    return 1
  } else if ( match(used, /([0-9]+)Mi/, value_split) ) {
    output[0] = int(value_split[1])
    output[1] = "Mi"
    return 1
  } else if ( match(used, /([0-9]+)Gi/, value_split) ) {
    output[0] = int(value_split[1])
    output[1] = "Gi"
    return 1
  } else if ( match(used, /([0-9]+)Ti/, value_split) ) {
    output[0] = int(value_split[1])
    output[1] = "Ti"
    return 1
  } else if ( match(used, /([0-9]+)k/, value_split) ) {
    output[0] = int(value_split[1])
    output[1] = "k"
    return 1
  } else if ( match(used, /([0-9]+)m/, value_split) ) {
    output[0] = int(value_split[1])
    output[1] = "m"
    return 1
  } else if ( match(used, /([0-9]+)/, value_split) ) {
		output[0] = int(value_split[1])
    output[1] = "" # unknown unit
    return 1
  }
  return 0
}
function to_unit(a, a_unit, b, b_unit) {
  if (a_unit == b_unit)
    return a
  if (b_unit == "Ti") {
    if (a_unit == "Ki")
      return a / 1024 / 1024 / 1024
    if (a_unit == "Mi")
      return a / 1024 / 1024
    if (a_unit == "Gi")
      return a / 1024
    if (a_unit == "")
      return a / 1024 / 1024 / 1024 / 1024
    print "unknown a unit: " a  a_unit " for b " b b_unit
    exit 1
  } else if (b_unit == "Gi") {
    if (a_unit == "Ki")
      return a / 1024 / 1024
    if (a_unit == "Mi")
      return a / 1024
    if (a_unit == "Gi")
      return a
    if (a_unit == "")
      return a / 1024 / 1024 / 1024
    print "unknown a unit: " a  a_unit " for b " b b_unit
    exit 1
  } else if (b_unit == "k") {
    if (a_unit == "")
      return a / 1000
    if (a_unit == "m")
      return a / 1000 / 1000
    print "unknown a unit: " a  a_unit " for b " b b_unit
    exit 1
  }
  print "unknown b unit: " b b_unit " with a unit " a a_unit
  exit 1
}
{
  if (split_unit($2, used) && split_unit($3, hard)) {
    value = to_unit(used[0], used[1], hard[0], hard[1])
    #value = normalize_units(unit, $2, $3)
    if (value != 0) {
      percentage = value / int(hard[0]) * 100
      printf "%-55s%-20s%-20s%s\n", $1, (sprintf("%.2f", value) hard[1]), $3, sprintf("%.2f%", percentage)
    }
  } else if ($1 == "Resource" && $2 == "Used") {
    printf "%-55s%-20s%-20s%s\n", "Resource", "Used", "Hard", "Used %"
  } else if ($1 == "--------") {
    printf "%-55s%-20s%-20s%s\n", "--------", "----", "----", "----"
  } else {
    print $0
  }
}

function split_unit(used, output) {
  if ( match(used, /([0-9]+)Ki/, value_split) ) {
    output[0] = int(value_split[1])
    output[1] = "Ki"
    return 1
  } else if ( match(used, /([0-9]+)Mi/, value_split) ) {
    output[0] = int(value_split[1])
    output[1] = "Mi"
    return 1
  } else if ( match(used, /([0-9]+)Gi/, value_split) ) {
    output[0] = int(value_split[1])
    output[1] = "Gi"
    return 1
  } else if ( match(used, /([0-9]+)Ti/, value_split) ) {
    output[0] = int(value_split[1])
    output[1] = "Ti"
    return 1
  } else if ( match(used, /([0-9]+)k/, value_split) ) {
    output[0] = int(value_split[1])
    output[1] = "k"
    return 1
  } else if ( match(used, /([0-9]+)m/, value_split) ) {
    output[0] = int(value_split[1])
    output[1] = "m"
    return 1
  } else if ( match(used, /([0-9]+)/, value_split) ) {
		output[0] = int(value_split[1])
    output[1] = "" # unknown unit
    return 1
  }
  return 0
}
function to_unit(a, a_unit, b, b_unit) {
  if (a_unit == b_unit)
    return a
  if (b_unit == "Ti") {
    if (a_unit == "Ki")
      return a / 1024 / 1024 / 1024
    if (a_unit == "Mi")
      return a / 1024 / 1024
    if (a_unit == "Gi")
      return a / 1024
    if (a_unit == "")
      return a / 1024 / 1024 / 1024 / 1024
    print "unknown a unit: " a  a_unit " for b " b b_unit
    exit 1
  } else if (b_unit == "Gi") {
    if (a_unit == "Ki")
      return a / 1024 / 1024
    if (a_unit == "Mi")
      return a / 1024
    if (a_unit == "Gi")
      return a
    if (a_unit == "")
      return a / 1024 / 1024 / 1024
    print "unknown a unit: " a  a_unit " for b " b b_unit
    exit 1
  } else if (b_unit == "k") {
    if (a_unit == "")
      return a / 1000
    if (a_unit == "m")
      return a / 1000 / 1000
    print "unknown a unit: " a  a_unit " for b " b b_unit
    exit 1
  }
  print "unknown b unit: " b b_unit " with a unit " a a_unit
  exit 1
}
{
  if (split_unit($2, used) && split_unit($3, hard)) {
    value = to_unit(used[0], used[1], hard[0], hard[1])
    #value = normalize_units(unit, $2, $3)
    if (value != 0) {
      percentage = value / int(hard[0]) * 100
      printf "%-55s%-20s%-20s%s\n", $1, (sprintf("%.2f", value) hard[1]), $3, sprintf("%.2f%", percentage)
    }
  } else {
    print $0
  }
}


Handle 3 digit episodes in BBox

Handle 3 digit episodes in BBox

# Print the processed "Episode" lines from a WebScraper csv file saved in tsv format

# INVOCATION:
#       awk -v EPISODE_INFO_FILE=$EPISODE_INFO_FILE -v ERROR_FILE=$ERROR_FILE \
#           -f getBBoxEpisodesFrom-webscraper.awk $EPISODES_SORTED_FILE >$EPISODES_SPREADSHEET_FILE

# Field numbers
#    1 web-scraper-start-url  2 SsnURL     3 SsnURL-href    4 URL        5 Program_Title
#    6 Episode_Title          7 Year       8 Duration       9 Rating    10 Description

BEGIN {
    FS="\t"
    OFS="\t"
    print "Sortkey\tTitle\tSeasons\tEpisodes\tDuration\tYear(s)\tRating\tDescription"
}

{
    for ( i = 1; i <= NF; i++ ) {
        if ($i == "null")
            $i = ""
    }
}

/\/us\/movie\/|\/us\/episode\// {
    baseURL = $1
    URL = $4
    showTitle = $5
    episodeTitle = $6
    Years = $7
    Duration = $8
    Rating = $9
    Description = $10
    showtype = "S"

    # Extract sortkey from URL
    nflds = split (URL,fld,"_")
    if (URL ~ /_S[[:digit:]]*_E[[:digit:]]*_[[:digit:]]*$/) {
        seasonNumber = substr(fld[nflds-2], 2)
        episodeNumber = substr(fld[nflds-1], 2)
        sortkey = sprintf ("%s%02dE%03d", showtype, seasonNumber, episodeNumber)
    } else {
        URL ~ /^\/us\/movie\// ? showtype = "M" : showtype = "E"
        sortkey = sprintf ("%s%05d", showtype, fld[nflds])
    }

    # Convert duration from minutes to HMS
    sub( / min/,"",Duration)
    secs = 0
    mins = Duration % 60
    hrs = int(Duration / 60)
    HMS = sprintf ("%02d:%02d:%02d", hrs, mins, secs)
    if (HMS == "00:00:00")
        HMS = ""

    # Titles starting with "The" should not sort based on "The"
    # unless it's never used without the "The" included, such as "The Queen"
    # But since Britbox, unlike others, uses the "The" when sorting ...
    # if (showTitle !~ /^The Queen/ && showTitle !~ /^The Shard/ && match (showTitle, /^The /))
    #     showTitle = substr(showTitle, 5) ", The"

    # Some shows that need special processing
    if (baseURL ~ /Maigret_15974$/) {
        showTitle = "Maigret (2016)"
    }
    if (baseURL ~ /Porridge_9509$/) {
        showTitle = "Porridge (1974-1977)"
    }
    if (baseURL ~ /Porridge_14747$/) {
        showTitle = "Porridge (2016-2017)"
    }

    # Get rid of redundant "Series #" or "Series #" from episodeTitle
    if (match (episodeTitle, /^Season [[:digit:]]*, |^Series [[:digit:]]*, /))
        episodeTitle = substr(episodeTitle,RLENGTH+1)

    # Non-existent fields in movies or episodes
    if (URL ~ /^\/us\/movie\//) {
        depth = "1"
        numSeasons = 1
        numEpisodes = 1
        fullTitle = showTitle
    } else {
        depth = "2"
        numSeasons = ""
        numEpisodes = ""
        fullTitle = showTitle ", " sortkey ", " episodeTitle
    }

    # Create spreadsheet row in common format
    savedLine = sprintf \
        ("%s (%s) %s %s\t=HYPERLINK(\"https://www.britbox.com%s\";\"%s\"\)\t%s\t%s\t%s\t%s\t%s\t%s",\
         showTitle, depth, Years, sortkey, URL, fullTitle, numSeasons, numEpisodes, \
         HMS, Years, Rating, Description)

    # Make sure line doesn't start with a single quote so it sorts correctly in Open Office
    sub (/^'/,"",savedLine)

    print savedLine
    next
}

/\/us\/show\// {
    # Shouldn't get here
    badEpisodes += 1
    URL = $4
    nflds = split (URL,URLflds,"/")
    print "    " URLflds[nflds]  >> ERROR_FILE
}

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

# Print the processed "Episode" lines from a WebScraper csv file saved in tsv format

# INVOCATION:
#       awk -v EPISODE_INFO_FILE=$EPISODE_INFO_FILE -v ERROR_FILE=$ERROR_FILE \
#           -f getBBoxEpisodesFrom-webscraper.awk $EPISODES_SORTED_FILE >$EPISODES_SPREADSHEET_FILE

# Field numbers
#    1 web-scraper-start-url  2 SsnURL     3 SsnURL-href    4 URL        5 Program_Title
#    6 Episode_Title          7 Year       8 Duration       9 Rating    10 Description

BEGIN {
    FS="\t"
    OFS="\t"
    print "Sortkey\tTitle\tSeasons\tEpisodes\tDuration\tYear(s)\tRating\tDescription"
}

{
    for ( i = 1; i <= NF; i++ ) {
        if ($i == "null")
            $i = ""
    }
}

/\/us\/movie\/|\/us\/episode\// {
    baseURL = $1
    URL = $4
    showTitle = $5
    episodeTitle = $6
    Years = $7
    Duration = $8
    Rating = $9
    Description = $10
    showtype = "S"

    # Extract sortkey from URL
    nflds = split (URL,fld,"_")
    if (URL ~ /_S[[:digit:]]*_E[[:digit:]]*_[[:digit:]]*$/) {
        seasonNumber = substr(fld[nflds-2], 2)
        episodeNumber = substr(fld[nflds-1], 2)
        sortkey = sprintf ("%s%02dE%02d", showtype, seasonNumber, episodeNumber)
    } else {
        URL ~ /^\/us\/movie\// ? showtype = "M" : showtype = "E"
        sortkey = sprintf ("%s%05d", showtype, fld[nflds])
    }

    # Convert duration from minutes to HMS
    sub( / min/,"",Duration)
    secs = 0
    mins = Duration % 60
    hrs = int(Duration / 60)
    HMS = sprintf ("%02d:%02d:%02d", hrs, mins, secs)
    if (HMS == "00:00:00")
        HMS = ""

    # Titles starting with "The" should not sort based on "The"
    # unless it's never used without the "The" included, such as "The Queen"
    # But since Britbox, unlike others, uses the "The" when sorting ...
    # if (showTitle !~ /^The Queen/ && showTitle !~ /^The Shard/ && match (showTitle, /^The /))
    #     showTitle = substr(showTitle, 5) ", The"

    # Some shows that need special processing
    if (baseURL ~ /Maigret_15974$/) {
        showTitle = "Maigret (2016)"
    }
    if (baseURL ~ /Porridge_9509$/) {
        showTitle = "Porridge (1974-1977)"
    }
    if (baseURL ~ /Porridge_14747$/) {
        showTitle = "Porridge (2016-2017)"
    }

    # Get rid of redundant "Series #" or "Series #" from episodeTitle
    if (match (episodeTitle, /^Season [[:digit:]]*, |^Series [[:digit:]]*, /))
        episodeTitle = substr(episodeTitle,RLENGTH+1)

    # Non-existent fields in movies or episodes
    if (URL ~ /^\/us\/movie\//) {
        depth = "1"
        numSeasons = 1
        numEpisodes = 1
        fullTitle = showTitle
    } else {
        depth = "2"
        numSeasons = ""
        numEpisodes = ""
        fullTitle = showTitle ", " sortkey ", " episodeTitle
    }

    # Create spreadsheet row in common format
    savedLine = sprintf \
        ("%s (%s) %s %s\t=HYPERLINK(\"https://www.britbox.com%s\";\"%s\"\)\t%s\t%s\t%s\t%s\t%s\t%s",\
         showTitle, depth, Years, sortkey, URL, fullTitle, numSeasons, numEpisodes, \
         HMS, Years, Rating, Description)

    # Make sure line doesn't start with a single quote so it sorts correctly in Open Office
    sub (/^'/,"",savedLine)

    print savedLine
    next
}

/\/us\/show\// {
    # Shouldn't get here
    badEpisodes += 1
    URL = $4
    nflds = split (URL,URLflds,"/")
    print "    " URLflds[nflds]  >> ERROR_FILE
}

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


gnumake3: fixing deliver for solaris nawk

#*************************************************************************
#
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
#
# Copyright 2000, 2010 Oracle and/or its affiliates.
#
# OpenOffice.org - a multi-platform office productivity suite
#
# This file is part of OpenOffice.org.
#
# OpenOffice.org is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3
# only, as published by the Free Software Foundation.
#
# OpenOffice.org is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License version 3 for more details
# (a copy is included in the LICENSE file that accompanied this code).
#
# You should have received a copy of the GNU Lesser General Public License
# version 3 along with OpenOffice.org.  If not, see
# <http://www.openoffice.org/license.html>
# for a copy of the LGPLv3 License.
#
#*************************************************************************

BEGIN {
    RS=" "
    FS=":"
}

{
    if( NF == 2 )
        print "COPY " $1 " " $2;
}

#*************************************************************************
#
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
#
# Copyright 2000, 2010 Oracle and/or its affiliates.
#
# OpenOffice.org - a multi-platform office productivity suite
#
# This file is part of OpenOffice.org.
#
# OpenOffice.org is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3
# only, as published by the Free Software Foundation.
#
# OpenOffice.org is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License version 3 for more details
# (a copy is included in the LICENSE file that accompanied this code).
#
# You should have received a copy of the GNU Lesser General Public License
# version 3 along with OpenOffice.org.  If not, see
# <http://www.openoffice.org/license.html>
# for a copy of the LGPLv3 License.
#
#*************************************************************************

BEGIN {
    RS=" "
    FS=":"
}

NF == 2 {
    print "COPY " $1 " " $2;
}


Setup variable showType and episodeType

Setup variable showType and episodeType

Assists proper grouping/sorting

# 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"
    #
    # 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
}

# 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"
    # Wrap up this episode
    episodeType = "E"
    # =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,
                    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
}


Commons: improve squeeze(line)

Commons: improve squeeze(line)

####################################################################
# Commons.awk                                                      #
#                                                                  #
# Commonly used functions for string and array operations, logging.#
####################################################################

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

# Return the real character represented by an escape sequence.
# Example: escapeChar("n") returns "\n".
# See: <https://en.wikipedia.org/wiki/Escape_character>
#      <https://en.wikipedia.org/wiki/Escape_sequences_in_C>
function escapeChar(char) {
    switch (char) {
    case "b":
        return "\b" # Backspace
    case "f":
        return "\f" # Formfeed
    case "n":
        return "\n" # Newline (Line Feed)
    case "r":
        return "\r" # Carriage Return
    case "t":
        return "\t" # Horizontal Tab
    case "v":
        return "\v" # Vertical Tab
    default:
        return char
    }
}

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

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

# Return the escaped string.
function escape(string) {
    gsub(/"/, "\\\"", string)
    gsub(/\\/, "\\\\", string)
    return string
}

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

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

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

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

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

    return line
}

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

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

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

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

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

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

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

    return temp
}

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

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

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

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

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

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

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

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

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

# Print error message.
function e(text) {
    print ansi("bold", ansi("red", text)) > "/dev/stderr"
}

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

# Log an array.
function da(array, formatString, sortedIn,
            ####
            i, j, saveSortedIn) {
    # Default parameters
    if (!formatString)
        formatString = "_[%s]='%s'"
    if (!sortedIn)
        sortedIn = "@ind_num_asc"

    saveSortedIn = PROCINFO["sorted_in"]
    PROCINFO["sorted_in"] = sortedIn
    for (i in array) {
        split(i, j, SUBSEP)
        d(sprintf(formatString, join(j, ","), array[i]))
    }
    PROCINFO["sorted_in"] = saveSortedIn
}

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

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

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

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

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

####################################################################
# Commons.awk                                                      #
#                                                                  #
# Commonly used functions for string and array operations, logging.#
####################################################################

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

# Return the real character represented by an escape sequence.
# Example: escapeChar("n") returns "\n".
# See: <https://en.wikipedia.org/wiki/Escape_character>
#      <https://en.wikipedia.org/wiki/Escape_sequences_in_C>
function escapeChar(char) {
    switch (char) {
    case "b":
        return "\b" # Backspace
    case "f":
        return "\f" # Formfeed
    case "n":
        return "\n" # Newline (Line Feed)
    case "r":
        return "\r" # Carriage Return
    case "t":
        return "\t" # Horizontal Tab
    case "v":
        return "\v" # Vertical Tab
    default:
        return char
    }
}

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

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

# Return the escaped string.
function escape(string) {
    gsub(/"/, "\\\"", string)
    gsub(/\\/, "\\\\", string)
    return string
}

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

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

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

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

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

    return line
}

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

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

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

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

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

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

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

    return temp
}

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

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

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

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

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

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

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

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

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

# Print error message.
function e(text) {
    print ansi("bold", ansi("red", text)) > "/dev/stderr"
}

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

# Log an array.
function da(array, formatString, sortedIn,
            ####
            i, j, saveSortedIn) {
    # Default parameters
    if (!formatString)
        formatString = "_[%s]='%s'"
    if (!sortedIn)
        sortedIn = "@ind_num_asc"

    saveSortedIn = PROCINFO["sorted_in"]
    PROCINFO["sorted_in"] = sortedIn
    for (i in array) {
        split(i, j, SUBSEP)
        d(sprintf(formatString, join(j, ","), array[i]))
    }
    PROCINFO["sorted_in"] = saveSortedIn
}

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

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

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

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

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


support extract when ADDing a tar.gz

support extract when ADDing a tar.gz

/^ADD/ {
    sub(/ADD/, "")
    cmd = "mkdir -p $(dirname " $2 ")"
    # system(cmd)
    print cmd
    cmd = "cp " $0
    # system(cmd)
    print cmd
    # if ($1 ~ /"tar.gz"/ ) {
    cmd = "tar xvfz " $1 " --directory " $2
    # system(cmd)
    print cmd
    # }
}

/^RUN/ {
    sub(/RUN/, "")
    cmd = $0
    # system(cmd)
    print cmd
}

/^ENV/ {
    sub(/ENV /, "")
    sub(/ /, "=")
    env = $0
    print env
}

/^USER/ {
    sub(/USER/, "")
    cmd = "useradd -g staff -G staff -s /bin/bash -p banana -d /home/"$1" -m "$1
    # system(cmd)
    print cmd
}

/^ADD/ {
    sub(/ADD/, "")
    cmd = "mkdir -p $(dirname " $2 ")"
    # system(cmd)
    print cmd
    cmd = "cp " $0
    # system(cmd)
    print cmd
}

/^RUN/ {
    sub(/RUN/, "")
    cmd = $0
    # system(cmd)
    print cmd
}

/^ENV/ {
    sub(/ENV /, "")
    sub(/ /, "=")
    env = $0
    print env
}

/^USER/ {
    sub(/USER/, "")
    cmd = "useradd -g staff -G staff -s /bin/bash -p banana -d /home/"$1" -m "$1
    # system(cmd)
    print cmd
}


Fix processing

Fix processing

BEGIN { skipping=0; cur = ""; }
{
  if ($0 ~ /trkpt lat/) {
      if (cur == $0) {
	  skipping = 1;
      } else {
	  skipping = 0;
      }
      cur = $0;
  }
  if ($0 ~ /trkseg/) {
    skipping = 0;
    cur = "INVALID_STRING";
  }
  if (skipping) {
  } else {
      print $0;
  }
}
END   { }

BEGIN { skipping=0; cur = ""; }
{
  if ($0 ~ /trkpt lat/) {
      if (cur == $0) {
	  skipping = 1;
      } else {
	  skipping = 0;
      }
      cur = $0;
  }
  if (skipping) {
  } else {
      print $0;
  }
}
END   { }


MONDRIAN: Only check for NON-NLS in mondrian; extra excluded file for checkFile in mondrian-lagunitas branch.

[git-p4: depot-paths = "//open/mondrian/": change = 14355]

#!/bin/gawk
# $Id: //open/util/bin/checkFile#13 $
# Checks that a file is valid.

function error(fname, linum, msg) {
    printf "%s:%d: %s\n", fname, linum, msg;
    if (0) print; # for debug
}
function _matchFile(fname) {
    return fname ~ "/mondrian/" \
       || fname ~ "/org/olap4j/" \
       || fname ~ "/aspen/" \
       || fname ~ "/farrago/" \
       || fname ~ "/fennel/" \
       || fname ~ "/extensions/" \
       || fname ~ "/com/sqlstream/" \
       || !lenient;
}
function _isCpp(fname) {
    return fname ~ /\.(cpp|h)$/;
}
function _isJava(fname) {
    return fname ~ /\.(java|jj)$/;
}
function _isMondrian(fname) {
    return fname ~ /mondrian/;
}
function push(val) {
    switchStack[switchStackLen++] = val;
}
function pop() {
    --switchStackLen
    val = switchStack[switchStackLen];
    delete switchStack[switchStackLen];
    return val;
}
function afterFile() {
    # Compute basename. If fname="/foo/bar/baz.txt" then basename="baz.txt".
    basename = fname;
    gsub(".*/", "", basename);
    gsub(lf, "", lastNonEmptyLine);
    terminator = "// End " basename;
    if (matchFile && (lastNonEmptyLine != terminator)) {
        error(fname, FNR, sprintf("Last line should be %c%s%c", 39, terminator, 39));
    }
}
# Returns whether there are unmatched open parentheses.
# unmatchedOpenParens("if ()") returns false.
# unmatchedOpenParens("if (") returns true.
# unmatchedOpenParens("if (foo) bar(") returns false
function unmatchedOpenParens(s) {
    i = index(s, "(");
    if (i == 0) {
    if (0)         print FNR, "unmatchedOpenParens=0";
        return 0;
    }
    openCount = 1;
    while (++i <= length(s)) {
        c = substr(s, i, 1);
        if (c == "(") {
            ++openCount;
        }
        if (c == ")") {
            if (--openCount == 0) {
    if (0)         print FNR, "unmatchedOpenParens=0 (b)";
                return 0;
            }
        }
    }
    if (0) print FNR, "unmatchedOpenParens=1";
    return 1;
}

function countLeadingSpaces(str) {
    i = 0;
    while (i < length(str) && substr(str, i + 1, 1) == " ") {
        ++i;
    }
    return i;
}

BEGIN {
    # pre-compute regexp for quotes, linefeed
    apos = sprintf("%c", 39);
    quot = sprintf("%c", 34);
    lf = sprintf("%c", 13);
    pattern = apos "(\\" apos "|[^" apos "])" apos;
    if (0) printf "maxLineLength=%s lenient=%s\n", maxLineLength, lenient;
}
FNR == 1 {
    if (fname) {
        afterFile();
    }
    fname = FILENAME;
    matchFile = _matchFile(fname);
    isCpp = _isCpp(fname);
    isJava = _isJava(fname);
    mondrian = _isMondrian(fname);
}
{
    if (previousLineEndedInCloseBrace > 0) {
        --previousLineEndedInCloseBrace;
    }
    if (previousLineEndedInOpenBrace > 0) {
        --previousLineEndedInOpenBrace;
    }
    if (previousLineWasEmpty > 0) {
        --previousLineWasEmpty;
    }
    s = $0;
    # remove DOS linefeeds
    gsub(lf, "", s);
    # replace strings
    gsub(/"(\\"|[^"\\]|\\[^"])*"/, "string", s);
    # replace single-quoted strings
    gsub(pattern, "string", s);
    # replace {: and :} in .cup files
    if (fname ~ /\.cup$/) {
        gsub(/{:/, "{", s);
        gsub(/:}/, "}", s);
        gsub(/:/, " : ", s);
    }
    if (inComment && $0 ~ /\*\//) {
        # end of multiline comment "*/"
        inComment = 0;
        gsub(/^.*\*\//, "/* comment */", s);
    } else if (inComment) {
        s = "/* comment */";
    } else if ($0 ~ /\/\*/ && $0 !~ /\/\*.*\*\//) {
        # beginning of multiline comment "/*"
        inComment = 1;
        gsub(/\/\*.*$/, "/* comment */", s);
    } else {
        # mask out /* */ comments
        gsub(/\/\*.*\*\//, "/* comment */", s);
    }
    if (mondrian && s ~ /\/\/\$NON-NLS/) {
        error(fname, FNR, "NON-NLS not allowed");
    }
    # mask out // comments
    gsub(/\/\/.*$/, "// comment", s);
    # line starts with string or plus?
    if (s ~ /^ *string/ \
        && s !~ /)/)
    {
        stringCol = index(s, "string");
    } else if (s ~ /^ *[+] string/) {
        if (stringCol != 0 && index(s, "+") != stringCol) {
            error(fname, FNR, "String '+' must be aligned with string on line above");
        }
    } else if (s ~ /comment/) {
        # in comment; string target carries forward
    } else {
        stringCol = 0;
    }

    # Is the line indented as expected?
    if (nextIndent > 0) {
        indent = countLeadingSpaces(s);
        if (indent != nextIndent) {
            error(fname, FNR, "Incorrect indent for first line of arg list");
        }
    }
    nextIndent = -1;
}
/ $/ {
    error(fname, FNR, "Line ends in space");
}
/[\t]/ {
    if (matchFile) {
        error(fname, FNR, "Tab character");
    }
}
/[\r]/ {
    if (matchFile) {
        error(fname, FNR, "Carriage return character (file is in DOS format?)");
    }
}
/./ {
    lastNonEmptyLine = $0;
}
{
    # Rules beyond this point only apply to Java and C++.
    if (!isCpp && !isJava) {
        next;
    }
}

/^$/ {
    if (matchFile && previousLineEndedInOpenBrace) {
        error(fname, FNR, "Empty line following open brace");
    }
}
/^ +}( catch| finally| while|[;,)])/ ||
/^ +}$/ {
    if (matchFile && previousLineWasEmpty) {
        error(fname, FNR - 1, "Empty line before close brace");
    }
}
s ~ /\<if\>.*;$/ {
    if (!matchFile) {}
    else {
        error(fname, FNR, "if followed by statement on same line");
    }
}
s ~ /\<(if) *\(/ {
    if (!matchFile) {
    } else if (s !~ /\<(if) /) {
        error(fname, FNR, "if must be followed by space");
    } else if (s ~ / else if /) {
    } else if (s ~ /^#if /) {
    } else if (s !~ /^(    )*(if)/) {
        error(fname, FNR, "if must be correctly indented");
    }
}
s ~ /\<(while) *\(/ {
    if (!matchFile) {
    } else if (s !~ /\<(while) /) {
        error(fname, FNR, "while must be followed by space");
    } else if (s ~ /} while /) {
    } else if (s !~ /^(    )+(while)/) {
        error(fname, FNR, "while must be correctly indented");
    }
}
s ~ /\<(for|switch|synchronized|} catch) *\(/ {
    if (!matchFile) {}
    else if (s !~ /^(    )*(for|switch|synchronized|} catch)/) {
        error(fname, FNR, "for/switch/synchronized/catch must be correctly indented");
    } else if (s !~ /\<(for|switch|synchronized|} catch) /) {
        error(fname, FNR, "for/switch/synchronized/catch must be followed by space");
    }
}
s ~ /\<(if|while|for|switch|catch)\>/ {
    # Check single-line if statements, such as
    #   if (condition) return;
    # We recognize such statements because there are equal numbers of open and
    # close parentheses.
    opens = s;
    gsub(/[^(]/, "", opens);
    closes = s;
    gsub(/[^)]/, "", closes);
    if (!matchFile) {
    } else if (s ~ /{( *\/\/ comment)?$/) {
        # lines which end with { and optional comment are ok
    } else if (s ~ /{.*\\$/ && isCpp) {
        # lines which end with backslash are ok in c++ macros
    } else if (s ~ /} while/) {
        # lines like "} while (foo);" are ok
    } else if (s ~ /^#/) {
        # lines like "#if 0" are ok
    } else if (s ~ /if \(true|false\)/) {
        # allow "if (true)" and "if (false)" because they are
        # used for commenting
    } else if (!unmatchedOpenParens(s)  \
               && length($0) != 79      \
               && length($0) != 80)
    {
        error(fname, FNR, "single-line if/while/for/switch/catch must end in {");
    }
}
s ~ /[[:alnum:]]\(/ &&
s !~ /\<(if|while|for|switch|assert)\>/ {
    ss = s;
    while (match(ss, /[[:alnum:]]\(/)) {
        ss = substr(ss, RSTART + RLENGTH - 1);
        parens = ss;
        gsub(/[^()]/, "", parens);
        while (substr(parens, 1, 2) == "()") {
            parens = substr(parens, 3);
        }
        opens = parens;
        gsub(/[^(]/, "", opens);
        closes = parens;
        gsub(/[^)]/, "", closes);
        if (length(opens) > length(closes)) {
            if (ss ~ /,$/) {
                bras = ss;
                gsub(/[^<]/, "", bras);
                kets = ss;
                gsub(/->/, "", kets);
                gsub(/[^>]/, "", kets);
                if (length(bras) > length(kets)) {
                    # Ignore case like 'for (Map.Entry<Foo,{nl} Bar> entry : ...'
                } else if (s ~ / for /) {
                    # Ignore case like 'for (int i = 1,{nl} j = 2; i < j; ...'
                } else {
                    error(                                              \
                        fname, FNR,                                     \
                        "multi-line parameter list should start with newline");
                    break;
                }
            } else if (s ~ /[;(]( *\\)?$/) {
                # If open paren is at end of line (with optional backslash
                # for macros), we're fine.
            } else if (s ~ /@.*\({/) {
                # Ignore Java annotations.
            } else {
                error(                                                  \
                    fname, FNR,                                         \
                    "Open parenthesis should be at end of line (function call spans several lines)");
                break;
            }
        }
        ss = substr(ss, 2); # remove initial "("
    }
}
s ~ /\<switch\>/ {
    push(switchCol);
    switchCol = index($0, "switch");
}
s ~ /{/ {
    braceCol = index($0, "{");
    if (braceCol == switchCol) {
        push(switchCol);
    }
}
s ~ /}/ {
    braceCol = index($0, "}");
    if (braceCol == switchCol) {
        switchCol = pop();
    }
}
s ~ /\<(case|default)\>/ {
    caseDefaultCol = match($0, /case|default/);
    if (!matchFile) {}
    else if (caseDefaultCol != switchCol) {
        error(fname, FNR, "case/default must be aligned with switch");
    }
}
s ~ /\<assert\>/ {
    if (!matchFile) {}
    else if (isCpp) {} # rule only applies to java
    else if (s !~ /^(    )+(assert)/) {
        error(fname, FNR, "assert must be correctly indented");
    } else if (s !~ /\<assert /) {
        error(fname, FNR, "assert must be followed by space");
    }
}
s ~ /\<return\>/ {
    if (!matchFile) {}
    else if (isCpp && s ~ /^#/) {
        # ignore macros
    } else if (s !~ /^(    )+(return)/) {
        error(fname, FNR, "return must be correctly indented");
    } else if (s !~ /\<return[ ;]/ && s !~ /\<return$/) {
        error(fname, FNR, "return must be followed by space or ;");
    }
}
s ~ /\<throw\>/ {
    if (!matchFile) {}
    else if (isCpp) {
        # cannot yet handle C++ cases like 'void foo() throw(int)'
    } else if (s !~ /^(    )+(throw)/) {
        error(fname, FNR, "throw must be correctly indented");
    } else if (s !~ /\<throw / && s !~ /\<throw$/) {
        error(fname, FNR, "throw must be followed by space");
    }
}
s ~ /\<else\>/ {
    if (!matchFile) {}
    else if (isCpp && s ~ /^# *else$/) {} # ignore "#else"
    else if (s !~ /^(    )+} else (if |{$|{ *\/\/|{ *\/\*)/) {
        error(fname, FNR, "else must be preceded by } and followed by { or if and correctly indented");
    }
}
s ~ /\<do\>/ {
    if (!matchFile) {}
    else if (s !~ /^(    )*do {/) {
        error(fname, FNR, "do must be followed by space {, and correctly indented");
    }
}
s ~ /\<try\>/ {
    if (!matchFile) {}
    else if (s !~ /^(    )+try {/) {
        error(fname, FNR, "try must be followed by space {, and correctly indented");
    }
}
s ~ /\<catch\>/ {
    if (!matchFile) {}
    else if (s !~ /^(    )+} catch /) {
        error(fname, FNR, "catch must be preceded by }, followed by space, and correctly indented");
    }
}
s ~ /\<finally\>/ {
    if (!matchFile) {}
    else if (s !~ /^(    )+} finally {/) {
        error(fname, FNR, "finally must be preceded by }, followed by space {, and correctly indented");
    }
}
s ~ /\($/ {
    nextIndent = countLeadingSpaces(s) + 4;
    if (s ~ / (if|while) .*\(.*\(/) {
        nextIndent += 4;
    }
}
match(s, /([]A-Za-z0-9()])(+|-|\*|\^|\/|%|=|==|+=|-=|\*=|\/=|>=|<=|!=|&|&&|\||\|\||^|\?|:) *[A-Za-z0-9(]/, a) {
    # < and > are not handled here - they have special treatment below
    if (!matchFile) {}
#    else if (s ~ /<.*>/) {} # ignore templates
    else if (a[2] == "-" && s ~ /\(-/) {} # ignore case "foo(-1)"
    else if (a[2] == "-" && s ~ /[eE][+-][0-9]/) {} # ignore e.g. 1e-5
    else if (a[2] == "+" && s ~ /[eE][+-][0-9]/) {} # ignore e.g. 1e+5
    else if (a[2] == ":" && s ~ /(case.*|default):$/) {} # ignore e.g. "case 5:"
    else if (isCpp && s ~ /[^ ][*&]/) {} # ignore e.g. "Foo* p;" in c++ - debatable
    else if (isCpp && s ~ /\<operator.*\(/) {} # ignore e.g. "operator++()" in c++
    else if (isCpp && a[2] == "/" && s ~ /#include/) {} # ignore e.g. "#include <x/y.hpp>" in c++
    else {
        error(fname, FNR, "operator '" a[2] "' must be preceded by space");
    }
}
match(s, /([]A-Za-z0-9() ] *)(+|-|\*|\^|\/|%|=|==|+=|-=|\*=|\/=|>=|<=|!=|&|&&|\||\|\||^|\?|:|,)[A-Za-z0-9(]/, a) {
    if (!matchFile) {}
#    else if (s ~ /<.*>/) {} # ignore templates
    else if (a[2] == "-" && s ~ /(\(|return |case |= )-/) {} # ignore prefix -
    else if (a[2] == ":" && s ~ /(case.*|default):$/) {} # ignore e.g. "case 5:"
    else if (s ~ /, *-/) {} # ignore case "foo(x, -1)"
    else if (s ~ /-[^ ]/ && s ~ /[^A-Za-z0-9] -/) {} # ignore case "x + -1" but not "x -1" or "3 -1"
    else if (a[2] == "-" && s ~ /[eE][+-][0-9]/) {} # ignore e.g. 1e-5
    else if (a[2] == "+" && s ~ /[eE][+-][0-9]/) {} # ignore e.g. 1e+5
    else if (a[2] == "*" && isCpp && s ~ /\*[^ ]/) {} # ignore e.g. "Foo *p;" in c++
    else if (a[2] == "&" && isCpp && s ~ /&[^ ]/) {} # ignore case "foo(&x)" in c++
    else if (isCpp && s ~ /\<operator[^ ]+\(/) {} # ignore e.g. "operator++()" in c++
    else if (isCpp && a[2] == "/" && s ~ /#include/) {} # ignore e.g. "#include <x/y.hpp>" in c++
    else if (lenient && fname ~ /(fennel)/ && a[1] = ",") {} # not enabled yet
    else {
        error(fname, FNR, "operator '" a[2] "' must be followed by space");
    }
}
match(s, /( )(,)/, a) {
    # (, < and > are not handled here - they have special treatment below
    if (!matchFile) {}
    else {
        error(fname, FNR, "operator '" a[2] "' must not be preceded by space");
    }
}
match(s, / (+|-|\*|\/|==|>=|<=|!=|<<|<<<|>>|&|&&|\|\||\?|:)$/, a) || \
match(s, /(\.|->)$/, a) {
    if (lenient && fname ~ /(aspen)/ && a[1] != ":") {} # not enabled yet
    else if (lenient && fname ~ /(fennel|farrago|aspen)/ && a[1] = "+") {} # not enabled yet
    else if (a[1] == ":" && s ~ /(case.*|default):$/) {
        # ignore e.g. "case 5:"
    } else if ((a[1] == "*" || a[1] == "&") && isCpp && s ~ /^[[:alnum:]:_ ]* [*&]$/) {
        # ignore e.g. "const int *\nClass::Subclass2::method(int x)"
    } else {
        error(fname, FNR, "operator '" a[1] "' must not be at end of line");
    }
}
match(s, /^ *(=) /, a) {
    error(fname, FNR, "operator '" a[1] "' must not be at start of line");
}
match(s, /([[:alnum:]~]+)( )([(])/, a) {
    # (, < and > are not handled here - they have special treatment below
    if (!matchFile) {}
    else if (isJava && a[1] ~ /\<(if|while|for|catch|switch|case|return|throw|synchronized|assert)\>/) {}
    else if (isCpp && a[1] ~ /\<(if|while|for|catch|switch|case|return|throw|operator|void|PBuffer)\>/) {}
    else if (isCpp && s ~ /^#define /) {}
    else {
        error(fname, FNR, "there must be no space before '" a[3] "' in fun call or fun decl");
    }
}
s ~ /\<[[:digit:][:lower:]][[:alnum:]_]*</ {
    # E.g. "p<" but not "Map<"
    if (!matchFile) {}
    else if (isCpp) {} # in C++ 'xyz<5>' could be a template
    else {
        error(fname, FNR, "operator '<' must be preceded by space");
    }
}
s ~ /\<[[:digit:][:lower:]][[:alnum:]_]*>/ {
    # E.g. "g>" but not "String>" as in "List<String>"
    if (!matchFile) {}
    else if (isCpp) {} # in C++ 'xyz<int>' could be a template
    else {
        error(fname, FNR, "operator '>' must be preceded by space");
    }
}
match(s, /<([[:digit:][:lower:]][[:alnum:].]*)\>/, a) {
    if (!matchFile) {}
    else if (isCpp) {
        # in C++, template and include generate too many false positives
    } else if (isJava && a[1] ~ /(int|char|long|boolean|byte|double|float)/) {
        # Allow e.g. 'List<int[]>'
    } else if (isJava && a[1] ~ /^[[:lower:]]+\./) {
        # Allow e.g. 'List<java.lang.String>'
    } else {
        error(fname, FNR, "operator '<' must be followed by space");
    }
}
match(s, /^(.*[^-])>([[:digit:][:lower:]][[:alnum:]]*)\>/, a) {
    if (!matchFile) {}
    else if (isJava && a[1] ~ /.*\.<.*/) {
        # Ignore 'Collections.<Type>member'
    } else {
        error(fname, FNR, "operator '>' must be followed by space");
    }
}
s ~ /[[(] / {
    if (!matchFile) {}
    else if (s ~ /[[(] +\\$/) {} # ignore '#define foo(   \'
    else {
        error(fname, FNR, "( or [ must not be followed by space");
    }
}
s ~ / [])]/ {
    if (!matchFile) {}
    else if (s ~ /^ *\)/ && previousLineEndedInCloseBrace) {} # ignore "bar(new Foo() { } );"
    else {
        error(fname, FNR, ") or ] must not be followed by space");
    }
}
s ~ /}/ {
    if (!matchFile) {}
    else if (s !~ /}( |;|,|$|\))/) {
        error(fname, FNR, "} must be followed by space");
    } else if (s !~ /(    )*}/) {
        error(fname, FNR, "} must be at start of line and correctly indented");
    }
}
s ~ /{/ {
    if (!matchFile) {}
    else if (s ~ /(\]\)?|=) *{/) {} # ignore e.g. "(int[]) {1, 2}" or "int[] x = {1, 2}"
    else if (s ~ /\({/) {} # ignore e.g. @SuppressWarnings({"unchecked"})
    else if (s ~ /{ *(\/\/|\/\*)/) {} # ignore e.g. "do { // a comment"
    else if (s ~ / {}$/) {} # ignore e.g. "Constructor() {}"
    else if (s ~ / },$/) {} # ignore e.g. "{ yada },"
    else if (s ~ / };$/) {} # ignore e.g. "{ yada };"
    else if (s ~ / {};$/) {} # ignore e.g. "template <> class Foo<int> {};"
    else if (s ~ / },? *\/\/.*$/) {} # ignore e.g. "{ yada }, // comment"
    else if (s ~ /\\$/) {} # ignore multiline macros
    else if (s ~ /{}/) { # e.g. "Constructor(){}"
        error(fname, FNR, "{} must be preceded by space and at end of line");
    } else if (isCpp && s ~ /{ *\\$/) {
        # ignore - "{" can be followed by "\" in c macro
    } else if (s !~ /{$/) {
        error(fname, FNR, "{ must be at end of line");
    } else if (s !~ /(^| ){/) {
        error(fname, FNR, "{ must be preceded by space or at start of line");
    } else {
        opens = s;
        gsub(/[^(]/, "", opens);
        closes = s;
        gsub(/[^)]/, "", closes);
        if (0 && lenient && fname ~ /aspen/) {} # not enabled
        else if (length(closes) > length(opens)) {
            error(fname, FNR, "Open brace should be on new line (function call/decl spans several lines)");
        }
    }
}
s ~ /(^| )(class|interface|enum) / ||
s ~ /(^| )namespace / && isCpp {
    if (isCpp && s ~ /;$/) {} # ignore type declaration
    else {
        classDeclStartLine = FNR;
        t = s;
        gsub(/.*(class|interface|enum|namespace) /, "", t);
        gsub(/ .*$/, "", t);
        if (s ~ /template/) {
            # ignore case "template <class INSTCLASS> static void foo()"
            classDeclStartLine = 0;
        } else if (t ~ /[[:upper:]][[:upper:]][[:upper:]][[:upper:]]/     \
            && t !~ /LRU/ \
            && t !~ /WAL/ \
            && t !~ /classUUID/ \
            && t !~ /classSQLException/ \
            && t !~ /BBRC/ \
            && t !~ /_/ \
            && t !~ /EncodedSqlInterval/)
        {
            error(fname, FNR, "Class name " t " has consecutive uppercase letters");
        }
    }
}
s ~ / throws\>/ {
    if (s ~ /\(/) {
        funDeclStartLine = FNR;
    } else {
        funDeclStartLine = FNR - 1;
    }
}
length($0) > maxLineLength                      \
&& $0 !~ /@(throws|see|link)/                   \
&& $0 !~ /\$Id: /                               \
&& $0 !~ /^import /                             \
&& $0 !~ /http:/                                \
&& $0 !~ /\/\/ Expect "/                        \
&& s !~ /^ *(\+ |<< )?string\)?[;,]?$/ {
    error( \
        fname, \
        FNR, \
        "Line length (" length($0) ") exceeds " maxLineLength " chars");
}
/}$/ {
    previousLineEndedInCloseBrace = 2;
}
/;$/ {
    funDeclStartLine = 0;
}
/{$/ {
    # Ignore open brace if it is part of class or interface declaration.
    if (classDeclStartLine) {
        if (classDeclStartLine < FNR \
            && $0 !~ /^ *{$/)
        {
            error(fname, FNR, "Open brace should be on new line (class decl spans several lines)");
        }
        classDeclStartLine = 0;
    } else {
        previousLineEndedInOpenBrace = 2;
    }
    if (funDeclStartLine) {
        if (funDeclStartLine < FNR \
            && $0 !~ /^ *{$/)
        {
            if (lenient && fname ~ /aspen/) {} # not enabled
            else error(fname, FNR, "Open brace should be on new line (function decl spans several lines)");
        }
        funDeclStartLine = 0;
    }
}
/^$/ {
    previousLineWasEmpty = 2;
}
{
    next;
}
END {
    afterFile();
}

# End checkFile.awk

#!/bin/gawk
# $Id: //open/util/bin/checkFile#13 $
# Checks that a file is valid.

function error(fname, linum, msg) {
    printf "%s:%d: %s\n", fname, linum, msg;
    if (0) print; # for debug
}
function _matchFile(fname) {
    return fname ~ "/mondrian/" \
       || fname ~ "/org/olap4j/" \
       || fname ~ "/aspen/" \
       || fname ~ "/farrago/" \
       || fname ~ "/fennel/" \
       || fname ~ "/extensions/" \
       || fname ~ "/com/sqlstream/" \
       || !lenient;
}
function _isCpp(fname) {
    return fname ~ /\.(cpp|h)$/;
}
function _isJava(fname) {
    return fname ~ /\.(java|jj)$/;
}
function push(val) {
    switchStack[switchStackLen++] = val;
}
function pop() {
    --switchStackLen
    val = switchStack[switchStackLen];
    delete switchStack[switchStackLen];
    return val;
}
function afterFile() {
    # Compute basename. If fname="/foo/bar/baz.txt" then basename="baz.txt".
    basename = fname;
    gsub(".*/", "", basename);
    gsub(lf, "", lastNonEmptyLine);
    terminator = "// End " basename;
    if (matchFile && (lastNonEmptyLine != terminator)) {
        error(fname, FNR, sprintf("Last line should be %c%s%c", 39, terminator, 39));
    }
}
# Returns whether there are unmatched open parentheses.
# unmatchedOpenParens("if ()") returns false.
# unmatchedOpenParens("if (") returns true.
# unmatchedOpenParens("if (foo) bar(") returns false
function unmatchedOpenParens(s) {
    i = index(s, "(");
    if (i == 0) {
    if (0)         print FNR, "unmatchedOpenParens=0";
        return 0;
    }
    openCount = 1;
    while (++i <= length(s)) {
        c = substr(s, i, 1);
        if (c == "(") {
            ++openCount;
        }
        if (c == ")") {
            if (--openCount == 0) {
    if (0)         print FNR, "unmatchedOpenParens=0 (b)";
                return 0;
            }
        }
    }
    if (0) print FNR, "unmatchedOpenParens=1";
    return 1;
}

function countLeadingSpaces(str) {
    i = 0;
    while (i < length(str) && substr(str, i + 1, 1) == " ") {
        ++i;
    }
    return i;
}

BEGIN {
    # pre-compute regexp for quotes, linefeed
    apos = sprintf("%c", 39);
    quot = sprintf("%c", 34);
    lf = sprintf("%c", 13);
    pattern = apos "(\\" apos "|[^" apos "])" apos;
    if (0) printf "maxLineLength=%s lenient=%s\n", maxLineLength, lenient;
}
FNR == 1 {
    if (fname) {
        afterFile();
    }
    fname = FILENAME;
    matchFile = _matchFile(fname);
    isCpp = _isCpp(fname);
    isJava = _isJava(fname);
}
{
    if (previousLineEndedInCloseBrace > 0) {
        --previousLineEndedInCloseBrace;
    }
    if (previousLineEndedInOpenBrace > 0) {
        --previousLineEndedInOpenBrace;
    }
    if (previousLineWasEmpty > 0) {
        --previousLineWasEmpty;
    }
    s = $0;
    # remove DOS linefeeds
    gsub(lf, "", s);
    # replace strings
    gsub(/"(\\"|[^"\\]|\\[^"])*"/, "string", s);
    # replace single-quoted strings
    gsub(pattern, "string", s);
    # replace {: and :} in .cup files
    if (fname ~ /\.cup$/) {
        gsub(/{:/, "{", s);
        gsub(/:}/, "}", s);
        gsub(/:/, " : ", s);
    }
    if (inComment && $0 ~ /\*\//) {
        # end of multiline comment "*/"
        inComment = 0;
        gsub(/^.*\*\//, "/* comment */", s);
    } else if (inComment) {
        s = "/* comment */";
    } else if ($0 ~ /\/\*/ && $0 !~ /\/\*.*\*\//) {
        # beginning of multiline comment "/*"
        inComment = 1;
        gsub(/\/\*.*$/, "/* comment */", s);
    } else {
        # mask out /* */ comments
        gsub(/\/\*.*\*\//, "/* comment */", s);
    }
    if (s ~ /\/\/\$NON-NLS/) {
        error(fname, FNR, "NON-NLS not allowed");
    }
    # mask out // comments
    gsub(/\/\/.*$/, "// comment", s);
    # line starts with string or plus?
    if (s ~ /^ *string/ \
        && s !~ /)/)
    {
        stringCol = index(s, "string");
    } else if (s ~ /^ *[+] string/) {
        if (stringCol != 0 && index(s, "+") != stringCol) {
            error(fname, FNR, "String '+' must be aligned with string on line above");
        }
    } else if (s ~ /comment/) {
        # in comment; string target carries forward
    } else {
        stringCol = 0;
    }

    # Is the line indented as expected?
    if (nextIndent > 0) {
        indent = countLeadingSpaces(s);
        if (indent != nextIndent) {
            error(fname, FNR, "Incorrect indent for first line of arg list");
        }
    }
    nextIndent = -1;
}
/ $/ {
    error(fname, FNR, "Line ends in space");
}
/[\t]/ {
    if (matchFile) {
        error(fname, FNR, "Tab character");
    }
}
/[\r]/ {
    if (matchFile) {
        error(fname, FNR, "Carriage return character (file is in DOS format?)");
    }
}
/./ {
    lastNonEmptyLine = $0;
}
{
    # Rules beyond this point only apply to Java and C++.
    if (!isCpp && !isJava) {
        next;
    }
}

/^$/ {
    if (matchFile && previousLineEndedInOpenBrace) {
        error(fname, FNR, "Empty line following open brace");
    }
}
/^ +}( catch| finally| while|[;,)])/ ||
/^ +}$/ {
    if (matchFile && previousLineWasEmpty) {
        error(fname, FNR - 1, "Empty line before close brace");
    }
}
s ~ /\<if\>.*;$/ {
    if (!matchFile) {}
    else {
        error(fname, FNR, "if followed by statement on same line");
    }
}
s ~ /\<(if) *\(/ {
    if (!matchFile) {
    } else if (s !~ /\<(if) /) {
        error(fname, FNR, "if must be followed by space");
    } else if (s ~ / else if /) {
    } else if (s ~ /^#if /) {
    } else if (s !~ /^(    )*(if)/) {
        error(fname, FNR, "if must be correctly indented");
    }
}
s ~ /\<(while) *\(/ {
    if (!matchFile) {
    } else if (s !~ /\<(while) /) {
        error(fname, FNR, "while must be followed by space");
    } else if (s ~ /} while /) {
    } else if (s !~ /^(    )+(while)/) {
        error(fname, FNR, "while must be correctly indented");
    }
}
s ~ /\<(for|switch|synchronized|} catch) *\(/ {
    if (!matchFile) {}
    else if (s !~ /^(    )*(for|switch|synchronized|} catch)/) {
        error(fname, FNR, "for/switch/synchronized/catch must be correctly indented");
    } else if (s !~ /\<(for|switch|synchronized|} catch) /) {
        error(fname, FNR, "for/switch/synchronized/catch must be followed by space");
    }
}
s ~ /\<(if|while|for|switch|catch)\>/ {
    # Check single-line if statements, such as
    #   if (condition) return;
    # We recognize such statements because there are equal numbers of open and
    # close parentheses.
    opens = s;
    gsub(/[^(]/, "", opens);
    closes = s;
    gsub(/[^)]/, "", closes);
    if (!matchFile) {
    } else if (s ~ /{( *\/\/ comment)?$/) {
        # lines which end with { and optional comment are ok
    } else if (s ~ /{.*\\$/ && isCpp) {
        # lines which end with backslash are ok in c++ macros
    } else if (s ~ /} while/) {
        # lines like "} while (foo);" are ok
    } else if (s ~ /^#/) {
        # lines like "#if 0" are ok
    } else if (s ~ /if \(true|false\)/) {
        # allow "if (true)" and "if (false)" because they are
        # used for commenting
    } else if (!unmatchedOpenParens(s)  \
               && length($0) != 79      \
               && length($0) != 80)
    {
        error(fname, FNR, "single-line if/while/for/switch/catch must end in {");
    }
}
s ~ /[[:alnum:]]\(/ &&
s !~ /\<(if|while|for|switch|assert)\>/ {
    ss = s;
    while (match(ss, /[[:alnum:]]\(/)) {
        ss = substr(ss, RSTART + RLENGTH - 1);
        parens = ss;
        gsub(/[^()]/, "", parens);
        while (substr(parens, 1, 2) == "()") {
            parens = substr(parens, 3);
        }
        opens = parens;
        gsub(/[^(]/, "", opens);
        closes = parens;
        gsub(/[^)]/, "", closes);
        if (length(opens) > length(closes)) {
            if (ss ~ /,$/) {
                bras = ss;
                gsub(/[^<]/, "", bras);
                kets = ss;
                gsub(/->/, "", kets);
                gsub(/[^>]/, "", kets);
                if (length(bras) > length(kets)) {
                    # Ignore case like 'for (Map.Entry<Foo,{nl} Bar> entry : ...'
                } else if (s ~ / for /) {
                    # Ignore case like 'for (int i = 1,{nl} j = 2; i < j; ...'
                } else {
                    error(                                              \
                        fname, FNR,                                     \
                        "multi-line parameter list should start with newline");
                    break;
                }
            } else if (s ~ /[;(]( *\\)?$/) {
                # If open paren is at end of line (with optional backslash
                # for macros), we're fine.
            } else if (s ~ /@.*\({/) {
                # Ignore Java annotations.
            } else {
                error(                                                  \
                    fname, FNR,                                         \
                    "Open parenthesis should be at end of line (function call spans several lines)");
                break;
            }
        }
        ss = substr(ss, 2); # remove initial "("
    }
}
s ~ /\<switch\>/ {
    push(switchCol);
    switchCol = index($0, "switch");
}
s ~ /{/ {
    braceCol = index($0, "{");
    if (braceCol == switchCol) {
        push(switchCol);
    }
}
s ~ /}/ {
    braceCol = index($0, "}");
    if (braceCol == switchCol) {
        switchCol = pop();
    }
}
s ~ /\<(case|default)\>/ {
    caseDefaultCol = match($0, /case|default/);
    if (!matchFile) {}
    else if (caseDefaultCol != switchCol) {
        error(fname, FNR, "case/default must be aligned with switch");
    }
}
s ~ /\<assert\>/ {
    if (!matchFile) {}
    else if (isCpp) {} # rule only applies to java
    else if (s !~ /^(    )+(assert)/) {
        error(fname, FNR, "assert must be correctly indented");
    } else if (s !~ /\<assert /) {
        error(fname, FNR, "assert must be followed by space");
    }
}
s ~ /\<return\>/ {
    if (!matchFile) {}
    else if (isCpp && s ~ /^#/) {
        # ignore macros
    } else if (s !~ /^(    )+(return)/) {
        error(fname, FNR, "return must be correctly indented");
    } else if (s !~ /\<return[ ;]/ && s !~ /\<return$/) {
        error(fname, FNR, "return must be followed by space or ;");
    }
}
s ~ /\<throw\>/ {
    if (!matchFile) {}
    else if (isCpp) {
        # cannot yet handle C++ cases like 'void foo() throw(int)'
    } else if (s !~ /^(    )+(throw)/) {
        error(fname, FNR, "throw must be correctly indented");
    } else if (s !~ /\<throw / && s !~ /\<throw$/) {
        error(fname, FNR, "throw must be followed by space");
    }
}
s ~ /\<else\>/ {
    if (!matchFile) {}
    else if (isCpp && s ~ /^# *else$/) {} # ignore "#else"
    else if (s !~ /^(    )+} else (if |{$|{ *\/\/|{ *\/\*)/) {
        error(fname, FNR, "else must be preceded by } and followed by { or if and correctly indented");
    }
}
s ~ /\<do\>/ {
    if (!matchFile) {}
    else if (s !~ /^(    )*do {/) {
        error(fname, FNR, "do must be followed by space {, and correctly indented");
    }
}
s ~ /\<try\>/ {
    if (!matchFile) {}
    else if (s !~ /^(    )+try {/) {
        error(fname, FNR, "try must be followed by space {, and correctly indented");
    }
}
s ~ /\<catch\>/ {
    if (!matchFile) {}
    else if (s !~ /^(    )+} catch /) {
        error(fname, FNR, "catch must be preceded by }, followed by space, and correctly indented");
    }
}
s ~ /\<finally\>/ {
    if (!matchFile) {}
    else if (s !~ /^(    )+} finally {/) {
        error(fname, FNR, "finally must be preceded by }, followed by space {, and correctly indented");
    }
}
s ~ /\($/ {
    nextIndent = countLeadingSpaces(s) + 4;
    if (s ~ / (if|while) .*\(.*\(/) {
        nextIndent += 4;
    }
}
match(s, /([]A-Za-z0-9()])(+|-|\*|\^|\/|%|=|==|+=|-=|\*=|\/=|>=|<=|!=|&|&&|\||\|\||^|\?|:) *[A-Za-z0-9(]/, a) {
    # < and > are not handled here - they have special treatment below
    if (!matchFile) {}
#    else if (s ~ /<.*>/) {} # ignore templates
    else if (a[2] == "-" && s ~ /\(-/) {} # ignore case "foo(-1)"
    else if (a[2] == "-" && s ~ /[eE][+-][0-9]/) {} # ignore e.g. 1e-5
    else if (a[2] == "+" && s ~ /[eE][+-][0-9]/) {} # ignore e.g. 1e+5
    else if (a[2] == ":" && s ~ /(case.*|default):$/) {} # ignore e.g. "case 5:"
    else if (isCpp && s ~ /[^ ][*&]/) {} # ignore e.g. "Foo* p;" in c++ - debatable
    else if (isCpp && s ~ /\<operator.*\(/) {} # ignore e.g. "operator++()" in c++
    else if (isCpp && a[2] == "/" && s ~ /#include/) {} # ignore e.g. "#include <x/y.hpp>" in c++
    else {
        error(fname, FNR, "operator '" a[2] "' must be preceded by space");
    }
}
match(s, /([]A-Za-z0-9() ] *)(+|-|\*|\^|\/|%|=|==|+=|-=|\*=|\/=|>=|<=|!=|&|&&|\||\|\||^|\?|:|,)[A-Za-z0-9(]/, a) {
    if (!matchFile) {}
#    else if (s ~ /<.*>/) {} # ignore templates
    else if (a[2] == "-" && s ~ /(\(|return |case |= )-/) {} # ignore prefix -
    else if (a[2] == ":" && s ~ /(case.*|default):$/) {} # ignore e.g. "case 5:"
    else if (s ~ /, *-/) {} # ignore case "foo(x, -1)"
    else if (s ~ /-[^ ]/ && s ~ /[^A-Za-z0-9] -/) {} # ignore case "x + -1" but not "x -1" or "3 -1"
    else if (a[2] == "-" && s ~ /[eE][+-][0-9]/) {} # ignore e.g. 1e-5
    else if (a[2] == "+" && s ~ /[eE][+-][0-9]/) {} # ignore e.g. 1e+5
    else if (a[2] == "*" && isCpp && s ~ /\*[^ ]/) {} # ignore e.g. "Foo *p;" in c++
    else if (a[2] == "&" && isCpp && s ~ /&[^ ]/) {} # ignore case "foo(&x)" in c++
    else if (isCpp && s ~ /\<operator[^ ]+\(/) {} # ignore e.g. "operator++()" in c++
    else if (isCpp && a[2] == "/" && s ~ /#include/) {} # ignore e.g. "#include <x/y.hpp>" in c++
    else if (lenient && fname ~ /(fennel)/ && a[1] = ",") {} # not enabled yet
    else {
        error(fname, FNR, "operator '" a[2] "' must be followed by space");
    }
}
match(s, /( )(,)/, a) {
    # (, < and > are not handled here - they have special treatment below
    if (!matchFile) {}
    else {
        error(fname, FNR, "operator '" a[2] "' must not be preceded by space");
    }
}
match(s, / (+|-|\*|\/|==|>=|<=|!=|<<|<<<|>>|&|&&|\|\||\?|:)$/, a) || \
match(s, /(\.|->)$/, a) {
    if (lenient && fname ~ /(aspen)/ && a[1] != ":") {} # not enabled yet
    else if (lenient && fname ~ /(fennel|farrago|aspen)/ && a[1] = "+") {} # not enabled yet
    else if (a[1] == ":" && s ~ /(case.*|default):$/) {
        # ignore e.g. "case 5:"
    } else if ((a[1] == "*" || a[1] == "&") && isCpp && s ~ /^[[:alnum:]:_ ]* [*&]$/) {
        # ignore e.g. "const int *\nClass::Subclass2::method(int x)"
    } else {
        error(fname, FNR, "operator '" a[1] "' must not be at end of line");
    }
}
match(s, /^ *(=) /, a) {
    error(fname, FNR, "operator '" a[1] "' must not be at start of line");
}
match(s, /([[:alnum:]~]+)( )([(])/, a) {
    # (, < and > are not handled here - they have special treatment below
    if (!matchFile) {}
    else if (isJava && a[1] ~ /\<(if|while|for|catch|switch|case|return|throw|synchronized|assert)\>/) {}
    else if (isCpp && a[1] ~ /\<(if|while|for|catch|switch|case|return|throw|operator|void|PBuffer)\>/) {}
    else if (isCpp && s ~ /^#define /) {}
    else {
        error(fname, FNR, "there must be no space before '" a[3] "' in fun call or fun decl");
    }
}
s ~ /\<[[:digit:][:lower:]][[:alnum:]_]*</ {
    # E.g. "p<" but not "Map<"
    if (!matchFile) {}
    else if (isCpp) {} # in C++ 'xyz<5>' could be a template
    else {
        error(fname, FNR, "operator '<' must be preceded by space");
    }
}
s ~ /\<[[:digit:][:lower:]][[:alnum:]_]*>/ {
    # E.g. "g>" but not "String>" as in "List<String>"
    if (!matchFile) {}
    else if (isCpp) {} # in C++ 'xyz<int>' could be a template
    else {
        error(fname, FNR, "operator '>' must be preceded by space");
    }
}
match(s, /<([[:digit:][:lower:]][[:alnum:].]*)\>/, a) {
    if (!matchFile) {}
    else if (isCpp) {
        # in C++, template and include generate too many false positives
    } else if (isJava && a[1] ~ /(int|char|long|boolean|byte|double|float)/) {
        # Allow e.g. 'List<int[]>'
    } else if (isJava && a[1] ~ /^[[:lower:]]+\./) {
        # Allow e.g. 'List<java.lang.String>'
    } else {
        error(fname, FNR, "operator '<' must be followed by space");
    }
}
match(s, /^(.*[^-])>([[:digit:][:lower:]][[:alnum:]]*)\>/, a) {
    if (!matchFile) {}
    else if (isJava && a[1] ~ /.*\.<.*/) {
        # Ignore 'Collections.<Type>member'
    } else {
        error(fname, FNR, "operator '>' must be followed by space");
    }
}
s ~ /[[(] / {
    if (!matchFile) {}
    else if (s ~ /[[(] +\\$/) {} # ignore '#define foo(   \'
    else {
        error(fname, FNR, "( or [ must not be followed by space");
    }
}
s ~ / [])]/ {
    if (!matchFile) {}
    else if (s ~ /^ *\)/ && previousLineEndedInCloseBrace) {} # ignore "bar(new Foo() { } );"
    else {
        error(fname, FNR, ") or ] must not be followed by space");
    }
}
s ~ /}/ {
    if (!matchFile) {}
    else if (s !~ /}( |;|,|$|\))/) {
        error(fname, FNR, "} must be followed by space");
    } else if (s !~ /(    )*}/) {
        error(fname, FNR, "} must be at start of line and correctly indented");
    }
}
s ~ /{/ {
    if (!matchFile) {}
    else if (s ~ /(\]\)?|=) *{/) {} # ignore e.g. "(int[]) {1, 2}" or "int[] x = {1, 2}"
    else if (s ~ /\({/) {} # ignore e.g. @SuppressWarnings({"unchecked"})
    else if (s ~ /{ *(\/\/|\/\*)/) {} # ignore e.g. "do { // a comment"
    else if (s ~ / {}$/) {} # ignore e.g. "Constructor() {}"
    else if (s ~ / },$/) {} # ignore e.g. "{ yada },"
    else if (s ~ / };$/) {} # ignore e.g. "{ yada };"
    else if (s ~ / {};$/) {} # ignore e.g. "template <> class Foo<int> {};"
    else if (s ~ / },? *\/\/.*$/) {} # ignore e.g. "{ yada }, // comment"
    else if (s ~ /\\$/) {} # ignore multiline macros
    else if (s ~ /{}/) { # e.g. "Constructor(){}"
        error(fname, FNR, "{} must be preceded by space and at end of line");
    } else if (isCpp && s ~ /{ *\\$/) {
        # ignore - "{" can be followed by "\" in c macro
    } else if (s !~ /{$/) {
        error(fname, FNR, "{ must be at end of line");
    } else if (s !~ /(^| ){/) {
        error(fname, FNR, "{ must be preceded by space or at start of line");
    } else {
        opens = s;
        gsub(/[^(]/, "", opens);
        closes = s;
        gsub(/[^)]/, "", closes);
        if (0 && lenient && fname ~ /aspen/) {} # not enabled
        else if (length(closes) > length(opens)) {
            error(fname, FNR, "Open brace should be on new line (function call/decl spans several lines)");
        }
    }
}
s ~ /(^| )(class|interface|enum) / ||
s ~ /(^| )namespace / && isCpp {
    if (isCpp && s ~ /;$/) {} # ignore type declaration
    else {
        classDeclStartLine = FNR;
        t = s;
        gsub(/.*(class|interface|enum|namespace) /, "", t);
        gsub(/ .*$/, "", t);
        if (s ~ /template/) {
            # ignore case "template <class INSTCLASS> static void foo()"
            classDeclStartLine = 0;
        } else if (t ~ /[[:upper:]][[:upper:]][[:upper:]][[:upper:]]/     \
            && t !~ /LRU/ \
            && t !~ /WAL/ \
            && t !~ /classUUID/ \
            && t !~ /classSQLException/ \
            && t !~ /BBRC/ \
            && t !~ /_/ \
            && t !~ /EncodedSqlInterval/)
        {
            error(fname, FNR, "Class name " t " has consecutive uppercase letters");
        }
    }
}
s ~ / throws\>/ {
    if (s ~ /\(/) {
        funDeclStartLine = FNR;
    } else {
        funDeclStartLine = FNR - 1;
    }
}
length($0) > maxLineLength                      \
&& $0 !~ /@(throws|see|link)/                   \
&& $0 !~ /\$Id: /                               \
&& $0 !~ /^import /                             \
&& $0 !~ /http:/                                \
&& $0 !~ /\/\/ Expect "/                        \
&& s !~ /^ *(\+ |<< )?string\)?[;,]?$/ {
    error( \
        fname, \
        FNR, \
        "Line length (" length($0) ") exceeds " maxLineLength " chars");
}
/}$/ {
    previousLineEndedInCloseBrace = 2;
}
/;$/ {
    funDeclStartLine = 0;
}
/{$/ {
    # Ignore open brace if it is part of class or interface declaration.
    if (classDeclStartLine) {
        if (classDeclStartLine < FNR \
            && $0 !~ /^ *{$/)
        {
            error(fname, FNR, "Open brace should be on new line (class decl spans several lines)");
        }
        classDeclStartLine = 0;
    } else {
        previousLineEndedInOpenBrace = 2;
    }
    if (funDeclStartLine) {
        if (funDeclStartLine < FNR \
            && $0 !~ /^ *{$/)
        {
            if (lenient && fname ~ /aspen/) {} # not enabled
            else error(fname, FNR, "Open brace should be on new line (function decl spans several lines)");
        }
        funDeclStartLine = 0;
    }
}
/^$/ {
    previousLineWasEmpty = 2;
}
{
    next;
}
END {
    afterFile();
}

# End checkFile.awk