#compdef mtn # -*-sh-*-
#
# ZSH Completion for Monotone
#
# Author: Joel Reed  <joelwreed@comcast.com>
# Last changed by: Thomas Moschny  <thomas.moschny@gmx.de>
#
#
# Installing into /usr/share/zsh/site-functions:
#
# (1) If that directory is not already present in $fpath, add the line
#     "fpath=(/usr/share/zsh/site-functions $fpath)" to either your
#     system wide zshrc, or user ~/.zshrc.
#
# (2) Copy $MONOTONE/contrib/monotone.zsh_completion (this file) to
#     /usr/share/zsh/site-functions/_mtn
#
# Alternatively, you can install this file as _mtn into some directory
# in your home (e.g. ~/.zsh), and add that directory to the $fpath.
#

# redefine _mtn.

_mtn() {
  local extra

  _arguments -s : \
    '--confdir=[set location of configuration directory]:dir:_files -/'\
    {'(--db)-d+','(-d)--db='}'[set name of database]:file:_files'\
    '--debug[print debug log to stderr while running]'\
    '--dump=[file to dump debugging log to, on failure]:file:_files'\
    {'-h','--help'}'[display help message]'\
    '--ignore-suspend-certs[do not ignore revisions marked as suspended]'\
    {'(--key)-k+','(-k)--key='}'[set key for signatures]:keys keys:_mtn_keys'\
    '--keydir=[set location of key store]:dir:_files -/'\
    '--log=[file to write the log to]:file:_files'\
    '--no-standard-rcfiles[do not load ~/.monotone/monotonerc or _MTN/monotonerc lua files]'\
    '--no-builtin-rcfiles[do not load standard lua hooks]'\
    '--quiet[suppress log and progress messages]'\
    '--rcfile=[load extra rc file]:file:_files'\
    '--reallyquiet[suppress warning, verbose, informational and progress messages]'\
    '--root=[limit search for working copy to specified root]:dir:_files -/'\
    '--ssh-sign=[sign with ssh-agent]:ssh_sign:(yes no check)'\
    '--ticker=[set ticker style]:style:(count dot none)'\
    '(- *)--version[print version number, then exit]'\
    {'-@+','--xargs='}'[insert command line arguments taken from the given file]:file:_files'\
    '*::monotone command:_mtn_command'
}

# define monotone command dispatch function.

(( $+functions[_mtn_command] )) ||
_mtn_command() {
    (( $+_mtn_cmds )) || 
    _mtn_cmds=(
	add annotate approve asciik attr au automate cat cert checkout
	ci clone co comment commit complete cvs_import db diff
	disapprove drop dropkey explicit_merge fdiff fload fmerge
	genkey get_roster heads help identify import list log ls merge
	merge_into_dir merge_into_workspace migrate_workspace mkdir mv
	passphrase pivot_root pluck privkey propagate pubkey pull push
	rcs_import read refresh_inodeprints rename revert rm serve set
	setup show_conflicts ssh_agent_add ssh_agent_export status
	suspend sync tag testresult trusted unset update version
    )
    
    local cmd="$words[1]"
    if (( CURRENT == 1 )); then
	_describe -t commands 'monotone command' _mtn_cmds
    else
	local curcontext="$curcontext"
	
	if [[ -n $cmd ]]; then
	    curcontext="${curcontext%:*:*}:mtn-${cmd}:"
	    _call_function ret _mtn_$cmd || _message 'no more arguments'
	else
	    _message "unknown monotone command: $cmd"
	fi
	return ret
    fi
}

# define completion functions for each monotone command

(( $+functions[_mtn_add] )) ||
_mtn_add() {
    _arguments -s : \
	'--unknown[add unknown files from workspace]'\
	'*:file to add:_mtn_files_unknown'
}

(( $+functions[_mtn_annotate] )) ||
_mtn_annotate() {
    _arguments -s : \
	{'(--revision)-r+','(-r)--revision='}'[select revision id for operation]:revision:_mtn_selector_or_revision'\
	'--revs-only[annotate using full revision ids only]'\
	'(*)*:file to annotate:_mtn_files_known'
}

(( $+functions[_mtn_approve] )) ||
_mtn_approve() {
    _arguments -s : \
	{'(--branch)-b+','(-b)--branch='}'[select branch cert for operation]:branch:_mtn_branches'\
	'(*)*:revision:_mtn_selector_or_revision'
}

# missing: asciik

(( $+functions[_mtn_attr] )) ||
_mtn_attr() {
    if (( CURRENT == 2 )); then	
	compadd -- set get drop
    elif (( CURRENT == 3 )); then	
	# fixme: should differentiate between set, get and drop
	compadd -- $(mtn automate attributes)
    else
	local fname="$words[3]"
	compadd -- $(mtn automate attributes $fname)
    fi
}

(( $+functions[_mtn_au] )) ||
_mtn_au() {
    _mtn_automate
}

(( $+functions[_mtn_automate] )) ||
_mtn_automate() {
    (( $+_mtn_automate_cmds )) ||
    _mtn_automate_cmds=(
	ancestors ancestry_difference branches cert certs children
	common_ancestors content_diff db_get db_set descendents
	drop_attribute erase_ancestors genkey get_attributes
	get_base_revision_id get_content_changed
	get_corresponding_path get_current_revision_id get_file
	get_file_of get_manifest_of get_option get_revision graph
	heads identify interface_version inventory keys leaves
	packet_for_fdata packet_for_fdelta packet_for_rdata
	packets_for_certs parents put_file put_revision roots select
	set_attribute stdio tags toposort
    )
    if (( CURRENT == 2 )) ; then
	compadd -a _mtn_automate_cmds
    fi
}

(( $+functions[_mtn_cat] )) ||
_mtn_cat() {
    _arguments -s : \
	{'(--revision)-r+','(-r)--revision='}'[select revision id for operation]:revision:_mtn_selector_or_revision'\
	'*:file to cat:_mtn_files_known'
}

(( $+functions[_mtn_cert] )) ||
_mtn_cert() {
    _arguments -s : \
	'1:revision:_mtn_selector_or_revision'\
	'2:certname: '\
	'3:certval: '
}

(( $+functions[_mtn_checkout] )) ||
_mtn_checkout() {
    _arguments -s : \
	{'(--branch)-b+','(-b)--branch='}'[select branch cert for operation]:branch:_mtn_branches'\
	{'(--revision)-r+','(-r)--revision='}'[select revision id for operation]:revision:_mtn_selector_or_revision'\
	'(*)*:directory for checkout: '
}

(( $+functions[_mtn_ci] )) ||
_mtn_ci() {
    _mtn_commit
}

# missing: clone

(( $+functions[_mtn_co] )) ||
_mtn_co() {
    _mtn_checkout
}

(( $+functions[_mtn_comment] )) ||
_mtn_comment() {
    _arguments -s : \
	'1:revision:_mtn_selector_or_revision'\
	'2:comment: '
}

(( $+functions[_mtn_commit] )) ||
_mtn_commit() {
    _arguments -s : \
	{'(--branch)-b+','(-b)--branch='}'[select branch cert for operation]:branch:_mtn_branches'\
	{'(--message)-m+','(-m)--message='}'[set commit changelog message]:message: '\
	'--message-file=[set filename containing commit changelog message]:file:_files'\
	'--date=[override date/time for commit]:date: '\
	'--author=[override author for commit]:author: '\
	'--depth=[limit the number of levels of directories to descend]:depth'\
	'--exclude=[leave out anything described by its argument]:file:_mtn_files_known'\
	'*:files to commit:_mtn_files_changed'
}

(( $+functions[_mtn_complete] )) ||
_mtn_complete() {
    _arguments -s : \
	'1:what to complete:(file key manifest revision)'\
	'2:partial id: '
}

(( $+functions[_mtn_cvs_import] )) ||
_mtn_cvs_import() {
    _arguments : \
	'*:file:_files'
}

(( $+functions[_mtn_db] )) ||
_mtn_db() {
    (( $+_mtn_db_cmds )) ||
    _mtn_db_cmds=( 
	changesetify check dump execute info init
	kill_branch_certs_locally kill_rev_locally kill_tag_locally
	load migrate regenerate_caches rosterify set_epoch version
    )
    if (( CURRENT == 2 )) ; then
	compadd -a _mtn_db_cmds
    fi
}

(( $+functions[_mtn_diff] )) ||
_mtn_diff() {
    _arguments -s : \
	{'(--revision)-r+','(-r)--revision='}'[select revision id for operation]:revision:_mtn_selector_or_revision'\
	'--depth=[limit the number of levels of directories to descend]:depth: '\
	'--exclude=[leave out anything described by its argument]:file:_mtn_files_known'\
	{'(--unified)-u','(-u)--unified'}'[use unified diff format]'\
	{'(--contex)-c','(-c)--contex'}'[use context diff format]'\
	'--external[use external diff hook for generating diffs]'\
	'--diff-args=[argument to pass external diff hook]:arg: '\
	{'-no-show-encloser','--no-show-c-function'}'[do not show the function containing each block of changes]'\
	'*:files to diff:_mtn_files_changed'
}

(( $+functions[_mtn_disapprove] )) ||
_mtn_disapprove() {
    _arguments -s : \
	{'(--branch)-b+','(-b)--branch='}'[select branch cert for operation]:branch:_mtn_branches'\
	'(*)*:revision to disapprove:_mtn_selector_or_revision'
}

(( $+functions[_mtn_drop] )) ||
_mtn_drop() {
    _arguments -s : \
	{'(--execute)-e','(-e)--execute'}'[perform the associated file operation]'\
	'--missing[perform the operations for files missing from workspace]'\
	{'(--recursive)-R','(-R)--recursive'}'[also operate on the contents of any listed directories]'\
	'*:entries to drop:_mtn_files_known'
}

(( $+functions[_mtn_dropkey] )) ||
_mtn_dropkey() {
    _mtn_keys
}

(( $+functions[_mtn_explicit_merge] )) ||
_mtn_explicit_merge() {
    _arguments -s : \
	'1:left revision:_mtn_selector_or_revision'\
	'2:right revision:_mtn_selector_or_revision'\
	'3:dest branch:_mtn_branches'
}

# missing: fdiff
# missing: fload
# missing: fmerge
# missing: genkey
# missing: get_roster

(( $+functions[_mtn_heads] )) ||
_mtn_heads() {
    _arguments -s : \
	{'(--branch)-b+','(-b)--branch='}'[select branch cert for operation]:branch:_mtn_branches'
}

# missing: help
# missing: identify
# missing: import

(( $+functions[_mtn_list] )) ||
_mtn_list() {
    (( $+_mtn_ls_cmds )) ||
    _mtn_ls_cmds=(
	certs keys branches epochs tags vars 
	known unknown ignored missing changed
    )
    if (( CURRENT == 2 )) ; then
	compadd -a _mtn_ls_cmds
    fi
}

(( $+functions[_mtn_log] )) ||
_mtn_log() {
    _arguments -s : \
	{'(--revision)-r+','(-r)--revision='}'[select revision id for operation]:revision:_mtn_selector_or_revision'\
	'--last=[limit log output to the last number of entries]:last: ' \
	'--next=[limit log output to the next number of entries]:next: ' \
	'--brief[print a brief version of the normal output]'\
	'--diffs[print diffs along with logs]'\
	'--no-merges[exclude merges when printing logs]'\
	'--no-files[exclude files when printing logs]'\
	'*:restriction for log:_mtn_files_known'
}

(( $+functions[_mtn_ls] )) ||
_mtn_ls() {
    _mtn_list
}

(( $+functions[_mtn_merge] )) ||
_mtn_merge() {
    _arguments -s : \
	{'(--branch)-b+','(-b)--branch='}'[select branch cert for operation]:branch:_mtn_branches'\
	'--date=[override date/time for commit]:date: '\
	'--author=[override author for commit]:author: '
}

# missing: merge_into_dir
# missing: merge_into_workspace

(( $+functions[_mtn_mkdir] )) ||
_mtn_mkdir() {
    _arguments -s : \
	'--no-respect-ignore[do not ignore any files]'\
	'*:directory to create: '
}

(( $+functions[_mtn_mv] )) ||
_mtn_mv() {
    _mtn_rename
}

# missing: passphrase
# missing: pivot_root
# missing: pluck
# missing: privkey

(( $+functions[_mtn_propagate] )) ||
_mtn_propagate() {
    _arguments -s : \
	{'(--message)-m+','(-m)--message='}'[set commit changelog message]:message: '\
	'--message-file=[set filename containing commit changelog message]:file:_files'\
	'--date=[override date/time for commit]:date: '\
	'--author=[override author for commit]:author: '\
	'1:source branch:_mtn_branches'\
	'2:dest branch:_mtn_branches'\
}

# missing: pubkey

(( $+functions[_mtn_pull] )) ||
_mtn_pull() {
    _arguments -s : \
	'--set-default[use the current arguments as the future default]'\
	'--exclude=[leave out anything described by its argument]:exclude: '
}

(( $+functions[_mtn_push] )) ||
_mtn_push() {
    _mtn_sync
}

(( $+functions[_mtn_rcs_import] )) ||
_mtn_rcs_import() {
    _arguments -s : \
	{'(--branch)-b+','(-b)--branch='}'[select branch cert for operation]:branch:_mtn_branches'\
	'*:files to import:_files'
}

# missing: read
# missing: refresh_inodeprints

(( $+functions[_mtn_rename] )) ||
_mtn_rename() {
    _arguments -s : \
	{'(--execute)-e','(-e)--execute'}'[perform the associated file operation]'\
	'*:files to rename:_mtn_files_known'
}

(( $+functions[_mtn_revert] )) ||
_mtn_revert() {
    _arguments -s : \
	'--depth=[limit the number of levels of directories to descend]:depth: '\
	'--exclude=[leave out anything described by its argument]:file:_mtn_files_known'\
	'--missing[perform the operations for files missing from workspace]'\
	'*:files to revert:_mtn_files_changed'
}

(( $+functions[_mtn_rm] )) ||
_mtn_rm() {
    _mtn_drop
}

(( $+functions[_mtn_serve] )) ||
_mtn_serve() {
    _arguments -s : \
	'--pid-file=[record process id of server]:file:_files'\
	'--exclude=[leave out anything described by its argument]:exclude: '\
	'--bind=[address:port to listen on]:bind: '\
	'--stdio[serve netsync on stdio]'\
	'--no-transport-auth[disable transport authentication]'\
	'*:pattern: '
}

# missing: set
# missing: setup
# missing: show_conflicts
# missing: ssh_agent_add
# missing: ssh_agent_export

(( $+functions[_mtn_status] )) ||
_mtn_status() {
    _arguments -s : \
	'--depth=[limit the number of levels of directories to descend]:depth: '\
	'--exclude=[leave out anything described by its argument]:file:_mtn_files_known'\
	'*:file:_mtn_files_known'
}

# missing: suspend

(( $+functions[_mtn_sync] )) ||
_mtn_sync() {
    _arguments -s : \
	'--set-default[use the current arguments as the future default]'\
	'--exclude=[leave out anything described by its argument]:exclude: '\
	'--key-to-push=[push the specified key]:key: '
}

(( $+functions[_mtn_tag] )) ||
_mtn_tag() {
    _arguments -s : \
	'1:revision to tag:_mtn_selector_or_revision'\
	'(1)*:tagname: '
}

(( $+functions[_mtn_result] )) ||
_mtn_testresult() {
    _arguments -s : \
	'1:revision:_mtn_selector_or_revision'\
	'2:result:(pass fail true false yes no 1 0)'
}

# missing: trusted
# missing: unset

(( $+functions[_mtn_update] )) ||
_mtn_update() {
    _arguments -s : \
	{'(--branch)-b+','(-b)--branch='}'[select branch cert for operation]:branch:_mtn_branches'\
	{'(--revision)-r+','(-r)--revision='}'[select revision id for operation]:revision:_mtn_selector_or_revision'
}

(( $+functions[_mtn_version] )) ||
_mtn_version() {
    _arguments -s : \
	'--verbose[print detailed version number]'
}

# define completion functions for files maintained by monotone.

(( $+functions[_mtn_files_unknown] )) ||
_mtn_files_unknown() {
    _mtn_files "unknown"
}

(( $+functions[_mtn_files_changed] )) ||
_mtn_files_changed() {
    _mtn_files "changed"
}

(( $+functions[_mtn_files_known] )) ||
_mtn_files_known() {
    _mtn_files "known"
}

(( $+functions[_mtn_files] )) ||
_mtn_files() {
    # "mtn ls --depth=0 foo ." should output the names of directories
    # containing suppressed matches
    local prefix=${PREFIX}X
    local rcwd="$(_mtn_rcwd)"
    local -a list
    list=( ${${${(f)"$(mtn ls --no-pager $1 ${prefix:h} 2>/dev/null)"}#$rcwd}#/} )
    compadd -f -a list 
}

(( $+functions[_mtn_branches] )) ||
_mtn_branches() {
    compadd -- $(mtn ls --no-pager branches 2>/dev/null)
}

(( $+functions[_mtn_tags] )) ||
_mtn_tags() {
    compadd -- ${(u)${(f)"$(mtn ls --no-pager tags 2>/dev/null)"}%% *}
}

(( $+functions[_mtn_public_keys] )) ||
_mtn_public_keys() {
    compadd -- ${${${(@M)${(f)"$(mtn ls --no-pager keys 2>/dev/null)"}:#*@*}#* }%%* *}
}

(( $+functions[_mtn_keys] )) ||
_mtn_keys() {
    if [[ -n $PREFIX ]]; then
	compadd -X "(prefix of a) key id" -n \
	    -- $(mtn complete key $PREFIX 2>/dev/null)
    fi
}

(( $+functions[_mtn_revisions] )) ||
_mtn_revisions() {
    if [[ -n $PREFIX ]]; then
	compadd -X "(prefix of a) revision id" -n \
	    -- $(mtn complete revision $PREFIX 2>/dev/null)
    fi
}

(( $+functions[_mtn_selectors] )) ||
_mtn_selectors() {
    _values -s / -S : selector \
	'*b[branches]:branch:_mtn_branches' \
	'*h[branch heads]:branch head:_mtn_branches' \
	'*t[tags]:tag:_mtn_tags' \
	'*a[authors]:author:_mtn_public_keys' \
	'*d[date]:date:' \
	'*e[earlier than]:date:' \
	'*l[later than]:date:' \
	'*i[revision id]:revision id:_mtn_revisions' \
	'*p[parent of]:revision_id:_mtn_revisions'
}

(( $+functions[_mtn_selector_or_revision] )) ||
_mtn_selector_or_revision() {
    _alternative 'selector:selector:_mtn_selectors' \
	'revision:revision:_mtn_revisions'
}

# helper function, determines cwd relative to the root of the workspace

(( $+functions[_mtn_rcwd] )) ||
_mtn_rcwd() {
    local current="$(pwd)"
    local base=$current
    while [[ "${base}" != "/" && ! -d "${base}/_MTN" ]]; do
	base=${base:h}
    done
    echo ${current#$base/}
}

_mtn "$@"
