# readreclx: a no-binary readrec substitute for Bash 4.3+
# Just source this file in your scripts
# Created by Luxferre in 2023, released into public domain

rrlx_emit() { # args: UPEF flag, field name, buffer value
  local upef="$1"
  local fieldname="$2"
  local buf="$3"
  local -n realval="$fieldname" # get the field name reference
  if [[ -n "$realval" ]]; then # this variable already exists
    if [[ "$(declare -ap "$fieldname" 2>/dev/null)" =~ ^declare\ -a.* ]]; then
      if (( "$upef" == 1 && "${#realval[@]}" > 0 )); then # update the last entry in the array
        realval[-1]="$buf" # I love Bash 4.3+
      else
        realval+=("$buf") # just append the value
      fi
    else # it's a simple variable
      if [[ "$upef" == 1 ]]; then # just update the previous variable
        declare -g "$fieldname"="$buf"
      else # convert the variable into an array and append the new value
        declare -ag "$fieldname" # now realval should point to the array
        realval+=("$buf") # append the value
      fi
    fi
  else # just create a single-value variable
    declare -g "$fieldname"="$buf"
  fi
}

readreclx() {
  local buf=''  # value buffer
  local fieldname='' # fieldname buffer
  local L='' # input string buffer
  local litf=0 # literal mode flag
  local upef=0 # flag to update the previously emitted field
  local LF=$'\n' # line feed character cache
  while read -r -d "$LF" L; do # read line by line
    if [[ "$litf" == 1 ]]; then # we're in the literal mode
      buf="${buf}${L}" # append the contents
    else
      [[ -z "$L" ]] && break # line empty, end of record
      [[ "$L" =~ ^#.* ]] && continue # it's a comment, skip
      if [[ "$L" =~ ^\+.* ]]; then # newline syntax
        L="${L#+}" # remove the leading +
        L="${L# }" # remove the single leading whitespace
        buf="${buf}${LF}${L}" # add it after the newline
        upef=1 # set UPEF flag
      else # any other values
        upef=0 # reset UPEF flag
        IFS=: read -r -s fieldname buf < <(printf '%s' "$L")
        if [[ "$fieldname" =~ ^[a-zA-Z%][a-zA-Z0-9_]*$ ]]; then # valid field
          # remove the starting whitespace or tab if the buffer starts with it
          if [[ "$buf" == $' '* ]]; then buf="${buf:1}"
          elif [[ "$buf" == $'\t'* ]]; then buf="${buf:1}"
          fi
        else
          printf '%s\n' 'Invalid field name detected, discarding!' 1>&2
          fieldname=''
          buf=''
        fi
      fi
    fi
    # now, check if we need to maintain the literal mode
    if [[ "$buf" == *$'\\' ]]; then # buffer ends with a backspace
      litf=1 # set the literal mode
      buf="${buf:0:-1}" # remove the backspace from buffer
    else # finally, we have the name in fieldname and the value in buf
      litf=0 # unset the literal mode
      if [[ ! -z "$fieldname" ]]; then # perform the actual var assignment
        rrlx_emit "$upef" "$fieldname" "$buf"
      fi
    fi
  done
}
