#!/bin/bash
#
# list_apps_in_pkg
#

###
### settings
###

set -e                # exit on any uncaught error
set +o histexpand     # don't expand history expressions
shopt -s nocasematch  # case-insensitive regular expressions

###
### global variables
###

opt_lax=''
opt_pkg=''
pkgdir=''

# prefer GNU xargs
xargs="$(/usr/bin/which gxargs || printf '/usr/bin/xargs')"

###
### functions
###

warn () {
    local message="$@"
    message="${message//\\t/$'\011'}"
    message="${message//\\n/$'\012'}"
    message="${message%"${message##*[![:space:]]}"}"
    printf "%s\n" "$message" 1>&2
}

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

app_source_1 () {
    /usr/bin/find "$pkgdir" -name PackageInfo -print0 |   \
    "$xargs" -0 /usr/bin/perl -0777 -ne                   \
      'while (m{<pkg-info[^\n]*install-location="/Applications".*?path\s*=\s*"([^"]+)"}sg) { my $p = $1; $p =~ s{\A.*/}{}; print "$p\n" }';
}

app_source_2 () {
    /usr/bin/find "$pkgdir" -name PackageInfo -print0  |  \
    "$xargs" -0 /usr/bin/perl -0777 -ne                   \
      'while (m{<pkg-info[^\n]*install-location="/".*?path\s*=\s*"./Applications/([^"]+)"}sg) { my $p = $1; $p =~ s{\A.*/}{}; print "$p\n" }';
}

app_source_3 () {
    /usr/bin/find "$pkgdir" -type d -name '*.app' |       \
    perl -pe 's{\A.*/}{}';
}

app_source_4 () {
    /usr/bin/find "$pkgdir" -name PackageInfo -print0  |  \
    "$xargs" -0 /usr/bin/perl -0777 -ne                   \
      'while (m{path\s*=\s*"([^"]+\.app)"}sg) { my $p = $1; $p =~ s{\A.*/}{}; print "$p\n" }';
}

app_source_5 () {
    /usr/bin/find "$pkgdir" -name Archive.pax.gz -print0  |           \
    "$xargs" -0 -n1 -I{} /bin/bash -c                                 \
      "/usr/bin/gunzip -c '{}' | /bin/pax | /usr/bin/egrep '\.app'" | \
    /usr/bin/perl -pe 's{\A.*/([^/]+\.app).*\Z}{$1}';
}

merge_sources () {
    /usr/bin/sort | /usr/bin/uniq
}

mark_up_sources () {
    /usr/bin/perl -pe 's{\n}{\000}sg' |                     \
    "$xargs" -0 -I{} -n1 /bin/bash -c                       \
      'printf "{}"; /bin/test -n "$(/usr/bin/find /Applications -type d -maxdepth 3 -name "{}" -print0; /usr/bin/find ~/Applications -type d -maxdepth 3 -name "{}")" && printf " (+)"; printf "\n"'
}

process_args () {
    local arg
    if [[ "$#" -eq 0 ]]; then
        die "ERROR: A file argument is required"
    else
        opt_pkg="${!#}"   # last arg
    fi
    for arg in "$@"; do
        if [[ $arg =~ ^-+h(elp)?$ ]]; then
            printf "list_apps_in_pkg [ -lax ] <file.pkg>

Given a package file, extract a list of candidate App names from
inside the pkg, which may be useful for naming a Cask.

The given package file need not be installed.

If an App of the listed name is already installed in /Applications
or ~/Applications, it will be followed by a plus symbol '(+)' in
the output.  This can be verified via 'ls' or the Finder.

Arguments

   -lax   Be less selective in looking for App names.  Generate
          more, but less accurate, guesses.

Bugs: This script is imperfect.
 - It does not fully parse PackageInfo files
 - An App can be hidden within a nested archive and not found
 - Some pkg files simply don't contain any Apps

See CONTRIBUTING.md and 'man pkgutil' for more information.

"
            exit
        elif [[ $arg =~ ^-+lax$ ]]; then
            opt_lax='true'
        elif [[ "$arg" = "$opt_pkg" ]]; then
            true
        else
            die "ERROR: Unknown argument '$arg'"
        fi
    done
    if [[ -h "$opt_pkg" ]]; then
        opt_pkg="$(/usr/bin/readlink "$opt_pkg")"
    fi
    if ! [[ -e "$opt_pkg" ]]; then
        die "ERROR: No such pkg file: '$opt_pkg'"
    fi
}

###
### main
###

_list_apps_in_pkg () {

    if [[ -d "$opt_pkg" ]]; then
        pkgdir="$opt_pkg"
    else
        local tmpdir="$(/usr/bin/mktemp -d -t list_ids_in_pkg)"
        trap "/bin/rm -rf -- '$tmpdir'" EXIT
        pkgdir="$tmpdir/unpack"
        /usr/sbin/pkgutil --expand "$opt_pkg" "$tmpdir/unpack" "$pkgdir"
    fi

    {
      # strings that look like App names (Something.app)
      app_source_1;
      app_source_2;
      app_source_3;
      if [[ -n "$opt_lax" ]]; then
          app_source_4;
          app_source_5;
      fi
    } |                \
    merge_sources |    \
    mark_up_sources
}

process_args "${@}"

# dispatch main
_list_apps_in_pkg

#
