bopher-ng

A better Gopher client in pure Bash
git clone git://git.luxferre.top/bopher-ng.git
Log | Files | Refs | README | LICENSE

gmi2map.sh (4756B)


      1 #!/bin/bash
      2 # A simple helper tool to create a valid Gophermap from the Gemtext passed via standard input
      3 #
      4 # Usage: cat [file] | gmi2map.sh [page_width] [leading_spaces] [trailing_spaces] [placeholder_char]
      5 # (pass 0 as the reflow width if you want to pass the other parameters but don't want to turn on reflow logic)
      6 #
      7 # Created by Luxferre in 2023, released into public domain
      8 
      9 shopt -s extglob # enable extended pattern matching (just to be sure)
     10 
     11 TARGET_WIDTH="$1"
     12 LSPACES="$2"
     13 TSPACES="$3"
     14 DELIM="$4"
     15 
     16 TAB=$'\t'
     17 SPC=$'\x20'
     18 CRLF=$'\r\n'
     19 
     20 [[ -z "$TARGET_WIDTH" ]] && TARGET_WIDTH=0 # reflow off by default
     21 [[ -z "$LSPACES" ]] && LSPACES=0
     22 [[ -z "$TSPACES" ]] && TSPACES=0
     23 [[ -z "$DELIM" ]] && DELIM=';'
     24 
     25 FORMAT_WIDTH=0 # make formatting width distinct from the target reflow width
     26 (( TSPACES > 0 )) && FORMAT_WIDTH="$TARGET_WIDTH" # and only use it if there are trailing spaces
     27 
     28 # format strings to use in different situations:
     29 reflowfmt="%-$(( LSPACES ))s%-${FORMAT_WIDTH}s%-$(( TSPACES ))s\n" # params: smth, line, smth
     30 infofmt="i%s${TAB}%s${TAB}%s${TAB}0${CRLF}"        # params: line, DELIM, DELIM
     31 gopherlinkfmt="%s%s${TAB}%s${TAB}%s${TAB}%d${CRLF}" # params: type, name, selector, host, port
     32 extlinkfmt="h%s${TAB}URL:%s${TAB}%s${TAB}0${CRLF}" # params: name, URL, DELIM
     33 
     34 reflow_line() { # single-line logic from phlow.sh, adapted into a function and separating by LF only
     35   local line="$1"
     36   local llen="${#line}" # get effective line length
     37   if (( 0 == TARGET_WIDTH || llen < TARGET_WIDTH )); then # no need to run the logic for smaller lines or if TARGET_WIDTH is 0
     38     printf "$reflowfmt" '' "$line" ''
     39     return
     40   fi
     41   local lastws=0 # variable to track last whitespace
     42   local cpos=0 # variable to track current position within the page line
     43   local pagepos=0 # variable to track the position of new line start
     44   local outbuf='' # temporary output buffer
     45   local c='' # temporary character buffer
     46   for ((i=0;i<llen;i++,cpos++)); do # start iterating over characters
     47     c="${line:i:1}" # get the current one
     48     if (( cpos >= TARGET_WIDTH )); then # we already exceeded the page width
     49       (( lastws == 0 )) && lastws=$TARGET_WIDTH # no whitespace encountered here
     50       printf "$reflowfmt" '' "${outbuf:0:$lastws}" '' # truncate the buffer
     51       outbuf=''
     52       pagepos=$(( pagepos + lastws ))
     53       cpos=0
     54       lastws=0
     55       i=$pagepos # update current iteration index from the last valid whitespace
     56     else # save the whitespace position if found
     57       [[ "$c" == "$SPC" ]] && lastws="$cpos"
     58       outbuf="${outbuf}${c}" # save the character itself
     59     fi
     60   done
     61   [[ ! -z "$outbuf" ]] && printf "$reflowfmt" '' "$outbuf" '' # output the last unprocessed chunk
     62 }
     63 
     64 readarray -t LINES -d $'\n' # read the input line array (split by LF)
     65 for line in "${LINES[@]}"; do # iterate over the read text
     66   line="${line%%$'\r'}" # remove a trailing CR if it is there
     67   if [[ "${line:0:2}" == $'=>' ]]; then # we have a linkable resource
     68     linkline="${line##=>*([[:blank:]])}" # remove the link signature and any leading whitespace
     69     linkurl="${linkline%%[[:blank:]]*}" # treat anything until the next whitespace (or the end of line) as a URL
     70     linkdesc="${linkline##${linkurl}*([[:blank:]])}" # remove the URL and any other leading whitespace to get the description
     71     linkdesc="${linkdesc%%*([[:blank:]])}" # remove any trailing whitespace from the description
     72     if [[ "$linkurl" =~ ^gopher:// ]]; then # now, proceed according to the URL type (just like in Bopher-NG)
     73       preurl="${linkurl#gopher://}" # remove the scheme to ease parsing
     74       hostport="${preurl%%/*}" # extract the host+:port part (where :port is also optional)
     75       selpath="${preurl##$hostport}" # extract the selector+path part
     76       reshost="${hostport%%:*}" # extract the hostname
     77       resport="${hostport:(( 1 + ${#reshost} ))}" # extract the port
     78       restype="${selpath:1:1}" # extract the type character
     79       ressel="${selpath:2}" # extract the selector
     80       [[ -z "$resport" ]] && resport=70 # default port is 70
     81       [[ -z "$ressel" ]] && ressel="/" # default selector is root
     82       [[ -z "$restype" ]] && restype=1 # default request type is a Gophermap
     83       printf "$gopherlinkfmt" "$restype" "$linkdesc" "$ressel" "$reshost" "$resport"
     84     else
     85       printf "$extlinkfmt" "$linkdesc" "$linkurl" "$DELIM"
     86     fi
     87   else # we have an info line
     88     infoline='' 
     89     [[ "${line:0:3}" != $'```' ]] && infoline="$line" # ignore the preformatting togglers, pass everything else
     90     readarray -t reflowed_lines -d $'\n' < <(reflow_line "$infoline")
     91     for rline in "${reflowed_lines[@]}"; do # iterate over the reflowed line parts
     92       printf "$infofmt" "$rline" "$DELIM" "$DELIM"
     93     done
     94   fi
     95 done
     96 printf '.\r\n' # finish the Gophermap generation