completion
Advanced tools
| var Completion = require('../'); | ||
| var gitCompletion = new Completion({ | ||
| name: 'git', | ||
| options: [{ | ||
| // `git --help`, a terminal option | ||
| name: '--help' | ||
| }], | ||
| commands: [{ | ||
| // `git checkout master` | ||
| name: 'checkout', | ||
| option: [{ | ||
| // `git checkout -b dev/hello` | ||
| name: '-b', | ||
| completion: function (info, cb) { | ||
| // `-b` was matched by `completion` so keep on recursing | ||
| return this.resolveInfo(info, cb); | ||
| } | ||
| }], | ||
| completion: function getGitBranches (info, cb) { | ||
| // Get git branches and find matches | ||
| } | ||
| }, { | ||
| name: 'remote', | ||
| commands: [{ | ||
| // `git remote add origin git@github.com:...` | ||
| // No possible completion here | ||
| name: 'add' | ||
| }, { | ||
| // `git remote rm origin` | ||
| name: 'rm', | ||
| completion: function getGitBranches (info, cb) { | ||
| // Get git branches and find matches | ||
| } | ||
| }] | ||
| }] | ||
| }); | ||
| gitCompletion.complete({ | ||
| // `git remo|add` | ||
| line: 'git remoadd', | ||
| cursor: 8 | ||
| }, function (err, results) { | ||
| console.log(results); // ['remote'] | ||
| }); | ||
| gitCompletion.complete({ | ||
| // `git remote |` | ||
| line: 'git remote ', | ||
| cursor: 11 | ||
| }, function (err, results) { | ||
| console.log(results); // ['add', 'remove'] | ||
| }); |
| var Completion = require('../'); | ||
| var completion = new Completion({ | ||
| name: 'git', | ||
| commands: [{ | ||
| name: 'checkout', | ||
| completion: function (info, cb) { | ||
| // For `git checkout dev/|` | ||
| // info.words.value = ['git', 'checkout', 'dev/'] | ||
| // info.word.partialLeft = 'dev/' | ||
| var that = this; | ||
| getGitBranches(function (err, allBranches) { | ||
| if (err) { | ||
| return cb(err); | ||
| } | ||
| // Match 'dev/' === 'dev/' (from 'dev/hello') | ||
| var partialLeftWord = info.word.partialLeft; | ||
| var branches = that.matchLeftWord(partialLeftWord, allBranches); | ||
| cb(null, branches); | ||
| }); | ||
| } | ||
| }] | ||
| }); | ||
| completion.complete({ | ||
| // `git chec|` | ||
| line: 'git chec', | ||
| cursor: 8 | ||
| }, function (err, results) { | ||
| console.log(results); // ['checkout'] | ||
| }); |
| // Load in dependencies | ||
| var assert = require('assert'); | ||
| var completionUtils = require('./utils/completion'); | ||
| // Start our tests | ||
| describe('A command with terminal options', function () { | ||
| completionUtils.init({ | ||
| name: 'git', | ||
| options: [{ | ||
| // Do not complete anything new after `help` | ||
| name: '--help' | ||
| }], | ||
| commands: [{ | ||
| name: 'checkout', | ||
| completion: function (info, cb) { | ||
| cb(null, ['hello-world', 'hello-there']); | ||
| } | ||
| }] | ||
| }); | ||
| describe('completing a terminal option followed by a command', function () { | ||
| completionUtils.completeCommand('git --help chec|'); | ||
| it('does not return any matching commands', function () { | ||
| assert.deepEqual(this.results, []); | ||
| }); | ||
| }); | ||
| describe('completing an option', function () { | ||
| completionUtils.completeCommand('git --he|'); | ||
| it('does not return any values', function () { | ||
| assert.deepEqual(this.results, []); | ||
| }); | ||
| }); | ||
| }); | ||
| describe('A command with non-terminal options', function () { | ||
| completionUtils.init({ | ||
| name: 'git', | ||
| commands: [{ | ||
| name: 'checkout', | ||
| options: [{ | ||
| name: '-b', | ||
| completion: function (info, cb) { | ||
| // The `-b` has already been shifted because we matched `-b` | ||
| // As a result, attempt to complete once again from `git's` context | ||
| this.resolveInfo(info, cb); | ||
| } | ||
| }], | ||
| completion: function (info, cb) { | ||
| cb(null, ['hello-world', 'hello-there']); | ||
| } | ||
| }] | ||
| }); | ||
| describe('completing a command\'s values', function () { | ||
| completionUtils.completeCommand('git checkout -b hello|'); | ||
| it('returns matching values', function () { | ||
| assert.deepEqual(this.results, ['hello-world', 'hello-there']); | ||
| }); | ||
| }); | ||
| }); | ||
| describe('A command with non-terminal command options', function () { | ||
| completionUtils.init({ | ||
| name: 'git', | ||
| options: [{ | ||
| name: '--dry-run', | ||
| completion: function (info, cb) { | ||
| // --dry-run has already been shifted, continue resolving | ||
| this.resolveInfo(info, cb); | ||
| } | ||
| }], | ||
| commands: [{ | ||
| name: 'checkout', | ||
| completion: function (info, cb) { | ||
| cb(null, ['hello-world', 'hello-there']); | ||
| } | ||
| }] | ||
| }); | ||
| describe('completing a command', function () { | ||
| completionUtils.completeCommand('git --dry-run chec|'); | ||
| it('returns a matching command', function () { | ||
| assert.deepEqual(this.results, ['checkout']); | ||
| }); | ||
| }); | ||
| }); |
| var Completion = require('../../'); | ||
| var cursorUtils = require('./cursor'); | ||
| // Define set of utilities for `completion` | ||
| exports.completeCommand = function (command) { | ||
| before(function (done) { | ||
| var params = cursorUtils.splitAtCursor(command); | ||
| var that = this; | ||
| this.completion.complete(params, function (err, results) { | ||
| that.results = results; | ||
| done(err); | ||
| }); | ||
| }); | ||
| after(function () { | ||
| delete this.results; | ||
| }); | ||
| }; | ||
| exports.init = function (params) { | ||
| before(function initCompletion () { | ||
| this.completion = new Completion(params); | ||
| }); | ||
| after(function cleanupCompletion () { | ||
| delete this.completion; | ||
| }); | ||
| }; |
+2
-0
| # completion changelog | ||
| 1.0.0 - Reworked data structure to support options | ||
| 0.4.0 - Moved onto recursive solver, allowing for completion commands to re-enter context | ||
@@ -3,0 +5,0 @@ |
+95
-57
@@ -5,14 +5,10 @@ // Load in dependencies | ||
| // TODO: In order to make `options` not appear in `object`-based resolutons (e.g. ['-p', 'checkout']) | ||
| // allow them to be tagged with a special flag (e.g. `Completion.optional(fn)` -> sets _completionOptional = true) | ||
| // Define our completion constructor | ||
| function Completion(tree, parentNode) { | ||
| function Completion(tree) { | ||
| // Save the tree as our node | ||
| this.node = tree; | ||
| this.parentNode = parentNode; | ||
| } | ||
| Completion.prototype = { | ||
| // Helper functions for working with `info` | ||
| matchLeftWord: function (_info) { | ||
| shiftLeftWord: function (_info) { | ||
| // Prevent mutation on the original `info` | ||
@@ -29,10 +25,9 @@ var info = deepClone(_info); | ||
| }, | ||
| matchLeftWord: function (leftWord, words) { | ||
| return words.filter(function findMatchingWords (word) { | ||
| return leftWord === word.substr(0, leftWord.length); | ||
| }); | ||
| }, | ||
| // Define completion methods | ||
| complete: function (params, cb) { | ||
| // Collect info | ||
| var info = lineInfo(params); | ||
| return this.completeInfo(info, cb); | ||
| }, | ||
| completeInfo: function (info, cb) { | ||
| _guaranteeMatchedInfo: function (info) { | ||
| // If there is no `remainingLeft` or `matchedLeft` words, add them | ||
@@ -50,39 +45,61 @@ if (!info.words.remainingLeft || !info.words.matchedLeft) { | ||
| return info; | ||
| }, | ||
| // Define completion methods | ||
| complete: function (params, cb) { | ||
| // Collect info | ||
| var info = lineInfo(params); | ||
| return this.completeInfo(info, cb); | ||
| }, | ||
| completeInfo: function (info, cb) { | ||
| // Remove the newest matching word | ||
| info = this._guaranteeMatchedInfo(info); | ||
| info = this.shiftLeftWord(info); | ||
| var matchedWord = info.words.matchedLeft[info.words.matchedLeft.length - 1]; | ||
| // If the matched word did not match the command, exit with no results | ||
| // `npm pub|` matching ['git', 'checkout'] -> npm !== git | ||
| var node = this.node; | ||
| if (matchedWord !== node.name) { | ||
| return cb(null, []); | ||
| } | ||
| return this.resolveInfo(info, cb); | ||
| }, | ||
| resolveInfo: function (info, cb) { | ||
| // If there are no words left, exit early with nothing | ||
| // `npm|` -> ['npm'] matched -> [] | ||
| info = this._guaranteeMatchedInfo(info); | ||
| var node = this.node; | ||
| if (info.words.remainingLeft.length === 0) { | ||
| return cb(null, []); | ||
| } | ||
| // The following is a recursive loop that creates child completion's until we arrive | ||
| // at the second to last word in the left half of the command | ||
| // at the last word in the left half of the command | ||
| // `git che|c` -> ['git', 'che'] + ['c'] -> git.che (404) -> git.*.filter('che') | ||
| // `git checkout |world` -> ['git', 'checkout'] + ['world'] -> git.checkout(params, cb) (`['world']`) | ||
| var node = this.node; | ||
| // If we have less than 2 remaining left words, return early | ||
| if (info.words.remainingLeft.length < 2) { | ||
| // DEV: `node` can be an object (more commands remaining), | ||
| // DEV: a function (custom complete logic), or something falsy (e.g. no future autocompletes possible) | ||
| // If the second to last node is falsy, callback with nothing | ||
| // ['npm', 'publish', ''] on {npm: {publish: null}} -> [] (nothing to complete) | ||
| var secondToLastNode = node; | ||
| if (!secondToLastNode) { | ||
| // If there is 1 word remaining, determine what to do | ||
| if (info.words.remainingLeft.length === 1) { | ||
| // If there is completion logic, use it | ||
| // ['git', 'checkout', 'hello'] on {name: git, commands: [{name: checkout, completion: getBranches}]} | ||
| // -> ['hello.word' (branch)] | ||
| if (node.completion) { | ||
| return node.completion.call(this, info, cb); | ||
| // If there are more commands, match them | ||
| // ['git', 'che'] on {name: git, commands: [{name: checkout, completion: getBranches}]} | ||
| // -> ['checkout'] | ||
| } else if (node.commands) { | ||
| var cmds = node.commands.map(function getCommandName (commandNode) { | ||
| return commandNode.name; | ||
| }); | ||
| var partialLeftWord = info.word.partialLeft; | ||
| var matchingCmds = this.matchLeftWord(partialLeftWord, cmds); | ||
| matchingCmds.sort(); | ||
| return cb(null, matchingCmds); | ||
| // Otherwise, this is a terminal command so callback with nothing | ||
| } else { | ||
| return cb(null, []); | ||
| // Otherwise, attempt to understand what to do | ||
| } else { | ||
| var partialLeftWord = info.word.partialLeft; | ||
| // If the node is a function, find all possible completions | ||
| // ['git', 'checkout', 'hello'] on {git: {checkout: getGitBranches}} -> ['hello.word' (branch)] | ||
| if (typeof secondToLastNode === 'function') { | ||
| return secondToLastNode.call(this, info, cb); | ||
| // If the node is an objet, find matching commands | ||
| // ['git', 'che'] on {git: {checkout: getGitBranches}} -> ['checkout'] | ||
| } else if (typeof secondToLastNode === 'object') { | ||
| var cmds = Object.getOwnPropertyNames(secondToLastNode); | ||
| var matchingCmds = cmds.filter(function (cmd) { | ||
| return partialLeftWord === cmd.substr(0, partialLeftWord.length); | ||
| }); | ||
| matchingCmds.sort(); | ||
| return cb(null, matchingCmds); | ||
| // Otherwise, we don't know what to do (not an object or fn) | ||
| // so callback with nothing | ||
| } else { | ||
| return cb(null, []); | ||
| } | ||
| } | ||
@@ -92,18 +109,39 @@ // Otherwise, attempt to keep on recursing | ||
| // Match the newest left word | ||
| info = this.matchLeftWord(info); | ||
| var nextWord = info.words.remainingLeft[0]; | ||
| // Find the next node | ||
| var matchedWord = info.words.matchedLeft[info.words.matchedLeft.length - 1]; | ||
| var childNode = node[matchedWord]; | ||
| // If the next word is an option | ||
| var optionNodes = node.options || []; | ||
| var matchedOptionNode = optionNodes.filter(function matchoption (optionNode) { | ||
| return optionNode.name === nextWord; | ||
| })[0]; | ||
| if (matchedOptionNode) { | ||
| // If there is a completion action, match it and use it | ||
| if (matchedOptionNode.completion) { | ||
| info = this.shiftLeftWord(info); | ||
| return matchedOptionNode.completion.call(this, info, cb); | ||
| // Otherwise, exit with no results | ||
| } else { | ||
| return cb(null, []); | ||
| } | ||
| } | ||
| // If there is no new node, exit with no more results | ||
| // DEV: This could be `null` as defined by someone's completion case | ||
| // DEV: or it could be `undefined` if the completion algorithm has not been defined for this command | ||
| if (!childNode) { | ||
| cb(null, []); | ||
| // Otherwise, if the next word is a command | ||
| var commandNodes = node.commands || []; | ||
| var matchedCommandNode; | ||
| var i = 0; | ||
| var len = commandNodes.length; | ||
| for (; i < len; i++) { | ||
| var commandNode = commandNodes[i]; | ||
| if (commandNode.name === nextWord) { | ||
| matchedCommandNode = commandNode; | ||
| } | ||
| } | ||
| if (matchedCommandNode) { | ||
| // Recurse further | ||
| var childCompletion = new Completion(matchedCommandNode); | ||
| return childCompletion.completeInfo(info, cb); | ||
| } | ||
| // Otherwise, recurse further | ||
| var childCompletion = new Completion(childNode, this); | ||
| childCompletion.completeInfo(info, cb); | ||
| // Otherwise, there are no more matches and exit with no results | ||
| cb(null, []); | ||
| } | ||
@@ -110,0 +148,0 @@ } |
+6
-3
| { | ||
| "name": "completion", | ||
| "description": "Completion library for words, commands, and sentences", | ||
| "version": "0.4.0", | ||
| "description": "Completion library for CLI commands", | ||
| "version": "1.0.0", | ||
| "homepage": "https://github.com/twolfson/completion", | ||
@@ -46,4 +46,7 @@ "author": { | ||
| "auto", | ||
| "tab" | ||
| "tab", | ||
| "cli", | ||
| "bash", | ||
| "zsh" | ||
| ] | ||
| } |
+108
-31
| # completion [](https://travis-ci.org/twolfson/completion) | ||
| Completion library for words, commands, and sentences | ||
| Completion library for CLI commands | ||
@@ -23,10 +23,13 @@ This was built as part of [foundry][], a CLI utility for making releases painless. | ||
| ```javascript | ||
| ```js | ||
| var Completion = require('completion'); | ||
| var completion = new Completion({ | ||
| git: { | ||
| checkout: function (info, cb) { | ||
| name: 'git', | ||
| commands: [{ | ||
| name: 'checkout', | ||
| completion: function (info, cb) { | ||
| // For `git checkout dev/|` | ||
| // info.words.value = ['git', 'checkout', 'dev/'] | ||
| // info.word.partialLeft = 'dev/' | ||
| var that = this; | ||
| getGitBranches(function (err, allBranches) { | ||
@@ -37,7 +40,5 @@ if (err) { | ||
| // Match 'dev/' === 'dev/' (from 'dev/hello') | ||
| var partialLeftWord = info.word.partialLeft; | ||
| var branches = allBranches.filter(function (branch) { | ||
| // 'chec' === 'chec' (from 'checkout') | ||
| return partialLeftWord === branch.substr(0, partialLeftWord.length); | ||
| }); | ||
| var branches = that.matchLeftWord(partialLeftWord, allBranches); | ||
| cb(null, branches); | ||
@@ -85,20 +86,69 @@ }); | ||
| - tree `Object` - Outline of program | ||
| - Each key represents a new command (e.g. `git`, `checkout`) | ||
| - Each value can be | ||
| - An object representing another layer of commands | ||
| - A function that will callback with potential matches | ||
| - The function should be error-first; have a signature of `function (info, cb)` | ||
| - info `Object` - Collection of distilled information | ||
| - The format will be the returned value from [twolfson/line-info][] | ||
| - cb `Function` - Error-first callback function to run with matches | ||
| - `cb` has a signature of `function (err, results)` | ||
| - `null` representing a terminal function which has no further predictive input | ||
| - **If you want to list out files, do so. Don't use `null` for that case.** | ||
| - tree `Object` - Outline of a program/command | ||
| - name `String` - Command that is being executed (e.g. `git`, `checkout`) | ||
| - options `Object[]` - Optional array of objects that represent options | ||
| - name `String` - Name of option (e.g. `--help`) | ||
| - completion `Function` - Optional function to complete the remainder of the invocation | ||
| - If no `completion` is specified, we assume this is terminal and stop recursing | ||
| - Details on completion functions can be found below | ||
| - commands `Object[]` - Optional array of new `tree` instances to complete against | ||
| - This cannot exist on the same node as `completion` as they are contradictory | ||
| - completion `Function` - Optional completion function to determine results for a command | ||
| - Details on completion can be found below | ||
| #### `command/option completion` functions | ||
| `options` and `commands` share a common completion function signature, `function (info, cb)` | ||
| Each `completion` function will be executed with the command node as its `this` context | ||
| - info `Object` - Information about original input | ||
| - Content will be information from [twolfson/line-info][] | ||
| - We provide 2 additional properties | ||
| - words.matchedLeft `String[]` - Words matched from `words.partialLeft` while walking the tree | ||
| - words.remainingLeft `String[]` - Unmatched words that need to be/can be matched against | ||
| - cb `Function` - Error-first callback function to return matches via | ||
| - `cb` has a signature of `function (err, results)` | ||
| [twolfson/line-info]: https://github.com/twolfson/line-info#lineinfoparams | ||
| ### `completion.complete(params, cb)` | ||
| Get potential completion matches | ||
| For options, it is often preferred to remove more words that are matched (e.g. `-m <msg>`). For this, we suggest using the [`shiftLeftWord` method][shift-left-word]. | ||
| For completing partial matches, we provide the [`matchLeftWord` method][match-left-word]. | ||
| To create non-terminal options, we can use the [method `resolveInfo`][resolve-info] to keep on searching against the `remainingLeft` words. | ||
| [shift-left-word]: #completionshiftleftwordinfo | ||
| [match-left-word]: #completionmatchleftwordleftword-words | ||
| [resolve-info]: #completionresolveinfoinfo-cb | ||
| #### `completion.shiftLeftWord(info)` | ||
| Helper function to shift word from `info.words.remainingLeft` to `info.words.matchedLeft` | ||
| - info `Object` - Information passed into `completion` functon | ||
| ```js | ||
| var info = {words: {remainingLeft: ['hello', 'world'], matchedLeft: []}}; | ||
| info = this.shiftLeftWord(info); | ||
| info; // {words: {remainingLeft: ['world'], matchedLeft: ['hello']}} | ||
| ``` | ||
| #### `completion.matchLeftWord(leftWord, words)` | ||
| Helper function to find words from `words` that start with `leftWord` | ||
| - leftWord `String` - Word to match left content of | ||
| - `leftWord` gets its name from usually coming from `words.partialLeft` | ||
| - words `String[]` - Array of words to filter against | ||
| Returns: | ||
| - matchedWords `String[]` - Matching words from `words` that start with `leftWord` | ||
| ```js | ||
| this.matchLeftWord('hello', ['hello-world', 'hello-there', 'goodbye-moon']); | ||
| // ['hello-world', 'hello-there']; | ||
| ``` | ||
| #### `completion.complete(params, cb)` | ||
| Get potential completion matches for given parameters | ||
| - params `Object` - Information similar to that passed in by `bash's` tab completion | ||
@@ -110,2 +160,10 @@ - line `String` - Input to complete against (similar to `COMP_LINE`) | ||
| #### `completion.resolveInfo(info, cb)` | ||
| Recursively find matches against the `Completion's tree` with a given `info` | ||
| - info `Object` - CLI information provided by [twolfson/line-info][] | ||
| - This is converted from `params` to its current equivalent by [twolfson/line-info][] | ||
| - cb `Function` - Error first callback function that receives matches | ||
| - `cb` should be the same as in `completion.complete` | ||
| ## Examples | ||
@@ -116,16 +174,35 @@ An example of `git` would be | ||
| var gitCompletion = new Completion({ | ||
| git: { | ||
| name: 'git', | ||
| options: [{ | ||
| // `git --help`, a terminal option | ||
| name: '--help' | ||
| }], | ||
| commands: [{ | ||
| // `git checkout master` | ||
| checkout: function (info, cb) { | ||
| name: 'checkout', | ||
| option: [{ | ||
| // `git checkout -b dev/hello` | ||
| name: '-b', | ||
| completion: function (info, cb) { | ||
| // `-b` was matched by `completion` so keep on recursing | ||
| return this.resolveInfo(info, cb); | ||
| } | ||
| }], | ||
| completion: function getGitBranches (info, cb) { | ||
| // Get git branches and find matches | ||
| }, | ||
| remote: { | ||
| } | ||
| }, { | ||
| name: 'remote', | ||
| commands: [{ | ||
| // `git remote add origin git@github.com:...` | ||
| add: null, // No possible tab completion here | ||
| // No possible completion here | ||
| name: 'add' | ||
| }, { | ||
| // `git remote rm origin` | ||
| rm: function (info, cb) { | ||
| name: 'rm', | ||
| completion: function getGitBranches (info, cb) { | ||
| // Get git branches and find matches | ||
| } | ||
| } | ||
| } | ||
| }] | ||
| }] | ||
| }); | ||
@@ -143,3 +220,3 @@ | ||
| // `git remote |` | ||
| line: 'git remote', | ||
| line: 'git remote ', | ||
| cursor: 11 | ||
@@ -146,0 +223,0 @@ }, function (err, results) { |
+55
-85
| // Load in dependencies | ||
| var assert = require('assert'); | ||
| var deepClone = require('clone'); | ||
| var Completion = require('../'); | ||
| var cursorUtils = require('./utils/cursor'); | ||
| var completionUtils = require('./utils/completion'); | ||
| // Define set of utilities for `completion` | ||
| var completionUtils = { | ||
| completeCommand: function (command) { | ||
| before(function (done) { | ||
| var params = cursorUtils.splitAtCursor(command); | ||
| var that = this; | ||
| this.completion.complete(params, function (err, results) { | ||
| that.results = results; | ||
| done(err); | ||
| }); | ||
| }); | ||
| after(function () { | ||
| delete this.results; | ||
| }); | ||
| }, | ||
| init: function (params) { | ||
| before(function initCompletion () { | ||
| this.completion = new Completion(params); | ||
| }); | ||
| after(function cleanupCompletion () { | ||
| delete this.completion; | ||
| }); | ||
| } | ||
| }; | ||
| // Start our tests | ||
| describe('A partial command with one completion match', function () { | ||
| completionUtils.init({ | ||
| npm: { | ||
| publish: null | ||
| } | ||
| name: 'npm', | ||
| commands: [{ | ||
| name: 'publish' | ||
| }] | ||
| }); | ||
@@ -50,10 +25,14 @@ | ||
| completionUtils.init({ | ||
| git: { | ||
| checkout: function (params, cb) { | ||
| name: 'git', | ||
| commands: [{ | ||
| name: 'checkout', | ||
| completion: function (info, cb) { | ||
| cb(null, ['hello.world']); | ||
| }, | ||
| 'cherry-pick': function (params, cb) { | ||
| } | ||
| }, { | ||
| name: 'cherry-pick', | ||
| completion: function (info, cb) { | ||
| cb(null, ['maraschino']); | ||
| } | ||
| } | ||
| }] | ||
| }); | ||
@@ -72,10 +51,14 @@ | ||
| completionUtils.init({ | ||
| git: { | ||
| checkout: function (params, cb) { | ||
| name: 'git', | ||
| commands: [{ | ||
| name: 'checkout', | ||
| completion: function (info, cb) { | ||
| cb(null, ['hello.world']); | ||
| }, | ||
| 'cherry-pick': function (params, cb) { | ||
| } | ||
| }, { | ||
| name: 'cherry-pick', | ||
| completion: function (info, cb) { | ||
| cb(null, ['maraschino']); | ||
| } | ||
| } | ||
| }] | ||
| }); | ||
@@ -94,5 +77,6 @@ | ||
| completionUtils.init({ | ||
| npm: { | ||
| publish: null | ||
| } | ||
| name: 'npm', | ||
| commands: [{ | ||
| name: 'publish' | ||
| }] | ||
| }); | ||
@@ -110,6 +94,7 @@ | ||
| describe('A terminal command with whitespace', function () { | ||
| completionUtils.init({ | ||
| npm: { | ||
| publish: null | ||
| } | ||
| completionUtils.init({ | ||
| name: 'npm', | ||
| commands: [{ | ||
| name: 'publish' | ||
| }] | ||
| }); | ||
@@ -128,7 +113,9 @@ | ||
| completionUtils.init({ | ||
| git: { | ||
| checkout: function (params, cb) { | ||
| name: 'git', | ||
| commands: [{ | ||
| name: 'checkout', | ||
| completion: function (info, cb) { | ||
| cb(null, ['hello-world', 'hello-there']); | ||
| } | ||
| } | ||
| }] | ||
| }); | ||
@@ -147,7 +134,9 @@ | ||
| completionUtils.init({ | ||
| git: { | ||
| checkout: function (params, cb) { | ||
| name: 'git', | ||
| commands: [{ | ||
| name: 'checkout', | ||
| completion: function (info, cb) { | ||
| cb(null, ['hello-world', 'hello-there']); | ||
| } | ||
| } | ||
| }] | ||
| }); | ||
@@ -164,39 +153,20 @@ | ||
| describe.skip('A command with options', function () { | ||
| describe('A many level command', function () { | ||
| completionUtils.init({ | ||
| git: { | ||
| '-b': function (params, cb) { | ||
| // The `-b` has already been shifted because we matched `-b` | ||
| // As a result, attempt to complete once again from `git's` context | ||
| this.parentNode.completeInfo(params, cb); | ||
| }, | ||
| checkout: function (params, cb) { | ||
| cb(null, ['hello-world', 'hello-there']); | ||
| } | ||
| } | ||
| name: 'git', | ||
| commands: [{ | ||
| name: 'remote', | ||
| commands: [{ | ||
| name: 'add' | ||
| }] | ||
| }] | ||
| }); | ||
| describe('being completed with a terminal command', function () { | ||
| completionUtils.completeCommand('git -b chec|'); | ||
| describe('when completing an incomplete command', function () { | ||
| completionUtils.completeCommand('git remote a|'); | ||
| it('returns the command without completion options', function () { | ||
| assert.deepEqual(this.results, ['checkout']); | ||
| it('returns the expected command', function () { | ||
| assert.deepEqual(this.results, ['add']); | ||
| }); | ||
| }); | ||
| describe('being completed with a non-terminal command', function () { | ||
| completionUtils.completeCommand('git -b checkout hello|'); | ||
| it('returns the command without completion options', function () { | ||
| assert.deepEqual(this.results, ['hello-world', 'hello-there']); | ||
| }); | ||
| }); | ||
| describe('being completed with a command followed by an option', function () { | ||
| completionUtils.completeCommand('git checkout -b wat|'); | ||
| it('returns the command without completion options', function () { | ||
| assert.deepEqual(this.results, []); | ||
| }); | ||
| }); | ||
| }); |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
28867
37.1%18
28.57%528
58.08%0
-100%239
47.53%1
Infinity%