options.js |
|
---|---|
OptionsOptions provides the CLI interface for Narrativ. |
var File = require('./file').File,
Directory = require('./directory').Directory,
Parser = require('./parser').Parser,
Destination = require('./destination').Destination,
plate = require('plate'),
_path = require('path'),
join = _path.join,
basename = _path.basename,
fs = require('fs');
var rpad = function(what, len) { |
Make sure that the string is at least |
var add = len - what.length;
while(add > 0) {
what += ' ';
--add;
}
return what;
};
var option = function() { |
Defines a flag or positional argument, a callback for applying the result to the options object, and help text. |
var args = [].slice.call(arguments),
callback = args[args.length-1], |
Grab |
flags = args.slice(0, args.length-1); |
Store the returned function here so we can modify attributes of it later, dynamically. |
var retval = !flags.length ? |
If there aren't any flags defined
return a positional callback, taking |
function(opts, head, tail) {
callback(opts, head, tail); |
Returning one means that we've successfully popped an argument off the list. |
return 1;
} :
function(opts, head, tail) { |
If |
if(flags.indexOf(head) !== -1) { |
splice from 0 to the number of non-options arguments that the
|
callback.apply({}, [opts].concat(tail.splice(0, callback.length-1)));
return 1;
}
return 0;
}; |
the help function assigns |
retval.help = function(h) {
retval.help_text = '\t'+rpad(flags.join(', '), 40) + h;
return retval;
};
return retval;
}; |
Our list of flags and positional options. |
var flag_options = [
option('-O', '--target-dir', function(opts, target_dir) {
opts.target_dir = target_dir;
}).help('Set the output directory'),
option('-u', '--url', function(opts, url) {
opts.base_url = url;
}).help('Base URL for serving stylesheets and javascript.'),
option('--css', function(opts, stylesheet) {
opts.css_file = fs.readFileSync(stylesheet);
opts.stylesheet = basename(stylesheet);
}).help('Use a custom stylesheet'),
option('-T', '--template', function(opts, template) { |
Bias Alert: I like using plate, partially because I wrote it and partially because it's got nice support for asynchronous template rendering. |
opts.file_template = new plate.Template(fs.readFileSync(template).toString());
}).help('Django-style template file to use when generating docs.'),
option('-X', '--extensions', function(opts, extensions) {
opts.extensions = JSON.parse(fs.readFileSync(extensions));
}).help('Extension JSON file, providing support for other languages.'),
option('-h', '--help', '-?', '?', function(opts) {
opts.usage();
opts.error();
}),
option('-I', '--ignore-dirs', function(opts, dir) {
opts.ignore_dirs = dir;
}).help('Portion of target directory path to ignore when generating docs.'),
option(function(opts, target) {
var retval;
opts.roots = opts.roots || []; |
this will throw an Error if the target does not exist on the file system. the calling loop will catch it and skip this argument when this happens. |
if(fs.statSync(target).isFile()) {
retval = function(parser) {
return new File(target, parser);
};
retval.target = dirname(target);
} else {
retval = function(parser) {
return new Directory(target, parser);
};
retval.target = target;
}
opts.roots.push(retval);
}).help('Targets')
]; |
OptionsThis class provides useful shortcuts for outputing information, as well as stores the incoming flags and positional argument results from the command line. |
var Options = function(args) { |
If we've got no options, print the usage and then exit. |
if(!args.length) {
this.usage();
this.error();
} |
while there are args we'll continue to search for options to match them. |
while(args.length) {
for(var i = 0, len = flag_options.length; i < len && args.length; ++i) {
var head = args.shift(),
tail = args;
var ret = flag_options[i](this, head, tail);
if(!ret) { |
if we didn't match, push the head back onto the arg stack. |
args.unshift(head);
} else { |
otherwise, break and retry the options from the top. |
break;
}
}
} |
Set default value for |
!this.css_file &&
(this.css_file = fs.readFileSync(join(__dirname, '../resources/base.css')),
(this.stylesheet = 'base.css')); |
Set the default value for the |
!this.file_template &&
(this.file_template = new plate.Template(fs.readFileSync(join(__dirname, '../resources/templates/default.html')).toString())); |
Set the default extensions (can you tell I like the |
!this.extensions &&
(this.extensions = JSON.parse(fs.readFileSync(join(__dirname, '../resources/extensions/base.json')).toString()));
this.ignore_dirs === undefined &&
(this.ignore_dirs = ''); |
If there's no target dir, just output into docs in the current directory. |
!this.target_dir &&
(this.target_dir = join(process.cwd(), 'docs')); |
Set the default base url -- this is how external media (like styles) will be referenced. |
this.base_url === undefined &&
(this.base_url = ''); |
if our target dir isn't absolute, make it absolute. |
if(this.target_dir[0] !== '/') {
this.target_dir = join(process.cwd(), this.target_dir);
} |
a flag that doesn't have an option yet -- the idea being that we occasionally won't want to recurse into subdirectories. |
this.recurse = true; |
set |
var self = this;
self.files = [];
File.prototype.register = function() {
self.files.push(this);
}; |
if we've got roots -- directories or files that need to be compiled -- create docs for each of them. |
this.roots &&
this.roots.forEach(function(fn) {
var destination = new Destination(fn.target, this.target_dir, this.ignore_dirs);
var parser = new Parser(destination, this.extensions);
fn(parser).compile(this, destination);
destination.write_media(this);
}, this);
};
Options.prototype.visit = function(what) { |
Prints a notification whenever a file or directory is visited by |
console.log('compiling '+what.path+'...');
};
Options.prototype.log = function() { |
Dummy log wrapper. |
console.log.apply(console, arguments);
};
Options.prototype.error = function() { |
Error spits out the arguments and then closes the process. |
console.error.apply(console, arguments);
process.exit(1);
};
Options.parse = function() {
var args = process.ARGV.slice(); |
skip |
if(args[0] === 'node') {
args.shift();
} |
do the same with |
if(args[0].indexOf('narrativ') !== -1) {
args.shift();
} |
make sure that we don't get args like |
args = args.filter(function(arg) {
return arg !== '=';
}).map(function(arg) {
if(arg[arg.length-1] === '=') {
return arg.slice(0,-1);
}
return arg;
});
return new Options(args);
}; |
Print the usage, and print any information the flags have about their usage, and exit. |
Options.prototype.usage = function() {
console.log("Usage: narrativ [options] [list of directories or files to generate documentation from]");
flag_options.forEach(function(flag) {
console.log(flag.help_text);
});
};
exports.Options = Options;
|