• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict'
2const path = require('path')
3
4// add bash completions to your
5//  yargs-powered applications.
6module.exports = function completion (yargs, usage, command) {
7  const self = {
8    completionKey: 'get-yargs-completions'
9  }
10
11  const zshShell = (process.env.SHELL && process.env.SHELL.indexOf('zsh') !== -1) ||
12    (process.env.ZSH_NAME && process.env.ZSH_NAME.indexOf('zsh') !== -1)
13  // get a list of completion commands.
14  // 'args' is the array of strings from the line to be completed
15  self.getCompletion = function getCompletion (args, done) {
16    const completions = []
17    const current = args.length ? args[args.length - 1] : ''
18    const argv = yargs.parse(args, true)
19    const aliases = yargs.parsed.aliases
20    const parentCommands = yargs.getContext().commands
21
22    // a custom completion function can be provided
23    // to completion().
24    if (completionFunction) {
25      if (completionFunction.length < 3) {
26        const result = completionFunction(current, argv)
27
28        // promise based completion function.
29        if (typeof result.then === 'function') {
30          return result.then((list) => {
31            process.nextTick(() => { done(list) })
32          }).catch((err) => {
33            process.nextTick(() => { throw err })
34          })
35        }
36
37        // synchronous completion function.
38        return done(result)
39      } else {
40        // asynchronous completion function
41        return completionFunction(current, argv, (completions) => {
42          done(completions)
43        })
44      }
45    }
46
47    const handlers = command.getCommandHandlers()
48    for (let i = 0, ii = args.length; i < ii; ++i) {
49      if (handlers[args[i]] && handlers[args[i]].builder) {
50        const builder = handlers[args[i]].builder
51        if (typeof builder === 'function') {
52          const y = yargs.reset()
53          builder(y)
54          return y.argv
55        }
56      }
57    }
58
59    if (!current.match(/^-/) && parentCommands[parentCommands.length - 1] !== current) {
60      usage.getCommands().forEach((usageCommand) => {
61        const commandName = command.parseCommand(usageCommand[0]).cmd
62        if (args.indexOf(commandName) === -1) {
63          if (!zshShell) {
64            completions.push(commandName)
65          } else {
66            const desc = usageCommand[1] || ''
67            completions.push(commandName.replace(/:/g, '\\:') + ':' + desc)
68          }
69        }
70      })
71    }
72
73    if (current.match(/^-/) || (current === '' && completions.length === 0)) {
74      const descs = usage.getDescriptions()
75      Object.keys(yargs.getOptions().key).forEach((key) => {
76        // If the key and its aliases aren't in 'args', add the key to 'completions'
77        const keyAndAliases = [key].concat(aliases[key] || [])
78        const notInArgs = keyAndAliases.every(val => args.indexOf(`--${val}`) === -1)
79        if (notInArgs) {
80          if (!zshShell) {
81            completions.push(`--${key}`)
82          } else {
83            const desc = descs[key] || ''
84            completions.push(`--${key.replace(/:/g, '\\:')}:${desc.replace('__yargsString__:', '')}`)
85          }
86        }
87      })
88    }
89
90    done(completions)
91  }
92
93  // generate the completion script to add to your .bashrc.
94  self.generateCompletionScript = function generateCompletionScript ($0, cmd) {
95    const templates = require('./completion-templates')
96    let script = zshShell ? templates.completionZshTemplate : templates.completionShTemplate
97    const name = path.basename($0)
98
99    // add ./to applications not yet installed as bin.
100    if ($0.match(/\.js$/)) $0 = `./${$0}`
101
102    script = script.replace(/{{app_name}}/g, name)
103    script = script.replace(/{{completion_command}}/g, cmd)
104    return script.replace(/{{app_path}}/g, $0)
105  }
106
107  // register a function to perform your own custom
108  // completions., this function can be either
109  // synchrnous or asynchronous.
110  let completionFunction = null
111  self.registerFunction = (fn) => {
112    completionFunction = fn
113  }
114
115  return self
116}
117