Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

executable file 377 lines (304 sloc) 11.615 kB
#!/usr/bin/env node
/*
Selleck
Copyright (c) 2011 Yahoo! Inc.
Licensed under the BSD License.
*/
var fs = require('fs'),
fsPath = require('path'),
selleck = require('selleck'),
fileutils = require('selleck/lib/fileutils'),
higgins = require('selleck/lib/higgins'),
util = require('selleck/lib/util'), // Selleck's util, not Node's util.
arg,
args = [], // leftover args after params are parsed out of argv
argv = process.argv.slice(2), // get rid of the leading 'node' arg
docs = {components: []},
log = selleck.log,
nextArg,
// Default values for command line options.
options = {
'out' : './build_docs',
'out-ext' : '.html',
'overrideMeta': {},
'theme' : selleck.defaultTheme
},
components = {},
project = {
layouts : {},
meta : {},
pages : {},
partials : {},
project : true,
validator : projectValidator
},
stack = new util.Stack(),
theme = {
layouts : {},
meta : {},
partials: {}
},
help = [
"Selleck generates YUI user documentation, Mustache-style.",
"",
"Usage: selleck [options] [input path]",
"",
"Common Options:",
" -m, --meta <filename> Augment and override project/component metadata with",
" data in the specified file.",
" -o, --out <path> Write generated files to this output path.",
" -p, --project <path> Use the specified project path instead of searching for",
" the project.",
" -s, --server [port] Run Selleck as a web server for real-time previews of",
" changes while editing docs.",
" -t, --theme <path> Use the specified theme instead of the default.",
" -h, --help Display this help message.",
" -v, --version Display version information.",
"",
"Advanced Options:",
" --dump-views Write a .json file alongside every generated page that",
" contains the exact view metadata (minus any helper",
" functions) that was used to generate that page.",
" --out-assets <path> Copy all assets to this output path instead of the",
" default. Component assets will be copied to",
" subdirectories named for the component.",
" --out-ext <extension> Use the specified file extension for generated pages",
" instead of the default (.html).",
" --out-meta <filename> Write combined project and component JSON metadata to",
" the specified filename.",
""
].join('\n');
// Process command line parameters.
while ((arg = argv.shift())) {
// If this isn't a parameter, push it onto the leftover args stack and move
// on.
if (arg.indexOf('-') !== 0) {
args.push(arg);
continue;
}
switch (arg) {
case '--dump-views':
options.dumpViews = true;
break;
case '--help':
case '-h':
process.stdout.write(help);
process.exit();
break;
case '--metadata':
case '--meta':
case '-m':
options.overrideMeta = JSON.parse(fs.readFileSync(argv.shift(), 'utf8'));
break;
case '--output':
case '--out':
case '-o':
options.out = argv.shift();
break;
case '--output-assets':
case '--out-assets':
options['out-assets'] = argv.shift();
break;
case '--output-extension':
case '--output-ext':
case '--out-extension':
case '--out-ext':
options['out-ext'] = argv.shift();
break;
case '--output-metadata':
case '--output-meta':
case '--out-metadata':
case '--out-meta':
options['out-meta'] = argv.shift();
break;
case '--project':
case '-p':
options.project = docs.project = {path: argv.shift()};
if (!selleck.isProjectDirectory(docs.project.path)) {
log('Invalid project path: ' + docs.project.path, 'error');
process.exit(1);
}
break;
case '--server':
case '--serve':
case '-s':
options.server = true;
if ((nextArg = argv.shift())) {
if (nextArg.search(/^\d+$/) === -1) {
// Not a port number; put the argument back on the argv array.
argv.unshift(nextArg);
} else {
options.serverPort = +nextArg;
}
}
break;
case '--theme':
case '-t':
options.theme = argv.shift();
break;
case '--version':
case '-v':
pkg = JSON.parse(fs.readFileSync(__dirname + '/../package.json', 'utf-8'));
version = [
"Selleck " + pkg.version,
pkg.copyright,
pkg.homepage,
""
].join('\n');
process.stdout.write(version);
process.exit();
break;
default:
log('Unrecognized parameter: ' + arg, 'error');
process.exit(1);
}
}
// First leftover arg (if any) is the root path; if no arg, then use the current
// directory.
options.rootPath = args.shift() || process.cwd();
options['out-assets'] || (options['out-assets'] = fsPath.join(options.out, 'assets'));
if (options.server) {
// If running as a server, start the server instead of continuing.
require('selleck/lib/server')(options);
} else {
// Load theme layouts and partials, and copy theme assets to the output dir,
// if there are any.
theme.layouts = selleck.getLayouts(options.theme);
theme.meta = selleck.getMetadata(options.theme, 'theme');
theme.partials = selleck.getPartials(options.theme);
// Traverse the root path and look for documentation directories.
docs = selleck.findDocs(options.rootPath, docs);
if (docs.project) {
log('Parsing project: ' + docs.project.path, 'info');
project.layouts = util.merge(theme.layouts, selleck.getLayouts(docs.project.path));
project.meta = util.merge(theme.meta, selleck.getMetadata(docs.project.path, 'project'));
project.partials = util.merge(theme.partials, selleck.getPartials(docs.project.path));
project.pages = selleck.getPages(docs.project.path);
}
log('Parsing components', 'info');
// Initial pass to load all component info.
docs.components.forEach(function(component, index) {
var path = component.path;
selleck.prepare(path, util.merge(options, project, {
component: true,
index : index,
path : path,
validator: componentValidator
}), stack.add(function (err, result) {
if (err) { throw err; }
if (result.meta.examples) {
result.meta.hasOwnExamples = result.meta.examples.some(function (example) {
return !example.inherited;
});
}
components[result.meta.name] = result;
}));
});
// Second pass to cross-link examples.
stack.done(function () {
util.each(components, function (component) {
if (!component.meta.examples) { return; }
component.meta.examples.forEach(function (example) {
example.componentName || (example.componentName = component.meta.name);
if (!example.modules) { return; }
example.modules.forEach(function (moduleName) {
var module = components[moduleName];
if (module && moduleName !== example.componentName
&& !example.inherited) {
module.meta.hasInheritedExamples = true;
module.meta.examples || (module.meta.examples = []);
module.meta.examples.push(util.merge(example, {
inherited: true
}));
}
});
});
});
project.meta.components = util.values(components).sort(function (a, b) {
var aName = a.meta.displayName.toLowerCase(),
bName = b.meta.displayName.toLowerCase();
return aName < bName ? -1 : (aName > bName ? 1 : 0);
});
generateProject();
});
function generateProject() {
selleck.createOutputDir(options.out);
selleck.createOutputDir(options['out-assets']);
selleck.copyAssets(fsPath.join(options.theme, 'assets'),
options['out-assets'], true, function (err) {
if (err) { throw err; }
});
if (docs.project) {
log('Generating project docs for ' + docs.project.path, 'info');
selleck.generate(docs.project.path, util.merge(options, project,
{skipLoad: true}), generateComponents);
} else {
generateComponents();
}
}
function generateComponents(err) {
if (err) { return finish(err); }
var toGenerate = util.size(components);
if (!toGenerate) { return finish(); }
// Generate component docs.
util.each(components, function(component) {
var path = component.path;
log('Generating component docs for ' + path, 'info');
selleck.generate(path, util.merge(options, component,
{skipLoad: true}), finishComponent);
});
function finishComponent(err) {
if (err) { return finish(err); }
toGenerate -= 1;
if (toGenerate === 0) {
finish();
}
}
}
function finish(err) {
if (err) { throw err; }
var meta;
if (options['out-meta']) {
log('Writing combined metadata to ' + options['out-meta'], 'info');
meta = {
components: {},
project : util.merge(project.meta, options.overrideMeta)
};
util.each(components, function (component) {
var data = util.merge(component.meta);
delete data.componentDefaults;
meta.components[component.meta.name] = data;
});
fs.writeFile(options['out-meta'], JSON.stringify(meta, null, 2), 'utf8', function (err) {
if (err) {
throw err;
} else {
reallyFinish();
}
});
} else {
reallyFinish();
}
function reallyFinish() {
log('Done! Docs were written to: ' + options.out, 'info');
}
}
}
// -- Functions ----------------------------------------------------------------
function componentValidator(data, docPath) {
// Validate the component name.
if (!data.meta.name) {
log('Component name not specified; skipping: ' + docPath, 'error');
return false;
}
if (data.meta.name.search(/^[\w\-]+$/) === -1) {
log('Invalid component name: ' + data.meta.name + '; skipping: ' + docPath, 'error');
return false;
}
}
function projectValidator(data, docPath) {
if (!data.meta.projectName) {
log('Project name not specified: ' + docPath, 'error');
process.exit(1);
}
}
Jump to Line
Something went wrong with that request. Please try again.