#! /usr/bin/gawk -f # # Copyright (C) 2013, 2014, 2015, 2016, 2019 Arnold David Robbins # # This file is part of TexiWeb Jr., a literate programming system. # # TexiWeb Jr. is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # TexiWeb Jr. is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA # # Up-to-date source code for TexiWeb Jr. can be obtained via # Git from github: # # git clone http://github.com/arnoldrobbins/texiwebjr # BEGIN { v[0] = 0 if ("version" in PROCINFO) { match(PROCINFO["version"], /^[0-9]+\./, v); } if (v[0] < 4) { print("gawk >= 4.0 required") > "/dev/stderr" exit 63 # for "missing" script } } BEGIN { TRUE = 1 FALSE = 0 File_chunk_pattern = "^@\\(([^)]+)@\\)[[:space:]]*=[[:space:]]*$" Code_chunk_pattern = "^@" "<(.+)" "@>[[:space:]]*=[[:space:]]*$" Chunk_name_pattern = "@<[^>\n]+@>" } # Error checking: # Use brackets to avoid triggering the warning on ourselves! /(^<[@])|(>[@]([[:space:]]*=[[:space:]]*)?$)/ { # Ditto, with concatenation warning("<" "@ or >" "@ used instead of @" "< or @" ">\n\t%s\n", $0) } END { check_unfinished() } BEGIN { if (ARGC < 2) fatal(_"usage: jrweave file.twjr [...]\n") Pass = 1 n = ARGC ARGV[ARGC++] = "Pass=2" for (i = 1; i < n; i++) { if (ARGV[i] == "-" || ARGV[i] == "/dev/stdin") fatal(_"jrweave: standard input not allowed\n") ARGV[ARGC++] = ARGV[i] } } Pass == 2 && FNR == 1 && Debug ~ /pass2/ { junk++ } BEGIN { print_do_not_edit(ARGV[1]) } # print_do_not_edit --- create and print warning function print_do_not_edit(filename, i, pl, pr, l, s, t) { t = _"DO NOT EDIT THIS FILE!!!!" if (ARGC > 4) # more than one file s = sprintf(_"It was created by jrweave from `%s' (or maybe others).", filename) else s = sprintf(_"It was created by jrweave from `%s'.", filename) l = length(s) pl = int((l - length(t)) / 2) # padding on left side, integer division! pr = l - (pl + length(t)) # padding on right side if ((pl + pr + length(t)) < l) # account for odd lengths pr++ for (i = 1; i <= l + 4; i++) printf("%%") printf "\n" # print the titles with their padding printf("%% %*s%s%*s %%\n", pl, " ", t, pr, " ") printf("%% %s %%\n", s) for (i = 1; i <= l + 4; i++) printf("%%") printf "\n" } # check_unfinished --- print a fatal error when an unfinished code or # file chunk is detected. Also ifweave / iftangle. function check_unfinished() { if (Flags["file chunk"]) fatal(_"unfinished file chunk (started at %s)\n", Line_numbers["file chunk"]) else if (Flags["code chunk"]) fatal(_"unfinished code chunk (started at %s)\n", Line_numbers["code chunk"]) if ("ifweave" in Line_numbers) fatal(_"unfinished @ifweave section (started at %s)\n", Line_numbers["ifweave"]) if ("iftangle" in Line_numbers) fatal(_"unfinished @iftangle section (started at %s)\n", Line_numbers["iftangle"]) } # strip_out_name --- get the name from name function strip_out_name(name, l) { l = length(name) name = substr(name, 3, l - 4) return name } # Helper functions # message --- write a particular kind of message out to stderr function message(msg, format, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) { printf("%s:%d: %s: " format, FILENAME, FNR, msg, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) > "/dev/stderr" if (substr(format, length(format), 1) != "\n") printf("\n") > "/dev/stderr" } # fatal --- print a fatal error message and exit. # No varargs, so fake it with lots of parameters. function fatal(format, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) { message(_"fatal", format, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) exit 1 } # warning --- print a warning message to stderr # No varargs, so fake it with lots of parameters. function warning(format, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) { message(_"warning", format, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) } # join.awk --- join an array into a string # # Arnold Robbins, arnold@skeeve.com, Public Domain # May 1993 function join(array, start, end, sep, result, i) { if (sep == "") sep = " " else if (sep == SUBSEP) # magic value sep = "" result = array[start] for (i = start + 1; i <= end; i++) result = result sep array[i] return result } /^@ignore/, /^@end ignore/ { next } # iftangle lines should be removed /^@iftangle[[:space:]]*$/, /^@end iftangle[[:space:]]*$/ { if (Pass == 1) next if ("ifweave" in Line_numbers) fatal(_"cannot nest @iftangle inside @ifweave\n") # start of construct, save line number if (/^@iftangle[[:space:]]*$/) Line_numbers["iftangle"] = (FILENAME ":" FNR) # end of construct, delete line number if (/^@end iftangle[[:space:]]*$/) delete Line_numbers["iftangle"] # simply skip these lines, this is weaving next } # For weaving we remove the bracketing control lines and let anything # in between fall through. /^@ifweave[[:space:]]*$/, /^@end ifweave[[:space:]]*$/ { if (Pass == 1) next if ("iftangle" in Line_numbers) fatal(_"cannot nest @ifweave inside @iftangle\n") # start of construct, save line number, skip this line if (/^@ifweave[[:space:]]*$/) { Line_numbers["ifweave"] = (FILENAME ":" FNR) next } # end of construct, delete line number, skip this line if (/^@end ifweave[[:space:]]*$/) { delete Line_numbers["ifweave"] next } # otherwise fall through into the rest of the code } /^@file_update_recipe[[:space:]]*$/, /^@end file_update_recipe[[:space:]]*$/ { next } /^@file_update[[:space:]]/ { next } /^@post_create[[:space:]]+/ { next } BEGIN { Example_start = "@example" Example_end = "@end example" } Pass == 2 && /^@use_smallexample[[:space:]]*$/ { Example_start = "@smallexample" Example_end = "@end smallexample" next } Pass == 2 && /^@use_example[[:space:]]*$/ { Example_start = "@example" Example_end = "@end example" next } Pass == 2 && /^@titlepage/ { print "@c Let texinfo.tex give us full section titles" print "@xrefautomaticsectiontitle on" print "" print "@c Start extra commands added by jrweave\n" print "@c For HTML, spell out email addresses, to avoid problems with" print "@c address harvesters for spammers." print "@ifhtml" print "@macro EMAIL{real,spelled}" print "``\\spelled\\''" print "@end macro" print "@end ifhtml" print "@ifnothtml" print "@macro EMAIL{real,spelled}" print "@email{\\real\\}" print "@end macro" print "@end ifnothtml" print "" print "@macro FIXME{text}" print "@strong{FIXME}: \\text\\" print "@end macro" print "" print "@macro oldnum{value}" print "\\value\\" print "@end macro" print "" print "@tex" print "\\gdef\\oldnum#1{\\begingroup\\oldstyle #1\\endgroup}%" print "@end tex" print "\n@c End extra commands added by jrweave" print "" print # print the line next } /^@numberedchunks[[:space:]]*$/ { if (Pass == 1) Numbered_chunks = TRUE next } /^@file_chunk_full_defs[[:space:]]*$/ { if (Pass == 1) Print_file_full_defs = TRUE next } Pass == 2 && /^@sidebar[[:space:]]+/ { sub(/^@sidebar[[:space:]]+/, "", $0) Sidebar_title = $0 Sidebar_body = "" Collecting_sidebar = TRUE next } Pass == 2 && /^@end[[:space:]]+sidebar[[:space:]]*$/ { Collecting_sidebar = FALSE printf "@cindex sidebar, %s\n", Sidebar_title printf "@ifdocbook\n" printf "@docbook\n" printf "%s\n", Sidebar_title printf "@end docbook\n" print Sidebar_body print "" printf "@docbook\n" printf "\n" printf "@end docbook\n" printf "@end ifdocbook\n\n" printf "@ifnotdocbook\n" printf "@cartouche\n" printf "@center @b{%s}\n\n", Sidebar_title print "@noindent" sub(/^\n*/, "", Sidebar_body) # remove initial newlines print Sidebar_body printf "@end cartouche\n" printf "@end ifnotdocbook\n" Sidebar_body = "" next } Pass == 2 && Collecting_sidebar { Sidebar_body = Sidebar_body "\n" $0 next } Pass == 2 && /^@dquotexrefs[[:space:]]*$/ { print "@tex" # print "%\\gdef\\xrefprintnodename#1{{\\it #1}}" print "\\gdef\\xrefprintnodename#1{``#1''}" print "@end tex" next } Pass == 2 && /^@pdflinkcolor[[:space:]]*.*$/ { if (NF != 1 && NF != 4) fatal(_"@pdflinkcolor: wrong number of arguments\n") if (NF == 1) Link_color = Dark_red else { $1 = "" $0 = $0 Link_color = $0 } print "@tex" print "\\gdef\\linkcolor{" Link_color "}" print "@end tex" next } Pass == 2 && /^@urllinkcolor[[:space:]]*.*$/ { if (NF != 1 && NF != 4) fatal(_"@urllinkcolor: wrong number of arguments\n") if (NF == 1) URL_color = Dark_red else { $1 = "" $0 = $0 URL_color = $0 } print "@tex" print "\\gdef\\urlcolor{" URL_color "}" print "@end tex" next } Pass == 2 && /^@hideurls[[:space:]]*$/ { print "@tex" print "\\global\\urefurlonlylinktrue" # NOTE: *not* \gdef print "@end tex" next } Pass == 2 && /^@allowindexbraces[[:space:]]*$/ { print "@tex" print "\\global\\usebracesinindexestrue" # NOTE: *not* \gdef print "@end tex" next } BEGIN { Dark_red = "0.5 0.09 0.12" } Pass == 2 && /^@c %\*\*end of header/ { print # print the line print "\n@c Extra indices added by jrweave" print "@defindex cd @c chunk definition" print "@defindex cr @c chunk reference" next } $0 ~ File_chunk_pattern { Chunk_type = "file chunk" Pattern = File_chunk_pattern Debug_pat = "filename" new_chunk = gensub(Pattern, "\\1", 1) if (Flags[Chunk_type]) { fatal(_"%s start of %s found while still collecting %s\n", Chunk_type, new_chunk, Current_chunk) } check_unfinished() Flags[Chunk_type] = TRUE Line_numbers[Chunk_type] = (FILENAME ":" FNR) Current_chunk = new_chunk Chunk_info[Current_chunk]["type"] = Chunk_type if (Debug ~ Debug_pat) printf("saw new %s %s\n", Debug_pat, Current_chunk) > "/dev/stderr" next } $0 ~ Code_chunk_pattern { Chunk_type = "code chunk" Pattern = Code_chunk_pattern Debug_pat = "code" new_chunk = gensub(Pattern, "\\1", 1) if (Flags[Chunk_type]) { fatal(_"%s start of %s found while still collecting %s\n", Chunk_type, new_chunk, Current_chunk) } check_unfinished() Flags[Chunk_type] = TRUE Line_numbers[Chunk_type] = (FILENAME ":" FNR) Current_chunk = new_chunk Chunk_info[Current_chunk]["type"] = Chunk_type if (Debug ~ Debug_pat) printf("saw new %s %s\n", Debug_pat, Current_chunk) > "/dev/stderr" next } /^@[[:space:]]*$/ { if (Flags["file chunk"]) end_file_gathering() else if (Flags["code chunk"]) end_code_gathering() else warning(_"unmatched terminating @-sign: ignored\n") Chunk_lines = "" Flags[Chunk_type] = FALSE Line_numbers[Chunk_type] = "" Chunk_type = "" next } Flags["file chunk"] || Flags["code chunk"] { if (Chunk_lines == "") Chunk_lines = $0 else Chunk_lines = Chunk_lines "\n" $0 next } function end_chunk_gathering() { if (Pass == 1) { collect_chunk_info() Chunk_lines = "" Flags[Chunk_type] = FALSE } else print_out_chunk() } function end_file_gathering() { end_chunk_gathering() } function collect_chunk_info( i, n, x, called, junk) { # Current_chunk, Chunk_type already set by initial code # Chunk number: if (! ("chunk number" in Chunk_info[Current_chunk])) { Chunk_info[Current_chunk]["chunk number"] = \ ++Chunk_numbers[Chunk_type] } # Definition instance Chunk_info[Current_chunk]["defn"]++ # Get names of called chunks into called n = split(Chunk_lines, junk, Chunk_name_pattern, called) # Add ourselves to the callers for (i in called) { x = strip_out_name(called[i]) Chunk_info[x]["callers"][Current_chunk] = TRUE } } function print_out_chunk( x, y, n, i, parts, names, name, anchor, chunk_being_used) { # Redefinition instance Chunk_info[Current_chunk]["redefn"]++ print "@need 400" anchor = format_anchor(Current_chunk, Chunk_info[Current_chunk]["redefn"], Chunk_info[Current_chunk]["defn"]) printf("%s\n", anchor) x = expand_tabs(Chunk_lines, Tabstop) # extract code chunks n = split(x, parts, Chunk_name_pattern, names) # escape special chars in parts of code that aren't chunk names for (i = 1; i in parts; i++) gsub(/[@{}]/, "@&", parts[i]) y = parts[1] if (n > 1) { # embedded chunk names for (i = 1; i in names; i++) { name = strip_out_name(names[i]) chunk_being_used = \ format_chunk_name(name, Chunk_info[name]["chunk number"], Chunk_info[name]["type"]) printf("@crindex %s @sortas{%s} @subentry use\n", chunk_being_used, remove_markup(name)) y = y chunk_being_used y = y parts[i+1] } } chunk_being_defined = \ format_chunk_name(Current_chunk, Chunk_info[Current_chunk]["chunk number"], Chunk_type) printf("@cdindex %s @sortas{%s} @subentry definition\n", chunk_being_defined, remove_markup(Current_chunk)) printf("@noindent\n%s %s@equiv{}\n", chunk_being_defined, Chunk_info[Current_chunk]["redefn"] == 1 ? "" : "+") print Example_start printf("%s\n", y) print Example_end for (i in names) names[i] = strip_out_name(names[i]) # remove delimiters print "@iftex" print "@smallfonts @rm" print "@end iftex" # Print other definition sites for code chunks, or for file # chunk if Print_file_full_defs is true if (Chunk_type == "code chunk" || Print_file_full_defs) { print_other_defns(Current_chunk, Chunk_info[Current_chunk]["defn"], Chunk_info[Current_chunk]["redefn"]) } # Print callers for code chunks if (Chunk_type == "code chunk") { if ("callers" in Chunk_info[Current_chunk]) { print "" asorti(Chunk_info[Current_chunk]["callers"], my_callers) if (length(my_callers) > 1) { print "@noindent" print "This chunk is called by the following chunks:\n" print_ref_table(my_callers) } else { n = Chunk_info[my_callers[1]]["defn"] print "@noindent" printf("This chunk is called by %s; see its first definition at %s.\n", format_chunk_name(my_callers[1], Chunk_info[my_callers[1]]["chunk number"], Chunk_info[my_callers[1]]["type"]), format_xref(my_callers[1], n > 1 ? 1 : 0)) } } else warning(_"chunk %s has no callers\n", Current_chunk) } sort_and_remove_duplicates(names) switch (length(names)) { case 0: break case 1: print "\n@noindent" printf("The called chunk %s is first defined at\n%s.\n", format_chunk_name(names[1], Chunk_info[names[1]]["chunk number"], Chunk_info[names[1]]["type"]), format_xref(names[1], (Chunk_info[names[1]]["defn"] > 1) ? 1 : 0)) break; default: print "\n@noindent" printf("The following table lists called chunk definition points.\n") print_ref_table(names) break; } print "@iftex" print "@textfonts @rm" print "@end iftex" } function print_other_defns(chunk, total_defns, current_defn, other_defns, i, j) # locals { if (total_defns == 1) return print "" print "@noindent" print "This chunk is also defined in" for (i = j = 1; i <= total_defns; i++) { if (i == current_defn) continue other_defns[j++] = i } for (i = 1; i < j; i++) { printf("%s", format_xref(chunk, other_defns[i])) if (i + 2 == j) print ", and" else if (i + 2 < j) print "," } print "." } function print_ref_table(chunklist, i, x, n) { print "@multitable @columnfractions .35 .65" print "@headitem Chunk name @tab First definition point" for (i = 1; i in chunklist; i++) { x = chunklist[i] n = (Chunk_info[x]["defn"] > 1) ? 1 : 0 printf("@item %s @tab See %s.\n", format_chunk_name(x, Chunk_info[x]["chunk number"], Chunk_info[x]["type"]), format_xref(x, n)) } print "@end multitable" } function sort_and_remove_duplicates(names, i, dups) { for (i in names) dups[names[i]] = 1 asorti(dups) delete names for (i in dups) names[i] = dups[i] } function end_code_gathering() { end_chunk_gathering() if (Debug ~ /code/) printf("finished formatting code %s\n", Code_chunk) > "/dev/stderr" } Pass == 2 && /^@print_file_defs[[:space:]]*$/ { delete Sorted_file_names j = 1 for (i in Chunk_info) { if (Chunk_info[i]["type"] == "file chunk") Sorted_file_names[j++] = i } asort(Sorted_file_names) # Sorted by value print "@table @asis" for (i = 1; i in Sorted_file_names; i++) { name = Sorted_file_names[i] x = format_chunk_name(name, Chunk_info[name]["chunk number"], Chunk_info[name]["type"]) printf("@item %s\n", x) n = Chunk_info[name]["defn"] if (n == 1) { printf("This chunk is defined in\n") printf("%s.\n", format_xref(name, 0)) } else { printf("Multiple definitions occur in\n") for (j = 1; j <= n; j++) { printf("%s", format_xref(name, j)) if (j == n - 1) printf(",\nand\n") else if (j < n - 1) printf(",\n") } print ".\n" } } print "@end table" next } Pass == 2 && /^@print_code_defs[[:space:]]*$/ { delete Sorted_code_names j = 1 for (i in Chunk_info) { if (Chunk_info[i]["type"] == "code chunk") Sorted_code_names[j++] = i } asort(Sorted_code_names) # Sorted by value print "@table @asis" for (i = 1; i in Sorted_code_names; i++) { name = Sorted_code_names[i] x = format_chunk_name(name, Chunk_info[name]["chunk number"], Chunk_info[name]["type"]) printf("@item %s\n", x) n = Chunk_info[name]["defn"] if (n == 1) { printf("This chunk is defined in\n") printf("%s.\n", format_xref(name, 0)) } else { printf("Multiple definitions occur in\n") for (j = 1; j <= n; j++) { printf("%s", format_xref(name, j)) if (j == n - 1) printf(",\nand\n") else if (j < n - 1) printf(",\n") } print ".\n" } } print "@end table" next } Pass == 2 && /^@print_code_refs[[:space:]]*$/ { delete Sorted_code_names j = 1 for (i in Chunk_info) { if (Chunk_info[i]["type"] == "code chunk") Sorted_code_names[j++] = i } asort(Sorted_code_names) # Sorted by value print "@table @asis" for (i = 1; i in Sorted_code_names; i++) { name = Sorted_code_names[i] n = Chunk_info[name]["defn"] if (n == 0) # warning printed elsewhere continue fmt_name = format_chunk_name(name, Chunk_info[name]["chunk number"], Chunk_info[name]["type"]) printf("@item %s\n", fmt_name) Current_chunk = name # for use by next chunk if ("callers" in Chunk_info[Current_chunk]) { print "" asorti(Chunk_info[Current_chunk]["callers"], my_callers) if (length(my_callers) > 1) { print "@noindent" print "This chunk is called by the following chunks:\n" print_ref_table(my_callers) } else { n = Chunk_info[my_callers[1]]["defn"] print "@noindent" printf("This chunk is called by %s; see its first definition at %s.\n", format_chunk_name(my_callers[1], Chunk_info[my_callers[1]]["chunk number"], Chunk_info[my_callers[1]]["type"]), format_xref(my_callers[1], n > 1 ? 1 : 0)) } } else warning(_"chunk %s has no callers\n", Current_chunk) } print "@end table" next } /^@print_initial_setup([[:space:]]+.*|[[:space:]]*)$/ { Print_initial_setup = TRUE if (NF > 1) { $1 = "" $0 = $0 Initial_setup_name = $0 } else Initial_setup_name = "Initial setup" next } /^@initial_setup[[:space:]]*$/, /^@end initial_setup[[:space:]]*$/ { if (Pass == 1 || ! Print_initial_setup) next Chunk_info[Initial_setup_name]["type"] = "code chunk" Chunk_info[Initial_setup_name]["defn"] = 1 if (/^@initial_setup[[:space:]]*$/) { print "@need 400" printf("%s\n", format_anchor(Initial_setup_name, 0)) printf("@noindent\n%s @equiv{}\n", format_chunk_name(Initial_setup_name, 0, "code chunk")) print Example_start } else if (/^@end initial_setup[[:space:]]*$/) { print Example_end } else { x = expand_tabs($0, Tabstop) gsub(/[@{}]/, "@&", x) print x } next } Pass == 2 { print } BEGIN { if (Tabstop == 0) Tabstop = 4 # default tab stops } # expand_tabs --- expand tabs in the string function expand_tabs(string, tabstop, chars, out, i, j, k, n) { if (tabstop < 2) fatal(_"expand_tabs: tabstop %d < 2\n", tabstop) n = split(string, chars, "") j = k = 0 for (i = 1; i <= n;) { if (chars[i] == "\n") { out[j++] = chars[i++] k = 0 continue } if (chars[i] != "\t") { out[j++] = chars[i++] k++ continue } i++ # skip the tab do { out[j++] = " " k++ } while (and(k, tabstop-1) != 0) } return join(out, 0, j, SUBSEP) } function sanitize_name(name) { gsub(/[^[:alnum:]]/, "-", name) return name } function remove_markup(text, result) { result = gensub(/([^@])(@[[:alpha:]]+[{]([^}]+)[}])/, "\\1\\3", "g", text) result = gensub(/@TeX[{][}]/, "TeX", "g", result) return result } function format_anchor_or_ref(type, name, defn, clean_name, result) # locals { clean_name = sanitize_name(name) if (defn > 0) result = sprintf("@%s{%s-%d}", type, clean_name, defn) else result = sprintf("@%s{%s}", type, clean_name) return result } function format_xref(name, defn) { return format_anchor_or_ref("ref", name, defn) } function format_anchor(name, cur_defn, total_defns, defn) { if (total_defns == 1) defn = 0 else if (cur_defn <= total_defns) defn = cur_defn return format_anchor_or_ref("anchor", name, defn) } function format_chunk_name(name, count, type, result, left, right, style) # locals { if (type == "file chunk") { left = "@{" right = "@}" style = "file" } else if (type == "code chunk") { left = "<" right = ">" style = "i" } else fatal(_"format_chunk_name: Unknown chunk type `%s'\n", type) if (count > 0 && Numbered_chunks) result = sprintf("@r{%s@%s{%s} @oldnum{%d}%s}", left, style, name, count, right) else result = sprintf("@r{%s@%s{%s}%s}", left, style, name, right) return result }