#compdef dub

# Useful help:
# https://github.com/zsh-users/zsh-completions/blob/master/zsh-completions-howto.org
# http://zsh.sourceforge.net/Doc/Release/Completion-System.html
# http://zdharma.org/Zsh-100-Commits-Club/Zsh-Native-Scripting-Handbook.html
#
# Completions installed on your system, e.g. for MacOSX + Homebrew users:
# /usr/local/Cellar/zsh/$VERSION/share/zsh/functions
# The GIT completion is quite amazing (and equally complex)
# The CVS completion is much easier to grok (e.g. for function dispatch)


# Entry point
_dub() {
    # TODO:
    # - Handle registry URLs
    # - Handle multiple dub (e.g. ./bin/dub add [TAB])
    # - Interactively query configuration (for -c and --override-config)
    #   => Dub does not currently support this
    # - Add ability to provide version, e.g. vibe-d@0.8.6 (see dub add)
    # - Get registry packages if it doesn't make us lag
    # - Query compilers

    # Note that global arguments won't show up when completing commands
    # This is on purpose, to reduce the amount of options being shown during completion,
    # as users are much more likely to be looking for command-specific options.
    _arguments -S -C \
        '(* : -)'{-h,--help}'[Display general or command specific help and exit]' \
        '(* : -)--version[Print version information and exit]' \
        \
        '--root=[Run as if dub was started in given path]: :_directories' \
        '--skip-registry=[Skips searching packages on certain repositories]:mode:(none standard configured all)' \
        '--registry=[Search the given registry URL first when resolving dependencies]:registry URL:_urls' \
        '--bare[Read only packages contained in the current directory]' \
        '--cache=[Puts any fetched packages in the specified location]:cache location:(local|system|user)' \
        '--annotate[Do not perform any action, just print what would be done]' \
        \
        + '(verbosity)' \
        '--vquiet[Print no messages]' \
        '--verror[Only print errors]' \
        {-q,--quiet}'[Only print warnings and errors]' \
        {-v,--verbose}'[Print diagnostic output]' \
        '--vverbose[Print debug output]' \
        \
        '--[End of dub arguments, the following will be sent to the program]' \
        '*::dub command:_command_dispatch'
}


# Command dispatch function
_command_dispatch() {
    declare -a commands=(
        init:'Initialize a new dub package'
        run:'Build and run a dub package (default action)'
        build:'Build a dub package (by name, or in the working directory by default)'
        test:'Execute the tests of a dub package'
        generate:'Generates project files using the specified generator'
        describe:'Prints a JSON description of the project and its dependencies'
        clean:'Removes intermediate build files and cached build results'
        dustmite:'Create reduced test cases for build errors'

        fetch:'Manually retrieves and caches a package'
        add:'Adds dependencies to the package file'
        remove:'Removes a cached package'
        upgrade:'Forces an upgrade of the dependencies'
        add-path:'Adds a default package search path'
        remove-path:'Removes a package search path'
        add-local:'Adds a local package directory (e.g. a git repository)'
        remove-local:'Removes a local package directory'
        list:'Prints a list of all local packages dub is aware of'
        search:'Search for available packages'
        add-override:'Adds a new package override'
        remove-override:'Removes an existing package override'
        list-overrides:'Prints a list of all local package overrides'
        clean-caches:'Removes cached metadata'
        convert:'Converts the file format of the package recipe'
    )
    if (( CURRENT == 1 )); then
        _alternative \
            'files:filename:_files -g "*.d"' \
            "commands:dub command: _describe -t commands command commands"
    else
        integer ret=0
        local cmd=${${(k)commands}[(r)$words[1]:*]%%:*}
        if [ ! -z "$cmd" ]; then
            _call_function ret _dub_$cmd
        else
           # Assume single file, it takes program arguments
            _message "Arguments for single file package $words[1]"
        fi
        return 0
    fi
}

(( $+functions[_dub_add] )) ||
_dub_add() {
    # TODO: Make dub list more machine-readable
    local -a dubList=("${(@f)$(dub list)}")
    # First element is 'Packages present in the system...'
    # Last element is an empty line
    dubList=(${dubList[2,$#dubList-1]})
    local -A pkgs
    # Collect versions and names
    for ((i = 1; i <= ${#dubList}; i++)); do
        pkg_name=${${=${dubList[i]}}[1]}
        pkg_version=${${${=${dubList[i]}}[2]}%:}
        # Subpackages are currently not supported by 'dub add' (see dlang/dub#1846)
        if [ ! -z "${pkg_name:#*:*}" ]; then
            pkgs[${pkg_name}]+="${pkg_version}, "
        fi
    done
    # Merge versions
    local -a packages
    for name ver in ${(kv)pkgs}; do
        packages+=${name}:"${ver%, }"
    done

    # Package list includes ':' which is used as description
    #_values 'local packages' ${pkgs//:/\\:}

    # Use the unique property to get rid of subpkgs
    _describe -t packages package packages
}

(( $+functions[_dub_init] )) ||
_dub_init() {
    _arguments -S -C \
        ':package directory:_directories' \
        '*:package dependency:_dub_add' \
        '(-t --type)'{-t,--type}'[Set the type of project to generate]:project type:((minimal\:"simple hello world project (default)" vibe.d\:"minimal HTTP server based on vibe.d" deimos\:"skeleton for C header bindings" custom\:"custom project provided by dub package"))' \
        '(-f --format)'{-f,--format}'[Sets the format to use for the manifest file]:format:(json sdl)' \
        '(-n --non-iteractive)'{-n,--non-iteractive}'[Do not prompt for values and use only defaults]' \
        '(* : -)'{-h,--help}'[Display general or command specific help and exit]'
}

(( $+functions[_dub_list] )) ||
_dub_list() {
    _arguments -S -C \
    '(* : -)'{-h,--help}'[Display general or command specific help and exit]'
}

# dub generate, dub build, dub run...
(( $+functions[_dub_generate_generic] )) ||
_dub_generate_generic() {
    _arguments -S -C \
    $@ \
    '::package:_dub_add' \
    '(* : -)'{-h,--help}'[Display general or command specific help and exit]' \
    '(-b --build)'{-b,--build=}'[Specifies the type of build to perform]:build type:("debug (default)" plain release release-debug release-nobounds unittest profile profile-gc docs ddox cov cov-ctfe unittest-cov unittest-cov-ctfe syntax)' \
    '(-c --config)'{-c,--config=}'[Builds the specified configuration]:package configuration: ' \
    '*--override-config=[ Uses the specified configuration for a certain dependency]:dependency/config: ' \
    '--compiler=[Specifies the compiler binary to use (can be a path)]:compiler:(dmd gdc ldc gdmd ldmd)' \
    '(-a --arch)'{-a,--arch=}'[Force a different architecture (e.g. x86 or x86_64)]:architecture: ' \
    '(-d --debug)*'{-d,--debug=}'[Define the specified debug version identifier when building]:Debug version: ' \
    '--d-version=[Define the specified version identifier when building]:Version identifier: ' \
    '--nodeps[Do not resolve missing dependencies before building]' \
    '--build-mode=[Specifies the way the compiler and linker are invoked]:build mode:("separate (default)" allAtOnce singleFile)' \
    '--single[Treats the package name as a filename. The file must contain a package recipe comment]:file:_files -g "*.d"' \
    '--filter-versions[Experimental: Filter version identifiers and debug version identifiers to improve build cache efficiency]' \
    '--combined[Tries to build the whole project in a single compiler run]' \
    '--print-builds[Prints the list of available build types]' \
    '--print-configs[Prints the list of available configurations]' \
    '--print-platform[Prints the identifiers for the current build platform as used for the manifests build field]' \
    '--parallel[Runs multiple compiler instances in parallel, if possible]'
}

(( $+functions[_dub_generate] )) ||
_dub_generate() {
    local curcontext="$curcontext"
    declare -a generators=(
        visuald:'VisualD project files',
        sublimetext:'SublimeText project file'
        cmake:'CMake build scripts'
        build:'Builds the package directly (use "dub build" instead)'
    )
    local localArgs=(
        ':generator: _describe -t generators generator generators'
    )

    integer ret=0
    _call_function ret _dub_generate_generic ${(@)localArgs}
    return ret
}

(( $+functions[_dub_build] )) ||
_dub_build() {
    local localArgs=(
        $@
        '--rdmd[Use rdmd instead of directly invoking the compiler]'
        '(-f --force)'{-f,--force}'[Forces a recompilation even if the target is up to date]'
        '(-y--yes)'{-y,--yes}'[Assume "yes" as answer to all interactive prompts]'
        '(-n--non-interactive)'{-n,--non-interactive}'[Do not enter interactive mode]'
    )

    integer ret=0
    _call_function ret _dub_generate_generic ${(@)localArgs}
    return ret
}

(( $+functions[_dub_run] )) ||
_dub_run() {
    local localArgs=(
        '--[End of dub arguments, the following will be sent to the program]'
        '--temp-build[Builds the project in the temp folder if possible]'
    )

    integer ret=0
    _call_function ret _dub_build ${(@)localArgs}
    return ret
}

(( $+functions[_dub_test] )) ||
_dub_test() {
    local localArgs=(
        '--main-file=[Specifies a custom file containing the main() function to use for running the tests]:main file:_files -g "*.d"'
    )

    integer ret=0
    _call_function ret _dub_build ${(@)localArgs}
    return ret
}

(( $+functions[_dub_describe] )) ||
_dub_describe() {
    local localArgs=(
        '--import-paths[Shortcut for --data=import-paths --data-list]'
        '--string-import-paths[Shortcut for --data=string-import-paths --data-list]'
        '--data=[List the values of a particular build setting]:listing options: _values -s , argument main-source-file dflags lflags libs linker-files source-files versions debug-versions import-paths string-import-paths import-files options'
        '--data-list[Output --data information in list format (line-by-line)]'
        '--data-0[Output --data information using null-delimiters, rather than spaces or newlines]'
    )

    integer ret=0
    _call_function ret _dub_build ${(@)localArgs}
    return ret
}

(( $+functions[_dub_clean] )) ||
_dub_clean() {
    _arguments -S -C \
        '(* : -)'{-h,--help}'[Display general or command specific help and exit]' \
        + '(toclean)' \
        '--all-packages[Cleans up *all* known packages (dub list)]' \
        ':package:_dub_add'
}

(( $+functions[_dub_dustmite] )) ||
_dub_dustmite() {
    local localArgs=(
        ':target directory:_directories'
        '--compiler-status=[The expected status code of the compiler run]:status code: '
        '--compiler-regex=[A regular expression used to match against the compiler output]:regex: '
        '--linker-status=[The expected status code of the linker run]:status code: '
        '--linker-regex=[A regular expression used to match against the linker output]:regex: '
        '--program-status=[The expected status code of the built executable]:status code: '
        '--program-regex=[A regular expression used to match against the program output]:regex: '
        '--[End of dub arguments, the following will be sent to the program]'
    )

    integer ret=0
    _call_function ret _dub_generate_generic ${(@)localArgs}
    return ret
}

_dub
