/
completion.ts
167 lines (143 loc) · 6.07 KB
/
completion.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
import { CommandInstance, isCommandBuilderCallback } from './command'
import * as templates from './completion-templates'
import { isPromise } from './is-promise'
import { parseCommand } from './parse-command'
import * as path from 'path'
import { UsageInstance } from './usage'
import { YargsInstance } from './yargs'
import { Arguments, DetailedArguments } from 'yargs-parser/build/lib/yargs-parser-types'
import { assertNotStrictEqual } from './common-types'
// add bash completions to your
// yargs-powered applications.
export function completion (yargs: YargsInstance, usage: UsageInstance, command: CommandInstance) {
const self: CompletionInstance = {
completionKey: 'get-yargs-completions'
} as CompletionInstance
let aliases: DetailedArguments['aliases']
self.setParsed = function setParsed (parsed) {
aliases = parsed.aliases
}
const zshShell = (process.env.SHELL && process.env.SHELL.indexOf('zsh') !== -1) ||
(process.env.ZSH_NAME && process.env.ZSH_NAME.indexOf('zsh') !== -1)
// get a list of completion commands.
// 'args' is the array of strings from the line to be completed
self.getCompletion = function getCompletion (args, done) {
const completions: string[] = []
const current = args.length ? args[args.length - 1] : ''
const argv = yargs.parse(args, true)
const parentCommands = yargs.getContext().commands
// a custom completion function can be provided
// to completion().
function runCompletionFunction (argv: Arguments) {
assertNotStrictEqual(completionFunction, null)
if (isSyncCompletionFunction(completionFunction)) {
const result = completionFunction(current, argv)
// promise based completion function.
if (isPromise(result)) {
return result.then((list) => {
process.nextTick(() => { done(list) })
}).catch((err) => {
process.nextTick(() => { throw err })
})
}
// synchronous completion function.
return done(result)
} else {
// asynchronous completion function
return completionFunction(current, argv, (completions) => {
done(completions)
})
}
}
if (completionFunction) {
return isPromise(argv) ? argv.then(runCompletionFunction) : runCompletionFunction(argv)
}
const handlers = command.getCommandHandlers()
for (let i = 0, ii = args.length; i < ii; ++i) {
if (handlers[args[i]] && handlers[args[i]].builder) {
const builder = handlers[args[i]].builder
if (isCommandBuilderCallback(builder)) {
const y = yargs.reset()
builder(y)
return y.argv
}
}
}
if (!current.match(/^-/) && parentCommands[parentCommands.length - 1] !== current) {
usage.getCommands().forEach((usageCommand) => {
const commandName = parseCommand(usageCommand[0]).cmd
if (args.indexOf(commandName) === -1) {
if (!zshShell) {
completions.push(commandName)
} else {
const desc = usageCommand[1] || ''
completions.push(commandName.replace(/:/g, '\\:') + ':' + desc)
}
}
})
}
if (current.match(/^-/) || (current === '' && completions.length === 0)) {
const descs = usage.getDescriptions()
const options = yargs.getOptions()
Object.keys(options.key).forEach((key) => {
const negable = !!options.configuration['boolean-negation'] && options.boolean.includes(key)
// If the key and its aliases aren't in 'args', add the key to 'completions'
let keyAndAliases = [key].concat(aliases[key] || [])
if (negable) keyAndAliases = keyAndAliases.concat(keyAndAliases.map(key => `no-${key}`))
function completeOptionKey (key: string) {
const notInArgs = keyAndAliases.every(val => args.indexOf(`--${val}`) === -1)
if (notInArgs) {
const startsByTwoDashes = (s: string) => /^--/.test(s)
const isShortOption = (s: string) => /^[^0-9]$/.test(s)
const dashes = !startsByTwoDashes(current) && isShortOption(key) ? '-' : '--'
if (!zshShell) {
completions.push(dashes + key)
} else {
const desc = descs[key] || ''
completions.push(dashes + `${key.replace(/:/g, '\\:')}:${desc.replace('__yargsString__:', '')}`)
}
}
}
completeOptionKey(key)
if (negable && !!options.default[key]) completeOptionKey(`no-${key}`)
})
}
done(completions)
}
// generate the completion script to add to your .bashrc.
self.generateCompletionScript = function generateCompletionScript ($0, cmd) {
let script = zshShell ? templates.completionZshTemplate : templates.completionShTemplate
const name = path.basename($0)
// add ./to applications not yet installed as bin.
if ($0.match(/\.js$/)) $0 = `./${$0}`
script = script.replace(/{{app_name}}/g, name)
script = script.replace(/{{completion_command}}/g, cmd)
return script.replace(/{{app_path}}/g, $0)
}
// register a function to perform your own custom
// completions., this function can be either
// synchrnous or asynchronous.
let completionFunction: CompletionFunction | null = null
self.registerFunction = (fn) => {
completionFunction = fn
}
return self
}
/** Instance of the completion module. */
export interface CompletionInstance {
completionKey: string
generateCompletionScript($0: string, cmd: string): string
getCompletion(args: string[], done: (completions: string[]) => any): any
registerFunction(fn: CompletionFunction): void
setParsed(parsed: DetailedArguments): void
}
export type CompletionFunction = SyncCompletionFunction | AsyncCompletionFunction
interface SyncCompletionFunction {
(current: string, argv: Arguments): string[] | Promise<string[]>
}
interface AsyncCompletionFunction {
(current: string, argv: Arguments, done: (completions: string[]) => any): any
}
function isSyncCompletionFunction (completionFunction: CompletionFunction): completionFunction is SyncCompletionFunction {
return completionFunction.length < 3
}