Select Git revision
AutoCompletionHelper.ts

orestis.malaspin authored
AutoCompletionHelper.ts 10.64 KiB
import { Command } from 'commander';
const fishFunction = `
function __fish_dojo_using_commands
set cmd (commandline -opc)
set num_cmd (count $cmd)
if [ $num_cmd -eq $argv[1] ]
for i in (seq 1 (math $num_cmd))
if [ $argv[(math $i+1)] != $cmd[$i] ]
return 1
end
end
return 0
end
return 1
end
complete -f -c dojo
`
function isLeaf(cmd: Command): boolean {
return cmd.commands.length == 0
}
function flatten(cmd: Command): Array<Command> {
if (isLeaf(cmd)) {
return [cmd]
} else {
return cmd.commands
.map(child => flatten(child))
.reduce((acc, cmd) => acc.concat(cmd), [cmd])
}
}
// Computes the maximum number of commands until a leaf is reached
function computeDepth(cmd: Command | undefined): number {
if (cmd === undefined) {
return 0
} else {
return 1 + cmd.commands.map(subCmd => computeDepth(subCmd)).reduce((acc, depth) => depth > acc ? depth : acc, 0)
}
}
// Computes the maximum number of commands until the root is reached
function computeHeight(cmd: Command | null): number {
let height = 0
let tmp = cmd
while (tmp !== null) {
tmp = tmp.parent
height += 1
}
return height
}
// Computes the maximum number of commands until the root is reached
export function getRoot(cmd: Command): Command {
if (cmd.parent == null) {
return cmd
} else {
return getRoot(cmd.parent)
}
}
function getOptions(cmd: Command): string {
// we remove <args>, [command], and , from option lines
return cmd.options.filter(opt => !opt.hidden).map(opt =>
opt.flags.replace(/<.*?>/, '').replace(/\[.*?\]/, '').replace(',', '').trimEnd()
).join(" ")
}
function commandsAndOptionsToString(cmd: Command): string {
return cmd.commands.map(c => c.name()).join(" ").concat(' ' + getOptions(cmd)).trim().concat(' --help -h').trim()
}
function addLine(identLevel: number, pattern: string): string {
return `${' '.repeat(identLevel)}${pattern}\n`
}
function generateBashSubCommands(cmd: Command, current: number, maxDepth: number, ident: number): string {
if (current == maxDepth) {
return addLine(ident, `case "\${COMP_WORDS[$COMP_CWORD - ${maxDepth - current + 1}]}" in`)
+ addLine(ident + 1, `${cmd.name()})`)
+ addLine(ident + 2, `words="${commandsAndOptionsToString(cmd)}"`)
+ addLine(ident + 1, ';;')
+ addLine(ident + 1, '*)')
+ addLine(ident + 1, ';;')
+ addLine(ident, 'esac')
} else {
let data = addLine(ident, `case "\${COMP_WORDS[$COMP_CWORD - ${maxDepth - current + 1}]}" in`)
+ addLine(ident + 1, `${cmd.name()})`)
cmd.commands.forEach(subCmd => {
data += generateBashSubCommands(subCmd, current + 1, maxDepth, ident + 2)
})
data +=
addLine(ident + 1, ';;')
+ addLine(ident + 1, '*)')
+ addLine(ident + 1, ';;')
+ addLine(ident, 'esac')
return data
}
}
export function generateBashCompletion(root: Command): string {
const depth = computeDepth(root)
let data =
addLine(0, '#/usr/bin/env bash\nfunction _dojo_completions()')
+ addLine(0, '{')
+ addLine(1, 'latest="${COMP_WORDS[$COMP_CWORD]}"')
for (let i = 1; i <= depth; i++) {
data += addLine(1, `${i == 1 ? 'if' : 'elif'} [ $COMP_CWORD -eq ${depth - i + 1} ]`)
+ addLine(1, 'then')
data += generateBashSubCommands(root, i, depth, 2)
}
data += addLine(1, 'fi')
+ addLine(1, 'COMPREPLY=($(compgen -W "$words" -- $latest))')
+ addLine(1, 'return 0')
+ addLine(0, '}')
+ addLine(0, 'complete -F _dojo_completions dojo')
return data
}
const prefix = 'complete -f -c dojo -n \'__fish_dojo_using_commands'
function generateCommandChain(cmd: Command | null): string {
let data = ''
while (cmd !== null) {
data = cmd.name().concat(` ${data}`)
cmd = cmd.parent
}
return data.trimEnd()
}
function hasOptions(cmd: Command): boolean {
return cmd.options.length > 0
}
function optionsToString(cmd: Command): string {
return cmd.options.filter(opt => !opt.hidden).map(opt => {
return `${prefix} ${computeHeight(cmd)} ${generateCommandChain(cmd)}' -a "${opt.short ?? ''} ${opt.long ?? ''}" -d "${opt.description}"`
}).join('\n').concat('\n')
}
export function generateFishCompletion(root: Command): string {
const commands = flatten(root)
const data = fishFunction.concat(
// add completions for options
commands.filter(cmd => hasOptions(cmd)).map(cmd => optionsToString(cmd)).filter(str => str != '').join('')
).concat(
// add completions for commands and subcommands
commands.filter(
cmd => !isLeaf(cmd)).map(
cmd => cmd.commands.map(subCmd => {
return `${prefix} ${computeHeight(cmd)} ${generateCommandChain(cmd)}' -a ${subCmd.name()} -d "${subCmd.description()}"`
}).join('\n').concat('\n')).join('')
)
return data
}
// The following code should create a bash completion automatically from the Commander
// CLI library. The file should look something like that (it looks at the time
// this comment is written).
// #/usr/bin/env bash
// function _dojo_completions()
// {
// latest="${COMP_WORDS[$COMP_CWORD]}"
// if [ $COMP_CWORD -eq 3 ]
// then
// case "${COMP_WORDS[$COMP_CWORD - 3]}" in
// dojo)
// case "${COMP_WORDS[$COMP_CWORD - 2]}" in
// session)
// case "${COMP_WORDS[$COMP_CWORD - 1]}" in
// login)
// words="-c --cli --help -h"
// ;;
// *)
// ;;
// esac
// case "${COMP_WORDS[$COMP_CWORD - 1]}" in
// logout)
// words="-f --force --help -h"
// ;;
// *)
// ;;
// esac
// case "${COMP_WORDS[$COMP_CWORD - 1]}" in
// test)
// words="--help -h"
// ;;
// *)
// ;;
// esac
// ;;
// *)
// ;;
// esac
// case "${COMP_WORDS[$COMP_CWORD - 2]}" in
// assignment)
// case "${COMP_WORDS[$COMP_CWORD - 1]}" in
// create)
// words="-n --name -i --members_id -u --members_username -t --template -c --clone --help -h"
// ;;
// *)
// ;;
// esac
// case "${COMP_WORDS[$COMP_CWORD - 1]}" in
// check)
// words="-p --path -v --verbose -w --super-verbose --help -h"
// ;;
// *)
// ;;
// esac
// case "${COMP_WORDS[$COMP_CWORD - 1]}" in
// run)
// words="-p --path -v --verbose -w --super-verbose --help -h"
// ;;
// *)
// ;;
// esac
// case "${COMP_WORDS[$COMP_CWORD - 1]}" in
// publish)
// words="-f --force --help -h"
// ;;
// *)
// ;;
// esac
// case "${COMP_WORDS[$COMP_CWORD - 1]}" in
// unpublish)
// words="-f --force --help -h"
// ;;
// *)
// ;;
// esac
// ;;
// *)
// ;;
// esac
// case "${COMP_WORDS[$COMP_CWORD - 2]}" in
// exercise)
// case "${COMP_WORDS[$COMP_CWORD - 1]}" in
// create)
// words="-a --assignment -i --members_id -u --members_username -c --clone --help -h"
// ;;
// *)
// ;;
// esac
// case "${COMP_WORDS[$COMP_CWORD - 1]}" in
// run)
// words="-p --path -v --verbose -w --super-verbose --help -h"
// ;;
// *)
// ;;
// esac
// ;;
// *)
// ;;
// esac
// ;;
// *)
// ;;
// esac
// elif [ $COMP_CWORD -eq 2 ]
// then
// case "${COMP_WORDS[$COMP_CWORD - 2]}" in
// dojo)
// case "${COMP_WORDS[$COMP_CWORD - 1]}" in
// session)
// words="login logout test --help -h"
// ;;
// *)
// ;;
// esac
// case "${COMP_WORDS[$COMP_CWORD - 1]}" in
// assignment)
// words="create check run publish unpublish --help -h"
// ;;
// *)
// ;;
// esac
// case "${COMP_WORDS[$COMP_CWORD - 1]}" in
// exercise)
// words="create run --help -h"
// ;;
// *)
// ;;
// esac
// ;;
// *)
// ;;
// esac
// elif [ $COMP_CWORD -eq 1 ]
// then
// case "${COMP_WORDS[$COMP_CWORD - 1]}" in
// dojo)
// words="session assignment exercise -V --version -H --host --help -h"
// ;;
// *)
// ;;
// esac
// fi
// COMPREPLY=($(compgen -W "$words" -- $latest))
// return 0
// }
// complete -F _dojo_completions dojo