Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Packaging polish.

  • Loading branch information...
commit 85904556cf1e95c8dd2cf6f1065bb58cfa5d103e 1 parent 9dfb9cf
@rgrove authored
View
6 .npmignore
@@ -1,2 +1,6 @@
-._*
.git/
+contrib/
+node_modules/
+tmp/
+._*
+.DS_Store
View
16 bin/selleck
@@ -64,13 +64,6 @@ var fs = require('fs'),
" --out-meta <filename> Write combined project and component JSON metadata to",
" the specified filename.",
""
- ].join('\n'),
-
- version = [
- "Selleck 0.1.3",
- "Copyright (c) 2011 Yahoo! Inc. All rights reserved.",
- "https://github.com/rgrove/selleck/",
- ""
].join('\n');
// Process command line parameters.
@@ -156,6 +149,15 @@ while ((arg = argv.shift())) {
case '--version':
case '-v':
+ pkg = JSON.parse(fs.readFileSync(__dirname + '/../package.json', 'utf-8'));
+
+ version = [
+ "Selleck " + pkg.version,
+ pkg.copyright,
+ pkg.homepage,
+ ""
+ ].join('\n');
+
// See rant above re. forcing Node to flush stdout before exiting.
process.stdout.write(version);
while (!process.stdout.flush()) {};
View
1  docs/html/assets/css/main.css
@@ -114,6 +114,7 @@ table code, table kbd, table samp,
pre.code, pre.terminal {
overflow-x: auto;
+ *overflow-x: scroll;
padding: 0.3em 0.6em;
}
View
26 docs/html/index.html
@@ -3,17 +3,20 @@
<head>
<meta charset="utf-8">
<title>Selleck User Guide</title>
- <link rel="stylesheet" href="http://yui.yahooapis.com/3.3.0/build/cssgrids/grids-min.css">
+ <link rel="stylesheet" href="http:&#x2F;&#x2F;yui.yahooapis.com&#x2F;3.3.0&#x2F;build&#x2F;cssgrids&#x2F;grids-min.css">
<link rel="stylesheet" href="assets/css/main.css">
<link rel="stylesheet" href="assets/vendor/prettify/prettify-min.css">
- <script src="http://yui.yahooapis.com/combo?3.3.0/build/yui/yui-min.js&amp;3.3.0/build/loader/loader-min.js"></script>
+ <script src="http:&#x2F;&#x2F;yui.yahooapis.com&#x2F;combo?3.3.0&#x2F;build&#x2F;yui&#x2F;yui-min.js&amp;3.3.0&#x2F;build&#x2F;loader&#x2F;loader-min.js"></script>
</head>
<body>
<div id="doc">
<h1>Selleck User Guide</h1>
- <a href="#toc" class="jump">Jump to Table of Contents</a>
+
+ <a href="#toc" class="jump">Jump to Table of Contents</a>
+
+
<div class="yui3-g">
<div id="main" class="yui3-u">
<div class="content"><div class="intro">
@@ -34,14 +37,15 @@ <h2 id="installation">Installation</h2>
Install Selleck using npm:
</p>
-<pre class="terminal"><span class="noselect">$ </span>npm install selleck</pre>
+<pre class="terminal"><span class="noselect">$ </span>npm install selleck -g</pre>
<p>
-Or just clone the <a href="https://github.com/rgrove/selleck/">GitHub repo</a>:
+Or clone the <a href="https://github.com/rgrove/selleck/">GitHub repo</a> to install the latest development version:
</p>
-<pre class="terminal"><span class="noselect">$ </span>git clone git:&#x2F;&#x2F;github.com&#x2F;rgrove&#x2F;selleck.git</pre>
+<pre class="terminal"><span class="noselect">$ </span>git clone git:&#x2F;&#x2F;github.com&#x2F;rgrove&#x2F;selleck.git
+<span class="noselect">$ </span>cd selleck &amp;&amp; npm install -g</pre>
<h2 id="overview">Overview</h2>
@@ -110,7 +114,7 @@ <h5 id="componentjson-sample"><code>component.json</code> sample</h5>
&quot;description&quot;: &quot;Provides automatic input completion or suggestions for text input fields and textareas.&quot;,
&quot;author&quot; : &quot;rgrove&quot;,
- &quot;tags&quot;: [&quot;widget&quot;, &quot;beta&quot;, &quot;autocomplete&quot;],
+ &quot;tags&quot;: [&quot;widget&quot;, &quot;autocomplete&quot;],
&quot;use&quot; : [&quot;autocomplete&quot;]
}</pre>
@@ -420,7 +424,8 @@ <h2 id="license">License</h2>
</div>
<div id="sidebar" class="yui3-u">
- <div id="toc" class="sidebox">
+
+ <div id="toc" class="sidebox">
<div class="hd">
<h2 class="no-toc">Table of Contents</h2>
</div>
@@ -484,7 +489,10 @@ <h2 class="no-toc">Table of Contents</h2>
</ul>
</div>
</div>
- </div>
+
+
+
+ </div>
</div>
</div>
View
7 docs/raw/index.mustache
@@ -17,15 +17,16 @@ Install Selleck using npm:
</p>
```terminal
-$ npm install selleck
+$ npm install selleck -g
```
<p>
-Or just clone the <a href="https://github.com/rgrove/selleck/">GitHub repo</a>:
+Or clone the <a href="https://github.com/rgrove/selleck/">GitHub repo</a> to install the latest development version:
</p>
```terminal
$ git clone git://github.com/rgrove/selleck.git
+$ cd selleck && npm install -g
```
<h2>Overview</h2>
@@ -96,7 +97,7 @@ Project metadata files must contain a `projectName` property, and component meta
"description": "Provides automatic input completion or suggestions for text input fields and textareas.",
"author" : "rgrove",
- "tags": ["widget", "beta", "autocomplete"],
+ "tags": ["widget", "autocomplete"],
"use" : ["autocomplete"]
}
```
View
448 index.js
@@ -1,447 +1 @@
-/*
-Selleck
-Copyright (c) 2011 Yahoo! Inc.
-Licensed under the BSD License.
-*/
-
-/**
-@module selleck
-**/
-
-var fs = require('fs'),
- path = require('path'),
- Handlebars = require('./support/handlebars/handlebars').Handlebars,
-
- fileutils = require('./lib/fileutils'),
- util = require('./lib/util'), // Selleck's util, not Node's util.
-
- ComponentView = exports.ComponentView = require('./lib/view/component'),
- Higgins = exports.Higgins = require('./lib/higgins'),
- View = exports.View = require('./lib/view');
-
-// -- Public Properties --------------------------------------------------------
-
-/**
-Path to the default theme directory.
-
-@property defaultTheme
-@type {String}
-**/
-exports.defaultTheme = path.join(__dirname, 'themes', 'default');
-
-// -- Public Functions ---------------------------------------------------------
-
-/**
-Copies static assets recursively from one directory to another.
-
-@method copyAssets
-@param {String} from Directory to copy from.
-@param {String} to Directory to copy to.
-@param {bool} [deleteFirst=false] If `true` and _to_ already exists, it (and all
- its contents) will be deleted first.
-@param {callback}
- @param {Error} callback.err
-**/
-function copyAssets() {
- var args = Array.prototype.slice.call(arguments),
- callback = args.pop(),
- from = args.shift(),
- to = args.shift(),
- deleteFirst = args.shift();
-
- if (fileutils.isDirectory(from)) {
- if (deleteFirst && fileutils.isDirectory(to)) {
- fileutils.deletePath(to);
- }
-
- fileutils.copyPath(from, to, true, callback);
- } else {
- callback();
- }
-}
-exports.copyAssets = copyAssets;
-
-/**
-Creates the specified output directory if it doesn't already exist.
-
-@method createOutputDir
-@param {String} outDir Output directory.
-**/
-function createOutputDir(outDir) {
- var stats = fileutils.lstatSync(outDir);
-
- if (stats) {
- if (!stats.isDirectory()) {
- throw new Error('Output path already exists and is not a directory: ' + outDir);
- }
- } else {
- // TODO: mkdir -p
- fs.mkdirSync(outDir, 0755);
- }
-}
-exports.createOutputDir = createOutputDir;
-
-/**
-@method findDocs
-@return {Object}
-**/
-function findDocs(dir, docs) {
- docs || (docs = {components: []});
-
- if (!fileutils.isDirectory(dir)) {
- log('Not a directory: ' + dir, 'error');
- return docs;
- }
-
- if (isComponentDirectory(dir)) {
- docs.components.push({path: dir});
- } else if (isProjectDirectory(dir)) {
- if (docs.project) {
- log('Multiple projects found; ignoring ' + dir, 'warn');
- } else {
- docs.project = {path: dir};
- }
- } else {
- fs.readdirSync(dir).forEach(function (filename) {
- var filePath = path.join(dir, filename);
-
- // Skip hidden files and directories.
- if (filename.indexOf('.') === 0) { return; }
-
- if (fileutils.isDirectory(filePath)) {
- findDocs(filePath, docs);
- }
- });
- }
-
- return docs;
-}
-exports.findDocs = findDocs;
-
-/**
-@method generate
-@param {String} inDir Input directory containing docs and assets to generate.
-@param {Object} options Generation options.
-@param {callback}
- @param {Error} callback.err
-**/
-function generate(inDir, options, callback) {
- prepare(inDir, options, function (err, options) {
- if (err) { return callback(err); }
-
- var out = options.out,
- outAssets = options['out-assets'];
-
- // Append meta.name to the output path if this is a component.
- if (options.component) {
- out = path.join(out, options.meta.name);
- outAssets = path.join(outAssets, options.meta.name);
- }
-
- createOutputDir(out);
-
- copyAssets(path.join(inDir, 'assets'), outAssets, function (err) {
- if (err) { return callback(err); }
- writePages(out, options, callback);
- });
- });
-}
-exports.generate = generate;
-
-/**
-@method getMetadata
-**/
-function getMetadata(dir, type, throwOnError) {
- var filePath = path.join(dir, type + '.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');
- if (throwOnError) { throw ex; }
- }
- }
-
- 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, 'layouts'));
-}
-exports.getLayouts = getLayouts;
-
-/**
-Loads and returns the content of the specified page file.
-
-@method getPage
-@param {String} pagePath Path to a single `.mustache` page.
-@return {String|null} Page content, or `null` if not found.
-**/
-function getPage(pagePath) {
- if (!fileutils.isFile(pagePath)) { return null; }
- return fs.readFileSync(pagePath, 'utf8');
-}
-exports.getPage = getPage;
-
-/**
-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, 'partials'));
-}
-exports.getPartials = getPartials;
-
-/**
-Returns `true` if _dir_ appears to be a Selleck component directory.
-
-@method isComponentDirectory
-@param {String} dir Directory.
-@return {Boolean}
-**/
-function isComponentDirectory(dir) {
- var metaStats, indexStats;
-
- try {
- metaStats = fs.statSync(path.join(dir, 'component.json'));
- indexStats = fs.statSync(path.join(dir, 'index.mustache'));
- } catch (ex) {
- return false;
- }
-
- return metaStats.isFile() && indexStats.isFile();
-}
-exports.isComponentDirectory = isComponentDirectory;
-
-/**
-Returns `true` if _dir_ appears to be a Selleck project directory.
-
-@method isProjectDirectory
-@param {String} dir Directory.
-@return {Boolean}
-**/
-function isProjectDirectory(dir) {
- var metaStats, indexStats;
-
- try {
- metaStats = fs.statSync(path.join(dir, 'project.json'));
- indexStats = fs.statSync(path.join(dir, 'index.mustache'));
- } catch (ex) {
- return false;
- }
-
- return metaStats.isFile() && indexStats.isFile();
-}
-exports.isProjectDirectory = isProjectDirectory;
-
-/**
-@method log
-**/
-function log(message, level) {
- console.log('[' + (level || 'info') + '] ' + message);
-}
-exports.log = log;
-
-/**
-@method prepare
-@param {String} inDir
-@param {Object} options
-@param {callback}
- @param {Error} err
- @param {Object} options Merged options.
-**/
-function prepare(inDir, options, callback) {
- var compiled = {},
- meta = {},
- type = options.component ? 'component' : 'project';
-
- if (options && options.skipLoad) {
- // Skip loading layouts, metadata, pages, and partials and assume that
- // the caller has provided them if they want them.
- options = util.merge({
- layouts : {},
- meta : {},
- pages : {},
- partials : {},
- viewClass: options.component ? ComponentView : View
- }, options);
- } else {
-
- // Gather layouts, metadata, pages, and partials from the specified
- // input directory, then merge them into the provided options (if any).
- //
- // Gathered data will override provided data if there are conflicts, in
- // order to support a use case where global data are provided by the
- // caller and overridden by more specific component-level data gathered
- // from the input directory.
- //
- // The metadata inheritance chain looks like this:
- //
- // - override metadata specified via CLI (highest precedence)
- // - component metadata (if this is a component)
- // - project-level component default metadata (if specified and this is a component)
- // - theme-level component default metadata (if specified and this is a component)
- // - project metadata
- // - theme metadata (lowest precedence)
-
- if (options.component && options.meta.componentDefaults) {
- meta = options.meta.componentDefaults;
- }
-
- try {
- options = util.merge(
- {viewClass: options.component ? ComponentView : View},
- options || {},
- {
- layouts : getLayouts(inDir),
- meta : util.merge(meta, getMetadata(inDir, type, true)),
- partials: getPartials(inDir)
- }
- );
- } catch (ex) {
- return callback(ex);
- }
- }
-
- // Pages are never merged.
- options.pages = getPages(inDir);
-
- // Mix in the override metadata, if any. It takes precedence over everything
- // else.
- util.mix(options.meta, options.overrideMeta);
-
- // Set a default asset path if one isn't specified in the metadata.
- if (!options.meta.projectAssets) {
- options.meta.projectAssets = options.component ? '../assets' : 'assets';
- }
-
- if (!options.meta.componentAssets && options.component) {
- options.meta.componentAssets = '../assets/' + options.meta.name;
- }
-
- // If a validator function was provided, run it.
- if (options.validator && options.validator(options, inDir) === false) {
- return callback(new Error('Validation failed.')); // TODO: get the error from the validator itself
- }
-
- if (typeof options.meta.layout === 'undefined') {
- options.meta.layout = options.layouts[type] ? type : 'main';
- }
-
- callback(null, options);
-}
-exports.prepare = prepare;
-
-/**
-Renders the specified template source.
-
-@method render
-@param {String} source Template source to render.
-@param {Object} view View object.
-@param {String} [layout] Layout template to use.
-@param {Object} [partials] Partials object.
-@param {callback}
- @param {Error} err
- @param {String} html Rendered HTML.
-**/
-function render(source, view, layout, partials, callback) {
- var html, template;
-
- // Allow callback as third or fourth param.
- if (typeof partials === 'function') {
- callback = partials;
- partials = {};
- } else if (typeof layout === 'function') {
- callback = layout;
- layout = null;
- }
-
- try {
- if (layout) {
- template = Handlebars.compile(layout);
- partials = util.merge(partials || {}, {layout_content: source});
- } else {
- template = Handlebars.compile(source);
- }
-
- html = template(view, null, partials);
- } catch (ex) {
- return callback(ex);
- }
-
- callback(null, Higgins.render(html));
-}
-exports.render = render;
-
-/**
-@method writePages
-**/
-function writePages(outDir, options, callback) {
- var ext = options['out-ext'],
- toWrite = util.size(options.pages);
-
- if (!toWrite) { return callback(); }
-
- // Render each page to HTML and write it to the output directory.
- util.each(options.pages, function (source, name) {
- var view = new options.viewClass(options.meta, name),
- layout = options.layouts[view.layout];
-
- render(source, view, layout, options.partials, function (err, html) {
- if (err) { return callback(err); }
- fs.writeFile(path.join(outDir, name + ext), html, 'utf8', finish);
- });
- });
-
- function finish(err) {
- if (err) { return callback(err); }
-
- if (!(toWrite -= 1)) {
- callback();
- }
- }
-}
-exports.writePages = writePages;
+module.exports = require('./lib/selleck');
View
447 lib/selleck.js
@@ -0,0 +1,447 @@
+/*
+Selleck
+Copyright (c) 2011 Yahoo! Inc.
+Licensed under the BSD License.
+*/
+
+/**
+@module selleck
+**/
+
+var fs = require('fs'),
+ path = require('path'),
+ Handlebars = require('../support/handlebars/handlebars').Handlebars,
+
+ fileutils = require('./fileutils'),
+ util = require('./util'), // Selleck's util, not Node's util.
+
+ ComponentView = exports.ComponentView = require('./view/component'),
+ Higgins = exports.Higgins = require('./higgins'),
+ View = exports.View = require('./view');
+
+// -- Public Properties --------------------------------------------------------
+
+/**
+Path to the default theme directory.
+
+@property defaultTheme
+@type {String}
+**/
+exports.defaultTheme = path.join(__dirname, '..', 'themes', 'default');
+
+// -- Public Functions ---------------------------------------------------------
+
+/**
+Copies static assets recursively from one directory to another.
+
+@method copyAssets
+@param {String} from Directory to copy from.
+@param {String} to Directory to copy to.
+@param {bool} [deleteFirst=false] If `true` and _to_ already exists, it (and all
+ its contents) will be deleted first.
+@param {callback}
+ @param {Error} callback.err
+**/
+function copyAssets() {
+ var args = Array.prototype.slice.call(arguments),
+ callback = args.pop(),
+ from = args.shift(),
+ to = args.shift(),
+ deleteFirst = args.shift();
+
+ if (fileutils.isDirectory(from)) {
+ if (deleteFirst && fileutils.isDirectory(to)) {
+ fileutils.deletePath(to);
+ }
+
+ fileutils.copyPath(from, to, true, callback);
+ } else {
+ callback();
+ }
+}
+exports.copyAssets = copyAssets;
+
+/**
+Creates the specified output directory if it doesn't already exist.
+
+@method createOutputDir
+@param {String} outDir Output directory.
+**/
+function createOutputDir(outDir) {
+ var stats = fileutils.lstatSync(outDir);
+
+ if (stats) {
+ if (!stats.isDirectory()) {
+ throw new Error('Output path already exists and is not a directory: ' + outDir);
+ }
+ } else {
+ // TODO: mkdir -p
+ fs.mkdirSync(outDir, 0755);
+ }
+}
+exports.createOutputDir = createOutputDir;
+
+/**
+@method findDocs
+@return {Object}
+**/
+function findDocs(dir, docs) {
+ docs || (docs = {components: []});
+
+ if (!fileutils.isDirectory(dir)) {
+ log('Not a directory: ' + dir, 'error');
+ return docs;
+ }
+
+ if (isComponentDirectory(dir)) {
+ docs.components.push({path: dir});
+ } else if (isProjectDirectory(dir)) {
+ if (docs.project) {
+ log('Multiple projects found; ignoring ' + dir, 'warn');
+ } else {
+ docs.project = {path: dir};
+ }
+ } else {
+ fs.readdirSync(dir).forEach(function (filename) {
+ var filePath = path.join(dir, filename);
+
+ // Skip hidden files and directories.
+ if (filename.indexOf('.') === 0) { return; }
+
+ if (fileutils.isDirectory(filePath)) {
+ findDocs(filePath, docs);
+ }
+ });
+ }
+
+ return docs;
+}
+exports.findDocs = findDocs;
+
+/**
+@method generate
+@param {String} inDir Input directory containing docs and assets to generate.
+@param {Object} options Generation options.
+@param {callback}
+ @param {Error} callback.err
+**/
+function generate(inDir, options, callback) {
+ prepare(inDir, options, function (err, options) {
+ if (err) { return callback(err); }
+
+ var out = options.out,
+ outAssets = options['out-assets'];
+
+ // Append meta.name to the output path if this is a component.
+ if (options.component) {
+ out = path.join(out, options.meta.name);
+ outAssets = path.join(outAssets, options.meta.name);
+ }
+
+ createOutputDir(out);
+
+ copyAssets(path.join(inDir, 'assets'), outAssets, function (err) {
+ if (err) { return callback(err); }
+ writePages(out, options, callback);
+ });
+ });
+}
+exports.generate = generate;
+
+/**
+@method getMetadata
+**/
+function getMetadata(dir, type, throwOnError) {
+ var filePath = path.join(dir, type + '.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');
+ if (throwOnError) { throw ex; }
+ }
+ }
+
+ 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, 'layouts'));
+}
+exports.getLayouts = getLayouts;
+
+/**
+Loads and returns the content of the specified page file.
+
+@method getPage
+@param {String} pagePath Path to a single `.mustache` page.
+@return {String|null} Page content, or `null` if not found.
+**/
+function getPage(pagePath) {
+ if (!fileutils.isFile(pagePath)) { return null; }
+ return fs.readFileSync(pagePath, 'utf8');
+}
+exports.getPage = getPage;
+
+/**
+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, 'partials'));
+}
+exports.getPartials = getPartials;
+
+/**
+Returns `true` if _dir_ appears to be a Selleck component directory.
+
+@method isComponentDirectory
+@param {String} dir Directory.
+@return {Boolean}
+**/
+function isComponentDirectory(dir) {
+ var metaStats, indexStats;
+
+ try {
+ metaStats = fs.statSync(path.join(dir, 'component.json'));
+ indexStats = fs.statSync(path.join(dir, 'index.mustache'));
+ } catch (ex) {
+ return false;
+ }
+
+ return metaStats.isFile() && indexStats.isFile();
+}
+exports.isComponentDirectory = isComponentDirectory;
+
+/**
+Returns `true` if _dir_ appears to be a Selleck project directory.
+
+@method isProjectDirectory
+@param {String} dir Directory.
+@return {Boolean}
+**/
+function isProjectDirectory(dir) {
+ var metaStats, indexStats;
+
+ try {
+ metaStats = fs.statSync(path.join(dir, 'project.json'));
+ indexStats = fs.statSync(path.join(dir, 'index.mustache'));
+ } catch (ex) {
+ return false;
+ }
+
+ return metaStats.isFile() && indexStats.isFile();
+}
+exports.isProjectDirectory = isProjectDirectory;
+
+/**
+@method log
+**/
+function log(message, level) {
+ console.log('[' + (level || 'info') + '] ' + message);
+}
+exports.log = log;
+
+/**
+@method prepare
+@param {String} inDir
+@param {Object} options
+@param {callback}
+ @param {Error} err
+ @param {Object} options Merged options.
+**/
+function prepare(inDir, options, callback) {
+ var compiled = {},
+ meta = {},
+ type = options.component ? 'component' : 'project';
+
+ if (options && options.skipLoad) {
+ // Skip loading layouts, metadata, pages, and partials and assume that
+ // the caller has provided them if they want them.
+ options = util.merge({
+ layouts : {},
+ meta : {},
+ pages : {},
+ partials : {},
+ viewClass: options.component ? ComponentView : View
+ }, options);
+ } else {
+
+ // Gather layouts, metadata, pages, and partials from the specified
+ // input directory, then merge them into the provided options (if any).
+ //
+ // Gathered data will override provided data if there are conflicts, in
+ // order to support a use case where global data are provided by the
+ // caller and overridden by more specific component-level data gathered
+ // from the input directory.
+ //
+ // The metadata inheritance chain looks like this:
+ //
+ // - override metadata specified via CLI (highest precedence)
+ // - component metadata (if this is a component)
+ // - project-level component default metadata (if specified and this is a component)
+ // - theme-level component default metadata (if specified and this is a component)
+ // - project metadata
+ // - theme metadata (lowest precedence)
+
+ if (options.component && options.meta.componentDefaults) {
+ meta = options.meta.componentDefaults;
+ }
+
+ try {
+ options = util.merge(
+ {viewClass: options.component ? ComponentView : View},
+ options || {},
+ {
+ layouts : getLayouts(inDir),
+ meta : util.merge(meta, getMetadata(inDir, type, true)),
+ partials: getPartials(inDir)
+ }
+ );
+ } catch (ex) {
+ return callback(ex);
+ }
+ }
+
+ // Pages are never merged.
+ options.pages = getPages(inDir);
+
+ // Mix in the override metadata, if any. It takes precedence over everything
+ // else.
+ util.mix(options.meta, options.overrideMeta);
+
+ // Set a default asset path if one isn't specified in the metadata.
+ if (!options.meta.projectAssets) {
+ options.meta.projectAssets = options.component ? '../assets' : 'assets';
+ }
+
+ if (!options.meta.componentAssets && options.component) {
+ options.meta.componentAssets = '../assets/' + options.meta.name;
+ }
+
+ // If a validator function was provided, run it.
+ if (options.validator && options.validator(options, inDir) === false) {
+ return callback(new Error('Validation failed.')); // TODO: get the error from the validator itself
+ }
+
+ if (typeof options.meta.layout === 'undefined') {
+ options.meta.layout = options.layouts[type] ? type : 'main';
+ }
+
+ callback(null, options);
+}
+exports.prepare = prepare;
+
+/**
+Renders the specified template source.
+
+@method render
+@param {String} source Template source to render.
+@param {Object} view View object.
+@param {String} [layout] Layout template to use.
+@param {Object} [partials] Partials object.
+@param {callback}
+ @param {Error} err
+ @param {String} html Rendered HTML.
+**/
+function render(source, view, layout, partials, callback) {
+ var html, template;
+
+ // Allow callback as third or fourth param.
+ if (typeof partials === 'function') {
+ callback = partials;
+ partials = {};
+ } else if (typeof layout === 'function') {
+ callback = layout;
+ layout = null;
+ }
+
+ try {
+ if (layout) {
+ template = Handlebars.compile(layout);
+ partials = util.merge(partials || {}, {layout_content: source});
+ } else {
+ template = Handlebars.compile(source);
+ }
+
+ html = template(view, null, partials);
+ } catch (ex) {
+ return callback(ex);
+ }
+
+ callback(null, Higgins.render(html));
+}
+exports.render = render;
+
+/**
+@method writePages
+**/
+function writePages(outDir, options, callback) {
+ var ext = options['out-ext'],
+ toWrite = util.size(options.pages);
+
+ if (!toWrite) { return callback(); }
+
+ // Render each page to HTML and write it to the output directory.
+ util.each(options.pages, function (source, name) {
+ var view = new options.viewClass(options.meta, name),
+ layout = options.layouts[view.layout];
+
+ render(source, view, layout, options.partials, function (err, html) {
+ if (err) { return callback(err); }
+ fs.writeFile(path.join(outDir, name + ext), html, 'utf8', finish);
+ });
+ });
+
+ function finish(err) {
+ if (err) { return callback(err); }
+
+ if (!(toWrite -= 1)) {
+ callback();
+ }
+ }
+}
+exports.writePages = writePages;
View
13 package.json
@@ -1,10 +1,13 @@
{
"name" : "selleck",
"description": "Generator for YUI's mustache-based user documentation.",
- "version" : "0.1.3",
- "keywords" : ["yui", "mustache", "docs", "documentation"],
+
+ "version" : "0.1.4-git",
"homepage" : "https://github.com/rgrove/selleck/",
"author" : "Ryan Grove <ryan@wonko.com> (http://wonko.com/)",
+ "copyright" : "Copyright (c) 2011 Yahoo! Inc. All rights reserved.",
+
+ "keywords" : ["yui", "mustache", "docs", "documentation"],
"licenses": [
{"type": "BSD", "url": "http://developer.yahoo.com/yui/license.html"}
@@ -15,7 +18,7 @@
],
"engines": [
- "node >=0.4.0 <0.5.0",
+ "node ~0.4.0",
"npm >=0.3.1 <1.1.0"
],
@@ -24,5 +27,7 @@
},
"main": "index",
- "bin" : {"selleck": "./bin/selleck"}
+ "bin" : {"selleck": "./bin/selleck"},
+
+ "preferGlobal": true
}
Please sign in to comment.
Something went wrong with that request. Please try again.