Skip to content

Commit

Permalink
TomDocify
Browse files Browse the repository at this point in the history
  • Loading branch information
mklabs committed Apr 22, 2016
1 parent 305b0b4 commit 9587418
Show file tree
Hide file tree
Showing 5 changed files with 158 additions and 53 deletions.
1 change: 0 additions & 1 deletion lib/cli.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

const debug = require('debug')('tabtab');
const minimist = require('minimist');
const npmlog = require('npmlog');
Expand Down
30 changes: 25 additions & 5 deletions lib/commands/index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
// const debug = require('debug')('tabtab:commands');
const fs = require('fs');
const path = require('path');
const join = path.join;
const debug = require('../debug');
const debug = require('../debug')('tabtab:commands')

const Complete = require('../complete');

Expand All @@ -11,39 +10,60 @@ const {
existsSync: exists
} = require('fs');

// Public: Commands class that defines the various command available with tabtab(1)
//
// Examples
//
// // TODO: document all methods
//
// var commands = new Commands({});
// commands.install();
class Commands {


get allowed() {
return ['install', 'uninstall', 'list', 'search', 'add', 'rm', 'completion'];
}

// Constructor
// Public: Creates a new command instance. this.complete is an instance of
// Complete.
constructor(options) {
this.options = options || {};
this.complete = new Complete(this.options);
}

// Commands

// Fow now, just output to the console
// Public: Fow now, just output to the console
install(options) {
options = options || {};
var script = this.complete.script(this.name, this.name || 'tabtab');
console.log(script);
}

// Public: Delegates to this.handle
completion(options) {
options = options || this.options;
return this.complete.handle(options);
}

// Public: to be implemented.
uninstall() {}

// Public: to be implemented.
search() {}

// Public: to be implemented.
list() {}

// Public: to be implemented.
add() {}

// Public: to be implemented.
rm() {}

// Public: --help output
//
// Returns the String output
help() {
return `
$ tabtab <command> [options]
Expand Down
118 changes: 80 additions & 38 deletions lib/complete.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,48 @@
// const debug = require('debug')('tabtab:complete');
const fs = require('fs');
const path = require('path');
const read = fs.readFileSync;
const exists = fs.existsSync;
const join = path.join;
const debug = require('./debug');

const debug = require('./debug')('tabtab:complete')
const { EventEmitter } = require('events');

// Public: Complete class. This is the main API to interract with the
// completion system and extends EventEmitter.
//
// Examples
//
// var complete = new Complete({
// name: 'binary-name'
// });
//
// complete.on('list', function(data, done) {
// return done(null, ['completion', 'result', 'here']);
// });
class Complete extends EventEmitter {

// Defaults
// Public: Options defaults.
//
// Returns the binary name being completed. Uses process.title if not the
// default "node" value, or attempt to determine the package name from
// package.json file.
get defaults() {
return {
name: process.title !== 'node' ? process.title : ''
};
}

// Constructor: Extends EventEmitter, accepts options hash with:
// Public: Complete constructor, accepts options hash with
//
// options - Accepts options hash (default: {})
//
// Examples
//
// - name - the package beeing completed name, defaults to process.title (if
// not node default) or will attempt to determine parent's package.json
// location and extract the name from it
// new Complete({
// // the String package name being completed, defaults to process.title
// // (if not node default) or will attempt to determine parent's
// // package.json location and extract the name from it.
// name: 'foobar'
// });
constructor(options) {
super();
this.options = options || this.defaults;
Expand All @@ -33,11 +54,13 @@ class Complete extends EventEmitter {
this.handle();
}


// Main handler
//
// Responsible for the `completion` command, both in normal and "plumbing"
// mode.
// Public: Takes care of all the completion plumbing mechanism.
//
// It checks the environment to determine if we act in plumbing mode or not,
// to parse COMP args and emit the appropriated events to gather completion
// results.
//
// options - options hash to pass to self#parseEnv
handle(options) {
options = options || this.options;
var env = this.parseEnv(this.options);
Expand Down Expand Up @@ -71,20 +94,44 @@ class Complete extends EventEmitter {
});
}

// Completions callback
// Public: Completions handlers
//
// completions handlers will call back this function with an Array of
// completion items.
// will call back this function with an Array of completion items.
//
// err - Error object
// completions - The Array of String completion results to write to stdout
recv(err, completions) {
debug('Completion results', err, completions);
if (err) return this.emit('error', err);
completions = Array.isArray(completions) ? completions : [completions];
console.log(completions.join(' '));
}

// Main utility to extract information from command line arguments and
// Public: Main utility to extract information from command line arguments and
// Environment variables, namely COMP args in "plumbing" mode.
//
// options - The options hash as parsed by minimist, plus an env property
// representing user environment (default: { env: process.env })
// :_ - The arguments Array parsed by minimist (positional arguments)
// :env - The environment Object that holds COMP args (default: process.env)
//
// Examples
//
// var env = complete.parseEnv();
// // env:
// // args the positional arguments used
// // complete A Boolean indicating whether we act in "plumbing mode" or not
// // words The Number of words in the completed line
// // point A Number indicating cursor position
// // line The String input line
// // partial The String part of line preceding cursor position
// // last The last String word of the line
// // lastPartial The last word String of partial
// // prev The String word preceding last
//
// Returns the data env object.
parseEnv(options) {
options = options || {};
options = Object.assign({}, options, this.options);
var args = options._ || process.argv.slice(2);
var env = options.env || process.env;
Expand Down Expand Up @@ -112,51 +159,41 @@ class Complete extends EventEmitter {

return {
args: args,

// whether we act in "plumbing mode" or not
complete: complete,

// number of words
words: cword,

// cursor position
point: point,

// input line
line: line,

// part of line preceding point
partial: partial,

// last word of the line
last: last,

// last word of partial
lastPartial: lastPartial,

// word preceding last
prev: prev || ''
};
}

// Script templating helper
// Public: Script templating helper
//
// Outputs npm's completion script with pkgname and completer placeholder
// replaced.
//
// name - The String package/binary name
// complete - The String completer name, usualy the same as name above. Can
// differ to delegate the completion behavior to another command.
//
// Returns the script content with placeholders replaced
script(name, completer) {
return read(join(__dirname, '../scripts/completion.sh'), 'utf8')
.replace(/\{pkgname\}/g, name)
.replace(/{completer}/g, completer);
}

// Recursively walk up the `module.parent` chain to find original file.
// Public: Recursively walk up the `module.parent` chain to find original file.
findParent(module, last) {
if (!module.parent) return module;
return this.findParent(module.parent);
}

// Recursively walk up the directories, untill it finds the `file` provided,
// or reach the user $HOME dir.
// Public: Recursively walk up the directories, untill it finds the `file`
// provided, or reach the user $HOME dir.
findUp(file, dir) {
dir = path.resolve(dir || './');

Expand All @@ -166,8 +203,13 @@ class Complete extends EventEmitter {
return this.findUp(file, path.dirname(dir));
}

// When options.name is not defined, this gets called to attempt to determine
// Public: package name resolver.
//
// When options.name is not defined, this gets called to attempt to determine
// completer name.
//
// It'll attempt to follow the module chain and find the package.json file to
// determine the command name being completed.
resolveName() {
// `module` is special node builtin
var parent = this.findParent(module);
Expand Down
38 changes: 31 additions & 7 deletions lib/debug.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,36 @@
const fs = require('fs');
const path = require('path');
const util = require('util');
const util = require('util');
const dbg = require('debug');

let out = fs.createWriteStream(process.env.TABTAB_DEBUG || '/tmp/tabtab.log', {
flags: 'a'
});
process.env.TABTAB_DEBUG = process.env.TABTAB_DEBUG || '/tmp/tabtab.log';

module.exports = function debug() {
// console.log('write', util.format.apply(util, arguments));
out.write(util.format.apply(util, arguments) + '\n');
let out = process.env.TABTAB_DEBUG ?
fs.createWriteStream(process.env.TABTAB_DEBUG, { flags: 'a' }) :
null;

module.exports = log;

// Internal: Facade to debug module, which provides the exact same interface.
//
// The added benefit is with the TABTAB_DEBUG environment variable, which when
// defined, will write debug output to the specified filename.
//
// Usefull when debugging tab completion, as logs on stdout / stderr are either
// shallowed or used as tab completion results.
//
// namespace - The String namespace to use when TABTAB_DEBUG is not defined,
// delegates to debug module.
//
// Examples
//
// // Use with following command to redirect output to file
// // TABTAB_DEBUG="debug.log" tabtab ...
// debug('Foo');
function debug(namespace) {
var log = dbg(namespace);
return () => {
out && out.write(util.format.apply(util, arguments) + '\n');
out || log.apply(null, arguments);
}
}
24 changes: 22 additions & 2 deletions lib/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
const Complete = require('./complete');

module.exports = (options) => {
module.exports = tabtab;
complete.Commands = require('./commands');
complete.Complete = Complete;

// Public: Tabtab entry point.
//
// It provides the main API to the completion and plumbing system, in the form
// of an EventEmitter with the following events.
//
// options - The options hash as parsed by minimist (default: {})
//
// Examples
//
// // The binary name being completed.
// complete.on('name', function(data, done) {});
//
// // The very last word being completed, preceding the cursor tab position
// complete.on('list', function(data, done) {});
//
// Returns an instance of Complete.
function tabtab(options) {
return new Complete(options);
};
}

0 comments on commit 9587418

Please sign in to comment.