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 "$@"