# Input: file text file with list of chapters
# stdin text with list of chapters
# Output: [[../Chapter 4|Next]], [[../Chapter 2|Previous]], [[../|Up]]
-# Version: 0.0.1
-# Attrib: Steven Baltakatei Sandoval. (2024-01-29). reboil.com
+# Version: 0.1.1
+# Attrib: Steven Baltakatei Sandoval. (2024-07-17). reboil.com
# License: GPLv3+
yell() { echo "$0: $*" >&2; } # print script path and all args to stderr
return 0;
}; # read stdin to stdout lines
+get_path_fork_level() {
+ # Desc: Get fork level from two paths
+ # Input: arg1 str path
+ # arg2 str path
+ # Output: stdout int fork level
+ # Version: 0.0.1
+ local path1="$1";
+ local path2="$2";
+
+ # Squeeze multiple slashes and remove trailing slashes
+ path1="$(echo "$path1" | tr -s '/' | sed 's:/*$::' )";
+ path2="$(echo "$path2" | tr -s '/' | sed 's:/*$::' )";
+
+ # Check for mixed absolute/relative paths
+ if [[ "$path1" =~ ^/ ]] && [[ "$path2" =~ ^/ ]]; then
+ flag_root=true;
+ # Remove initial /
+ path1="$(echo "$path1" | sed -e 's:^/::' )";
+ path2="$(echo "$path2" | sed -e 's:^/::' )";
+ elif [[ ! "$path1" =~ ^/ ]] && [[ ! "$path2" =~ ^/ ]]; then
+ flag_root=false;
+ else
+ declare -p path1 path2 flag_root;
+ echo "FATAL:Mixed relative and absolute paths not supported." 1>&2;
+ return 1;
+ fi;
+
+ # Save path as arrays with `/` as element delimiter
+ local IFS='/';
+ read -ra parts1 <<< "$path1";
+ read -ra parts2 <<< "$path2";
+
+ # Get fork level by counting identical path elements from rootside
+ local fork_level=0;
+ for (( i=0; i<${#parts1[@]} && i<${#parts2[@]}; i++ )); do
+ if [[ "${parts1[i]}" != "${parts2[i]}" ]]; then break; fi;
+ ((fork_level++));
+ done;
+
+ echo "$fork_level";
+ #declare -p path1 path2 flag_root parts1 parts2 fork_level; # debug
+ return 0;
+}; # Get fork level int from two paths
+prune_path_rootside() {
+ # Desc: Prunes a path from the root-side to a specified prune level.
+ # Input: arg1 str path
+ # arg2 int prune level (0-indexed)
+ # Depends: GNU sed 4.8
+ # Version: 0.0.1
+ local path="$1";
+ local prune_level="$2";
+
+ # Check for absolute or relative path
+ if [[ "$path" =~ ^/ ]]; then
+ flag_root=true;
+ # Remove initial /
+ path="$(echo "$path" | sed -e 's:^/::' )";
+ else
+ flag_root=false;
+ fi;
+
+ # Save path as array with `/` as element delimiter
+ local IFS='/';
+ read -ra parts <<< "$path";
+
+ # Assemble pruned path from prune_level
+ local pruned_path="";
+ for (( i=prune_level; i<${#parts[@]}; i++ )); do
+ pruned_path+="${parts[i]}/";
+ done;
+
+ # Trim trailing `/` delimiter
+ pruned_path=$(echo "$pruned_path" | sed 's:/*$::');
+
+ # Restore initial / if appropriate
+ if [[ "$flag_root" == "true" ]] && [[ "$prune_level" -eq 0 ]]; then
+ pruned_path=/"$pruned_path";
+ fi;
+
+ # Output pruned path
+ echo "$pruned_path";
+ #declare -p path prune_level parts pruned_path && printf "========\n"; # debug
+ return 0;
+}; # prune path rootside to int specified level
+get_path_hierarchy_level() {
+ # Desc: Outputs hierarchy level of input paths
+ # Example: $ cat lines.txt | get_path_hierarchy_level
+ # Input: stdin str lines with /-delimited paths
+ # Output: stdout int hierarchy level of each path
+ # Version: 0.0.1
+
+ local line level;
+ local flag_root;
+ local -a output;
+
+ n=0;
+ while read -r line; do
+ # Check for mixed absolute/relative paths.
+ if [[ $n -le 0 ]] && [[ "$line" =~ ^/ ]]; then
+ flag_root=true;
+ else
+ flag_root=false;
+ fi;
+ if { [[ "$flag_root" == "true" ]] && [[ ! "$line" =~ ^/ ]]; } || \
+ { [[ "$flag_root" == "false" ]] && [[ "$line" =~ ^/ ]]; } then
+ echo "FATAL:Mixed relative and absolute paths not supported." 1>&2; return 1;
+ fi;
+
+ # Squeeze multiple slashes and remove trailing slashes
+ line="$(echo "$line" | tr -s '/' | sed 's:/*$::' )";
+
+ # Count the number of slashes to determine hierarchy level
+ level="$(echo "$line" | awk -F'/' '{print NF-1}' )";
+ if [[ "$flag_root" == "true" ]]; then ((level--)); fi;
+
+ # Append to output
+ output+=("$level");
+ #declare -p flag_root level; # debug
+ ((n++));
+ done;
+ # Print output
+ printf "%s\n" "${output[@]}";
+}; # return hierarchy level of lines as integers
validate_subpage_list() {
# Desc: Check for illegal characters in subpage titles
# Input: stdin unvalidated subpage list
echo "FATAL:Error reading stdin." 1>&2; return 1; };
};
generate_wikicode() {
+ # Desc: Generates navigational link wikicode for subpages
# Input: stdin validated subpage list
# Output: stdout wikicode
- local lprev lnext;
+ # Depends: get_path_fork_level()
+ # prune_path_rootside()
+ # get_path_hierarchy_level()
n=0;
while read -r line; do
#yell "$n:Processing line:$line"; # debug
+ # Advance input lines
lprev="$lcurr";
lcurr="$lnext";
lnext="$line";
#declare -p lprev lcurr lnext; # debug
+ # Update hierarchy tracker states
+ lprev_hier="$lcurr_hier";
+ lcurr_hier="$lnext_hier";
+ lnext_hier="$(echo "$lnext" | get_path_hierarchy_level)";
+
# Skip first iteration
if [[ "$n" -eq 0 ]]; then
# yell "$n:DEBUG:Skipping first iteration."; # debug
#printf -- "----\n" 1>&2; # debug
continue; fi;
- # Handle first valid input set
- # yell "$n:DEBUG:Handling first valid input set."; # debug
- if [[ "$n" -eq 1 ]]; then
- printf "[[../%s|Next]], [[../|Up]]\n" \
- "$lnext";
- #printf -- "----\n" 1>&2; # debug
- ((n++)); continue; fi;
-
- # Handle middle lines
- # yell "$n:DEBUG:Handling middle lines."; # debug
- printf "[[../%s|Next]], [[../%s|Previous]], [[../|Up]]\n" \
- "$lnext" "$lprev";
- ((n++));
- #printf -- "----\n" 1>&2; # debug
- done;
+ # Get path fork levels
+ fork_level_next="$(get_path_fork_level "$lcurr" "$lnext")";
+ fork_level_prev="$(get_path_fork_level "$lcurr" "$lprev")";
- # Handle last line
- lprev="$lcurr";
- lcurr="$lnext";
- lnext="$line";
- printf "[[../%s|Previous]], [[../|Up]]\n" \
- "$lprev";
- ((n++));
-};
+ # Count relative ups needed (`../`)
+ relups_next="$((lcurr_hier - fork_level_next + 1))";
+ relups_prev="$((lcurr_hier - fork_level_prev + 1))";
+
+ # Initialize Next and Prev links with relative ups to fork.
+ link_next="";
+ for (( i=0; i<relups_next; i++ )); do link_next+="../"; done;
+ if [[ "$relups_next" -eq 0 ]]; then link_next+="/"; fi; # handle new subpage path dive
+ link_prev="";
+ for (( i=0; i<relups_prev; i++ )); do link_prev+="../"; done;
+
+ # Append branchs from fork to Next and Prev targets
+ link_next+="$(prune_path_rootside "$lnext" "$fork_level_next")";
+ link_prev+="$(prune_path_rootside "$lprev" "$fork_level_prev")";
+
+ # Print navigation link wikicode
+ if [[ -z "$lprev" ]]; then
+ printf "[[%s|Next]], [[../|Up]]\n" "$link_next";
+ elif [[ -n "$lnext" ]]; then
+ printf "[[%s|Next]], [[%s|Previous]], [[../|Up]]\n" "$link_next" "$link_prev";
+ elif [[ -z "$lnext" ]]; then
+ printf "[[%s|Previous]], [[../|Up]]\n" "$link_prev";
+ else
+ echo "FATAL:Here be dragons." 1>&2;
+ fi;
+
+ #declare -p n line lprev lcurr lnext lprev_hier lcurr_hier lnext_hier; # debug
+ #declare -p fork_level_next fork_level_prev relups_next relups_prev; # debug
+ #declare -p link_next link_prev; # debug
+ #printf "====================\n" # debug
+ ((n++));
+ done < <(read_stdin; printf "\n"; ); # read stdin plus one more blank line
+}; # Generate wikicode from validated subpage lines
main() {
read_input "$@" | validate_subpage_list | generate_wikicode;
}; # main program