#compdef knife

# These flags should be available everywhere according to the knife man page.
knife_general_flags=(
  --help
  --server-url
  --key
  --config
  --editor
  --format
  --log_level
  --logfile
  --no-editor
  --user
  --print-after
  --version
  --yes
)

# Knife has a very special syntax, some example calls are:
#   knife status
#   knife cookbook list
#   knife role show ROLENAME
#   knife data bag show DATABAGNAME
#   knife role show ROLENAME --attribute ATTRIBUTENAME
#   knife cookbook show COOKBOOKNAME COOKBOOKVERSION recipes

# The -Q switch in compadd allows for completion of things like "data bag" without
# having to go through two rounds of completion and avoids ZSH inserting a '\' for
# escaping spaces.
function _knife() {
  local curcontext="$curcontext" state line
  typeset -A opt_args
  cloudproviders=(bluebox ec2 rackspace slicehost terremark)
  _arguments \
    '1: :->knifecmd'\
    '2: :->knifesubcmd'\
    '3: :->knifesubcmd2' \
    '4: :->knifesubcmd3' \
    '5: :->knifesubcmd4' \
    '6: :->knifesubcmd5'

  case $state in
    (knifecmd)
      compadd -Q "$@" bootstrap client configure cookbook "cookbook site" "data bag" exec index node recipe role search ssh status windows $cloudproviders
    ;;
    (knifesubcmd)
      case $words[2] in
        (bluebox|ec2|rackspace|slicehost|terremark)
          compadd "$@" server images
        ;;
        (client)
          compadd -Q "$@" "bulk delete" list create show delete edit reregister
        ;;
        (configure)
          compadd "$@" client
        ;;
        (cookbook)
          compadd -Q "$@" test list create download delete "metadata from" show "bulk delete" metadata upload
        ;;
        (node)
        compadd -Q "$@" "from file" create show edit delete list run_list "bulk delete"
        ;;
        (recipe)
        compadd "$@" list
        ;;
        (role)
          compadd -Q "$@" "bulk delete" create delete edit "from file" list show
        ;;
        (windows)
          compadd "$@" bootstrap
        ;;
        (*)
          _arguments '2:Subsubcommands:($(_knife_options1))'
      esac
    ;;
    (knifesubcmd2)
      case $words[3] in
        (server)
          compadd "$@" list create delete
        ;;
        (images)
          compadd "$@" list
        ;;
        (site)
          compadd "$@" vendor show share search download list unshare
        ;;
        (show|delete|edit)
          _arguments '3:Subsubcommands:($(_chef_$words[2]s_remote))'
        ;;
        (upload|test)
          _arguments '3:Subsubcommands:($(_chef_$words[2]s_local) --all)'
        ;;
        (list)
          compadd -a "$@" knife_general_flags
        ;;
        (bag)
          compadd -Q "$@" show edit list "from file" create delete
        ;;
        (*)
          _arguments '3:Subsubcommands:($(_knife_options2))'
      esac
    ;;
    (knifesubcmd3)
      case $words[3] in
        (show)
          case $words[2] in
            (cookbook)
              versioncomp=1
              _arguments '4:Cookbookversions:($(_cookbook_versions) latest)'
            ;;
            (node|client|role)
              compadd "$@" --attribute
          esac
      esac
      case $words[4] in
        (show|edit)
          _arguments '4:Subsubsubcommands:($(_chef_$words[2]_$words[3]s_remote))'
        ;;
        (file)
          _arguments '*:file or directory:_files -g "*.(rb|json)"'
        ;;
        (list)
          compadd -a "$@" knife_general_flags
        ;;
        (*)
          _arguments '*:Subsubcommands:($(_knife_options3))'
      esac
    ;;
    (knifesubcmd4)
      if (( versioncomp > 0 )); then
        compadd "$@" attributes definitions files libraries providers recipes resources templates
      else
        _arguments '*:Subsubcommands:($(_knife_options2))'
      fi
    ;;
    (knifesubcmd5)
      _arguments '*:Subsubcommands:($(_knife_options3))'
  esac
}

# Helper functions to provide the argument completion for several depths of commands
function _knife_options1() {
  ( for line in $( knife $words[2] --help | grep -v "^knife" ); do echo $line | grep "\-\-"; done )
}

function _knife_options2() {
  ( for line in $( knife $words[2] $words[3] --help | grep -v "^knife" ); do echo $line | grep "\-\-"; done )
}

function _knife_options3() {
  ( for line in $( knife $words[2] $words[3] $words[4] --help | grep -v "^knife" ); do echo $line | grep "\-\-"; done )
}

# The chef_x_remote functions use knife to get a list of objects of type x on the server
function _chef_roles_remote() {
  (knife role list | grep \" | awk '{print $1}' | awk -F"," '{print $1}' | awk -F"\"" '{print $2}')
}

function _chef_clients_remote() {
  (knife client list | grep \" | awk '{print $1}' | awk -F"," '{print $1}' | awk -F"\"" '{print $2}')
}

function _chef_nodes_remote() {
  (knife node list | grep \" | awk '{print $1}' | awk -F"," '{print $1}' | awk -F"\"" '{print $2}')
}

function _chef_cookbooks_remote() {
  (knife cookbook list | grep \" | awk '{print $1}' | awk -F"," '{print $1}' | awk -F"\"" '{print $2}')
}

function _chef_sitecookbooks_remote() {
  (knife cookbook site list | grep \" | awk '{print $1}' | awk -F"," '{print $1}' | awk -F"\"" '{print $2}')
}

function _chef_data_bags_remote() {
  (knife data bag list | grep \" | awk '{print $1}' | awk -F"," '{print $1}' | awk -F"\"" '{print $2}')
}

# The chef_x_local functions use the knife config to find the paths of relevant objects x to be uploaded to the server
function _chef_cookbooks_local() {
  (for i in $( grep cookbook_path $HOME/.chef/knife.rb | awk 'BEGIN {FS = "[" }; {print $2}' | sed 's/\,//g' | sed "s/'//g" | sed 's/\(.*\)]/\1/' ); do ls $i; done)
}

# This function extracts the available cookbook versions on the chef server
function _cookbook_versions() {
  (knife cookbook show $words[4] | grep -v $words[4] | grep -v -E '\]|\[|\{|\}' | sed 's/ //g' | sed 's/"//g')
}

function _knife "$@"