123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503 |
- /* 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);
|