lib/completion.js
'use strict'
const glob = require('glob')
const CUSTOM = ['']
const BOOLEAN = false
const options = {
start: {
'--port': CUSTOM,
'--auto-watch': BOOLEAN,
'--no-auto-watch': BOOLEAN,
'--log-level': ['disable', 'debug', 'info', 'warn', 'error'],
'--colors': BOOLEAN,
'--no-colors': BOOLEAN,
'--reporters': ['dots', 'progress'],
'--no-reporters': BOOLEAN,
'--browsers': ['Chrome', 'ChromeHeadless', 'ChromeCanary', 'Firefox', 'PhantomJS', 'Safari', 'Opera'],
'--no-browsers': BOOLEAN,
'--single-run': BOOLEAN,
'--no-single-run': BOOLEAN,
'--help': BOOLEAN
},
init: {
'--log-level': ['disable', 'debug', 'info', 'warn', 'error'],
'--colors': BOOLEAN,
'--no-colors': BOOLEAN,
'--help': BOOLEAN
},
run: {
'--no-refresh': BOOLEAN,
'--port': CUSTOM,
'--help': BOOLEAN
}
}
function opositeWord (word) {
if (word.startsWith('-')) {
return word.startsWith('--no-') ? `--${word.slice(5)}` : `--no-${word.slice(2)}`
} else {
return null
}
}
function sendCompletion (possibleWords, env) {
const regexp = new RegExp('^' + env.last)
possibleWords
.filter((word) => regexp.test(word) && !env.words.includes(word) && !env.words.includes(opositeWord(word)))
.forEach((word) => {
console.log(word)
})
}
function sendCompletionFiles (env) {
glob(env.last + '*', { mark: true, nocase: true }, (err, files) => {
if (err) return console.error(err)
if (files.length === 1 && files[0].endsWith('/')) {
sendCompletionFiles({ last: files[0] })
} else {
console.log(files.join('\n'))
}
})
}
function complete (env) {
if (env.count === 1) {
return sendCompletion(env.words[0].startsWith('-') ? ['--help', '--version'] : Object.keys(options), env)
} else if (env.count === 2 && !env.words[1].startsWith('-')) {
return sendCompletionFiles(env)
}
const cmdOptions = options[env.words[0]]
if (cmdOptions) {
if (cmdOptions[env.prev] === CUSTOM && env.last) {
console.log(env.last)
} else {
return sendCompletion(cmdOptions[env.prev] || Object.keys(cmdOptions), env)
}
}
}
function completion () {
if (process.argv[3] === '--') {
return complete({
words: process.argv.slice(5),
count: parseInt(process.env.COMP_CWORD, 10),
last: process.argv[process.argv.length - 1],
prev: process.argv[process.argv.length - 2]
})
}
// just print out the karma-completion.sh
const fs = require('graceful-fs')
const path = require('path')
fs.readFile(path.resolve(__dirname, '../scripts/karma-completion.sh'), 'utf8', function (err, data) {
if (err) return console.error(err)
process.stdout.write(data)
process.stdout.on('error', function (error) {
// Darwin is a real dick sometimes.
//
// This is necessary because the "source" or "." program in
// bash on OS X closes its file argument before reading
// from it, meaning that you get exactly 1 write, which will
// work most of the time, and will always raise an EPIPE.
//
// Really, one should not be tossing away EPIPE errors, or any
// errors, so casually. But, without this, `. <(karma completion)`
// can never ever work on OS X.
if (error.errno === 'EPIPE') {
error = null
}
})
})
}
// PUBLIC API
exports.completion = completion
// for testing
exports.opositeWord = opositeWord
exports.sendCompletion = sendCompletion
exports.complete = complete