BUILD: properly escape multiline XML strings in intlfix-xml.awk

# GNU AWK Script to extract translatable strings from XML files
# Copyright (C) 2010 Tim Janik
#
# The original public domain XML parser was authored by at least:
# - getXMLEVENT.awk:	Copyright (C) 2007 Juergen Kahrs
# - getXML.awk:     	Copyright (C) 2005 Jan Weber
# - xmlparse.awk:   	Copyright (C) 2001 Steve Coile

BEGIN {
  MARKER = ""; if (ENVIRON["INTLFIX"] ~ /:marker:/) MARKER = "__INTLFIX__:";
  OLINE = 1
  TAG = ""
  while (getXMLEVENT(ARGV[1])) {
    while (OLINE < XMLROW) { print ""; OLINE++ }
    next_tag = ""
    if (XMLEVENT == "STARTELEM") {
      next_tag = XMLSTARTELEM
      printf ("%*s<%s>", 2 * XMLDEPTH - 2, "", XMLSTARTELEM)
      for (i = 1; i <= NF; i++) {
	if ($i ~ /^_[^_]/) {
	  aname = $i; avalue = XMLATTR[$i]
	  sub ("^_", "", aname); gsub ("[\\\"]", "\\" "\\&", avalue)
          printf ("%s /*%s=*/_(\"%s\"); ", MARKER, aname, avalue)
	}
      }
      #print ""
    } else if (XMLEVENT == "COMMENT") {
      gsub ("\\*/", "* /", XMLNAME)     # escape enclosed C-comment ends
      ostring = sprintf ("/* %s */\n", XMLNAME)
      printf ("__XML_LINE__%-3d: %s", OLINE, ostring); gsub (/[^\n]/, "", ostring); OLINE += length (ostring)
    } else if (XMLEVENT == "CHARDATA" && match (TAG, "^_[^_]")) {
      gsub ("[\\\"]", "\\" "\\&", XMLNAME) # escape string quotes
      gsub (/\n/, "\\n\"\n\"", XMLNAME)    # escape newlines
      sub ("^_", "", TAG)
      ostring = sprintf ("%s /*<%s/>*/_(\"%s\");\n", MARKER, TAG, XMLNAME);
      printf ("__XML_LINE__%-3d: %s", OLINE, ostring); gsub (/[^\n]/, "", ostring); OLINE += length (ostring)
    } else if (XMLEVENT == "ENDELEM") {
      printf ("</>")
    }
    TAG = next_tag
  }
}

##
# getXMLEVENT( file ): # read next xml-data into XMLEVENT,XMLNAME,XMLATTR
#                      # referenced entities are not resolved
# Parameters:
#   file       -- path to xml file
# External variables:
#   XMLEVENT   -- type of item read, e.g. "STARTELEM"(tag), "ENDELEM"(end tag),
#                 "COMMENT"(comment), "CHARDATA"(data)
#   XMLNAME    -- value of item, e.g. tagname if type is "STARTELEM" or "ENDELEM"
#   XMLATTR    -- Map of attributes, only set if XMLEVENT=="STARTELEM"
#   XMLPATH    -- Path to current tag, e.g. /TopLevelTag/SubTag1/SubTag2
#   XMLROW     -- current line number in input file
#   XMLERROR   -- error text, set on parse error
# Returns:
#    1         on successful read: XMLEVENT, XMLNAME, XMLATTR are set accordingly
#    ""        at end of file or parse error, XMLERROR is set on error
# Private Data:
#   _XMLIO     -- buffer, XMLROW, XMLPATH for open files
##

function getXMLEVENT( file            ,end,p,q,tag,att,accu,mline,mode,S0,ex,dtd) {
    XMLEVENT=XMLNAME=XMLERROR=XMLSTARTELEM=XMLENDELEM = ""
    split("", XMLATTR)
    S0    = _XMLIO[file,"S0"]
    XMLROW  = _XMLIO[file,"line"];
    XMLPATH = _XMLIO[file,"path"];
    XMLDEPTH=_XMLIO[file,"depth"]+0;
    dtd   = _XMLIO[file,"dtd"];
    while (!XMLEVENT) {
        if (S0 == "") {
            if (1 != (getline S0 < file))
                break;
             XMLROW ++;
             S0 = S0 RS;
        }
        if (mode == "") {
            mline = XMLROW
            accu=""
            p = substr(S0,1,1)
            if (p != "<" && !(dtd && p=="]"))
                mode="CHARDATA"
            else if (p == "]") {
                S0 = substr(S0,2)
                mode="ENDDOCT"
                end=">"
                dtd=0
            } else if ( substr(S0,1,4) == "<!--" ) {
                S0=substr(S0,5)
                mode="COMMENT"
                end="-->"
            } else if ( substr(S0,1,9) == "<!DOCTYPE" ) {
                S0 = substr(S0,10)
                mode = "STARTDOCT"
                end  = ">"
            } else if (substr(S0,1,9) == "<![CDATA[" ) {
                S0 = substr(S0,10)
                mode = "CDA"
                end = "]]>"
            } else if ( substr(S0,1,2) == "<!" ) {
                S0 = substr(S0,3)
                mode = "DEC"
                end = ">"
            } else if (substr(S0,1,2) == "<?") {
                S0 = substr(S0,3)
                mode = "PROCINST"
                end = "?>"
            } else if ( substr(S0,1,2)=="</" ) {
                S0 = substr(S0,3)
                mode = "ENDELEM"
                end = ">";
                tag = S0
                sub(/[ \n\r\t>].*$/,"",tag)
                S0 = substr(S0,length(tag)+1)
                ex = XMLPATH
                sub(/\/[^\/]*$/,"",XMLPATH)
                ex = substr(ex, length(XMLPATH)+2)
                if (tag != ex) {
                    XMLERROR = "unexpected close tag <" ex ">..</" tag ">"
                    break
                }
            } else {
                S0 = substr(S0,2)
                mode = "STARTELEM"
                tag = S0
                sub(/[ \n\r\t\/>].*$/,"",tag)
                S0 = substr(S0, length(tag)+1)
                if (tag !~ /^[A-Za-z:_][0-9A-Za-z:_.-]*$/ ) { # /^[[:alpha:]:_][[:alnum:]:_.-]*$/
                    XMLERROR = "invalid tag name '" tag "'"
                    break
                }
                XMLPATH = XMLPATH "/" tag;
            }
        } else if (mode == "CHARDATA") {                            # terminated by "<" or EOF
            p = index(S0, "<")
            if (dtd && (q=index(S0,"]")) && (!p || q<p) )
                p = q
            if (p) {
                XMLEVENT = "CHARDATA"
                XMLNAME = accu unescapeXML(substr(S0, 1, p-1))
                S0 = substr(S0, p)
                mode = ""
            } else {
                accu = accu unescapeXML(S0)
                S0 = ""
            }
        } else if ( mode == "STARTELEM" ) {
            sub(/^[ \n\r\t]*/,"",S0)
            if (S0 == "")
                continue
            if (substr(S0, 1, 2) == "/>" ) {
                S0 = substr(S0, 3)
                mode = ""
                XMLEVENT = "STARTELEM"
                XMLNAME = XMLSTARTELEM = tag
                XMLDEPTH ++
                S0 = "</" tag ">" S0
            } else if (substr(S0, 1, 1) == ">" ) {
                S0 = substr(S0, 2)
                mode = ""
                XMLEVENT = "STARTELEM"
                XMLNAME = XMLSTARTELEM = tag
                XMLDEPTH ++
            } else {
                att = S0
                sub(/[= \n\r\t\/>].*$/,"",att)
                S0 = substr(S0, length(att) + 1)
                mode = "ATTR"
                if (att !~ /^[A-Za-z:_][0-9A-Za-z:_.-]*$/ ) { # /^[[:alpha:]:_][[:alnum:]:_.-]*$/
                    XMLERROR = "invalid attribute name '" att "'"
                    break
                }
            }
        } else if (mode == "ATTR") {
            sub(/^[ \n\r\t]*/, "", S0)
            if (S0 == "")
                continue
            if (substr(S0,1,1) == "=" ) {
                S0 = substr(S0,2)
                mode = "EQ"
            } else {
                XMLATTR[att] = att
                mode = "STARTELEM"
            }
        } else if (mode == "EQ") {
            sub(/^[ \n\r\t]*/,"",S0)
            if (S0 == "")
              continue
            end = substr(S0,1,1)
            if (end == "\"" || end == "'") {
                S0 = substr(S0,2)
                accu = ""
                mode = "VALUE"
            } else {
                accu = S0
                sub(/[ \n\r\t\/>].*$/,"", accu)
                S0 = substr(S0, length(accu)+1)
                XMLATTR[att] = unescapeXML(accu)
                mode = "STARTELEM"
            }
        } else if (mode == "VALUE") {                          # terminated by end
            if (p = index(S0, end)) {
                XMLATTR[att] = accu unescapeXML(substr(S0,1,p-1))
                S0 = substr(S0, p+length(end))
                mode = "STARTELEM"
            } else {
                accu = accu unescapeXML(S0)
                S0=""
            }
        } else if (mode == "STARTDOCT") {                      # terminated by "[" or ">"
            if ((q = index(S0, "[")) && (!(p = index(S0,end)) || q<p )) {
                XMLEVENT = mode
                XMLNAME = accu substr(S0, 1, q-1)
                S0 = substr(S0, q+1)
                mode = ""
                dtd = 1
            } else if (p = index(S0,end)) {
                XMLEVENT = mode
                XMLNAME = accu substr(S0, 1, p-1)
                S0 = "]" substr(S0, p)
                mode = ""
                dtd = 1
            } else {
                accu = accu S0
                S0 = ""
            }
        } else if (p = index(S0,end)) {  # terminated by end
            XMLEVENT = mode
            XMLNAME = XMLENDELEM = ( mode=="ENDELEM" ? tag : accu substr(S0,1,p-1))
            if (mode=="ENDELEM") XMLDEPTH --
            S0 = substr(S0, p+length(end))
            mode = ""
        } else {
            accu = accu S0
            S0 = ""
        }
    }
    _XMLIO[file, "S0"]   = S0;
    _XMLIO[file, "line"] = XMLROW;
    _XMLIO[file, "path"] = XMLPATH;
    _XMLIO[file, "depth"] = XMLDEPTH;
    _XMLIO[file, "dtd"]  = dtd;
    if (mode == "CHARDATA") {
        mode = ""
        if (accu != "")
            XMLEVENT = "CHARDATA"
        XMLNAME = ""
        $0 = accu
    }
    if (XMLEVENT) {
        if (XMLEVENT == "STARTELEM") {
            # Copy attributes into $0.
            NF=0
            for (ex in XMLATTR) {
                NF ++
                $NF = ex
            }
        }
        return 1
    }
    close(file);
    delete _XMLIO[file, "S0"];
    delete _XMLIO[file, "line"];
    delete _XMLIO[file, "path"];
    delete _XMLIO[file, "depth"];
    delete _XMLIO[file, "dtd"];
    if (XMLERROR)
        XMLERROR = file ":" XMLROW": " XMLERROR
    else if (mode) XMLERROR=file ":" mline ": " "unterminated " mode
    else if (XMLPATH) XMLERROR=file ":" XMLROW": "  "unclosed tag(s) " XMLPATH
} # function getXMLEVENT

# unescape data and attribute values, used by getXMLEVENT
function unescapeXML(text) {
    gsub( "'", "'",  text )
    gsub( """, "\"", text )
    gsub( ">",   ">",  text )
    gsub( "<",   "<",  text )
    gsub( "&",  "\\&",  text)
    return text
}

# close xml file
function closeXMLEVENT(file) {
    close(file);
    delete _XMLIO[file,"S0"]
    delete _XMLIO[file,"line"]
    delete _XMLIO[file,"path"];
    delete _XMLIO[file,"depth"];
    delete _XMLIO[file,"dtd"]
    delete _XMLIO[file,"open"]
    delete _XMLIO[file,"IND"]
}

# GNU AWK Script to extract translatable strings from XML files
# Copyright (C) 2010 Tim Janik
#
# The original public domain XML parser was authored by at least:
# - getXMLEVENT.awk:	Copyright (C) 2007 Juergen Kahrs
# - getXML.awk:     	Copyright (C) 2005 Jan Weber
# - xmlparse.awk:   	Copyright (C) 2001 Steve Coile

BEGIN {
  MARKER = ""; if (ENVIRON["INTLFIX"] ~ /:marker:/) MARKER = "__INTLFIX__:";
  OLINE = 1
  TAG = ""
  while (getXMLEVENT(ARGV[1])) {
    while (OLINE < XMLROW) { print ""; OLINE++ }
    next_tag = ""
    if (XMLEVENT == "STARTELEM") {
      next_tag = XMLSTARTELEM
      printf ("%*s<%s>", 2 * XMLDEPTH - 2, "", XMLSTARTELEM)
      for (i = 1; i <= NF; i++) {
	if ($i ~ /^_[^_]/) {
	  aname = $i; avalue = XMLATTR[$i]
	  sub ("^_", "", aname); gsub ("[\\\"]", "\\" "\\&", avalue)
          printf ("%s /*%s=*/_(\"%s\"); ", MARKER, aname, avalue)
	}
      }
      #print ""
    } else if (XMLEVENT == "COMMENT") {
      gsub ("\\*/", "* /", XMLNAME)
      ostring = sprintf ("/* %s */\n", XMLNAME)
      printf ("__XML_LINE__%-3d: %s", OLINE, ostring); gsub (/[^\n]/, "", ostring); OLINE += length (ostring)
    } else if (XMLEVENT == "CHARDATA" && match (TAG, "^_[^_]")) {
      sub ("^_", "", TAG); gsub ("[\\\"]", "\\" "\\&", XMLNAME)
      ostring = sprintf ("%s /*<%s/>*/_(\"%s\");\n", MARKER, TAG, XMLNAME);
      printf ("__XML_LINE__%-3d: %s", OLINE, ostring); gsub (/[^\n]/, "", ostring); OLINE += length (ostring)
    } else if (XMLEVENT == "ENDELEM") {
      printf ("</>")
    }
    TAG = next_tag
  }
}

##
# getXMLEVENT( file ): # read next xml-data into XMLEVENT,XMLNAME,XMLATTR
#                      # referenced entities are not resolved
# Parameters:
#   file       -- path to xml file
# External variables:
#   XMLEVENT   -- type of item read, e.g. "STARTELEM"(tag), "ENDELEM"(end tag),
#                 "COMMENT"(comment), "CHARDATA"(data)
#   XMLNAME    -- value of item, e.g. tagname if type is "STARTELEM" or "ENDELEM"
#   XMLATTR    -- Map of attributes, only set if XMLEVENT=="STARTELEM"
#   XMLPATH    -- Path to current tag, e.g. /TopLevelTag/SubTag1/SubTag2
#   XMLROW     -- current line number in input file
#   XMLERROR   -- error text, set on parse error
# Returns:
#    1         on successful read: XMLEVENT, XMLNAME, XMLATTR are set accordingly
#    ""        at end of file or parse error, XMLERROR is set on error
# Private Data:
#   _XMLIO     -- buffer, XMLROW, XMLPATH for open files
##

function getXMLEVENT( file            ,end,p,q,tag,att,accu,mline,mode,S0,ex,dtd) {
    XMLEVENT=XMLNAME=XMLERROR=XMLSTARTELEM=XMLENDELEM = ""
    split("", XMLATTR)
    S0    = _XMLIO[file,"S0"]
    XMLROW  = _XMLIO[file,"line"];
    XMLPATH = _XMLIO[file,"path"];
    XMLDEPTH=_XMLIO[file,"depth"]+0;
    dtd   = _XMLIO[file,"dtd"];
    while (!XMLEVENT) {
        if (S0 == "") {
            if (1 != (getline S0 < file))
                break;
             XMLROW ++;
             S0 = S0 RS;
        }
        if (mode == "") {
            mline = XMLROW
            accu=""
            p = substr(S0,1,1)
            if (p != "<" && !(dtd && p=="]"))
                mode="CHARDATA"
            else if (p == "]") {
                S0 = substr(S0,2)
                mode="ENDDOCT"
                end=">"
                dtd=0
            } else if ( substr(S0,1,4) == "<!--" ) {
                S0=substr(S0,5)
                mode="COMMENT"
                end="-->"
            } else if ( substr(S0,1,9) == "<!DOCTYPE" ) {
                S0 = substr(S0,10)
                mode = "STARTDOCT"
                end  = ">"
            } else if (substr(S0,1,9) == "<![CDATA[" ) {
                S0 = substr(S0,10)
                mode = "CDA"
                end = "]]>"
            } else if ( substr(S0,1,2) == "<!" ) {
                S0 = substr(S0,3)
                mode = "DEC"
                end = ">"
            } else if (substr(S0,1,2) == "<?") {
                S0 = substr(S0,3)
                mode = "PROCINST"
                end = "?>"
            } else if ( substr(S0,1,2)=="</" ) {
                S0 = substr(S0,3)
                mode = "ENDELEM"
                end = ">";
                tag = S0
                sub(/[ \n\r\t>].*$/,"",tag)
                S0 = substr(S0,length(tag)+1)
                ex = XMLPATH
                sub(/\/[^\/]*$/,"",XMLPATH)
                ex = substr(ex, length(XMLPATH)+2)
                if (tag != ex) {
                    XMLERROR = "unexpected close tag <" ex ">..</" tag ">"
                    break
                }
            } else {
                S0 = substr(S0,2)
                mode = "STARTELEM"
                tag = S0
                sub(/[ \n\r\t\/>].*$/,"",tag)
                S0 = substr(S0, length(tag)+1)
                if (tag !~ /^[A-Za-z:_][0-9A-Za-z:_.-]*$/ ) { # /^[[:alpha:]:_][[:alnum:]:_.-]*$/
                    XMLERROR = "invalid tag name '" tag "'"
                    break
                }
                XMLPATH = XMLPATH "/" tag;
            }
        } else if (mode == "CHARDATA") {                            # terminated by "<" or EOF
            p = index(S0, "<")
            if (dtd && (q=index(S0,"]")) && (!p || q<p) )
                p = q
            if (p) {
                XMLEVENT = "CHARDATA"
                XMLNAME = accu unescapeXML(substr(S0, 1, p-1))
                S0 = substr(S0, p)
                mode = ""
            } else {
                accu = accu unescapeXML(S0)
                S0 = ""
            }
        } else if ( mode == "STARTELEM" ) {
            sub(/^[ \n\r\t]*/,"",S0)
            if (S0 == "")
                continue
            if (substr(S0, 1, 2) == "/>" ) {
                S0 = substr(S0, 3)
                mode = ""
                XMLEVENT = "STARTELEM"
                XMLNAME = XMLSTARTELEM = tag
                XMLDEPTH ++
                S0 = "</" tag ">" S0
            } else if (substr(S0, 1, 1) == ">" ) {
                S0 = substr(S0, 2)
                mode = ""
                XMLEVENT = "STARTELEM"
                XMLNAME = XMLSTARTELEM = tag
                XMLDEPTH ++
            } else {
                att = S0
                sub(/[= \n\r\t\/>].*$/,"",att)
                S0 = substr(S0, length(att) + 1)
                mode = "ATTR"
                if (att !~ /^[A-Za-z:_][0-9A-Za-z:_.-]*$/ ) { # /^[[:alpha:]:_][[:alnum:]:_.-]*$/
                    XMLERROR = "invalid attribute name '" att "'"
                    break
                }
            }
        } else if (mode == "ATTR") {
            sub(/^[ \n\r\t]*/, "", S0)
            if (S0 == "")
                continue
            if (substr(S0,1,1) == "=" ) {
                S0 = substr(S0,2)
                mode = "EQ"
            } else {
                XMLATTR[att] = att
                mode = "STARTELEM"
            }
        } else if (mode == "EQ") {
            sub(/^[ \n\r\t]*/,"",S0)
            if (S0 == "")
              continue
            end = substr(S0,1,1)
            if (end == "\"" || end == "'") {
                S0 = substr(S0,2)
                accu = ""
                mode = "VALUE"
            } else {
                accu = S0
                sub(/[ \n\r\t\/>].*$/,"", accu)
                S0 = substr(S0, length(accu)+1)
                XMLATTR[att] = unescapeXML(accu)
                mode = "STARTELEM"
            }
        } else if (mode == "VALUE") {                          # terminated by end
            if (p = index(S0, end)) {
                XMLATTR[att] = accu unescapeXML(substr(S0,1,p-1))
                S0 = substr(S0, p+length(end))
                mode = "STARTELEM"
            } else {
                accu = accu unescapeXML(S0)
                S0=""
            }
        } else if (mode == "STARTDOCT") {                      # terminated by "[" or ">"
            if ((q = index(S0, "[")) && (!(p = index(S0,end)) || q<p )) {
                XMLEVENT = mode
                XMLNAME = accu substr(S0, 1, q-1)
                S0 = substr(S0, q+1)
                mode = ""
                dtd = 1
            } else if (p = index(S0,end)) {
                XMLEVENT = mode
                XMLNAME = accu substr(S0, 1, p-1)
                S0 = "]" substr(S0, p)
                mode = ""
                dtd = 1
            } else {
                accu = accu S0
                S0 = ""
            }
        } else if (p = index(S0,end)) {  # terminated by end
            XMLEVENT = mode
            XMLNAME = XMLENDELEM = ( mode=="ENDELEM" ? tag : accu substr(S0,1,p-1))
            if (mode=="ENDELEM") XMLDEPTH --
            S0 = substr(S0, p+length(end))
            mode = ""
        } else {
            accu = accu S0
            S0 = ""
        }
    }
    _XMLIO[file, "S0"]   = S0;
    _XMLIO[file, "line"] = XMLROW;
    _XMLIO[file, "path"] = XMLPATH;
    _XMLIO[file, "depth"] = XMLDEPTH;
    _XMLIO[file, "dtd"]  = dtd;
    if (mode == "CHARDATA") {
        mode = ""
        if (accu != "")
            XMLEVENT = "CHARDATA"
        XMLNAME = ""
        $0 = accu
    }
    if (XMLEVENT) {
        if (XMLEVENT == "STARTELEM") {
            # Copy attributes into $0.
            NF=0
            for (ex in XMLATTR) {
                NF ++
                $NF = ex
            }
        }
        return 1
    }
    close(file);
    delete _XMLIO[file, "S0"];
    delete _XMLIO[file, "line"];
    delete _XMLIO[file, "path"];
    delete _XMLIO[file, "depth"];
    delete _XMLIO[file, "dtd"];
    if (XMLERROR)
        XMLERROR = file ":" XMLROW": " XMLERROR
    else if (mode) XMLERROR=file ":" mline ": " "unterminated " mode
    else if (XMLPATH) XMLERROR=file ":" XMLROW": "  "unclosed tag(s) " XMLPATH
} # function getXMLEVENT

# unescape data and attribute values, used by getXMLEVENT
function unescapeXML(text) {
    gsub( "'", "'",  text )
    gsub( """, "\"", text )
    gsub( ">",   ">",  text )
    gsub( "<",   "<",  text )
    gsub( "&",  "\\&",  text)
    return text
}

# close xml file
function closeXMLEVENT(file) {
    close(file);
    delete _XMLIO[file,"S0"]
    delete _XMLIO[file,"line"]
    delete _XMLIO[file,"path"];
    delete _XMLIO[file,"depth"];
    delete _XMLIO[file,"dtd"]
    delete _XMLIO[file,"open"]
    delete _XMLIO[file,"IND"]
}


HvD: Added a line to further reduce the number of duplicate lines being printed.

#
# This script is used by the makefile in building util_version.f.
# It expects on standard input lines containing
#
# a CVS ID    -> produce a write statement with the ID ("Exp" at the end)
# a SVN ID    -> produce a write statement with the ID (no "Exp" at the end)
# module name -> produce a write statement for the module name
# prev_str    -> skip to eliminate duplicates
# other       -> produce nothing
#
# $Id$

BEGIN {
		underline = "---------------------------------------------------------";
		printf("      subroutine util_version\n");
                printf("#include \"global.fh\"\n");
                printf("      if (ga_nodeid().eq.0) then\n");
		printf("      write(6,*)\n");
		printf("      write(6,*) ' Software version information'\n");
		printf("      write(6,*) ' ----------------------------'\n");
		prev_str = underline;
}

/^module/			{
				 len = length($0);
				 printf("      write(6,*)\n");
				 printf("      write(6,*) ' %s'\n",$0);
				 printf("      write(6,*) ' %s'\n",substr(underline,1,len));
				 prev_str = underline;
				}

0 != match($0,prev_str)		{
				  $0="";
				}

/\$\I\d: [^\n]*Exp \$/		{
				 i = index($0, "$Id: ") + 5;
				 j = index($0, "Exp $") - 1;
				 n = j - i + 1;
				 if (n > (72 - 20)) n = 72 - 20;
				 printf("      write(6,*) ' %s'\n",substr($0,i,n));
				 prev_str = substr($0,i,n);
				 $0=""
				}

/\$\I\d: [^\n]* \$/		{
				 i = index($0, "$Id: ") + 5;
				 n = index(substr($0,i), " $") - 1;
				 if (n > (72 - 20)) n = 72 - 20;
				 printf("      write(6,*) ' %s'\n",substr($0,i,n));
				 prev_str = substr($0,i,n);
				}

END {
		printf("      write(6,*)\n");
		printf("      call util_flush(6)\n");
                printf("      endif\n");
                printf("      call ga_sync()\n");
		printf("      end\n");
}

#
# This script is used by the makefile in building util_version.f.
# It expects on standard input lines containing
#
# a CVS ID    -> produce a write statement with the ID ("Exp" at the end)
# a SVN ID    -> produce a write statement with the ID (no "Exp" at the end)
# module name -> produce a write statement for the module name
# prev_str    -> skip to eliminate duplicates
# other       -> produce nothing
#
# $Id$

BEGIN {
		underline = "---------------------------------------------------------";
		printf("      subroutine util_version\n");
                printf("#include \"global.fh\"\n");
                printf("      if (ga_nodeid().eq.0) then\n");
		printf("      write(6,*)\n");
		printf("      write(6,*) ' Software version information'\n");
		printf("      write(6,*) ' ----------------------------'\n");
		prev_str = underline;
}

/^module/			{
				 len = length($0);
				 printf("      write(6,*)\n");
				 printf("      write(6,*) ' %s'\n",$0);
				 printf("      write(6,*) ' %s'\n",substr(underline,1,len));
				}

0 != match($0,prev_str)		{
				  $0="";
				}

/\$\I\d: [^\n]*Exp \$/		{
				 i = index($0, "$Id: ") + 5;
				 j = index($0, "Exp $") - 1;
				 n = j - i + 1;
				 if (n > (72 - 20)) n = 72 - 20;
				 printf("      write(6,*) ' %s'\n",substr($0,i,n));
				 prev_str = substr($0,i,n);
				}

/\$\I\d: [^\n]* \$/		{
				 i = index($0, "$Id: ") + 5;
				 n = index(substr($0,i), " $") - 1;
				 if (n > (72 - 20)) n = 72 - 20;
				 printf("      write(6,*) ' %s'\n",substr($0,i,n));
				 prev_str = substr($0,i,n);
				}

END {
		printf("      write(6,*)\n");
		printf("      call util_flush(6)\n");
                printf("      endif\n");
                printf("      call ga_sync()\n");
		printf("      end\n");
}


Translate: preserve line breaks in non-interactive brief mode, fix #164

####################################################################
# Translate.awk                                                    #
####################################################################

function provides(engineName) {
    Translator[tolower(engineName)] = TRUE
}

function engineMethod(methodName,    engine, translator) {
    if (!Translator[Option["engine"]]) {
        # case-insensitive match engine name
        engine = tolower(Option["engine"])
        if (!Translator[engine]) # fuzzy match engine name
            for (translator in Translator)
                if (Translator[translator] && # there IS such a translator
                    translator ~ "^"engine) {
                    engine = translator
                    break
                }
        if (!Translator[engine]) {
            e("[ERROR] Translator not found: " Option["engine"] "\n"    \
              "        Run '-list-engines / -S' to see a list of available engines.")
            exit 1
        }
        Option["engine"] = engine
    }
    return Option["engine"] methodName
}

# Detect external audio player (mplayer, mpv, mpg123).
function initAudioPlayer() {
    AudioPlayer = !system("mplayer" SUPOUT SUPERR) ?
        "mplayer" :
        (!system("mpv" SUPOUT SUPERR) ?
         "mpv" :
         (!system("mpg123 --version" SUPOUT SUPERR) ?
          "mpg123" :
          ""))
}

# Detect external speech synthesizer (say, espeak).
function initSpeechSynthesizer() {
    SpeechSynthesizer = !system("say ''" SUPOUT SUPERR) ?
        "say" :
        (!system("espeak ''" SUPOUT SUPERR) ?
         "espeak" :
         "")
}

# Detect external terminal pager (less, more, most).
function initPager() {
    Pager = !system("less -V" SUPOUT SUPERR) ?
        "less" :
        (!system("more -V" SUPOUT SUPERR) ?
         "more" :
         (!system("most" SUPOUT SUPERR) ?
          "most" :
          ""))
}

# Initialize `HttpService`.
function initHttpService() {
    _Init()

    if (Option["proxy"]) {
        match(Option["proxy"], /^(http:\/*)?([^\/]*):([^\/:]*)/, HttpProxySpec)
        HttpService = "/inet/tcp/0/" HttpProxySpec[2] "/" HttpProxySpec[3]
        HttpPathPrefix = HttpProtocol HttpHost
    } else {
        HttpService = "/inet/tcp/0/" HttpHost "/" HttpPort
        HttpPathPrefix = ""
    }
}

# Pre-process string (URL-encode before send).
function preprocess(text) {
    return quote(text)
}

# Post-process string (remove any redundant whitespace).
function postprocess(text) {
    text = gensub(/ ([.,;:?!"])/, "\\1", "g", text)
    text = gensub(/(["]) /, "\\1", "g", text)
    return text
}

# Send an HTTP GET request and get response from an online translator.
function getResponse(text, sl, tl, hl,    content, header, isBody, url) {
    url = _RequestUrl(text, sl, tl, hl)

    header = "GET " url " HTTP/1.1\n"           \
        "Host: " HttpHost "\n"                  \
        "Connection: close\n"
    if (Option["user-agent"])
        header = header "User-Agent: " Option["user-agent"] "\n"
    if (Cookie)
        header = header "Cookie: " Cookie "\n"

    content = NULLSTR; isBody = 0
    print header |& HttpService
    while ((HttpService |& getline) > 0) {
        if (isBody)
            content = content ? content "\n" $0 : $0
        else if (length($0) <= 1)
            isBody = 1
        l(sprintf("%4s bytes > %s", length($0), $0))
    }
    close(HttpService)

    return assert(content, "[ERROR] Null response.")
}

# Print a string (to output file or terminal pager).
function p(string) {
    if (Option["view"])
        print string | Option["pager"]
    else
        print string > Option["output"]
}

# Play using a Text-to-Speech engine.
function play(text, tl,    url) {
    url = _TTSUrl(text, tl)

    # Don't use getline from pipe here - the same pipe will be run only once for each AWK script!
    system(Option["player"] " " parameterize(url) SUPOUT SUPERR)
}

# Get the translation of a string.
function getTranslation(text, sl, tl, hl,
                        isVerbose, toSpeech, returnPlaylist, returnIl) {
    return _Translate(text, sl, tl, hl,
                      isVerbose, toSpeech, returnPlaylist, returnIl)
}

# Translate a file.
function fileTranslation(uri,    group, temp1, temp2) {
    temp1 = Option["input"]
    temp2 = Option["verbose"]

    match(uri, /^file:\/\/(.*)/, group)
    Option["input"] = group[1]
    Option["verbose"] = 0

    translateMain()

    Option["input"] = temp1
    Option["verbose"] = temp2
}

# Start a browser session and translate a web page.
function webTranslation(uri, sl, tl, hl) {
    system(Option["browser"] " "                                \
           parameterize(_WebTranslateUrl(uri, sl, tl, hl)) "&")
}

# Translate the source text (into all target languages).
function translate(text, inline,
                   ####
                   i, j, playlist, il, saveSortedIn) {

    if (!getCode(Option["hl"])) {
        # Check if home language is supported
        w("[WARNING] Unknown language code: " Option["hl"] ", fallback to English: en")
        Option["hl"] = "en" # fallback to English
    } else if (isRTL(Option["hl"])) {
        # Check if home language is R-to-L
        if (!FriBidi)
            w("[WARNING] " getName(Option["hl"]) " is a right-to-left language, but FriBidi is not found.")
    }

    if (!getCode(Option["sl"])) {
        # Check if source language is supported
        w("[WARNING] Unknown source language code: " Option["sl"])
    } else if (isRTL(Option["sl"])) {
        # Check if source language is R-to-L
        if (!FriBidi)
            w("[WARNING] " getName(Option["sl"]) " is a right-to-left language, but FriBidi is not found.")
    }

    saveSortedIn = PROCINFO["sorted_in"]
    PROCINFO["sorted_in"] = "@ind_num_asc"
    for (i in Option["tl"]) {
        # Non-interactive verbose mode: separator between targets
        if (!Option["interactive"])
            if (Option["verbose"] && i > 1)
                p(prettify("target-seperator", replicate(Option["chr-target-seperator"], Option["width"])))

        if (inline &&
            startsWithAny(text, UriSchemes) == "file://") {
            # translate URL only from command-line parameters (inline)
            fileTranslation(text)
        } else if (inline &&
                   startsWithAny(text, UriSchemes) == "http://" ||
                   startsWithAny(text, UriSchemes) == "https://") {
            # translate URL only from command-line parameters (inline)
            webTranslation(text, Option["sl"], Option["tl"][i], Option["hl"])
        } else {
            p(getTranslation(text, Option["sl"], Option["tl"][i], Option["hl"], Option["verbose"], Option["play"], playlist, il))

            if (Option["play"] == 1) {
                if (Option["player"])
                    for (j in playlist)
                        play(playlist[j]["text"], playlist[j]["tl"])
                else if (SpeechSynthesizer)
                    for (j in playlist)
                        print playlist[j]["text"] | SpeechSynthesizer
            } else if (Option["play"] == 2) {
                if (Option["player"])
                    play(text, il[0])
                else if (SpeechSynthesizer)
                    print text | SpeechSynthesizer
            }
        }
    }
    PROCINFO["sorted_in"] = saveSortedIn
}

# Read from input and translate each line.
function translateMain(    i, line) {
    if (Option["interactive"])
        prompt()

    if (Option["input"] == STDIN || fileExists(Option["input"])) {
        i = 0
        while (getline line < Option["input"])
            if (line) {
                # Non-interactive verbose mode: separator between sources
                if (!Option["interactive"])
                    if (Option["verbose"] && i++ > 0)
                        p(prettify("source-seperator",
                                   replicate(Option["chr-source-seperator"],
                                             Option["width"])))

                if (Option["interactive"])
                    repl(line)
                else
                    translate(line)
            } else {
                # Non-interactive brief mode: preserve line breaks
                if (!Option["interactive"])
                    if (!Option["verbose"])
                        p(line)
            }
    } else
        e("[ERROR] File not found: " Option["input"])
}

####################################################################
# Translate.awk                                                    #
####################################################################

function provides(engineName) {
    Translator[tolower(engineName)] = TRUE
}

function engineMethod(methodName,    engine, translator) {
    if (!Translator[Option["engine"]]) {
        # case-insensitive match engine name
        engine = tolower(Option["engine"])
        if (!Translator[engine]) # fuzzy match engine name
            for (translator in Translator)
                if (Translator[translator] && # there IS such a translator
                    translator ~ "^"engine) {
                    engine = translator
                    break
                }
        if (!Translator[engine]) {
            e("[ERROR] Translator not found: " Option["engine"] "\n"    \
              "        Run '-list-engines / -S' to see a list of available engines.")
            exit 1
        }
        Option["engine"] = engine
    }
    return Option["engine"] methodName
}

# Detect external audio player (mplayer, mpv, mpg123).
function initAudioPlayer() {
    AudioPlayer = !system("mplayer" SUPOUT SUPERR) ?
        "mplayer" :
        (!system("mpv" SUPOUT SUPERR) ?
         "mpv" :
         (!system("mpg123 --version" SUPOUT SUPERR) ?
          "mpg123" :
          ""))
}

# Detect external speech synthesizer (say, espeak).
function initSpeechSynthesizer() {
    SpeechSynthesizer = !system("say ''" SUPOUT SUPERR) ?
        "say" :
        (!system("espeak ''" SUPOUT SUPERR) ?
         "espeak" :
         "")
}

# Detect external terminal pager (less, more, most).
function initPager() {
    Pager = !system("less -V" SUPOUT SUPERR) ?
        "less" :
        (!system("more -V" SUPOUT SUPERR) ?
         "more" :
         (!system("most" SUPOUT SUPERR) ?
          "most" :
          ""))
}

# Initialize `HttpService`.
function initHttpService() {
    _Init()

    if (Option["proxy"]) {
        match(Option["proxy"], /^(http:\/*)?([^\/]*):([^\/:]*)/, HttpProxySpec)
        HttpService = "/inet/tcp/0/" HttpProxySpec[2] "/" HttpProxySpec[3]
        HttpPathPrefix = HttpProtocol HttpHost
    } else {
        HttpService = "/inet/tcp/0/" HttpHost "/" HttpPort
        HttpPathPrefix = ""
    }
}

# Pre-process string (URL-encode before send).
function preprocess(text) {
    return quote(text)
}

# Post-process string (remove any redundant whitespace).
function postprocess(text) {
    text = gensub(/ ([.,;:?!"])/, "\\1", "g", text)
    text = gensub(/(["]) /, "\\1", "g", text)
    return text
}

# Send an HTTP GET request and get response from an online translator.
function getResponse(text, sl, tl, hl,    content, header, isBody, url) {
    url = _RequestUrl(text, sl, tl, hl)

    header = "GET " url " HTTP/1.1\n"           \
        "Host: " HttpHost "\n"                  \
        "Connection: close\n"
    if (Option["user-agent"])
        header = header "User-Agent: " Option["user-agent"] "\n"
    if (Cookie)
        header = header "Cookie: " Cookie "\n"

    content = NULLSTR; isBody = 0
    print header |& HttpService
    while ((HttpService |& getline) > 0) {
        if (isBody)
            content = content ? content "\n" $0 : $0
        else if (length($0) <= 1)
            isBody = 1
        l(sprintf("%4s bytes > %s", length($0), $0))
    }
    close(HttpService)

    return assert(content, "[ERROR] Null response.")
}

# Print a string (to output file or terminal pager).
function p(string) {
    if (Option["view"])
        print string | Option["pager"]
    else
        print string > Option["output"]
}

# Play using a Text-to-Speech engine.
function play(text, tl,    url) {
    url = _TTSUrl(text, tl)

    # Don't use getline from pipe here - the same pipe will be run only once for each AWK script!
    system(Option["player"] " " parameterize(url) SUPOUT SUPERR)
}

# Get the translation of a string.
function getTranslation(text, sl, tl, hl,
                        isVerbose, toSpeech, returnPlaylist, returnIl) {
    return _Translate(text, sl, tl, hl,
                      isVerbose, toSpeech, returnPlaylist, returnIl)
}

# Translate a file.
function fileTranslation(uri,    group, temp1, temp2) {
    temp1 = Option["input"]
    temp2 = Option["verbose"]

    match(uri, /^file:\/\/(.*)/, group)
    Option["input"] = group[1]
    Option["verbose"] = 0

    translateMain()

    Option["input"] = temp1
    Option["verbose"] = temp2
}

# Start a browser session and translate a web page.
function webTranslation(uri, sl, tl, hl) {
    system(Option["browser"] " "                                \
           parameterize(_WebTranslateUrl(uri, sl, tl, hl)) "&")
}

# Translate the source text (into all target languages).
function translate(text, inline,
                   ####
                   i, j, playlist, il, saveSortedIn) {

    if (!getCode(Option["hl"])) {
        # Check if home language is supported
        w("[WARNING] Unknown language code: " Option["hl"] ", fallback to English: en")
        Option["hl"] = "en" # fallback to English
    } else if (isRTL(Option["hl"])) {
        # Check if home language is R-to-L
        if (!FriBidi)
            w("[WARNING] " getName(Option["hl"]) " is a right-to-left language, but FriBidi is not found.")
    }

    if (!getCode(Option["sl"])) {
        # Check if source language is supported
        w("[WARNING] Unknown source language code: " Option["sl"])
    } else if (isRTL(Option["sl"])) {
        # Check if source language is R-to-L
        if (!FriBidi)
            w("[WARNING] " getName(Option["sl"]) " is a right-to-left language, but FriBidi is not found.")
    }

    saveSortedIn = PROCINFO["sorted_in"]
    PROCINFO["sorted_in"] = "@ind_num_asc"
    for (i in Option["tl"]) {
        # Non-interactive verbose mode: separator between targets
        if (!Option["interactive"])
            if (Option["verbose"] && i > 1)
                p(prettify("target-seperator", replicate(Option["chr-target-seperator"], Option["width"])))

        if (inline &&
            startsWithAny(text, UriSchemes) == "file://") {
            # translate URL only from command-line parameters (inline)
            fileTranslation(text)
        } else if (inline &&
                   startsWithAny(text, UriSchemes) == "http://" ||
                   startsWithAny(text, UriSchemes) == "https://") {
            # translate URL only from command-line parameters (inline)
            webTranslation(text, Option["sl"], Option["tl"][i], Option["hl"])
        } else {
            p(getTranslation(text, Option["sl"], Option["tl"][i], Option["hl"], Option["verbose"], Option["play"], playlist, il))

            if (Option["play"] == 1) {
                if (Option["player"])
                    for (j in playlist)
                        play(playlist[j]["text"], playlist[j]["tl"])
                else if (SpeechSynthesizer)
                    for (j in playlist)
                        print playlist[j]["text"] | SpeechSynthesizer
            } else if (Option["play"] == 2) {
                if (Option["player"])
                    play(text, il[0])
                else if (SpeechSynthesizer)
                    print text | SpeechSynthesizer
            }
        }
    }
    PROCINFO["sorted_in"] = saveSortedIn
}

# Read from input and translate each line.
function translateMain(    i, line) {
    if (Option["interactive"])
        prompt()

    if (Option["input"] == STDIN || fileExists(Option["input"])) {
        i = 0
        while (getline line < Option["input"])
            if (line) {
                # Non-interactive verbose mode: separator between sources
                if (!Option["interactive"])
                    if (Option["verbose"] && i++ > 0)
                        p(prettify("source-seperator",
                                   replicate(Option["chr-source-seperator"],
                                             Option["width"])))

                if (Option["interactive"])
                    repl(line)
                else
                    translate(line)
            }
    } else
        e("[ERROR] File not found: " Option["input"])
}


Trivial change.

Trivial change.

BEGIN {
	version = "0.1.3"
	printf("link-parser, v%s\n", version)

	true = 1
	false = 0
	g_pat_srccommon = "\\/sw\\/vobs.+"
	g_sroot = ""
	inst_root = ""
	tar_root = ""

	# Colors
	RED="\033[0;31m"
	GRN="\033[0;32m"
	BLU="\033[0;34m"
	NC="\033[0m"
	BOL="\033[1m"

	count = 0

	if (arg ~ /ut/) {
		do_test();
		SKIP_END = true
		exit 0
	}

	if (arg ~ /pretend/) {
		pretend = true
	}

	if (arg ~ /dest=/) {
		destdir = gensub(/^.+dest=([^ \t;,]+)/, "\\1", "g", arg)
		if (system("test -d " destdir) != 0) {
			print "Directory [" destdir "] doesn't exist!"
			SKIP_END = true
			exit 0
		}
	}

	# Skip head lines
	getline
}

/^[^-]/ {
	link = $0
	# Remove the ending ' \'
	gsub(/[ \t]+\\$/, "", link)

	if (g_sroot == "") {
		printf("Grabbing sources and headers ...\n")
		g_sroot = get_sroot(link)
		"date +%Y%m%d-%H%M%S" | getline date
		inst_root = g_sroot"-"date
	}

	# ---------
	# Sources
	# ---------

	cmd_file = link".cmd"
	while ((getline line < cmd_file) > 0) {
		# abosolute path
		if (line ~ /[ \t]+-c[ \t]+\/.+$/)
			source = gensub(/^.+[ \t]+-c[ \t]+(.+)$/, "\\1", "g", line)
		else # relative path
			source = gensub(/^cd (.+);.+-c (.+)/, "\\1/\\2", "g", line)

		rel_install(source, g_sroot, inst_root)
	}
	close(cmd_file)

	# ---------
	# Headers
	# ---------

	gsub(/\.(obj|o|lib)$/, "", link)
	dep_file = link ".D"

	if ((getline < dep_file) < 0)
		next

	# Get path prefix
	cur_path = $1
	gsub(/^#/, "", cur_path)

	while ((getline < dep_file) > 0) {
		gsub(/:/, " ")
		for (i = 1; i <= NF; i++) {
			if ($i !~ /\.(h|hpp)$/)
				continue
			header = cur_path "/" $i
			rel_install(header, g_sroot, inst_root)
		}
	}
	close(dep_file)
}

END {
	if (SKIP_END)
		exit 0
	print "Total dumped files: " count
	if (arg ~ /tar/)
		make_tarball(arg)
}

function rel_install(source, rel_root, new_root) {
	count++

	dir = dirname(source)
	base = basename(source)
	patt = "^.+\\/"rel_root"\\/(sw.+)"
	rel_path = gensub(patt, "\\1", "g", dir)

	if (destdir)
		new_root = destdir "/" new_root

	if (pretend) {
		print source > rel_root".source.list"
		print rel_path"/"base > rel_root".relative.list"
		return 0
	}

	system("mkdir -p " new_root "/" rel_path)
	system("cp -L " source " " new_root "/" rel_path)
}

function make_tarball(flag) {
	tarball = inst_root ".tar.gz"
	printf("Generating source tarball %s%s%s%s ...\n", RED, BOL, tarball, NC)

	if (pretend || SKIP_END)
		exit 0

	"pwd" |getline oldpwd
	if (destdir)
		system("cd " destdir)

	system("tar zcf " tarball " " inst_root)
	if (flag ~ /purge/)
		system("rm -r "inst_root)

	if (destdir)
		system("cd " oldpwd)
}

function get_sroot(line) {
	if (line !~ g_pat_srccommon)
		return ""

	gsub(g_pat_srccommon, "", line);
	gsub(/.+\//, "", line);
	return line;
}

# dirname
function dirname(path) {
	if (path == "")
		return ""

	# Remove the ending '/'
	gsub(/\/+$/, "", path)

	# Remove basename
	gsub(/[^\/]+$/, "", path)
	if (path == "") return "."

	# Remove the ending '/' again
	gsub(/\/+$/, "", path)
	if (path == "")
		return "/"

	return path
}

# basename
function basename(path) {
	if (path == "")
		return ""

	# Remove the ending '/'
	gsub(/\/+$/, "", path)
	if (path == "")
		return "/"

	path = gensub(/^.+\/+(.+)$/, "\\1", "g", path)

	return path;
}

###### Test #########
function do_test() {
	printf("%s%s%s%s\n", BLU, BOL, "---- Test get_sroot ----", NC)
	test_get_sroot("/repo/lulinw/isam/sw/vobs/esam/build");
	test_get_sroot("/repo/lulinw/isam/sw/esam/build");

	printf("%s%s%s%s\n", BLU, BOL, "---- Test dirname ----", NC)
	test_dirname("/home/abc");
	test_dirname("home///abc//");
	test_dirname("home///abc/de//");
	test_dirname("../abc//");
	test_dirname("abc");
	test_dirname("..");
	test_dirname("..///");

	printf("%s%s%s%s\n", BLU, BOL, "---- Test basename ----", NC)
	test_basename("/home//abc")
	test_basename("///")
	test_basename("/")
	test_basename("..")
	test_basename("../abc//")
	test_basename("/../abc//")
	test_basename("/../abc/efg/")
}

function test_get_sroot(line) {
	if ((ret = get_sroot(line)) == "") {
		printf("Unexpected line [PAT=%s]:\n  %s%s%s\n",
				g_pat_srccommon, RED, line, NC)
	} else
		printf("Root of %s is\n  %s%s%s\n", line, GRN, get_sroot(line), NC)
}

function test_dirname(path) {
	printf("dirname(%s) is \n  %s%s%s\n", path, GRN, dirname(path), NC)
}

function test_basename(path) {
	printf("basename(%s) is \n %s%s%s\n", path, GRN, basename(path), NC)
}

BEGIN {
	version = "0.1.3"
	printf("link-parser, v%s\n", version)

	true = 1
	false = 0
	g_pat_srccommon = "\\/sw\\/vobs.+"
	g_sroot = ""
	inst_root = ""
	tar_root = ""

	# Colors
	RED="\033[0;31m"
	GRN="\033[0;32m"
	BLU="\033[0;34m"
	NC="\033[0m"
	BOL="\033[1m"

	count = 0

	if (arg ~ /ut/) {
		do_test();
		SKIP_END = true
		exit 0
	}

	if (arg ~ /pretend/) {
		pretend = true
	}

	if (arg ~ /dest=/) {
		destdir = gensub(/^.+dest=([^ \t;,]+)/, "\\1", "g", arg)
		if (system("test -d " destdir) != 0) {
			print "Directory [" destdir "] doesn't exist!"
			SKIP_END = true
			exit 0
		}
	}

	# Skip head lines
	getline
}

/^[^-]/ {
	link = $0
	# Remove the ending ' \'
	gsub(/[ \t]+\\$/, "", link)

	if (g_sroot == "") {
		printf("Grabbing sources and headers ...\n")
		g_sroot = get_sroot(link)
		"date +%Y%m%d-%H%M%S" | getline date
		inst_root = g_sroot"-"date
	}

	# ---------
	# Sources
	# ---------

	cmd_file = link".cmd"
	while ((getline line < cmd_file) > 0) {
		# abosolute path
		if (line ~ /[ \t]+-c[ \t]+\/.+$/)
			source = gensub(/^.+[ \t]+-c[ \t]+(.+)$/, "\\1", "g", line)
		else # relative path
			source = gensub(/^cd (.+);.+-c (.+)/, "\\1/\\2", "g", line)

		rel_install(source, g_sroot, inst_root)
	}
	close(cmd_file)

	# ---------
	# Headers
	# ---------

	gsub(/\.(obj|o|lib)$/, "", link)
	dep_file = link ".D"

	if ((getline < dep_file) < 0)
		next

	# Get path prefix
	cur_path = $1
	gsub(/^#/, "", cur_path)

	while ((getline < dep_file) > 0) {
		gsub(/:/, " ")
		for (i = 1; i <= NF; i++) {
			if ($i !~ /\.(h|hpp)$/)
				continue
			header = cur_path "/" $i
			rel_install(header, g_sroot, inst_root)
		}
	}
	close(dep_file)
}

END {
	if (SKIP_END)
		exit 0
	print "Total dumped files: " count
	if (arg ~ /tar/)
		make_tarball(arg)
}

function rel_install(source, rel_root, new_root) {
	count++
	if (pretend)
		return 0

	dir = dirname(source)
	base = basename(source)
	patt = "^.+\\/"rel_root"\\/(sw.+)"
	rel_path = gensub(patt, "\\1", "g", dir)

	if (destdir)
		new_root = destdir "/" new_root

	system("mkdir -p " new_root "/" rel_path)
	system("cp -L " source " " new_root "/" rel_path)
}

function make_tarball(flag) {
	tarball = inst_root ".tar.gz"
	printf("Generating source tarball %s%s%s%s ...\n", RED, BOL, tarball, NC)

	if (pretend || SKIP_END)
		exit 0

	"pwd" |getline oldpwd
	if (destdir)
		system("cd " destdir)

	system("tar zcf " tarball " " inst_root)
	if (flag ~ /purge/)
		system("rm -r "inst_root)

	if (destdir)
		system("cd " oldpwd)
}

function get_sroot(line) {
	if (line !~ g_pat_srccommon)
		return ""

	gsub(g_pat_srccommon, "", line);
	gsub(/.+\//, "", line);
	return line;
}

# dirname
function dirname(path) {
	if (path == "")
		return ""

	# Remove the ending '/'
	gsub(/\/+$/, "", path)

	# Remove basename
	gsub(/[^\/]+$/, "", path)
	if (path == "") return "."

	# Remove the ending '/' again
	gsub(/\/+$/, "", path)
	if (path == "")
		return "/"

	return path
}

# basename
function basename(path) {
	if (path == "")
		return ""

	# Remove the ending '/'
	gsub(/\/+$/, "", path)
	if (path == "")
		return "/"

	path = gensub(/^.+\/+(.+)$/, "\\1", "g", path)

	return path;
}

###### Test #########
function do_test() {
	printf("%s%s%s%s\n", BLU, BOL, "---- Test get_sroot ----", NC)
	test_get_sroot("/repo/lulinw/isam/sw/vobs/esam/build");
	test_get_sroot("/repo/lulinw/isam/sw/esam/build");

	printf("%s%s%s%s\n", BLU, BOL, "---- Test dirname ----", NC)
	test_dirname("/home/abc");
	test_dirname("home///abc//");
	test_dirname("home///abc/de//");
	test_dirname("../abc//");
	test_dirname("abc");
	test_dirname("..");
	test_dirname("..///");

	printf("%s%s%s%s\n", BLU, BOL, "---- Test basename ----", NC)
	test_basename("/home//abc")
	test_basename("///")
	test_basename("/")
	test_basename("..")
	test_basename("../abc//")
	test_basename("/../abc//")
	test_basename("/../abc/efg/")
}

function test_get_sroot(line) {
	if ((ret = get_sroot(line)) == "") {
		printf("Unexpected line [PAT=%s]:\n  %s%s%s\n",
				g_pat_srccommon, RED, line, NC)
	} else
		printf("Root of %s is\n  %s%s%s\n", line, GRN, get_sroot(line), NC)
}

function test_dirname(path) {
	printf("dirname(%s) is \n  %s%s%s\n", path, GRN, dirname(path), NC)
}

function test_basename(path) {
	printf("basename(%s) is \n %s%s%s\n", path, GRN, basename(path), NC)
}


Clean up "No genre/country" message in MHz

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

/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: " seriesTitle " " seriesURL >> 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:
#       <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
#

/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: " 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
}


publish-home would miss new logos - CTR

publish-home would miss new logos - CTR

# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.

#
# @author Daniel Kuppitz (http://gremlin.guru)
#
/^Files / {
  print "U " gensub("^[^/]*/", "", "g", $2)
}

/^Only in (images|css|js)/ {
  print "D " gensub(/:$/, "", "g", gensub("^[^/]*/", "", "g", $3)) "/" $4
}

/^Only in \.\.\/site\/home/ {
  print "A " gensub(/:$/, "", "g", gensub("^../site/home/", "", "g", $3)) "/" $4
  next
}

/^Only in \.\./ {
  print "A " gensub(/:$/, "", "g", gensub("^[^/]*/[^/]*/", "", "g", $3)) "/" $4
}

# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.

#
# @author Daniel Kuppitz (http://gremlin.guru)
#
/^Files / {
  print "U " gensub("^[^/]*/", "", "g", $2)
}

/^Only in (images|css|js)/ {
  print "D " gensub(/:$/, "", "g", gensub("^[^/]*/", "", "g", $3)) "/" $4
}

/^Only in \.\./ {
  print "A " gensub(/:$/, "", "g", gensub("^[^/]*/[^/]*/", "", "g", $3)) "/" $4
}


Help: update

Help: update

####################################################################
# Help.awk                                                         #
####################################################################

# Return version as a string.
function getVersion(    build, gitHead, platform) {
    initAudioPlayer()
    initPager()
    platform = detectProgram("uname", "-o", 1)
    if (ENVIRON["TRANS_BUILD"])
        build = "-" ENVIRON["TRANS_BUILD"]
    else {
        gitHead = getGitHead()
        build = gitHead ? "-git:" gitHead : ""
    }

    return ansi("bold", sprintf("%-22s%s%s\n\n", Name, Version, build)) \
        sprintf("%-22s%s\n", "platform", platform)                      \
        sprintf("%-22s%s\n", "gawk (GNU Awk)", PROCINFO["version"])     \
        sprintf("%s\n", FriBidi ? FriBidi :
                "fribidi (GNU FriBidi) [NOT INSTALLED]")                \
        sprintf("%-22s%s\n", "audio player", AudioPlayer ? AudioPlayer :
                "[NOT INSTALLED]")                                      \
        sprintf("%-22s%s\n", "terminal pager", Pager ? Pager :
                "[NOT INSTALLED]")                                      \
        sprintf("%-22s%s\n", "terminal type", ENVIRON["TERM"])          \
        sprintf("%-22s%s (%s)\n", "user locale", UserLocale, getName(UserLang)) \
        sprintf("%-22s%s\n", "home language", Option["hl"])             \
        sprintf("%-22s%s\n", "source language", Option["sl"])           \
        sprintf("%-22s%s\n", "target language", join(Option["tl"], "+")) \
        sprintf("%-22s%s\n", "theme", Option["theme"])                  \
        sprintf("%-22s%s\n", "init file", InitScript ? InitScript : "[NONE]") \
        sprintf("\n%-22s%s", "Report bugs to:", "https://github.com/soimort/translate-shell/issues")
}

# Return help message as a string.
function getHelp() {
    return "Usage:  " ansi("bold", Command)                             \
        " [" ansi("underline", "OPTIONS") "]"                           \
        " [" ansi("underline", "SOURCE") "]"                            \
        ":[" ansi("underline", "TARGETS") "]"                           \
        " [" ansi("underline", "TEXT") "]..." RS                        \
        RS "Information options:" RS                                    \
        ins(1, ansi("bold", "-V") ", " ansi("bold", "-version")) RS     \
        ins(2, "Print version and exit.") RS                            \
        ins(1, ansi("bold", "-H") ", " ansi("bold", "-help")) RS        \
        ins(2, "Print help message and exit.") RS                       \
        ins(1, ansi("bold", "-M") ", " ansi("bold", "-man")) RS         \
        ins(2, "Show man page and exit.") RS                            \
        ins(1, ansi("bold", "-T") ", " ansi("bold", "-reference")) RS   \
        ins(2, "Print reference table of languages and exit.") RS       \
        ins(1, ansi("bold", "-R") ", " ansi("bold", "-reference-english")) RS \
        ins(2, "Print reference table of languages (in English names) and exit.") RS \
        ins(1, ansi("bold", "-L ") ansi("underline", "CODES")           \
            ", " ansi("bold", "-list ") ansi("underline", "CODES")) RS  \
        ins(2, "Print details of languages and exit.") RS               \
        ins(1, ansi("bold", "-U") ", " ansi("bold", "-upgrade")) RS     \
        ins(2, "Check for upgrade of this program.") RS                 \
        RS "Display options:" RS                                        \
        ins(1, ansi("bold", "-verbose")) RS                             \
        ins(2, "Verbose mode. (default)") RS                            \
        ins(1, ansi("bold", "-b") ", " ansi("bold", "-brief")) RS       \
        ins(2, "Brief mode.") RS                                        \
        ins(1, ansi("bold", "-d") ", " ansi("bold", "-dictionary")) RS  \
        ins(2, "Dictionary mode.") RS                                   \
        ins(1, ansi("bold", "-show-original ") ansi("underline", "Y/n")) RS \
        ins(2, "Show original text or not.") RS                         \
        ins(1, ansi("bold", "-show-original-phonetics ") ansi("underline", "Y/n")) RS \
        ins(2, "Show phonetic notation of original text or not.") RS    \
        ins(1, ansi("bold", "-show-translation ") ansi("underline", "Y/n")) RS \
        ins(2, "Show translation or not.") RS                           \
        ins(1, ansi("bold", "-show-translation-phonetics ") ansi("underline", "Y/n")) RS \
        ins(2, "Show phonetic notation of translation or not.") RS      \
        ins(1, ansi("bold", "-show-prompt-message ") ansi("underline", "Y/n")) RS \
        ins(2, "Show prompt message or not.") RS                        \
        ins(1, ansi("bold", "-show-languages ") ansi("underline", "Y/n")) RS \
        ins(2, "Show source and target languages or not.") RS           \
        ins(1, ansi("bold", "-show-original-dictionary ") ansi("underline", "y/N")) RS \
        ins(2, "Show dictionary entry of original text or not.") RS     \
        ins(1, ansi("bold", "-show-dictionary ") ansi("underline", "Y/n")) RS \
        ins(2, "Show dictionary entry of translation or not.") RS       \
        ins(1, ansi("bold", "-show-alternatives ") ansi("underline", "Y/n")) RS \
        ins(2, "Show alternative translations or not.") RS              \
        ins(1, ansi("bold", "-w ") ansi("underline", "NUM")             \
            ", " ansi("bold", "-width ") ansi("underline", "NUM")) RS   \
        ins(2, "Specify the screen width for padding.") RS              \
        ins(1, ansi("bold", "-indent ") ansi("underline", "NUM")) RS    \
        ins(2, "Specify the size of indent (number of spaces).") RS     \
        ins(1, ansi("bold", "-theme ") ansi("underline", "FILENAME")) RS \
        ins(2, "Specify the theme to use.") RS                          \
        ins(1, ansi("bold", "-no-theme")) RS                            \
        ins(2, "Do not use any other theme than default.") RS           \
        ins(1, ansi("bold", "-no-ansi")) RS                             \
        ins(2, "Do not use ANSI escape codes.") RS                      \
        RS "Audio options:" RS                                          \
        ins(1, ansi("bold", "-p, -play")) RS                            \
        ins(2, "Listen to the translation.") RS                         \
        ins(1, ansi("bold", "-player ") ansi("underline", "PROGRAM")) RS \
        ins(2, "Specify the audio player to use, and listen to the translation.") RS \
        ins(1, ansi("bold", "-no-play")) RS                             \
        ins(2, "Do not listen to the translation.") RS                  \
        RS "Terminal paging and browsing options:" RS                   \
        ins(1, ansi("bold", "-v") ", " ansi("bold", "-view")) RS        \
        ins(2, "View the translation in a terminal pager.") RS          \
        ins(1, ansi("bold", "-pager ") ansi("underline", "PROGRAM")) RS \
        ins(2, "Specify the terminal pager to use, and view the translation.") RS \
        ins(1, ansi("bold", "-no-view")) RS                             \
        ins(2, "Do not view the translation in a terminal pager.") RS   \
        ins(1, ansi("bold", "-browser ") ansi("underline", "PROGRAM")) RS \
        ins(2, "Specify the web browser to use.") RS                    \
        RS "Networking options:" RS                                     \
        ins(1, ansi("bold", "-x ") ansi("underline", "HOST:PORT")       \
            ", " ansi("bold", "-proxy ") ansi("underline", "HOST:PORT")) RS \
        ins(2, "Use HTTP proxy on given port.") RS                      \
        ins(1, ansi("bold", "-u ") ansi("underline", "STRING")          \
            ", " ansi("bold", "-user-agent ") ansi("underline", "STRING")) RS \
        ins(2, "Specify the User-Agent to identify as.") RS             \
        RS "Interactive shell options:" RS                              \
        ins(1, ansi("bold", "-I") ", " ansi("bold", "-interactive") ", " ansi("bold", "-shell")) RS \
        ins(2, "Start an interactive shell.") RS                        \
        ins(1, ansi("bold", "-E") ", " ansi("bold", "-emacs")) RS       \
        ins(2, "Start the GNU Emacs front-end for an interactive shell.") RS \
        ins(1, ansi("bold", "-no-rlwrap")) RS                           \
        ins(2, "Do not invoke rlwrap when starting an interactive shell.") RS \
        RS "I/O options:" RS                                            \
        ins(1, ansi("bold", "-i ") ansi("underline", "FILENAME")        \
            ", " ansi("bold", "-input ") ansi("underline", "FILENAME")) RS \
        ins(2, "Specify the input file.") RS                            \
        ins(1, ansi("bold", "-o ") ansi("underline", "FILENAME")        \
            ", " ansi("bold", "-output ") ansi("underline", "FILENAME")) RS \
        ins(2, "Specify the output file.") RS                           \
        RS "Language preference options:" RS                            \
        ins(1, ansi("bold", "-l ") ansi("underline", "CODE")            \
            ", " ansi("bold", "-hl ") ansi("underline", "CODE")         \
            ", " ansi("bold", "-lang ") ansi("underline", "CODE")) RS   \
        ins(2, "Specify your home language.") RS                        \
        ins(1, ansi("bold", "-s ") ansi("underline", "CODE")            \
            ", " ansi("bold", "-sl ") ansi("underline", "CODE")         \
            ", " ansi("bold", "-source ") ansi("underline", "CODE")) RS \
        ins(2, "Specify the source language.") RS                       \
        ins(1, ansi("bold", "-t ") ansi("underline", "CODES")           \
            ", " ansi("bold", "-tl ") ansi("underline", "CODE")         \
            ", " ansi("bold", "-target ") ansi("underline", "CODES")) RS \
        ins(2, "Specify the target language(s), joined by '+'.") RS     \
        RS "Other options:" RS                                          \
        ins(1, ansi("bold", "-no-init")) RS                             \
        ins(2, "Do not load any initialization script.") RS             \
        RS "See the man page " Command "(1) for more information."
}

# Show man page.
function showMan(    temp) {
    if (ENVIRON["TRANS_MANPAGE"]) {
        initPager()
        Groff = detectProgram("groff", "--version")
        if (Pager && Groff) {
            temp = "echo -E \"${TRANS_MANPAGE}\""
            temp = temp PIPE                                            \
                Groff " -Wall -mtty-char -mandoc -Tutf8 "               \
                "-rLL=" Option["width"] "n -rLT=" Option["width"] "n"
            switch (Pager) {
            case "less":
                temp = temp PIPE                                        \
                    Pager " -s -P\"\\ \\Manual page " Command "(1) line %lt (press h for help or q to quit)\""
                break
            case "most":
                temp = temp PIPE Pager " -Cs"
                break
            default: # more
                temp = temp PIPE Pager
            }
            system(temp)
            return
        }
    }
    if (fileExists("man/" Command ".1"))
        system("man man/" Command ".1" SUPERR)
    else if (system("man " Command SUPERR))
        print getHelp()
}

# Return a reference table of languages as a string.
# Parameters:
#     displayName = "endonym" or "name"
function getReference(displayName,
                      ####
                      code, col, cols, i, j, name, r, rows, saveSortedIn, t, tt) {
    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"] = displayName == "endonym" ? "@ind_num_asc" :
        "compName"
    for (code in Locale) {
        col = int(i / rows)
        append(cols[col], code)
        i++
    }
    PROCINFO["sorted_in"] = saveSortedIn

    if (displayName == "endonym") {
        r = "┌" replicate("─", 22) "┬" replicate("─", 23) "┬" replicate("─", 23) "┐" RS
        for (i = 0; i < rows; i++) {
            r = r "│ "
            for (j = 0; j < 3; j++) {
                if (cols[j][i]) {
                    t = cols[j][i] == "bo" ||
                        cols[j][i] == "dz" ||
                        cols[j][i] == "he" ||
                        cols[j][i] == "hi" ||
                        cols[j][i] == "hu" ||
                        cols[j][i] == "la" ||
                        cols[j][i] == "ml" ||
                        cols[j][i] == "mn" ||
                        cols[j][i] == "ne" ||
                        cols[j][i] == "ny" ||
                        cols[j][i] == "pa" ||
                        cols[j][i] == "pl" ||
                        cols[j][i] == "ro" ||
                        cols[j][i] == "sr" ||
                        cols[j][i] == "te" ||
                        cols[j][i] == "tg" ||
                        cols[j][i] == "tr" ||
                        cols[j][i] == "ur" ||
                        cols[j][i] == "wo" ||
                        cols[j][i] == "yi" ||
                        cols[j][i] == "yo" ||
                        (cols[j][i] != "zh-CN" && cols[j][i] != "zh-TW" &&
                         length(getEndonym(cols[j][i])) < 6) ? "\t\t " :
                        cols[j][i] == "id" ? "" :
                        "\t "
                    tt = length(cols[j][i]) == 3 ? " │" :
                        (cols[j][i] != "zh-CN" && cols[j][i] != "zh-TW") ? "  │" : ""
                    r = r getDisplay(cols[j][i]) t "- " ansi("bold", cols[j][i]) tt " "
                } else
                    r = r "\t\t       │"
            }
            r = r RS
        }
        r = r "└" replicate("─", 22) "┴" replicate("─", 23) "┴" replicate("─", 23) "┘"
    } else {
        r = "┌" replicate("─", 22) "┬" replicate("─", 23) "┬" replicate("─", 23) "┐" RS
        for (i = 0; i < rows; i++) {
            r = r "│ "
            for (j = 0; j < 3; j++) {
                if (cols[j][i]) {
                    t = cols[j][i] == "he" ||
                        cols[j][i] == "kk" ||
                        cols[j][i] == "ko" ||
                        cols[j][i] == "ky" ||
                        cols[j][i] == "ne" ||
                        cols[j][i] == "pl" ||
                        cols[j][i] == "ps" ||
                        cols[j][i] == "sd" ||
                        cols[j][i] == "sk" ||
                        cols[j][i] == "sm" ||
                        cols[j][i] == "so" ||
                        cols[j][i] == "te" ||
                        cols[j][i] == "ug" ||
                        cols[j][i] == "yo" ||
                        (cols[j][i] != "zh-CN" && cols[j][i] != "zh-TW" &&
                         length(getName(cols[j][i])) < 6) ? "\t\t " :
                        cols[j][i] == "fy" ||
                        cols[j][i] == "gd" ||
                        cols[j][i] == "ht" ? " " :
                        "\t "
                    tt = length(cols[j][i]) == 3 ? " │" :
                        (cols[j][i] != "zh-CN" && cols[j][i] != "zh-TW") ? "  │" : ""
                    name = getName(cols[j][i])
                    if (cols[j][i] == "zh-CN" ||
                        cols[j][i] == "zh-TW")
                        name = substr(name, 1, 12) "."
                    r = r name t "- " ansi("bold", cols[j][i]) tt " "
                } else
                    r = r "\t\t       │"
            }
            r = r RS
        }
        r = r "└" replicate("─", 22) "┴" replicate("─", 23) "┴" replicate("─", 23) "┘"
    }
    return r
}

# Return detailed information of languages as a string.
function getList(codes,    code, i, r, saveSortedIn) {
    r = NULLSTR
    if (!isarray(codes))
        r = getDetails(codes)
    else if (anything(codes)) {
        saveSortedIn = PROCINFO["sorted_in"]
        PROCINFO["sorted_in"] = "@ind_num_asc"
        for (i in codes)
            r = (r ? r RS prettify("target-seperator", replicate(Option["chr-target-seperator"], Option["width"])) RS \
                 : r) getDetails(codes[i])
        PROCINFO["sorted_in"] = saveSortedIn
    } else
        r = getDetails(Option["hl"])
    return r
}

####################################################################
# Help.awk                                                         #
####################################################################

# Return version as a string.
function getVersion(    build, gitHead, platform) {
    initAudioPlayer()
    initPager()
    platform = detectProgram("uname", "-o", 1)
    if (ENVIRON["TRANS_BUILD"])
        build = "-" ENVIRON["TRANS_BUILD"]
    else {
        gitHead = getGitHead()
        build = gitHead ? "-git:" gitHead : ""
    }

    return ansi("bold", sprintf("%-22s%s%s\n\n", Name, Version, build)) \
        sprintf("%-22s%s\n", "platform", platform)                      \
        sprintf("%-22s%s\n", "gawk (GNU Awk)", PROCINFO["version"])     \
        sprintf("%s\n", FriBidi ? FriBidi :
                "fribidi (GNU FriBidi) [NOT INSTALLED]")                \
        sprintf("%-22s%s\n", "audio player", AudioPlayer ? AudioPlayer :
                "[NOT INSTALLED]")                                      \
        sprintf("%-22s%s\n", "terminal pager", Pager ? Pager :
                "[NOT INSTALLED]")                                      \
        sprintf("%-22s%s\n", "terminal type", ENVIRON["TERM"])          \
        sprintf("%-22s%s (%s)\n", "user locale", UserLocale, getName(UserLang)) \
        sprintf("%-22s%s\n", "home language", Option["hl"])             \
        sprintf("%-22s%s\n", "source language", Option["sl"])           \
        sprintf("%-22s%s\n", "target language", join(Option["tl"], "+")) \
        sprintf("%-22s%s\n", "theme", Option["theme"])                  \
        sprintf("%-22s%s\n", "init file", InitScript ? InitScript : "[NONE]") \
        sprintf("\n%-22s%s", "Report bugs to:", "https://github.com/soimort/translate-shell/issues")
}

# Return help message as a string.
function getHelp() {
    return "Usage:  " ansi("bold", Command)                             \
        " [" ansi("underline", "OPTIONS") "]"                           \
        " [" ansi("underline", "SOURCE") "]"                            \
        ":[" ansi("underline", "TARGETS") "]"                           \
        " [" ansi("underline", "TEXT") "]..." RS                        \
        RS "Information options:" RS                                    \
        ins(1, ansi("bold", "-V") ", " ansi("bold", "-version")) RS     \
        ins(2, "Print version and exit.") RS                            \
        ins(1, ansi("bold", "-H") ", " ansi("bold", "-help")) RS        \
        ins(2, "Print help message and exit.") RS                       \
        ins(1, ansi("bold", "-M") ", " ansi("bold", "-man")) RS         \
        ins(2, "Show man page and exit.") RS                            \
        ins(1, ansi("bold", "-T") ", " ansi("bold", "-reference")) RS   \
        ins(2, "Print reference table of languages and exit.") RS       \
        ins(1, ansi("bold", "-R") ", " ansi("bold", "-reference-english")) RS \
        ins(2, "Print reference table of languages (in English names) and exit.") RS \
        ins(1, ansi("bold", "-L ") ansi("underline", "CODES")           \
            ", " ansi("bold", "-list ") ansi("underline", "CODES")) RS  \
        ins(2, "Print details of languages and exit.") RS               \
        ins(1, ansi("bold", "-U") ", " ansi("bold", "-upgrade")) RS     \
        ins(2, "Check for upgrade of this program.") RS                 \
        RS "Display options:" RS                                        \
        ins(1, ansi("bold", "-verbose")) RS                             \
        ins(2, "Verbose mode. (default)") RS                            \
        ins(1, ansi("bold", "-b") ", " ansi("bold", "-brief")) RS       \
        ins(2, "Brief mode.") RS                                        \
        ins(1, ansi("bold", "-d") ", " ansi("bold", "-dictionary")) RS  \
        ins(2, "Dictionary mode.") RS                                   \
        ins(1, ansi("bold", "-show-original ") ansi("underline", "Y/n")) RS \
        ins(2, "Show original text or not.") RS                         \
        ins(1, ansi("bold", "-show-original-phonetics ") ansi("underline", "Y/n")) RS \
        ins(2, "Show phonetic notation of original text or not.") RS    \
        ins(1, ansi("bold", "-show-translation ") ansi("underline", "Y/n")) RS \
        ins(2, "Show translation or not.") RS                           \
        ins(1, ansi("bold", "-show-translation-phonetics ") ansi("underline", "Y/n")) RS \
        ins(2, "Show phonetic notation of translation or not.") RS      \
        ins(1, ansi("bold", "-show-prompt-message ") ansi("underline", "Y/n")) RS \
        ins(2, "Show prompt message or not.") RS                        \
        ins(1, ansi("bold", "-show-languages ") ansi("underline", "Y/n")) RS \
        ins(2, "Show source and target languages or not.") RS           \
        ins(1, ansi("bold", "-show-original-dictionary ") ansi("underline", "y/N")) RS \
        ins(2, "Show dictionary entry of original text or not.") RS     \
        ins(1, ansi("bold", "-show-dictionary ") ansi("underline", "Y/n")) RS \
        ins(2, "Show dictionary entry of translation or not.") RS       \
        ins(1, ansi("bold", "-show-alternatives ") ansi("underline", "Y/n")) RS \
        ins(2, "Show alternative translations or not.") RS              \
        ins(1, ansi("bold", "-w ") ansi("underline", "NUM")             \
            ", " ansi("bold", "-width ") ansi("underline", "NUM")) RS   \
        ins(2, "Specify the screen width for padding.") RS              \
        ins(1, ansi("bold", "-indent ") ansi("underline", "NUM")) RS    \
        ins(2, "Specify the size of indent (number of spaces).") RS     \
        ins(1, ansi("bold", "-theme ") ansi("underline", "FILENAME")) RS \
        ins(2, "Specify the theme to use.") RS                          \
        ins(1, ansi("bold", "-no-theme")) RS                            \
        ins(2, "Do not use any other theme than default.") RS           \
        ins(1, ansi("bold", "-no-ansi")) RS                             \
        ins(2, "Do not use ANSI escape codes.") RS                      \
        RS "Audio options:" RS                                          \
        ins(1, ansi("bold", "-p, -play")) RS                            \
        ins(2, "Listen to the translation.") RS                         \
        ins(1, ansi("bold", "-player ") ansi("underline", "PROGRAM")) RS \
        ins(2, "Specify the audio player to use, and listen to the translation.") RS \
        ins(1, ansi("bold", "-no-play")) RS                             \
        ins(2, "Do not listen to the translation.") RS                  \
        RS "Terminal paging and browsing options:" RS                   \
        ins(1, ansi("bold", "-v") ", " ansi("bold", "-view")) RS        \
        ins(2, "View the translation in a terminal pager.") RS          \
        ins(1, ansi("bold", "-pager ") ansi("underline", "PROGRAM")) RS \
        ins(2, "Specify the terminal pager to use, and view the translation.") RS \
        ins(1, ansi("bold", "-no-view")) RS                             \
        ins(2, "Do not view the translation in a terminal pager.") RS   \
        ins(1, ansi("bold", "-browser ") ansi("underline", "PROGRAM")) RS \
        ins(2, "Specify the web browser to use.") RS                    \
        RS "Networking options:" RS                                     \
        ins(1, ansi("bold", "-x ") ansi("underline", "HOST:PORT")       \
            ", " ansi("bold", "-proxy ") ansi("underline", "HOST:PORT")) RS \
        ins(2, "Use HTTP proxy on given port.") RS                      \
        ins(1, ansi("bold", "-u ") ansi("underline", "STRING")          \
            ", " ansi("bold", "-user-agent ") ansi("underline", "STRING")) RS \
        ins(2, "Specify the User-Agent to identify as.") RS             \
        RS "Interactive shell options:" RS                              \
        ins(1, ansi("bold", "-I") ", " ansi("bold", "-interactive") ", " ansi("bold", "-shell")) RS \
        ins(2, "Start an interactive shell.") RS                        \
        ins(1, ansi("bold", "-E") ", " ansi("bold", "-emacs")) RS       \
        ins(2, "Start the GNU Emacs front-end for an interactive shell.") RS \
        ins(1, ansi("bold", "-no-rlwrap")) RS                           \
        ins(2, "Do not invoke rlwrap when starting an interactive shell.") RS \
        RS "I/O options:" RS                                            \
        ins(1, ansi("bold", "-i ") ansi("underline", "FILENAME")        \
            ", " ansi("bold", "-input ") ansi("underline", "FILENAME")) RS \
        ins(2, "Specify the input file.") RS                            \
        ins(1, ansi("bold", "-o ") ansi("underline", "FILENAME")        \
            ", " ansi("bold", "-output ") ansi("underline", "FILENAME")) RS \
        ins(2, "Specify the output file.") RS                           \
        RS "Language preference options:" RS                            \
        ins(1, ansi("bold", "-l ") ansi("underline", "CODE")            \
            ", " ansi("bold", "-lang ") ansi("underline", "CODE")) RS   \
        ins(2, "Specify your home language.") RS                        \
        ins(1, ansi("bold", "-s ") ansi("underline", "CODE")            \
            ", " ansi("bold", "-source ") ansi("underline", "CODE")) RS \
        ins(2, "Specify the source language.") RS                       \
        ins(1, ansi("bold", "-t ") ansi("underline", "CODES")           \
            ", " ansi("bold", "-target ") ansi("underline", "CODES")) RS \
        ins(2, "Specify the target language(s), joined by '+'.") RS     \
        RS "Other options:" RS                                          \
        ins(1, ansi("bold", "-no-init")) RS                             \
        ins(2, "Do not load any initialization script.") RS             \
        RS "See the man page " Command "(1) for more information."
}

# Show man page.
function showMan(    temp) {
    if (ENVIRON["TRANS_MANPAGE"]) {
        initPager()
        Groff = detectProgram("groff", "--version")
        if (Pager && Groff) {
            temp = "echo -E \"${TRANS_MANPAGE}\""
            temp = temp PIPE                                            \
                Groff " -Wall -mtty-char -mandoc -Tutf8 "               \
                "-rLL=" Option["width"] "n -rLT=" Option["width"] "n"
            switch (Pager) {
            case "less":
                temp = temp PIPE                                        \
                    Pager " -s -P\"\\ \\Manual page " Command "(1) line %lt (press h for help or q to quit)\""
                break
            case "most":
                temp = temp PIPE Pager " -Cs"
                break
            default: # more
                temp = temp PIPE Pager
            }
            system(temp)
            return
        }
    }
    if (fileExists("man/" Command ".1"))
        system("man man/" Command ".1" SUPERR)
    else if (system("man " Command SUPERR))
        print getHelp()
}

# Return a reference table of languages as a string.
# Parameters:
#     displayName = "endonym" or "name"
function getReference(displayName,
                      ####
                      code, col, cols, i, j, name, r, rows, saveSortedIn, t, tt) {
    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"] = displayName == "endonym" ? "@ind_num_asc" :
        "compName"
    for (code in Locale) {
        col = int(i / rows)
        append(cols[col], code)
        i++
    }
    PROCINFO["sorted_in"] = saveSortedIn

    if (displayName == "endonym") {
        r = "┌" replicate("─", 22) "┬" replicate("─", 23) "┬" replicate("─", 23) "┐" RS
        for (i = 0; i < rows; i++) {
            r = r "│ "
            for (j = 0; j < 3; j++) {
                if (cols[j][i]) {
                    t = cols[j][i] == "bo" ||
                        cols[j][i] == "dz" ||
                        cols[j][i] == "he" ||
                        cols[j][i] == "hi" ||
                        cols[j][i] == "hu" ||
                        cols[j][i] == "la" ||
                        cols[j][i] == "ml" ||
                        cols[j][i] == "mn" ||
                        cols[j][i] == "ne" ||
                        cols[j][i] == "ny" ||
                        cols[j][i] == "pa" ||
                        cols[j][i] == "pl" ||
                        cols[j][i] == "ro" ||
                        cols[j][i] == "sr" ||
                        cols[j][i] == "te" ||
                        cols[j][i] == "tg" ||
                        cols[j][i] == "tr" ||
                        cols[j][i] == "ur" ||
                        cols[j][i] == "wo" ||
                        cols[j][i] == "yi" ||
                        cols[j][i] == "yo" ||
                        (cols[j][i] != "zh-CN" && cols[j][i] != "zh-TW" &&
                         length(getEndonym(cols[j][i])) < 6) ? "\t\t " :
                        cols[j][i] == "id" ? "" :
                        "\t "
                    tt = length(cols[j][i]) == 3 ? " │" :
                        (cols[j][i] != "zh-CN" && cols[j][i] != "zh-TW") ? "  │" : ""
                    r = r getDisplay(cols[j][i]) t "- " ansi("bold", cols[j][i]) tt " "
                } else
                    r = r "\t\t       │"
            }
            r = r RS
        }
        r = r "└" replicate("─", 22) "┴" replicate("─", 23) "┴" replicate("─", 23) "┘"
    } else {
        r = "┌" replicate("─", 22) "┬" replicate("─", 23) "┬" replicate("─", 23) "┐" RS
        for (i = 0; i < rows; i++) {
            r = r "│ "
            for (j = 0; j < 3; j++) {
                if (cols[j][i]) {
                    t = cols[j][i] == "he" ||
                        cols[j][i] == "kk" ||
                        cols[j][i] == "ko" ||
                        cols[j][i] == "ky" ||
                        cols[j][i] == "ne" ||
                        cols[j][i] == "pl" ||
                        cols[j][i] == "ps" ||
                        cols[j][i] == "sd" ||
                        cols[j][i] == "sk" ||
                        cols[j][i] == "sm" ||
                        cols[j][i] == "so" ||
                        cols[j][i] == "te" ||
                        cols[j][i] == "ug" ||
                        cols[j][i] == "yo" ||
                        (cols[j][i] != "zh-CN" && cols[j][i] != "zh-TW" &&
                         length(getName(cols[j][i])) < 6) ? "\t\t " :
                        cols[j][i] == "fy" ||
                        cols[j][i] == "gd" ||
                        cols[j][i] == "ht" ? " " :
                        "\t "
                    tt = length(cols[j][i]) == 3 ? " │" :
                        (cols[j][i] != "zh-CN" && cols[j][i] != "zh-TW") ? "  │" : ""
                    name = getName(cols[j][i])
                    if (cols[j][i] == "zh-CN" ||
                        cols[j][i] == "zh-TW")
                        name = substr(name, 1, 12) "."
                    r = r name t "- " ansi("bold", cols[j][i]) tt " "
                } else
                    r = r "\t\t       │"
            }
            r = r RS
        }
        r = r "└" replicate("─", 22) "┴" replicate("─", 23) "┴" replicate("─", 23) "┘"
    }
    return r
}

# Return detailed information of languages as a string.
function getList(codes,    code, i, r, saveSortedIn) {
    r = NULLSTR
    if (!isarray(codes))
        r = getDetails(codes)
    else if (anything(codes)) {
        saveSortedIn = PROCINFO["sorted_in"]
        PROCINFO["sorted_in"] = "@ind_num_asc"
        for (i in codes)
            r = (r ? r RS prettify("target-seperator", replicate(Option["chr-target-seperator"], Option["width"])) RS \
                 : r) getDetails(codes[i])
        PROCINFO["sorted_in"] = saveSortedIn
    } else
        r = getDetails(Option["hl"])
    return r
}