├── .gitattributes ├── examples └── tabtab-test-complete │ ├── .gitignore │ ├── package.json │ ├── package-lock.json │ ├── readme.md │ └── index.js ├── .eslintrc.json ├── test ├── fixtures │ └── tabtab-install.js ├── basic.js ├── parse-env.js ├── tabtab-install.js ├── utils │ └── index.js ├── log.js └── installer.js ├── .gitignore ├── lib ├── utils │ ├── index.js │ ├── systemShell.js │ ├── exists.js │ └── tabtabDebug.js ├── constants.js ├── scripts │ ├── zsh.sh │ ├── fish.sh │ └── bash.sh ├── prompt.js ├── index.js └── installer.js ├── api ├── prompt.js.md ├── index.js.md └── installer.js.md ├── .travis.yml ├── LICENSE ├── package.json ├── readme.md └── CHANGELOG.md /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /examples/tabtab-test-complete/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "mklabs", 3 | 4 | "rules": { 5 | "no-param-reassign": "off", 6 | "no-console": "off", 7 | "consistent-return": "off" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/fixtures/tabtab-install.js: -------------------------------------------------------------------------------- 1 | const tabtab = require('../..'); 2 | 3 | (async () => { 4 | await tabtab.install({ 5 | name: 'foo', 6 | completer: 'foo-complete' 7 | }); 8 | })(); 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .completions/tt 2 | .tern-port 3 | src/ 4 | .completions/ 5 | node_modules/ 6 | .nyc_output/ 7 | 8 | note.txt 9 | quick-test.js 10 | coverage/ 11 | tabtab/ 12 | test/tabtab.log 13 | -------------------------------------------------------------------------------- /lib/utils/index.js: -------------------------------------------------------------------------------- 1 | const tabtabDebug = require('./tabtabDebug'); 2 | const systemShell = require('./systemShell'); 3 | const exists = require('./exists'); 4 | 5 | module.exports = { 6 | systemShell, 7 | tabtabDebug, 8 | exists 9 | }; 10 | -------------------------------------------------------------------------------- /api/prompt.js.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## prompt() 4 | Asks user about SHELL and desired location. 5 | 6 | It is too difficult to check spawned SHELL, the user has to use chsh before 7 | it is reflected in process.env.SHELL 8 | 9 | **Kind**: global function 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | 4 | node_js: 5 | - 10 6 | - 8 7 | - 7 8 | 9 | cache: 10 | directories: 11 | - node_modules 12 | 13 | notifications: 14 | email: false 15 | 16 | before_install: 17 | - npm install -g npm@latest 18 | 19 | after_success: 20 | - npm run coverage 21 | -------------------------------------------------------------------------------- /lib/constants.js: -------------------------------------------------------------------------------- 1 | const BASH_LOCATION = '~/.bashrc'; 2 | const FISH_LOCATION = '~/.config/fish/config.fish'; 3 | const ZSH_LOCATION = '~/.zshrc'; 4 | const COMPLETION_DIR = '~/.config/tabtab'; 5 | const TABTAB_SCRIPT_NAME = '__tabtab'; 6 | 7 | module.exports = { 8 | BASH_LOCATION, 9 | ZSH_LOCATION, 10 | FISH_LOCATION, 11 | COMPLETION_DIR, 12 | TABTAB_SCRIPT_NAME 13 | }; 14 | -------------------------------------------------------------------------------- /lib/utils/systemShell.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Utility to figure out the shell used on the system. 3 | * 4 | * Sadly, we can't use `echo $0` in node, maybe with more work. So we rely on 5 | * process.env.SHELL. 6 | * 7 | * TODO: More work on this, namely to detect Git bash on Windows (bash.exe) 8 | */ 9 | const systemShell = () => (process.env.SHELL || '').split('/').slice(-1)[0]; 10 | 11 | module.exports = systemShell; 12 | -------------------------------------------------------------------------------- /lib/utils/exists.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const untildify = require('untildify'); 3 | const { promisify } = require('es6-promisify'); 4 | 5 | const readFile = promisify(fs.readFile); 6 | 7 | module.exports = async file => { 8 | let fileExists; 9 | try { 10 | await readFile(untildify(file)); 11 | fileExists = true; 12 | } catch (err) { 13 | fileExists = false; 14 | } 15 | 16 | return fileExists; 17 | }; 18 | -------------------------------------------------------------------------------- /examples/tabtab-test-complete/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tabtab-test-complete", 3 | "version": "1.0.0", 4 | "bin": { 5 | "tabtab-test": "index.js" 6 | }, 7 | "description": "Basic test package for tabtab completion", 8 | "scripts": { 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "debug": "^4.0.1", 15 | "minimist": "^1.2.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/scripts/zsh.sh: -------------------------------------------------------------------------------- 1 | ###-begin-{pkgname}-completion-### 2 | if type compdef &>/dev/null; then 3 | _{pkgname}_completion () { 4 | local reply 5 | local si=$IFS 6 | 7 | IFS=$'\n' reply=($(COMP_CWORD="$((CURRENT-1))" COMP_LINE="$BUFFER" COMP_POINT="$CURSOR" {completer} completion -- "${words[@]}")) 8 | IFS=$si 9 | 10 | _describe 'values' reply 11 | } 12 | compdef _{pkgname}_completion {pkgname} 13 | fi 14 | ###-end-{pkgname}-completion-### 15 | -------------------------------------------------------------------------------- /lib/scripts/fish.sh: -------------------------------------------------------------------------------- 1 | ###-begin-{pkgname}-completion-### 2 | function _{pkgname}_completion 3 | set cmd (commandline -o) 4 | set cursor (commandline -C) 5 | set words (node -pe "'$cmd'.split(' ').length") 6 | 7 | set completions (eval env DEBUG=\"" \"" COMP_CWORD=\""$words\"" COMP_LINE=\""$cmd \"" COMP_POINT=\""$cursor\"" {completer} completion -- $cmd) 8 | 9 | for completion in $completions 10 | echo -e $completion 11 | end 12 | end 13 | 14 | complete -f -d '{pkgname}' -c {pkgname} -a "(eval _{pkgname}_completion)" 15 | ###-end-{pkgname}-completion-### 16 | -------------------------------------------------------------------------------- /test/basic.js: -------------------------------------------------------------------------------- 1 | const tabtab = require('..'); 2 | const assert = require('assert'); 3 | 4 | describe('tabtab', () => { 5 | it('tabtab.shell()', () => { 6 | let shell = tabtab.shell(); 7 | assert.equal(shell, 'bash'); 8 | 9 | const previousShell = process.env.SHELL; 10 | process.env.SHELL = '/bin/bash'; 11 | shell = tabtab.shell(); 12 | assert.equal(shell, 'bash'); 13 | 14 | process.env.SHELL = '/usr/bin/zsh'; 15 | shell = tabtab.shell(); 16 | assert.equal(shell, 'zsh'); 17 | 18 | process.env.SHELL = '/usr/bin/fish'; 19 | shell = tabtab.shell(); 20 | assert.equal(shell, 'fish'); 21 | 22 | process.env.SHELL = previousShell; 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /test/parse-env.js: -------------------------------------------------------------------------------- 1 | const tabtab = require('..'); 2 | const assert = require('assert'); 3 | 4 | describe('tabtab.parseEnv()', () => { 5 | it('parseEnv with COMP stuff', () => { 6 | assert.equal(typeof tabtab.parseEnv, 'function'); 7 | 8 | const result = tabtab.parseEnv( 9 | Object.assign({}, process.env, { 10 | COMP_CWORD: 3, 11 | COMP_LINE: 'foo bar baz', 12 | COMP_POINT: 11 13 | }) 14 | ); 15 | 16 | assert.deepEqual(result, { 17 | complete: true, 18 | words: 3, 19 | point: 11, 20 | line: 'foo bar baz', 21 | partial: 'foo bar baz', 22 | last: 'baz', 23 | lastPartial: 'baz', 24 | prev: 'bar' 25 | }); 26 | }); 27 | 28 | it('parseEnv without COMP stuff', () => { 29 | const result = tabtab.parseEnv(Object.assign({}, process.env)); 30 | assert.equal(result.complete, false); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /lib/scripts/bash.sh: -------------------------------------------------------------------------------- 1 | ###-begin-{pkgname}-completion-### 2 | if type complete &>/dev/null; then 3 | _{pkgname}_completion () { 4 | local words cword 5 | if type _get_comp_words_by_ref &>/dev/null; then 6 | _get_comp_words_by_ref -n = -n @ -n : -w words -i cword 7 | else 8 | cword="$COMP_CWORD" 9 | words=("${COMP_WORDS[@]}") 10 | fi 11 | 12 | local si="$IFS" 13 | IFS=$'\n' COMPREPLY=($(COMP_CWORD="$cword" \ 14 | COMP_LINE="$COMP_LINE" \ 15 | COMP_POINT="$COMP_POINT" \ 16 | {completer} completion -- "${words[@]}" \ 17 | 2>/dev/null)) || return $? 18 | IFS="$si" 19 | if type __ltrim_colon_completions &>/dev/null; then 20 | __ltrim_colon_completions "${words[cword]}" 21 | fi 22 | } 23 | complete -o default -F _{pkgname}_completion {pkgname} 24 | fi 25 | ###-end-{pkgname}-completion-### 26 | -------------------------------------------------------------------------------- /examples/tabtab-test-complete/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tabtab-test-complete", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "debug": { 8 | "version": "4.0.1", 9 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.0.1.tgz", 10 | "integrity": "sha512-K23FHJ/Mt404FSlp6gSZCevIbTMLX0j3fmHhUEhQ3Wq0FMODW3+cUSoLdy1Gx4polAf4t/lphhmHH35BB8cLYw==", 11 | "requires": { 12 | "ms": "^2.1.1" 13 | } 14 | }, 15 | "minimist": { 16 | "version": "1.2.0", 17 | "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", 18 | "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" 19 | }, 20 | "ms": { 21 | "version": "2.1.1", 22 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", 23 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/utils/tabtabDebug.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const util = require('util'); 3 | 4 | /** 5 | * If TABTAB_DEBUG env is set, make it so that debug statements are also log to 6 | * TABTAB_DEBUG file provided. 7 | */ 8 | const tabtabDebug = name => { 9 | /* eslint-disable global-require */ 10 | let debug = require('debug')(name); 11 | 12 | if (process.env.TABTAB_DEBUG) { 13 | const file = process.env.TABTAB_DEBUG; 14 | const stream = fs.createWriteStream(file, { 15 | flags: 'a+' 16 | }); 17 | 18 | const log = (...args) => { 19 | args = args.map(arg => { 20 | if (typeof arg === 'string') return arg; 21 | return JSON.stringify(arg); 22 | }); 23 | 24 | const str = `${util.format(...args)}\n`; 25 | stream.write(str); 26 | }; 27 | 28 | if (process.env.COMP_LINE) { 29 | debug = log; 30 | } else { 31 | debug.log = log; 32 | } 33 | } 34 | 35 | return debug; 36 | }; 37 | 38 | module.exports = tabtabDebug; 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT 2011-2018 License (MIT) 2 | 3 | Copyright (c) Mickael Daniel 4 | 5 | Permission is hereby granted, free of charge, to any person 6 | obtaining a copy of this software and associated documentation 7 | files (the "Software"), to deal in the Software without 8 | restriction, including without limitation the rights to use, 9 | copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the 11 | Software is furnished to do so, subject to the following 12 | conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 19 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 21 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 22 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 | OTHER DEALINGS IN THE SOFTWARE. 25 | -------------------------------------------------------------------------------- /examples/tabtab-test-complete/readme.md: -------------------------------------------------------------------------------- 1 | # tabtab-test-complete 2 | 3 | A simple package to test out tabtab against real completions. 4 | 5 | To install, simply run `npm link` 6 | 7 | npm link 8 | 9 | It'll install the following binary system-wide: 10 | 11 | - tabtab-test: The actual binary being completed 12 | 13 | ## Shell notes 14 | 15 | To test against **bash**, make sure to have `$SHELL` set to either `bash` or `/bin/bash` or similar. 16 | 17 | To test against **zsh**, make sure to have zsh installed, and then, if you use bash 18 | as your standard SHELL, type `zsh`. It'll spawn a new zsh session. Within this, 19 | run `SHELL=zsh` to set the environment accordingly so that tabtab understands 20 | the current shell used is actually zsh. 21 | 22 | Similarly, to test against **fish**, make sure to have fish installed, and then 23 | the same steps to reproduce. This time, make sure to type `fish` and run `set 24 | SHELL fish`. This is required for tabtab to understand the shell being used is 25 | actually fish. 26 | 27 | Those steps are not required if testing against your system shell (possibly using `chsh`). 28 | 29 | ## Completion install 30 | 31 | In this example package, simply run: 32 | 33 | tabtab-test install-completion 34 | 35 | You'll need to do this for each and every shell you're testing against. Follow 36 | the `Shell notes` described above for details. 37 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "mklabs", 3 | "name": "tabtab", 4 | "description": "tab completion helpers, for node cli programs. Inspired by npm completion.", 5 | "main": "lib/index.js", 6 | "files": [ 7 | "lib/**/*.{js,sh}" 8 | ], 9 | "scripts": { 10 | "test": "mkdirp ~/.config/tabtab && DEBUG='tabtab*' c8 mocha --timeout 5000", 11 | "posttest": "npm run eslint", 12 | "mocha": "DEBUG='tabtab*' mocha --timeout 5000", 13 | "coverage": "c8 report --reporter=text-lcov | coveralls", 14 | "coverage-html": "npm run mocha && c8 report --reporter=html && serve coverage", 15 | "eslint": "eslint lib/ test/", 16 | "watch": "npm-watch", 17 | "readme": "remark readme.md --use toc --output", 18 | "changelog": "auto-changelog -p", 19 | "api": "for file in `echo index.js installer.js prompt.js`; do jsdoc2md lib/$file > api/$file.md; done", 20 | "docs": "npm run api && npm run readme && npm run changelog" 21 | }, 22 | "watch": { 23 | "test": "{lib,test}/**/*.js" 24 | }, 25 | "devDependencies": { 26 | "auto-changelog": "^1.8.0", 27 | "c8": "^3.2.0", 28 | "coveralls": "^3.0.2", 29 | "eslint-config-mklabs": "^1.0.9", 30 | "inquirer-test": "^2.0.1", 31 | "jsdoc-to-markdown": "^4.0.1", 32 | "mocha": "^5.2.0", 33 | "npm-watch": "^0.4.0", 34 | "remark-cli": "^5.0.0", 35 | "remark-toc": "^5.0.0", 36 | "serve": "^10.0.2" 37 | }, 38 | "license": "MIT", 39 | "keywords": [ 40 | "terminal", 41 | "tab", 42 | "unix", 43 | "console", 44 | "complete", 45 | "completion" 46 | ], 47 | "repository": { 48 | "type": "git", 49 | "url": "https://github.com/mklabs/tabtab.git" 50 | }, 51 | "dependencies": { 52 | "debug": "^4.0.1", 53 | "es6-promisify": "^6.0.0", 54 | "inquirer": "^6.0.0", 55 | "minimist": "^1.2.0", 56 | "mkdirp": "^0.5.1", 57 | "untildify": "^3.0.3" 58 | }, 59 | "auto-changelog": { 60 | "template": "keepachangelog", 61 | "unreleased": true, 62 | "commitLimit": false, 63 | "ignoreCommitPattern": "changelog|readme|^test" 64 | }, 65 | "version": "3.0.3" 66 | } 67 | -------------------------------------------------------------------------------- /lib/prompt.js: -------------------------------------------------------------------------------- 1 | const inquirer = require('inquirer'); 2 | const path = require('path'); 3 | const debug = require('./utils/tabtabDebug')('tabtab:prompt'); 4 | 5 | /** 6 | * Asks user about SHELL and desired location. 7 | * 8 | * It is too difficult to check spawned SHELL, the user has to use chsh before 9 | * it is reflected in process.env.SHELL 10 | */ 11 | const prompt = () => { 12 | const ask = inquirer.createPromptModule(); 13 | 14 | const questions = [ 15 | { 16 | type: 'list', 17 | name: 'shell', 18 | message: 'Which Shell do you use ?', 19 | choices: ['bash', 'zsh', 'fish'], 20 | default: 'bash' 21 | } 22 | ]; 23 | 24 | const locations = { 25 | bash: '~/.bashrc', 26 | zsh: '~/.zshrc', 27 | fish: '~/.config/fish/config.fish' 28 | }; 29 | 30 | const finalAnswers = {}; 31 | 32 | return ask(questions) 33 | .then(answers => { 34 | const { shell } = answers; 35 | debug('answers', shell); 36 | 37 | const location = locations[shell]; 38 | debug(`Will install completion to ${location}`); 39 | 40 | Object.assign(finalAnswers, { location, shell }); 41 | return location; 42 | }) 43 | .then(location => 44 | ask({ 45 | type: 'confirm', 46 | name: 'locationOK', 47 | message: `We will install completion to ${location}, is it ok ?` 48 | }) 49 | ) 50 | .then(answers => { 51 | const { locationOK } = answers; 52 | if (locationOK) { 53 | debug('location is ok, return', finalAnswers); 54 | return finalAnswers; 55 | } 56 | 57 | // otherwise, ask for specific **absolute** path 58 | return ask({ 59 | name: 'userLocation', 60 | message: 'Which path then ? Must be absolute.', 61 | validate: input => { 62 | debug('Validating input', input); 63 | return path.isAbsolute(input); 64 | } 65 | }).then(lastAnswer => { 66 | const { userLocation } = lastAnswer; 67 | console.log(`Very well, we will install using ${userLocation}`); 68 | Object.assign(finalAnswers, { location: userLocation }); 69 | 70 | return finalAnswers; 71 | }); 72 | }); 73 | }; 74 | 75 | module.exports = prompt; 76 | -------------------------------------------------------------------------------- /examples/tabtab-test-complete/index.js: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | 3 | const opts = require('minimist')(process.argv.slice(2), { 4 | string: ['foo', 'bar'], 5 | boolean: ['help', 'version', 'loglevel'] 6 | }); 7 | 8 | const tabtab = require('../..'); 9 | 10 | const args = opts._; 11 | 12 | const completion = env => { 13 | if (!env.complete) return; 14 | 15 | if (env.prev === 'someCommand') { 16 | return tabtab.log(['is', 'this', 'the', 'real', 'life']); 17 | } 18 | 19 | if (env.prev === 'anotherOne') { 20 | return tabtab.log(['is', 'this', 'just', 'fantasy']); 21 | } 22 | 23 | if (env.prev === '--loglevel') { 24 | return tabtab.log(['error', 'warn', 'info', 'notice', 'verbose']); 25 | } 26 | 27 | return tabtab.log([ 28 | '--help', 29 | '--version', 30 | '--loglevel', 31 | 'foo', 32 | 'bar', 33 | 'install-completion', 34 | 'uninstall-completion', 35 | 'completion', 36 | 'someCommand:someCommand is a some kind of command with a description', 37 | { 38 | name: 'someOtherCommand:hey', 39 | description: 'You must add a description for items with ":" in them' 40 | }, 41 | 'anotherOne' 42 | ]); 43 | }; 44 | 45 | const init = async () => { 46 | const cmd = args[0]; 47 | 48 | if (opts.help) { 49 | return console.log('Output help here'); 50 | } 51 | 52 | if (opts.version) { 53 | return console.log('Output version here'); 54 | } 55 | 56 | if (opts.loglevel) { 57 | return console.log('Output version here'); 58 | } 59 | 60 | if (cmd === 'foo') { 61 | return console.log('foobar'); 62 | } 63 | 64 | if (cmd === 'bar') { 65 | return console.log('barbar'); 66 | } 67 | 68 | if (cmd === 'someCommand') { 69 | return console.log('is this the real life ?'); 70 | } 71 | 72 | if (cmd === 'anotherOne') { 73 | return console.log('is this just fantasy ?'); 74 | } 75 | 76 | if (cmd === 'install-completion') { 77 | // Here we install for the program `tabtab-test` (this file), with 78 | // completer being the same program. Sometimes, you want to complete 79 | // another program that's where the `completer` option might come handy. 80 | await tabtab 81 | .install({ 82 | name: 'tabtab-test', 83 | completer: 'tabtab-test' 84 | }) 85 | .catch(err => console.error('INSTALL ERROR', err)); 86 | 87 | return; 88 | } 89 | 90 | if (cmd === 'uninstall-completion') { 91 | // Here we uninstall for the program `tabtab-test` (this file). 92 | await tabtab 93 | .uninstall({ 94 | name: 'tabtab-test' 95 | }) 96 | .catch(err => console.error('UNINSTALL ERROR', err)); 97 | 98 | return; 99 | } 100 | 101 | if (cmd === 'completion') { 102 | const env = tabtab.parseEnv(process.env); 103 | return completion(env); 104 | } 105 | }; 106 | 107 | init(); 108 | -------------------------------------------------------------------------------- /test/tabtab-install.js: -------------------------------------------------------------------------------- 1 | const tabtab = require('..'); 2 | const assert = require('assert'); 3 | const run = require('inquirer-test'); 4 | const debug = require('debug')('tabtab:test:install'); 5 | const untildify = require('untildify'); 6 | const path = require('path'); 7 | const fs = require('fs'); 8 | const { promisify } = require('es6-promisify'); 9 | const { COMPLETION_DIR } = require('../lib/constants'); 10 | const { rejects, setupSuiteForInstall } = require('./utils'); 11 | 12 | const readFile = promisify(fs.readFile); 13 | 14 | // For node 7 / 8 15 | assert.rejects = rejects; 16 | 17 | // inquirer-test needs a little bit more time, or my setup 18 | const TIMEOUT = 500; 19 | const { ENTER } = run; 20 | 21 | describe('tabtab.install()', () => { 22 | it('is a function', () => { 23 | assert.equal(typeof tabtab.install, 'function'); 24 | }); 25 | 26 | it('rejects on missing options', async () => { 27 | await assert.rejects(async () => tabtab.install(), TypeError); 28 | }); 29 | 30 | it('rejects on missing name options', async () => { 31 | await assert.rejects( 32 | async () => tabtab.install({}), 33 | /options\.name is required/ 34 | ); 35 | }); 36 | 37 | it('rejects on missing completer options', async () => { 38 | await assert.rejects( 39 | async () => tabtab.install({ name: 'foo' }), 40 | /options\.completer is required/ 41 | ); 42 | }); 43 | 44 | describe('tabtab.install() on ~/.bashrc', () => { 45 | setupSuiteForInstall(); 46 | 47 | it('asks about shell (bash) with custom location', () => { 48 | const cliPath = path.join(__dirname, 'fixtures/tabtab-install.js'); 49 | 50 | return run( 51 | [cliPath], 52 | [ENTER, 'n', ENTER, '/tmp/foo', ENTER], 53 | TIMEOUT 54 | ).then(result => { 55 | debug('Test result', result); 56 | 57 | assert.ok(/Which Shell do you use \? bash/.test(result)); 58 | assert.ok( 59 | /We will install completion to ~\/\.bashrc, is it ok \?/.test(result) 60 | ); 61 | assert.ok(/Which path then \? Must be absolute/.test(result)); 62 | assert.ok(/Very well, we will install using \/tmp\/foo/.test(result)); 63 | }); 64 | }); 65 | 66 | it('asks about shell (bash) with default location', () => { 67 | const cliPath = path.join(__dirname, 'fixtures/tabtab-install.js'); 68 | 69 | return run([cliPath], [ENTER, ENTER], TIMEOUT) 70 | .then(result => { 71 | debug('Test result', result); 72 | 73 | assert.ok(/Which Shell do you use \? bash/.test(result)); 74 | assert.ok( 75 | /install completion to ~\/\.bashrc, is it ok \? Yes/.test(result) 76 | ); 77 | }) 78 | .then(() => readFile(untildify('~/.bashrc'), 'utf8')) 79 | .then(filecontent => { 80 | assert.ok(/tabtab source for packages/.test(filecontent)); 81 | assert.ok(/uninstall by removing these lines/.test(filecontent)); 82 | assert.ok( 83 | filecontent.match(`. ${path.join(COMPLETION_DIR, '__tabtab.bash')}`) 84 | ); 85 | }); 86 | }); 87 | }); 88 | }); 89 | -------------------------------------------------------------------------------- /test/utils/index.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const untildify = require('untildify'); 4 | const { promisify } = require('es6-promisify'); 5 | const { COMPLETION_DIR, TABTAB_SCRIPT_NAME } = require('../../lib/constants'); 6 | 7 | const { exists } = require('../../lib/utils'); 8 | 9 | const writeFile = promisify(fs.writeFile); 10 | const readFile = promisify(fs.readFile); 11 | 12 | /** 13 | * Returns both { exists, content } 14 | * 15 | * @param {filename} filename - The file to check and read 16 | */ 17 | const readIfExists = async filename => { 18 | /* eslint-disable no-return-await */ 19 | const filepath = untildify(filename); 20 | const fileExists = await exists(filepath); 21 | const content = fileExists ? await readFile(filepath, 'utf8') : ''; 22 | 23 | return { 24 | exists: fileExists, 25 | content 26 | }; 27 | }; 28 | 29 | const afterWrites = (prevBashrc, prevScript) => async () => { 30 | const bashrc = untildify('~/.bashrc'); 31 | const tabtabScript = untildify( 32 | path.join(COMPLETION_DIR, `${TABTAB_SCRIPT_NAME}.bash`) 33 | ); 34 | 35 | await writeFile(bashrc, prevBashrc); 36 | await writeFile(tabtabScript, prevScript); 37 | }; 38 | 39 | /** This simply setup a suite with after hook for tabtab.install. 40 | * 41 | * Defaults to afterEach, pass in true to make it so that it uses "after" 42 | * instead. 43 | * 44 | * @param {Boolean} shouldUseAfter - True to use after instead of afterEach 45 | */ 46 | const setupSuiteForInstall = async (shouldUseAfter = false) => { 47 | const files = {}; 48 | const hook = shouldUseAfter ? after : afterEach; 49 | const tabtabScript = path.join(COMPLETION_DIR, `${TABTAB_SCRIPT_NAME}.bash`); 50 | 51 | before(async () => { 52 | const { exists: bashrcExists, content: bashrcContent } = await readIfExists( 53 | '~/.bashrc' 54 | ); 55 | 56 | const { 57 | exists: tabtabScriptExists, 58 | content: tabtabScriptContent 59 | } = await readIfExists(tabtabScript); 60 | 61 | files.bashrcExists = bashrcExists; 62 | files.bashrcContent = bashrcContent; 63 | files.tabtabScriptExists = tabtabScriptExists; 64 | files.tabtabScriptContent = tabtabScriptContent; 65 | }); 66 | 67 | hook(async () => { 68 | const { 69 | bashrcExists, 70 | bashrcContent, 71 | tabtabScriptExists, 72 | tabtabScriptContent 73 | } = files; 74 | 75 | if (bashrcExists) { 76 | await writeFile(untildify('~/.bashrc'), bashrcContent); 77 | } 78 | 79 | if (tabtabScriptExists) { 80 | await writeFile(untildify(tabtabScript), tabtabScriptContent); 81 | } 82 | }); 83 | }; 84 | 85 | // For node 7 / 8 86 | const rejects = async (promise, error, message = '') => { 87 | let toThrow; 88 | await promise().catch(err => { 89 | if (error instanceof RegExp) { 90 | const ok = error.test(err.message); 91 | if (!ok) { 92 | toThrow = new Error( 93 | `AssertionError: ${error} is not validated 94 | ${message}` 95 | ); 96 | } 97 | } else { 98 | const ok = err instanceof error; 99 | if (!ok) { 100 | toThrow = new Error( 101 | `AssertionError: ${err.name} is not an instanceof ${error.name} 102 | ${message}` 103 | ); 104 | } 105 | } 106 | }); 107 | 108 | if (toThrow) { 109 | throw toThrow; 110 | } 111 | }; 112 | 113 | module.exports = { 114 | readIfExists, 115 | rejects, 116 | afterWrites, 117 | setupSuiteForInstall 118 | }; 119 | -------------------------------------------------------------------------------- /test/log.js: -------------------------------------------------------------------------------- 1 | const tabtab = require('..'); 2 | const assert = require('assert'); 3 | 4 | describe('tabtab.log', () => { 5 | it('tabtab.log throws an Error in case args is not an Array', () => { 6 | assert.throws(() => { 7 | tabtab.log('foo', 'bar'); 8 | }, /^Error: log: Invalid arguments, must be an array$/); 9 | }); 10 | 11 | const logTestHelper = items => { 12 | const logs = []; 13 | const { log } = console; 14 | console.log = data => logs.push(data); 15 | tabtab.log(items); 16 | console.log = log; 17 | return logs; 18 | }; 19 | 20 | it('tabtab.log logs item to the console', () => { 21 | assert.equal(typeof tabtab.log, 'function'); 22 | 23 | const logs = logTestHelper(['--foo', '--bar']); 24 | 25 | assert.equal(logs.length, 2); 26 | assert.deepStrictEqual(logs, ['--foo', '--bar']); 27 | }); 28 | 29 | it('tabtab.log accepts { name, description }', () => { 30 | const logs = logTestHelper([ 31 | { name: '--foo', description: 'Foo options' }, 32 | { name: '--bar', description: 'Bar option' } 33 | ]); 34 | 35 | assert.equal(logs.length, 2); 36 | assert.deepStrictEqual(logs, ['--foo', '--bar']); 37 | }); 38 | 39 | it('tabtab.log normalize String and Objects', () => { 40 | const logs = logTestHelper([ 41 | { name: '--foo', description: 'Foo options' }, 42 | { name: '--bar', description: 'Bar option' }, 43 | 'foobar' 44 | ]); 45 | 46 | assert.equal(logs.length, 3); 47 | assert.deepStrictEqual(logs, ['--foo', '--bar', 'foobar']); 48 | }); 49 | 50 | it('tabtab.log normalize String and Objects, with description stripped out on Bash', () => { 51 | const shell = process.env.SHELL; 52 | process.env.SHELL = '/bin/bash'; 53 | const logs = logTestHelper([ 54 | { name: '--foo', description: 'Foo options' }, 55 | { name: '--bar', description: 'Bar option' }, 56 | 'foobar', 57 | 'barfoo:barfoo is not foobar' 58 | ]); 59 | 60 | assert.equal(logs.length, 4); 61 | assert.deepStrictEqual(logs, ['--foo', '--bar', 'foobar', 'barfoo']); 62 | process.env.SHELL = shell; 63 | }); 64 | 65 | it('tabtab.log with description NOT stripped out on Zsh', () => { 66 | const shell = process.env.SHELL; 67 | process.env.SHELL = '/usr/bin/zsh'; 68 | const logs = logTestHelper([ 69 | { name: '--foo', description: 'Foo option' }, 70 | { name: '--bar', description: 'Bar option' }, 71 | 'foobar', 72 | 'barfoo:barfoo is not foobar' 73 | ]); 74 | 75 | assert.equal(logs.length, 4); 76 | assert.deepStrictEqual(logs, [ 77 | '--foo:Foo option', 78 | '--bar:Bar option', 79 | 'foobar', 80 | 'barfoo:barfoo is not foobar' 81 | ]); 82 | process.env.SHELL = shell; 83 | }); 84 | 85 | it('tabtab.log with description NOT stripped out on fish', () => { 86 | const shell = process.env.SHELL; 87 | process.env.SHELL = '/usr/bin/fish'; 88 | const logs = logTestHelper([ 89 | { name: '--foo', description: 'Foo option' }, 90 | { name: '--bar', description: 'Bar option' }, 91 | 'foobar', 92 | 'barfoo:barfoo is not foobar' 93 | ]); 94 | 95 | assert.equal(logs.length, 4); 96 | assert.deepStrictEqual(logs, [ 97 | '--foo\tFoo option', 98 | '--bar\tBar option', 99 | 'foobar', 100 | 'barfoo\tbarfoo is not foobar' 101 | ]); 102 | process.env.SHELL = shell; 103 | }); 104 | 105 | it('tabtab.log could use {name, description} for completions with ":" in them', () => { 106 | const shell = process.env.SHELL; 107 | process.env.SHELL = '/usr/bin/zsh'; 108 | const logs = logTestHelper([ 109 | { name: '--foo:bar', description: 'Foo option' }, 110 | { name: '--bar:foo', description: 'Bar option' }, 111 | 'foobar', 112 | 'barfoo:barfoo is not foobar' 113 | ]); 114 | 115 | assert.equal(logs.length, 4); 116 | assert.deepStrictEqual(logs, [ 117 | '--foo\\:bar:Foo option', 118 | '--bar\\:foo:Bar option', 119 | 'foobar', 120 | 'barfoo:barfoo is not foobar' 121 | ]); 122 | process.env.SHELL = shell; 123 | }); 124 | }); 125 | -------------------------------------------------------------------------------- /test/installer.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | const untildify = require('untildify'); 5 | const { promisify } = require('es6-promisify'); 6 | const { 7 | install, 8 | uninstall, 9 | writeToShellConfig, 10 | writeToCompletionScript 11 | } = require('../lib/installer'); 12 | const { COMPLETION_DIR, TABTAB_SCRIPT_NAME } = require('../lib/constants'); 13 | const { rejects, setupSuiteForInstall } = require('./utils'); 14 | 15 | // For node 7 / 8 16 | assert.rejects = rejects; 17 | 18 | const readFile = promisify(fs.readFile); 19 | const writeFile = promisify(fs.writeFile); 20 | 21 | describe('installer', () => { 22 | it('has install / uninstall functions', () => { 23 | assert.equal(typeof install, 'function'); 24 | assert.equal(typeof uninstall, 'function'); 25 | }); 26 | 27 | it('install rejects on missing options', async () => { 28 | await assert.rejects(async () => install(), /options.name is required/); 29 | await assert.rejects( 30 | async () => install({ name: 'foo ' }), 31 | /options.completer is required/ 32 | ); 33 | 34 | await assert.rejects( 35 | async () => install({ name: 'foo ', completer: 'foo-complete' }), 36 | /options.location is required/ 37 | ); 38 | }); 39 | 40 | it('uninstall rejects on missing options', async () => { 41 | await assert.rejects( 42 | async () => uninstall(), 43 | /Unable to uninstall if options.name is missing/, 44 | 'Uninstall should throw the expected message when name is missing' 45 | ); 46 | }); 47 | 48 | it('has writeToShellConfig / writeToCompletionScript functions', () => { 49 | assert.equal(typeof writeToShellConfig, 'function'); 50 | assert.equal(typeof writeToCompletionScript, 'function'); 51 | }); 52 | 53 | describe('installer on ~/.bashrc', () => { 54 | setupSuiteForInstall(true); 55 | 56 | before(async () => { 57 | // Make sure __tabtab.bash starts with empty content, it'll be restored by setupSuiteForInstall 58 | await writeFile( 59 | untildify(path.join(COMPLETION_DIR, `${TABTAB_SCRIPT_NAME}.bash`)), 60 | '' 61 | ); 62 | }); 63 | 64 | it('installs the necessary line into ~/.bashrc', () => 65 | install({ 66 | name: 'foo', 67 | completer: 'foo-complete', 68 | location: '~/.bashrc' 69 | }) 70 | .then(() => readFile(untildify('~/.bashrc'), 'utf8')) 71 | .then(filecontent => { 72 | assert.ok(/tabtab source for packages/.test(filecontent)); 73 | assert.ok(/uninstall by removing these lines/.test(filecontent)); 74 | assert.ok( 75 | filecontent.match(`. ${path.join(COMPLETION_DIR, '__tabtab.bash')}`) 76 | ); 77 | }) 78 | .then(() => 79 | readFile( 80 | untildify(path.join(COMPLETION_DIR, '__tabtab.bash')), 81 | 'utf8' 82 | ) 83 | ) 84 | .then(filecontent => { 85 | assert.ok(/tabtab source for foo/.test(filecontent)); 86 | assert.ok( 87 | filecontent.match(`. ${path.join(COMPLETION_DIR, 'foo.bash')}`) 88 | ); 89 | })); 90 | 91 | it('uninstalls the necessary line from ~/.bashrc and completion scripts', () => 92 | uninstall({ 93 | name: 'foo' 94 | }) 95 | .then(() => readFile(untildify('~/.bashrc'), 'utf8')) 96 | .then(filecontent => { 97 | assert.ok(!/tabtab source for packages/.test(filecontent)); 98 | assert.ok(!/uninstall by removing these lines/.test(filecontent)); 99 | assert.ok( 100 | !filecontent.match( 101 | `. ${path.join(COMPLETION_DIR, '__tabtab.bash')}` 102 | ) 103 | ); 104 | }) 105 | .then(() => 106 | readFile( 107 | untildify(path.join(COMPLETION_DIR, '__tabtab.bash')), 108 | 'utf8' 109 | ) 110 | ) 111 | .then(filecontent => { 112 | assert.ok(!/tabtab source for foo/.test(filecontent)); 113 | assert.ok( 114 | !filecontent.match(`. ${path.join(COMPLETION_DIR, 'foo.bash')}`) 115 | ); 116 | })); 117 | }); 118 | }); 119 | -------------------------------------------------------------------------------- /api/index.js.md: -------------------------------------------------------------------------------- 1 | ## Functions 2 | 3 |
4 |
install(Options)
5 |

Install and enable completion on user system. It'll ask for:

6 | 10 |
11 |
parseEnv()
12 |

Public: Main utility to extract information from command line arguments and 13 | Environment variables, namely COMP args in "plumbing" mode.

14 |

options - The options hash as parsed by minimist, plus an env property 15 | representing user environment (default: { env: process.env }) 16 | :_ - The arguments Array parsed by minimist (positional arguments) 17 | :env - The environment Object that holds COMP args (default: process.env)

18 |

Examples

19 |

const env = tabtab.parseEnv(); 20 | // env: 21 | // complete A Boolean indicating whether we act in "plumbing mode" or not 22 | // words The Number of words in the completed line 23 | // point A Number indicating cursor position 24 | // line The String input line 25 | // partial The String part of line preceding cursor position 26 | // last The last String word of the line 27 | // lastPartial The last word String of partial 28 | // prev The String word preceding last

29 |

Returns the data env object.

30 |
31 |
completionItem(item)
32 |

Helper to normalize String and Objects with { name, description } when logging out.

33 |
34 |
log(Arguments)
35 |

Main logging utility to pass completion items.

36 |

This is simply an helper to log to stdout with each item separated by a new 37 | line.

38 |

Bash needs in addition to filter out the args for the completion to work 39 | (zsh, fish don't need this).

40 |
41 |
42 | 43 | 44 | 45 | ## install(Options) 46 | Install and enable completion on user system. It'll ask for: 47 | 48 | - SHELL (bash, zsh or fish) 49 | - Path to shell script (with sensible defaults) 50 | 51 | **Kind**: global function 52 | 53 | | Param | Type | Description | 54 | | --- | --- | --- | 55 | | Options | Object | to use with namely `name` and `completer` | 56 | 57 | 58 | 59 | ## parseEnv() 60 | Public: Main utility to extract information from command line arguments and 61 | Environment variables, namely COMP args in "plumbing" mode. 62 | 63 | options - The options hash as parsed by minimist, plus an env property 64 | representing user environment (default: { env: process.env }) 65 | :_ - The arguments Array parsed by minimist (positional arguments) 66 | :env - The environment Object that holds COMP args (default: process.env) 67 | 68 | Examples 69 | 70 | const env = tabtab.parseEnv(); 71 | // env: 72 | // complete A Boolean indicating whether we act in "plumbing mode" or not 73 | // words The Number of words in the completed line 74 | // point A Number indicating cursor position 75 | // line The String input line 76 | // partial The String part of line preceding cursor position 77 | // last The last String word of the line 78 | // lastPartial The last word String of partial 79 | // prev The String word preceding last 80 | 81 | Returns the data env object. 82 | 83 | **Kind**: global function 84 | 85 | 86 | ## completionItem(item) 87 | Helper to normalize String and Objects with { name, description } when logging out. 88 | 89 | **Kind**: global function 90 | 91 | | Param | Type | Description | 92 | | --- | --- | --- | 93 | | item | String \| Object | Item to normalize | 94 | 95 | 96 | 97 | ## log(Arguments) 98 | Main logging utility to pass completion items. 99 | 100 | This is simply an helper to log to stdout with each item separated by a new 101 | line. 102 | 103 | Bash needs in addition to filter out the args for the completion to work 104 | (zsh, fish don't need this). 105 | 106 | **Kind**: global function 107 | 108 | | Param | Type | Description | 109 | | --- | --- | --- | 110 | | Arguments | Array | to log, Strings or Objects with name and description property. | 111 | 112 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | const prompt = require('./prompt'); 2 | const installer = require('./installer'); 3 | const { tabtabDebug, systemShell } = require('./utils'); 4 | 5 | // If TABTAB_DEBUG env is set, make it so that debug statements are also log to 6 | // TABTAB_DEBUG file provided. 7 | const debug = tabtabDebug('tabtab'); 8 | 9 | /** 10 | * Install and enable completion on user system. It'll ask for: 11 | * 12 | * - SHELL (bash, zsh or fish) 13 | * - Path to shell script (with sensible defaults) 14 | * 15 | * @param {Object} Options to use with namely `name` and `completer` 16 | * 17 | */ 18 | const install = async (options = { name: '', completer: '' }) => { 19 | const { name, completer } = options; 20 | if (!name) throw new TypeError('options.name is required'); 21 | if (!completer) throw new TypeError('options.completer is required'); 22 | 23 | return prompt().then(({ location }) => 24 | installer.install({ 25 | name, 26 | completer, 27 | location 28 | }) 29 | ); 30 | }; 31 | 32 | const uninstall = async (options = { name: '' }) => { 33 | const { name } = options; 34 | if (!name) throw new TypeError('options.name is required'); 35 | 36 | return installer 37 | .uninstall({ name }) 38 | .catch(err => console.error('ERROR while uninstalling', err)); 39 | }; 40 | 41 | /** 42 | * Public: Main utility to extract information from command line arguments and 43 | * Environment variables, namely COMP args in "plumbing" mode. 44 | * 45 | * options - The options hash as parsed by minimist, plus an env property 46 | * representing user environment (default: { env: process.env }) 47 | * :_ - The arguments Array parsed by minimist (positional arguments) 48 | * :env - The environment Object that holds COMP args (default: process.env) 49 | * 50 | * Examples 51 | * 52 | * const env = tabtab.parseEnv(); 53 | * // env: 54 | * // complete A Boolean indicating whether we act in "plumbing mode" or not 55 | * // words The Number of words in the completed line 56 | * // point A Number indicating cursor position 57 | * // line The String input line 58 | * // partial The String part of line preceding cursor position 59 | * // last The last String word of the line 60 | * // lastPartial The last word String of partial 61 | * // prev The String word preceding last 62 | * 63 | * Returns the data env object. 64 | */ 65 | const parseEnv = env => { 66 | if (!env) { 67 | throw new Error('parseEnv: You must pass in an environment object.'); 68 | } 69 | 70 | debug( 71 | 'Parsing env. CWORD: %s, COMP_POINT: %s, COMP_LINE: %s', 72 | env.COMP_CWORD, 73 | env.COMP_POINT, 74 | env.COMP_LINE 75 | ); 76 | 77 | let cword = Number(env.COMP_CWORD); 78 | let point = Number(env.COMP_POINT); 79 | const line = env.COMP_LINE || ''; 80 | 81 | if (Number.isNaN(cword)) cword = 0; 82 | if (Number.isNaN(point)) point = 0; 83 | 84 | const partial = line.slice(0, point); 85 | 86 | const parts = line.split(' '); 87 | const prev = parts.slice(0, -1).slice(-1)[0]; 88 | 89 | const last = parts.slice(-1).join(''); 90 | const lastPartial = partial 91 | .split(' ') 92 | .slice(-1) 93 | .join(''); 94 | 95 | let complete = true; 96 | if (!env.COMP_CWORD || !env.COMP_POINT || !env.COMP_LINE) { 97 | complete = false; 98 | } 99 | 100 | return { 101 | complete, 102 | words: cword, 103 | point, 104 | line, 105 | partial, 106 | last, 107 | lastPartial, 108 | prev 109 | }; 110 | }; 111 | 112 | /** 113 | * Helper to normalize String and Objects with { name, description } when logging out. 114 | * 115 | * @param {String|Object} item - Item to normalize 116 | */ 117 | const completionItem = item => { 118 | debug('completion item', item); 119 | 120 | if (item.name || item.description) return item; 121 | const shell = systemShell(); 122 | 123 | let name = item; 124 | let description = ''; 125 | const matching = /^(.*?)(\\)?:(.*)$/.exec(item); 126 | if (matching) { 127 | [, name, , description] = matching; 128 | } 129 | 130 | if (shell === 'zsh' && /\\/.test(item)) { 131 | name += '\\'; 132 | } 133 | 134 | return { 135 | name, 136 | description 137 | }; 138 | }; 139 | 140 | /** 141 | * Main logging utility to pass completion items. 142 | * 143 | * This is simply an helper to log to stdout with each item separated by a new 144 | * line. 145 | * 146 | * Bash needs in addition to filter out the args for the completion to work 147 | * (zsh, fish don't need this). 148 | * 149 | * @param {Array} Arguments to log, Strings or Objects with name and 150 | * description property. 151 | */ 152 | const log = args => { 153 | const shell = systemShell(); 154 | 155 | if (!Array.isArray(args)) { 156 | throw new Error('log: Invalid arguments, must be an array'); 157 | } 158 | 159 | // Normalize arguments if there are some Objects { name, description } in them. 160 | args = args.map(completionItem).map(item => { 161 | const { name, description } = item; 162 | let str = name; 163 | if (shell === 'zsh' && description) { 164 | str = `${name.replace(/:/g, '\\:')}:${description}`; 165 | } else if (shell === 'fish' && description) { 166 | str = `${name}\t${description}`; 167 | } 168 | 169 | return str; 170 | }); 171 | 172 | if (shell === 'bash') { 173 | const env = parseEnv(process.env); 174 | args = args.filter(arg => arg.indexOf(env.last) === 0); 175 | } 176 | 177 | for (const arg of args) { 178 | console.log(`${arg}`); 179 | } 180 | }; 181 | 182 | module.exports = { 183 | shell: systemShell, 184 | install, 185 | uninstall, 186 | parseEnv, 187 | log 188 | }; 189 | -------------------------------------------------------------------------------- /api/installer.js.md: -------------------------------------------------------------------------------- 1 | ## Functions 2 | 3 |
4 |
shellExtension()
5 |

Little helper to return the correct file extension based on the SHELL value.

6 |
7 |
scriptFromShell(shell)
8 |

Helper to return the correct script template based on the SHELL provided

9 |
10 |
locationFromShell(shell)String
11 |

Helper to return the expected location for SHELL config file, based on the 12 | provided shell value.

13 |
14 |
sourceLineForShell(scriptname, shell)
15 |

Helper to return the source line to add depending on the SHELL provided or detected.

16 |

If the provided SHELL is not known, it returns the source line for a Bash shell.

17 |
18 |
isInShellConfig(filename)Boolean
19 |

Helper to check if a filename is one of the SHELL config we expect

20 |
21 |
checkFilenameForLine(filename, line)Boolean
22 |

Checks a given file for the existence of a specific line. Used to prevent 23 | adding multiple completion source to SHELL scripts.

24 |
25 |
writeLineToFilename(options)
26 |

Opens a file for modification adding a new source line for the given 27 | SHELL. Used for both SHELL script and tabtab internal one.

28 |
29 |
writeToShellConfig(options)
30 |

Writes to SHELL config file adding a new line, but only one, to the SHELL 31 | config script. This enables tabtab to work for the given SHELL.

32 |
33 |
writeToTabtabScript(options)
34 |

Writes to tabtab internal script that acts as a frontend router for the 35 | completion mechanism, in the internal ~/.config/tabtab directory. Every 36 | completion is added to this file.

37 |
38 |
writeToCompletionScript(options)
39 |

This writes a new completion script in the internal ~/.config/tabtab 40 | directory. Depending on the SHELL used, a different script is created for 41 | the given SHELL.

42 |
43 |
install(options)
44 |

Top level install method. Does three things:

45 | 50 |
51 |
removeLinesFromFilename(filename, name)
52 |

Removes the 3 relevant lines from provided filename, based on the package 53 | name passed in.

54 |
55 |
uninstall(options)
56 |

Here the idea is to uninstall a given package completion from internal 57 | tabtab scripts and / or the SHELL config.

58 |

It also removes the relevant scripts if no more completion are installed on 59 | the system.

60 |
61 |
62 | 63 | 64 | 65 | ## shellExtension() ⇒ 66 | Little helper to return the correct file extension based on the SHELL value. 67 | 68 | **Kind**: global function 69 | **Returns**: The correct file extension for the given SHELL script location 70 | 71 | 72 | ## scriptFromShell(shell) ⇒ 73 | Helper to return the correct script template based on the SHELL provided 74 | 75 | **Kind**: global function 76 | **Returns**: The template script content, defaults to Bash for shell we don't know yet 77 | 78 | | Param | Type | Description | 79 | | --- | --- | --- | 80 | | shell | String | Shell to base the check on, defaults to system shell. | 81 | 82 | 83 | 84 | ## locationFromShell(shell) ⇒ String 85 | Helper to return the expected location for SHELL config file, based on the 86 | provided shell value. 87 | 88 | **Kind**: global function 89 | **Returns**: String - Either ~/.bashrc, ~/.zshrc or ~/.config/fish/config.fish, 90 | untildified. Defaults to ~/.bashrc if provided SHELL is not valid. 91 | 92 | | Param | Type | Description | 93 | | --- | --- | --- | 94 | | shell | String | Shell value to test against | 95 | 96 | 97 | 98 | ## sourceLineForShell(scriptname, shell) 99 | Helper to return the source line to add depending on the SHELL provided or detected. 100 | 101 | If the provided SHELL is not known, it returns the source line for a Bash shell. 102 | 103 | **Kind**: global function 104 | 105 | | Param | Type | Description | 106 | | --- | --- | --- | 107 | | scriptname | String | The script to source | 108 | | shell | String | Shell to base the check on, defaults to system shell. | 109 | 110 | 111 | 112 | ## isInShellConfig(filename) ⇒ Boolean 113 | Helper to check if a filename is one of the SHELL config we expect 114 | 115 | **Kind**: global function 116 | **Returns**: Boolean - Either true or false 117 | 118 | | Param | Type | Description | 119 | | --- | --- | --- | 120 | | filename | String | Filename to check against | 121 | 122 | 123 | 124 | ## checkFilenameForLine(filename, line) ⇒ Boolean 125 | Checks a given file for the existence of a specific line. Used to prevent 126 | adding multiple completion source to SHELL scripts. 127 | 128 | **Kind**: global function 129 | **Returns**: Boolean - true or false, false if the line is not present. 130 | 131 | | Param | Type | Description | 132 | | --- | --- | --- | 133 | | filename | String | The filename to check against | 134 | | line | String | The line to look for | 135 | 136 | 137 | 138 | ## writeLineToFilename(options) 139 | Opens a file for modification adding a new `source` line for the given 140 | SHELL. Used for both SHELL script and tabtab internal one. 141 | 142 | **Kind**: global function 143 | 144 | | Param | Type | Description | 145 | | --- | --- | --- | 146 | | options | Object | Options with - filename: The file to modify - scriptname: The line to add sourcing this file - name: The package being configured | 147 | 148 | 149 | 150 | ## writeToShellConfig(options) 151 | Writes to SHELL config file adding a new line, but only one, to the SHELL 152 | config script. This enables tabtab to work for the given SHELL. 153 | 154 | **Kind**: global function 155 | 156 | | Param | Type | Description | 157 | | --- | --- | --- | 158 | | options | Object | Options object with - location: The SHELL script location (~/.bashrc, ~/.zshrc or ~/.config/fish/config.fish) - name: The package configured for completion | 159 | 160 | 161 | 162 | ## writeToTabtabScript(options) 163 | Writes to tabtab internal script that acts as a frontend router for the 164 | completion mechanism, in the internal ~/.config/tabtab directory. Every 165 | completion is added to this file. 166 | 167 | **Kind**: global function 168 | 169 | | Param | Type | Description | 170 | | --- | --- | --- | 171 | | options | Object | Options object with - name: The package configured for completion | 172 | 173 | 174 | 175 | ## writeToCompletionScript(options) 176 | This writes a new completion script in the internal `~/.config/tabtab` 177 | directory. Depending on the SHELL used, a different script is created for 178 | the given SHELL. 179 | 180 | **Kind**: global function 181 | 182 | | Param | Type | Description | 183 | | --- | --- | --- | 184 | | options | Object | Options object with - name: The package configured for completion - completer: The binary that will act as the completer for `name` program | 185 | 186 | 187 | 188 | ## install(options) 189 | Top level install method. Does three things: 190 | 191 | - Writes to SHELL config file, adding a new line to tabtab internal script. 192 | - Creates or edit tabtab internal script 193 | - Creates the actual completion script for this package. 194 | 195 | **Kind**: global function 196 | 197 | | Param | Type | Description | 198 | | --- | --- | --- | 199 | | options | Object | Options object with - name: The program name to complete - completer: The actual program or binary that will act as the completer for `name` program. Can be the same. - location: The SHELL script config location (~/.bashrc, ~/.zshrc or ~/.config/fish/config.fish) | 200 | 201 | 202 | 203 | ## removeLinesFromFilename(filename, name) 204 | Removes the 3 relevant lines from provided filename, based on the package 205 | name passed in. 206 | 207 | **Kind**: global function 208 | 209 | | Param | Type | Description | 210 | | --- | --- | --- | 211 | | filename | String | The filename to operate on | 212 | | name | String | The package name to look for | 213 | 214 | 215 | 216 | ## uninstall(options) 217 | Here the idea is to uninstall a given package completion from internal 218 | tabtab scripts and / or the SHELL config. 219 | 220 | It also removes the relevant scripts if no more completion are installed on 221 | the system. 222 | 223 | **Kind**: global function 224 | 225 | | Param | Type | Description | 226 | | --- | --- | --- | 227 | | options | Object | Options object with - name: The package name to look for | 228 | 229 | -------------------------------------------------------------------------------- /lib/installer.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const untildify = require('untildify'); 4 | const { promisify } = require('es6-promisify'); 5 | const mkdirp = promisify(require('mkdirp')); 6 | const { tabtabDebug, systemShell, exists } = require('./utils'); 7 | 8 | const debug = tabtabDebug('tabtab:installer'); 9 | 10 | const readFile = promisify(fs.readFile); 11 | const writeFile = promisify(fs.writeFile); 12 | const unlink = promisify(fs.unlink); 13 | 14 | const { 15 | BASH_LOCATION, 16 | FISH_LOCATION, 17 | ZSH_LOCATION, 18 | COMPLETION_DIR, 19 | TABTAB_SCRIPT_NAME 20 | } = require('./constants'); 21 | 22 | /** 23 | * Little helper to return the correct file extension based on the SHELL value. 24 | * 25 | * @returns The correct file extension for the given SHELL script location 26 | */ 27 | const shellExtension = () => systemShell(); 28 | 29 | /** 30 | * Helper to return the correct script template based on the SHELL provided 31 | * 32 | * @param {String} shell - Shell to base the check on, defaults to system shell. 33 | * @returns The template script content, defaults to Bash for shell we don't know yet 34 | */ 35 | const scriptFromShell = (shell = systemShell()) => { 36 | if (shell === 'fish') { 37 | return path.join(__dirname, 'scripts/fish.sh'); 38 | } 39 | 40 | if (shell === 'zsh') { 41 | return path.join(__dirname, 'scripts/zsh.sh'); 42 | } 43 | 44 | // For Bash and others 45 | return path.join(__dirname, 'scripts/bash.sh'); 46 | }; 47 | 48 | /** 49 | * Helper to return the expected location for SHELL config file, based on the 50 | * provided shell value. 51 | * 52 | * @param {String} shell - Shell value to test against 53 | * @returns {String} Either ~/.bashrc, ~/.zshrc or ~/.config/fish/config.fish, 54 | * untildified. Defaults to ~/.bashrc if provided SHELL is not valid. 55 | */ 56 | const locationFromShell = (shell = systemShell()) => { 57 | if (shell === 'bash') return untildify(BASH_LOCATION); 58 | if (shell === 'zsh') return untildify(ZSH_LOCATION); 59 | if (shell === 'fish') return untildify(FISH_LOCATION); 60 | return BASH_LOCATION; 61 | }; 62 | 63 | /** 64 | * Helper to return the source line to add depending on the SHELL provided or detected. 65 | * 66 | * If the provided SHELL is not known, it returns the source line for a Bash shell. 67 | * 68 | * @param {String} scriptname - The script to source 69 | * @param {String} shell - Shell to base the check on, defaults to system 70 | * shell. 71 | */ 72 | const sourceLineForShell = (scriptname, shell = systemShell()) => { 73 | if (shell === 'fish') { 74 | return `[ -f ${scriptname} ]; and . ${scriptname}; or true`; 75 | } 76 | 77 | if (shell === 'zsh') { 78 | return `[[ -f ${scriptname} ]] && . ${scriptname} || true`; 79 | } 80 | 81 | // For Bash and others 82 | return `[ -f ${scriptname} ] && . ${scriptname} || true`; 83 | }; 84 | 85 | /** 86 | * Helper to check if a filename is one of the SHELL config we expect 87 | * 88 | * @param {String} filename - Filename to check against 89 | * @returns {Boolean} Either true or false 90 | */ 91 | const isInShellConfig = filename => 92 | [ 93 | BASH_LOCATION, 94 | ZSH_LOCATION, 95 | FISH_LOCATION, 96 | untildify(BASH_LOCATION), 97 | untildify(ZSH_LOCATION), 98 | untildify(FISH_LOCATION) 99 | ].includes(filename); 100 | 101 | /** 102 | * Checks a given file for the existence of a specific line. Used to prevent 103 | * adding multiple completion source to SHELL scripts. 104 | * 105 | * @param {String} filename - The filename to check against 106 | * @param {String} line - The line to look for 107 | * @returns {Boolean} true or false, false if the line is not present. 108 | */ 109 | const checkFilenameForLine = async (filename, line) => { 110 | debug('Check filename (%s) for "%s"', filename, line); 111 | 112 | let filecontent = ''; 113 | try { 114 | filecontent = await readFile(untildify(filename), 'utf8'); 115 | } catch (err) { 116 | if (err.code !== 'ENOENT') { 117 | return console.error( 118 | 'Got an error while trying to read from %s file', 119 | filename, 120 | err 121 | ); 122 | } 123 | } 124 | 125 | return !!filecontent.match(`${line}`); 126 | }; 127 | 128 | /** 129 | * Opens a file for modification adding a new `source` line for the given 130 | * SHELL. Used for both SHELL script and tabtab internal one. 131 | * 132 | * @param {Object} options - Options with 133 | * - filename: The file to modify 134 | * - scriptname: The line to add sourcing this file 135 | * - name: The package being configured 136 | */ 137 | const writeLineToFilename = ({ filename, scriptname, name }) => ( 138 | resolve, 139 | reject 140 | ) => { 141 | const filepath = untildify(filename); 142 | 143 | debug('Creating directory for %s file', filepath); 144 | mkdirp(path.dirname(filepath)) 145 | .then(() => { 146 | const stream = fs.createWriteStream(filepath, { flags: 'a' }); 147 | stream.on('error', reject); 148 | stream.on('finish', () => resolve()); 149 | 150 | debug('Writing to shell configuration file (%s)', filename); 151 | debug('scriptname:', scriptname); 152 | 153 | const inShellConfig = isInShellConfig(filename); 154 | if (inShellConfig) { 155 | stream.write(`\n# tabtab source for packages`); 156 | } else { 157 | stream.write(`\n# tabtab source for ${name} package`); 158 | } 159 | 160 | stream.write('\n# uninstall by removing these lines'); 161 | stream.write(`\n${sourceLineForShell(scriptname)}`); 162 | stream.end('\n'); 163 | 164 | console.log('=> Added tabtab source line in "%s" file', filename); 165 | }) 166 | .catch(err => { 167 | console.error('mkdirp ERROR', err); 168 | reject(err); 169 | }); 170 | }; 171 | 172 | /** 173 | * Writes to SHELL config file adding a new line, but only one, to the SHELL 174 | * config script. This enables tabtab to work for the given SHELL. 175 | * 176 | * @param {Object} options - Options object with 177 | * - location: The SHELL script location (~/.bashrc, ~/.zshrc or 178 | * ~/.config/fish/config.fish) 179 | * - name: The package configured for completion 180 | */ 181 | const writeToShellConfig = async ({ location, name }) => { 182 | const scriptname = path.join( 183 | COMPLETION_DIR, 184 | `${TABTAB_SCRIPT_NAME}.${shellExtension()}` 185 | ); 186 | 187 | const filename = location; 188 | 189 | // Check if SHELL script already has a line for tabtab 190 | const existing = await checkFilenameForLine(filename, scriptname); 191 | if (existing) { 192 | return console.log('=> Tabtab line already exists in %s file', filename); 193 | } 194 | 195 | return new Promise( 196 | writeLineToFilename({ 197 | filename, 198 | scriptname, 199 | name 200 | }) 201 | ); 202 | }; 203 | 204 | /** 205 | * Writes to tabtab internal script that acts as a frontend router for the 206 | * completion mechanism, in the internal ~/.config/tabtab directory. Every 207 | * completion is added to this file. 208 | * 209 | * @param {Object} options - Options object with 210 | * - name: The package configured for completion 211 | */ 212 | const writeToTabtabScript = async ({ name }) => { 213 | const filename = path.join( 214 | COMPLETION_DIR, 215 | `${TABTAB_SCRIPT_NAME}.${shellExtension()}` 216 | ); 217 | 218 | const scriptname = path.join(COMPLETION_DIR, `${name}.${shellExtension()}`); 219 | 220 | // Check if tabtab completion file already has this line in it 221 | const existing = await checkFilenameForLine(filename, scriptname); 222 | if (existing) { 223 | return console.log('=> Tabtab line already exists in %s file', filename); 224 | } 225 | 226 | return new Promise(writeLineToFilename({ filename, scriptname, name })); 227 | }; 228 | 229 | /** 230 | * This writes a new completion script in the internal `~/.config/tabtab` 231 | * directory. Depending on the SHELL used, a different script is created for 232 | * the given SHELL. 233 | * 234 | * @param {Object} options - Options object with 235 | * - name: The package configured for completion 236 | * - completer: The binary that will act as the completer for `name` program 237 | */ 238 | const writeToCompletionScript = ({ name, completer }) => { 239 | const filename = untildify( 240 | path.join(COMPLETION_DIR, `${name}.${shellExtension()}`) 241 | ); 242 | 243 | const script = scriptFromShell(); 244 | debug('Writing completion script to', filename); 245 | debug('with', script); 246 | 247 | return readFile(script, 'utf8') 248 | .then(filecontent => 249 | filecontent 250 | .replace(/\{pkgname\}/g, name) 251 | .replace(/{completer}/g, completer) 252 | // on Bash on windows, we need to make sure to remove any \r 253 | .replace(/\r?\n/g, '\n') 254 | ) 255 | .then(filecontent => 256 | mkdirp(path.dirname(filename)).then(() => 257 | writeFile(filename, filecontent) 258 | ) 259 | ) 260 | .then(() => console.log('=> Wrote completion script to %s file', filename)) 261 | .catch(err => console.error('ERROR:', err)); 262 | }; 263 | 264 | /** 265 | * Top level install method. Does three things: 266 | * 267 | * - Writes to SHELL config file, adding a new line to tabtab internal script. 268 | * - Creates or edit tabtab internal script 269 | * - Creates the actual completion script for this package. 270 | * 271 | * @param {Object} options - Options object with 272 | * - name: The program name to complete 273 | * - completer: The actual program or binary that will act as the completer 274 | * for `name` program. Can be the same. 275 | * - location: The SHELL script config location (~/.bashrc, ~/.zshrc or 276 | * ~/.config/fish/config.fish) 277 | */ 278 | const install = async (options = { name: '', completer: '', location: '' }) => { 279 | debug('Install with options', options); 280 | if (!options.name) { 281 | throw new Error('options.name is required'); 282 | } 283 | 284 | if (!options.completer) { 285 | throw new Error('options.completer is required'); 286 | } 287 | 288 | if (!options.location) { 289 | throw new Error('options.location is required'); 290 | } 291 | 292 | await Promise.all([ 293 | writeToShellConfig(options), 294 | writeToTabtabScript(options), 295 | writeToCompletionScript(options) 296 | ]).then(() => { 297 | const { location, name } = options; 298 | console.log(` 299 | => Tabtab source line added to ${location} for ${name} package. 300 | 301 | Make sure to reload your SHELL. 302 | `); 303 | }); 304 | }; 305 | 306 | /** 307 | * Removes the 3 relevant lines from provided filename, based on the package 308 | * name passed in. 309 | * 310 | * @param {String} filename - The filename to operate on 311 | * @param {String} name - The package name to look for 312 | */ 313 | const removeLinesFromFilename = async (filename, name) => { 314 | /* eslint-disable no-unused-vars */ 315 | debug('Removing lines from %s file, looking for %s package', filename, name); 316 | if (!(await exists(filename))) { 317 | return debug('File %s does not exist', filename); 318 | } 319 | 320 | const filecontent = await readFile(filename, 'utf8'); 321 | const lines = filecontent.split(/\r?\n/); 322 | 323 | const sourceLine = isInShellConfig(filename) 324 | ? `# tabtab source for packages` 325 | : `# tabtab source for ${name} package`; 326 | 327 | const hasLine = !!filecontent.match(`${sourceLine}`); 328 | if (!hasLine) { 329 | return debug('File %s does not include the line: %s', filename, sourceLine); 330 | } 331 | 332 | let lineIndex = -1; 333 | const buffer = lines 334 | // Build up the new buffer, removing the 3 lines following the sourceline 335 | .map((line, index) => { 336 | const match = line.match(sourceLine); 337 | if (match) { 338 | lineIndex = index; 339 | } else if (lineIndex + 3 <= index) { 340 | lineIndex = -1; 341 | } 342 | 343 | return lineIndex === -1 ? line : ''; 344 | }) 345 | // Remove any double empty lines from this file 346 | .map((line, index, array) => { 347 | const next = array[index + 1]; 348 | if (line === '' && next === '') { 349 | return; 350 | } 351 | 352 | return line; 353 | }) 354 | // Remove any undefined value from there 355 | .filter(line => line !== undefined) 356 | .join('\n') 357 | .trim(); 358 | 359 | await writeFile(filename, buffer); 360 | console.log('=> Removed tabtab source lines from %s file', filename); 361 | }; 362 | 363 | /** 364 | * Here the idea is to uninstall a given package completion from internal 365 | * tabtab scripts and / or the SHELL config. 366 | * 367 | * It also removes the relevant scripts if no more completion are installed on 368 | * the system. 369 | * 370 | * @param {Object} options - Options object with 371 | * - name: The package name to look for 372 | */ 373 | const uninstall = async (options = { name: '' }) => { 374 | debug('Uninstall with options', options); 375 | const { name } = options; 376 | 377 | if (!name) { 378 | throw new Error('Unable to uninstall if options.name is missing'); 379 | } 380 | 381 | const completionScript = untildify( 382 | path.join(COMPLETION_DIR, `${name}.${shellExtension()}`) 383 | ); 384 | 385 | // First, lets remove the completion script itself 386 | if (await exists(completionScript)) { 387 | await unlink(completionScript); 388 | console.log('=> Removed completion script (%s)', completionScript); 389 | } 390 | 391 | // Then the lines in ~/.config/tabtab/__tabtab.shell 392 | const tabtabScript = untildify( 393 | path.join(COMPLETION_DIR, `${TABTAB_SCRIPT_NAME}.${shellExtension()}`) 394 | ); 395 | await removeLinesFromFilename(tabtabScript, name); 396 | 397 | // Then, check if __tabtab.shell is empty, if so remove the last source line in SHELL config 398 | const isEmpty = (await readFile(tabtabScript, 'utf8')).trim() === ''; 399 | if (isEmpty) { 400 | const shellScript = locationFromShell(); 401 | debug( 402 | 'File %s is empty. Removing source line from %s file', 403 | tabtabScript, 404 | shellScript 405 | ); 406 | await removeLinesFromFilename(shellScript, name); 407 | } 408 | 409 | console.log('=> Uninstalled completion for %s package', name); 410 | }; 411 | 412 | module.exports = { 413 | install, 414 | uninstall, 415 | checkFilenameForLine, 416 | writeToShellConfig, 417 | writeToTabtabScript, 418 | writeToCompletionScript, 419 | writeLineToFilename 420 | }; 421 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # tabtab 2 | 3 | [![Build Status](https://travis-ci.org/mklabs/tabtab.svg?branch=3.0.0)](https://travis-ci.org/mklabs/tabtab) 4 | [![Coverage Status](https://coveralls.io/repos/github/mklabs/tabtab/badge.svg?branch=3.0.0)](https://coveralls.io/github/mklabs/tabtab?branch=3.0.0) 5 | 6 | A node package to do some custom command line `` completion for any 7 | system command, for Bash, Zsh, and Fish shells. 8 | 9 | Made possible using the same technique as npm (whose completion is quite 10 | awesome) relying on a shell script bridge to do the actual completion from 11 | node's land. 12 | 13 | ![tabtab](https://user-images.githubusercontent.com/113832/46506243-deb39b00-c833-11e8-8f5f-7136987e7341.gif) 14 | 15 | **Warning / Breaking changes** 16 | 17 | - Windows is not supported 18 | - Cache has been removed 19 | - Now only support node `> 7.10.1`, for previous version with support for node 20 | 6 be sure to use tabtab `2.2.x` 21 | 22 | ## Table of Contents 23 | 24 | - [Goal of this 3.0.0 version](#goal-of-this-300-version) 25 | 26 | - [Installation](#installation) 27 | 28 | - [Usage](#usage) 29 | 30 | - [1. Install completion](#1-install-completion) 31 | - [2. Log completion](#2-log-completion) 32 | - [3. Parsing env](#3-parsing-env) 33 | 34 | - [Completion mechanism](#completion-mechanism) 35 | 36 | - [Completion install](#completion-install) 37 | - [Completion uninstall](#completion-uninstall) 38 | 39 | - [Debugging](#debugging) 40 | 41 | - [API Documentation](#api-documentation) 42 | 43 | - [Changelog](#changelog) 44 | 45 | - [Credits](#credits) 46 | 47 | ## Goal of this 3.0.0 version 48 | 49 | Simplify everything, major overhaul, rewrite from scratch. 50 | 51 | Functional, less abstraction, clearer documentation, good test coverage, 52 | support for node 10 without babel. 53 | 54 | Up to date dependencies, easier to debug, easier to test. 55 | 56 | Should still support bash, zsh and fish but bash is the primary focus of this 57 | alpha version. 58 | 59 | No binary file anymore, just a library (still debating with myself) 60 | 61 | The goal of this rewrite is two-folded: 62 | 63 | - Integrate nicely with [yo](https://github.com/yeoman/yo) (Yeoman) 64 | - Have a robust and fast enough library for [yarn-completions](https://github.com/mklabs/yarn-completions) 65 | 66 | ## Installation 67 | 68 | npm install tabtab 69 | 70 | ## Usage 71 | 72 | Writing completion is a two-step process: Installation and Logging. Tabtab 73 | provides just that. 74 | 75 | Here is a basic example using 76 | [minimist](https://www.npmjs.com/package/minimist) to parse arguments. 77 | 78 | ```js 79 | #! /usr/bin/env node 80 | 81 | const tabtab = require('tabtab'); 82 | const opts = require('minimist')(process.argv.slice(2), { 83 | string: ['foo', 'bar'], 84 | boolean: ['help', 'version', 'loglevel'] 85 | }); 86 | 87 | const args = opts._; 88 | const completion = env => { 89 | if (!env.complete) return; 90 | 91 | // Write your completions there 92 | 93 | if (env.prev === 'foo') { 94 | return tabtab.log(['is', 'this', 'the', 'real', 'life']); 95 | } 96 | 97 | if (env.prev === 'bar') { 98 | return tabtab.log(['is', 'this', 'just', 'fantasy']); 99 | } 100 | 101 | if (env.prev === '--loglevel') { 102 | return tabtab.log(['error', 'warn', 'info', 'notice', 'verbose']); 103 | } 104 | 105 | return tabtab.log([ 106 | '--help', 107 | '--version', 108 | '--loglevel', 109 | 'foo', 110 | 'bar', 111 | 'install-completion', 112 | 'completion', 113 | 'someCommand:someCommand is some kind of command with a description', 114 | { 115 | name: 'someOtherCommand:hey', 116 | description: 'You must add a description for items with ":" in them' 117 | }, 118 | 'anotherOne' 119 | ]); 120 | }; 121 | 122 | const run = async () => { 123 | const cmd = args[0]; 124 | 125 | // Write your CLI there 126 | 127 | // Here we install for the program `tabtab-test` (this file), with 128 | // completer being the same program. Sometimes, you want to complete 129 | // another program that's where the `completer` option might come handy. 130 | if (cmd === 'install-completion') { 131 | await tabtab 132 | .install({ 133 | name: 'tabtab-test', 134 | completer: 'tabtab-test' 135 | }) 136 | .catch(err => console.error('INSTALL ERROR', err)); 137 | 138 | return; 139 | } 140 | 141 | if (cmd === 'uninstall-completion') { 142 | // Here we uninstall for the program `tabtab-test` (this file). 143 | await tabtab 144 | .uninstall({ 145 | name: 'tabtab-test' 146 | }) 147 | .catch(err => console.error('UNINSTALL ERROR', err)); 148 | 149 | return; 150 | } 151 | 152 | // The completion command is added automatically by tabtab when the program 153 | // is completed. 154 | if (cmd === 'completion') { 155 | const env = tabtab.parseEnv(process.env); 156 | return completion(env); 157 | } 158 | }; 159 | 160 | run(); 161 | ``` 162 | 163 | Please refer to the 164 | [examples/tabtab-test-complete](./examples/tabtab-test-complete) package for a 165 | working example. The following usage documentation is based on it. 166 | 167 | ### 1. Install completion 168 | 169 | To enable completion for a given program or package, you must enable the 170 | completion on your or user's system. This is done by calling `tabtab.install()` 171 | usually behind a `program install-completion` command or something similar. 172 | 173 | ```js 174 | // Here we install for the program `tabtab-test`, with completer being the same 175 | // program. Sometimes, you want to complete another program that's where the 176 | // `completer` option might come handy. 177 | tabtab.install({ 178 | name: 'tabtab-test', 179 | completer: 'tabtab-test' 180 | }) 181 | .then(() => console.log('Completion installed')) 182 | .catch(err => console.error(err)) 183 | ``` 184 | 185 | The method returns a promise, so `await / async` usage is possible. It takes an 186 | `options` parameter, with: 187 | 188 | - `name`: The program to complete 189 | - `completer`: The program that does the completion (can be the same program). 190 | 191 | `tabtab.install()` will ask the user which SHELL to use, and optionally a path 192 | to write to. This will add a new line to either `~/.bashrc`, `~/.zshrc` or 193 | `~/.config/fish/config.fish` file to source tabtab completion script. 194 | 195 | Only one line will be added, even if it is called multiple times. 196 | 197 | ### 2. Log completion 198 | 199 | Once the completion is enabled and active, you can write completions for the 200 | program (here, in this example `tabtab-test`). Briefly, adding completions is 201 | as simple as logging output to `stdout`, with a few particularities (namely on 202 | Bash, and for descriptions), but this is taken care of by `tabtab.log()`. 203 | 204 | ```js 205 | tabtab.log([ 206 | '--help', 207 | '--version', 208 | 'command' 209 | 'command-two' 210 | ]); 211 | ``` 212 | 213 | This is the simplest way of adding completions. You can also use an object, 214 | instead of a simple string, with `{ name, description }` property if you want 215 | to add descriptions for each completion item, for the shells that support them 216 | (like Zsh or Fish). Or use the simpler `name:description` form. 217 | 218 | ```js 219 | tabtab.log([ 220 | { name: 'command', description: 'Description for command' }, 221 | 'command-two:Description for command-two' 222 | ]); 223 | ``` 224 | 225 | The `{ name, description }` approach is preferable in case you have completion 226 | items with `:` in them. 227 | 228 | Note that you can call `tabtab.log()` multiple times if you prefer to do so, it 229 | simply logs to the console in sequence. 230 | 231 | ### 3. Parsing env 232 | 233 | If you ever want to add more intelligent completion, you'll need to check and 234 | see what is the last or previous word in the completed line, so that you can 235 | add options for a specific command or flag (such as loglevels for `--loglevel` 236 | for instance). 237 | 238 | Tabtab adds a few environment variables for you to inspect and use, this is 239 | done by calling `tabtab.parseEnv()` method. 240 | 241 | ```js 242 | const env = tabtab.parseEnv(process.env); 243 | // env: 244 | // 245 | // - complete A Boolean indicating whether we act in "plumbing mode" or not 246 | // - words The Number of words in the completed line 247 | // - point A Number indicating cursor position 248 | // - line The String input line 249 | // - partial The String part of line preceding cursor position 250 | // - last The last String word of the line 251 | // - lastPartial The last word String of partial 252 | // - prev The String word preceding last 253 | ``` 254 | 255 | Usually, you'll want to check against `env.last` or `env.prev`. 256 | 257 | ```js 258 | if (env.prev === '--loglevel') { 259 | tabtab.log(['error', 'warn', 'info', 'notice', 'verbose']); 260 | } 261 | ``` 262 | 263 | ## Completion mechanism 264 | 265 | Feel free to browse the [scripts](./scripts) directory to inspect the various 266 | template files used when creating a completion with `tabtab.install()`. 267 | 268 | Here is a Bash completion snippet created by tabtab. 269 | 270 | ```bash 271 | ###-begin-tabtab-test-completion-### 272 | if type complete &>/dev/null; then 273 | _tabtab-test_completion () { 274 | local words cword 275 | if type _get_comp_words_by_ref &>/dev/null; then 276 | _get_comp_words_by_ref -n = -n @ -n : -w words -i cword 277 | else 278 | cword="$COMP_CWORD" 279 | words=("${COMP_WORDS[@]}") 280 | fi 281 | 282 | local si="$IFS" 283 | IFS=$'\n' COMPREPLY=($(COMP_CWORD="$cword" \ 284 | COMP_LINE="$COMP_LINE" \ 285 | COMP_POINT="$COMP_POINT" \ 286 | tabtab-test completion -- "${words[@]}" \ 287 | 2>/dev/null)) || return $? 288 | IFS="$si" 289 | if type __ltrim_colon_completions &>/dev/null; then 290 | __ltrim_colon_completions "${words[cword]}" 291 | fi 292 | } 293 | complete -o default -F _tabtab-test_completion tabtab-test 294 | fi 295 | ###-end-tabtab-test-completion-### 296 | ``` 297 | 298 | The system is quite simple (though hard to nail it down, thank you npm). A new 299 | Bash function is created, which is invoked whenever `tabtab-test` is tab 300 | completed. This function then invokes the completer `tabtab-test completion` 301 | with `COMP_CWORD`, `COMP_LINE` and `COMP_POINT` environment variables (which is 302 | parsed by `tabtab.parseEnv()`). 303 | 304 | The same mechanism can be applied to Zsh and Fish. 305 | 306 | ### Completion install 307 | 308 | As described in the [`Usage > Install Completion`](#1-install-completion) 309 | section, installing a completion involves adding a new line to source in either 310 | `~/.bashrc`, `~/.zshrc` or `~/.config/fish/config.fish` file. 311 | 312 | In the `3.0.0` version, it has been improved to only add a single line instead 313 | of multiple ones, one for each completion package installed on the system. 314 | 315 | This way, a single line is added to enable the completion of for various 316 | programs without cluttering the Shell configuration file. 317 | 318 | Example for `~/.bashrc` 319 | 320 | ```bash 321 | # tabtab source for packages 322 | # uninstall by removing these lines 323 | [ -f ~/.config/tabtab/__tabtab.bash ] && . ~/.config/tabtab/__tabtab.bash || true 324 | ``` 325 | 326 | It'll load a file `__tabtab.bash`, created in the `~/.config/tabtab` directory, 327 | which will hold all the source lines for each tabtab packages defining a 328 | completion. 329 | 330 | ```bash 331 | # tabtab source for foo package 332 | # uninstall by removing these lines 333 | [ -f ~/.config/tabtab/foo.bash ] && . ~/.config/tabtab/foo.bash || true 334 | 335 | # tabtab source for tabtab-test package 336 | # uninstall by removing these lines 337 | [ -f ~/.config/tabtab/tabtab-test.bash ] && . ~/.config/tabtab/tabtab-test.bash || true 338 | ``` 339 | 340 | ### Completion uninstall 341 | 342 | You can follow the file added in your SHELL configuration file and disable a 343 | completion by removing the above lines. 344 | 345 | Or simply disable tabtab by removing the line in your SHELL configuration file. 346 | 347 | Or, you can use `tabtab.uninstall()` to do this for you. 348 | 349 | ```js 350 | if (cmd === 'uninstall-completion') { 351 | // Here we uninstall for the program `tabtab-test` 352 | await tabtab 353 | .uninstall({ 354 | name: 'tabtab-test' 355 | }) 356 | .catch(err => console.error('UNINSTALL ERROR', err)); 357 | 358 | return; 359 | } 360 | ``` 361 | 362 | ## Debugging 363 | 364 | tabtab internally logs a lot of things, using the 365 | [debug](https://www.npmjs.com/package/debug) package. 366 | 367 | When testing a completion, it can be useful to see those logs, but writing to 368 | `stdout` or `stderr` while completing something can be troublesome. 369 | 370 | You can use the `TABTAB_DEBUG` environment variable to specify a file to log to 371 | instead. 372 | 373 | export TABTAB_DEBUG="/tmp/tabtab.log" 374 | tail -f /tmp/tabtab.log 375 | 376 | # in another shell 377 | tabtab-test 378 | 379 | See [tabtabDebug.js](./lib/utils/tabtabDebug.js) file for details. 380 | 381 | ## API Documentation 382 | 383 | Please refer to [api](./api) directory to see generated documentation (using 384 | [jsdoc2md](https://github.com/jsdoc2md/jsdoc-to-markdown)) 385 | 386 | ## Changelog 387 | 388 | Please refer to [CHANGELOG](./CHANGELOG.md) file to see all possible changes to this project. 389 | 390 | ## Credits 391 | 392 | npm does pretty amazing stuff with its completion feature. bash and zsh 393 | provides command tab-completion, which allow you to complete the names 394 | of commands in your $path. usually these functions means bash 395 | scripting, and in the case of npm, it is partially true. 396 | 397 | there is a special `npm completion` command you may want to look around, 398 | if not already. 399 | 400 | npm completion 401 | 402 | running this should dump [this 403 | script](https://raw.github.com/isaacs/npm/caafb7323708e113d100e3e8145b949ed7a16c22/lib/utils/completion.sh) 404 | to the console. this script works with both bash/zsh and map the correct 405 | completion functions to the npm executable. these functions takes care 406 | of parsing the `comp_*` variables available when hitting tab to complete 407 | a command, set them up as environment variables and run the `npm 408 | completion` command followed by `-- words` where words match value of 409 | the command being completed. 410 | 411 | this means that using this technique npm manage to perform bash/zsh 412 | completion using node and javascript. actually, the comprehensiveness of npm 413 | completion is quite amazing. 414 | 415 | this whole package/module is based entirely on npm's code and @isaacs 416 | work. 417 | 418 | * * * 419 | 420 | > [mit](./LICENSE)  ·  > [mklabs.github.io](https://mklabs.github.io)  ·  > [@mklabs](https://github.com/mklabs) 421 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) 6 | and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). 7 | 8 | Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). 9 | 10 | ## [v3.0.3](https://github.com/mklabs/tabtab/compare/v3.0.2...v3.0.3) - 2022-01-26 11 | 12 | ### Merged 13 | 14 | - package.json: specify the files to include [`#75`](https://github.com/mklabs/tabtab/pull/75) 15 | 16 | ### Commits 17 | 18 | - Update package-lock.json to fix security issue [`8e16e47`](https://github.com/mklabs/tabtab/commit/8e16e476331777a33b2b04fa913644c52f63b328) 19 | 20 | ## [v3.0.2](https://github.com/mklabs/tabtab/compare/v3.0.1-beta...v3.0.2) - 2018-10-06 21 | 22 | ### Commits 23 | 24 | - feat: add tabtab.uninstall() [`23907cd`](https://github.com/mklabs/tabtab/commit/23907cdfbe16e17aefa171a750550c6fc7af42db) 25 | - docs: update doc files [`c18b614`](https://github.com/mklabs/tabtab/commit/c18b61417bb782de9469547424b502796788a593) 26 | 27 | ## [v3.0.1-beta](https://github.com/mklabs/tabtab/compare/v3.0.0-beta...v3.0.1-beta) - 2018-10-05 28 | 29 | ### Fixed 30 | 31 | - Fixing check of filename when installing [`#47`](https://github.com/mklabs/tabtab/issues/47) 32 | 33 | ### Commits 34 | 35 | - Begin to write uninstall method [`e143f80`](https://github.com/mklabs/tabtab/commit/e143f80909cfc06ab223c41a41ae4bc7ec1f9e1a) 36 | - npm: remove assert-rejects [`399a3da`](https://github.com/mklabs/tabtab/commit/399a3da9216d565d61c101f3d97f082d83809228) 37 | - example: adding uninstall-completion to example [`c48223a`](https://github.com/mklabs/tabtab/commit/c48223ae3ca204f322b523c14b887689e6ea23ee) 38 | 39 | ## [v3.0.0-beta](https://github.com/mklabs/tabtab/compare/v2.2.2...v3.0.0-beta) - 2018-10-04 40 | 41 | ### Merged 42 | 43 | - installer: ensure successful status/$? [`#38`](https://github.com/mklabs/tabtab/pull/38) 44 | - Remove `npmlog`. [`#31`](https://github.com/mklabs/tabtab/pull/31) 45 | - Fix wrong shell reference [`#35`](https://github.com/mklabs/tabtab/pull/35) 46 | 47 | ### Commits 48 | 49 | - npm: setup nyc [`1f86b2a`](https://github.com/mklabs/tabtab/commit/1f86b2a0a57a136de6299a472336e7d05ecf8004) 50 | - first cleanup [`c89f237`](https://github.com/mklabs/tabtab/commit/c89f237f2a7539a9a205bf0a23cc817c2ea8e4d8) 51 | - setup npm-watch [`a47d73a`](https://github.com/mklabs/tabtab/commit/a47d73ad4be0c215be20389a7c42a975702ccd2c) 52 | - npm: remove reactify, reset babel [`da65059`](https://github.com/mklabs/tabtab/commit/da65059e87bbda4b1feefbb94820e71d373a6d73) 53 | - npm: update dependencies [`dd19437`](https://github.com/mklabs/tabtab/commit/dd1943774f36b8d9910f9bef4b161beff6a44f43) 54 | - eslint: setup eslint and config [`64f3364`](https://github.com/mklabs/tabtab/commit/64f33648ac9131b26ff3d404501ff0b1807984b9) 55 | - npm: update dependencies [`63cd043`](https://github.com/mklabs/tabtab/commit/63cd043b02797ace9022276917f9dc63587c3831) 56 | - ci: setup nyc and coverage scripts [`e6fe55a`](https://github.com/mklabs/tabtab/commit/e6fe55ab9a7438341cd074262c1732bce0bcc9d7) 57 | - feat: tabtab.install() with prompt (wip) [`f599724`](https://github.com/mklabs/tabtab/commit/f5997246c19e042e9d30654662df53a73882df0d) 58 | - ci: disable nyc for now [`c1e1806`](https://github.com/mklabs/tabtab/commit/c1e180654579890d09e5352522526af4b0992492) 59 | - log: better handling of descriptions [`12d5897`](https://github.com/mklabs/tabtab/commit/12d5897e38958a64f4c05b0ffa4a36210b4efdc8) 60 | - es6: replace vars by const/let [`71ad75a`](https://github.com/mklabs/tabtab/commit/71ad75a882e7fd4ee5bc834fe809192ce0ecfa22) 61 | - feat: avoid adding multiple lines to SHELL scripts [`036d9c0`](https://github.com/mklabs/tabtab/commit/036d9c036b77c0b47469ae38960bfe77401c04de) 62 | - feat: add necessary completion lines in shell config [`2030676`](https://github.com/mklabs/tabtab/commit/20306764122aa7534e26094adaad8049b38d22f7) 63 | - log: fix TABTAB_DEBUG and use it in tests [`dbd6ffb`](https://github.com/mklabs/tabtab/commit/dbd6ffb4e97a0db6eac40b646b93e567996d0e5d) 64 | - examples: add tabtab-test-complete to serve as completion tests [`c0423dc`](https://github.com/mklabs/tabtab/commit/c0423dca0556e1fe6b724ba9806ff9185c650acc) 65 | - cli: test out completions [`c861535`](https://github.com/mklabs/tabtab/commit/c8615352664e19b0f8966cfcd38ee844927f1d51) 66 | - eslint: fix all eslint errros and update config [`bbf2a5b`](https://github.com/mklabs/tabtab/commit/bbf2a5bbdfc2ff317debe93485478f86867098a1) 67 | - fix: ensure fileformat is unix and remove code related to bash-completion [`9b83d76`](https://github.com/mklabs/tabtab/commit/9b83d765c8cb10102bac19113786c0cb9fca2751) 68 | - npm: setup prettier [`a2cd163`](https://github.com/mklabs/tabtab/commit/a2cd163ae19fd465816654ac61f071ddf8744e66) 69 | - node: support for version 10, 8 and 7 [`c01b443`](https://github.com/mklabs/tabtab/commit/c01b44301de30f1be0c0ff7322c5b3357dadd8f2) 70 | - example: remove completion script, all in one [`fae0553`](https://github.com/mklabs/tabtab/commit/fae05531d957c454de732f2de1567731e93783bb) 71 | - completion: now emits the whole line (breaking change) [`75c7b6a`](https://github.com/mklabs/tabtab/commit/75c7b6ac5b96bb85c0ac7047ba6d0a3f82e3e0dd) 72 | - log: slight change to tabtab.log [`1aa7d1a`](https://github.com/mklabs/tabtab/commit/1aa7d1a8146a29f4a0fd454ac6df12ac8f7c8e63) 73 | - eslint: include test dir as well [`345d191`](https://github.com/mklabs/tabtab/commit/345d191a9046a514f52b4af7652abc60dd1477ae) 74 | - babel: remove babel continuation [`6f279be`](https://github.com/mklabs/tabtab/commit/6f279be38b197f3f9e9fcf13feceefb48f9638a9) 75 | - npm: remove prettier / es6-promisify [`b5ab126`](https://github.com/mklabs/tabtab/commit/b5ab126d95d4706a7c4a4da63f031e17b40cfdc8) 76 | - babel: start to remove babel [`7f19043`](https://github.com/mklabs/tabtab/commit/7f19043c2be3461f0d98434a428c46a74a0da186) 77 | - bash: handle semicolon [`51f1de7`](https://github.com/mklabs/tabtab/commit/51f1de7f215dacba5b321daf615ad31d1579eadb) 78 | - fix: a bit more debugging and fix entry point [`4875c90`](https://github.com/mklabs/tabtab/commit/4875c906ea3efa8eb7ccdffcb6a807e0a96f28a7) 79 | - debug: use tabtab debug in all files [`e81aa93`](https://github.com/mklabs/tabtab/commit/e81aa931c8a4996d015de54e287dd3117e2e676b) 80 | - complete: always trigger original event based on options.name [`ca0ab39`](https://github.com/mklabs/tabtab/commit/ca0ab3953733e560f4993f26ba2bece54b3516ee) 81 | - Add .babelrc with preset env [`5f0eaf1`](https://github.com/mklabs/tabtab/commit/5f0eaf1fab2515f26cd9002d8d9552ddbc3843b9) 82 | - debug: dont log into console [`db192d7`](https://github.com/mklabs/tabtab/commit/db192d76419c5773ba899970d0b3c63a1bf413f8) 83 | - parseEnv: change signature to only take environment [`3e60094`](https://github.com/mklabs/tabtab/commit/3e60094f00e6f357da627b7aa13f8dfd055a936d) 84 | - debug: change debug for parseEnv, too verbose [`627ef79`](https://github.com/mklabs/tabtab/commit/627ef79636acc116c1da065720b907e5644a3cbc) 85 | - ci: remove npm prune [`7a319e2`](https://github.com/mklabs/tabtab/commit/7a319e22c1f9b83c8e876b21095980b7513ab560) 86 | - ci: forget about node 6 for the moment [`aa7ade3`](https://github.com/mklabs/tabtab/commit/aa7ade38aefd2cd1cf2a06a0544e37d8820d7833) 87 | - commands: remove options.auto mandatory in uninstall [`36dce25`](https://github.com/mklabs/tabtab/commit/36dce25198e582b629a779d8e4e8a0cd37ac7df9) 88 | - uninstall: use default options on uninstall [`7e179b6`](https://github.com/mklabs/tabtab/commit/7e179b6fa3e743e514fcbda9171c059530de9aa6) 89 | - mocha: increase timeout to avoid failure on node 6 [`fc97626`](https://github.com/mklabs/tabtab/commit/fc976262a37d6da3ef86eea7ff39883c6c8872fe) 90 | - ci(package.json): run nyc on tests [`eff6e95`](https://github.com/mklabs/tabtab/commit/eff6e9552cf75232d04445474f975f5b2a1b0b10) 91 | - fix: package.json syntax [`0a0238b`](https://github.com/mklabs/tabtab/commit/0a0238bac7d56d0bfbe229a0adcce862c4bb52cf) 92 | - Do NOT test with cache for the moment [`3cf9115`](https://github.com/mklabs/tabtab/commit/3cf911529cbe32dd32300a008f605b723869fb03) 93 | - Update package.json [`f3a9580`](https://github.com/mklabs/tabtab/commit/f3a95802b777a5cbc94ea144a5cdee9f168f265c) 94 | - add .gitattributes to force unix line endings [`9a7440e`](https://github.com/mklabs/tabtab/commit/9a7440edeaaef13b04cfba2af67365cf2a90c6bb) 95 | 96 | ## [v2.2.2](https://github.com/mklabs/tabtab/compare/v2.2.1...v2.2.2) - 2017-01-06 97 | 98 | ### Merged 99 | 100 | - fix(win32): fix usage of SHELL environment variable when it is not set [`#30`](https://github.com/mklabs/tabtab/pull/30) 101 | 102 | ## [v2.2.1](https://github.com/mklabs/tabtab/compare/v2.2.0...v2.2.1) - 2016-10-13 103 | 104 | ### Commits 105 | 106 | - fix: create duplicate-free version of completion items accross evt listeners [`dc8b587`](https://github.com/mklabs/tabtab/commit/dc8b58795d57a010800e1f858580217d141aef8e) 107 | 108 | ## [v2.2.0](https://github.com/mklabs/tabtab/compare/v2.1.1...v2.2.0) - 2016-10-11 109 | 110 | ### Commits 111 | 112 | - feat(fish): handle description by adding a tab character between name and description [`9290dcc`](https://github.com/mklabs/tabtab/commit/9290dcc042db1bf960ba0b91dd55696660cb9970) 113 | 114 | ## [v2.1.1](https://github.com/mklabs/tabtab/compare/v2.1.0...v2.1.1) - 2016-10-09 115 | 116 | ### Commits 117 | 118 | - fix(zsh): fix uninstall typo in zshrc (instead of zshhrc) [`3d29317`](https://github.com/mklabs/tabtab/commit/3d293170db57a4c74d8ced7a919e527178bfb2fc) 119 | 120 | ## [v2.1.0](https://github.com/mklabs/tabtab/compare/v2.0.2...v2.1.0) - 2016-10-09 121 | 122 | ### Commits 123 | 124 | - fix(fish): Disable description in fish completion per command / options [`1f04613`](https://github.com/mklabs/tabtab/commit/1f04613f5db70e0aaa265df7f8397fcd7f962a76) 125 | - fix(fish): fix COMP_LINE by appending a space so that prev is correctly positioned [`861f8ef`](https://github.com/mklabs/tabtab/commit/861f8ef2290b2cf61d238a8ff1b648d2712fb359) 126 | - feat(fish): prevent filenames from being completed [`282b941`](https://github.com/mklabs/tabtab/commit/282b94122938c0e26db8a54f30c0e94a1fb2e694) 127 | 128 | ## [v2.0.2](https://github.com/mklabs/tabtab/compare/v2.0.1...v2.0.2) - 2016-10-06 129 | 130 | ### Commits 131 | 132 | - fix: have output done after recv to handle async completion handler [`d8596ed`](https://github.com/mklabs/tabtab/commit/d8596edb4e1c8be8cd66c22c350e87b49f00aa95) 133 | - Remove bake from package.json [`c306bce`](https://github.com/mklabs/tabtab/commit/c306bcefc044e488ba3b99df968b5f291001fb2f) 134 | 135 | ## [v2.0.1](https://github.com/mklabs/tabtab/compare/v2.0.0...v2.0.1) - 2016-10-06 136 | 137 | ### Commits 138 | 139 | - Remove src/ folder and babel compiled files [`8531a62`](https://github.com/mklabs/tabtab/commit/8531a62fa8b35a3ffedcde58f1838420fdbd238a) 140 | - rm Makefile [`d717594`](https://github.com/mklabs/tabtab/commit/d717594ef80d3e74e4b6968ff65b8ffdfbd37ebd) 141 | - fix: have uninstall command working as expected by fixing regexp [`21e2de6`](https://github.com/mklabs/tabtab/commit/21e2de6b95688b72b5cddbf549c119b62770c96a) 142 | 143 | ## [v2.0.0](https://github.com/mklabs/tabtab/compare/v1.4.3...v2.0.0) - 2016-09-30 144 | 145 | ## [v1.4.3](https://github.com/mklabs/tabtab/compare/v1.4.2...v1.4.3) - 2016-09-30 146 | 147 | ### Merged 148 | 149 | - allow installing on a `windows` system when running in a `git bash` [`#27`](https://github.com/mklabs/tabtab/pull/27) 150 | - add $CURSOR for position in zsh.sh script [`#24`](https://github.com/mklabs/tabtab/pull/24) 151 | 152 | ### Fixed 153 | 154 | - add $cursor for position in zsh.sh script [`#23`](https://github.com/mklabs/tabtab/issues/23) 155 | 156 | ### Commits 157 | 158 | - add babel'ed files to `src` folder so that you can directly install it from github [`4cd37ec`](https://github.com/mklabs/tabtab/commit/4cd37ecc2772964a79209500a2a60de03a14b2ec) 159 | - src: update build [`1c5619d`](https://github.com/mklabs/tabtab/commit/1c5619db8348dca61833620ee46c142a880bff36) 160 | 161 | ## [v1.4.2](https://github.com/mklabs/tabtab/compare/v1.4.1...v1.4.2) - 2016-05-21 162 | 163 | ### Commits 164 | 165 | - fix(babel): remove transform-runtime plugin [`845eb54`](https://github.com/mklabs/tabtab/commit/845eb54c2c31ed28a3e0dfd668341831fbb86d5a) 166 | 167 | ## [v1.4.1](https://github.com/mklabs/tabtab/compare/v1.4.0...v1.4.1) - 2016-05-21 168 | 169 | ## [v1.4.0](https://github.com/mklabs/tabtab/compare/v1.3.0...v1.4.0) - 2016-05-21 170 | 171 | ### Fixed 172 | 173 | - feat(description): Handle zsh description using _describe fn [`#19`](https://github.com/mklabs/tabtab/issues/19) 174 | 175 | ### Commits 176 | 177 | - rework cache, fix bash completion handling [`b7cecf7`](https://github.com/mklabs/tabtab/commit/b7cecf7bd7e7ff8325a3f7643d4faf9fce2d4e77) 178 | - feat(uninstall): Implement uninstall command and --auto flag [`de37993`](https://github.com/mklabs/tabtab/commit/de3799343d5d30835383250887531c29c11f91ae) 179 | - fix(completion): gather results and write only once to STDOUT [`b928bc9`](https://github.com/mklabs/tabtab/commit/b928bc987e2f9116325040deb4dbe65e203868e8) 180 | - Fix zsh template script [`a22e6b0`](https://github.com/mklabs/tabtab/commit/a22e6b049b0ad67874a7b79010c111f0e96a0bec) 181 | - zsh: check for compdef [`f216888`](https://github.com/mklabs/tabtab/commit/f216888de7200ca7155c0de0ae0f238c993a81ed) 182 | - fix: Skip completion install for win32 platform or unknown shell [`c4f6073`](https://github.com/mklabs/tabtab/commit/c4f6073c1a25413eecbc2519712ab20ca05d6fdd) 183 | - babel: add plugin default transform [`1dcc302`](https://github.com/mklabs/tabtab/commit/1dcc3024edc44eb8f5edaa189a24f6c4faec3466) 184 | - fix(bash): Silently fail if pkg-config bash-completion exists with non 0 [`0765749`](https://github.com/mklabs/tabtab/commit/07657490f381d147072064adb64edc30c5541acf) 185 | - feat(debug): automatically JSON.stringify non string objects [`e4423f8`](https://github.com/mklabs/tabtab/commit/e4423f81f85ae74ea348c5f325e4fe7eff3b6cdf) 186 | 187 | ## [v1.3.0](https://github.com/mklabs/tabtab/compare/v1.2.1...v1.3.0) - 2016-05-08 188 | 189 | ### Fixed 190 | 191 | - feat(cache): Implement cache TTL (default: 5 min) [`#20`](https://github.com/mklabs/tabtab/issues/20) 192 | - feat(cache): Add option to enable / disable cache [`#20`](https://github.com/mklabs/tabtab/issues/20) 193 | 194 | ## [v1.2.1](https://github.com/mklabs/tabtab/compare/v1.2.0...v1.2.1) - 2016-05-08 195 | 196 | ## [v1.2.0](https://github.com/mklabs/tabtab/compare/v1.1.1...v1.2.0) - 2016-05-08 197 | 198 | ### Commits 199 | 200 | - feat: implement a basic cache mechanism [`bb4216c`](https://github.com/mklabs/tabtab/commit/bb4216c5b4abe5236ed5c03d96575e8d678c13d0) 201 | - fix: Use Object.assign polyfill to run on older version of node [`157057a`](https://github.com/mklabs/tabtab/commit/157057a0f25bca2a6ab9ee5f3a2b0d6005c1f724) 202 | 203 | ## [v1.1.1](https://github.com/mklabs/tabtab/compare/v1.1.0...v1.1.1) - 2016-05-01 204 | 205 | ### Commits 206 | 207 | - fix: more generic assert on prompt [`bbcd350`](https://github.com/mklabs/tabtab/commit/bbcd350379a1d5bf40fe2381d5fc7544b054d58b) 208 | 209 | ## [v1.1.0](https://github.com/mklabs/tabtab/compare/v1.0.5...v1.1.0) - 2016-05-01 210 | 211 | ### Commits 212 | 213 | - example: have yo-complete based on yeoman-environment and parse-help [`115fdae`](https://github.com/mklabs/tabtab/commit/115fdaec737ea64e3016b113b5b77360c38637fb) 214 | - Add notes on debug and log output [`e42149a`](https://github.com/mklabs/tabtab/commit/e42149a1349331ab6bea22497731b2e8a55e8d37) 215 | - feat(completion): Enhance package.json completion to support last word [`ce794d4`](https://github.com/mklabs/tabtab/commit/ce794d4a3a14e6f7519e027c240b9b7c0f536a96) 216 | - feat(completion): Emit completion events along package.json results [`2ed8ef5`](https://github.com/mklabs/tabtab/commit/2ed8ef560610db0c1da05b14f6a0e79197333858) 217 | - fish - set default description to package name [`9f8e934`](https://github.com/mklabs/tabtab/commit/9f8e9345657fc99655ddef6eef40c16a38c848c8) 218 | - fix(fish): Better handling of description [`779a188`](https://github.com/mklabs/tabtab/commit/779a188d5dbc3dd591938a56e2c9babf28249be3) 219 | 220 | ## [v1.0.5](https://github.com/mklabs/tabtab/compare/v1.0.4...v1.0.5) - 2016-04-30 221 | 222 | ### Commits 223 | 224 | - release: git push tags && npm publish [`29035d8`](https://github.com/mklabs/tabtab/commit/29035d8d5ddc0c57c75b101ce0dd827e543bb1ef) 225 | 226 | ## [v1.0.4](https://github.com/mklabs/tabtab/compare/v1.0.3...v1.0.4) - 2016-04-30 227 | 228 | ### Commits 229 | 230 | - Change standard-version msg [`d1d19f6`](https://github.com/mklabs/tabtab/commit/d1d19f6b15705bbc50a8f953f2e8a0738b2159fa) 231 | 232 | ## [v1.0.3](https://github.com/mklabs/tabtab/compare/v1.0.1...v1.0.3) - 2016-04-30 233 | 234 | ### Commits 235 | 236 | - chore(release): 1.0.3 [`dbbdcac`](https://github.com/mklabs/tabtab/commit/dbbdcac74412ccc5ef00d42dd3544d9b01f5bfcc) 237 | - fix(babel): Add babel as prepublish step [`97fc9ce`](https://github.com/mklabs/tabtab/commit/97fc9ceec13931d8b40aeb15297af9edeb6db6a9) 238 | 239 | ## [v1.0.1](https://github.com/mklabs/tabtab/compare/v1.0.0...v1.0.1) - 2016-04-29 240 | 241 | ### Merged 242 | 243 | - fix: zsh (on osx anyway) seems to require a space before the ]] [`#16`](https://github.com/mklabs/tabtab/pull/16) 244 | 245 | ### Commits 246 | 247 | - examples: add yo-complete example [`1a18381`](https://github.com/mklabs/tabtab/commit/1a1838146065230a488e2e1de3deedef224f448b) 248 | - fix: fix fish shell script to properly escape variables [`6f9664e`](https://github.com/mklabs/tabtab/commit/6f9664e49cd4626409f87c009103f9bc23ae5c70) 249 | - bash: apply same spacing before closing ] [`50f0340`](https://github.com/mklabs/tabtab/commit/50f034057d14cc5535804afc9d968cc813490087) 250 | - zsh (on osx anyway seems to require a space before the ]] [`1f9f983`](https://github.com/mklabs/tabtab/commit/1f9f983194a664cb385bbd1b0ba55b833b7c3249) 251 | 252 | ## [v1.0.0](https://github.com/mklabs/tabtab/compare/v1.0.0-pre...v1.0.0) - 2016-04-26 253 | 254 | ### Commits 255 | 256 | - Check in examples [`82de5ef`](https://github.com/mklabs/tabtab/commit/82de5ef2cd8bab7c2b5ebe30cce90a786e268ccb) 257 | - fix: check in example and fix bower-complete package.json [`c46185f`](https://github.com/mklabs/tabtab/commit/c46185ffd6663dc3fd9508a1c86583e5788f6477) 258 | 259 | ## [v1.0.0-pre](https://github.com/mklabs/tabtab/compare/v0.0.4...v1.0.0-pre) - 2016-04-26 260 | 261 | ### Commits 262 | 263 | - Main API and plumbing system done [`c3cba1d`](https://github.com/mklabs/tabtab/commit/c3cba1d0cccb98717340e1f594c21a09a97747b9) 264 | - Cleanup old dir [`45b09af`](https://github.com/mklabs/tabtab/commit/45b09af17c7897573097bb3de774d731c948f1df) 265 | - Prompt user for completion script installation method [`73f6090`](https://github.com/mklabs/tabtab/commit/73f60907f17dfb2d66d7c26fba8ac9d3f9b7ff88) 266 | - rm old completion file [`cef3c00`](https://github.com/mklabs/tabtab/commit/cef3c003b1ee85ca1cbc991b63587547de4ed3a8) 267 | - Update docs, less verbose debug output [`927e08c`](https://github.com/mklabs/tabtab/commit/927e08c39d0a76191dac37b7433b39da4fa80974) 268 | - Init v1 [`3314024`](https://github.com/mklabs/tabtab/commit/331402462a639f2111c2df3112d6ad3b29a8b5d3) 269 | - TomDocify [`9587418`](https://github.com/mklabs/tabtab/commit/95874188b01421e1850fc344c70a91c50755cb82) 270 | - Init command plumbing system [`0361905`](https://github.com/mklabs/tabtab/commit/0361905bf777b9014003b012387193b8dfdd4d97) 271 | - Implement fish bridge, template system depending on $SHELL [`1823230`](https://github.com/mklabs/tabtab/commit/18232300a0bb80d2c715a9b87f4eab69a239f744) 272 | - Shell adapters, handle bash / zsh / fish [`ab90a1a`](https://github.com/mklabs/tabtab/commit/ab90a1ae2920eb2a15913da85bfb9225194af7aa) 273 | - More docs [`a483822`](https://github.com/mklabs/tabtab/commit/a483822ab72a20dfc1783a432d4490f4cb897b43) 274 | - install - check for existing content before writing [`2250e08`](https://github.com/mklabs/tabtab/commit/2250e0830943d59ea0a3d490aaa49f3454aec088) 275 | - More docs [`731222e`](https://github.com/mklabs/tabtab/commit/731222e68dbe912cfd85366093c16c251f04c875) 276 | - Support completion item description for fish, still need work to do on zsh [`5dfc6f0`](https://github.com/mklabs/tabtab/commit/5dfc6f031ae6f18caa90f3d15e3e9b6346a6704d) 277 | - Implement json based completion results [`5421395`](https://github.com/mklabs/tabtab/commit/542139513e576c79f48acfcbc8ffff013dcad148) 278 | - wip install / uninstall [`16cdf73`](https://github.com/mklabs/tabtab/commit/16cdf7390e42ccab025563df427b4dc03e0dd890) 279 | - Handle permission issue [`c44ef31`](https://github.com/mklabs/tabtab/commit/c44ef315f7c10278bd5bd39bf56fbfff27e48f75) 280 | - Ensure directory exists before writing [`bed76b3`](https://github.com/mklabs/tabtab/commit/bed76b35a0795230b15d6df08396cf2e9f6e0fc0) 281 | - Event chaining, walking up the line untill it find a listener [`3c4241c`](https://github.com/mklabs/tabtab/commit/3c4241c51d68a33b52c287818d98c5d88ff93c91) 282 | - API example [`4c4d86c`](https://github.com/mklabs/tabtab/commit/4c4d86c876bdf47b286d3eaf196a51a4aabe342c) 283 | - docs task [`305b0b4`](https://github.com/mklabs/tabtab/commit/305b0b4f0015a39cbc71843d2207f4b02bd0517a) 284 | - ghpages task [`62c4362`](https://github.com/mklabs/tabtab/commit/62c4362434a76fd1657059ccc2d0c7968ecd858d) 285 | - badge version [`751af46`](https://github.com/mklabs/tabtab/commit/751af468b68d9140e3fd1144ec0139427e37c076) 286 | - doc -wrong prefix [`2c8b91a`](https://github.com/mklabs/tabtab/commit/2c8b91a817044328c598f0b004d96388369565f4) 287 | - travis - run mocha with babel-node [`962127c`](https://github.com/mklabs/tabtab/commit/962127c3824615bca48cf008c6d73559c08610a4) 288 | - travis [`5fe6b73`](https://github.com/mklabs/tabtab/commit/5fe6b73e0a970cb31e962f8c1a350ec5e9495095) 289 | - travis - add babelrc file [`8a2a29b`](https://github.com/mklabs/tabtab/commit/8a2a29b4a2f4a9b0f100ec3d87e4b7a08f943f4b) 290 | - travis - babel compile before test [`ddac422`](https://github.com/mklabs/tabtab/commit/ddac4220c10f255ba82562f79d78964dbea5162c) 291 | - Check in screenshots [`b7e3724`](https://github.com/mklabs/tabtab/commit/b7e37248108f24aba9d9e41becdadc80e1db72c8) 292 | - init completion directory registry [`3f92281`](https://github.com/mklabs/tabtab/commit/3f92281dff40f56364bf3dec070d179452ed1839) 293 | - Move old stuff to .old [`94369a0`](https://github.com/mklabs/tabtab/commit/94369a065d98d52ed4b1b4daef52157112084ee8) 294 | - Move old stuff [`a53de4a`](https://github.com/mklabs/tabtab/commit/a53de4a509f653fb361d961ea3fc98e174ddc81c) 295 | 296 | ## [v0.0.4](https://github.com/mklabs/tabtab/compare/v0.0.3...v0.0.4) - 2015-06-06 297 | 298 | ### Merged 299 | 300 | - Issues with tabtab in zsh. [`#10`](https://github.com/mklabs/tabtab/pull/10) 301 | - Fix typo [`#11`](https://github.com/mklabs/tabtab/pull/11) 302 | 303 | ### Commits 304 | 305 | - Updated the completion script to match current npm output. [`be1c512`](https://github.com/mklabs/tabtab/commit/be1c512fde5d7c64e9725e3cdf89e343ac8945b7) 306 | - Added default filesystem matching. [`f57a254`](https://github.com/mklabs/tabtab/commit/f57a2545ed45b2ceaef74d9f559e5588fce7d585) 307 | - :book: Fix typo [`45c6ead`](https://github.com/mklabs/tabtab/commit/45c6eadc3eeadaea4994a66272210e81ec9e17a6) 308 | - Didn't realize the line had {completer} before. Changing back. [`10f3472`](https://github.com/mklabs/tabtab/commit/10f3472f1886ac3a4a6c9929a3ceefcb6223d242) 309 | - Added back new line. [`c74f7ab`](https://github.com/mklabs/tabtab/commit/c74f7ab23bc37818d997578c7ba607c2f8c00a86) 310 | 311 | ## [v0.0.3](https://github.com/mklabs/tabtab/compare/v0.0.2...v0.0.3) - 2015-01-26 312 | 313 | ### Merged 314 | 315 | - Allow completing long options [`#5`](https://github.com/mklabs/tabtab/pull/5) 316 | - Catching EPIPE error caused by `source` closing file descriptor before reading it [`#4`](https://github.com/mklabs/tabtab/pull/4) 317 | 318 | ### Fixed 319 | 320 | - Fix #3 - Add license info [`#3`](https://github.com/mklabs/tabtab/issues/3) 321 | 322 | ### Commits 323 | 324 | - rm old .pkgrc file [`42bcf50`](https://github.com/mklabs/tabtab/commit/42bcf50dbf2b4d6a6533c08f56534e08f17847f7) 325 | - Catching error caused by `source` closing file argument before reading from it. [`4fca6aa`](https://github.com/mklabs/tabtab/commit/4fca6aaf04b30b04e3c66e46dd87b90c43b49bbc) 326 | - travis - node 0.10 [`e13de5b`](https://github.com/mklabs/tabtab/commit/e13de5b9ab83e480ba1c77a2fa7e9aeb57df3cdb) 327 | 328 | ## [v0.0.2](https://github.com/mklabs/tabtab/compare/v0.0.1...v0.0.2) - 2012-02-08 329 | 330 | ### Commits 331 | 332 | - tidy up the whole mess. remove unused / unnecessary code [`6a1e9c3`](https://github.com/mklabs/tabtab/commit/6a1e9c3879a454b1db4f277e26c1e4555390516a) 333 | - add missing devDependency [`fab4faf`](https://github.com/mklabs/tabtab/commit/fab4faf8115416902c64539472881c18d86d47eb) 334 | - bumping version [`cd56910`](https://github.com/mklabs/tabtab/commit/cd56910a847e3d77a5b3a8ed168ff81659f8bccd) 335 | - correct abbrev with `-` in it [`0b51ad8`](https://github.com/mklabs/tabtab/commit/0b51ad8140f152cdbbd15ed9bdbab46309cb8b82) 336 | 337 | ## v0.0.1 - 2011-11-11 338 | 339 | ### Commits 340 | 341 | - edit package.json [`9be6eba`](https://github.com/mklabs/tabtab/commit/9be6eba26133bed9d21bd0b5329dcc39b00d2449) 342 | - return warn messages as state [`8da7d5b`](https://github.com/mklabs/tabtab/commit/8da7d5bc2dc6cf781e7065790964f259c214db36) 343 | - warn without exiting with error, and ensure numbers on parsed env [`34a2ede`](https://github.com/mklabs/tabtab/commit/34a2ede7ebb5d0f21ad8021c712adaf87dc056a8) 344 | - rm gendoc script [`06d3a7a`](https://github.com/mklabs/tabtab/commit/06d3a7a4772edbc684d4777c88b9b84ef882dd0c) 345 | - add gendoc script [`dbd4739`](https://github.com/mklabs/tabtab/commit/dbd4739c965529be5f71e6fa30b2765e5efc2ea5) 346 | - package.json: specify directories for the docs task [`08a25ef`](https://github.com/mklabs/tabtab/commit/08a25ef1f829612fd8cf96b16e545bad42d82f49) 347 | - add some completion install/uninstall docs [`46d324a`](https://github.com/mklabs/tabtab/commit/46d324a9d72ecb9bcaa42d37d24c006b7b41e189) 348 | - rename to tabtab and edit test assert to use dynamic path [`061a357`](https://github.com/mklabs/tabtab/commit/061a357ae5af36541a52bf205b610aba0700ba01) 349 | - add vows test suite for completion output and install/uninstall cmd [`029de43`](https://github.com/mklabs/tabtab/commit/029de431ac136a92cf8498011c6937e08feb9da0) 350 | - edit docs.js comments and rm lib/cli.js (was empty anyway) [`4abc675`](https://github.com/mklabs/tabtab/commit/4abc675573a6b9107be8eb6caa2636cb400c46aa) 351 | - add pkgrc help command [`fff228f`](https://github.com/mklabs/tabtab/commit/fff228f68060ba567a463c1445c5f31c1654dd3b) 352 | - add install/uninstall helper [`6cfb0ee`](https://github.com/mklabs/tabtab/commit/6cfb0ee6a40684e918463b30b15240c939c132a3) 353 | - some docs, have more to write [`9ccd0d7`](https://github.com/mklabs/tabtab/commit/9ccd0d7841fad68552bee1d638b3fb2a51ac260d) 354 | - add play-complete script, completion from `play help` output [`f8347bb`](https://github.com/mklabs/tabtab/commit/f8347bb7d4d9949b758eb1a0b7b4ebf800f3bd9d) 355 | - Use readline's default filename completion if no matches. [`5ea2d4c`](https://github.com/mklabs/tabtab/commit/5ea2d4cb8a3159551e508906019ef698dcab1469) 356 | - log instruction on examples when not called within completion context [`bfc6ad0`](https://github.com/mklabs/tabtab/commit/bfc6ad064152268c23ec6557073e0ab84894224b) 357 | - parse ``` and ~~~~ special code marker in markdowns [`31ee00f`](https://github.com/mklabs/tabtab/commit/31ee00fad380e363fe9767df2d778326bbf0f846) 358 | - add help module, takes a file input (md, js or cs) and man a generated manpage [`11d5d70`](https://github.com/mklabs/tabtab/commit/11d5d70559205d16fd792f10c644f3c3d91ce779) 359 | - add basic script for vagrant completion [`5a8fd4d`](https://github.com/mklabs/tabtab/commit/5a8fd4dce74ed6275f1a4ecf56e7c0473a88fc31) 360 | - move helper functions to completion module [`5fc9fa0`](https://github.com/mklabs/tabtab/commit/5fc9fa058f37b8b8d5a7028c13ea1ad814c9de5b) 361 | - add cake/rake completion, very similar [`92f125f`](https://github.com/mklabs/tabtab/commit/92f125f4bcc4dc7adc3e245754b656e104355c60) 362 | - add completer options, decouple completed process from completer process [`c864c9d`](https://github.com/mklabs/tabtab/commit/c864c9d66e7900a179f00195f43d9bcb4ccada49) 363 | - completion: add cakefile completion, testing options/tasks completion [`33c272b`](https://github.com/mklabs/tabtab/commit/33c272b5be8641c7be1ffd447aa616dba9e9d00c) 364 | - completion: add optimist completion, have to parse out the help output [`6c1b1bb`](https://github.com/mklabs/tabtab/commit/6c1b1bb49cfc9ad641e350a09b3fcb1fb240a20d) 365 | - completion: add basic abbrev support and test with nopt/commander opt [`a857dd2`](https://github.com/mklabs/tabtab/commit/a857dd28b167d15b2c8ef45baaf3e3d02e23046a) 366 | - played a little with nopt/commander options and basic completion [`c6fa6de`](https://github.com/mklabs/tabtab/commit/c6fa6de2860e050dde8b02e8cff71f17d5f041d4) 367 | - add prev to options parsed from compgen [`cfb2894`](https://github.com/mklabs/tabtab/commit/cfb2894f5c4cd8c0f56dad31e7662fbf6c2bae87) 368 | - add some commander/optimist/nopt examples script [`22e0681`](https://github.com/mklabs/tabtab/commit/22e06814744b52c7d3b4450ea52c1cd5e1ab7f0d) 369 | - completion - install instruction and simple line parsing/callback api [`ce1f1f3`](https://github.com/mklabs/tabtab/commit/ce1f1f3960939b0a50c2806feddf8640893d69cd) 370 | - completion start [`94b103f`](https://github.com/mklabs/tabtab/commit/94b103f086f9d22d4a77a7de450976349a2e2a52) 371 | - initial config work, merge of global/local rc file [`64a0f7a`](https://github.com/mklabs/tabtab/commit/64a0f7a268398ddea17163f9edae4e64cb51fbc6) 372 | - a start [`a46ca29`](https://github.com/mklabs/tabtab/commit/a46ca2996264c6c4b2bf300855bdd11f3f4dadb1) 373 | --------------------------------------------------------------------------------