<commit_before>import Emitter from 'api/emitter';
import EVENTS from 'api/constants/events';
import { PersistentStorage } from 'api/storage';
import { Mock } from 'api/models/mock';
import { Request } from 'api/models/request';

import find from 'lodash/find';
import filter from 'lodash/filter';
import remove from 'lodash/remove';
import isObject from 'lodash/isObject';
import isString from 'lodash/isString';
import isArray from 'lodash/isArray';
import omit from 'lodash/omit';
import cloneDeep from 'lodash/cloneDeep';
import assign from 'lodash/assign';

export class Mocks {

  init() {
    this.loadFromStorage();
    this._registerEvents();
  }

  _registerEvents() {
    Emitter.on(EVENTS.UPDATE_MOCK, this.loadFromStorage, this);
    Emitter.on(EVENTS.IMPORT, this.loadFromStorage, this);
    Emitter.on(EVENTS.STORAGE_PERSIST, this.loadFromStorage, this);
  }

  loadFromStorage() {
    this.all = PersistentStorage.dataTree.mocks
      .map((mock) => new Mock(mock));

    PersistentStorage.dataTree.mocks = this.all;
  }

  setMocks(mocks) {
    this.all = mocks.map((mock) => new Mock(mock));

    PersistentStorage.dataTree.mocks = this.all;
    PersistentStorage.persist();
  }

  find(options) {
    return find(this.all, options);
  }

  findAll(options) {
    const results = filter(this.all, options);

    if (!results) {
      return [];
    }

    return results;
  }

  addMock() {
    const newRequest = new Request({
      method: 'GET',
      url: '/',
      headers: { 'content-type': 'application/json' },
      origin: typeof window !== "undefined" ? window.location.origin : null
    });

    const newMock = this.mockRequest(newRequest);

    PersistentStorage.persist();

    return newMock;
  }

  mockRequest(request) {
    this.findAll({ requestHash: request.requestHash }).forEach((mock) => mock.disable());

    const mock = new Mock(request);
    this.all.push(mock);

    PersistentStorage.persist();

    return mock;
  }

  mergeMocks(mocks, options = {}) {
    for (const mock of mocks) {

      // deserialize json mock request body
      if (isObject(mock.response.body)) {
        mock.response.body = JSON.stringify(mock.response.body);
      }

      if (isObject(mock.params)) {
        mock.params = JSON.stringify(mock.params);
      }

      const existingMock = cloneDeep(find(this.all, { id: mock.id }));

      if (!existingMock) {
        this.all.push(new Mock(mock));
        continue;
      }

      const existingMockClone = cloneDeep(existingMock);
      const newMockClone = cloneDeep(mock);

      if (existingMockClone.response.body) {
        existingMockClone.response.body = existingMockClone.response.body.replace(/\s/g, '');
      }

      if (newMockClone.response.body) {
        newMockClone.response.body = newMockClone.response.body.replace(/\s/g, '');
      }

      const isMockEqual = JSON.stringify(newMockClone) === JSON.stringify(existingMockClone);

      if (existingMock && isMockEqual) {
        continue;
      } else if (existingMock && options.mode === 'append') {
        this.all.push(new Mock(omit(mock, ['id'])));
      } else if (existingMock) {
        const updatedMock = assign({}, existingMock, mock);

        this.updateMock(existingMock.id, updatedMock);
      } else {
        this.all.push(new Mock(mock));
      }
    }

    PersistentStorage.persist();
  }

  recapture(mockIds, cb = () => {}) {
    const done = (id) => {
      PersistentStorage.persist();
      cb(id);
    };

    mockIds.forEach((mockId) => this.find({ id: mockId }).recapture(done));
  }

  toggleMock(mockId) {
    const mock = this.find({ id: mockId });

    if (!mock.active) {
      this.findAll({ requestHash: mock.requestHash }).forEach((mock) => mock.disable());
    }

    mock.toggle();

    PersistentStorage.persist();
  }

  export(arg) {
    if (isString(arg)) {
      return this.find({ id: arg }).export();
    }

    if (isArray(arg)) {
      return arg.map((mockId) => this.find({ id: mockId }).export());
    }

    return this.all.map((mock) => mock.export());
  }

  updateMock(mockId, request) {
    this.find({ id: mockId }).update(request);

    PersistentStorage.persist();
  }

  duplicateMock(mockId, overrides = {}) {
    const mock = this.find({ id: mockId });

    const duplicatedMock = {
      ...omit(mock, ['id']),
      name: mock.name ? `${mock.name} Copy` : mock.name,
      groupId: mock.groupId,
      ...overrides
    };

    this.mockRequest(duplicatedMock);
  }

  removeMock(mockId) {
    remove(this.all, { id: mockId });

    PersistentStorage.persist();
  }

  renameMock(mockId, newName) {
    this.find({ id: mockId }).rename(newName);

    PersistentStorage.persist();
  }

  getMatchingMocks(request) {
    const matches = [];

    for (let mock of this.all) {
      if (mock.matches({ url: request.url, params: request.body, method: request.method })) {
        matches.push(mock);
      }
    }

    return matches;
  }
}

export default new Mocks();
<commit_msg>FIX (mocks): deep duplicate mock so changes don't propagate (#284)<commit_after>import Emitter from 'api/emitter';
import EVENTS from 'api/constants/events';
import { PersistentStorage } from 'api/storage';
import { Mock } from 'api/models/mock';
import { Request } from 'api/models/request';

import find from 'lodash/find';
import filter from 'lodash/filter';
import remove from 'lodash/remove';
import isObject from 'lodash/isObject';
import isString from 'lodash/isString';
import isArray from 'lodash/isArray';
import omit from 'lodash/omit';
import cloneDeep from 'lodash/cloneDeep';
import assign from 'lodash/assign';

export class Mocks {

  init() {
    this.loadFromStorage();
    this._registerEvents();
  }

  _registerEvents() {
    Emitter.on(EVENTS.UPDATE_MOCK, this.loadFromStorage, this);
    Emitter.on(EVENTS.IMPORT, this.loadFromStorage, this);
    Emitter.on(EVENTS.STORAGE_PERSIST, this.loadFromStorage, this);
  }

  loadFromStorage() {
    this.all = PersistentStorage.dataTree.mocks
      .map((mock) => new Mock(mock));

    PersistentStorage.dataTree.mocks = this.all;
  }

  setMocks(mocks) {
    this.all = mocks.map((mock) => new Mock(mock));

    PersistentStorage.dataTree.mocks = this.all;
    PersistentStorage.persist();
  }

  find(options) {
    return find(this.all, options);
  }

  findAll(options) {
    const results = filter(this.all, options);

    if (!results) {
      return [];
    }

    return results;
  }

  addMock() {
    const newRequest = new Request({
      method: 'GET',
      url: '/',
      headers: { 'content-type': 'application/json' },
      origin: typeof window !== "undefined" ? window.location.origin : null
    });

    const newMock = this.mockRequest(newRequest);

    PersistentStorage.persist();

    return newMock;
  }

  mockRequest(request) {
    this.findAll({ requestHash: request.requestHash }).forEach((mock) => mock.disable());

    const mock = new Mock(request);
    this.all.push(mock);

    PersistentStorage.persist();

    return mock;
  }

  mergeMocks(mocks, options = {}) {
    for (const mock of mocks) {

      // deserialize json mock request body
      if (isObject(mock.response.body)) {
        mock.response.body = JSON.stringify(mock.response.body);
      }

      if (isObject(mock.params)) {
        mock.params = JSON.stringify(mock.params);
      }

      const existingMock = cloneDeep(find(this.all, { id: mock.id }));

      if (!existingMock) {
        this.all.push(new Mock(mock));
        continue;
      }

      const existingMockClone = cloneDeep(existingMock);
      const newMockClone = cloneDeep(mock);

      if (existingMockClone.response.body) {
        existingMockClone.response.body = existingMockClone.response.body.replace(/\s/g, '');
      }

      if (newMockClone.response.body) {
        newMockClone.response.body = newMockClone.response.body.replace(/\s/g, '');
      }

      const isMockEqual = JSON.stringify(newMockClone) === JSON.stringify(existingMockClone);

      if (existingMock && isMockEqual) {
        continue;
      } else if (existingMock && options.mode === 'append') {
        this.all.push(new Mock(omit(mock, ['id'])));
      } else if (existingMock) {
        const updatedMock = assign({}, existingMock, mock);

        this.updateMock(existingMock.id, updatedMock);
      } else {
        this.all.push(new Mock(mock));
      }
    }

    PersistentStorage.persist();
  }

  recapture(mockIds, cb = () => {}) {
    const done = (id) => {
      PersistentStorage.persist();
      cb(id);
    };

    mockIds.forEach((mockId) => this.find({ id: mockId }).recapture(done));
  }

  toggleMock(mockId) {
    const mock = this.find({ id: mockId });

    if (!mock.active) {
      this.findAll({ requestHash: mock.requestHash }).forEach((mock) => mock.disable());
    }

    mock.toggle();

    PersistentStorage.persist();
  }

  export(arg) {
    if (isString(arg)) {
      return this.find({ id: arg }).export();
    }

    if (isArray(arg)) {
      return arg.map((mockId) => this.find({ id: mockId }).export());
    }

    return this.all.map((mock) => mock.export());
  }

  updateMock(mockId, request) {
    this.find({ id: mockId }).update(request);

    PersistentStorage.persist();
  }

  duplicateMock(mockId, overrides = {}) {
    const mock = this.find({ id: mockId });

    const duplicatedMock = {
      ...omit(cloneDeep(mock), ['id']),
      name: mock.name ? `${mock.name} Copy` : mock.name,
      groupId: mock.groupId,
      ...overrides
    };

    this.mockRequest(duplicatedMock);
  }

  removeMock(mockId) {
    remove(this.all, { id: mockId });

    PersistentStorage.persist();
  }

  renameMock(mockId, newName) {
    this.find({ id: mockId }).rename(newName);

    PersistentStorage.persist();
  }

  getMatchingMocks(request) {
    const matches = [];

    for (let mock of this.all) {
      if (mock.matches({ url: request.url, params: request.body, method: request.method })) {
        matches.push(mock);
      }
    }

    return matches;
  }
}

export default new Mocks();
<|endoftext|>


<commit_before>"use strict"
var assert = require('assert');

module.exports.value = function(attribute, expectation) {
  this.attribute = attribute;
  this.expectation = expectation;
}

module.exports.exitCode = function(expectation) {
  this.expectation = expectation;
}

module.exports.value.prototype.assert = function(value) {
  if (this.expectation.constructor.name === "RegExp") {
    assert(this.expectation.test(value), "Expected " + this.attribute + " to match " + this.expectation + " got " + value);
  } else {
    assert.equal(value, this.expectation, "Expected " + this.attribute + " to equal " + this.expectation + " got " + value);
  }
}

module.exports.exitCode.prototype.assert = function(_, code) {
  assert.equal(code, this.expectation, "Expected exit code of " + this.expectation + " got " + code);
}
<commit_msg>Adding comments around the assertion constructors<commit_after>"use strict"
var assert = require('assert');

// Value constructs a new assertion on an
// attribute of the test case execution.
//
// This assertion type takes an attribute
// and expectation value, and will raise
// an assertion error if the attribute's
// value does not match the expectation.
module.exports.value = function(attribute, expectation) {
  this.attribute = attribute;
  this.expectation = expectation;
}

// ExitCode constructs a new assertion on
// the exit code of a test case execution.
//
// This assertion type takes the expected
// exit code and raises an assertion error
// if the real exit code doesn't match the
// expectation.
module.exports.exitCode = function(expectation) {
  this.expectation = expectation;
}

module.exports.value.prototype.assert = function(value) {
  if (this.expectation.constructor.name === "RegExp") {
    assert(this.expectation.test(value), "Expected " + this.attribute + " to match " + this.expectation + " got " + value);
  } else {
    assert.equal(value, this.expectation, "Expected " + this.attribute + " to equal " + this.expectation + " got " + value);
  }
}

module.exports.exitCode.prototype.assert = function(_, code) {
  assert.equal(code, this.expectation, "Expected exit code of " + this.expectation + " got " + code);
}
<|endoftext|>


<commit_before>const inferTypes = require('./typeInference.js')
const updateComment = require('./updateComment.js')

let patterns = []

const buildExplicitCases = (funcdecl, patterndecls) => {
  let patterncases = []
  for (let pattern of patterndecls) {
    let patterncase = {'type': 'SwitchCase', 'consequent': []}
    patterncase.test = pattern.declarations[0].init.params[0]
    let consequent = {'type': 'ReturnStatement'}
    consequent.argument = pattern.declarations[0].init.body
    patterncase.consequent.push(consequent)

    patterncases.push(patterncase)
  }
  return patterncases
}

const buildDefaultCase = funcdecl => {
  let defaultcase = {'type': 'SwitchCase', 'test': null, 'consequent': []}
  let consequent = {'type': 'ReturnStatement'}
  consequent.argument = funcdecl.declarations[0].init.body
  defaultcase.consequent.push(consequent)
  return defaultcase
}

const formMultiPatternAst = (funcdecl, patterndecls, ast) => {
  let funcdeclbody = {'type': 'BlockStatement', 'body': []}
  let blockstmtbody = {'type': 'SwitchStatement', 'cases': []}
  blockstmtbody.discriminant = funcdecl.declarations[0].init.params[0]
  let cases = buildExplicitCases(funcdecl, patterndecls)
  cases.push(buildDefaultCase(funcdecl))
  blockstmtbody.cases = cases

  funcdeclbody.body.push(blockstmtbody)

  funcdecl.declarations[0].init.body = funcdeclbody
  funcdecl.declarations[0].init.expression = false

  return funcdecl
}

const isArrowFuncDecl = decl => {
  let declarations = decl.declarations

  if (declarations !== undefined && declarations[0].init.type === 'ArrowFunctionExpression') return true
  return false
}

const isFunction = decl => {
  let declarations = decl.declarations
  let funcexp = declarations[0].init
  let params = funcexp.params

  let isFunc = true
  for (let param of params) {
    if (param.type === 'Literal') {
      isFunc = false
      break
    }
  }
  return isFunc
}

const formFunctionAst = (funcdecl, patterndecls) => {
  let patternscount = patterndecls.length
  return patternscount === 0 ? funcdecl : formMultiPatternAst(funcdecl, patterndecls)
}

const processSubTree = decl => {
  if (isArrowFuncDecl(decl)) {
    if (isFunction(decl)) { // funcdeclns
      let funcast = formFunctionAst(decl, patterns)
      patterns = []
      return funcast
    } else { // patternfuncdeclns
      patterns.push(decl)
      return null
    }
  }
  return decl
}

const updateAst = ast => {
  let newbody = []
  let declarations = ast.body

  declarations.forEach(decl => {
    let subtree = processSubTree(decl)
    if (subtree !== null) newbody.push(subtree)
  })
  ast.body = inferTypes(newbody)
  ast.body = updateComment(newbody)
  return ast
}

/*  Module Exports estemplate  */
module.exports = updateAst
<commit_msg>Pass `ast` to `updateComment`<commit_after>const inferTypes = require('./typeInference.js')
const updateComment = require('./updateComment.js')

let patterns = []

const buildExplicitCases = (funcdecl, patterndecls) => {
  let patterncases = []
  for (let pattern of patterndecls) {
    let patterncase = {'type': 'SwitchCase', 'consequent': []}
    patterncase.test = pattern.declarations[0].init.params[0]
    let consequent = {'type': 'ReturnStatement'}
    consequent.argument = pattern.declarations[0].init.body
    patterncase.consequent.push(consequent)

    patterncases.push(patterncase)
  }
  return patterncases
}

const buildDefaultCase = funcdecl => {
  let defaultcase = {'type': 'SwitchCase', 'test': null, 'consequent': []}
  let consequent = {'type': 'ReturnStatement'}
  consequent.argument = funcdecl.declarations[0].init.body
  defaultcase.consequent.push(consequent)
  return defaultcase
}

const formMultiPatternAst = (funcdecl, patterndecls, ast) => {
  let funcdeclbody = {'type': 'BlockStatement', 'body': []}
  let blockstmtbody = {'type': 'SwitchStatement', 'cases': []}
  blockstmtbody.discriminant = funcdecl.declarations[0].init.params[0]
  let cases = buildExplicitCases(funcdecl, patterndecls)
  cases.push(buildDefaultCase(funcdecl))
  blockstmtbody.cases = cases

  funcdeclbody.body.push(blockstmtbody)

  funcdecl.declarations[0].init.body = funcdeclbody
  funcdecl.declarations[0].init.expression = false

  return funcdecl
}

const isArrowFuncDecl = decl => {
  let declarations = decl.declarations

  if (declarations !== undefined && declarations[0].init.type === 'ArrowFunctionExpression') return true
  return false
}

const isFunction = decl => {
  let declarations = decl.declarations
  let funcexp = declarations[0].init
  let params = funcexp.params

  let isFunc = true
  for (let param of params) {
    if (param.type === 'Literal') {
      isFunc = false
      break
    }
  }
  return isFunc
}

const formFunctionAst = (funcdecl, patterndecls) => {
  let patternscount = patterndecls.length
  return patternscount === 0 ? funcdecl : formMultiPatternAst(funcdecl, patterndecls)
}

const processSubTree = decl => {
  if (isArrowFuncDecl(decl)) {
    if (isFunction(decl)) { // funcdeclns
      let funcast = formFunctionAst(decl, patterns)
      patterns = []
      return funcast
    } else { // patternfuncdeclns
      patterns.push(decl)
      return null
    }
  }
  return decl
}

const updateAst = ast => {
  let newbody = []
  let declarations = ast.body

  declarations.forEach(decl => {
    let subtree = processSubTree(decl)
    if (subtree !== null) newbody.push(subtree)
  })
  ast.body = inferTypes(newbody)
  ast.body = updateComment(newbody, ast)
  return ast
}

/*  Module Exports estemplate  */
module.exports = updateAst
<|endoftext|>


<commit_before>var _requestQueue = {};
var _coreFrameHost = 'core.base.network';
var _coreFrameURL = location.protocol + '//' + _coreFrameHost + '/';
var _cache = {};

base.site = {

    isCore: false, // is core frame

    fatal: function(err) {
        if(this.isCore && window.parent !== window) {
            window.parent.postMessage(JSON.stringify({
                command: 'fatal',
                data: err
            }), '*');
        } else {
            document.body.innerHTML = '
' +
                '
base.network FATAL:
' +
                '
' + err + '
' +
                '<button class="btn btn-danger" onclick="location.reload()">RELOAD PAGE</button>' +
            '
';
            throw err;
        }
    },

    init: function() {
        if(document.readyState !== 'complete') {
            return setTimeout(arguments.callee.bind(this));
        }
        this.isCore = _coreFrameHost === location.host;// && parent && parent !== window;

        if(this.isCore) {
            window.base = base;
            base.core.init();

        } else {
            this.initSite();
        }
    },

    initSite: function() {
        // listen messages from base-core-frame
        window.addEventListener("message", this.onCoreResponse.bind(this));

        // append base-core-frame
        this.baseCoreFrame = _.element("IFRAME", document.body, {
            id: 'base-core-frame',
            name: 'base-core-frame',
            src: _coreFrameURL
        });
        this.ex_setStyle();
    },

    requestToCore: function(command, data, callback) {
        if(this.isCore) {
            base.core[command].call(base.core, data, callback);
        } else {
            var id = (Math.random()*0x7fffffff|0).toString(36);
            _requestQueue[id] = callback;
            window.frames["base-core-frame"].postMessage({
                id: id,
                command: command,
                data: data
            }, _coreFrameURL);
        }
    },

    onCoreResponse: function(event) {
        if(event.origin !== location.protocol+'//'+_coreFrameHost) return;

        var resp = event.data, fn;
        if(resp && resp.id && (fn = _requestQueue[resp.id])) {
            delete _requestQueue[resp.id];
            fn.apply(this, resp.args || []);
        }
        if(resp && (fn=this['ex_'+resp.command])) {
            fn.apply(this, resp.args || []);
        }
    },

    ex_reload: function() {
        window.location.reload();
    },

    ex_onNodesInfo: function(nodes) {
        base.site.nodes = nodes;
    },

    ex_onInitCore: function() {
        if(/[?&]_clearcache\b/.test(location.search)) {
            trace('Clear cache');
            delete localStorage.domainInfo;
            delete localStorage.siteInfo;
        }
        this.loadDomainInfo(function(domainInfo) {
            trace('Downloaded Domain info: ', domainInfo);
            base.domainInfo = domainInfo;
            base.domainInfo.cert = base.Certificate.parsePublicCertificate(domainInfo.owner);

            this.loadSiteInfo(function(siteInfo){
                trace('Downloaded Site info: ', siteInfo);
                base.siteInfo = siteInfo;

                if(localStorage.testJS) {
                    trace('!!! INCLUDE TEST JavaScript by URL: ', localStorage.testJS);
                    _.element('script', document.head, {
                        type: "text/javascript",
                        src: localStorage.testJS
                    });
                } else {
                    var jsLib = siteInfo.js; // || "D/...."  - todo: by default jsLib should be link on default js-file
                    jsLib = jsLib || "F/9eef42c1f935a27533d676b0983ecc19b24ccaec18967b308227dcd799c4dc5b.js";
                    // todo: if siteInfo.js is Array then download sequentially
                    trace('loading js-script... ', jsLib);
                    this.requestToCore('requestFile', jsLib, function(err, content, info) {
                        trace('include js-script.', 'err:',err, 'info:', info,'cont:', content);
                        content && _.element('script', document.head, {
                            type:"text/javascript",
                            innerHTML:content
                        });
                    });
                }
            }.bind(this));
        }.bind(this));
    },

    ex_fatal: function(err) {
        this.fatal(err);
    },

    ex_setStyle: function(style) {
        this.baseCoreFrame.setAttribute("style", style || "width:50px; height:50px; position:fixed; z-index:10001; top:0; right:0; border:none;");
    },

    ex_setStatus: function(status) {
        log('Status: '+ status);
    },

    //-------- domain ------------
    loadDomainInfo: function(fn) {
        // load from LS or by request
        var info = _.parseJSON(localStorage.domainInfo);
        if(info) {
            setTimeout(fn.bind(this, info));
            fn = null;
        }
        if(info && (info.ts|0) > +new Date() - 86400e3) return;

        var hostname = location.hostname;
        info = {
            ring: 1,
            name: hostname,
            zone: null,
            registrar: null,
            owner: null
        };
        // is it registrar domain?
        base.registrars.forEach(function(registrar){
            if(hostname === registrar.zone || hostname === registrar.zoneDNS) {
                info.ring = 0;
                info.name = registrar.zone;
                info.zone = registrar.zone;
                info.owner = registrar.cert;
                info.registrar = registrar;
                return;
            }
            function check(zone) {
                if(hostname.substr(-zone.length-1) === "."+zone) {
                    info.name = hostname.substr(0, hostname.length - zone.length) + registrar.zone;
                    info.zone = registrar.zone;
                    info.registrar = registrar;
                    return true;
                }
            }
            check(registrar.zoneDNS) || check(registrar.zone);
        });
        if(info.owner) { // it is registrar domain
            return fn && setTimeout(fn.bind(this, info));
        }
        // load domain info by request
        var req = {
            storage: "N",
            ring: 0,
            uid: info.name
            // todo: use browser cache. request with expire param
        };
        this.requestToCore("requestData", req, function(err, packs) {
            if(err || !packs.length) this.fatal('Can not load domain-info '+info.name+'. (Error: '+(err||'Empty response')+')');
            var data = packs[0].data;
            info.owner = data.owner;
            if(!info.owner) this.fatal('Can not load domain-info '+info.name);
            if(data.ring !== undefined) info.ring = data.ring|0;
            info.ts = +new Date();
            localStorage.domainInfo = JSON.stringify(info);
            fn && fn(info);
        }.bind(this));
    },

    loadSiteInfo: function(fn) {
        // load from LS or by request
        var domain = base.domainInfo;
        var owner = domain.owner;
        var info = _.parseJSON(localStorage.siteInfo);
        if(info && info.owner === owner) {
            setTimeout(fn.bind(this, info));
            fn = null;
        }
        if(info && (info.ts|0) > +new Date() - 300e3) return;

        // request to site-info-data
        var req = {
            ring: domain.ring,
            uid: domain.name,
            aid: domain.cert.getID(),
            pos: "",
            cmd: "doc"
        };
        this.requestToCore("requestData", req, function(err, packs) {
            if(err || !packs.length) this.fatal('Can not load site-manifest. ('+domain.name+')');
            var pack = packs[0];
            var info = pack.data;
            info.ts = +new Date();
            info.owner = owner;
            info.ver = pack.ver;
            if(info.ring === undefined) info.ring = domain.ring || 1;
            localStorage.siteInfo = JSON.stringify(info);
            fn && fn(info);
        }.bind(this));
    }
};

base.site.init();
<commit_msg>set default js script<commit_after>var _requestQueue = {};
var _coreFrameHost = 'core.base.network';
var _coreFrameURL = location.protocol + '//' + _coreFrameHost + '/';
var _cache = {};

base.site = {

    isCore: false, // is core frame

    fatal: function(err) {
        if(this.isCore && window.parent !== window) {
            window.parent.postMessage(JSON.stringify({
                command: 'fatal',
                data: err
            }), '*');
        } else {
            document.body.innerHTML = '
' +
                '
base.network FATAL:
' +
                '
' + err + '
' +
                '<button class="btn btn-danger" onclick="location.reload()">RELOAD PAGE</button>' +
            '
';
            throw err;
        }
    },

    init: function() {
        if(document.readyState !== 'complete') {
            return setTimeout(arguments.callee.bind(this));
        }
        this.isCore = _coreFrameHost === location.host;// && parent && parent !== window;

        if(this.isCore) {
            window.base = base;
            base.core.init();

        } else {
            this.initSite();
        }
    },

    initSite: function() {
        // listen messages from base-core-frame
        window.addEventListener("message", this.onCoreResponse.bind(this));

        // append base-core-frame
        this.baseCoreFrame = _.element("IFRAME", document.body, {
            id: 'base-core-frame',
            name: 'base-core-frame',
            src: _coreFrameURL
        });
        this.ex_setStyle();
    },

    requestToCore: function(command, data, callback) {
        if(this.isCore) {
            base.core[command].call(base.core, data, callback);
        } else {
            var id = (Math.random()*0x7fffffff|0).toString(36);
            _requestQueue[id] = callback;
            window.frames["base-core-frame"].postMessage({
                id: id,
                command: command,
                data: data
            }, _coreFrameURL);
        }
    },

    onCoreResponse: function(event) {
        if(event.origin !== location.protocol+'//'+_coreFrameHost) return;

        var resp = event.data, fn;
        if(resp && resp.id && (fn = _requestQueue[resp.id])) {
            delete _requestQueue[resp.id];
            fn.apply(this, resp.args || []);
        }
        if(resp && (fn=this['ex_'+resp.command])) {
            fn.apply(this, resp.args || []);
        }
    },

    ex_reload: function() {
        window.location.reload();
    },

    ex_onNodesInfo: function(nodes) {
        base.site.nodes = nodes;
    },

    ex_onInitCore: function() {
        if(/[?&]_clearcache\b/.test(location.search)) {
            trace('Clear cache');
            delete localStorage.domainInfo;
            delete localStorage.siteInfo;
        }
        this.loadDomainInfo(function(domainInfo) {
            trace('Downloaded Domain info: ', domainInfo);
            base.domainInfo = domainInfo;
            base.domainInfo.cert = base.Certificate.parsePublicCertificate(domainInfo.owner);

            this.loadSiteInfo(function(siteInfo){
                trace('Downloaded Site info: ', siteInfo);
                base.siteInfo = siteInfo;

                if(localStorage.testJS) {
                    trace('!!! INCLUDE TEST JavaScript by URL: ', localStorage.testJS);
                    _.element('script', document.head, {
                        type: "text/javascript",
                        src: localStorage.testJS
                    });
                } else {
                    var jsLib = siteInfo.js; // || "D/...."  - todo: by default jsLib should be link on default js-file
                    jsLib = jsLib || "F/30f1c5104a6eb6ee6ba3a32e01de4478a9b1cc0d433f157660e208891e05e2e8.js";
                    // todo: if siteInfo.js is Array then download sequentially
                    trace('loading js-script... ', jsLib);
                    this.requestToCore('requestFile', jsLib, function(err, content, info) {
                        trace('include js-script.', 'err:',err, 'info:', info,'cont:', content);
                        content && _.element('script', document.head, {
                            type:"text/javascript",
                            innerHTML:content
                        });
                    });
                }
            }.bind(this));
        }.bind(this));
    },

    ex_fatal: function(err) {
        this.fatal(err);
    },

    ex_setStyle: function(style) {
        this.baseCoreFrame.setAttribute("style", style || "width:50px; height:50px; position:fixed; z-index:10001; top:0; right:0; border:none;");
    },

    ex_setStatus: function(status) {
        log('Status: '+ status);
    },

    //-------- domain ------------
    loadDomainInfo: function(fn) {
        // load from LS or by request
        var info = _.parseJSON(localStorage.domainInfo);
        if(info) {
            setTimeout(fn.bind(this, info));
            fn = null;
        }
        if(info && (info.ts|0) > +new Date() - 86400e3) return;

        var hostname = location.hostname;
        info = {
            ring: 1,
            name: hostname,
            zone: null,
            registrar: null,
            owner: null
        };
        // is it registrar domain?
        base.registrars.forEach(function(registrar){
            if(hostname === registrar.zone || hostname === registrar.zoneDNS) {
                info.ring = 0;
                info.name = registrar.zone;
                info.zone = registrar.zone;
                info.owner = registrar.cert;
                info.registrar = registrar;
                return;
            }
            function check(zone) {
                if(hostname.substr(-zone.length-1) === "."+zone) {
                    info.name = hostname.substr(0, hostname.length - zone.length) + registrar.zone;
                    info.zone = registrar.zone;
                    info.registrar = registrar;
                    return true;
                }
            }
            check(registrar.zoneDNS) || check(registrar.zone);
        });
        if(info.owner) { // it is registrar domain
            return fn && setTimeout(fn.bind(this, info));
        }
        // load domain info by request
        var req = {
            storage: "N",
            ring: 0,
            uid: info.name
            // todo: use browser cache. request with expire param
        };
        this.requestToCore("requestData", req, function(err, packs) {
            if(err || !packs.length) this.fatal('Can not load domain-info '+info.name+'. (Error: '+(err||'Empty response')+')');
            var data = packs[0].data;
            info.owner = data.owner;
            if(!info.owner) this.fatal('Can not load domain-info '+info.name);
            if(data.ring !== undefined) info.ring = data.ring|0;
            info.ts = +new Date();
            localStorage.domainInfo = JSON.stringify(info);
            fn && fn(info);
        }.bind(this));
    },

    loadSiteInfo: function(fn) {
        // load from LS or by request
        var domain = base.domainInfo;
        var owner = domain.owner;
        var info = _.parseJSON(localStorage.siteInfo);
        if(info && info.owner === owner) {
            setTimeout(fn.bind(this, info));
            fn = null;
        }
        if(info && (info.ts|0) > +new Date() - 300e3) return;

        // request to site-info-data
        var req = {
            ring: domain.ring,
            uid: domain.name,
            aid: domain.cert.getID(),
            pos: "",
            cmd: "doc"
        };
        this.requestToCore("requestData", req, function(err, packs) {
            if(err || !packs.length) this.fatal('Can not load site-manifest. ('+domain.name+')');
            var pack = packs[0];
            var info = pack.data;
            info.ts = +new Date();
            info.owner = owner;
            info.ver = pack.ver;
            if(info.ring === undefined) info.ring = domain.ring || 1;
            localStorage.siteInfo = JSON.stringify(info);
            fn && fn(info);
        }.bind(this));
    }
};

base.site.init();
<|endoftext|>


<commit_before>
/*!

  blueprint::main
  https://github.com/ingklabs/blueprint/
  Blueprint for a Startup. Middleware, & MVC routing over Node.js

*/

var path = require("path");
var mongoose = require("mongoose");
var models = require("./models");
var controllers = require("./controllers");

blueprint.dry = function() {
  models();
  controllers(blueprint);
};

blueprint.boot = function(port) {
  port = port || 8000;
  blueprint.dry();
  blueprint.listen(port);
};

/* EOF */<commit_msg>use custom router, not express<commit_after>
/*!

  blueprint::main
  https://github.com/ingklabs/blueprint/
  Blueprint for an App. Middleware, & MVC routing over Node.js & Mongoose

*/

// core
var fs  = require("fs");
var url = require("url");
var http = require("http");
var parse = require("url").parse;
var buffer = require("buffer").Buffer;
var path = require("path");

var mongoose = require("mongoose");
var models = require("./models");
var controllers = require("./controllers");

// DRY
blueprint.dry = function() {
  models();
  controllers(blueprint);
};

// routes
var routes = {};

// blueprint(){}
module.exports = blueprint = http.createServer(function(request, response) {
  var path = parse(request.url).pathname;
  if (routes[path] && routes[path][request.method]) {
    handler = routes[path][request.method];
     handler.apply(this, arguments);
  } else {
    // 404!
    notFound.apply(this, arguments);
  };
});

// response.send()
http.ServerResponse.prototype.send = function(message, code, type) {
    var type = type || "text/html";
    if (typeof(message) === "object") {
        message = JSON.stringify(message);
        type = "application/json";
    };
    this.writeHead(code, {
        "Content-Type": type,
    });
    this.end(message);
};

// redirect
http.ServerResponse.prototype.redirect = function(url, code) {
  code = code || 302;
  this.writeHead(code, {
    "Location" : url
  });
  this.end();
};

// add a route, generic
function addRoute(path, method, callback) {
  if (!routes[path]) {
    routes[path] = {};
  };
  routes[path][method] = callback;
};

// GET
blueprint.get = function(path, callback) {
  addRoute(path, "GET", callback);
};

// PUT
blueprint.put = function(path, callback) {
   addRoute(path, "PUT", callback);
};

// POST
blueprint.post = function(path, callback) {
  addRoute(path, "POST", callback);
};

// DELETE
blueprint["delete"] = function(path, callback) {
  addRoute(path, "DELETE", callback);
};

// 404
function notFound(request, response) {
  var no = function() {
    response.redirect("/404.html", 302);
  };
  // mime types
  var fileTypes = {
    ".json" : "application/json",
    ".css"  : "text/css",
    ".png"  : "image/png",
    ".ico"  : "image/x-icon",
    ".html" : "text/html",
  };
  // check for mime types
  var getMime = function(extension) {
    extension = extension.toLowerCase();
    return fileTypes[extension] ? fileTypes[extension] : "application/octet-stream";
  };
  // static file to check for
   var staticFile = path.normalize(__dirname + "/../public" + request.url);
  fs.stat(staticFile, function(error, results) {
    if (error) {
      no();
    } else {
      var mime = getMime(path.extname(staticFile));
      response.writeHead(200, {
        "Content-Type": mime,
         "Content-Length": results.size
      });
      fs.createReadStream(staticFile, {"bufferSize" : 32 * 1024 }).pipe(response);
    };
  });
};

// boot/listen
blueprint.boot = function(port) {
  port = port || 8000;
  blueprint.dry();
  blueprint.listen(port);
};

/* EOF */<|endoftext|>


<commit_before>'use strict';
const fs = require('fs-extra');
const each = require('lodash/each');
const path = require('path');
const abbrev = require('abbrev');
const createDebug = require('debug');
const Command = require('./command');
const findExtensions = require('./utils/find-extensions');

const debug = createDebug('ghost-cli:bootstrap');

// Extra alises for commands
const extraAliases = {
    '--version': 'version',
    '-v': 'version',
    busters: 'buster',
    intsall: 'install',
    insall: 'install',
    status: 'ls',
    upgrade: 'update',
    udpate: 'update'
};

// Ensure if any promises aren't handled correctly then they get logged
process.on('unhandledRejection', (reason, promise) => {
    console.warn('A promise was rejected but did not have a .catch() handler:');
    console.warn(reason && reason.stack || reason || promise);
    throw reason;
});

const bootstrap = {
    /**
     * Discovers any commands inside of an extension folder
     * If a command name conflict occurs, commands will be namespaced according to
     * the extension name, e.g. `ghost exname:commandname`
     *
     * @param {Object} commands Current commands hash
     * @param {string} dir Directory to look for commands in
     * @param {string} extensionName Name of extension
     * @return {Object} Object hash with any discovered commands added
     */
    discoverCommands: function discoverCommands(commands, dir, extensionName) {
        const commandsDir = path.join(dir, 'commands');

        // No commands here if commands dir doesn't exist
        if (!fs.existsSync(commandsDir)) {
            return commands;
        }

        // Read the directory and find the commands
        fs.readdirSync(commandsDir).filter(
            // Don't treat non-js files as commands, but also make sure any commands that are folders
            // with index.js files in them are loaded
            command => path.extname(command) === '.js' || fs.existsSync(path.join(commandsDir, command, 'index.js'))
        ).forEach((command) => {
            const basename = path.basename(command, '.js');
            const commandName = commands[basename] ? `${extensionName}:${basename}` : basename;
            commands[commandName] = path.resolve(commandsDir, basename);
        });

        return commands;
    },

    /**
     * Configure a single command
     *
     * @param {String} commandName name of command
     * @param {String} commandPath path to command file
     * @param {Yargs} yargs Yargs instance
     * @param {Array<String>} array of string aliases for command
     * @param {Array<Object>} array of extensions
     */
    loadCommand: function loadCommand(commandName, commandPath, yargs, aliases, extensions) {
        const CommandClass = require(commandPath);

        if (!(CommandClass.prototype instanceof Command)) {
            // This should only be thrown by extensions, not internal CLI commands,
            // but needs to be here nonetheless
            console.error(`Command class for ${commandName} does not inherit from Ghost-CLI's command class.`);
            return;
        }

        // Configure the command
        CommandClass.configure(commandName, aliases, yargs, extensions);
    },

    /**
     * Runs the CLI!
     * This is where the magic happens
     *
     * @param {Array} Array of string arguments (taken from process.argv)
     * @param Yargs yargs instance
     */
    run: function run(argv, yargs) {
        debug('Starting bootstrap process');
        // Look for any available extensions, including ones built-in to the CLI itself
        const extensions = findExtensions();
        debug(`Found ${extensions.length} extensions: ${extensions.map(({pkg}) => pkg.name).join(', ')}`);

        debug('loading built-in commands');
        // Load built-in commands first
        let commands = bootstrap.discoverCommands({}, __dirname);

        debug('loading commands from extensions');
        // Then load commands from extensions
        extensions.forEach((extension) => {
            commands = bootstrap.discoverCommands(commands, extension.dir, extension.name);
        });

        debug(`discovered commands: ${Object.keys(commands).join(', ')}`);
        // Use abbrev to handle shortnened command names
        let abbreviations = abbrev(Object.keys(commands));
        // Get the first argument so we can not load all the commands at once
        const firstArg = argv.shift();

        // Apply extra aliases
        abbreviations = Object.assign(abbreviations, extraAliases);

        // Special-case `help` because we only want to configure all the commands
        // for this one case
        if (firstArg === 'help' || firstArg === '--help') {
            debug('running help command, requiring and configuring every command');

            each(commands, (commandPath, commandName) => {
                // Don't fetch aliases for help commands to keep the output clean.
                bootstrap.loadCommand(commandName, commandPath, yargs, [], extensions);
            });
            argv.unshift('help');
        } else if (abbreviations[firstArg]) {
            // Command found, instantiate/configure it and run!
            const commandName = abbreviations[firstArg];
            const commandPath = commands[commandName];
            // Get aliases of the command (so it will show up in yargs help)
            const aliases = Object.keys(abbreviations).filter(key => abbreviations[key] === commandName);
            bootstrap.loadCommand(commandName, commandPath, yargs, aliases, extensions);
            // Put the first arg back into the array
            argv.unshift(commandName);
        } else {
            // Command not found :( Error and exit
            console.error(`Unrecognized command: '${firstArg}'. Run \`ghost help\` for usage.`);
            process.exit(1);
        }

        // Yargs magic
        yargs.wrap(Math.min(150, yargs.terminalWidth()))
            .epilogue('For more information, see our docs at https://docs.ghost.org/api/ghost-cli/')
            .option('d', {
                alias: 'dir',
                describe: 'Folder to run command in'
            }).option('D', {
                alias: 'development',
                describe: 'Run in development mode',
                type: 'boolean'
            })
            .option('V', {
                alias: 'verbose',
                describe: 'Enable verbose output',
                type: 'boolean'
            })
            .options('prompt', {
                describe: '[--no-prompt] Allow/Disallow UI prompting',
                type: 'boolean',
                default: true
            })
            .option('color', {
                describe: '[--no-color] Allow/Disallow colorful logging',
                type: 'boolean',
                default: true
            })
            .option('auto', {
                describe: 'Automatically run as much as possible',
                type: 'boolean',
                default: false
            })
            .version(false)
            .parse(argv);
    }
};

module.exports = bootstrap;
<commit_msg>fix(help): Group global options together<commit_after>'use strict';
const fs = require('fs-extra');
const each = require('lodash/each');
const path = require('path');
const abbrev = require('abbrev');
const createDebug = require('debug');
const Command = require('./command');
const findExtensions = require('./utils/find-extensions');

const debug = createDebug('ghost-cli:bootstrap');

// Extra alises for commands
const extraAliases = {
    '--version': 'version',
    '-v': 'version',
    busters: 'buster',
    intsall: 'install',
    insall: 'install',
    status: 'ls',
    upgrade: 'update',
    udpate: 'update'
};

// Ensure if any promises aren't handled correctly then they get logged
process.on('unhandledRejection', (reason, promise) => {
    console.warn('A promise was rejected but did not have a .catch() handler:');
    console.warn(reason && reason.stack || reason || promise);
    throw reason;
});

const bootstrap = {
    /**
     * Discovers any commands inside of an extension folder
     * If a command name conflict occurs, commands will be namespaced according to
     * the extension name, e.g. `ghost exname:commandname`
     *
     * @param {Object} commands Current commands hash
     * @param {string} dir Directory to look for commands in
     * @param {string} extensionName Name of extension
     * @return {Object} Object hash with any discovered commands added
     */
    discoverCommands: function discoverCommands(commands, dir, extensionName) {
        const commandsDir = path.join(dir, 'commands');

        // No commands here if commands dir doesn't exist
        if (!fs.existsSync(commandsDir)) {
            return commands;
        }

        // Read the directory and find the commands
        fs.readdirSync(commandsDir).filter(
            // Don't treat non-js files as commands, but also make sure any commands that are folders
            // with index.js files in them are loaded
            command => path.extname(command) === '.js' || fs.existsSync(path.join(commandsDir, command, 'index.js'))
        ).forEach((command) => {
            const basename = path.basename(command, '.js');
            const commandName = commands[basename] ? `${extensionName}:${basename}` : basename;
            commands[commandName] = path.resolve(commandsDir, basename);
        });

        return commands;
    },

    /**
     * Configure a single command
     *
     * @param {String} commandName name of command
     * @param {String} commandPath path to command file
     * @param {Yargs} yargs Yargs instance
     * @param {Array<String>} array of string aliases for command
     * @param {Array<Object>} array of extensions
     */
    loadCommand: function loadCommand(commandName, commandPath, yargs, aliases, extensions) {
        const CommandClass = require(commandPath);

        if (!(CommandClass.prototype instanceof Command)) {
            // This should only be thrown by extensions, not internal CLI commands,
            // but needs to be here nonetheless
            console.error(`Command class for ${commandName} does not inherit from Ghost-CLI's command class.`);
            return;
        }

        // Configure the command
        CommandClass.configure(commandName, aliases, yargs, extensions);
    },

    /**
     * Runs the CLI!
     * This is where the magic happens
     *
     * @param {Array} Array of string arguments (taken from process.argv)
     * @param Yargs yargs instance
     */
    run: function run(argv, yargs) {
        debug('Starting bootstrap process');
        // Look for any available extensions, including ones built-in to the CLI itself
        const extensions = findExtensions();
        debug(`Found ${extensions.length} extensions: ${extensions.map(({pkg}) => pkg.name).join(', ')}`);

        debug('loading built-in commands');
        // Load built-in commands first
        let commands = bootstrap.discoverCommands({}, __dirname);

        debug('loading commands from extensions');
        // Then load commands from extensions
        extensions.forEach((extension) => {
            commands = bootstrap.discoverCommands(commands, extension.dir, extension.name);
        });

        debug(`discovered commands: ${Object.keys(commands).join(', ')}`);
        // Use abbrev to handle shortnened command names
        let abbreviations = abbrev(Object.keys(commands));
        // Get the first argument so we can not load all the commands at once
        const firstArg = argv.shift();

        // Apply extra aliases
        abbreviations = Object.assign(abbreviations, extraAliases);

        // Special-case `help` because we only want to configure all the commands
        // for this one case
        if (firstArg === 'help' || firstArg === '--help') {
            debug('running help command, requiring and configuring every command');

            each(commands, (commandPath, commandName) => {
                // Don't fetch aliases for help commands to keep the output clean.
                bootstrap.loadCommand(commandName, commandPath, yargs, [], extensions);
            });
            argv.unshift('help');
        } else if (abbreviations[firstArg]) {
            // Command found, instantiate/configure it and run!
            const commandName = abbreviations[firstArg];
            const commandPath = commands[commandName];
            // Get aliases of the command (so it will show up in yargs help)
            const aliases = Object.keys(abbreviations).filter(key => abbreviations[key] === commandName);
            bootstrap.loadCommand(commandName, commandPath, yargs, aliases, extensions);
            // Put the first arg back into the array
            argv.unshift(commandName);
        } else {
            // Command not found :( Error and exit
            console.error(`Unrecognized command: '${firstArg}'. Run \`ghost help\` for usage.`);
            process.exit(1);
        }

        // Yargs magic
        yargs.wrap(Math.min(150, yargs.terminalWidth()))
            .epilogue('For more information, see our docs at https://docs.ghost.org/api/ghost-cli/')
            .group('help', 'Global Options:')
            .option('d', {
                alias: 'dir',
                describe: 'Folder to run command in',
                group: 'Global Options:'
            }).option('D', {
                alias: 'development',
                describe: 'Run in development mode',
                type: 'boolean',
                group: 'Global Options:'
            })
            .option('V', {
                alias: 'verbose',
                describe: 'Enable verbose output',
                type: 'boolean',
                group: 'Global Options:'
            })
            .options('prompt', {
                describe: '[--no-prompt] Allow/Disallow UI prompting',
                type: 'boolean',
                default: true,
                group: 'Global Options:'
            })
            .option('color', {
                describe: '[--no-color] Allow/Disallow colorful logging',
                type: 'boolean',
                default: true,
                group: 'Global Options:'
            })
            .option('auto', {
                describe: 'Automatically run as much as possible',
                type: 'boolean',
                default: false,
                group: 'Global Options:'
            })
            .version(false)
            .parse(argv);
    }
};

module.exports = bootstrap;
<|endoftext|>


<commit_before>/**
 * Bootstrap JavaScript Library
 * (c) 2006 - 2012 Juerg Lehni, http://lehni.org/
 *
 * Bootstrap is released under the MIT license
 * http://bootstrapjs.org/
 *
 * Inspirations:
 * http://dean.edwards.name/weblog/2006/03/base/
 * http://dev.helma.org/Wiki/JavaScript+Inheritance+Sugar/
 * http://prototypejs.org/
 */

var Base = new function() { // Bootstrap scope
	var hidden = /^(statics|generics|preserve|enumerable|prototype|toString|valueOf)$/,
		proto = Object.prototype,
		toString = proto.toString,
		proto = Array.prototype,
		isArray = Array.isArray = Array.isArray || function(obj) {
			return toString.call(obj) === '[object Array]';
		},
		slice = proto.slice,
		forEach = proto.forEach || function(iter, bind) {
			for (var i = 0, l = this.length; i < l; i++)
				iter.call(bind, this[i], i, this);
		},
		forIn = function(iter, bind) {
			// Do not use Object.keys for iteration as iterators might modify
			// the object we're iterating over, making the hasOwnProperty still
			// necessary.
			for (var i in this)
				if (this.hasOwnProperty(i))
					iter.call(bind, this[i], i, this);
		},
		// A ahort cut to a simplified version of Object.create that only
		// supports the first parameter (in the emulation):
		create = Object.create || function(proto) {
			// From all browsers that do not offer Object.create(), we only
			// support Firefox 3.5 & 3.5,  but luckily this hack works there:
			return { __proto__: proto };
		},
		_define = Object.defineProperty,
		_describe = Object.getOwnPropertyDescriptor;

	// Support a mixed environment of some ECMAScript 5 features present,
	// along with __defineGetter/Setter__ functions, as found in browsers today.
	function define(obj, name, desc) {
		// Unfortunately Safari seems to ignore configurable: true and
		// does not override existing properties, so we need to delete
		// first:
		if (_define) {
			try {
				delete obj[name];
				return _define(obj, name, desc);
			} catch (e) {}
		}
		if ((desc.get || desc.set) && obj.__defineGetter__) {
			desc.get && obj.__defineGetter__(name, desc.get);
			desc.set && obj.__defineSetter__(name, desc.set);
		} else {
			obj[name] = desc.value;
		}
		return obj;
	}

	function describe(obj, name) {
		if (_describe) {
			try {
				return _describe(obj, name);
			} catch (e) {}
		}
		var get = obj.__lookupGetter__ && obj.__lookupGetter__(name);
		return get
			? { get: get, set: obj.__lookupSetter__(name), enumerable: true,
					configurable: true }
			: obj.hasOwnProperty(name)
				? { value: obj[name], enumerable: true, configurable: true,
						writable: true }
				: null;
	}

	/**
	 * Private function that injects functions from src into dest, overriding
	 * (and inherinting from) base.
	 */
	function inject(dest, src, enumerable, base, preserve, generics) {
		var beans, bean;

		/**
		 * Private function that injects one field with given name and checks if
		 * the field is a function that needs to be wrapped for calls of base().
		 * This is only needed if the function in base is different from the one
		 * in src, and if the one in src is actually calling base through base.
		 * The string of the function is parsed for this.base to detect calls.
		 */
		function field(name, val, dontCheck, generics) {
			// This does even work for prop: 0, as it will just be looked up
			// again through describe.
			var val = val || (val = describe(src, name))
					&& (val.get ? val : val.value),
				func = typeof val === 'function',
				res = val,
				// Only lookup previous value if we preserve or define a
				// function that might need it for this.base(). If we're
				// defining a getter, don't lookup previous value, but look if
				// the property exists (name in dest) and store result in prev
				prev = preserve || func
					? (val && val.get ? name in dest : dest[name]) : null;
			if ((dontCheck || val !== undefined && src.hasOwnProperty(name))
					&& (!preserve || !prev)) {
				if (func) {
					if (prev && /\bthis\.base\b/.test(val)) {
						var fromBase = base && base[name] == prev;
						res = function() {
							// Look up the base function each time if we can,
							// to reflect changes to the base class after
							// inheritance.
							var tmp = describe(this, 'base');
							define(this, 'base', { value: fromBase
								? base[name] : prev, configurable: true });
							try {
								return val.apply(this, arguments);
							} finally {
								tmp ? define(this, 'base', tmp)
									: delete this.base;
							}
						};
						// Make wrapping closure pretend to be the original
						// function on inspection
						res.toString = function() {
							return val.toString();
						};
						res.valueOf = function() {
							return val.valueOf();
						};
					}
					// Produce bean properties if getters are specified. This
					// does not produce properties for setter-only properties.
					// Just collect beans for now, and look them up in dest at
					// the end of fields injection. This ensures this.base()
					// works in beans too, and inherits setters for redefined
					// getters in subclasses. Only add getter beans if they do
					// not expect arguments. Functions that should function both
					// with optional arguments and as beans should not declare
					// the parameters and use the arguments array internally
					// instead.
					if (beans && val.length === 0
							&& (bean = name.match(/^(get|is)(([A-Z])(.*))$/)))
						beans.push([ bean[3].toLowerCase() + bean[4], bean[2] ]);
				}
				// No need to look up getter if this is a function already.
				if (!res || func || !res.get)
					res = { value: res, writable: true };
				// Only set/change configurable and enumerable if this field is
				// configurable
				if ((describe(dest, name)
						|| { configurable: true }).configurable) {
					res.configurable = true;
					res.enumerable = enumerable;
				}
				define(dest, name, res);
			}
			if (generics && func && (!preserve || !generics[name])) {
				generics[name] = function(bind) {
					// Do not call Array.slice generic here, as on Safari,
					// this seems to confuse scopes (calling another
					// generic from generic-producing code).
					return bind && dest[name].apply(bind,
							slice.call(arguments, 1));
				};
			}
		}
		// Iterate through all definitions in src now and call field() for each.
		if (src) {
			beans = [];
			for (var name in src)
				if (src.hasOwnProperty(name) && !hidden.test(name))
					field(name, null, true, generics);
			// IE (and some other browsers?) never enumerate these, even  if
			// they are simply set on an object. Force their creation. Do not
			// create generics for these, and check them for not being defined
			// (by passing undefined for dontCheck).
			field('toString');
			field('valueOf');
			// Now finally define beans as well. Look up methods on dest, for
			// support of this.base() (See above).
			for (var i = 0, l = beans && beans.length; i < l; i++)
				try {
					var bean = beans[i], part = bean[1];
					field(bean[0], {
						get: dest['get' + part] || dest['is' + part],
						set: dest['set' + part]
					}, true);
				} catch (e) {}
		}
		return dest;
	}

	/**
	 * Converts the argument to an iterator function. If none is specified, the
	 * identity function is returned.
	 * This supports normal functions, which are returned unmodified, and values
	 * to compare to. Wherever this function is used in the Enumerable
	 * functions, a value, a Function or null may be passed.
	 */
	function iterator(iter) {
		return !iter
			? function(val) { return val }
			: typeof iter !== 'function'
				? function(val) { return val == iter }
				: iter;
	}

	function each(obj, iter, bind, asArray) {
		try {
			if (obj)
				(asArray || asArray === undefined && isArray(obj)
					? forEach : forIn).call(obj, iterator(iter),
						bind = bind || obj);
		} catch (e) {
			if (e !== Base.stop) throw e;
		}
		return bind;
	}

	function clone(obj) {
		return each(obj, function(val, i) {
			this[i] = val;
		}, new obj.constructor());
	}

	/**
	 * Checks if two values or objects are equals to each other, by using their
	 * equals() methods if available, and also comparing elements of arrays.
	 */
	function equals(obj1, obj2) {
		if (obj1 == obj2)
			return true;
		if (obj1 != null && obj1.equals)
			return obj1.equals(obj2);
		if (isArray(obj1) && isArray(obj2)) {
			if (obj1.length !== obj2.length)
				return false;
			for (var i = 0, l = obj1.length; i < l; i++) {
				if (!Base.equals(obj1, obj2))
					return false;
			}
			return true;
		}
		if (typeof obj1 === 'object' && typeof obj2 === 'object') {
			function checkKeys(o1, o2) {
				for (var i in o1)
					if (typeof o2[i] === 'undefined')
						return false;
				return true;
			}
			if (!checkKeys(obj1, obj2) || !checkKeys(obj2, obj1))
				return false;
			for (var i in obj1)
				if (obj1.hasOwnProperty(i) && !Base.equals(obj1[i], obj2[i]))
					return false;
			return true;
		}
		return false;
	}

	// Inject into new ctor object that's passed to inject(), and then returned
	return inject(function() {}, {
		inject: function(src/*, ... */) {
			if (src) {
				var proto = this.prototype,
					base = Object.getPrototypeOf(proto).constructor,
					// Allow the whole scope to just define statics by defining
					// statics: true.
					statics = src.statics === true ? src : src.statics;
				if (statics != src)
					inject(proto, src, src.enumerable, base && base.prototype,
							src.preserve, src.generics && this);
				// Define new static fields as enumerable, and inherit from
				// base. enumerable is necessary so they can be copied over from
				// base, and it does not harm to have enumerable properties in
				// the constructor. Use the preserve setting in src.preserve for
				// statics too, not their own.
				inject(this, statics, true, base, src.preserve);
			}
			// If there are more than one argument, loop through them and call
			// inject again. Do not simple inline the above code in one loop,
			// since each of the passed objects might override this.inject.
			for (var i = 1, l = arguments.length; i < l; i++)
				this.inject(arguments[i]);
			return this;
		},

		extend: function(src/* , ... */) {
			var ctor = function() {
				// Call the constructor function, if defined
				if (this.initialize)
					return this.initialize.apply(this, arguments);
			};
			ctor.prototype = create(this.prototype);
			// Add a toString function that delegates to initialize if possible
			ctor.toString = function() {
				return (this.prototype.initialize || function() {}).toString();
			};
			// The new prototype extends the constructor on which extend is
			// called. Fix constructor.
			define(ctor.prototype, 'constructor',
					{ value: ctor, writable: true, configurable: true });
			// Copy over static fields, as prototype-like inheritance
			// is not possible for static fields. Mark them as enumerable
			// so they can be copied over again.
			inject(ctor, this, true);
			// Inject all the definitions in src. Use the new inject instead of
			// the one in ctor, in case it was overriden. this is needed when
			// overriding the static .inject(). But only inject if there's
			// something to actually inject.
			return arguments.length ? this.inject.apply(ctor, arguments) : ctor;
		}
		// Pass true for enumerable, so inject() and extend() can be passed on
		// to subclasses of Base through Base.inject() / extend().
	}, true).inject({
		/**
		 * Injects the fields from the given object, adding this.base()
		 * functionality
		 */
		inject: function(/* src, ... */) {
			for (var i = 0, l = arguments.length; i < l; i++)
				inject(this, arguments[i], arguments[i].enumerable);
			return this;
		},

		/**
		 * Returns a new object that inherits all properties from "this",
		 * through proper JS inheritance, not copying.
		 * Optionally, src and hide parameters can be passed to fill in the
		 * newly created object just like in inject(), to copy the behavior
		 * of Function.prototype.extend.
		 */
		extend: function(/* src, ... */) {
			var res = create(this);
			return res.inject.apply(res, arguments);
		},

		each: function(iter, bind) {
			return each(this, iter, bind);
		},

		/**
		 * Creates a new object of the same type and copies over all
		 * name / value pairs from this object.
		 */
		clone: function() {
			return clone(this);
		},

		statics: {
			// Expose some local privates as Base generics.
			each: each,
			clone: clone,
			equals: equals,
			// Base.create does something different from Object.create, it only
			// works on constructors and uses their prototype.
			create: function(ctor) {
				return create(ctor.prototype);
			},
			define: define,
			describe: describe,
			iterator: iterator,

			check: function(obj) {
				return !!(obj || obj === 0);
			},

			/**
			 * Returns the first argument that is defined.
			 * Null is counted as defined too, since !== undefined is used for
			 * comparisons. In this it differs from Mootools!
			 */
			pick: function() {
				for (var i = 0, l = arguments.length; i < l; i++)
					if (arguments[i] !== undefined)
						return arguments[i];
				return null;
			},

			/**
			 * A special constant, to be thrown by closures passed to each()
			 *
			 * $continue / Base.next is not implemented, as the same
			 * functionality can achieved by using return in the closure.
			 * In prototype, the implementation of $continue also leads to a
			 * huge speed decrease, as the closure is wrapped in another closure
			 * that does nothing else than handling $continue.
			 */
			stop: {}
		}
	});
};
<commit_msg>Further improve Base.equals().<commit_after>/**
 * Bootstrap JavaScript Library
 * (c) 2006 - 2012 Juerg Lehni, http://lehni.org/
 *
 * Bootstrap is released under the MIT license
 * http://bootstrapjs.org/
 *
 * Inspirations:
 * http://dean.edwards.name/weblog/2006/03/base/
 * http://dev.helma.org/Wiki/JavaScript+Inheritance+Sugar/
 * http://prototypejs.org/
 */

var Base = new function() { // Bootstrap scope
	var hidden = /^(statics|generics|preserve|enumerable|prototype|toString|valueOf)$/,
		proto = Object.prototype,
		toString = proto.toString,
		proto = Array.prototype,
		isArray = Array.isArray = Array.isArray || function(obj) {
			return toString.call(obj) === '[object Array]';
		},
		slice = proto.slice,
		forEach = proto.forEach || function(iter, bind) {
			for (var i = 0, l = this.length; i < l; i++)
				iter.call(bind, this[i], i, this);
		},
		forIn = function(iter, bind) {
			// Do not use Object.keys for iteration as iterators might modify
			// the object we're iterating over, making the hasOwnProperty still
			// necessary.
			for (var i in this)
				if (this.hasOwnProperty(i))
					iter.call(bind, this[i], i, this);
		},
		// A ahort cut to a simplified version of Object.create that only
		// supports the first parameter (in the emulation):
		create = Object.create || function(proto) {
			// From all browsers that do not offer Object.create(), we only
			// support Firefox 3.5 & 3.5,  but luckily this hack works there:
			return { __proto__: proto };
		},
		_define = Object.defineProperty,
		_describe = Object.getOwnPropertyDescriptor;

	// Support a mixed environment of some ECMAScript 5 features present,
	// along with __defineGetter/Setter__ functions, as found in browsers today.
	function define(obj, name, desc) {
		// Unfortunately Safari seems to ignore configurable: true and
		// does not override existing properties, so we need to delete
		// first:
		if (_define) {
			try {
				delete obj[name];
				return _define(obj, name, desc);
			} catch (e) {}
		}
		if ((desc.get || desc.set) && obj.__defineGetter__) {
			desc.get && obj.__defineGetter__(name, desc.get);
			desc.set && obj.__defineSetter__(name, desc.set);
		} else {
			obj[name] = desc.value;
		}
		return obj;
	}

	function describe(obj, name) {
		if (_describe) {
			try {
				return _describe(obj, name);
			} catch (e) {}
		}
		var get = obj.__lookupGetter__ && obj.__lookupGetter__(name);
		return get
			? { get: get, set: obj.__lookupSetter__(name), enumerable: true,
					configurable: true }
			: obj.hasOwnProperty(name)
				? { value: obj[name], enumerable: true, configurable: true,
						writable: true }
				: null;
	}

	/**
	 * Private function that injects functions from src into dest, overriding
	 * (and inherinting from) base.
	 */
	function inject(dest, src, enumerable, base, preserve, generics) {
		var beans, bean;

		/**
		 * Private function that injects one field with given name and checks if
		 * the field is a function that needs to be wrapped for calls of base().
		 * This is only needed if the function in base is different from the one
		 * in src, and if the one in src is actually calling base through base.
		 * The string of the function is parsed for this.base to detect calls.
		 */
		function field(name, val, dontCheck, generics) {
			// This does even work for prop: 0, as it will just be looked up
			// again through describe.
			var val = val || (val = describe(src, name))
					&& (val.get ? val : val.value),
				func = typeof val === 'function',
				res = val,
				// Only lookup previous value if we preserve or define a
				// function that might need it for this.base(). If we're
				// defining a getter, don't lookup previous value, but look if
				// the property exists (name in dest) and store result in prev
				prev = preserve || func
					? (val && val.get ? name in dest : dest[name]) : null;
			if ((dontCheck || val !== undefined && src.hasOwnProperty(name))
					&& (!preserve || !prev)) {
				if (func) {
					if (prev && /\bthis\.base\b/.test(val)) {
						var fromBase = base && base[name] == prev;
						res = function() {
							// Look up the base function each time if we can,
							// to reflect changes to the base class after
							// inheritance.
							var tmp = describe(this, 'base');
							define(this, 'base', { value: fromBase
								? base[name] : prev, configurable: true });
							try {
								return val.apply(this, arguments);
							} finally {
								tmp ? define(this, 'base', tmp)
									: delete this.base;
							}
						};
						// Make wrapping closure pretend to be the original
						// function on inspection
						res.toString = function() {
							return val.toString();
						};
						res.valueOf = function() {
							return val.valueOf();
						};
					}
					// Produce bean properties if getters are specified. This
					// does not produce properties for setter-only properties.
					// Just collect beans for now, and look them up in dest at
					// the end of fields injection. This ensures this.base()
					// works in beans too, and inherits setters for redefined
					// getters in subclasses. Only add getter beans if they do
					// not expect arguments. Functions that should function both
					// with optional arguments and as beans should not declare
					// the parameters and use the arguments array internally
					// instead.
					if (beans && val.length === 0
							&& (bean = name.match(/^(get|is)(([A-Z])(.*))$/)))
						beans.push([ bean[3].toLowerCase() + bean[4], bean[2] ]);
				}
				// No need to look up getter if this is a function already.
				if (!res || func || !res.get)
					res = { value: res, writable: true };
				// Only set/change configurable and enumerable if this field is
				// configurable
				if ((describe(dest, name)
						|| { configurable: true }).configurable) {
					res.configurable = true;
					res.enumerable = enumerable;
				}
				define(dest, name, res);
			}
			if (generics && func && (!preserve || !generics[name])) {
				generics[name] = function(bind) {
					// Do not call Array.slice generic here, as on Safari,
					// this seems to confuse scopes (calling another
					// generic from generic-producing code).
					return bind && dest[name].apply(bind,
							slice.call(arguments, 1));
				};
			}
		}
		// Iterate through all definitions in src now and call field() for each.
		if (src) {
			beans = [];
			for (var name in src)
				if (src.hasOwnProperty(name) && !hidden.test(name))
					field(name, null, true, generics);
			// IE (and some other browsers?) never enumerate these, even  if
			// they are simply set on an object. Force their creation. Do not
			// create generics for these, and check them for not being defined
			// (by passing undefined for dontCheck).
			field('toString');
			field('valueOf');
			// Now finally define beans as well. Look up methods on dest, for
			// support of this.base() (See above).
			for (var i = 0, l = beans && beans.length; i < l; i++)
				try {
					var bean = beans[i], part = bean[1];
					field(bean[0], {
						get: dest['get' + part] || dest['is' + part],
						set: dest['set' + part]
					}, true);
				} catch (e) {}
		}
		return dest;
	}

	/**
	 * Converts the argument to an iterator function. If none is specified, the
	 * identity function is returned.
	 * This supports normal functions, which are returned unmodified, and values
	 * to compare to. Wherever this function is used in the Enumerable
	 * functions, a value, a Function or null may be passed.
	 */
	function iterator(iter) {
		return !iter
			? function(val) { return val }
			: typeof iter !== 'function'
				? function(val) { return val == iter }
				: iter;
	}

	function each(obj, iter, bind, asArray) {
		try {
			if (obj)
				(asArray || asArray === undefined && isArray(obj)
					? forEach : forIn).call(obj, iterator(iter),
						bind = bind || obj);
		} catch (e) {
			if (e !== Base.stop) throw e;
		}
		return bind;
	}

	function clone(obj) {
		return each(obj, function(val, i) {
			this[i] = val;
		}, new obj.constructor());
	}

	/**
	 * Checks if two values or objects are equals to each other, by using their
	 * equals() methods if available, and also comparing elements of arrays
	 * and properties of objects.
	 */
	function equals(obj1, obj2) {
		if (obj1 == obj2)
			return true;
		// Call #equals() on both obj1 and obj2
		if (obj1 != null && obj1.equals)
			return obj1.equals(obj2);
		if (obj2 != null && obj2.equals)
			return obj2.equals(obj1);
		// Compare arrays
		if (isArray(obj1) && isArray(obj2)) {
			if (obj1.length !== obj2.length)
				return false;
			for (var i = 0, l = obj1.length; i < l; i++) {
				if (!Base.equals(obj1, obj2))
					return false;
			}
			return true;
		}
		// Compare objects
		if (typeof obj1 === 'object' && typeof obj2 === 'object') {
			function checkKeys(o1, o2) {
				for (var i in o1)
					if (typeof o2[i] === 'undefined')
						return false;
				return true;
			}
			if (!checkKeys(obj1, obj2) || !checkKeys(obj2, obj1))
				return false;
			for (var i in obj1) {
				if (obj1.hasOwnProperty(i) && !Base.equals(obj1[i], obj2[i]))
					return false;
			}
			return true;
		}
		return false;
	}

	// Inject into new ctor object that's passed to inject(), and then returned
	return inject(function() {}, {
		inject: function(src/*, ... */) {
			if (src) {
				var proto = this.prototype,
					base = Object.getPrototypeOf(proto).constructor,
					// Allow the whole scope to just define statics by defining
					// statics: true.
					statics = src.statics === true ? src : src.statics;
				if (statics != src)
					inject(proto, src, src.enumerable, base && base.prototype,
							src.preserve, src.generics && this);
				// Define new static fields as enumerable, and inherit from
				// base. enumerable is necessary so they can be copied over from
				// base, and it does not harm to have enumerable properties in
				// the constructor. Use the preserve setting in src.preserve for
				// statics too, not their own.
				inject(this, statics, true, base, src.preserve);
			}
			// If there are more than one argument, loop through them and call
			// inject again. Do not simple inline the above code in one loop,
			// since each of the passed objects might override this.inject.
			for (var i = 1, l = arguments.length; i < l; i++)
				this.inject(arguments[i]);
			return this;
		},

		extend: function(src/* , ... */) {
			var ctor = function() {
				// Call the constructor function, if defined
				if (this.initialize)
					return this.initialize.apply(this, arguments);
			};
			ctor.prototype = create(this.prototype);
			// Add a toString function that delegates to initialize if possible
			ctor.toString = function() {
				return (this.prototype.initialize || function() {}).toString();
			};
			// The new prototype extends the constructor on which extend is
			// called. Fix constructor.
			define(ctor.prototype, 'constructor',
					{ value: ctor, writable: true, configurable: true });
			// Copy over static fields, as prototype-like inheritance
			// is not possible for static fields. Mark them as enumerable
			// so they can be copied over again.
			inject(ctor, this, true);
			// Inject all the definitions in src. Use the new inject instead of
			// the one in ctor, in case it was overriden. this is needed when
			// overriding the static .inject(). But only inject if there's
			// something to actually inject.
			return arguments.length ? this.inject.apply(ctor, arguments) : ctor;
		}
		// Pass true for enumerable, so inject() and extend() can be passed on
		// to subclasses of Base through Base.inject() / extend().
	}, true).inject({
		/**
		 * Injects the fields from the given object, adding this.base()
		 * functionality
		 */
		inject: function(/* src, ... */) {
			for (var i = 0, l = arguments.length; i < l; i++)
				inject(this, arguments[i], arguments[i].enumerable);
			return this;
		},

		/**
		 * Returns a new object that inherits all properties from "this",
		 * through proper JS inheritance, not copying.
		 * Optionally, src and hide parameters can be passed to fill in the
		 * newly created object just like in inject(), to copy the behavior
		 * of Function.prototype.extend.
		 */
		extend: function(/* src, ... */) {
			var res = create(this);
			return res.inject.apply(res, arguments);
		},

		each: function(iter, bind) {
			return each(this, iter, bind);
		},

		/**
		 * Creates a new object of the same type and copies over all
		 * name / value pairs from this object.
		 */
		clone: function() {
			return clone(this);
		},

		statics: {
			// Expose some local privates as Base generics.
			each: each,
			clone: clone,
			equals: equals,
			// Base.create does something different from Object.create, it only
			// works on constructors and uses their prototype.
			create: function(ctor) {
				return create(ctor.prototype);
			},
			define: define,
			describe: describe,
			iterator: iterator,

			check: function(obj) {
				return !!(obj || obj === 0);
			},

			/**
			 * Returns the first argument that is defined.
			 * Null is counted as defined too, since !== undefined is used for
			 * comparisons. In this it differs from Mootools!
			 */
			pick: function() {
				for (var i = 0, l = arguments.length; i < l; i++)
					if (arguments[i] !== undefined)
						return arguments[i];
				return null;
			},

			/**
			 * A special constant, to be thrown by closures passed to each()
			 *
			 * $continue / Base.next is not implemented, as the same
			 * functionality can achieved by using return in the closure.
			 * In prototype, the implementation of $continue also leads to a
			 * huge speed decrease, as the closure is wrapped in another closure
			 * that does nothing else than handling $continue.
			 */
			stop: {}
		}
	});
};
<|endoftext|>


<commit_before>'use strict';

const { join, sep } = require('path');
const Promise = require('bluebird');
const File = require('./file');
const { Pattern, createSha1Hash } = require('hexo-util');
const { createReadStream, readdir, stat, watch } = require('hexo-fs');
const { magenta } = require('chalk');
const { EventEmitter } = require('events');
const { isMatch, makeRe } = require('micromatch');

const defaultPattern = new Pattern(() => ({}));

class Box extends EventEmitter {
  constructor(ctx, base, options) {
    super();

    this.options = Object.assign({
      persistent: true
    }, options);

    if (!base.endsWith(sep)) {
      base += sep;
    }

    this.context = ctx;
    this.base = base;
    this.processors = [];
    this._processingFiles = {};
    this.watcher = null;
    this.Cache = ctx.model('Cache');
    this.File = this._createFileClass();
    let targets = this.options.ignored || [];
    if (ctx.config.ignore && ctx.config.ignore.length) {
      targets = targets.concat(ctx.config.ignore);
    }
    this.ignore = targets;
    this.options.ignored = targets.map(s => toRegExp(ctx, s)).filter(x => x);
  }
  _createFileClass() {
    const ctx = this.context;

    class _File extends File {
      render(options, callback) {
        if (!callback && typeof options === 'function') {
          callback = options;
          options = {};
        }

        return ctx.render.render({
          path: this.source
        }, options).asCallback(callback);
      }

      renderSync(options) {
        return ctx.render.renderSync({
          path: this.source
        }, options);
      }
    }

    _File.prototype.box = this;

    return _File;
  }

  addProcessor(pattern, fn) {
    if (!fn && typeof pattern === 'function') {
      fn = pattern;
      pattern = defaultPattern;
    }

    if (typeof fn !== 'function') throw new TypeError('fn must be a function');
    if (!(pattern instanceof Pattern)) pattern = new Pattern(pattern);

    this.processors.push({
      pattern,
      process: fn
    });
  }
  _readDir(base, fn, prefix = '') {
    const { ignore } = this;

    if (base && ignore) {
      if (ignore.length && isMatch(base, ignore)) {
        return Promise.resolve([]);
      }
    }

    return readdir(base).map(path => stat(join(base, path)).then(stats => {
      const fullpath = join(base, path);
      if (stats.isDirectory()) {
        return this._readDir(fullpath, fn, `${prefix + path}/`);
      }

      if (ignore && ignore.length && isMatch(fullpath, ignore)) {
        return Promise.resolve([]);
      }

      return this._checkFileStatus(prefix + path).then(file => fn(file).thenReturn(file));
    })).catch(err => {
      if (err && err.code === 'ENOENT') return;
      throw err;
    }).reduce((files, item) => files.concat(item), []);
  }

  _checkFileStatus(path) {
    const { Cache, context: ctx } = this;
    const src = join(this.base, path);

    return Cache.compareFile(
      escapeBackslash(src.substring(ctx.base_dir.length)),
      () => getHash(src),

      () => stat(src)
    ).then(result => ({
      type: result.type,
      path
    }));
  }

  process(callback) {
    const { base, Cache, context: ctx } = this;

    return stat(base).then(stats => {
      if (!stats.isDirectory()) return;

      // Check existing files in cache
      const relativeBase = escapeBackslash(base.substring(ctx.base_dir.length));
      const cacheFiles = Cache.filter(item => item._id.startsWith(relativeBase)).map(item => item._id.substring(relativeBase.length));

      // Read files from directory
      return this._readDir(base, file => this._processFile(file.type, file.path)).map(file => file.path).then(files => // Handle deleted files
        Promise.filter(cacheFiles, path => !files.includes(path)).map(path => this._processFile(File.TYPE_DELETE, path)));
    }).catch(err => {
      if (err && err.code !== 'ENOENT') throw err;
    }).asCallback(callback);
  }

  _processFile(type, path) {
    if (this._processingFiles[path]) {
      return Promise.resolve();
    }

    this._processingFiles[path] = true;

    const { base, File, context: ctx } = this;

    this.emit('processBefore', {
      type,
      path
    });

    return Promise.reduce(this.processors, (count, processor) => {
      const params = processor.pattern.match(path);
      if (!params) return count;

      const file = new File({
        source: join(base, path),
        path,
        params,
        type
      });

      return Reflect.apply(Promise.method(processor.process), ctx, [file])
        .thenReturn(count + 1);
    }, 0).then(count => {
      if (count) {
        ctx.log.debug('Processed: %s', magenta(path));
      }

      this.emit('processAfter', {
        type,
        path
      });
    }).catch(err => {
      ctx.log.error({err}, 'Process failed: %s', magenta(path));
    }).finally(() => {
      this._processingFiles[path] = false;
    }).thenReturn(path);
  }

  watch(callback) {
    if (this.isWatching()) {
      return Promise.reject(new Error('Watcher has already started.')).asCallback(callback);
    }

    const { base } = this;

    function getPath(path) {
      return escapeBackslash(path.substring(base.length));
    }

    return this.process().then(() => watch(base, this.options)).then(watcher => {
      this.watcher = watcher;

      watcher.on('add', path => {
        this._processFile(File.TYPE_CREATE, getPath(path));
      });

      watcher.on('change', path => {
        this._processFile(File.TYPE_UPDATE, getPath(path));
      });

      watcher.on('unlink', path => {
        this._processFile(File.TYPE_DELETE, getPath(path));
      });

      watcher.on('addDir', path => {
        let prefix = getPath(path);
        if (prefix) prefix += '/';

        this._readDir(path, file => this._processFile(file.type, file.path), prefix);
      });
    }).asCallback(callback);
  }

  unwatch() {
    if (!this.isWatching()) return;

    this.watcher.close();
    this.watcher = null;
  }

  isWatching() {
    return Boolean(this.watcher);
  }
}

function escapeBackslash(path) {
  // Replace backslashes on Windows
  return path.replace(/\\/g, '/');
}

function getHash(path) {
  return new Promise((resolve, reject) => {
    const src = createReadStream(path);
    const hasher = createSha1Hash();
    let hash_value;

    src.pipe(hasher)
      .on('data', chunk => {
        hash_value = chunk.toString('hex');
      })
      .on('end', () => {
        resolve(hash_value);
      })
      .on('error', reject);
  });
}

function toRegExp(ctx, arg) {
  if (!arg) return null;
  if (typeof arg !== 'string') {
    ctx.log.warn('A value of "ignore:" section in "_config.yml" is not invalid (not a string)');
    return null;
  }
  const result = makeRe(arg);
  if (!result) {
    ctx.log.warn('A value of "ignore:" section in "_config.yml" can not be converted to RegExp:' + arg);
    return null;
  }
  return result;
}

module.exports = Box;
<commit_msg>Remove Array#reduce from Box#_readdir<commit_after>'use strict';

const { join, sep } = require('path');
const Promise = require('bluebird');
const File = require('./file');
const { Pattern, createSha1Hash } = require('hexo-util');
const { createReadStream, readdir, stat, watch } = require('hexo-fs');
const { magenta } = require('chalk');
const { EventEmitter } = require('events');
const { isMatch, makeRe } = require('micromatch');

const defaultPattern = new Pattern(() => ({}));

class Box extends EventEmitter {
  constructor(ctx, base, options) {
    super();

    this.options = Object.assign({
      persistent: true
    }, options);

    if (!base.endsWith(sep)) {
      base += sep;
    }

    this.context = ctx;
    this.base = base;
    this.processors = [];
    this._processingFiles = {};
    this.watcher = null;
    this.Cache = ctx.model('Cache');
    this.File = this._createFileClass();
    let targets = this.options.ignored || [];
    if (ctx.config.ignore && ctx.config.ignore.length) {
      targets = targets.concat(ctx.config.ignore);
    }
    this.ignore = targets;
    this.options.ignored = targets.map(s => toRegExp(ctx, s)).filter(x => x);
  }
  _createFileClass() {
    const ctx = this.context;

    class _File extends File {
      render(options, callback) {
        if (!callback && typeof options === 'function') {
          callback = options;
          options = {};
        }

        return ctx.render.render({
          path: this.source
        }, options).asCallback(callback);
      }

      renderSync(options) {
        return ctx.render.renderSync({
          path: this.source
        }, options);
      }
    }

    _File.prototype.box = this;

    return _File;
  }

  addProcessor(pattern, fn) {
    if (!fn && typeof pattern === 'function') {
      fn = pattern;
      pattern = defaultPattern;
    }

    if (typeof fn !== 'function') throw new TypeError('fn must be a function');
    if (!(pattern instanceof Pattern)) pattern = new Pattern(pattern);

    this.processors.push({
      pattern,
      process: fn
    });
  }

  _readDir(base, prefix = '') {
    const results = [];
    return readDirWalker(base, results, this.ignore, prefix)
      .return(results)
      .map(path => this._checkFileStatus(path))
      .map(file => this._processFile(file.type, file.path).return(file.path));
  }

  _checkFileStatus(path) {
    const { Cache, context: ctx } = this;
    const src = join(this.base, path);

    return Cache.compareFile(
      escapeBackslash(src.substring(ctx.base_dir.length)),
      () => getHash(src),
      () => stat(src)
    ).then(result => ({
      type: result.type,
      path
    }));
  }

  process(callback) {
    const { base, Cache, context: ctx } = this;

    return stat(base).then(stats => {
      if (!stats.isDirectory()) return;

      // Check existing files in cache
      const relativeBase = escapeBackslash(base.substring(ctx.base_dir.length));
      const cacheFiles = Cache.filter(item => item._id.startsWith(relativeBase)).map(item => item._id.substring(relativeBase.length));

      // Handle deleted files
      return this._readDir(base)
        .then(files => cacheFiles.filter(path => !files.includes(path)))
        .map(path => this._processFile(File.TYPE_DELETE, path));
    }).catch(err => {
      if (err && err.code !== 'ENOENT') throw err;
    }).asCallback(callback);
  }

  _processFile(type, path) {
    if (this._processingFiles[path]) {
      return Promise.resolve();
    }

    this._processingFiles[path] = true;

    const { base, File, context: ctx } = this;

    this.emit('processBefore', {
      type,
      path
    });

    return Promise.reduce(this.processors, (count, processor) => {
      const params = processor.pattern.match(path);
      if (!params) return count;

      const file = new File({
        source: join(base, path),
        path,
        params,
        type
      });

      return Reflect.apply(Promise.method(processor.process), ctx, [file])
        .thenReturn(count + 1);
    }, 0).then(count => {
      if (count) {
        ctx.log.debug('Processed: %s', magenta(path));
      }

      this.emit('processAfter', {
        type,
        path
      });
    }).catch(err => {
      ctx.log.error({err}, 'Process failed: %s', magenta(path));
    }).finally(() => {
      this._processingFiles[path] = false;
    }).thenReturn(path);
  }

  watch(callback) {
    if (this.isWatching()) {
      return Promise.reject(new Error('Watcher has already started.')).asCallback(callback);
    }

    const { base } = this;

    function getPath(path) {
      return escapeBackslash(path.substring(base.length));
    }

    return this.process().then(() => watch(base, this.options)).then(watcher => {
      this.watcher = watcher;

      watcher.on('add', path => {
        this._processFile(File.TYPE_CREATE, getPath(path));
      });

      watcher.on('change', path => {
        this._processFile(File.TYPE_UPDATE, getPath(path));
      });

      watcher.on('unlink', path => {
        this._processFile(File.TYPE_DELETE, getPath(path));
      });

      watcher.on('addDir', path => {
        let prefix = getPath(path);
        if (prefix) prefix += '/';

        this._readDir(path, prefix);
      });
    }).asCallback(callback);
  }

  unwatch() {
    if (!this.isWatching()) return;

    this.watcher.close();
    this.watcher = null;
  }

  isWatching() {
    return Boolean(this.watcher);
  }
}

function escapeBackslash(path) {
  // Replace backslashes on Windows
  return path.replace(/\\/g, '/');
}

function getHash(path) {
  return new Promise((resolve, reject) => {
    const src = createReadStream(path);
    const hasher = createSha1Hash();
    let hash_value;

    src.pipe(hasher)
      .on('data', chunk => {
        hash_value = chunk.toString('hex');
      })
      .on('end', () => {
        resolve(hash_value);
      })
      .on('error', reject);
  });
}

function toRegExp(ctx, arg) {
  if (!arg) return null;
  if (typeof arg !== 'string') {
    ctx.log.warn('A value of "ignore:" section in "_config.yml" is not invalid (not a string)');
    return null;
  }
  const result = makeRe(arg);
  if (!result) {
    ctx.log.warn('A value of "ignore:" section in "_config.yml" can not be converted to RegExp:' + arg);
    return null;
  }
  return result;
}

function isIgnoreMatch(path, ignore) {
  return path && ignore && ignore.length && isMatch(path, ignore);
}

function readDirWalker(base, results, ignore, prefix) {
  if (isIgnoreMatch(base, ignore)) return Promise.resolve();

  return Promise.map(readdir(base).catch(err => {
    if (err && err.code === 'ENOENT') return [];
    throw err;
  }), async path => {
    const fullpath = join(base, path);
    const stats = await stat(fullpath);
    const prefixdPath = `${prefix}${path}`;
    if (stats.isDirectory()) {
      return readDirWalker(fullpath, results, ignore, `${prefixdPath}/`);
    }
    if (!isIgnoreMatch(fullpath, ignore)) {
      results.push(prefixdPath);
    }
  });
}

module.exports = Box;
<|endoftext|>