123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437 |
- /* content.coffee */
- (function() {
- var ContentPlugin, ContentTree, StaticFile, async, chalk, fs, loadContent, minimatch, minimatchOptions, path, setImmediate, url,
- extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
- hasProp = {}.hasOwnProperty,
- slice = [].slice;
- async = require('async');
- fs = require('fs');
- path = require('path');
- url = require('url');
- chalk = require('chalk');
- minimatch = require('minimatch');
- minimatchOptions = {
- dot: false
- };
- if (typeof setImmediate === "undefined" || setImmediate === null) {
- setImmediate = process.nextTick;
- }
- ContentPlugin = (function() {
- /* The mother of all plugins */
- function ContentPlugin() {}
- ContentPlugin.property = function(name, getter) {
- /* Define read-only property with *name*. */
- var get;
- if (typeof getter === 'string') {
- get = function() {
- return this[getter].call(this);
- };
- } else {
- get = function() {
- return getter.call(this);
- };
- }
- return Object.defineProperty(this.prototype, name, {
- get: get,
- enumerable: true
- });
- };
- ContentPlugin.property('view', 'getView');
- ContentPlugin.prototype.getView = function() {
- /* Return a view that renders the plugin. Either a string naming a exisitng view or a function:
- `(env, locals, contents, templates, callback) ->`
- Where *environment* is the current wintersmith environment, *contents* is the content-tree
- and *templates* is a map of all templates as: {filename: templateInstance}. *callback* should be
- called with a stream/buffer or null if this plugin instance should not be rendered.
- */
- throw new Error('Not implemented.');
- };
- ContentPlugin.property('filename', 'getFilename');
- ContentPlugin.prototype.getFilename = function() {
- /* Return filename for this content. This is where the result of the plugin's view will be written to. */
- throw new Error('Not implemented.');
- };
- ContentPlugin.property('url', 'getUrl');
- ContentPlugin.prototype.getUrl = function(base) {
- /* Return url for this content relative to *base*. */
- var filename;
- filename = this.getFilename();
- if (base == null) {
- base = this.__env.config.baseUrl;
- }
- if (!base.match(/\/$/)) {
- base += '/';
- }
- if (process.platform === 'win32') {
- filename = filename.replace(/\\/g, '/');
- }
- return url.resolve(base, filename);
- };
- ContentPlugin.property('pluginColor', 'getPluginColor');
- ContentPlugin.prototype.getPluginColor = function() {
- /* Return vanity color used to identify the plugin when printing the content tree
- choices are: bold, italic, underline, inverse, yellow, cyan, white, magenta,
- green, red, grey, blue, rainbow, zebra or none.
- */
- return 'cyan';
- };
- ContentPlugin.property('pluginInfo', 'getPluginInfo');
- ContentPlugin.prototype.getPluginInfo = function() {
- /* Return plugin information. Also displayed in the content tree printout. */
- return "url: " + this.url;
- };
- return ContentPlugin;
- })();
- ContentPlugin.fromFile = function(filepath, callback) {
- /* Calls *callback* with an instance of class. Where *filepath* is an object containing
- both the absolute and realative paths for the file. e.g.
- {full: "/home/foo/mysite/contents/somedir/somefile.ext",
- relative: "somedir/somefile.ext"}
- */
- throw new Error('Not implemented.');
- };
- StaticFile = (function(superClass) {
- extend(StaticFile, superClass);
- /* Static file handler, simply serves content as-is. Last in chain. */
- function StaticFile(filepath1) {
- this.filepath = filepath1;
- }
- StaticFile.prototype.getView = function() {
- return function() {
- var args, callback, error, j, rs;
- args = 2 <= arguments.length ? slice.call(arguments, 0, j = arguments.length - 1) : (j = 0, []), callback = arguments[j++];
- try {
- rs = fs.createReadStream(this.filepath.full);
- } catch (error1) {
- error = error1;
- return callback(error);
- }
- return callback(null, rs);
- };
- };
- StaticFile.prototype.getFilename = function() {
- return this.filepath.relative;
- };
- StaticFile.prototype.getPluginColor = function() {
- return 'none';
- };
- return StaticFile;
- })(ContentPlugin);
- StaticFile.fromFile = function(filepath, callback) {
- return callback(null, new StaticFile(filepath));
- };
- loadContent = function(env, filepath, callback) {
- /* Helper that loads content plugin found in *filepath*. */
- var i, j, plugin, ref;
- env.logger.silly("loading " + filepath.relative);
- plugin = {
- "class": StaticFile,
- group: 'files'
- };
- for (i = j = ref = env.contentPlugins.length - 1; j >= 0; i = j += -1) {
- if (minimatch(filepath.relative, env.contentPlugins[i].pattern, minimatchOptions)) {
- plugin = env.contentPlugins[i];
- break;
- }
- }
- return plugin["class"].fromFile(filepath, function(error, instance) {
- if (error != null) {
- error.message = filepath.relative + ": " + error.message;
- }
- if (instance != null) {
- instance.__env = env;
- }
- if (instance != null) {
- instance.__plugin = plugin;
- }
- if (instance != null) {
- instance.__filename = filepath.full;
- }
- return callback(error, instance);
- });
- };
- ContentTree = function(filename, groupNames) {
- var groups, j, len, name, parent;
- if (groupNames == null) {
- groupNames = [];
- }
- parent = null;
- groups = {
- directories: [],
- files: []
- };
- for (j = 0, len = groupNames.length; j < len; j++) {
- name = groupNames[j];
- groups[name] = [];
- }
- Object.defineProperty(this, '__groupNames', {
- get: function() {
- return groupNames;
- }
- });
- Object.defineProperty(this, '_', {
- get: function() {
- return groups;
- }
- });
- Object.defineProperty(this, 'filename', {
- get: function() {
- return filename;
- }
- });
- Object.defineProperty(this, 'index', {
- get: function() {
- var item, key, ref;
- ref = this;
- for (key in ref) {
- item = ref[key];
- if (key.slice(0, 6) === 'index.') {
- return item;
- }
- }
- }
- });
- return Object.defineProperty(this, 'parent', {
- get: function() {
- return parent;
- },
- set: function(val) {
- return parent = val;
- }
- });
- };
- ContentTree.fromDirectory = function(env, directory, callback) {
- /* Recursively scan *directory* and build a ContentTree with enviroment *env*.
- Calls *callback* with a nested ContentTree or an error if something went wrong.
- */
- var createInstance, createInstances, filterIgnored, readDirectory, reldir, resolveFilenames, tree;
- reldir = env.relativeContentsPath(directory);
- tree = new ContentTree(reldir, env.getContentGroups());
- env.logger.silly("creating content tree from " + directory);
- readDirectory = function(callback) {
- return fs.readdir(directory, callback);
- };
- resolveFilenames = function(filenames, callback) {
- filenames.sort();
- return async.map(filenames, function(filename, callback) {
- var relname;
- relname = path.join(reldir, filename);
- return callback(null, {
- full: path.join(env.contentsPath, relname),
- relative: relname
- });
- }, callback);
- };
- filterIgnored = function(filenames, callback) {
- /* Exclude *filenames* matching ignore patterns in environment config. */
- if (env.config.ignore.length > 0) {
- return async.filter(filenames, function(filename, callback) {
- var include, j, len, pattern, ref;
- include = true;
- ref = env.config.ignore;
- for (j = 0, len = ref.length; j < len; j++) {
- pattern = ref[j];
- if (minimatch(filename.relative, pattern, minimatchOptions)) {
- env.logger.verbose("ignoring " + filename.relative + " (matches: " + pattern + ")");
- include = false;
- break;
- }
- }
- return callback(null, include);
- }, callback);
- } else {
- return callback(null, filenames);
- }
- };
- createInstance = function(filepath, callback) {
- /* Create plugin or subtree instance for *filepath*. */
- return setImmediate(function() {
- return async.waterfall([
- async.apply(fs.stat, filepath.full), function(stats, callback) {
- var basename;
- basename = path.basename(filepath.relative);
- if (stats.isDirectory()) {
- return ContentTree.fromDirectory(env, filepath.full, function(error, result) {
- result.parent = tree;
- tree[basename] = result;
- tree._.directories.push(result);
- return callback(error);
- });
- } else if (stats.isFile()) {
- return loadContent(env, filepath, function(error, instance) {
- if (!error) {
- instance.parent = tree;
- tree[basename] = instance;
- tree._[instance.__plugin.group].push(instance);
- }
- return callback(error);
- });
- } else {
- return callback(new Error("Invalid file " + filepath.full + "."));
- }
- }
- ], callback);
- });
- };
- createInstances = function(filenames, callback) {
- return async.forEachLimit(filenames, env.config._fileLimit, createInstance, callback);
- };
- return async.waterfall([readDirectory, resolveFilenames, filterIgnored, createInstances], function(error) {
- return callback(error, tree);
- });
- };
- ContentTree.inspect = function(tree, depth) {
- var cfn, i, j, k, keys, l, len, pad, ref, rv, s, v;
- if (depth == null) {
- depth = 0;
- }
- /* Return a pretty formatted string representing the content *tree*. */
- if (typeof tree === 'number') {
- return '[Function: ContentTree]';
- }
- rv = [];
- pad = '';
- for (i = j = 0, ref = depth; 0 <= ref ? j <= ref : j >= ref; i = 0 <= ref ? ++j : --j) {
- pad += ' ';
- }
- keys = Object.keys(tree).sort(function(a, b) {
- var ad, bd;
- ad = tree[a] instanceof ContentTree;
- bd = tree[b] instanceof ContentTree;
- if (ad !== bd) {
- return bd - ad;
- }
- if (a < b) {
- return -1;
- }
- if (a > b) {
- return 1;
- }
- return 0;
- });
- for (l = 0, len = keys.length; l < len; l++) {
- k = keys[l];
- v = tree[k];
- if (v instanceof ContentTree) {
- s = (chalk.bold(k)) + "/\n";
- s += ContentTree.inspect(v, depth + 1);
- } else {
- cfn = function(s) {
- return s;
- };
- if (v.pluginColor !== 'none') {
- if (!(cfn = chalk[v.pluginColor])) {
- throw new Error("Plugin " + k + " specifies invalid pluginColor: " + v.pluginColor);
- }
- }
- s = (cfn(k)) + " (" + (chalk.grey(v.pluginInfo)) + ")";
- }
- rv.push(pad + s);
- }
- return rv.join('\n');
- };
- ContentTree.flatten = function(tree) {
- /* Return all the items in the *tree* as an array of content plugins. */
- var key, rv, value;
- rv = [];
- for (key in tree) {
- value = tree[key];
- if (value instanceof ContentTree) {
- rv = rv.concat(ContentTree.flatten(value));
- } else {
- rv.push(value);
- }
- }
- return rv;
- };
- ContentTree.merge = function(root, tree) {
- /* Merge *tree* into *root* tree. */
- var item, key;
- for (key in tree) {
- item = tree[key];
- if (item instanceof ContentPlugin) {
- root[key] = item;
- item.parent = root;
- root._[item.__plugin.group].push(item);
- } else if (item instanceof ContentTree) {
- if (root[key] == null) {
- root[key] = new ContentTree(key, item.__groupNames);
- root[key].parent = root;
- root[key].parent._.directories.push(root[key]);
- }
- if (root[key] instanceof ContentTree) {
- ContentTree.merge(root[key], item);
- }
- } else {
- throw new Error("Invalid item in tree for '" + key + "'");
- }
- }
- };
- /* Exports */
- module.exports = {
- ContentTree: ContentTree,
- ContentPlugin: ContentPlugin,
- StaticFile: StaticFile,
- loadContent: loadContent
- };
- }).call(this);
|