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