<commit_before>import events from 'events';
import fs from 'fs';
import _ from 'lodash';
import chalk from 'chalk';
import parseJSON from 'parse-json';
import log from '../../lib/log';

const PREFIX = '[configstore]';

const defaultState = { // default state
    checkForUpdates: true
};

class ConfigStore extends events.EventEmitter {
    file = '';
    config = {};
    watcher = null;

    // @param {string} file The path to a filename.
    // @return {object} The config object.
    load(file) {
        this.file = file;
        this.reload();

        if (this.watcher) {
            // Stop watching for changes
            this.watcher.close();
            this.watcher = null;
        }

        try {
            if (!fs.existsSync(this.file)) {
                const content = JSON.stringify({});
                fs.writeFileSync(this.file, content, 'utf8');
            }

            this.watcher = fs.watch(this.file, (eventType, filename) => {
                log.debug(`${PREFIX} fs.watch(eventType='${eventType}', filename='${filename}')`);

                if (eventType === 'change') {
                    log.debug(`${PREFIX} "${filename}" has been changed`);
                    const ok = this.reload();
                    ok && this.emit('change'); // it is ok to emit change event
                }
            });
        } catch (err) {
            log.error(`${PREFIX} err=${err}`);
        }

        return this.config;
    }
    reload() {
        try {
            if (fs.existsSync(this.file)) {
                const content = fs.readFileSync(this.file, 'utf8');
                this.config = parseJSON(content);
            }
        } catch (err) {
            err.fileName = this.file;
            log.error(`${PREFIX} Unable to load data from "${this.file}"`);
            console.error(chalk.red(err));
            return false;
        }

        if (!_.isPlainObject(this.config)) {
            log.error(`${PREFIX} "${this.file}" does not contain valid JSON`);
            this.config = {};
        }

        this.config.state = {
            ...defaultState,
            ...this.config.state
        };

        return true;
    }
    sync() {
        try {
            const content = JSON.stringify(this.config, null, 4);
            fs.writeFileSync(this.file, content, 'utf8');
        } catch (err) {
            log.error(`${PREFIX} Unable to write data to "${this.file}"`);
            return false;
        }

        return true;
    }
    has(key) {
        return _.has(this.config, key);
    }
    get(key, defaultValue) {
        if (!this.config) {
            this.reload();
        }

        return (key !== undefined)
            ? _.get(this.config, key, defaultValue)
            : this.config;
    }
    set(key, value) {
        if (key === undefined) {
            return;
        }

        const ok = this.reload(); // reload before making changes
        _.set(this.config, key, value);
        ok && this.sync(); // it is ok to write
    }
    unset(key) {
        if (key === undefined) {
            return;
        }

        const ok = this.reload(); // reload before making changes
        _.unset(this.config, key);
        ok && this.sync(); // it is ok to write
    }
}

const configstore = new ConfigStore();

export default configstore;
<commit_msg>Emit load & error event with arguments<commit_after>import events from 'events';
import fs from 'fs';
import _ from 'lodash';
import chalk from 'chalk';
import parseJSON from 'parse-json';
import log from '../../lib/log';

const PREFIX = '[configstore]';

const defaultState = { // default state
    checkForUpdates: true
};

class ConfigStore extends events.EventEmitter {
    file = '';
    config = {};
    watcher = null;

    // @param {string} file The path to a filename.
    // @return {object} The config object.
    load(file) {
        this.file = file;
        if (this.reload()) {
            log.debug(`${PREFIX} Loaded Configuration File: "${this.file}"`);
        }
        this.emit('load', this.config); // emit load event

        if (this.watcher) {
            // Stop watching for changes
            this.watcher.close();
            this.watcher = null;
        }

        try {
            if (!fs.existsSync(this.file)) {
                const content = JSON.stringify({});
                fs.writeFileSync(this.file, content, 'utf8');
            }

            this.watcher = fs.watch(this.file, (eventType, filename) => {
                log.debug(`${PREFIX} fs.watch(eventType='${eventType}', filename='${filename}')`);

                if (eventType === 'change') {
                    log.debug(`${PREFIX} "${filename}" has been changed`);
                    const ok = this.reload();
                    ok && this.emit('change', this.config); // it is ok to emit change event
                }
            });
        } catch (err) {
            log.error(`${PREFIX} err=${err}`);
            this.emit('error', err); // emit error event
        }

        return this.config;
    }
    reload() {
        try {
            if (fs.existsSync(this.file)) {
                const content = fs.readFileSync(this.file, 'utf8');
                this.config = parseJSON(content);
            }
        } catch (err) {
            err.fileName = this.file;
            log.error(`${PREFIX} Unable to load data from "${this.file}"`);
            console.error(chalk.red(err));
            this.emit('error', err); // emit error event
            return false;
        }

        if (!_.isPlainObject(this.config)) {
            log.error(`${PREFIX} "${this.file}" does not contain valid JSON`);
            this.config = {};
        }

        this.config.state = {
            ...defaultState,
            ...this.config.state
        };

        return true;
    }
    sync() {
        try {
            const content = JSON.stringify(this.config, null, 4);
            fs.writeFileSync(this.file, content, 'utf8');
        } catch (err) {
            log.error(`${PREFIX} Unable to write data to "${this.file}"`);
            this.emit('error', err); // emit error event
            return false;
        }

        return true;
    }
    has(key) {
        return _.has(this.config, key);
    }
    get(key, defaultValue) {
        if (!this.config) {
            this.reload();
        }

        return (key !== undefined)
            ? _.get(this.config, key, defaultValue)
            : this.config;
    }
    set(key, value) {
        if (key === undefined) {
            return;
        }

        const ok = this.reload(); // reload before making changes
        _.set(this.config, key, value);
        ok && this.sync(); // it is ok to write
    }
    unset(key) {
        if (key === undefined) {
            return;
        }

        const ok = this.reload(); // reload before making changes
        _.unset(this.config, key);
        ok && this.sync(); // it is ok to write
    }
}

const configstore = new ConfigStore();

export default configstore;
<|endoftext|>


<commit_before>/**
 * Module dependencies
 */
var cheerio = require('cheerio');
var tableize = require('tableize');

var User = module.exports = function() {
  return {
    info: {
      url: 'https://drupal.org/user/uid',
      extractor: function(body, options, callback) {
        var data = {}
        $ = cheerio.load(body);

        // Get the username.
        data['username'] = $('#page-title').html();

        $('.main h3').each(function() {
          var dl = $(this).next();
          switch ($(this).text()) {
            case 'Personal information':
            case 'History':
              dl.find('dt').each(function() {
                data[$(this).text().toLowerCase()] = $(this).next('dd').text();
              });
              break;
          }
        });

        callback(tableize(data));
      }
    },

    projects: {
      url: 'https://drupal.org/user/uid',
      extractor: function(body, options, callback) {
        var data = {}
        $ = cheerio.load(body);

        $('.main dl .item-list ul li').each(function() {
          var link = $(this).find('a');
          if (link.length) {
            project = User.getProjectInfoFromLink(link);

            // Add commit count.
            project.commits = $(this).text().match(/\(([0-9]*) commit[s]?\)/m)[1];

            data[project.name] = project;
          }
        });

        callback(data);
      }
    },

    commitsCount: {
      url: 'https://drupal.org/user/uid',
      extractor: function(body, options, callback) {
        $ = cheerio.load(body);
        data = $('.profile .item-list li.last').html().replace(/\D+/g, '');
        callback(data);
      }
    },

    commits: {
      url: 'https://drupal.org/user/uid/track/code?page=pagenumber',
      extractor: function(body, options, callback) {
        var data = [];
        $ = cheerio.load(body);
        $('.view-vc-git-project-user-commits .views-row').each(function() {
          var commit = {};

          // Add commit info.
          commit.project = User.getProjectInfoFromLink($(this).find('.commit-global h3 a').first()),
          commit.date = $(this).find('.commit-global h3 a').last().text(),
          commit.hash = $(this).find('.commit-info a').text(),
          commit.branch = $(this).find('.commit-info strong').last().text(),
          commit.message = $(this).find('.views-field-message .field-content').text()

          data.push(commit);
        });

        callback(data);
      }
    },

    uid: {
      url: 'https://drupal.org/search/user/username',
      extractor: function(body, options, callback) {
        $ = cheerio.load(body);
        var data = 0;

        if ($('.user-results .search-result').length) {
          // Get info from first user result.
          firstResult = $('.user-results .search-result').first().find('.title a');
          // Check if the first result is equal to username.
          if (options.username == firstResult.text()) {
            data = firstResult.attr('href').match(/([0-9]*)$/m)[1];
          }
        }

        callback(data);
      }
    },

    // Helper function to extract project information from anchor html.
    // Example: Radix.
    getProjectInfoFromLink: function(link) {
      var project = {};

      // Extract project title and project name.
      project.title = link.text();
      project.name = link.attr('href').match(/^\/[\S]*\/([\S]*)$/m)[1];

      return project;
    }
  }
}();
<commit_msg>Check for users with zero commits<commit_after>/**
 * Module dependencies
 */
var cheerio = require('cheerio');
var tableize = require('tableize');

var User = module.exports = function() {
  return {
    info: {
      url: 'https://drupal.org/user/uid',
      extractor: function(body, options, callback) {
        var data = {}
        $ = cheerio.load(body);

        // Get the username.
        data['username'] = $('#page-title').html();

        $('.main h3').each(function() {
          var dl = $(this).next();
          switch ($(this).text()) {
            case 'Personal information':
            case 'History':
              dl.find('dt').each(function() {
                data[$(this).text().toLowerCase()] = $(this).next('dd').text();
              });
              break;
          }
        });

        callback(tableize(data));
      }
    },

    projects: {
      url: 'https://drupal.org/user/uid',
      extractor: function(body, options, callback) {
        var data = {}
        $ = cheerio.load(body);

        $('.main dl .item-list ul li').each(function() {
          var link = $(this).find('a');
          if (link.length) {
            project = User.getProjectInfoFromLink(link);

            // Add commit count.
            project.commits = $(this).text().match(/\(([0-9]*) commit[s]?\)/m)[1];

            data[project.name] = project;
          }
        });

        callback(data);
      }
    },

    commitsCount: {
      url: 'https://drupal.org/user/uid',
      extractor: function(body, options, callback) {
        $ = cheerio.load(body);
        var count = 0;

        // Get count from body.
        if ($('.profile .item-list li.last').length) {
          count = $('.profile .item-list li.last').html().replace(/\D+/g, '');
        }

        callback({ count: count });
      }
    },

    commits: {
      url: 'https://drupal.org/user/uid/track/code?page=pagenumber',
      extractor: function(body, options, callback) {
        var data = [];
        $ = cheerio.load(body);
        $('.view-vc-git-project-user-commits .views-row').each(function() {
          var commit = {};

          // Add commit info.
          commit.project = User.getProjectInfoFromLink($(this).find('.commit-global h3 a').first()),
          commit.date = $(this).find('.commit-global h3 a').last().text(),
          commit.hash = $(this).find('.commit-info a').text(),
          commit.branch = $(this).find('.commit-info strong').last().text(),
          commit.message = $(this).find('.views-field-message .field-content').text()

          data.push(commit);
        });

        callback(data);
      }
    },

    uid: {
      url: 'https://drupal.org/search/user/username',
      extractor: function(body, options, callback) {
        $ = cheerio.load(body);
        var data = 0;

        if ($('.user-results .search-result').length) {
          // Get info from first user result.
          firstResult = $('.user-results .search-result').first().find('.title a');
          // Check if the first result is equal to username.
          if (options.username == firstResult.text()) {
            data = firstResult.attr('href').match(/([0-9]*)$/m)[1];
          }
        }

        callback(data);
      }
    },

    // Helper function to extract project information from anchor html.
    // Example: Radix.
    getProjectInfoFromLink: function(link) {
      var project = {};

      // Extract project title and project name.
      project.title = link.text();
      project.name = link.attr('href').match(/^\/[\S]*\/([\S]*)$/m)[1];

      return project;
    }
  }
}();
<|endoftext|>


<commit_before>// TODO: add an api to Reify to update cached exports for a module
const ReifyEntry = require('/node_modules/meteor/modules/node_modules/reify/lib/runtime/entry.js')

const SOURCE_URL_PREFIX = "meteor://\u{1f4bb}app";

// Due to the bundler and proxy running in the same node process
// this could possibly be ran after the next build finished
// TODO: the builder should inject a build timestamp in the bundle
let lastUpdated = Date.now();
let appliedChangeSets = [];
let removeErrorMessage = null;

let arch = __meteor_runtime_config__.isModern ? 'web.browser' : 'web.browser.legacy';
let enabled = arch === 'web.browser';

if (!enabled) {
  console.log(`HMR is not supported in ${arch}`);
}

const imported = Object.create(null);
const importedBy = Object.create(null);

if (module._onRequire) {
  module._onRequire({
    before(importedModule, parentId) {
      imported[parentId] = imported[parentId] || new Set();
      imported[parentId].add(importedModule.id);

      importedBy[importedModule.id] = importedBy[importedModule.id] || new Set();
      importedBy[importedModule.id].add(parentId);
    },
  });
}

let pendingReload = () => Reload._reload({ immediateMigration: true });
let mustReload = false;

function handleMessage(message) {
  if (message.type === 'register-failed') {
    if (message.reason === 'wrong-app') {
      console.log('HMR: A different app is running on', Meteor.absoluteUrl());
      console.log('HMR: Once you start this app again reload the page to re-enable HMR');
    } else if (message.reason === 'wrong-secret') {
      // TODO: we could wait until the first update to use hot code push
      // instead of reloading the page immediately in case the user has any
      // client state they want to keep for now.
      console.log('HMR: Have the wrong secret, possibly because Meteor was restarted');
      console.log('HMR: Reloading page to get new secret');
      mustReload = true;
      pendingReload();
    } else {
      console.log(`HMR: Register failed for unknown reason`, message);
    }
    return;
  } else if (message.type === 'app-state') {
    if (removeErrorMessage) {
      removeErrorMessage();
    }

    if (message.state === 'error' && Package['dev-error-overlay']) {
      removeErrorMessage = Package['dev-error-overlay']
        .DevErrorOverlay
        .showMessage('Your app is crashing. Here are the latest logs:', message.log.join('\n'));
    }

    return;
  }

  if (message.type !== 'changes') {
    throw new Error(`Unknown HMR message type ${message.type}`);
  }

  const hasUnreloadable = message.changeSets.find(changeSet => {
    return !changeSet.reloadable;
  });

  if (
    pendingReload &&
    hasUnreloadable ||
    message.changeSets.length === 0
  ) {
    if (message.eager) {
      // This was an attempt to reload before the build finishes
      // If we can't, we will wait until the build finishes to properly handle it
      return;
    }

    console.log('HMR: Unable to do HMR. Falling back to hot code push.')
    // Complete hot code push if we can not do hot module reload
    mustReload = true;
    return pendingReload();
  }

  // In case the user changed how a module works with HMR
  // in one of the earlier change sets, we want to apply each
  // change set one at a time in order.
  const succeeded = message.changeSets.filter(changeSet => {
    return !appliedChangeSets.includes(changeSet.id)
  }).every(changeSet => {
    const applied = applyChangeset(changeSet, message.eager);

    // We don't record if a module is unreplaceable
    // during an eager update so we can retry and
    // handle the failure after the build finishes
    if (applied || !message.eager) {
      appliedChangeSets.push(changeSet.id);
    }

    return applied;
  });

  if (message.eager) {
    // We will ignore any failures at this time
    // and wait to handle them until the build finishes
    return;
  }

  if (!succeeded) {
    if (pendingReload) {
      console.log('HMR: Some changes can not be applied with HMR. Using hot code push.')
      mustReload = true;
      return pendingReload();
    }

    throw new Error('HMR failed and unable to fallback to hot code push?');
  }

  if (message.changeSets.length > 0) {
    lastUpdated = message.changeSets[message.changeSets.length - 1].linkedAt;
  }
}

let socket;
let pendingMessages = [];

function send(message) {
  if (socket) {
    socket.send(JSON.stringify(message));
  } else {
    pendingMessages.push(message);
  }
}

function connect() {
  if (mustReload) {
    // The page will reload, no reason to
    // connect and show more logs in the console
    return;
  }

  let wsUrl = Meteor.absoluteUrl('/__meteor__hmr__/websocket');
  const protocol = wsUrl.startsWith('https://') ? 'wss://' : 'ws://';
  wsUrl = wsUrl.replace(/^.+\/\//, protocol);
  socket = new WebSocket(wsUrl);

  socket.addEventListener('close', function () {
    socket = null;
    console.log('HMR: websocket closed');
    setTimeout(connect, 2000);
  });

  socket.addEventListener('open', function () {
    console.log('HMR: connected');
    socket.send(JSON.stringify({
      type: 'register',
      arch,
      secret: __meteor_runtime_config__._hmrSecret,
      appId: __meteor_runtime_config__.appId,
    }));

    const toSend = pendingMessages.slice();
    pendingMessages = [];

    toSend.forEach(message => {
      send(message);
    });
  });

  socket.addEventListener('message', function (event) {
    handleMessage(JSON.parse(event.data));
  });

  socket.addEventListener('error', console.error);
}

connect();

function requestChanges() {
  send({
    type: 'request-changes',
    arch,
    after: lastUpdated
  });
}

function walkTree(pathParts, tree) {
  const part = pathParts.shift();
  const _module = tree.contents[part];

  if (!_module) {
    console.log('HMR: file does not exist', part, pathParts, _module, tree);
    throw new Error('not-exist');
  }

  if (pathParts.length === 0) {
    return _module;
  }

  return walkTree(pathParts, _module);
}

function findFile(moduleId) {
  return walkTree(moduleId.split('/').slice(1), module._getRoot());
}

// btoa with unicode support
function utoa(data) {
  return btoa(unescape(encodeURIComponent(data)));
}

function createInlineSourceMap(map) {
  return "//# sourceMappingURL=data:application/json;base64," + utoa(JSON.stringify(map));
}

function createModuleContent (code, map) {
  return function () {
    return eval(
      // Wrap the function(require,exports,module){...} expression in
      // parentheses to force it to be parsed as an expression.
      // The sourceURL is treated as a prefix for the sources array
      // in the source map
      "(" + code + ")\n//# sourceURL=" + SOURCE_URL_PREFIX +
      "\n" + createInlineSourceMap(map)
    ).apply(this, arguments);
  }
}

function replaceFileContent(file, contents) {
  // TODO: to replace content in packages, we need an eval function that runs
  // within the package scope, like dynamic imports does.
  const moduleFunction = createModuleContent(contents.code, contents.map, file.module.id);

  file.contents = moduleFunction;
}

function checkModuleAcceptsUpdate(moduleId, checked) {
  checked.add(moduleId);

  if (moduleId === '/' ) {
    return false;
  }

  const file = findFile(moduleId);
  const moduleHot = file.module.hot;
  const moduleAccepts = moduleHot ? moduleHot._canAcceptUpdate() : false;

  if (moduleAccepts !== null) {
    return moduleAccepts;
  }

  let accepts = null;

  // The module did not accept the update. If the update is accepted depends
  // on if the modules that imported this module accept the update.
  importedBy[moduleId].forEach(depId => {
    if (depId === '/' && importedBy[moduleId].size > 1) {
      // This module was eagerly required by Meteor.
      // Meteor won't know if the module can be updated
      // but we can check with the other modules that imported it.
      return;
    }

    if (checked.has(depId)) {
      // There is a circular dependency
      return;
    }

    const depResult = checkModuleAcceptsUpdate(depId, checked);

    if (accepts !== false) {
      accepts = depResult;
    }
  });

  return accepts === null ? false : accepts;
}

function addFiles(addedFiles) {
  addedFiles.forEach(file => {
    const tree = {};
    const segments = file.path.split('/').slice(1);
    const fileName = segments.pop();

    let previous = tree;
    segments.forEach(segment => {
      previous[segment] = previous[segment] || {}
      previous = previous[segment]
    });
    previous[fileName] = createModuleContent(
      file.content.code,
      file.content.map,
      file.path
    );

    meteorInstall(tree, file.meteorInstallOptions);
  });
}

module.constructor.prototype._reset = function (id) {
  const moduleId = id || this.id;
  const file = findFile(moduleId);

  const hotState = file.module._hotState;

  const hotData = {};
  hotState._disposeHandlers.forEach(cb => {
    cb(hotData);
  });

  hotState.data = hotData;
  hotState._disposeHandlers = [];
  hotState._hotAccepts = null;

  // Clear cached exports
  // TODO: check how this affects live bindings for ecmascript modules
  delete file.module.exports;
  const entry = ReifyEntry.getOrCreate(moduleId);
  entry.getters = {};
  entry.setters = {};
  entry.module = null;
  Object.keys(entry.namespace).forEach(key => {
    if (key !== '__esModule') {
      delete entry.namespace[key];
    }
  });

  if (imported[moduleId]) {
    imported[moduleId].forEach(depId => {
      importedBy[depId].delete(moduleId);
    });
    imported[moduleId] = new Set();
  }
}

module.constructor.prototype._replaceModule = function (id, contents) {
  const moduleId = id || this.id;
  const root = this._getRoot();

  let file;
  try {
    file = walkTree(moduleId.split('/').slice(1), root);
  } catch (e) {
    if (e.message === 'not-exist') {
      return null;
    }

    throw e;
  }

  if (!file.contents) {
    // File is a dynamic import that hasn't been loaded
    return;
  }

  replaceFileContent(file, contents);

  if (!file.module.exports) {
    // File hasn't been imported.
    return;
  }
}

function applyChangeset({
  changedFiles,
  addedFiles
}) {
  let canApply = true;
  let toRerun = new Set();

  changedFiles.forEach(({ path }) => {
    const file = findFile(path);

    // Check if the file has been imported. If it hasn't been,
    // we can assume update to it can be accepted
    if (file.module.exports) {
      const checked = new Set();
      const accepts = checkModuleAcceptsUpdate(path, checked);

      if (canApply) {
        canApply = accepts;
        checked.forEach(moduleId => {
          toRerun.add(moduleId);
        });
      }
    }
  });

  if (!canApply) {
    return false;
  }

  changedFiles.forEach(({ content, path }) => {
    module._replaceModule(path, content);
  });

  if (addedFiles.length > 0) {
    addFiles(addedFiles);
  }

  toRerun.forEach(moduleId => {
    const file = findFile(moduleId);
    // clear module caches and hot state
    file.module._reset();
    file.module.loaded = false;
  });

  try {
    toRerun.forEach(moduleId => {
      require(moduleId);
    });
  } catch (error) {
    console.error('HMR: Error while applying changes:', error);
  }

  const updateCount = changedFiles.length + addedFiles.length;
  console.log(`HMR: updated ${updateCount} ${updateCount === 1 ? 'file' : 'files'}`);
  return true;
}

const initialVersions = (__meteor_runtime_config__.autoupdate.versions || {})['web.browser'];
let nonRefreshableVersion = initialVersions.versionNonRefreshable;
let replaceableVersion = initialVersions.versionReplaceable;

Meteor.startup(() => {
  if (!enabled) {
    return;
  }

  Package['autoupdate'].Autoupdate._clientVersions.watch((doc) => {
    if (doc._id !== 'web.browser') {
      return;
    }

    if (nonRefreshableVersion !== doc.versionNonRefreshable) {
      nonRefreshableVersion = doc.versionNonRefreshable;
      console.log('HMR: Some changes can not be applied with HMR. Using hot code push.')
      mustReload = true;
      pendingReload();
    } else if (doc.versionReplaceable !== replaceableVersion) {
      replaceableVersion = doc.versionReplaceable;
      requestChanges();
    }
  });

  // We disable hot code push for js until there were
  // changes that can not be applied through HMR.
  Package['reload'].Reload._onMigrate((tryReload) => {
    if (mustReload) {
      return [true];
    }

    pendingReload = tryReload;
    requestChanges();

    return [false];
  });
});
<commit_msg>Ignore imports from hot-module-replacement package<commit_after>// TODO: add an api to Reify to update cached exports for a module
const ReifyEntry = require('/node_modules/meteor/modules/node_modules/reify/lib/runtime/entry.js')

const SOURCE_URL_PREFIX = "meteor://\u{1f4bb}app";

// Due to the bundler and proxy running in the same node process
// this could possibly be ran after the next build finished
// TODO: the builder should inject a build timestamp in the bundle
let lastUpdated = Date.now();
let appliedChangeSets = [];
let removeErrorMessage = null;

let arch = __meteor_runtime_config__.isModern ? 'web.browser' : 'web.browser.legacy';
let enabled = arch === 'web.browser';

if (!enabled) {
  console.log(`HMR is not supported in ${arch}`);
}

const imported = Object.create(null);
const importedBy = Object.create(null);

if (module._onRequire) {
  module._onRequire({
    before(importedModule, parentId) {
      if (parentId === module.id) {
        // While applying updates we import modules to re-run them.
        // Don't track those imports since we don't want them to affect
        // if a future change to the file can be accepted
        return;
      }
      imported[parentId] = imported[parentId] || new Set();
      imported[parentId].add(importedModule.id);

      importedBy[importedModule.id] = importedBy[importedModule.id] || new Set();
      importedBy[importedModule.id].add(parentId);
    },
  });
}

let pendingReload = () => Reload._reload({ immediateMigration: true });
let mustReload = false;

function handleMessage(message) {
  if (message.type === 'register-failed') {
    if (message.reason === 'wrong-app') {
      console.log('HMR: A different app is running on', Meteor.absoluteUrl());
      console.log('HMR: Once you start this app again reload the page to re-enable HMR');
    } else if (message.reason === 'wrong-secret') {
      // TODO: we could wait until the first update to use hot code push
      // instead of reloading the page immediately in case the user has any
      // client state they want to keep for now.
      console.log('HMR: Have the wrong secret, possibly because Meteor was restarted');
      console.log('HMR: Reloading page to get new secret');
      mustReload = true;
      pendingReload();
    } else {
      console.log(`HMR: Register failed for unknown reason`, message);
    }
    return;
  } else if (message.type === 'app-state') {
    if (removeErrorMessage) {
      removeErrorMessage();
    }

    if (message.state === 'error' && Package['dev-error-overlay']) {
      removeErrorMessage = Package['dev-error-overlay']
        .DevErrorOverlay
        .showMessage('Your app is crashing. Here are the latest logs:', message.log.join('\n'));
    }

    return;
  }

  if (message.type !== 'changes') {
    throw new Error(`Unknown HMR message type ${message.type}`);
  }

  const hasUnreloadable = message.changeSets.find(changeSet => {
    return !changeSet.reloadable;
  });

  if (
    pendingReload &&
    hasUnreloadable ||
    message.changeSets.length === 0
  ) {
    if (message.eager) {
      // This was an attempt to reload before the build finishes
      // If we can't, we will wait until the build finishes to properly handle it
      return;
    }

    console.log('HMR: Unable to do HMR. Falling back to hot code push.')
    // Complete hot code push if we can not do hot module reload
    mustReload = true;
    return pendingReload();
  }

  // In case the user changed how a module works with HMR
  // in one of the earlier change sets, we want to apply each
  // change set one at a time in order.
  const succeeded = message.changeSets.filter(changeSet => {
    return !appliedChangeSets.includes(changeSet.id)
  }).every(changeSet => {
    const applied = applyChangeset(changeSet, message.eager);

    // We don't record if a module is unreplaceable
    // during an eager update so we can retry and
    // handle the failure after the build finishes
    if (applied || !message.eager) {
      appliedChangeSets.push(changeSet.id);
    }

    return applied;
  });

  if (message.eager) {
    // We will ignore any failures at this time
    // and wait to handle them until the build finishes
    return;
  }

  if (!succeeded) {
    if (pendingReload) {
      console.log('HMR: Some changes can not be applied with HMR. Using hot code push.')
      mustReload = true;
      return pendingReload();
    }

    throw new Error('HMR failed and unable to fallback to hot code push?');
  }

  if (message.changeSets.length > 0) {
    lastUpdated = message.changeSets[message.changeSets.length - 1].linkedAt;
  }
}

let socket;
let pendingMessages = [];

function send(message) {
  if (socket) {
    socket.send(JSON.stringify(message));
  } else {
    pendingMessages.push(message);
  }
}

function connect() {
  if (mustReload) {
    // The page will reload, no reason to
    // connect and show more logs in the console
    return;
  }

  let wsUrl = Meteor.absoluteUrl('/__meteor__hmr__/websocket');
  const protocol = wsUrl.startsWith('https://') ? 'wss://' : 'ws://';
  wsUrl = wsUrl.replace(/^.+\/\//, protocol);
  socket = new WebSocket(wsUrl);

  socket.addEventListener('close', function () {
    socket = null;
    console.log('HMR: websocket closed');
    setTimeout(connect, 2000);
  });

  socket.addEventListener('open', function () {
    console.log('HMR: connected');
    socket.send(JSON.stringify({
      type: 'register',
      arch,
      secret: __meteor_runtime_config__._hmrSecret,
      appId: __meteor_runtime_config__.appId,
    }));

    const toSend = pendingMessages.slice();
    pendingMessages = [];

    toSend.forEach(message => {
      send(message);
    });
  });

  socket.addEventListener('message', function (event) {
    handleMessage(JSON.parse(event.data));
  });

  socket.addEventListener('error', console.error);
}

connect();

function requestChanges() {
  send({
    type: 'request-changes',
    arch,
    after: lastUpdated
  });
}

function walkTree(pathParts, tree) {
  const part = pathParts.shift();
  const _module = tree.contents[part];

  if (!_module) {
    console.log('HMR: file does not exist', part, pathParts, _module, tree);
    throw new Error('not-exist');
  }

  if (pathParts.length === 0) {
    return _module;
  }

  return walkTree(pathParts, _module);
}

function findFile(moduleId) {
  return walkTree(moduleId.split('/').slice(1), module._getRoot());
}

// btoa with unicode support
function utoa(data) {
  return btoa(unescape(encodeURIComponent(data)));
}

function createInlineSourceMap(map) {
  return "//# sourceMappingURL=data:application/json;base64," + utoa(JSON.stringify(map));
}

function createModuleContent (code, map) {
  return function () {
    return eval(
      // Wrap the function(require,exports,module){...} expression in
      // parentheses to force it to be parsed as an expression.
      // The sourceURL is treated as a prefix for the sources array
      // in the source map
      "(" + code + ")\n//# sourceURL=" + SOURCE_URL_PREFIX +
      "\n" + createInlineSourceMap(map)
    ).apply(this, arguments);
  }
}

function replaceFileContent(file, contents) {
  // TODO: to replace content in packages, we need an eval function that runs
  // within the package scope, like dynamic imports does.
  const moduleFunction = createModuleContent(contents.code, contents.map, file.module.id);

  file.contents = moduleFunction;
}

function checkModuleAcceptsUpdate(moduleId, checked) {
  checked.add(moduleId);

  if (moduleId === '/' ) {
    return false;
  }

  const file = findFile(moduleId);
  const moduleHot = file.module.hot;
  const moduleAccepts = moduleHot ? moduleHot._canAcceptUpdate() : false;

  if (moduleAccepts !== null) {
    return moduleAccepts;
  }

  let accepts = null;

  // The module did not accept the update. If the update is accepted depends
  // on if the modules that imported this module accept the update.
  importedBy[moduleId].forEach(depId => {
    if (depId === '/' && importedBy[moduleId].size > 1) {
      // This module was eagerly required by Meteor.
      // Meteor won't know if the module can be updated
      // but we can check with the other modules that imported it.
      return;
    }

    if (checked.has(depId)) {
      // There is a circular dependency
      return;
    }

    const depResult = checkModuleAcceptsUpdate(depId, checked);

    if (accepts !== false) {
      accepts = depResult;
    }
  });

  return accepts === null ? false : accepts;
}

function addFiles(addedFiles) {
  addedFiles.forEach(file => {
    const tree = {};
    const segments = file.path.split('/').slice(1);
    const fileName = segments.pop();

    let previous = tree;
    segments.forEach(segment => {
      previous[segment] = previous[segment] || {}
      previous = previous[segment]
    });
    previous[fileName] = createModuleContent(
      file.content.code,
      file.content.map,
      file.path
    );

    meteorInstall(tree, file.meteorInstallOptions);
  });
}

module.constructor.prototype._reset = function (id) {
  const moduleId = id || this.id;
  const file = findFile(moduleId);

  const hotState = file.module._hotState;

  const hotData = {};
  hotState._disposeHandlers.forEach(cb => {
    cb(hotData);
  });

  hotState.data = hotData;
  hotState._disposeHandlers = [];
  hotState._hotAccepts = null;

  // Clear cached exports
  // TODO: check how this affects live bindings for ecmascript modules
  delete file.module.exports;
  const entry = ReifyEntry.getOrCreate(moduleId);
  entry.getters = {};
  entry.setters = {};
  entry.module = null;
  Object.keys(entry.namespace).forEach(key => {
    if (key !== '__esModule') {
      delete entry.namespace[key];
    }
  });

  if (imported[moduleId]) {
    imported[moduleId].forEach(depId => {
      importedBy[depId].delete(moduleId);
    });
    imported[moduleId] = new Set();
  }
}

module.constructor.prototype._replaceModule = function (id, contents) {
  const moduleId = id || this.id;
  const root = this._getRoot();

  let file;
  try {
    file = walkTree(moduleId.split('/').slice(1), root);
  } catch (e) {
    if (e.message === 'not-exist') {
      return null;
    }

    throw e;
  }

  if (!file.contents) {
    // File is a dynamic import that hasn't been loaded
    return;
  }

  replaceFileContent(file, contents);

  if (!file.module.exports) {
    // File hasn't been imported.
    return;
  }
}

function applyChangeset({
  changedFiles,
  addedFiles
}) {
  let canApply = true;
  let toRerun = new Set();

  changedFiles.forEach(({ path }) => {
    const file = findFile(path);

    // Check if the file has been imported. If it hasn't been,
    // we can assume update to it can be accepted
    if (file.module.exports) {
      const checked = new Set();
      const accepts = checkModuleAcceptsUpdate(path, checked);

      if (canApply) {
        canApply = accepts;
        checked.forEach(moduleId => {
          toRerun.add(moduleId);
        });
      }
    }
  });

  if (!canApply) {
    return false;
  }

  changedFiles.forEach(({ content, path }) => {
    module._replaceModule(path, content);
  });

  if (addedFiles.length > 0) {
    addFiles(addedFiles);
  }

  toRerun.forEach(moduleId => {
    const file = findFile(moduleId);
    // clear module caches and hot state
    file.module._reset();
    file.module.loaded = false;
  });

  try {
    toRerun.forEach(moduleId => {
      require(moduleId);
    });
  } catch (error) {
    console.error('HMR: Error while applying changes:', error);
  }

  const updateCount = changedFiles.length + addedFiles.length;
  console.log(`HMR: updated ${updateCount} ${updateCount === 1 ? 'file' : 'files'}`);
  return true;
}

const initialVersions = (__meteor_runtime_config__.autoupdate.versions || {})['web.browser'];
let nonRefreshableVersion = initialVersions.versionNonRefreshable;
let replaceableVersion = initialVersions.versionReplaceable;

Meteor.startup(() => {
  if (!enabled) {
    return;
  }

  Package['autoupdate'].Autoupdate._clientVersions.watch((doc) => {
    if (doc._id !== 'web.browser') {
      return;
    }

    if (nonRefreshableVersion !== doc.versionNonRefreshable) {
      nonRefreshableVersion = doc.versionNonRefreshable;
      console.log('HMR: Some changes can not be applied with HMR. Using hot code push.')
      mustReload = true;
      pendingReload();
    } else if (doc.versionReplaceable !== replaceableVersion) {
      replaceableVersion = doc.versionReplaceable;
      requestChanges();
    }
  });

  // We disable hot code push for js until there were
  // changes that can not be applied through HMR.
  Package['reload'].Reload._onMigrate((tryReload) => {
    if (mustReload) {
      return [true];
    }

    pendingReload = tryReload;
    requestChanges();

    return [false];
  });
});
<|endoftext|>


<commit_before>import React, { useState } from "react";
import { storiesOf } from "@storybook/react";
import { action } from "@storybook/addon-actions";
import Image from "@crave/farmblocks-image";

import Select, { fontSizes } from ".";

const items = [
  { value: "1", label: "Apple" },
  { value: "2", label: "Banana" },
  { value: "3", label: "Pear" },
];

const moreItems = [
  ...items,
  { value: "4", label: "Strawberry" },
  { value: "5", label: "Peach" },
  { value: "6", label: "Coconut" },
];

const imgSrc = "https://picsum.photos/100";

const ValueState = ({ children }) => {
  const [value, setValue] = useState([]);
  return React.Children.map(children, child =>
    React.cloneElement(child, { value, onChange: setValue }),
  );
};

storiesOf("Select Input", module)
  .add("Default", () => (
    <Select
      placeholder="Select fruit"
      label="Fruit"
      items={items}
      onChange={action("onChange")}
    />
  ))
  .add("Default with id", () => (
    <Select
      placeholder="Select fruit"
      id="select-fruit"
      label="Fruit"
      items={items}
      onChange={action("onChange")}
    />
  ))
  .add("With value", () => (
    <Select
      placeholder="Select fruit"
      label="Fruit"
      items={items}
      onChange={action("onChange")}
      value={items[1].value}
    />
  ))
  .add("With errors", () => (
    <Select
      placeholder="Select fruit"
      label="Fruit"
      items={items}
      onChange={action("onChange")}
      validationMessages={["Select at least one option"]}
    />
  ))
  .add("Custom items", () => (
    <Select
      placeholder="Select fruit"
      label="Fruit"
      items={items.map(item => ({ ...item, image: imgSrc }))}
      onChange={action("onChange")}
      renderItem={(item, selected) => (

            {item.label}
            {selected && }

          <Image src={item.image} />

      )}
    />
  ))
  .add("Disabled", () => (
    <Select
      placeholder="Select fruit"
      label="Fruit"
      items={items}
      onChange={action("onChange")}
      value={items[1].value}
      disabled
    />
  ))
  .add("Search (autocomplete) disabled", () => (
    <Select
      placeholder="Select fruit"
      label="Fruit"
      items={items}
      onChange={action("onChange")}
      disableSearch
    />
  ))
  .add("With Images", () => (
    <Select
      placeholder="Select fruit"
      label="Fruit"
      items={items.map((item, index) => ({
        ...item,
        image: `${imgSrc}?image=${index + 1080}`,
      }))}
      onChange={action("onChange")}
    />
  ))
  .add("With Selected Image", () => (
    <Select
      placeholder="Select fruit"
      label="Fruit"
      items={items.map((item, index) => ({
        ...item,
        image: `${imgSrc}?image=${index + 1080}`,
      }))}
      value={items[1].value}
      onChange={action("onChange")}
    />
  ))
  .add("Custom width", () => (
    <Select
      placeholder="Select fruit"
      label="Fruit"
      items={items}
      onChange={action("onChange")}
      width="400px"
    />
  ))
  .add("Empty items", () => (
    <Select
      placeholder="Select fruit"
      label="Fruit"
      value={1}
      onChange={action("onChange")}
      width="400px"
    />
  ))
  .add("Z-index", () => (

        Z-index: 10

      <Select
        placeholder="Z-index: 11"
        label="Fruit"
        items={items}
        onChange={action("onChange")}
        zIndex={11}
      />

        Z-index: 12

  ))
  .add("With max height", () => (
    <Select
      placeholder="Select fruit"
      label="Fruit"
      value={1}
      items={moreItems}
      onChange={action("onChange")}
      maxHeight="150px"
    />
  ))
  .add("Receiving new value", () => {
    class SelectStory extends React.Component {
      state = {
        itemValue: null,
      };

      items = [
        {
          value: 0,
          label: "zero",
        },
        {
          value: 1,
          label: "one",
        },
      ];

      changeToOne = () => {
        this.setState({
          itemValue: this.items[1].value,
        });
      };

      changeToZero = () => {
        this.setState({
          itemValue: this.items[0].value,
        });
      };

      onSelectChange = value => {
        this.setState({
          itemValue: value,
        });
        action("onChange")(value);
      };

      render() {
        return (

            <Select
              placeholder="Select fruit"
              label="Fruit"
              value={this.state.itemValue}
              items={this.items}
              onChange={this.onSelectChange}
              disableSearch
            />
            <button onClick={this.changeToOne}>Change to one</button>
            <button onClick={this.changeToZero}>Change to zero</button>

        );
      }
    }

    return <SelectStory />;
  })
  .add("With more info content", () => (
    <Select
      placeholder="Select fruit"
      label="Regular label"
      moreInfoContent="More info text"
      value={1}
      items={items}
      onChange={action("onChange")}
      width="300px"
    />
  ))
  .add("Small size", () => (
    <Select
      fontSize={fontSizes.SMALL}
      placeholder="Select fruit"
      label="Fruit"
      items={items}
      onChange={action("onChange")}
    />
  ))
  .add("Multi", () => (
    <Select
      multi
      placeholder="Select some fruits"
      items={moreItems}
      onChange={action("onChange")}
      width="500px"
    />
  ))
  .add("Multi + Images", () => (
    <Select
      multi
      placeholder="Select some fruits"
      items={moreItems.map((item, index) => ({
        ...item,
        image: `${imgSrc}?image=${index + 1000}`,
      }))}
      onChange={action("onChange")}
      width="500px"
    />
  ))
  .add("Multi with some selected", () => (
    <Select
      multi
      placeholder="Select some fruits"
      value={["1", "3", "4"]}
      items={moreItems}
      onChange={action("onChange")}
      width="500px"
    />
  ))
  .add("Multi with state", () => (
    <ValueState>
      <Select
        multi
        placeholder="Select some fruits"
        items={moreItems}
        width="500px"
      />
    </ValueState>
  ))
  .add("Multi disabled", () => (
    <Select
      multi
      disabled
      placeholder="Select some fruits"
      value={["1", "3", "4"]}
      items={moreItems}
      onChange={action("onChange")}
      width="500px"
    />
  ))
  .add("Multi without search", () => (
    <Select
      multi
      disableSearch
      placeholder="Select some fruits"
      items={moreItems}
      onChange={action("onChange")}
      width="500px"
    />
  ))
  .add("Multi with errors", () => (
    <Select
      multi
      placeholder="Select some fruits"
      items={moreItems}
      onChange={action("onChange")}
      width="500px"
      validationMessages={["Select at least one option"]}
    />
  ))
  .add("Duplicated labels", () => (
    <Select
      placeholder="Select fruit"
      label="Fruit"
      items={[...items, { ...items[0], value: "999" }]}
      onChange={action("onChange")}
      value="999"
    />
  ));
<commit_msg>docs(Select): group stories for multi-select<commit_after>import React, { useState } from "react";
import { storiesOf } from "@storybook/react";
import { action } from "@storybook/addon-actions";
import Image from "@crave/farmblocks-image";

import Select, { fontSizes } from ".";

const items = [
  { value: "1", label: "Apple" },
  { value: "2", label: "Banana" },
  { value: "3", label: "Pear" },
];

const moreItems = [
  ...items,
  { value: "4", label: "Strawberry" },
  { value: "5", label: "Peach" },
  { value: "6", label: "Coconut" },
];

const imgSrc = "https://picsum.photos/100";
const addImage = (item, index) => ({
  ...item,
  image: `${imgSrc}?image=${index + 1000}`,
});

storiesOf("Select Input", module)
  .add("Default", () => (
    <Select
      placeholder="Select fruit"
      label="Fruit"
      items={items}
      onChange={action("onChange")}
    />
  ))
  .add("Default with id", () => (
    <Select
      placeholder="Select fruit"
      id="select-fruit"
      label="Fruit"
      items={items}
      onChange={action("onChange")}
    />
  ))
  .add("With value", () => (
    <Select
      placeholder="Select fruit"
      label="Fruit"
      items={items}
      onChange={action("onChange")}
      value={items[1].value}
    />
  ))
  .add("With errors", () => (
    <Select
      placeholder="Select fruit"
      label="Fruit"
      items={items}
      onChange={action("onChange")}
      validationMessages={["Select at least one option"]}
    />
  ))
  .add("Custom items", () => (
    <Select
      placeholder="Select fruit"
      label="Fruit"
      items={items.map(item => ({ ...item, image: imgSrc }))}
      onChange={action("onChange")}
      renderItem={(item, selected) => (

            {item.label}
            {selected && }

          <Image src={item.image} />

      )}
    />
  ))
  .add("Disabled", () => (
    <Select
      placeholder="Select fruit"
      label="Fruit"
      items={items}
      onChange={action("onChange")}
      value={items[1].value}
      disabled
    />
  ))
  .add("Search (autocomplete) disabled", () => (
    <Select
      placeholder="Select fruit"
      label="Fruit"
      items={items}
      onChange={action("onChange")}
      disableSearch
    />
  ))
  .add("With Images", () => (
    <Select
      placeholder="Select fruit"
      label="Fruit"
      items={items.map(addImage)}
      onChange={action("onChange")}
    />
  ))
  .add("With Selected Image", () => (
    <Select
      placeholder="Select fruit"
      label="Fruit"
      items={items.map((item, index) => ({
        ...item,
        image: `${imgSrc}?image=${index + 1080}`,
      }))}
      value={items[1].value}
      onChange={action("onChange")}
    />
  ))
  .add("Custom width", () => (
    <Select
      placeholder="Select fruit"
      label="Fruit"
      items={items}
      onChange={action("onChange")}
      width="400px"
    />
  ))
  .add("Empty items", () => (
    <Select
      placeholder="Select fruit"
      label="Fruit"
      value={1}
      onChange={action("onChange")}
      width="400px"
    />
  ))
  .add("Z-index", () => (

        Z-index: 10

      <Select
        placeholder="Z-index: 11"
        label="Fruit"
        items={items}
        onChange={action("onChange")}
        zIndex={11}
      />

        Z-index: 12

  ))
  .add("With max height", () => (
    <Select
      placeholder="Select fruit"
      label="Fruit"
      value={1}
      items={moreItems}
      onChange={action("onChange")}
      maxHeight="150px"
    />
  ))
  .add("Receiving new value", () => {
    class SelectStory extends React.Component {
      state = {
        itemValue: null,
      };

      items = [
        {
          value: 0,
          label: "zero",
        },
        {
          value: 1,
          label: "one",
        },
      ];

      changeToOne = () => {
        this.setState({
          itemValue: this.items[1].value,
        });
      };

      changeToZero = () => {
        this.setState({
          itemValue: this.items[0].value,
        });
      };

      onSelectChange = value => {
        this.setState({
          itemValue: value,
        });
        action("onChange")(value);
      };

      render() {
        return (

            <Select
              placeholder="Select fruit"
              label="Fruit"
              value={this.state.itemValue}
              items={this.items}
              onChange={this.onSelectChange}
              disableSearch
            />
            <button onClick={this.changeToOne}>Change to one</button>
            <button onClick={this.changeToZero}>Change to zero</button>

        );
      }
    }

    return <SelectStory />;
  })
  .add("With more info content", () => (
    <Select
      placeholder="Select fruit"
      label="Regular label"
      moreInfoContent="More info text"
      value={1}
      items={items}
      onChange={action("onChange")}
      width="300px"
    />
  ))
  .add("Small size", () => (
    <Select
      fontSize={fontSizes.SMALL}
      placeholder="Select fruit"
      label="Fruit"
      items={items}
      onChange={action("onChange")}
    />
  ))
  .add("Duplicated labels", () => (
    <Select
      placeholder="Select fruit"
      label="Fruit"
      items={[...items, { ...items[0], value: "999" }]}
      onChange={action("onChange")}
      value="999"
    />
  ));

const ValueState = ({ children }) => {
  const [value, setValue] = useState([]);
  return React.Children.map(children, child =>
    React.cloneElement(child, { value, onChange: setValue }),
  );
};

storiesOf("Select Input/Multi", module)
  .add("Default", () => (
    <Select
      multi
      placeholder="Select some fruits"
      items={moreItems}
      onChange={action("onChange")}
      width="500px"
    />
  ))
  .add("With images", () => (
    <Select
      multi
      placeholder="Select some fruits"
      items={moreItems.map(addImage)}
      onChange={action("onChange")}
      width="500px"
    />
  ))
  .add("With some selected", () => (
    <Select
      multi
      placeholder="Select some fruits"
      value={["1", "3", "4"]}
      items={moreItems}
      onChange={action("onChange")}
      width="500px"
    />
  ))
  .add("With parent handling value", () => (
    <ValueState>
      <Select
        multi
        placeholder="Select some fruits"
        items={moreItems}
        width="500px"
      />
    </ValueState>
  ))
  .add("With images + Parent handling value", () => (
    <ValueState>
      <Select
        multi
        placeholder="Select some fruits"
        items={moreItems.map(addImage)}
        width="500px"
      />
    </ValueState>
  ))
  .add("Disabled", () => (
    <Select
      multi
      disabled
      placeholder="Select some fruits"
      value={["1", "3", "4"]}
      items={moreItems}
      onChange={action("onChange")}
      width="500px"
    />
  ))
  .add("Without search", () => (
    <Select
      multi
      disableSearch
      placeholder="Select some fruits"
      items={moreItems}
      onChange={action("onChange")}
      width="500px"
    />
  ))
  .add("With errors", () => (
    <Select
      multi
      placeholder="Select some fruits"
      items={moreItems}
      onChange={action("onChange")}
      width="500px"
      validationMessages={["Select at least one option"]}
    />
  ));
<|endoftext|>


<commit_before>/**
 * Created by nuintun on 2015/4/27.
 */

'use strict';

var is = require('is');
var path = require('path');
var join = path.join;
var extname = path.extname;
var dirname = path.dirname;
var basename = path.basename;
var relative = path.relative;
var util = require('util');
var colors = require('colors/safe');
var debug = require('debug')('gulp-cmd');

// variable declaration
var cwd = process.cwd();
var OUTBOUNDRE = /(?:^[\\\/]?)\.\.(?:[\\\/]|$)/;
var SPLITRE = /\/((?:\d+\.){2}\d+)(?:\/|$)/;
var PATHSRE = /^([^/:]+)(\/.+)$/;
var VARSRE = /{([^{]+)}/g;

// set debug color use 6
debug.color = 6;

/**
 * parse package form vinyl
 * @param vinyl
 * @param wwwroot
 * @returns {object}
 */
function parsePackage(vinyl, wwwroot){
  var relative = vinyl.relative;

  // vinyl not in base dir, user wwwroot
  if (OUTBOUNDRE.test(relative)) {
    relative = path.relative(wwwroot, vinyl.path);
    // vinyl not in wwwroot, throw error
    if (OUTBOUNDRE.test(relative)) {
      throwError('file: %s is out of bound of wwwroot: %s.', normalize(vinyl.path), normalize(wwwroot));
    }

    // reset relative
    relative = path.join('/', relative);
  }

  var dir = normalize(dirname(relative));
  var filename = basename(relative);
  var match = dir.split(SPLITRE);

  return {
    name: match[0] || '',
    version: match[1] || '',
    file: match[2] ? match[2] + '/' + filename : filename
  }
}

/**
 * simple template
 * @param format
 * @param data
 * @returns {string}
 * ```
 * var tpl = '{{name}}/{{version}}';
 * util.template(tpl, {name:'base', version: '1.0.0'});
 * ```
 */
function template(format, data){
  if (!is.string(format)) return '';

  return format.replace(/{{([a-z]*)}}/gi, function (all, match){
    return data[match] || '';
  });
}

/**
 * create a plugin
 * @param name
 * @param transport
 * @returns {Function}
 */
function plugin(name, transport){
  return function (vinyl, options){
    // debug
    debug('load plugin: %s', colors.green(name));
    // debug
    debug('read file: %s', colors.magenta(pathFromCwd(vinyl.path)));

    // run transport function
    vinyl = transport(vinyl, options);

    // result must be vinyl
    if (!Vinyl.isVinyl(vinyl)) {
      throwError('plugin %s: transport function must be return a vinyl.', name);
    }

    // normalize package
    if (!vinyl.package) {
      vinyl.package = {};
    }

    return vinyl;
  };
}

/**
 * parse alias
 * @param id
 * @param alias
 * @returns {String}
 */
function parseAlias(id, alias){
  alias = alias || {};

  return alias && is.string(alias[id]) ? alias[id] : id;
}

/**
 * parse paths
 * @param id
 * @param paths
 * @returns {String}
 */
function parsePaths(id, paths){
  var match;

  paths = paths || {};

  if (paths && (match = id.match(PATHSRE)) && is.string(paths[match[1]])) {
    id = paths[match[1]] + match[2];
  }

  return id;
}

/**
 * parse vars
 * @param id
 * @param vars
 * @returns {String}
 */
function parseVars(id, vars){
  vars = vars || {};

  if (vars && id.indexOf('{') !== -1) {
    id = id.replace(VARSRE, function (match, key){
      return is.string(vars[key]) ? vars[key] : match;
    });
  }

  return id;
}

/**
 * parse map
 * @param id
 * @param map
 * @returns {String}
 */
function parseMap(id, map){
  var ret = id;

  if (Array.isArray(map)) {
    for (var i = 0, len = map.length; i < len; i++) {
      var rule = map[i];

      // parse map
      if (is.fn(rule)) {
        ret = rule(id);
      } else if (Array.isArray(rule)) {
        ret = id.replace(rule[0], rule[1]);
      }

      // must be string
      if (!ret || !is.string(ret)) {
        ret = id;
      }

      // only apply the first matched rule
      if (ret !== id) break;
    }
  }

  return ret;
}

/**
 * normalize path
 * @param path
 * @returns {string}
 */
function normalize(path){
  path = path.replace(/\\+/g, '/');
  path = path.replace(/([^:/])\/+\//g, '$1/');
  path = path.replace(/(:)?\/{2,}/, '$1//');

  return path;
}

/**
 * resolve a `relative` path base on `base` path
 * @param relative
 * @param vinyl
 * @param wwwroot
 * @returns {string}
 */
function resolve(relative, vinyl, wwwroot){
  var base;
  var absolute;

  // debug
  debug('resolve path: %s', colors.magenta(normalize(relative)));

  // resolve
  if (isRelative(relative)) {
    base = dirname(vinyl.path);
    absolute = join(base, relative);

    // out of base, use wwwroot
    if (isOutBound(absolute, wwwroot)) {
      throwError('file: %s is out of bound of wwwroot: %s.', normalize(absolute), normalize(wwwroot));
    }
  } else if (isAbsolute(relative)) {
    base = wwwroot;
    absolute = join(base, relative.substring(1));
  } else {
    base = join(vinyl.cwd, vinyl.base);
    absolute = join(base, relative);
  }

  // debug
  debug('of base path: %s', colors.magenta(pathFromCwd(base)));
  debug('to: %s', colors.magenta(pathFromCwd(absolute)));

  return absolute;
}

/**
 * add .js if not exists
 * @param path
 * @returns {*}
 */
function addExt(path){
  return extname(path) === '.js' ? path : (path + '.js');
}

/**
 * hide .js if exists
 * @param path
 * @returns {*}
 */
function hideExt(path){
  return extname(path) === '.js' ? path.replace(/\.js$/, '') : path;
}

/**
 * test path is relative path or not
 * @param path
 * @returns {boolean}
 */
function isRelative(path){
  return /^\.{1,2}[\\/]/.test(path);
}

/**
 * test path is absolute path or not
 * @param path
 * @returns {boolean}
 */
function isAbsolute(path){
  return /^[\\/](?:[^\\/]|$)/.test(path);
}

/**
 * test path is local path or not
 * @param path
 * @returns {boolean}
 */
function isLocal(path){
  return !/^\w*?:\/\/|^\/\//.test(path) && !/^data:\w+?\/\w+?[,;]/i.test(path);
}

/**
 * test path is out of bound of base
 * @param path
 * @param base
 * @returns {boolean}
 */
function isOutBound(path, base){
  return OUTBOUNDRE.test(relative(base, path));
}

/**
 * Get relative path from cwd
 * @param path
 * @returns {string}
 */

function pathFromCwd(path){
  return normalize(relative(cwd, path)) || './';
}

/**
 * plugin error
 */
function throwError(){
  var slice = [].slice;
  var message = util.format
    .apply(null, slice.call(arguments));

  throw new Error(message);
}

/**
 * node extend
 * Copyright 2011, John Resig
 * Dual licensed under the MIT or GPL Version 2 licenses.
 * http://jquery.org/license
 *
 * @fileoverview
 * Port of jQuery.extend that actually works on node.js
 */
function extend(){
  var target = arguments[0] || {};
  var i = 1;
  var length = arguments.length;
  var deep = false;
  var options, name, src, copy, copyIsArray, clone;

  // handle a deep copy situation
  if (is.bool(target)) {
    deep = target;
    target = arguments[1] || {};
    // skip the boolean and the target
    i = 2;
  }

  // handle case when target is a string or something (possible in deep copy)
  if ((typeof target !== 'object' && !is.fn(target)) || target === null) {
    target = {};
  }

  for (; i < length; i++) {
    // only deal with non-null/undefined values
    options = arguments[i];

    if (options !== null) {
      // extend the base object
      for (name in options) {
        if (options.hasOwnProperty(name)) {
          src = target[name];
          copy = options[name];

          // prevent never-ending loop
          if (target === copy) {
            continue;
          }

          // recurse if we're merging plain objects or arrays
          if (deep && copy && (is.hash(copy) || (copyIsArray = Array.isArray(copy)))) {
            if (copyIsArray) {
              copyIsArray = false;
              clone = src && Array.isArray(src) ? src : [];
            } else {
              clone = src && is.hash(src) ? src : {};
            }

            // never move original objects, clone them
            target[name] = extend(deep, clone, copy);

            // don't bring in undefined values
          } else if (typeof copy !== 'undefined') {
            target[name] = copy;
          }
        }
      }
    }
  }

  // return the modified object
  return target;
}

/**
 * wrap module
 * @param id
 * @param deps
 * @param code
 * @returns {string}
 */
function wrapModule(id, deps, code){
  // header and footer template string
  var headerTpl = 'define({{id}}, {{deps}}, function(require, exports, module){';
  var footerTpl = '});\n';

  if (Buffer.isBuffer(code)) code = code.toString();

  // debug
  debug('compile module: %s', colors.magenta(id));

  id = JSON.stringify(id);
  deps = JSON.stringify(deps);

  return template(headerTpl, { id: id, deps: deps })
    + '\n' + code + '\n' + footerTpl;
}

/**
 * print message
 */
function print(){
  var slice = [].slice;
  var message = util.format
    .apply(null, slice.call(arguments));

  process.stdout.write(colors.cyan.bold('  gulp-cmd ') + message + '\n');
}

/**
 * exports module.
 */
module.exports.cwd = cwd;
module.exports.colors = colors;
module.exports.debug = debug;
module.exports.parsePackage = parsePackage;
module.exports.template = template;
module.exports.plugin = plugin;
module.exports.parseAlias = parseAlias;
module.exports.parsePaths = parsePaths;
module.exports.parseVars = parseVars;
module.exports.parseMap = parseMap;
module.exports.normalize = normalize;
module.exports.resolve = resolve;
module.exports.addExt = addExt;
module.exports.hideExt = hideExt;
module.exports.isRelative = isRelative;
module.exports.isAbsolute = isAbsolute;
module.exports.isLocal = isLocal;
module.exports.isOutBound = isOutBound;
module.exports.pathFromCwd = pathFromCwd;
module.exports.throwError = throwError;
module.exports.extend = extend;
module.exports.wrapModule = wrapModule;
module.exports.print = print;
<commit_msg>update files<commit_after>/**
 * Created by nuintun on 2015/4/27.
 */

'use strict';

var is = require('is');
var path = require('path');
var join = path.join;
var extname = path.extname;
var dirname = path.dirname;
var basename = path.basename;
var relative = path.relative;
var util = require('util');
var colors = require('colors/safe');
var debug = require('debug')('gulp-cmd');
var Vinyl = require('vinyl');

// variable declaration
var cwd = process.cwd();
var OUTBOUNDRE = /(?:^[\\\/]?)\.\.(?:[\\\/]|$)/;
var SPLITRE = /\/((?:\d+\.){2}\d+)(?:\/|$)/;
var PATHSRE = /^([^/:]+)(\/.+)$/;
var VARSRE = /{([^{]+)}/g;

// set debug color use 6
debug.color = 6;

/**
 * parse package form vinyl
 * @param vinyl
 * @param wwwroot
 * @returns {object}
 */
function parsePackage(vinyl, wwwroot){
  var relative = vinyl.relative;

  // vinyl not in base dir, user wwwroot
  if (OUTBOUNDRE.test(relative)) {
    relative = path.relative(wwwroot, vinyl.path);
    // vinyl not in wwwroot, throw error
    if (OUTBOUNDRE.test(relative)) {
      throwError('file: %s is out of bound of wwwroot: %s.', normalize(vinyl.path), normalize(wwwroot));
    }

    // reset relative
    relative = path.join('/', relative);
  }

  var dir = normalize(dirname(relative));
  var filename = basename(relative);
  var match = dir.split(SPLITRE);

  return {
    name: match[0] || '',
    version: match[1] || '',
    file: match[2] ? match[2] + '/' + filename : filename
  }
}

/**
 * simple template
 * @param format
 * @param data
 * @returns {string}
 * ```
 * var tpl = '{{name}}/{{version}}';
 * util.template(tpl, {name:'base', version: '1.0.0'});
 * ```
 */
function template(format, data){
  if (!is.string(format)) return '';

  return format.replace(/{{([a-z]*)}}/gi, function (all, match){
    return data[match] || '';
  });
}

/**
 * create a plugin
 * @param name
 * @param transport
 * @returns {Function}
 */
function plugin(name, transport){
  return function (vinyl, options){
    // debug
    debug('load plugin: %s', colors.green(name));
    // debug
    debug('read file: %s', colors.magenta(pathFromCwd(vinyl.path)));

    // run transport function
    vinyl = transport(vinyl, options);

    // result must be vinyl
    if (!Vinyl.isVinyl(vinyl)) {
      throwError('plugin %s: transport function must be return a vinyl.', name);
    }

    // normalize package
    if (!vinyl.package) {
      vinyl.package = {};
    }

    return vinyl;
  };
}

/**
 * parse alias
 * @param id
 * @param alias
 * @returns {String}
 */
function parseAlias(id, alias){
  alias = alias || {};

  return alias && is.string(alias[id]) ? alias[id] : id;
}

/**
 * parse paths
 * @param id
 * @param paths
 * @returns {String}
 */
function parsePaths(id, paths){
  var match;

  paths = paths || {};

  if (paths && (match = id.match(PATHSRE)) && is.string(paths[match[1]])) {
    id = paths[match[1]] + match[2];
  }

  return id;
}

/**
 * parse vars
 * @param id
 * @param vars
 * @returns {String}
 */
function parseVars(id, vars){
  vars = vars || {};

  if (vars && id.indexOf('{') !== -1) {
    id = id.replace(VARSRE, function (match, key){
      return is.string(vars[key]) ? vars[key] : match;
    });
  }

  return id;
}

/**
 * parse map
 * @param id
 * @param map
 * @returns {String}
 */
function parseMap(id, map){
  var ret = id;

  if (Array.isArray(map)) {
    for (var i = 0, len = map.length; i < len; i++) {
      var rule = map[i];

      // parse map
      if (is.fn(rule)) {
        ret = rule(id);
      } else if (Array.isArray(rule)) {
        ret = id.replace(rule[0], rule[1]);
      }

      // must be string
      if (!ret || !is.string(ret)) {
        ret = id;
      }

      // only apply the first matched rule
      if (ret !== id) break;
    }
  }

  return ret;
}

/**
 * normalize path
 * @param path
 * @returns {string}
 */
function normalize(path){
  path = path.replace(/\\+/g, '/');
  path = path.replace(/([^:/])\/+\//g, '$1/');
  path = path.replace(/(:)?\/{2,}/, '$1//');

  return path;
}

/**
 * resolve a `relative` path base on `base` path
 * @param relative
 * @param vinyl
 * @param wwwroot
 * @returns {string}
 */
function resolve(relative, vinyl, wwwroot){
  var base;
  var absolute;

  // debug
  debug('resolve path: %s', colors.magenta(normalize(relative)));

  // resolve
  if (isRelative(relative)) {
    base = dirname(vinyl.path);
    absolute = join(base, relative);

    // out of base, use wwwroot
    if (isOutBound(absolute, wwwroot)) {
      throwError('file: %s is out of bound of wwwroot: %s.', normalize(absolute), normalize(wwwroot));
    }
  } else if (isAbsolute(relative)) {
    base = wwwroot;
    absolute = join(base, relative.substring(1));
  } else {
    base = join(vinyl.cwd, vinyl.base);
    absolute = join(base, relative);
  }

  // debug
  debug('of base path: %s', colors.magenta(pathFromCwd(base)));
  debug('to: %s', colors.magenta(pathFromCwd(absolute)));

  return absolute;
}

/**
 * add .js if not exists
 * @param path
 * @returns {*}
 */
function addExt(path){
  return extname(path) === '.js' ? path : (path + '.js');
}

/**
 * hide .js if exists
 * @param path
 * @returns {*}
 */
function hideExt(path){
  return extname(path) === '.js' ? path.replace(/\.js$/, '') : path;
}

/**
 * test path is relative path or not
 * @param path
 * @returns {boolean}
 */
function isRelative(path){
  return /^\.{1,2}[\\/]/.test(path);
}

/**
 * test path is absolute path or not
 * @param path
 * @returns {boolean}
 */
function isAbsolute(path){
  return /^[\\/](?:[^\\/]|$)/.test(path);
}

/**
 * test path is local path or not
 * @param path
 * @returns {boolean}
 */
function isLocal(path){
  return !/^\w*?:\/\/|^\/\//.test(path) && !/^data:\w+?\/\w+?[,;]/i.test(path);
}

/**
 * test path is out of bound of base
 * @param path
 * @param base
 * @returns {boolean}
 */
function isOutBound(path, base){
  return OUTBOUNDRE.test(relative(base, path));
}

/**
 * Get relative path from cwd
 * @param path
 * @returns {string}
 */

function pathFromCwd(path){
  return normalize(relative(cwd, path)) || './';
}

/**
 * plugin error
 */
function throwError(){
  var slice = [].slice;
  var message = util.format
    .apply(null, slice.call(arguments));

  throw new Error(message);
}

/**
 * node extend
 * Copyright 2011, John Resig
 * Dual licensed under the MIT or GPL Version 2 licenses.
 * http://jquery.org/license
 *
 * @fileoverview
 * Port of jQuery.extend that actually works on node.js
 */
function extend(){
  var target = arguments[0] || {};
  var i = 1;
  var length = arguments.length;
  var deep = false;
  var options, name, src, copy, copyIsArray, clone;

  // handle a deep copy situation
  if (is.bool(target)) {
    deep = target;
    target = arguments[1] || {};
    // skip the boolean and the target
    i = 2;
  }

  // handle case when target is a string or something (possible in deep copy)
  if ((typeof target !== 'object' && !is.fn(target)) || target === null) {
    target = {};
  }

  for (; i < length; i++) {
    // only deal with non-null/undefined values
    options = arguments[i];

    if (options !== null) {
      // extend the base object
      for (name in options) {
        if (options.hasOwnProperty(name)) {
          src = target[name];
          copy = options[name];

          // prevent never-ending loop
          if (target === copy) {
            continue;
          }

          // recurse if we're merging plain objects or arrays
          if (deep && copy && (is.hash(copy) || (copyIsArray = Array.isArray(copy)))) {
            if (copyIsArray) {
              copyIsArray = false;
              clone = src && Array.isArray(src) ? src : [];
            } else {
              clone = src && is.hash(src) ? src : {};
            }

            // never move original objects, clone them
            target[name] = extend(deep, clone, copy);

            // don't bring in undefined values
          } else if (typeof copy !== 'undefined') {
            target[name] = copy;
          }
        }
      }
    }
  }

  // return the modified object
  return target;
}

/**
 * wrap module
 * @param id
 * @param deps
 * @param code
 * @returns {string}
 */
function wrapModule(id, deps, code){
  // header and footer template string
  var headerTpl = 'define({{id}}, {{deps}}, function(require, exports, module){';
  var footerTpl = '});\n';

  if (Buffer.isBuffer(code)) code = code.toString();

  // debug
  debug('compile module: %s', colors.magenta(id));

  id = JSON.stringify(id);
  deps = JSON.stringify(deps);

  return template(headerTpl, { id: id, deps: deps })
    + '\n' + code + '\n' + footerTpl;
}

/**
 * print message
 */
function print(){
  var slice = [].slice;
  var message = util.format
    .apply(null, slice.call(arguments));

  process.stdout.write(colors.cyan.bold('  gulp-cmd ') + message + '\n');
}

/**
 * exports module.
 */
module.exports.cwd = cwd;
module.exports.colors = colors;
module.exports.debug = debug;
module.exports.parsePackage = parsePackage;
module.exports.template = template;
module.exports.plugin = plugin;
module.exports.parseAlias = parseAlias;
module.exports.parsePaths = parsePaths;
module.exports.parseVars = parseVars;
module.exports.parseMap = parseMap;
module.exports.normalize = normalize;
module.exports.resolve = resolve;
module.exports.addExt = addExt;
module.exports.hideExt = hideExt;
module.exports.isRelative = isRelative;
module.exports.isAbsolute = isAbsolute;
module.exports.isLocal = isLocal;
module.exports.isOutBound = isOutBound;
module.exports.pathFromCwd = pathFromCwd;
module.exports.throwError = throwError;
module.exports.extend = extend;
module.exports.wrapModule = wrapModule;
module.exports.print = print;
<|endoftext|>


<commit_before>'use strict';

var fs = require('fs');

var appRoot = require('app-root-path');
var npmlog = require('npmlog');

exports.mergeConfig = function mergeConfig (base, ext) {
  return Object.assign(base || {}, ext, {
    webpack: Object.assign(base ? base.webpack : {}, ext && ext.webpack)
  });
};

exports.getConfig = function readConfig (overrides) {
  var config = {
    webpack: {
      build: require.resolve('../etc/build'),
      develop: require.resolve('../etc/develop'),
      render: require.resolve('../etc/render')
    },
    locations: []
  };
  if (fs.existsSync(appRoot.resolve('package.json'))) {
    exports.mergeConfig(config, appRoot.require('package.json').hops);
  }
  if (fs.existsSync(appRoot.resolve('hops.config.js'))) {
    exports.mergeConfig(config, appRoot.require('hops.config.js'));
  }
  exports.mergeConfig(config, overrides);
  Object.keys(config.webpack).forEach(function (key) {
    if (!fs.existsSync(config.webpack[key])) {
      config.webpack[key] = appRoot.resolve(config.webpack[key]);
    }
  });
  return config;
};

exports.log = function log () {
  npmlog.log.apply(npmlog, ['info', 'hops'].concat(Array.from(arguments)));
};

exports.logError = function logError (err) {
  npmlog.log('error', 'hops', err.stack.toString());
};
<commit_msg>fix webpack config overrides<commit_after>'use strict';

var fs = require('fs');
var path = require('path');

var appRoot = require('app-root-path');
var npmlog = require('npmlog');

exports.mergeConfig = function mergeConfig (base, ext) {
  return Object.assign(base || {}, ext, {
    webpack: Object.assign(base ? base.webpack : {}, ext && ext.webpack)
  });
};

exports.getConfig = function readConfig (overrides) {
  var config = {
    webpack: {
      build: require.resolve('../etc/build'),
      develop: require.resolve('../etc/develop'),
      render: require.resolve('../etc/render')
    },
    locations: []
  };
  if (fs.existsSync(appRoot.resolve('package.json'))) {
    exports.mergeConfig(config, appRoot.require('package.json').hops);
  }
  if (fs.existsSync(appRoot.resolve('hops.config.js'))) {
    exports.mergeConfig(config, appRoot.require('hops.config.js'));
  }
  exports.mergeConfig(config, overrides);
  Object.keys(config.webpack).forEach(function (key) {
    if (!path.isAbsolute(config.webpack[key])) {
      config.webpack[key] = appRoot.resolve(config.webpack[key]);
    }
  });
  return config;
};

exports.log = function log () {
  npmlog.log.apply(npmlog, ['info', 'hops'].concat(Array.from(arguments)));
};

exports.logError = function logError (err) {
  npmlog.log('error', 'hops', err.stack.toString());
};
<|endoftext|>


<commit_before>  'use strict'

const _ = require('lodash')
const call = require('call')
const lib = require('.')

module.exports = {

  /**
   * Build a complete route, with bound handler and attached preconditions
   */
  buildRoute (app, rawRoute) {
    const route = lib.Util.decorateRouteWithPrerequisites(app, rawRoute)
    route.handler = _.get(app.controllers, rawRoute.handler)

    if (!route.handler) {
      app.log.warn('trailpack-router: route handler [', rawRoute.handler, ']',
        'does not correspond to any defined Controller handler')

      return
    }

    return route
  },

  /**
   * Return the route the "pre" property populated with prerequisites.
   * @see http://hapijs.com/api#route-prerequisites
   */
  decorateRouteWithPrerequisites (app, route) {
    const policies = app.config.policies
    const prerequisites = lib.Util.getRoutePrerequisites(policies, route)

    return _.defaultsDeep({ }, route, {
      config: {
        pre: _.compact(_.map(prerequisites, policy => {
          const pre = _.get(app.policies, policy)

          if (!pre) {
            app.log.warn('trailpack-router: route prerequisite [', policy, ']',
              'does not correspond to any defined Policy')
          }

          return pre
        }))
      }
    })
  },

  /**
   * Get policy prerequisites for the given route. The route handler must be in
   * the form "Controller.handler"
   */
  getRoutePrerequisites (policiesConfig, route) {
    const handlerTokens = route.handler.split('.')
    const controller = handlerTokens[0]
    const handler = handlerTokens[1]

    const controllerPolicies = _.get(policiesConfig, controller) || []
    let handlerPolicies = _.get(policiesConfig, [controller, handler])

    if (!handlerPolicies) {
      const globControllerPolicies = _.get(policiesConfig, [controller, '*'])
      const globPolicies = _.get(policiesConfig, ['*'])
      if (globControllerPolicies) {
        handlerPolicies = globControllerPolicies
      }
      else if (globPolicies) {
        handlerPolicies = globPolicies
      }
      else {
        handlerPolicies = []
      }
    }

    return _.isArray(controllerPolicies) ? controllerPolicies : handlerPolicies
  },

  /**
   * Join an arbitrary number of path segments. This is probably the most
   * important method in this trailpack. Generating these route paths correctly
   * is critical for the correct behavior of the rest of the system. Don't be
   * scared of the regular expressions; this method is only run during boot,
   * and should not be called while the server is handling requests.
   *
   * Notes:
   * 1. path.join is os-dependent (i.e. it will break on windows with forward
   *  slashes). We learned this the hard way, so while it is a convenient tool
   *  for joining paths, we are not using it here.
   *
   * 2. url.resolve urlencodes the path, and thus breaks the hapi path
   *  parameter syntax involving braces. We learned this the hard way, also.
   *
   * 3. This method will never include a trailing slash in the route paths it
   * produces.
   *
   * 4. This function tries to be very helpful, and accept just about anything
   * you throw at it. It also self-validates its expected result, and will throw
   * if it's converted your input into something it doesn't recognize. This is
   * partially why it may look more complex than it needs to be.
   *
   */
  getRoutePath () {
    const slashGroupRegex = /\/{2,}/g
    const slashTrailRegex = /(^\/)|(\/$)/g
    const slashValidRegex = /^(\/)|((\/[^\/]+)+)$/

    const argv = _.compact(_.flattenDeep(_.toArray(arguments)))
    const isArgumentsValid = argv.length && _.every(argv, s => _.isArray(s) || _.isString(s))

    if (!isArgumentsValid) {
      throw new TypeError([
        'trailpack-router cannot parse the arguments given to Footprints.getRoutePath:',
        argv, 'Argument types not recognized'
      ].join(' '))
    }

    const path = '/' + argv.join('/')
      .replace(slashGroupRegex, '/')
      .replace(slashTrailRegex, '')

    if (!slashValidRegex.test(path)) {
      throw new RangeError(
        `trailpack-router produced an invalid route path: ${path}. Arguments given: ${argv}`
      )
    }

    return path
  },

  /**
   * Return routes that have inconsistent paths and methods. Multiple routes
   * can exist with the same path, but not if the methods overlap or are the
   * same.
   */
  findRoutePathConflicts (route, routeList) {
    return _.filter(routeList, _route => {
      const methodIntersection = _.intersection(
        _.flatten([ route.method ]), _.flatten([ _route.method ])
      )
      const isMethodOverlap = methodIntersection.length > 0
      const isPathIdentical = route.path == _route.path

      return isPathIdentical && isMethodOverlap
    })
  },

  /**
   * Return routes that have identical paths and handlers.
   */
  findRouteHandlerConflicts (route, routeList) {
    return _.filter(routeList, {
      handler: route.handler,
      path: route.path
    })
  },

  /**
   * Return an error if the given route is not compatible with the router;
   * return null otherwise.
   */
  getRouteConflict (router, route) {
    return _.compact(_.map(_.flatten([ route.method ]), method => {
      try {
        router.add(_.defaults({ method: method }, route))
        return null
      }
      catch (e) {
        return e
      }
    }))
  },

  /**
   * Find conflicts within a particular route list which cannot be cleverly
   * reconciled by the program logic, and which require corrective action from
   * the developer.
   */
  findRouteConflicts (routeList) {
    const router = new call.Router()
    const conflicts = _.map(routeList, route => {
      return {
        route: route,
        errors: lib.Util.getRouteConflict(router, route)
      }
    })
    return _.filter(conflicts, conflict => conflict.errors.length)
  },

  /**
   * Combine two route lists, removing duplicate (conflicting) routes,
   * preferring those in overrides.
   */
  mergeRoutes (routes, overrides) {
    const filteredRoutes = _.filter(routes, route => {
      const pathConflicts = lib.Util.findRoutePathConflicts(route, overrides)
      const handlerConflicts = lib.Util.findRouteHandlerConflicts(route, overrides)

      return pathConflicts.length === 0 && handlerConflicts.length === 0
    })

    return _.union(filteredRoutes, overrides)
  },

  /**
   * Generate an id from a hapi route object. If an id is already set, return
   * it.
   */
  getRouteId (route) {
    const id = _.get(route.config, 'id')
    if (id) return id

    return `[${route.method}] ${route.path} -> ${route.handler}`
  }
}

<commit_msg>fix eslint<commit_after>'use strict'

const _ = require('lodash')
const call = require('call')
const lib = require('.')

module.exports = {

  /**
   * Build a complete route, with bound handler and attached preconditions
   */
  buildRoute (app, rawRoute) {
    const route = lib.Util.decorateRouteWithPrerequisites(app, rawRoute)
    route.handler = _.get(app.controllers, rawRoute.handler)

    if (!route.handler) {
      app.log.warn('trailpack-router: route handler [', rawRoute.handler, ']',
        'does not correspond to any defined Controller handler')

      return
    }

    return route
  },

  /**
   * Return the route the "pre" property populated with prerequisites.
   * @see http://hapijs.com/api#route-prerequisites
   */
  decorateRouteWithPrerequisites (app, route) {
    const policies = app.config.policies
    const prerequisites = lib.Util.getRoutePrerequisites(policies, route)

    return _.defaultsDeep({}, route, {
      config: {
        pre: _.compact(_.map(prerequisites, policy => {
          const pre = _.get(app.policies, policy)

          if (!pre) {
            app.log.warn('trailpack-router: route prerequisite [', policy, ']',
              'does not correspond to any defined Policy')
          }

          return pre
        }))
      }
    })
  },

  /**
   * Get policy prerequisites for the given route. The route handler must be in
   * the form "Controller.handler"
   */
  getRoutePrerequisites (policiesConfig, route) {
    const handlerTokens = route.handler.split('.')
    const controller = handlerTokens[0]
    const handler = handlerTokens[1]

    const controllerPolicies = _.get(policiesConfig, controller) || []
    let handlerPolicies = _.get(policiesConfig, [controller, handler])

    if (!handlerPolicies) {
      const globControllerPolicies = _.get(policiesConfig, [controller, '*'])
      const globPolicies = _.get(policiesConfig, ['*'])
      if (globControllerPolicies) {
        handlerPolicies = globControllerPolicies
      }
      else if (globPolicies) {
        handlerPolicies = globPolicies
      }
      else {
        handlerPolicies = []
      }
    }

    return _.isArray(controllerPolicies) ? controllerPolicies : handlerPolicies
  },

  /**
   * Join an arbitrary number of path segments. This is probably the most
   * important method in this trailpack. Generating these route paths correctly
   * is critical for the correct behavior of the rest of the system. Don't be
   * scared of the regular expressions; this method is only run during boot,
   * and should not be called while the server is handling requests.
   *
   * Notes:
   * 1. path.join is os-dependent (i.e. it will break on windows with forward
   *  slashes). We learned this the hard way, so while it is a convenient tool
   *  for joining paths, we are not using it here.
   *
   * 2. url.resolve urlencodes the path, and thus breaks the hapi path
   *  parameter syntax involving braces. We learned this the hard way, also.
   *
   * 3. This method will never include a trailing slash in the route paths it
   * produces.
   *
   * 4. This function tries to be very helpful, and accept just about anything
   * you throw at it. It also self-validates its expected result, and will throw
   * if it's converted your input into something it doesn't recognize. This is
   * partially why it may look more complex than it needs to be.
   *
   */
  getRoutePath () {
    const slashGroupRegex = /\/{2,}/g
    const slashTrailRegex = /(^\/)|(\/$)/g
    const slashValidRegex = /^(\/)|((\/[^\/]+)+)$/

    const argv = _.compact(_.flattenDeep(_.toArray(arguments)))
    const isArgumentsValid = argv.length && _.every(argv, s => _.isArray(s) || _.isString(s))

    if (!isArgumentsValid) {
      throw new TypeError([
        'trailpack-router cannot parse the arguments given to Footprints.getRoutePath:',
        argv, 'Argument types not recognized'
      ].join(' '))
    }

    const path = '/' + argv.join('/')
        .replace(slashGroupRegex, '/')
        .replace(slashTrailRegex, '')

    if (!slashValidRegex.test(path)) {
      throw new RangeError(
        `trailpack-router produced an invalid route path: ${path}. Arguments given: ${argv}`
      )
    }

    return path
  },

  /**
   * Return routes that have inconsistent paths and methods. Multiple routes
   * can exist with the same path, but not if the methods overlap or are the
   * same.
   */
  findRoutePathConflicts (route, routeList) {
    return _.filter(routeList, _route => {
      const methodIntersection = _.intersection(
        _.flatten([route.method]), _.flatten([_route.method])
      )
      const isMethodOverlap = methodIntersection.length > 0
      const isPathIdentical = route.path == _route.path

      return isPathIdentical && isMethodOverlap
    })
  },

  /**
   * Return routes that have identical paths and handlers.
   */
  findRouteHandlerConflicts (route, routeList) {
    return _.filter(routeList, {
      handler: route.handler,
      path: route.path
    })
  },

  /**
   * Return an error if the given route is not compatible with the router;
   * return null otherwise.
   */
  getRouteConflict (router, route) {
    return _.compact(_.map(_.flatten([route.method]), method => {
      try {
        router.add(_.defaults({method: method}, route))
        return null
      }
      catch (e) {
        return e
      }
    }))
  },

  /**
   * Find conflicts within a particular route list which cannot be cleverly
   * reconciled by the program logic, and which require corrective action from
   * the developer.
   */
  findRouteConflicts (routeList) {
    const router = new call.Router()
    const conflicts = _.map(routeList, route => {
      return {
        route: route,
        errors: lib.Util.getRouteConflict(router, route)
      }
    })
    return _.filter(conflicts, conflict => conflict.errors.length)
  },

  /**
   * Combine two route lists, removing duplicate (conflicting) routes,
   * preferring those in overrides.
   */
  mergeRoutes (routes, overrides) {
    const filteredRoutes = _.filter(routes, route => {
      const pathConflicts = lib.Util.findRoutePathConflicts(route, overrides)
      const handlerConflicts = lib.Util.findRouteHandlerConflicts(route, overrides)

      return pathConflicts.length === 0 && handlerConflicts.length === 0
    })

    return _.union(filteredRoutes, overrides)
  },

  /**
   * Generate an id from a hapi route object. If an id is already set, return
   * it.
   */
  getRouteId (route) {
    const id = _.get(route.config, 'id')
    if (id) return id

    return `[${route.method}] ${route.path} -> ${route.handler}`
  }
}

<|endoftext|>


<commit_before>Rye.define('util', function(){

    function extend (destination, source) {
        for (var key in source) {
            if (Object.prototype.hasOwnProperty.call(parent, key)){
                destination[key] = source[key]
            }
        }
        return destination
    }

    function inherits (child, parent) {
        extend(child, parent)
        function Ctor () {
            this.constructor = child
        }
        Ctor.prototype = parent.prototype
        child.prototype = new Ctor()
        child.__super__ = parent.prototype
        return child
    }

    function isElement (element) {
        return element && (element.nodeType === 1 || element.nodeType === 9)
    }

    function isNodeList (obj) {
        return obj && (obj instanceof window.NodeList) || (obj instanceof window.HTMLCollection)
    }

    function unique (array) {
        return array.filter(function(item, idx){
            return array.indexOf(item) == idx
        })
    }

    function pluck (arr, property){
        return arr.map(function(item){
            return item[property]
        })
    }

    function prefix (key, obj) {
        var result
          , upcased = key[0].toUpperCase() + key.substring(1)
          , prefixes = ['moz', 'webkit', 'ms', 'o']

        obj = obj || window

        if (result = obj[key]){
            return result
        }

        // No pretty array methods here :(
        // http://jsperf.com/everywhile
        while(prefix = prefixes.shift()){
            if (result = obj[prefix + upcased]){
                break;
            }
        }
    }

    function bindMethod (obj, method, args_length) {
        return function(){
            obj[method].apply(obj, [].slice.call(arguments, 0, args_length))
        }

    }

    return {
        extend      : extend
      , inherits    : inherits
      , isElement   : isElement
      , isNodeList  : isNodeList
      , unique      : unique
      , pluck       : pluck
      , prefix      : prefix
      , bindMethod  : bindMethod
    }

})
<commit_msg>Fix extend method, copy&pasta error<commit_after>Rye.define('util', function(){

    function extend (destination, source) {
        for (var key in source) {
            if (Object.prototype.hasOwnProperty.call(source, key)){
                destination[key] = source[key]
            }
        }
        return destination
    }

    function inherits (child, parent) {
        extend(child, parent)
        function Ctor () {
            this.constructor = child
        }
        Ctor.prototype = parent.prototype
        child.prototype = new Ctor()
        child.__super__ = parent.prototype
        return child
    }

    function isElement (element) {
        return element && (element.nodeType === 1 || element.nodeType === 9)
    }

    function isNodeList (obj) {
        return obj && (obj instanceof window.NodeList) || (obj instanceof window.HTMLCollection)
    }

    function unique (array) {
        return array.filter(function(item, idx){
            return array.indexOf(item) == idx
        })
    }

    function pluck (arr, property){
        return arr.map(function(item){
            return item[property]
        })
    }

    function prefix (key, obj) {
        var result
          , upcased = key[0].toUpperCase() + key.substring(1)
          , prefixes = ['moz', 'webkit', 'ms', 'o']

        obj = obj || window

        if (result = obj[key]){
            return result
        }

        // No pretty array methods here :(
        // http://jsperf.com/everywhile
        while(prefix = prefixes.shift()){
            if (result = obj[prefix + upcased]){
                break;
            }
        }
    }

    function bindMethod (obj, method, args_length) {
        return function(){
            obj[method].apply(obj, [].slice.call(arguments, 0, args_length))
        }

    }

    return {
        extend      : extend
      , inherits    : inherits
      , isElement   : isElement
      , isNodeList  : isNodeList
      , unique      : unique
      , pluck       : pluck
      , prefix      : prefix
      , bindMethod  : bindMethod
    }

})
<|endoftext|>