forked from rgrove/selleck
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
World, meet Selleck. He has a fantastic mustache.
- Loading branch information
0 parents
commit 5a0fe9a
Showing
10 changed files
with
595 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
._* | ||
.git/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
#!/usr/bin/env node | ||
var fs = require('fs'), | ||
path = require('path'), | ||
|
||
selleck = require('selleck'), | ||
fileutils = require('selleck/fileutils'), | ||
util = require('selleck/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 | ||
docPaths = [], | ||
log = selleck.log, | ||
|
||
options = { | ||
out : './docs', | ||
template: selleck.defaultTemplate | ||
}, | ||
|
||
templateLayouts, | ||
templatePages, | ||
templatePartials, | ||
|
||
help = [ | ||
"Generates YUI user documentation, mustache-style.", | ||
"", | ||
"Usage: selleck [options] [path]", | ||
"", | ||
"Options:", | ||
" -o, --out <path> Write generated files to this output path.", | ||
" -t, --template <path> Use the specified template instead of the default.", | ||
" -h, --help Display this help message." | ||
].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); | ||
} | ||
|
||
switch (arg) { | ||
case '--help': | ||
case '-h': | ||
console.log(help); | ||
process.exit(); | ||
break; | ||
|
||
case '--output': | ||
case '--out': | ||
case '-o': | ||
options.out = argv.shift(); | ||
break; | ||
|
||
case '--template': | ||
case '-t': | ||
options.template = argv.shift(); | ||
break; | ||
} | ||
} | ||
|
||
// First leftover arg (if any) is the root path; if no arg, then use the current | ||
// directory. | ||
options.rootPath = args.shift() || process.cwd(); | ||
|
||
// Traverse the root path and look for directories containing a doc.json file | ||
// and an index.mustache file. These will be treated as documentation | ||
// directories and parsed. | ||
docPaths = selleck.findDocPaths(options.rootPath); | ||
|
||
if (!docPaths.length) { | ||
log('No documentation found! Nothing to do.', 'warning'); | ||
process.exit(); | ||
} | ||
|
||
// Load global template partials and pages. | ||
templateLayouts = selleck.getLayouts(options.template); | ||
templatePartials = selleck.getPartials(options.template); | ||
templatePages = selleck.getPages(options.template); | ||
|
||
// Create the output directory if it doesn't exist. | ||
if (!fileutils.isDirectory(options.out)) { | ||
fs.mkdirSync(options.out, 0755); | ||
} | ||
|
||
docPaths.forEach(function(docPath) { | ||
var assetsPath, html, meta, outPath, pageName, pages, partials, view; | ||
|
||
// Get metadata from doc.json. This will be merged into the view instance, | ||
// overriding instance properties if there are conflicts. | ||
meta = selleck.getMetadata(docPath); | ||
|
||
// Validate the component name. | ||
if (!meta.name) { | ||
log('Component name not specified; skipping: ' + docPath, 'error'); | ||
return; | ||
} | ||
|
||
if (meta.name.search(/^[\w\-]+$/) === -1) { | ||
log('Invalid component name: ' + meta.name + '; skipping: ' + docPath, 'error'); | ||
} | ||
|
||
log('Generating docs for ' + meta.name, 'info'); | ||
|
||
outPath = path.join(options.out, meta.name); | ||
|
||
// Create an output directory for this component. | ||
if (!fileutils.isDirectory(outPath)) { | ||
fs.mkdirSync(outPath, 0755); | ||
} | ||
|
||
// Create a pages object containing mustache content for each .mustache | ||
// page in the doc dir root. | ||
pages = selleck.getPages(docPath); | ||
|
||
// Create a partials object containing both global template-wide | ||
// partials and component-specific partials. Allow component partials to | ||
// override template-wide partials. | ||
partials = util.merge(templatePartials, selleck.getPartials(docPath)); | ||
|
||
// Create a view instance, which will be exposed to the page templates and | ||
// will be the source for all their data. | ||
view = new selleck.ComponentView(meta, {layout: templateLayouts.component}); | ||
|
||
// Render each page to HTML and write it to the output directory. | ||
for (pageName in pages) { | ||
html = []; | ||
|
||
// Render using a custom "send" function, since Mustache's default one | ||
// strips out empty lines, and that's dumb. | ||
selleck.render(pages[pageName], view, partials, function (line) { | ||
html.push(line); | ||
}); | ||
|
||
fs.writeFileSync(path.join(outPath, pageName + '.html'), html.join('\n')); | ||
} | ||
|
||
// If the component directory contains an "assets" subdirectory, copy it to | ||
// the output directory. | ||
assetsPath = path.join(docPath, 'assets'); | ||
|
||
if (fileutils.isDirectory(assetsPath)) { | ||
fileutils.copyPath(assetsPath, path.join(outPath, 'assets'), function () {}); | ||
} | ||
}); | ||
|
||
log('Done! Docs were generated at: ' + options.out, 'info'); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
var fs = require('fs'), | ||
path = require('path'), | ||
mustache = require('mustache'), | ||
fileutils = require('./lib/fileutils'), | ||
util = require('./lib/util'); // Selleck util, not Node util. | ||
|
||
exports.View = require('./lib/view'); | ||
exports.ComponentView = require('./lib/view/component'); | ||
|
||
/** | ||
@property defaultTemplate | ||
@type {String} | ||
**/ | ||
exports.defaultTemplate = path.join(__dirname, 'template', 'default'); | ||
|
||
/** | ||
@method findDocPaths | ||
**/ | ||
function findDocPaths(root) { | ||
var paths = []; | ||
|
||
if (!fileutils.isDirectory(root)) { | ||
log('Not a directory: ' + root, 'warning'); | ||
} | ||
|
||
fs.readdirSync(root).forEach(function (filename) { | ||
var filePath = path.join(root, filename); | ||
|
||
// Skip hidden files and directories. | ||
if (filename.indexOf('.') === 0) { return; } | ||
|
||
if (fileutils.isDirectory(filePath)) { | ||
if (isDocDirectory(filePath)) { | ||
paths.push(filePath); | ||
} else { | ||
findDocPaths(filePath).forEach(function (p) { | ||
paths.push(p); | ||
}); | ||
} | ||
} | ||
}); | ||
|
||
return paths; | ||
} | ||
exports.findDocPaths = findDocPaths; | ||
|
||
/** | ||
@method getMetadata | ||
**/ | ||
function getMetadata(dir) { | ||
var filePath = path.join(dir, 'doc.json'), | ||
json, meta; | ||
|
||
if (fileutils.isFile(filePath)) { | ||
json = fs.readFileSync(filePath, 'utf8'); | ||
|
||
try { | ||
meta = JSON.parse(json); | ||
} catch (ex) { | ||
log(filePath + ': JSON error: ' + ex.message, 'error'); | ||
} | ||
} | ||
|
||
return meta || {}; | ||
} | ||
exports.getMetadata = getMetadata; | ||
|
||
/** | ||
Like `getPages()`, but returns only the files under the `layout/` subdirectory | ||
of the specified _dir_. | ||
@method getLayouts | ||
@param {String} dir Directory path. | ||
@return {Object} Mapping of layout names to layout content. | ||
**/ | ||
function getLayouts(dir) { | ||
return getPages(path.join(dir, 'layout')); | ||
} | ||
exports.getLayouts = getLayouts; | ||
|
||
/** | ||
Loads pages (files with a .mustache extension) in the specified directory and | ||
returns an object containing a mapping of page names (the part of the filename) | ||
preceding the .mustache extension) to page content. | ||
@method getPages | ||
@param {String} dir Directory path. | ||
@return {Object} Mapping of page names to page content. | ||
**/ | ||
function getPages(dir) { | ||
var pages = {}; | ||
|
||
if (!fileutils.isDirectory(dir)) { return pages; } | ||
|
||
fs.readdirSync(dir).forEach(function (filename) { | ||
var filePath = path.join(dir, filename); | ||
|
||
if (path.extname(filename) === '.mustache' && fileutils.isFile(filePath)) { | ||
pages[path.basename(filename, '.mustache')] = fs.readFileSync(filePath, 'utf8'); | ||
} | ||
}); | ||
|
||
return pages; | ||
} | ||
exports.getPages = getPages; | ||
|
||
/** | ||
Like `getPages()`, but returns only the files under the `partial/` subdirectory | ||
of the specified _dir_. | ||
@method getPartials | ||
@param {String} dir Directory path. | ||
@return {Object} Mapping of partial names to partial content. | ||
**/ | ||
function getPartials(dir) { | ||
return getPages(path.join(dir, 'partial')); | ||
} | ||
exports.getPartials = getPartials; | ||
|
||
/** | ||
@method isDocDirectory | ||
**/ | ||
function isDocDirectory(dir) { | ||
var docStats, | ||
indexStats; | ||
|
||
try { | ||
docStats = fs.statSync(path.join(dir, 'doc.json')); | ||
indexStats = fs.statSync(path.join(dir, 'index.mustache')); | ||
} catch (ex) { | ||
return false; | ||
} | ||
|
||
return docStats.isFile() && indexStats.isFile(); | ||
} | ||
exports.isDocDirectory = isDocDirectory; | ||
|
||
/** | ||
@method log | ||
**/ | ||
function log(message, level) { | ||
console.log('[' + (level || 'info') + '] ' + message); | ||
} | ||
exports.log = log; | ||
|
||
/** | ||
@method render | ||
**/ | ||
function render(content, view, partials, sendFunction) { | ||
if (view.layout) { | ||
return mustache.to_html(view.layout, view, | ||
util.merge(partials || {}, {layout_content: content}), | ||
sendFunction); | ||
} else { | ||
return mustache.to_html(content, view, partials || {}, sendFunction); | ||
} | ||
} | ||
exports.render = render; |
Oops, something went wrong.