#!/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
)}


# TODO: remove --ignorearch once pacman>=4.2.x is sufficiently commonplace.
makepkg_args=('--source' '--ignorearch')
buildfile=./PKGBUILD

tmpdirs=()
trap 'rm -rf "${tmpdirs[@]}"' EXIT

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

warn() {
  printf "WARNING: $1\n" "${@:2}" >&2
}

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

usage() {
  printf '%s\n' \
      'mkaurball v8' \
      '' \
      'mkaurball wraps the process of creating a source tarball with an' \
      '.SRCINFO file, suitable for submission to the AUR.' \
      '' \
      'Usage: mkaurball [options]' \
      '' \
      '    -a <file>         package <file> as .SRCINFO' \
      '    -e                edit .SRCINFO before repackaging' \
      '    -f                pass the --force flag to makepkg' \
      '    -p <file>         use <file> as PKGBUILD' \
      '    -h                display this help message and exit' \
      '    -m                pass -the --nocolor flag to  makepkg' \
      '' \
      '    --skipchecksums   Do not verify checksums of the source files' \
      '    --skipinteg       Do not perform any verification checks on source files' \
      '    --skippgpcheck    Do not verify source files with PGP signatures'
}

mktempdir() {
  local _d

  _d=$(mktemp -d --tmpdir mkaurball.XXXXXX) || return 1
  tmpdirs+=("$_d")

  printf -v "$1" '%s' "$_d"
}

fakeroot() {
  if type -p fakeroot >/dev/null; then
    command fakeroot "$@"
  else
    "$@"
  fi
}

mkaurball() {
  local tarball_basename= tarball_fullname= tmpdir=

  if ! . -- "$buildfile"; then
    die 'Unable to source %s/%s' "$PWD" "$buildfile"
  fi

  if ! makepkg "${makepkg_args[@]}"; then
    die 'makepkg exited non-zero'
  fi

  tarball_basename=${pkgbase:-$pkgname}
  tarball_fullname=$tarball_basename-${epoch:+$epoch:}$pkgver-$pkgrel.src.tar.gz

  if [[ ! -f $tarball_fullname ]]; then
    die 'Expected tarball name not found: %s' "$tarball_fullname"
  fi

  mktempdir 'tmpdir' || return

  if ! bsdtar -C "$tmpdir" -xf "$tarball_fullname"; then
    die 'Failed to unpack tarball'
  fi

  if [[ $srcinfo_path ]]; then
    if ! cp "$srcinfo_path" "$tmpdir/$tarball_basename/.SRCINFO"; then
      die 'Failed to add %s to tarball' "$srcinfo_path"
    fi
  else
    if ! srcinfo_write >"$tmpdir/$tarball_basename/.SRCINFO"; then
      die 'Failed to write .SRCINFO'
    fi
  fi

  if (( edit_srcinfo )); then
    "${VISUAL:-${EDITOR:-vi}}" "$tmpdir/$tarball_basename/.SRCINFO"
  fi

  # chmod files before re-tarring. This enforces that all files are 644 or 755.
  if ! LANG=C fakeroot -- sh -c 'chmod og=rX -R "$1/$2" && bsdtar -C "$1" -czLf "$3" "$2"' \
      _ "$tmpdir" "$tarball_basename" "$tarball_fullname"; then
    die 'Failed to repack tarball'
  fi
}

warn_deprecated() {
  warn "The %s option is deprecated and will be removed in a future version" "$1"
}

if hash makepkg && grep -qw SRCINFO "$(hash -t makepkg 2>/dev/null)"; then
  use_makepkg=1
else
  use_makepkg=0
fi

while getopts ':a:efmp:h-:' flag; do
  case $flag in
    a)
      if (( use_makepkg )); then
        warn_deprecated "-a"
        use_makepkg=0
      fi
      srcinfo_path=$OPTARG
      ;;
    e)
      if (( use_makepkg )); then
        warn_deprecated "-e"
        use_makepkg=0
      fi
      edit_srcinfo=1
      ;;
    f)
      makepkg_args+=('--force')
      ;;
    m)
      makepkg_args+=('--nocolor')
      ;;
    p)
      buildfile=$OPTARG
      ;;
    h)
      usage
      exit 0
      ;;
    -)
      # pass through some long options that makepkg defines
      case $OPTARG in
        skipinteg|skippgpcheck|skipchecksums)
          makepkg_args+=("--$OPTARG")
          ;;
        *)
          die "invalid option -- '--%s' (use -h for help)" "$OPTARG"
          ;;
      esac
      ;;
    :)
      die "option '-%s' requires an argument" "$OPTARG"
      ;;
    \?)
      die "invalid option -- '-%s' (use -h for help)" "$OPTARG"
      ;;
  esac
done
makepkg_args+=("-p" "$buildfile")

mktempdir BUILDDIR || exit
export BUILDDIR

if (( use_makepkg )); then
  makepkg "${makepkg_args[@]}" -S
else
  mkaurball
fi

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