#compdef port
# ------------------------------------------------------------------------------
# Description
# -----------
#
#  Completion script for MacPorts 2.12.1 (https://www.macports.org/).
#
# ------------------------------------------------------------------------------
# Authors
# -------
#
#  * Matt Cable <wozz@wookie.net>
#  * Sorin Ionescu <sorin.ionescu@gmail.com>
#  * Aljaž Srebrnič <a2piratesoft@gmail.com>
# -----------------------------------------------------------------------------
# License
# -------
#
# Copyright (c) 2016, Matt Cable, Sorin Ionescu, Aljaž Srebrnič
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#     * Redistributions of source code must retain the above copyright
#       notice, this list of conditions and the following disclaimer.
#     * Redistributions in binary form must reproduce the above copyright
#       notice, this list of conditions and the following disclaimer in the
#       documentation and/or other materials provided with the distribution.
#     * Neither the name of the <organization> nor the
#       names of its contributors may be used to endorse or promote products
#       derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# ------------------------------------------------------------------------------

# global variable
#_port_available_packages
#_port_installed_packages

_port() {
  typeset -A opt_args
  local context state line
  local curcontext="$curcontext"
  local ret=1

  local port_prefix=${commands[port]%/bin/port}

  _arguments -s -C \
    '-v[Verbose mode (generate verbose messages)]' \
    '-d[Debug mode (generate debugging messages, implies -v)]' \
    '-q[Quiet mode (suppress messages)]' \
    '-N[Non-interactive mode]' \
    "-n[Don't follow dependencies in upgrade (affects upgrade and install)]" \
    "-R[Also upgrade dependents (only affects upgrade) ]" \
    '-u[Uninstall inactive ports when upgrading and uninstalling]' \
    '-y[Perform a dry run]' \
    '-s[Source-only mode (build and install from source)]' \
    '-b[Binary-only mode (build and install from binary archives)]' \
    '-c[Autoclean mode (execute clean after install)]' \
    "-k[Keep mode (don't autoclean after install)]" \
    '-p[Despite any errors encountered, proceed to process multiple ports and commands]' \
    '-o[Honor state files even if the Portfile has been modified since]' \
    '-t[Enable trace mode debug facilities on platforms that support it]' \
    '-f[Force mode, ignore state file]' \
    '-D[Specify portdir]:dir:_files -/' \
    '-F[Read and process the file of commands specified by the argument]:file:_files' \
    "1:Port actions:_port_actions" \
    '*::arg:->args' \
    && ret=0

  case "$state" in
    (args)
      local subcmd=$words[1]
      local cache_policy
      zstyle -s ":completion:${curcontext}:" cache-policy cache_policy
      zstyle ":completion:${curcontext}:" cache-policy ${cache_policy:-_port_caching_policy}

      local -a log_phases=('fetch' 'checksum' 'extract' 'patch' 'configure' 'build' 'destroot')
      local -a no_portname_commands=(
        'help' 'diagnose' 'migrate' 'outdated' 'platform' 'provides' 'reclaim' 'restore'
        'rev-upgrade' 'search' 'select' 'selfupdate' 'snapshot' 'sync' 'usage'
      )
      local -a show_installed_package_commands=(
        uninstall upgrade
        activate deactivate
        dependents rdependents
        setrequested unsetrequested setunrequested
        space location contents
      )

      local -a options=()
      case $subcmd in
        (activate|deactivate)
          options+=('--no-exec[Do not execute any stored pre- or post-uninstall procedures]')
          ;;
        (archivefetch)
          options+=('-p[Proceed even if a port fails to fetch]')
          ;;
        (fetch|checksum|extract|patch|configure|build|destroot|test)
          options+=('--no-mirrors[Only fetch files from URLs in master_sites]')
          ;;
        (bump)
          options+=('--patch[Create a Portfile.patch file instead of directly overwriting the Portfile]')
          ;;
        (clean)
          options+=(
            '--archive[Remove partially downloaded binary archives]'
            '--dist[Delete source code archives, the so-called distfiles]'
            '--logs[Delete log files]'
            '--work[Delete the work directory of a port]'
            '--all[Remove all temporary files]'
          )
          ;;
        (contents)
          options+=(
            "--size[Enable printing a human-readable representation of the files' size]"
            '--units[Used in conjunction with --size to choose the unit in which the size is given]:units:_port_units'
          )
          ;;
        (deps|rdeps)
          options+=(
            '--index[Do not read Portfiles, but instead rely solely on the PortIndex information]'
            '--no-build[Exclude dependencies only required at build time]'
            '--no-test[Exclude test dependencies]'
          )

          if [[ $subcmd == "rdeps" ]]; then
            options+=('--full[Display all branches of the dependency tree instead of listing each dependency only once]')
          fi
          ;;
        (diagnose)
          options+=('--quiet[Only display warnings or errors]')
          ;;
        (edit)
          options+=('--editor[Use the specified editor]:editor')
          ;;
        (help|usage)
          options+=('1:action:_port_actions')
          ;;
        (info)
          options+=(
            '--index[Do not read the Portfile, but rely solely on the port index information]'
            '--line[Print a single line for each port]'
            '--pretty[Format the output in a convenient, human-readable fashion]'
            '(--category --categories)'{--category,--categories}'[List the categories of a port]'
            '--conflicts[List other ports that cannot be activate at the same time as the given port]'
            '--depends_fetch[List the fetch dependencies of a port]'
            '--depends_extract[List the extract dependencies of a port]'
            '--depends_build[List the build dependencies of a port]'
            '--depends_lib[List the lib dependencies of a port]'
            '--depends_run[List the run dependencies of a port]'
            '--depends[List the all dependencies of a port]'
            '(--description --long_description)--description[Print the short description of a port]'
            '(--description --long_description)--long_description[Print the long description of a port]'
            '--epoch[List the epoch components of a MacPorts]'
            '--version[List the version components of a MacPorts]'
            '--revision[List the revision components of a MacPorts]'
            '--fullname[Print name and the full MacPorts version tuple]'
            '--heading[Like --fullname but including the categories]'
            '--homepage[List the homepage of a port]'
            '--license[Print the license that applies to the source code of a port]'
            '(--maintainer --maintainers)'{--maintainer,--maintainers}"[List the email address(es) of a port's maintainer(s)]"
            '--name[Print the name of a port]'
            '--pathfiles[List the patches that will be applied to the port]'
            '(--platform --platforms)'{--platform,--platforms}'[List the platforms supported by a port]'
            "--portdir[Print the path to a port's directory]"
            '--replaced_by[List the name of the port that replaces a port]'
            "--subports[Print a list of subports defined by this port's Portfile]"
            '(--variant --variants)'{--variant,--variants}'[List the variants defined by a port by name]'
          )
          ;;
        (install)
          options+=(
            '--allow-failing[Attempt installation even if the specified port is known to fail]'
            '--no-replace[Attempt to install specified port even if it is replaced by another port]'
            '--no-rev-upgrade[Do not run rev-upgrade after installation]'
            '--unrequested[Do not mark the installed ports as requested]'
          )
          ;;
        (lint)
          options+=('--nitpick[Enable additional checks that are mostly whitespace-related and best practices]')
          ;;
        (log)
          options+=(
            '--level[Log level]:level:(error warn msg info debug)'
            '--phase[Only print messages that were generated in the given installation phase]:phase:($log_phases)'
          )
          ;;
        (migrate)
          options+=(
            '--all[Migrate all ports including unrequested ones]'
            '--continue[Continue with migration of ports]'
          )
          ;;
        (mirror)
          options+=('--new[Delete the existing database of mirrored files and re-create it from scratch]')
          ;;
        (provides)
          options+=('*::file:_files')
          ;;
        (reclaim)
          options+=(
            '--keep-build-deps[Consider build-time dependency ports to be considered needed]'
            '(--enable-reminders --disable-reminders)--enable-reminders[Enable regular reminders to run "port reclaim"]'
            '(--enable-reminders --disable-reminders)--disable-reminders[Disable reminders to run "port reclaim"]'
          )
          ;;
        (restore)
          options+=(
            '--snapshot-id[Restore the snapshot with the specified ID]:id:_port_snapshot_ids'
            '--last[Restore the last created snapshot]'
            '--all[Restore all ports in the snapshot including unrequested ones]'
          )
          ;;
        (rev-upgrade)
          options+=('--id-loadcmd-check[Check the ID load command in each library installed by MacPorts]')
          ;;
        (search)
          options+=(
            '--case-sensitive[Do not ignore case when searching for the given keyword]'
            '(--exact --glob --regex)--exact[Search for the given string exactly]'
            '(--exact --glob --regex)--glob[Treat the keyword(s) as wildcard]'
            '(--exact --glob --regex)--regex[Treat the given string as a Tcl regular expression]'
            '--line[Print one match per line]'
            '(--category --categories)'{--category,--categories}'[Search the category]'
            '--depends_fetch[Search the fetch dependencies of a port]'
            '--depends_extract[Search the extract dependencies of a port]'
            '--depends_build[Search the build dependencies of a port]'
            '--depends_lib[Search the lib dependencies of a port]'
            '--depends_run[Search the run dependencies of a port]'
            '--depends[Search the all dependencies of a port]'
            '(--description --long_description)--description[Test the search string against the description]'
            '(--description --long_description)--long_description[Test the search string against the long description]'
            '--homepage[Search for the keyword(s) in the homepage property]'
            '(--maintainer --maintainers)'{--maintainer,--maintainers}"[Search for maintainers]"
            '--name[Search in ports name]'
            "--portdir[Test the search string against the path of the directory that contains the port]"
            '(--variant --variants)'{--variant,--variants}'[Search for variant names]'
          )
          ;;
        (select)
          options+=(
            '--summary[Display summary of selected options]'
            '--list[List available versions for the group]'
            '--set[Select the given version for the group]'
            '--show[Show which version is currently selected for the group (default if none given)]'
            '1:group:_port_select_groups'
            '2:variant:_port_select_variants'
          )
          ;;
        (selfupdate)
          options+=(
            '--no-sync[Only check for updates and install if available]'
            '--migrate[Rebuild even if no new version is available]'
            '--rsync[Use the older rsync method for downloading the new version of MacPorts]'
          )
          ;;
        (snapshot)
          options+=(
            '--create[Create a new snapshot that records the current active ports]'
            '--note[Label with the given note when creating a snapshot]:note'
            '--list[Display a list of all snapshots that currently exist]'
            '--diff[Display the differences between the given snapshot ID and currently installed ports]:id:_port_snapshot_ids'
            '--all[Consider all ports when displaying diffs]'
            '--delete[Delete the snapshot with the given ID number]:id:_port_snapshot_ids'
            '--export[Generate a JSON representation of the snapshot with the given ID number]:id:_port_snapshot_ids'
            '--import[Create a new snapshot from the information in the given file]:file:_files'
            '(- *)--help[Display brief usage information]'
          )
          ;;
        (space)
          options+=(
            '--total[Only print the total amount of space used by all given ports]'
            '--units[Choose the unit in which the size is given]:unit:_port_units'
          )
          ;;
        (uninstall)
          options+=(
            '--follow-dependents[Also uninstall all ports recursively depending directly or indirectly]'
            '--follow-dependencies[Also recursively uninstall all ports that the given port depends on]'
            '--no-exec[Do not execute any stored pre- or post-uninstall procedures]'
          )
          ;;
        (upgrade)
          options+=(
            '--enforce-variants[Upgrade all given ports and their dependencies]'
            '--force[Ignore circumstances that would normally cause ports to be skipped]'
            '--no-replace[Do not automatically install ports that replace a new-obsolete port you have installed]'
            '--no-rev-upgrade[Do not run rev-upgrade after upgrading]'
          )
          ;;
        (variants)
          options+=('--index[Do not read the Portfile]')
          ;;
      esac

      if (( $no_portname_commands[(Ie)$subcmd] )); then
        _arguments \
          $options[@] \
          && ret=0
      else
        local -a pseudo_port_names=(
          all current active inactive actinact installed uninstalled outdated
          obsolete requested unrequested leaves rleaves
        )

        local -a selectors=(
          'variants:' 'variant:' 'description:' 'depends:'
          'depends_lib:' 'depends_run:' 'depends_build:' 'depends_fetch:' 'depends_extract:'
          'portdir:' 'homepage:' 'epoch:' 'platforms:' 'platform:' 'name:' 'long_description:'
          'maintainers:' 'maintainer:' 'categories:' 'category:' 'version:' 'revision:' 'license:'
          'depof:' 'rdepof:' 'rdepends:' 'dependentof:' 'rdependentof:'
        )

        if (( $show_installed_package_commands[(Ie)$subcmd] )); then
          _port_update_cache "installed"

          _arguments \
            $options[@] \
            '1: :_port_installed_ports' \
            '*: :_port_options' \
            && ret=0
        else
          _port_update_cache "all"

          _arguments \
            $options[@] \
            '1: :_port_available_ports' \
            '*: :_port_options' \
            && ret=0
        fi
      fi

      ;;
  esac

  return ret
}

(( $+functions[_port_actions] )) ||
_port_actions() {
  local -a actions=(
    'activate:Activate the given ports'
    'archive:Archive the given ports, i.e. install the port image but do not activate'
    'archivefetch:Fetch archive for the given ports'
    'build:Build the given ports'
    'bump:Update the outdated checksums of a Portfile'
    'cat:Writes the Portfiles of the given ports to stdout'
    'checksum:Compares the checksums for the downloaded files of the given ports'
    'clean:Removes files associated with the given ports'
    'configure:Configure the given ports'
    'contents:Returns a list of files installed by given ports'
    'deactivate:Deactivates the given ports'
    'dependents:Returns a list of installed dependents for each of the given ports'
    'deps:Display a dependency listing for the given ports'
    'destroot:Destroot the given ports'
    'diagnose:Detects common issues'
    'dir:Returns the directories of the given ports'
    'distcheck:Checks if the given ports can be fetched from all of its master_sites'
    'distfiles:Returns a list of distfiles for the given port'
    'dmg:Creates a dmg for each of the given ports'
    'echo:Returns the list of ports the argument expands to'
    'edit:Edit given ports'
    'extract:Extract the downloaded files of the given ports'
    'fetch:Downloaded distfiles for the given ports'
    'file:Returns the path to the Portfile for each of the given ports'
    'gohome:Opens the homepages of the given ports in your browser'
    'help:Displays short help texts for the given actions'
    'info:Returns information about the given ports '
    'install:Installs the given ports'
    'installed:List installed versions of the given port, or all installed ports if no port is given'
    'lint:Checks if the Portfile is lint-free for each of the given ports'
    'list:List the available version for each of the given ports'
    'livecheck:Checks if a new version of the software is available'
    'load:Interface to launchctl(1) for ports providing startup items'
    'location:Returns the install location for each of the given ports'
    'log:Shows main log for given ports'
    'logfile:Returns the log file path for each of the given ports'
    'mdmg:Creates a dmg containing an mpkg for each of the given ports and their dependencies'
    'migrate:Update MacPorts for a new platform'
    'mirror:Fetches distfiles for the given ports'
    'mpkg:Creates an mpkg for each of the given ports and their dependencies'
    'notes:Displays informational notes for each of the given ports'
    'outdated:Returns a list of outdated ports'
    'patch:Applies patches to each of the given ports'
    'pkg:Creates a pkg for each of the given ports'
    'platform:Returns the current platform that port is running on'
    'provides:Return which port provides each of the files given'
    'rdependents:Recursive version of dependents'
    'rdeps:Display a recursive dependency listing for the given ports'
    'reclaim:Reclaims disk space'
    'reload:Restart daemon'
    'restore:Restore snapshots of installed ports'
    'rev-upgrade:Scan for broken binaries in the installed ports and rebuild them as needed'
    'search:Search for a port'
    'select:Select between multiple versions of a versioned port'
    'selfupdate:Upgrade MacPorts itself and run the sync target'
    'setrequested:Marks each of the given ports as requested'
    'setunrequested:Marks each of the given ports as unrequested'
    'snapshot:Create and manage snapshots of installed ports'
    'space:Show the disk space used by the given ports'
    'sync:Synchronize the set of Portfiles'
    'test:Run tests on each of the given ports'
    'unarchive:Unarchive the destroot of the given ports from installed images'
    'uninstall:Uninstall the given ports'
    'unload:Interface to launchctl(1) for ports providing startup items'
    'unsetrequested:Marks each of the given ports as unrequested'
    'upgrade:Upgrades the given ports to the latest version'
    'url:Returns the URL for each of the given ports'
    'usage:Returns basic usage of the port command'
    'variants:Returns a list of variants provided by the given ports, with descriptions if present'
    'version:Returns the version of MacPorts'
    'work:Returns the path to the work directory for each of the given ports'
  )

  _describe -t actions 'actions' actions
}

(( $+functions[_port_options] )) ||
_port_options() {
  local current_arg=$words[CURRENT]

  # complete variants
  if [[ $current_arg == +* || $current_arg == -* ]]; then
    IPREFIX=$current_arg[1]
    PREFIX=$current_arg[2,-1]
    _port_variants $words[2]
  fi
}

(( $+functions[_port_available_ports] )) ||
_port_available_ports() {
  _alternative \
    'ports:Available ports:($_port_available_packages)' \
    'pseudo-common:Common Pseudo-portnames:($pseudo_port_names)' \
    'selectors:Pseudo portname selectors:($selectors)'
}

(( $+functions[_port_installed_ports] )) ||
_port_installed_ports() {
  _alternative \
    "ports:Installed ports:($_port_installed_packages)" \
    "pseudo-common:Common Pseudo-portnames:($pseudo_port_names)" \
    "selectors:Pseudo portname selectors:($selectors)"
}

(( $+functions[_port_caching_policy] )) ||
_port_caching_policy() {
  local reg_time comp_time check_file

  zmodload -F zsh/stat b:zstat 2> /dev/null
  case "${1##*/}" in
    (PORT_INSTALLED_PACKAGES)
      check_file=$port_prefix/var/macports/registry/registry.db
      ;;
    (PORT_AVAILABLE_PACKAGES)
      check_file=${$(port dir MacPorts)%/*/*}/PortIndex
      ;;
  esac

  reg_time=$(zstat +mtime $check_file)
  comp_time=$(zstat +mtime $1)
  return $(( reg_time < comp_time ))
}

(( $+functions[_port_update_cache] )) ||
_port_update_cache() {
  local cache_type=$1

  case "$cache_type" in
    (all)
      # Cache the list of all ports.
      if ( [[ ${+_port_available_packages} -eq 0 ]] || _cache_invalid PORT_AVAILABLE_PACKAGES ) &&
           ! _retrieve_cache PORT_AVAILABLE_PACKAGES;
      then
        _port_available_packages=( $(_call_program path-all "port -q echo all") )
        _store_cache PORT_AVAILABLE_PACKAGES _port_available_packages
      fi
      ;;
    (installed)
      if ( [[ ${+_port_installed_packages} -eq 0 ]] || _cache_invalid PORT_INSTALLED_PACKAGES ) &&
           ! _retrieve_cache PORT_INSTALLED_PACKAGES;
      then
        _port_installed_packages=( $(_call_program path-all "port -q echo installed") )
        _store_cache PORT_INSTALLED_PACKAGES _port_installed_packages
      fi
      ;;
  esac
}

(( $+functions[_port_select_groups] )) ||
_port_select_groups() {
  local -a groups=($port_prefix/etc/select/*(N:t))
  _describe "Port select groups" groups
}

(( $+functions[_port_select_variants] )) ||
_port_select_variants() {
  local group=$words[2]
  local -a variants=("${(f)$(port select --list $group | awk '/^[ \t]+/{print $1}')}")
  _describe "Port select group $words[4] variants" variants
}

(( $+functions[_port_snapshot_ids] )) ||
_port_snapshot_ids() {
  local -a snapshot_ids=()
  if (( $+commands[perl] )); then
    local perl_expr='m{^\s+(\d+)\s+(.*)$} and printf "%s:%s\n", $1, $2'
    snapshot_ids=("${(f)$(port snapshot --list | perl -wln -e $perl_expr)}")
  else
    local awk_expr='NR > 2 { printf("%s:%s %s\n", $1, $2, $3) }'
    snapshot_ids=("${(f)$(port snapshot --list | awk $awk_expr)}")
  fi

  _describe "Port snapshot IDs" snapshot_ids
}

(( $+functions[_port_variants] )) ||
_port_variants() {
  local name=$1
  if (( $+commands[perl] )); then
    local -a variants=($(port variants $name | perl -wln -e 'm{^(?:\s+|\[\+\])([^:]+):} and print $1'))
    if [[ ${#variants} != 0 ]]; then
      _values 'variant' $variants
    fi
  fi
}

(( $+functions[_port_units] )) ||
_port_units() {
  local -a units=(
    "B[List sizes in bytes]"
    {K,Ki,KiB}"[List sizes in KiB, 1024 bytes]"
    {Mi,MiB}'[List sizes in MiB, 1024 * 1024 bytes]'
    {Gi,GiB}'[List sizes in GiB, 1024 * 1024 * 1024 bytes]'
    {k,kB}'[List sizes in kB, 1000 bytes]'
    {M,MB}'[List sizes in kB, 1000 * 1000 bytes]'
    {G,GB}'[List sizes in kB, 1000 * 1000 * 1000 bytes]'
  )
  _values 'unit' $units
}

_port "$@"

# Local Variables:
# mode: Shell-Script
# sh-indentation: 2
# indent-tabs-mode: nil
# sh-basic-offset: 2
# End:
# vim: ft=zsh sw=2 ts=2 et
