mirror of
https://github.com/bower/bower.git
synced 2026-04-24 03:00:19 -04:00
Tab completion, handler for install command
- results of the first lookup is cached to ~/.bower/completion/install.json - consider caching in ~/.bower/cache instead, so that cache-clean command also removes completion cache.
This commit is contained in:
102
lib/commands/completion.js
Normal file
102
lib/commands/completion.js
Normal file
@@ -0,0 +1,102 @@
|
||||
// ==========================================
|
||||
// BOWER: Completion API
|
||||
// ==========================================
|
||||
// Copyright 2012 Twitter, Inc
|
||||
// Licensed under The MIT License
|
||||
// http://opensource.org/licenses/MIT
|
||||
// ==========================================
|
||||
var Emitter = require('events').EventEmitter;
|
||||
var path = require('path');
|
||||
var nopt = require('nopt');
|
||||
var mkdirp = require('mkdirp');
|
||||
|
||||
var template = require('../util/template');
|
||||
var complete = require('../util/completion');
|
||||
var config = require('../core/config');
|
||||
var help = require('./help');
|
||||
|
||||
var optionTypes = { help: Boolean };
|
||||
var shorthand = { 'h': ['--help'] };
|
||||
|
||||
module.exports = function (argv, env) {
|
||||
env = env || process.env;
|
||||
|
||||
var emitter = new Emitter;
|
||||
var commands = require('../commands');
|
||||
|
||||
// top level flags
|
||||
var flags = ['--no-color', '--help', '--version'];
|
||||
|
||||
var done = function done() {
|
||||
process.nextTick(function () {
|
||||
emitter.emit('end');
|
||||
});
|
||||
|
||||
return emitter;
|
||||
};
|
||||
|
||||
// if the COMP_* isn't in the env, then just dump the script.
|
||||
if (!env.COMP_CWORD) {
|
||||
template('completion').on('data', emitter.emit.bind(emitter, 'end'));
|
||||
return emitter;
|
||||
}
|
||||
|
||||
// parse environment and arguments, returns a hash of completion config.
|
||||
var opts = complete(argv, env);
|
||||
|
||||
// if only one word, complete the list of command or top level flags
|
||||
if (opts.w === 1) {
|
||||
if (opts.word.charAt(0) === '-') complete.log(flags, opts);
|
||||
else complete.log(Object.keys(commands), opts);
|
||||
return done();
|
||||
}
|
||||
|
||||
// try to find the bower command. first thing after all the configs.
|
||||
var parsed = opts.conf = nopt({}, {}, opts.partialWords, 0);
|
||||
var cmd = parsed.argv.remain[0];
|
||||
|
||||
// unable to find a command, complete the lisf of commands
|
||||
if (!cmd) {
|
||||
complete.log(Object.keys(commands), opts);
|
||||
return done();
|
||||
}
|
||||
|
||||
// if words[0] is a bower command, then complete on it.
|
||||
cmd = commands[cmd];
|
||||
|
||||
if (cmd && cmd.completion) {
|
||||
// prior to that, ensure the completion cache directory is created first
|
||||
mkdirp(path.join(config.completion), function (err) {
|
||||
if (err) return emitter.emit('error', err);
|
||||
cmd.completion(opts, function (err, data) {
|
||||
if (err) return emitter.emit('error', err);
|
||||
|
||||
// completing options, then append top level flags
|
||||
if (opts.word.charAt(0) === '-') data = data.concat(flags);
|
||||
|
||||
complete.log(data, opts);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
return emitter;
|
||||
}
|
||||
|
||||
// otherwise, do nothing
|
||||
return emitter;
|
||||
};
|
||||
|
||||
module.exports.line = function (argv) {
|
||||
var emitter = new Emitter;
|
||||
var options = nopt(optionTypes, shorthand, argv);
|
||||
|
||||
if (options.help) return help('completion');
|
||||
|
||||
module.exports(options.argv.remain.slice(2), process.env)
|
||||
.on('data', emitter.emit.bind(emitter, 'data'))
|
||||
.on('error', emitter.emit.bind(emitter, 'error'))
|
||||
.on('end', emitter.emit.bind(emitter, 'end'));
|
||||
|
||||
return emitter;
|
||||
};
|
||||
@@ -13,8 +13,12 @@
|
||||
|
||||
var Emitter = require('events').EventEmitter;
|
||||
var nopt = require('nopt');
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
|
||||
var Manager = require('../core/manager');
|
||||
var config = require('../core/config');
|
||||
var source = require('../core/source');
|
||||
var save = require('../util/save');
|
||||
var help = require('./help');
|
||||
|
||||
@@ -43,3 +47,36 @@ module.exports.line = function (argv) {
|
||||
if (options.help) return help('install');
|
||||
return module.exports(paths, options);
|
||||
};
|
||||
|
||||
module.exports.completion = function (opts, cb) {
|
||||
var word = opts.word;
|
||||
|
||||
// completing options?
|
||||
if (opts.words[0] === 'install' && word.charAt(0) === '-') {
|
||||
return cb(null, Object.keys(optionTypes).map(function (option) {
|
||||
return '--' + option;
|
||||
}));
|
||||
}
|
||||
|
||||
var cache = path.join(config.completion, 'install.json');
|
||||
var done = function done(err, results) {
|
||||
if (err) return cb(err);
|
||||
var names = results.map(function (pkg) {
|
||||
return pkg.name;
|
||||
});
|
||||
|
||||
return cb(null, names);
|
||||
};
|
||||
|
||||
fs.readFile(cache, function (err, body) {
|
||||
if (!err) return done(null, JSON.parse(body));
|
||||
|
||||
// expected error, do the first request and cache the results
|
||||
source.all(function (err, results) {
|
||||
if (err) return cb(err);
|
||||
fs.writeFile(cache, JSON.stringify(results, null, 2), function (err) {
|
||||
done(err, results);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -26,6 +26,7 @@ var folder = process.platform === 'win32'
|
||||
var config = require('rc') ('bower', {
|
||||
cache : path.join(roaming, folder, 'cache'),
|
||||
links : path.join(roaming, folder, 'links'),
|
||||
completion : path.join(roaming, folder, 'completion'),
|
||||
json : 'component.json',
|
||||
endpoint : 'https://bower.herokuapp.com',
|
||||
directory : 'components'
|
||||
@@ -41,4 +42,4 @@ if (fileExists(localFile)) {
|
||||
// If an uncaught exception occurs, the temporary directories will be deleted nevertheless
|
||||
tmp.setGracefulCleanup();
|
||||
|
||||
module.exports = config;
|
||||
module.exports = config;
|
||||
|
||||
65
lib/util/completion.js
Normal file
65
lib/util/completion.js
Normal file
@@ -0,0 +1,65 @@
|
||||
// ==========================================
|
||||
// BOWER: completion
|
||||
// ==========================================
|
||||
// Copyright 2012 Twitter, Inc
|
||||
// Licensed under The MIT License
|
||||
// http://opensource.org/licenses/MIT
|
||||
// ==========================================
|
||||
|
||||
// This module exposes a simple helper to parse the environment variables in
|
||||
// case of a tab completion command. It parses the provided `argv` (nopt's
|
||||
// remain arguments after `--`) and `env` (should be process.env)
|
||||
//
|
||||
// It is inspired and based off Isaac's work on npm.
|
||||
|
||||
module.exports = function (argv, env) {
|
||||
var opts = {};
|
||||
|
||||
// w is the words number, based on the cursor position
|
||||
opts.w = +env.COMP_CWORD;
|
||||
|
||||
// words is the escaped sequence of words following `bower`
|
||||
opts.words = argv.map(function (word) {
|
||||
return word.charAt(0) === "\"" ?
|
||||
word.replace(/^"|"$/g, "") :
|
||||
word.replace(/\\ /g, " ");
|
||||
});
|
||||
|
||||
// word is a shortcut to the last word in the line
|
||||
opts.word = opts.words[opts.w - 1];
|
||||
|
||||
// line is the sequence of tab completed words.
|
||||
opts.line = env.COMP_LINE;
|
||||
|
||||
// point is the cursor position in the line
|
||||
opts.point = +env.COMP_POINT;
|
||||
|
||||
// length is the whole line's length.
|
||||
opts.length = opts.line.length;
|
||||
|
||||
// partialLine is the line ignoring the sequence of characters after
|
||||
// cursor position, ie. tabbing at: bower install j|qu
|
||||
// gives back a partialLine: bower install j
|
||||
opts.partialLine = opts.line.slice(0, opts.point);
|
||||
|
||||
// partialWords is only returning the words based on cursor position,
|
||||
// ie tabbing at: bower install ze|pto backbone
|
||||
// gives back a partialWords array: ['install', 'zepto']
|
||||
opts.partialWords = opts.words.slice(0, opts.w);
|
||||
|
||||
return opts;
|
||||
};
|
||||
|
||||
module.exports.log = function (arr, opts, prefix) {
|
||||
arr = Array.isArray(arr) ? arr : [arr];
|
||||
arr.filter(module.exports.abbrev(opts)).forEach(function (word) {
|
||||
console.log(word);
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.abbrev = function abbrev(opts) {
|
||||
var word = opts.word.replace(/\./g, '\\.');
|
||||
return function (it) {
|
||||
return new RegExp('^' + word).test(it);
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user