adamrenklint/asimov-static

View on GitHub
lib/watcher/Watcher.js

Summary

Maintainability
B
6 hrs
Test Coverage
var Model = require('../core/Model');
var _ = require('lodash');
var Filesystem = require('../core/Filesystem');

var StyleSheetParser = require('../parsers/StyleSheetParser');
var ScriptParser = require('../parsers/ScriptParser');
var PageParser = require('../parsers/PageParser');
var TemplateParser = require('../parsers/TemplateParser');

var PageHandler = require('../updaters/PageHandler');
var TemplateHandler = require('../updaters/TemplateHandler');
var StyleSheetHandler = require('../updaters/StyleSheetHandler');
var SiteDataHandler = require('../updaters/SiteDataHandler');
var ScriptHandler = require('../updaters/ScriptHandler');

var asimov = require('asimov');

var _super = Model.prototype;

module.exports = Model.extend({

  'namespace': 'watcher',

  'parsers': {

    'styleSheet': StyleSheetParser,
    'page': PageParser,
    'template': TemplateParser,
    'script': ScriptParser
  },

  'handlers': {

    'styleSheet': StyleSheetHandler,
    'page': PageHandler,
    'template': TemplateHandler,
    'script': ScriptHandler,
    'siteData': SiteDataHandler
  },

  'initialize': function () {

    var self = this;
    _super.initialize.apply(self, arguments);

    self.filesystem = new Filesystem();

    self.implement('parsers');
    self.implement('handlers');

    asimov.shutdown(function (next) {
      self.stopping = true;
      next();
    });
  },

  'implement': function (type) {

    var self = this;
    var hiddenType = '_' + type;

    self[hiddenType] = {};
    _.each(self[type], function (Ctor, name) {
      self[hiddenType][name] = new Ctor(_.merge({}, self.options, {
        'watcher': self
      }));
    });
  },

  'watch': function (model) {

    var self = this;
    var attributes = model.attributes;

    self.assert('string', attributes.path, 'Cannot watch model without string as path');
    self.assert('string', attributes.type, 'Cannot watch model without string as type @ ' + attributes.path);
    self.assert('string', attributes.raw, 'Cannot watch model without string as raw @ ' + attributes.path);

    self.startWatching(process.cwd());
    self.parseDependencies(model);
  },

  'startWatching': function (path) {

    var self = this;

    if (process.env.ENV !== 'development' && !process.env.WATCH) self.watching = true;

    if (!self.watching) {

      self.watching = self.filesystem.watchTree(path, self.handleChange);

      asimov.shutdown(function (next) {

        self.watching();
        self.watching = null;

        next();
      });
    }
  },

  'destroy': function (argument) {

    var self = this;

    self.watching && self.watching();
    _super.destroy.apply(self, arguments);
  },

  'forceChange': function (models) {

    var self = this;
    _.each(models, self.handleChange);
  },

  'getPathType': function (path) {

    var self = this;

    _.each(self.options.paths.initializers, function (initializersPath) {
      if (path.indexOf(initializersPath) === 0) {
        self.logger.pending(self.namespace, 'Initializer was invalidated, restarting process');
        self.restart(path);
      }
    });

    _.each(self.options.paths.middleware, function (middlewarePath) {
      if (path.indexOf(middlewarePath) === 0) {
        self.logger.pending(self.namespace, 'Middleware was invalidated, restarting process');
        self.restart(path);
      }
    });

    if (path.indexOf(process.cwd() + '/environments') === 0) {
      self.logger.pending(self.namespace, 'Configuration was invalidated, restarting process');
      self.restart(path);
    }

    if (path.indexOf(process.cwd() + '/site/data/') >= 0) {
      return 'siteData';
    }
    else if (self.filesystem.hasFileExtension(path, 'txt')) {
      return 'page';
    }
    else if (self.filesystem.hasFileExtension(path, 'tmpl')) {
      return 'template';
    }
    else if (self.filesystem.hasFileExtension(path, 'styl')) {
      return 'styleSheet';
    }
    else if (self.filesystem.hasFileExtension(path.replace('asimov.js', 'asimov'), 'js')) {
      return 'script';
    }
  },

  'handleChange': function (path, before, after, type) {

    var self = this;
    if (self.stopped) return false;
    if (path.indexOf(self.options.outputPath) >= 0) return;

    type = type || 'invalidated';
    var graph = self.get(path) || [];
    var pathType = self.getPathType(path);
    var handler = self._handlers[pathType];

    _.defer(function () {

      if (self.stopped) return false;
      handler && _.keys(after).length > 0 && self.logger.pending(self.namespace, 'A ' + pathType + ' file was ' + type + ', invalidating ' + graph.length + ' dependencies @ ' + path);

      type = type === 'invalidated' ? 'modified' : type;
      handler && handler[type](path, graph);
    });
  },

  'parseDependencies': function (model) {

    var self = this;
    var started = new Date();
    var attributes = model.attributes;
    var type = attributes.type;
    var parser = self._parsers[type];

    if (self.stopped) return false;

    if (parser) {

      var result = parser.parse(model, null, self);
      self.ensureForceChangeBindings();

      self.logger.lowSince(self.namespace, 'Parsed dependencies @ ' + model.attributes.path, started);

      return result;
    }
    else {
      throw new Error('No dependency parser exists for type "' + type + '"');
    }
  },

  'ensureForceChangeBindings': function () {

    var self = this;

    _.each(self.attributes, function (models, path) {
      _.each(models, function (model) {
        if (!model.forceChangeBinding) {
          model.forceChangeBinding = model.bindTo(model, 'force:change', self.forceChange);
        }
      });
    });
  }
});