go.dedis.ch/onet/v3@v3.2.11-0.20210930124529-e36530bca7ef/doctoc.sh (about) 1 #!/bin/bash 2 # 3 # Takes a mardown file in argument, checks if a table of content is already 4 # present, create a new one if there is none or update the current one. 5 # 6 # The script uses the $start_toc and $end_doc as delimiters for the table of 7 # content. It works only if there is one pair of opening/closing delimiters. If 8 # it finds a correct single pair, the content inside is updated. 9 # 10 # Attributions: Updated from https://gitlab.com/pedrolab/doctoc.sh/tree/master, 11 # which comes from 12 # https://gist.github.com/meleu/57867f4a01ede1bd730f14b2f018ae89. 13 # 14 # The list of invalid chars come from https://github.com/thlorenz/anchor-\ 15 # markdown-header/blob/56f77a232ab1915106ad1746b99333bf83ee32a2/anchor-\ 16 # markdown-header.js#L25 17 # 18 19 # Exits early in case of errors 20 set -e 21 set -u 22 23 INVALID_CHARS="'[]/?!:\`.,()*\";{}+=<>~$|#@&–—" 24 25 # For Mac user, we need to use 'gsed' 26 SED=sed 27 28 appname=$(basename "$0") 29 start_toc='<!-- START '$appname' generated TOC please keep comment here to allow auto update -->' 30 info_toc='<!-- DO NOT EDIT THIS SECTION, INSTEAD RE-RUN '$appname' TO UPDATE -->' 31 end_toc='<!-- END '$appname' generated TOC please keep comment here to allow auto update -->' 32 33 check() { 34 # Check if argument found 35 if [ -z "$1" ]; then 36 echo "Error. No argument found. Put as argument a file.md" 37 exit 1 38 fi 39 # Check if file found 40 [[ ! -f "$1" ]] && echo "Error. File not found" && exit 41 # Check if used from MacOS 42 if [[ "${OSTYPE//[0-9.]/}" == "darwin" ]]; then 43 if type gsed >/dev/null 2>&1; then 44 SED=gsed 45 else 46 warn="WARNING: Detecting you are on mac but didn't find the 'gsed' " 47 warn+="utility. Default 'sed' version of mac is not likely to work " 48 warn+="here. You can install 'gsed' with 'brew install gnu-sed'." 49 echo "$warn" 50 fi 51 fi 52 } 53 54 # This function parses each line of the file given in input. Each line is first 55 # parsed by awk in order to get only the ones concerning a title (ie. starting 56 # with a '#') and not in a code block (ie. inside a block defined by '```'). 57 # Then, in the while loop, we extract the "level": number of '#' minus one 58 # converted to spaces, the "title": the line without the '#', and then the 59 # "anchor": the title without invalid chars and uppercase and with spaces 60 # converted to dashes. In the case there is duplicated titles, a "-n" token is 61 # finally appended to the anchor, where "n" is a counter. Here is an example 62 # with two titles and the content of the corresponding variables: 63 # 64 # # Sample title! 65 # ## Sample title! 66 # 67 # This yields for each title: 68 # 69 # level="" 70 # title="Sample title!" 71 # anchor="sample-title" 72 # output="- [Sample title!](sample-title)" 73 # 74 # level=" " 75 # title="Sample title!" 76 # anchor="sample-title" 77 # output=" - [Sample title!](sample-title-2)" 78 toc() { 79 80 local line 81 local level 82 local title 83 local anchor 84 local output 85 86 while IFS='' read -r line || [[ -n "$line" ]]; do 87 level="$(echo "$line" | $SED -E 's/^(#+).*/\1/; s/#/ /g; s/^ //')" 88 title="$(echo "$line" | $SED -E 's/^#+ //')" 89 anchor="$(echo "$title" | tr '[:upper:] ' '[:lower:]-' | tr -d "$INVALID_CHARS")" 90 91 # Check that new lines introduced are not duplicated. If so, introduce a 92 # number at the end copying doctoc behavior. 93 temp_output=$output"$level- [$title](#$anchor)" 94 counter=1 95 while true; do 96 nlines=$(echo -e "$temp_output" | wc -l) 97 duplines=$(echo -e "$temp_output" | sort | uniq | wc -l) 98 if [ "$nlines" = "$duplines" ]; then 99 break 100 fi 101 temp_output=$output"$level- [$title](#$anchor-$counter)" 102 counter=$((counter+1)) 103 done 104 105 output="$temp_output\n" 106 107 done <<< "$(awk -v code='^```' ' BEGIN { in_code=0 } 108 { 109 if ($0 ~ code && in_code == 0) { in_code=1 } 110 else if ($0 ~ code && in_code == 1) { in_code=0 } 111 if ($0 ~ /^#{1,10}/ && in_code == 0) { print } 112 }' "$1" | tr -d '\r')" 113 114 echo "$output" 115 } 116 117 # This function takes the file path and the table of content in argument. It 118 # adds the toc title, checks if there is already a toc present and either 119 # updates or inserts a toc. 120 insert() { 121 122 local toc_text="$2" 123 local appname='doctoc.sh' 124 125 toc_block="$start_toc\n$info_toc\n**:book: Table of Contents**\n\n$toc_text\n$end_toc" 126 127 # temporary replace '/' (confused with separator of substitutions) and '&' 128 # (confused with match regex symbol) to run the special sed command 129 toc_block="$(echo "$toc_block" | $SED 's,&,id9992384923423gzz,g')" 130 toc_block="$(echo "$toc_block" | $SED 's,/,id8239230090230gzz,g')" 131 132 # Check if there is a block that begins with $start_toc and ends with 133 # $end_toc. We ensure there is a correct and single pair of opening/closing 134 # delimiters. 135 S=$(awk -v start="^$start_toc$" -v end="^$end_toc$" 'BEGIN { status=-1; start_c=0; end_c=0 } 136 { if ($0 ~ start && start_c > 0) { 137 start_c+=1; status=10; exit 138 } 139 if ($0 ~ start) { 140 start_c+=1 141 } 142 if (start_c == 1 && $0 ~ end) { 143 end_c+=1; status=0 144 } 145 if (start_c == 0 && $0 ~ end) { 146 status=11; exit 147 } 148 if (end_c > 1 ) { 149 status=12; exit 150 } 151 } END { 152 if (start_c == 1 && end_c == 0) { 153 status=13 154 } 155 print status }' "$1") 156 157 # If the status S is >=10, that means something went bad and we must abort. 158 if [ "$S" -ge 10 ]; then 159 echo "got an error while checking the opening/closing tags:" 160 161 case $S in 162 10) 163 echo " - found more than 1 opening tag. Please fix that" 164 ;; 165 11) 166 echo " - found a closing tag before an opening one. Please fix that" 167 ;; 168 12) 169 echo " - found more than 1 closing tag. Please fix that" 170 ;; 171 13) 172 echo " - found only an opening tag. Please fix that" 173 ;; 174 esac 175 exit 1 176 fi 177 178 if [ "$S" -eq 0 ]; then 179 # ":a" creates label 'a' 180 # "N" append the next line to the pattern space 181 # "$!" if not the last line 182 # "ba" branch (goto) label a 183 # In short, this loops throught the entire file until the last line and 184 # then performs the substitution. 185 $SED -i ":a;N;\$!ba;s/$start_toc.*$end_toc/$toc_block/g" "$1" 186 echo -e "\n Updated content of $appname block in $1 succesfully\n" 187 else 188 $SED -i 1i"$toc_block" "$1" 189 echo -e "\n Created $appname block in $1 succesfully\n" 190 fi 191 192 # undo symbol replacements 193 $SED -i 's,id9992384923423gzz,\&,g' "$1" 194 $SED -i 's,id8239230090230gzz,/,g' "$1" 195 196 } 197 198 main() { 199 check "$1" 200 toc_text=$(toc "$1") 201 insert "$1" "$toc_text" 202 } 203 204 [[ "$0" == "$BASH_SOURCE" ]] && main "$@"