/* environment.coffee */ (function() { var Config, ContentPlugin, ContentTree, Environment, EventEmitter, StaticFile, TemplatePlugin, async, fs, loadTemplates, logger, path, readJSON, readJSONSync, ref, ref1, render, runGenerator, utils, 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, indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; path = require('path'); async = require('async'); fs = require('fs'); EventEmitter = require('events').EventEmitter; utils = require('./utils'); Config = require('./config').Config; ref = require('./content'), ContentPlugin = ref.ContentPlugin, ContentTree = ref.ContentTree, StaticFile = ref.StaticFile; ref1 = require('./templates'), TemplatePlugin = ref1.TemplatePlugin, loadTemplates = ref1.loadTemplates; logger = require('./logger').logger; render = require('./renderer').render; runGenerator = require('./generator').runGenerator; readJSON = utils.readJSON, readJSONSync = utils.readJSONSync; Environment = (function(superClass) { extend(Environment, superClass); /* The Wintersmith environment. */ Environment.prototype.utils = utils; Environment.prototype.ContentTree = ContentTree; Environment.prototype.ContentPlugin = ContentPlugin; Environment.prototype.TemplatePlugin = TemplatePlugin; function Environment(config, workDir1, logger1) { this.workDir = workDir1; this.logger = logger1; /* Create a new Environment, *config* is a Config instance, *workDir* is the working directory and *logger* is a log instance implementing methods for error, warn, verbose and silly loglevels. */ this.loadedModules = []; this.workDir = path.resolve(this.workDir); this.setConfig(config); this.reset(); } Environment.prototype.reset = function() { /* Reset environment and clear any loaded modules from require.cache */ var id; this.views = { none: function() { var args, callback, i; args = 2 <= arguments.length ? slice.call(arguments, 0, i = arguments.length - 1) : (i = 0, []), callback = arguments[i++]; return callback(); } }; this.generators = []; this.plugins = { StaticFile: StaticFile }; this.templatePlugins = []; this.contentPlugins = []; this.helpers = {}; while (id = this.loadedModules.pop()) { this.logger.verbose("unloading: " + id); delete require.cache[id]; } return this.setupLocals(); }; Environment.prototype.setConfig = function(config1) { this.config = config1; this.contentsPath = this.resolvePath(this.config.contents); return this.templatesPath = this.resolvePath(this.config.templates); }; Environment.prototype.setupLocals = function() { /* Resolve locals and loads any required modules. */ var alias, error, filename, id, ref2; this.locals = {}; if (typeof this.config.locals === 'string') { filename = this.resolvePath(this.config.locals); this.logger.verbose("loading locals from: " + filename); this.locals = readJSONSync(filename); } else { this.locals = this.config.locals; } ref2 = this.config.require; for (alias in ref2) { id = ref2[alias]; logger.verbose("loading module '" + id + "' available in locals as '" + alias + "'"); if (this.locals[alias] != null) { logger.warn("module '" + id + "' overwrites previous local with the same key ('" + alias + "')"); } try { this.locals[alias] = this.loadModule(id); } catch (error1) { error = error1; logger.warn("unable to load '" + id + "': " + error.message); } } }; Environment.prototype.resolvePath = function(pathname) { /* Resolve *pathname* in working directory, returns an absolute path. */ return path.resolve(this.workDir, pathname || ''); }; Environment.prototype.resolveContentsPath = function(pathname) { /* Resolve *pathname* in contents directory, returns an absolute path. */ return path.resolve(this.contentsPath, pathname || ''); }; Environment.prototype.resolveModule = function(module) { /* Resolve *module* to an absolute path, mimicking the node.js module loading system. */ var error, nodeDir; switch (module[0]) { case '.': return require.resolve(this.resolvePath(module)); case '/': return require.resolve(module); default: nodeDir = this.resolvePath('node_modules'); try { return require.resolve(path.join(nodeDir, module)); } catch (error1) { error = error1; return require.resolve(module); } } }; Environment.prototype.relativePath = function(pathname) { /* Resolve path relative to working directory. */ return path.relative(this.workDir, pathname); }; Environment.prototype.relativeContentsPath = function(pathname) { /* Resolve path relative to contents directory. */ return path.relative(this.contentsPath, pathname); }; Environment.prototype.registerContentPlugin = function(group, pattern, plugin) { /* Add a content *plugin* to the environment. Files in the contents directory matching the glob *pattern* will be instantiated using the plugin's `fromFile` factory method. The *group* argument is used to group the loaded instances under each directory. I.e. plugin instances with the group 'textFiles' can be found in `contents.somedir._.textFiles`. */ this.logger.verbose("registering content plugin " + plugin.name + " that handles: " + pattern); this.plugins[plugin.name] = plugin; return this.contentPlugins.push({ group: group, pattern: pattern, "class": plugin }); }; Environment.prototype.registerTemplatePlugin = function(pattern, plugin) { /* Add a template *plugin* to the environment. All files in the template directory matching the glob *pattern* will be passed to the plugin's `fromFile` classmethod. */ this.logger.verbose("registering template plugin " + plugin.name + " that handles: " + pattern); this.plugins[plugin.name] = plugin; return this.templatePlugins.push({ pattern: pattern, "class": plugin }); }; Environment.prototype.registerGenerator = function(group, generator) { /* Add a generator to the environment. The generator function is called with the env and the current content tree. It should return a object with nested ContentPlugin instances. These will be merged into the final content tree. */ return this.generators.push({ group: group, fn: generator }); }; Environment.prototype.registerView = function(name, view) { /* Add a view to the environment. */ return this.views[name] = view; }; Environment.prototype.getContentGroups = function() { /* Return an array of all registered content groups */ var generator, groups, i, j, len, len1, plugin, ref2, ref3, ref4, ref5; groups = []; ref2 = this.contentPlugins; for (i = 0, len = ref2.length; i < len; i++) { plugin = ref2[i]; if (ref3 = plugin.group, indexOf.call(groups, ref3) < 0) { groups.push(plugin.group); } } ref4 = this.generators; for (j = 0, len1 = ref4.length; j < len1; j++) { generator = ref4[j]; if (ref5 = generator.group, indexOf.call(groups, ref5) < 0) { groups.push(generator.group); } } return groups; }; Environment.prototype.loadModule = function(module, unloadOnReset) { var id, rv; if (unloadOnReset == null) { unloadOnReset = false; } /* Requires and returns *module*, resolved from the current working directory. */ if (module.slice(-7) === '.coffee') { require('coffee-script/register'); } this.logger.silly("loading module: " + module); id = this.resolveModule(module); this.logger.silly("resolved: " + id); rv = require(id); if (unloadOnReset) { this.loadedModules.push(id); } return rv; }; Environment.prototype.loadPluginModule = function(module, callback) { /* Load a plugin *module*. Calls *callback* when plugin is done loading, or an error occurred. */ var done, error, id; id = 'unknown'; done = function(error) { if (error != null) { error.message = "Error loading plugin '" + id + "': " + error.message; } return callback(error); }; if (typeof module === 'string') { id = module; try { module = this.loadModule(module); } catch (error1) { error = error1; done(error); return; } } try { return module.call(null, this, done); } catch (error1) { error = error1; return done(error); } }; Environment.prototype.loadViewModule = function(id, callback) { /* Load a view *module* and add it to the environment. */ var error, module; this.logger.verbose("loading view: " + id); try { module = this.loadModule(id, true); } catch (error1) { error = error1; error.message = "Error loading view '" + id + "': " + error.message; callback(error); return; } this.registerView(path.basename(id), module); return callback(); }; Environment.prototype.loadPlugins = function(callback) { /* Loads any plugin found in *@config.plugins*. */ return async.series([ (function(_this) { return function(callback) { return async.forEachSeries(_this.constructor.defaultPlugins, function(plugin, callback) { var id, module; _this.logger.verbose("loading default plugin: " + plugin); id = require.resolve("./../plugins/" + plugin); module = require(id); _this.loadedModules.push(id); return _this.loadPluginModule(module, callback); }, callback); }; })(this), (function(_this) { return function(callback) { return async.forEachSeries(_this.config.plugins, function(plugin, callback) { _this.logger.verbose("loading plugin: " + plugin); return _this.loadPluginModule(plugin, callback); }, callback); }; })(this) ], callback); }; Environment.prototype.loadViews = function(callback) { /* Loads files found in the *@config.views* directory and registers them as views. */ if (this.config.views == null) { return callback(); } return async.waterfall([ (function(_this) { return function(callback) { return fs.readdir(_this.resolvePath(_this.config.views), callback); }; })(this), (function(_this) { return function(filenames, callback) { var modules; modules = filenames.map(function(filename) { return _this.config.views + "/" + filename; }); return async.forEach(modules, _this.loadViewModule.bind(_this), callback); }; })(this) ], callback); }; Environment.prototype.getContents = function(callback) { /* Build the ContentTree from *@contentsPath*, also runs any registered generators. */ return async.waterfall([ (function(_this) { return function(callback) { return ContentTree.fromDirectory(_this, _this.contentsPath, callback); }; })(this), (function(_this) { return function(contents, callback) { return async.mapSeries(_this.generators, function(generator, callback) { return runGenerator(_this, contents, generator, callback); }, function(error, generated) { var gentree, i, len, tree; if ((error != null) || generated.length === 0) { return callback(error, contents); } try { tree = new ContentTree('', _this.getContentGroups()); for (i = 0, len = generated.length; i < len; i++) { gentree = generated[i]; ContentTree.merge(tree, gentree); } ContentTree.merge(tree, contents); } catch (error1) { error = error1; return callback(error); } return callback(null, tree); }); }; })(this) ], callback); }; Environment.prototype.getTemplates = function(callback) { /* Load templates. */ return loadTemplates(this, callback); }; Environment.prototype.getLocals = function(callback) { /* Returns locals. */ return callback(null, this.locals); }; Environment.prototype.load = function(callback) { /* Convenience method to load plugins, views, contents, templates and locals. */ return async.waterfall([ (function(_this) { return function(callback) { return async.parallel([ function(callback) { return _this.loadPlugins(callback); }, function(callback) { return _this.loadViews(callback); } ], callback); }; })(this), (function(_this) { return function(_, callback) { return async.parallel({ contents: function(callback) { return _this.getContents(callback); }, templates: function(callback) { return _this.getTemplates(callback); }, locals: function(callback) { return _this.getLocals(callback); } }, callback); }; })(this) ], callback); }; Environment.prototype.preview = function(callback) { /* Start the preview server. Calls *callback* with the server instance when it is up and running or if an error occurs. NOTE: The returned server instance will be invalid if the config file changes and the server is restarted because of it. As a temporary workaround you can set the _restartOnConfChange key in settings to false. */ var server; this.mode = 'preview'; server = require('./server'); return server.run(this, callback); }; Environment.prototype.build = function(outputDir, callback) { /* Build the content tree and render it to *outputDir*. */ this.mode = 'build'; if (arguments.length < 2) { callback = outputDir || function() {}; outputDir = this.resolvePath(this.config.output); } return async.waterfall([ (function(_this) { return function(callback) { return _this.load(callback); }; })(this), (function(_this) { return function(result, callback) { var contents, locals, templates; contents = result.contents, templates = result.templates, locals = result.locals; return render(_this, outputDir, contents, templates, locals, callback); }; })(this) ], callback); }; return Environment; })(EventEmitter); Environment.create = function(config, workDir, log) { if (log == null) { log = logger; } /* Set up a new environment using the default logger, *config* can be either a config object, a Config instance or a path to a config file. */ if (typeof config === 'string') { if (workDir == null) { workDir = path.dirname(config); } config = Config.fromFileSync(config); } else { if (workDir == null) { workDir = process.cwd(); } if (!(config instanceof Config)) { config = new Config(config); } } return new Environment(config, workDir, log); }; Environment.defaultPlugins = ['page', 'pug', 'markdown']; /* Exports */ module.exports = { Environment: Environment }; }).call(this);