chore(prvt):Update private submodule
[BK-2020-03.git] / user / mw_create_subpage_navlinks.sh
old mode 100644 (file)
new mode 100755 (executable)
index de37365..c4aee6f
@@ -3,8 +3,8 @@
 # Input: file   text file with list of chapters
 #        stdin  text with list of chapters
 # Output: [[../Chapter 4|Next]], [[../Chapter 2|Previous]], [[../|Up]]
 # 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
 # License: GPLv3+
 
 yell() { echo "$0: $*" >&2; } # print script path and all args to stderr
@@ -63,6 +63,129 @@ read_stdin() {
 
     return 0;
 }; # read stdin to stdout lines
 
     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
 validate_subpage_list() {
     # Desc: Check for illegal characters in subpage titles
     # Input:  stdin   unvalidated subpage list
@@ -97,18 +220,27 @@ validate_subpage_list() {
         echo "FATAL:Error reading stdin." 1>&2; return 1; };
 };
 generate_wikicode() {
         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
     # 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
 
     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
 
         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
         # Skip first iteration
         if [[ "$n" -eq 0 ]]; then
             # yell "$n:DEBUG:Skipping first iteration.";  # debug
@@ -116,30 +248,43 @@ generate_wikicode() {
             #printf -- "----\n" 1>&2;  # debug
             continue; fi;
 
             #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
 main() {
     read_input "$@" | validate_subpage_list | generate_wikicode;
 }; # main program