| 1 | #!/bin/bash |
| 2 | # Desc: Generates Mediawiki subpage navigation links |
| 3 | # Input: file text file with list of chapters |
| 4 | # stdin text with list of chapters |
| 5 | # Output: [[../Chapter 4|Next]], [[../Chapter 2|Previous]], [[../|Up]] |
| 6 | # Version: 0.0.1 |
| 7 | # Attrib: Steven Baltakatei Sandoval. (2024-01-29). reboil.com |
| 8 | # License: GPLv3+ |
| 9 | |
| 10 | yell() { echo "$0: $*" >&2; } # print script path and all args to stderr |
| 11 | die() { yell "$*"; exit 111; } # same as yell() but non-zero exit status |
| 12 | must() { "$@" || die "cannot $*"; } # runs args as command, reports args if command fails |
| 13 | read_input() { |
| 14 | # Note: May accept specified file or stdin, but not both |
| 15 | # Input: arg input text (newline delimited) |
| 16 | # stdin input text (newline delimited) |
| 17 | # Output: stdout output text (newline delimited) |
| 18 | # Depends: BK-2020-03 read_stdin (v0.1.1), yell(), die() |
| 19 | |
| 20 | # Parse args |
| 21 | ## Only 1 argument. |
| 22 | if [[ "$#" -gt 1 ]]; then die "FATAL:Too many arguments ($#):$*"; fi; |
| 23 | ## File specified. |
| 24 | if [[ "$#" -eq 1 ]] && [[ -f "$1" ]] && [[ ! -p /dev/stdin ]]; then |
| 25 | while read -r line; do |
| 26 | printf "%s\n" "$line"; |
| 27 | done < "$1" && return 0; fi; |
| 28 | if [[ "$#" -eq 0 ]] && [[ -p /dev/stdin ]]; then |
| 29 | read_stdin && return 0; fi; |
| 30 | die "FATAL:Unknown error."; |
| 31 | }; |
| 32 | read_stdin() { |
| 33 | # Desc: Consumes stdin; outputs as stdout lines |
| 34 | # Input: stdin (consumes) |
| 35 | # Output: stdout (newline delimited) |
| 36 | # return 0 stdin read |
| 37 | # return 1 stdin not present |
| 38 | # Example: printf "foo\nbar\n" | read_stdin |
| 39 | # Depends: GNU bash (version 5.1.16), GNU Coreutils 8.32 (cat) |
| 40 | # Version: 0.1.1 |
| 41 | # Attrib: Steven Baltakatei Sandoval (2024-01-29). reboil.com |
| 42 | local input_stdin output; |
| 43 | |
| 44 | # Store stdin |
| 45 | if [[ -p /dev/stdin ]]; then |
| 46 | input_stdin="$(cat -)" || { |
| 47 | echo "FATAL:Error reading stdin." 1>&2; return 1; }; |
| 48 | else |
| 49 | return 1; |
| 50 | fi; |
| 51 | |
| 52 | # Store as output array elements |
| 53 | ## Read in stdin |
| 54 | if [[ -n $input_stdin ]]; then |
| 55 | while read -r line; do |
| 56 | output+=("$line"); |
| 57 | done < <(printf "%s\n" "$input_stdin") || { |
| 58 | echo "FATAL:Error parsing stdin."; return 1; }; |
| 59 | fi; |
| 60 | |
| 61 | # Print to stdout |
| 62 | printf "%s\n" "${output[@]}"; |
| 63 | |
| 64 | return 0; |
| 65 | }; # read stdin to stdout lines |
| 66 | validate_subpage_list() { |
| 67 | # Desc: Check for illegal characters in subpage titles |
| 68 | # Input: stdin unvalidated subpage list |
| 69 | # Output: stdout validated subpage list |
| 70 | # Depends: BK-2020-03 read_stdin(), yell(), die() |
| 71 | # GNU sed v4.8 |
| 72 | while read -r line; do |
| 73 | |
| 74 | # Reject chars illegal in Mediawiki page titles. |
| 75 | re_illegal='[][><|}{#_]'; # match illegal page names chars #, <, >, [, ], _, {, |, } |
| 76 | if [[ "$line" =~ $re_illegal ]]; then |
| 77 | die "FATAL:Illegal char. Not allowed: #, <, >, [, ], _, {, |, }:$line"; |
| 78 | fi; |
| 79 | |
| 80 | # Reject trailing spaces. |
| 81 | re_ts=' $'; # match trailing space |
| 82 | if [[ "$line" =~ $re_ts ]]; then |
| 83 | die "FATAL:Trailing spaces not allowed:$line"; |
| 84 | fi; |
| 85 | |
| 86 | # Replace some chars with HTML-style codes |
| 87 | ## replace ampersand & with & # must be first |
| 88 | ## replace double quote " with " |
| 89 | ## replace single quote ' with ' |
| 90 | line="$(sed \ |
| 91 | -e 's/&/\&/g' \ |
| 92 | -e 's/"/\"/g' \ |
| 93 | -e "s/'/\'/g" \ |
| 94 | <<< "$line" )" || { echo "FATAL:Error running sed."; }; |
| 95 | printf "%s\n" "$line"; |
| 96 | done || { |
| 97 | echo "FATAL:Error reading stdin." 1>&2; return 1; }; |
| 98 | }; |
| 99 | generate_wikicode() { |
| 100 | # Input: stdin validated subpage list |
| 101 | # Output: stdout wikicode |
| 102 | local lprev lnext; |
| 103 | |
| 104 | n=0; |
| 105 | while read -r line; do |
| 106 | #yell "$n:Processing line:$line"; # debug |
| 107 | lprev="$lcurr"; |
| 108 | lcurr="$lnext"; |
| 109 | lnext="$line"; |
| 110 | #declare -p lprev lcurr lnext; # debug |
| 111 | |
| 112 | # Skip first iteration |
| 113 | if [[ "$n" -eq 0 ]]; then |
| 114 | # yell "$n:DEBUG:Skipping first iteration."; # debug |
| 115 | ((n++)); |
| 116 | #printf -- "----\n" 1>&2; # debug |
| 117 | continue; fi; |
| 118 | |
| 119 | # Handle first valid input set |
| 120 | # yell "$n:DEBUG:Handling first valid input set."; # debug |
| 121 | if [[ "$n" -eq 1 ]]; then |
| 122 | printf "[[../%s|Next]], [[../|Up]]\n" \ |
| 123 | "$lnext"; |
| 124 | #printf -- "----\n" 1>&2; # debug |
| 125 | ((n++)); continue; fi; |
| 126 | |
| 127 | # Handle middle lines |
| 128 | # yell "$n:DEBUG:Handling middle lines."; # debug |
| 129 | printf "[[../%s|Next]], [[../%s|Previous]], [[../|Up]]\n" \ |
| 130 | "$lnext" "$lprev"; |
| 131 | ((n++)); |
| 132 | #printf -- "----\n" 1>&2; # debug |
| 133 | done; |
| 134 | |
| 135 | # Handle last line |
| 136 | lprev="$lcurr"; |
| 137 | lcurr="$lnext"; |
| 138 | lnext="$line"; |
| 139 | printf "[[../%s|Previous]], [[../|Up]]\n" \ |
| 140 | "$lprev"; |
| 141 | ((n++)); |
| 142 | }; |
| 143 | main() { |
| 144 | read_input "$@" | validate_subpage_list | generate_wikicode; |
| 145 | }; # main program |
| 146 | |
| 147 | main "$@"; |