#!/bin/bash

shopt -s extglob

array_build() {
  local dest=$1 src=$2 i keys values

  # it's an error to try to copy a value which doesn't exist.
  declare -p "$2" &>/dev/null || return 1

  # Build an array of the indicies of the source array.
  eval "keys=(\"\${!$2[@]}\")"

  # Clear the destination array
  eval "$dest=()"

  # Read values indirectly via their index. This approach gives us support
  # for associative arrays, sparse arrays, and empty strings as elements.
  for i in "${keys[@]}"; do
    values+=("printf -v '$dest[$i]' %s \"\${$src[$i]}\";")
  done

  eval "${values[*]}"
}

funcgrep() {
  { declare -f "$1" || declare -f package; } 2>/dev/null | grep -E "$2"
}

extract_global_var() {
  # $1: variable name
  # $2: multivalued
  # $3: name of output var

  local attr=$1 isarray=$2 outputvar=$3

  if (( isarray )); then
    declare -n ref=$attr
    # Still need to use array_build here because we can't handle the scoping
    # semantics that would be included with the use of 'declare -n'.
    [[ ${ref[@]} ]] && array_build "$outputvar" "$attr"
  else
    [[ ${!attr} ]] && printf -v "$outputvar" %s "${!attr}"
  fi
}

extract_function_var() {
  # $1: function name
  # $2: variable name
  # $3: multivalued
  # $4: name of output var

  local funcname=$1 attr=$2 isarray=$3 outputvar=$4 attr_regex= decl= r=1

  if (( isarray )); then
    printf -v attr_regex '^[[:space:]]* %s\+?=\(' "$2"
  else
    printf -v attr_regex '^[[:space:]]* %s\+?=[^(]' "$2"
  fi

  while read -r; do
    # strip leading whitespace and any usage of declare
    decl=${REPLY##*([[:space:]])}
    eval "${decl/#$attr/$outputvar}"

    # entering this loop at all means we found a match, so notify the caller.
    r=0
  done < <(funcgrep "$funcname" "$attr_regex")

  return $r
}

pkgbuild_get_attribute() {
  # $1: package name
  # $2: attribute name
  # $3: multivalued
  # $4: name of output var

  local pkgname=$1 attrname=$2 isarray=$3 outputvar=$4

  printf -v "$outputvar" %s ''

  if [[ $pkgname ]]; then
    extract_global_var "$attrname" "$isarray" "$outputvar"
    extract_function_var "package_$pkgname" "$attrname" "$isarray" "$outputvar"
  else
    extract_global_var "$attrname" "$isarray" "$outputvar"
  fi
}

srcinfo_open_section() {
  printf '%s = %s\n' "$1" "$2"
}

srcinfo_close_section() {
  echo
}

srcinfo_write_attr() {
  # $1: attr name
  # $2: attr values

  local attrname=$1 attrvalues=("${@:2}")

  # normalize whitespace, strip leading and trailing
  attrvalues=("${attrvalues[@]//+([[:space:]])/ }")
  attrvalues=("${attrvalues[@]#[[:space:]]}")
  attrvalues=("${attrvalues[@]%[[:space:]]}")

  printf "\t$attrname = %s\n" "${attrvalues[@]}"
}

pkgbuild_extract_to_srcinfo() {
  # $1: pkgname
  # $2: attr name
  # $3: multivalued

  local pkgname=$1 attrname=$2 isarray=$3 outvalue=

  if pkgbuild_get_attribute "$pkgname" "$attrname" "$isarray" 'outvalue'; then
    srcinfo_write_attr "$attrname" "${outvalue[@]}"
  fi
}

srcinfo_write_section_details() {
  local attr package_arch a
  local multivalued_arch_attrs=(source provides conflicts depends replaces
                                optdepends makedepends checkdepends
                                {md5,sha{1,224,256,384,512}}sums)

  for attr in "${singlevalued[@]}"; do
    pkgbuild_extract_to_srcinfo "$1" "$attr" 0
  done

  for attr in "${multivalued[@]}"; do
    pkgbuild_extract_to_srcinfo "$1" "$attr" 1
  done

  pkgbuild_get_attribute "$1" 'arch' 1 'package_arch'
  for a in "${package_arch[@]}"; do
    # 'any' is special. there's no support for, e.g. depends_any.
    [[ $a = any ]] && continue

    for attr in "${multivalued_arch_attrs[@]}"; do
      pkgbuild_extract_to_srcinfo "$1" "${attr}_$a" 1
    done
  done
}

srcinfo_write_global() {
  local singlevalued=(pkgdesc pkgver pkgrel epoch url install changelog)
  local multivalued=(arch groups license checkdepends makedepends
                     depends optdepends provides conflicts replaces
                     noextract options backup
                     source {md5,sha{1,224,256,384,512}}sums)

  srcinfo_open_section 'pkgbase' "${pkgbase:-$pkgname}"
  srcinfo_write_section_details ''
  srcinfo_close_section
}

srcinfo_write_package() {
  local singlevalued=(pkgdesc url install changelog)
  local multivalued=(arch groups license checkdepends depends optdepends
                     provides conflicts replaces options backup)

  srcinfo_open_section 'pkgname' "$1"
  srcinfo_write_section_details "$1"
  srcinfo_close_section
}

srcinfo_write() {
  local pkg

  srcinfo_write_global

  for pkg in "${pkgname[@]}"; do
    srcinfo_write_package "$pkg"
  done
}

clear_environment() {
  local environ

  mapfile -t environ < <(compgen -A variable |
      grep -xvF "$(printf '%s\n' "$@")")

  # expect that some variables marked read only will complain here
  unset -v "${environ[@]}" 2>/dev/null
}

srcinfo_write_from_pkgbuild() {(
  clear_environment PATH

  shopt -u extglob
  . "$1" || exit 1
  shopt -s extglob
  srcinfo_write
)}


usage() {
  printf '%s\n' \
      'mksrcinfo v8' \
      '' \
      'mksrcinfo reads the target PKGBUILD and writes an equivalent .SRCINFO.' \
      'Without passing any arguments, mksrcinfo will read from $PWD/PKGBUILD' \
      'and write to $PWD/.SRCINFO.' \
      '' \
      'Usage: mksrcinfo [/path/to/pkgbuild]' \
      '    -h                display this help message and exit' \
      '    -o <file>         write output to <file>'
}

error() {
  printf "==> ERROR: $1\n" "${@:2}" >&2
}

die() {
  error "$@"
  exit 1
}

write_srcinfo_header() {
  local timefmt='%a %b %e %H:%M:%S %Z %Y'

  # fiddle with the environment to ensure we get the a consistent language and
  # timezone.
  TZ=UTC LC_TIME=C \
    printf "# Generated by mksrcinfo %s\n# %($timefmt)T\n" 'v8' -1
}

srcinfo_dest=$PWD/.SRCINFO

while getopts ':o:h' flag; do
  case $flag in
    o)
      srcinfo_dest=$OPTARG
      ;;
    :)
      die "option '-%s' requires an argument" "$OPTARG"
      ;;
    h)
      usage
      exit 0
      ;;
    \?)
      die "invalid option -- '-%s' (use -h for help)" "$OPTARG"
      ;;
  esac
done
shift $(( OPTIND - 1 ))

[[ -f PKGBUILD ]] || die 'PKGBUILD not found in current directory'

# TODO: replace this with 'makepkg --printsrcinfo' once makepkg>=4.3 is released.
{
  write_srcinfo_header
  srcinfo_write_from_pkgbuild "${1:-$PWD/PKGBUILD}"
} >"$srcinfo_dest"

# vim: set et ts=2 sw=2:
