<commit_before>var path = require('path');
var url = require('url');

var Graph = require('algorithms/data_structures/graph');
var depthFirst = require('algorithms/graph').depthFirstSearch;

function fromFileURL(url) {
  return url.substr(7 + !!process.platform.match(/^win/)).replace(/\//g, path.sep);
}
exports.fromFileURL = fromFileURL;

function toFileURL(path) {
  return 'file://' + (process.platform.match(/^win/) ? '/' : '') + path.replace(/\\/g, '/');
}
exports.toFileURL = toFileURL;

function isFileURL(url) {
  return url.substr(0, 5) === 'file:';
}
exports.isFileURL = isFileURL;

/* Remove scheme prefix from file URLs, so that they are paths. */
function filePath(url) {
  if (isFileURL(url))
    return url.replace(/^file:\/+/, '/');
}
exports.filePath = filePath;

/* Coerce URLs to paths, assuming they are file URLs */
function coercePath(url) {
  if (isFileURL(url))
    return url.replace(/^file:\/+/, '/');
  else
    // assume relative
    return path.resolve(process.cwd(), url);
}
exports.coercePath = coercePath;

var absURLRegEx = /^[^\/]+:\/\//;

function normalizePath(loader, path, isPlugin) {
  var curPath;
  if (loader.paths[path][0] == '.')
    curPath = decodeURI(url.resolve(toFileURL(process.cwd()) + '/', loader.paths[path]));
  else
    curPath = decodeURI(url.resolve(loader.baseURL, loader.paths[path]));
  if (loader.defaultJSExtensions && !isPlugin && curPath.substr(curPath.length - 3, 3) != '.js')
    curPath += '.js';
  return curPath;
}

// syntax-free getCanonicalName
// just reverse-applies paths and defulatJSExtension to determine the canonical
function getCanonicalNamePlain(loader, normalized, isPlugin) {
  // now just reverse apply paths rules to get canonical name
  var pathMatch;

  // first check exact path matches
  for (var p in loader.paths) {
    if (loader.paths[p].indexOf('*') != -1)
      continue;

    var curPath = normalizePath(loader, p, isPlugin);

    if (normalized === curPath) {
      // always stop on first exact match
      pathMatch = p;
      break;
    }
  }

  // then wildcard matches
  var pathMatchLength = 0;
  var curMatchlength;
  if (!pathMatch)
    for (var p in loader.paths) {
      if (loader.paths[p].indexOf('*') == -1)
        continue;

      // normalize the output path
      var curPath = normalizePath(loader, p, isPlugin);

      // do reverse match
      var wIndex = curPath.indexOf('*');
      if (normalized.substr(0, wIndex) === curPath.substr(0, wIndex)
        && normalized.substr(normalized.length - curPath.length + wIndex + 1) === curPath.substr(wIndex + 1)) {
        curMatchLength = curPath.split('/').length;
        if (curMatchLength >= pathMatchLength) {
          pathMatch = p.replace('*', normalized.substr(wIndex, normalized.length - curPath.length + 1));
          pathMatchLength = curMatchLength;
        }
      }
    }

  // when no path was matched, act like the standard rule is *: baseURL/*
  if (!pathMatch) {
    if (normalized.substr(0, loader.baseURL.length) == loader.baseURL)
      pathMatch = normalized.substr(loader.baseURL.length);
    else if (normalized.match(absURLRegEx))
      throw 'Unable to calculate canonical name to bundle ' + normalized;
    else
      pathMatch = normalized;
  }

  return pathMatch;
}

exports.getCanonicalName = getCanonicalName;

// calculate the canonical name of the normalized module
// unwraps loader syntaxes to derive component parts
var interpolationRegEx = /#\{[^\}]+\}/;
function canonicalizeCondition(loader, conditionModule) {
  var conditionExport;
  var exportIndex = conditionModule.lastIndexOf('|');
  if (exportIndex != -1) {
    conditionExport = conditionModule.substr(exportIndex + 1)
    conditionModule = conditionModule.substr(0, exportIndex);
  }
  return getCanonicalName(loader, conditionModule) + (conditionExport ? '|' + conditionExport : '');
}

function getCanonicalName(loader, normalized, isPlugin) {
  // 1. Boolean conditional
  var booleanIndex = normalized.lastIndexOf('#?');
  if (booleanIndex != -1) {
    var booleanModule = normalized.substr(booleanIndex + 2);
    var negate = booleanModule[0] == '~';
    if (negate)
      booleanModule = booleanModule.substr(1);
    return getCanonicalName(loader, normalized.substr(0, booleanIndex)) + '#?' + (negate ? '~' : '') + canonicalizeCondition(loader, booleanModule);
  }

  // 2. Plugins
  var pluginIndex = loader.pluginFirst ? normalized.indexOf('!') : normalized.lastIndexOf('!');
  if (pluginIndex != -1)
    return getCanonicalName(loader, normalized.substr(0, pluginIndex), !loader.pluginFirst) + '!' + getCanonicalName(loader, normalized.substr(pluginIndex + 1), loader.pluginFirst);

  // 3. Package environment map
  var pkgEnvIndex = normalized.indexOf('#:');
  if (pkgEnvIndex != -1)
    return getCanonicalName(loader, normalized.substr(0, pkgEnvIndex), isPlugin) + normalized.substr(pkgEnvIndex);

  // Finally get canonical plain
  var canonical = getCanonicalNamePlain(loader, normalized, isPlugin);

  // 4. Canonicalize conditional interpolation
  var conditionalMatch = canonical.match(interpolationRegEx);
  if (conditionalMatch)
    return getCanonicalNamePlain(loader, normalized, isPlugin).replace(interpolationRegEx, '#{' + canonicalizeCondition(loader, conditionalMatch[0].substr(2, conditionalMatch[0].length - 3)) + '}');

  return canonical;
}

exports.getAlias = getAlias
function getAlias(loader, canonicalName) {
  var bestAlias;

  function getBestAlias(mapped) {
    return canonicalName.substr(0, mapped.length) == mapped
        && (canonicalName.length == mapped.length || canonicalName[mapped.length + 1] == '/');
  }

  Object.keys(loader.map).forEach(function(alias) {
    if (getBestAlias(loader.map[alias]))
      bestAlias = alias;
  });

  if (bestAlias)
    return bestAlias;

  Object.keys(loader.packages).forEach(function(pkg) {
    Object.keys(loader.packages[pkg].map || {}).forEach(function(alias) {
      if (getBestAlias(loader.packages[pkg].map[alias]))
        bestAlias = alias;
    });
  });

  return bestAlias || canonicalName;
}

function getGraphEntryPoints(graph, entryPoints) {
  entryPoints = [].concat(entryPoints || []);

  var modules = Object.keys(graph.adjList);
  var discarded = {};

  modules.forEach(function (moduleName) {
    Object.keys(graph.adjList[moduleName]).forEach(function (depName) {
      discarded[depName] = true;
    });
  });

  modules.filter(function (moduleName) {
    return !discarded[moduleName];
  }).sort().forEach(function (moduleName) {
    if (entryPoints.indexOf(moduleName) === -1) {
      entryPoints.push(moduleName);
    }
  });

  return entryPoints;
}

exports.postOrderTraverseTree = function postOrderTraverseTree(tree, loader, entryPoints) {
  entryPoints = entryPoints || [];

  // Post order traversal sorted module list
  var postOrder = [];
  var graph = new Graph(true);

  // Seed graph with all relations
  Object.keys(tree).forEach(function (moduleName) {
    var module = tree[moduleName];

    module.deps.forEach(function (depName) {
      graph.addEdge(module.name, module.depMap[depName]);
    });
  });

  // Post order traversal of graph, one per entryPoint
  getGraphEntryPoints(graph, entryPoints).forEach(function (entryPoint) {
    depthFirst(graph, entryPoint, {
      leaveVertex: function (moduleName) {
        // Avoidj duplicates
        if (postOrder.indexOf(moduleName) === -1) {
          postOrder.push(moduleName);
        }
      }
    });
  });

  return postOrder;
};
<commit_msg>Filter out modules that are skipped in the trace when post order traversing<commit_after>var path = require('path');
var url = require('url');

var Graph = require('algorithms/data_structures/graph');
var depthFirst = require('algorithms/graph').depthFirstSearch;

function fromFileURL(url) {
  return url.substr(7 + !!process.platform.match(/^win/)).replace(/\//g, path.sep);
}
exports.fromFileURL = fromFileURL;

function toFileURL(path) {
  return 'file://' + (process.platform.match(/^win/) ? '/' : '') + path.replace(/\\/g, '/');
}
exports.toFileURL = toFileURL;

function isFileURL(url) {
  return url.substr(0, 5) === 'file:';
}
exports.isFileURL = isFileURL;

/* Remove scheme prefix from file URLs, so that they are paths. */
function filePath(url) {
  if (isFileURL(url))
    return url.replace(/^file:\/+/, '/');
}
exports.filePath = filePath;

/* Coerce URLs to paths, assuming they are file URLs */
function coercePath(url) {
  if (isFileURL(url))
    return url.replace(/^file:\/+/, '/');
  else
    // assume relative
    return path.resolve(process.cwd(), url);
}
exports.coercePath = coercePath;

var absURLRegEx = /^[^\/]+:\/\//;

function normalizePath(loader, path, isPlugin) {
  var curPath;
  if (loader.paths[path][0] == '.')
    curPath = decodeURI(url.resolve(toFileURL(process.cwd()) + '/', loader.paths[path]));
  else
    curPath = decodeURI(url.resolve(loader.baseURL, loader.paths[path]));
  if (loader.defaultJSExtensions && !isPlugin && curPath.substr(curPath.length - 3, 3) != '.js')
    curPath += '.js';
  return curPath;
}

// syntax-free getCanonicalName
// just reverse-applies paths and defulatJSExtension to determine the canonical
function getCanonicalNamePlain(loader, normalized, isPlugin) {
  // now just reverse apply paths rules to get canonical name
  var pathMatch;

  // first check exact path matches
  for (var p in loader.paths) {
    if (loader.paths[p].indexOf('*') != -1)
      continue;

    var curPath = normalizePath(loader, p, isPlugin);

    if (normalized === curPath) {
      // always stop on first exact match
      pathMatch = p;
      break;
    }
  }

  // then wildcard matches
  var pathMatchLength = 0;
  var curMatchlength;
  if (!pathMatch)
    for (var p in loader.paths) {
      if (loader.paths[p].indexOf('*') == -1)
        continue;

      // normalize the output path
      var curPath = normalizePath(loader, p, isPlugin);

      // do reverse match
      var wIndex = curPath.indexOf('*');
      if (normalized.substr(0, wIndex) === curPath.substr(0, wIndex)
        && normalized.substr(normalized.length - curPath.length + wIndex + 1) === curPath.substr(wIndex + 1)) {
        curMatchLength = curPath.split('/').length;
        if (curMatchLength >= pathMatchLength) {
          pathMatch = p.replace('*', normalized.substr(wIndex, normalized.length - curPath.length + 1));
          pathMatchLength = curMatchLength;
        }
      }
    }

  // when no path was matched, act like the standard rule is *: baseURL/*
  if (!pathMatch) {
    if (normalized.substr(0, loader.baseURL.length) == loader.baseURL)
      pathMatch = normalized.substr(loader.baseURL.length);
    else if (normalized.match(absURLRegEx))
      throw 'Unable to calculate canonical name to bundle ' + normalized;
    else
      pathMatch = normalized;
  }

  return pathMatch;
}

exports.getCanonicalName = getCanonicalName;

// calculate the canonical name of the normalized module
// unwraps loader syntaxes to derive component parts
var interpolationRegEx = /#\{[^\}]+\}/;
function canonicalizeCondition(loader, conditionModule) {
  var conditionExport;
  var exportIndex = conditionModule.lastIndexOf('|');
  if (exportIndex != -1) {
    conditionExport = conditionModule.substr(exportIndex + 1)
    conditionModule = conditionModule.substr(0, exportIndex);
  }
  return getCanonicalName(loader, conditionModule) + (conditionExport ? '|' + conditionExport : '');
}

function getCanonicalName(loader, normalized, isPlugin) {
  // 1. Boolean conditional
  var booleanIndex = normalized.lastIndexOf('#?');
  if (booleanIndex != -1) {
    var booleanModule = normalized.substr(booleanIndex + 2);
    var negate = booleanModule[0] == '~';
    if (negate)
      booleanModule = booleanModule.substr(1);
    return getCanonicalName(loader, normalized.substr(0, booleanIndex)) + '#?' + (negate ? '~' : '') + canonicalizeCondition(loader, booleanModule);
  }

  // 2. Plugins
  var pluginIndex = loader.pluginFirst ? normalized.indexOf('!') : normalized.lastIndexOf('!');
  if (pluginIndex != -1)
    return getCanonicalName(loader, normalized.substr(0, pluginIndex), !loader.pluginFirst) + '!' + getCanonicalName(loader, normalized.substr(pluginIndex + 1), loader.pluginFirst);

  // 3. Package environment map
  var pkgEnvIndex = normalized.indexOf('#:');
  if (pkgEnvIndex != -1)
    return getCanonicalName(loader, normalized.substr(0, pkgEnvIndex), isPlugin) + normalized.substr(pkgEnvIndex);

  // Finally get canonical plain
  var canonical = getCanonicalNamePlain(loader, normalized, isPlugin);

  // 4. Canonicalize conditional interpolation
  var conditionalMatch = canonical.match(interpolationRegEx);
  if (conditionalMatch)
    return getCanonicalNamePlain(loader, normalized, isPlugin).replace(interpolationRegEx, '#{' + canonicalizeCondition(loader, conditionalMatch[0].substr(2, conditionalMatch[0].length - 3)) + '}');

  return canonical;
}

exports.getAlias = getAlias
function getAlias(loader, canonicalName) {
  var bestAlias;

  function getBestAlias(mapped) {
    return canonicalName.substr(0, mapped.length) == mapped
        && (canonicalName.length == mapped.length || canonicalName[mapped.length + 1] == '/');
  }

  Object.keys(loader.map).forEach(function(alias) {
    if (getBestAlias(loader.map[alias]))
      bestAlias = alias;
  });

  if (bestAlias)
    return bestAlias;

  Object.keys(loader.packages).forEach(function(pkg) {
    Object.keys(loader.packages[pkg].map || {}).forEach(function(alias) {
      if (getBestAlias(loader.packages[pkg].map[alias]))
        bestAlias = alias;
    });
  });

  return bestAlias || canonicalName;
}

function getGraphEntryPoints(graph, entryPoints) {
  entryPoints = [].concat(entryPoints || []);

  var modules = Object.keys(graph.adjList);
  var discarded = {};

  modules.forEach(function (moduleName) {
    Object.keys(graph.adjList[moduleName]).forEach(function (depName) {
      discarded[depName] = true;
    });
  });

  modules.filter(function (moduleName) {
    return !discarded[moduleName];
  }).sort().forEach(function (moduleName) {
    if (entryPoints.indexOf(moduleName) === -1) {
      entryPoints.push(moduleName);
    }
  });

  return entryPoints;
}

exports.postOrderTraverseTree = function postOrderTraverseTree(tree, loader, entryPoints) {
  entryPoints = entryPoints || [];

  // Post order traversal sorted module list
  var postOrder = [];
  var graph = new Graph(true);

  // Seed graph with all relations
  Object.keys(tree).forEach(function (moduleName) {
    var module = tree[moduleName];

    module.deps.forEach(function (depName) {
      graph.addEdge(module.name, module.depMap[depName]);
    });
  });

  // Post order traversal of graph, one per entryPoint
  getGraphEntryPoints(graph, entryPoints).forEach(function (entryPoint) {
    depthFirst(graph, entryPoint, {
      leaveVertex: function (moduleName) {
        // Avoidj duplicates
        if (postOrder.indexOf(moduleName) === -1) {
          postOrder.push(moduleName);
        }
      }
    });
  });

  // The graph some times includes things the tracer has already discarded. Filter those
  postOrder = postOrder.filter(function (moduleName) {
    return moduleName in tree;
  });

  return postOrder;
};
<|endoftext|>


<commit_before>var controllerDir;
var appName;

function getAppName() {
    var parts = __dirname.replace(/\\/g, '/').split('/');
    return parts[parts.length - 2].split('.')[0];
}

// Get js-controller directory to load libs
function getControllerDir(isInstall) {
    var fs = require('fs');
    // Find the js-controller location
    var controllerDir = __dirname.replace(/\\/g, '/');
    controllerDir = controllerDir.split('/');
    if (controllerDir[controllerDir.length - 3] === 'adapter') {
        controllerDir.splice(controllerDir.length - 3, 3);
        controllerDir = controllerDir.join('/');
    } else if (controllerDir[controllerDir.length - 3] === 'node_modules') {
        controllerDir.splice(controllerDir.length - 3, 3);
        controllerDir = controllerDir.join('/');
        if (fs.existsSync(controllerDir + '/node_modules/' + appName + '.js-controller')) {
            controllerDir += '/node_modules/' + appName + '.js-controller';
        } else if (fs.existsSync(controllerDir + '/node_modules/' + appName.toLowerCase() + '.js-controller')) {
            controllerDir += '/node_modules/' + appName.toLowerCase() + '.js-controller';
        } else if (!fs.existsSync(controllerDir + '/controller.js')) {
            if (!isInstall) {
                console.log('Cannot find js-controller');
                process.exit(10);
            } else {
                process.exit();
            }
        }
    } else if (fs.existsSync(__dirname + '/../../node_modules/' + appName.toLowerCase() + '.js-controller')) {
        controllerDir.splice(controllerDir.length - 2, 2);
        return controllerDir.join('/') + '/node_modules/' + appName.toLowerCase() + '.js-controller';
    } else {
        if (!isInstall) {
            console.log('Cannot find js-controller');
            process.exit(10);
        } else {
            process.exit();
        }
    }
    return controllerDir;
}

// Read controller configuration file
function getConfig() {
    var fs = require('fs');
    if (fs.existsSync(controllerDir + '/conf/' + appName + '.json')) {
        return JSON.parse(fs.readFileSync(controllerDir + '/conf/' + appName + '.json'));
    } else if (fs.existsSync(controllerDir + '/conf/' + appName.toLowerCase() + '.json')) {
        return JSON.parse(fs.readFileSync(controllerDir + '/conf/' + appName.toLowerCase() + '.json'));
    } else {
        throw new Error('Cannot find ' + controllerDir + '/conf/' + appName + '.json');
    }
}
appName       = getAppName();
controllerDir = getControllerDir(typeof process !== 'undefined' && process.argv && process.argv.indexOf('--install') !== -1);

exports.controllerDir = controllerDir;
exports.getConfig =     getConfig;
exports.Adapter =       require(controllerDir + '/lib/adapter.js');
exports.appName =       appName;
<commit_msg>update utils.js<commit_after>'use strict';

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

let controllerDir;
let appName;

/**
 * returns application name
 *
 * The name of the application can be different and this function finds it out.
 *
 * @returns {string}
 */
 function getAppName() {
    const parts = __dirname.replace(/\\/g, '/').split('/');
    return parts[parts.length - 2].split('.')[0];
}

/**
 * looks for js-controller home folder
 *
 * @param {boolean} isInstall
 * @returns {string}
 */
function getControllerDir(isInstall) {
    // Find the js-controller location
    const possibilities = [
        'iobroker.js-controller',
        'ioBroker.js-controller',
    ];
    /** @type {string} */
    let controllerPath;
    for (const pkg of possibilities) {
        try {
            const possiblePath = require.resolve(pkg);
            if (fs.existsSync(possiblePath)) {
                controllerPath = possiblePath;
                break;
            }
        } catch (e) { /* not found */ }
    }
    if (controllerPath == null) {
        if (!isInstall) {
            console.log('Cannot find js-controller');
            process.exit(10);
        } else {
            process.exit();
        }
    }
    // we found the controller
    return path.dirname(controllerPath);
}

/**
 * reads controller base settings
 *
 * @alias getConfig
 * @returns {object}
 */
 function getConfig() {
    let configPath;
    if (fs.existsSync(
        configPath = path.join(controllerDir, 'conf', appName + '.json')
    )) {
        return JSON.parse(fs.readFileSync(configPath, 'utf8'));
    } else if (fs.existsSync(
        configPath = path.join(controllerDir, 'conf', + appName.toLowerCase() + '.json')
    )) {
        return JSON.parse(fs.readFileSync(configPath, 'utf8'));
    } else {
        throw new Error('Cannot find ' + controllerDir + '/conf/' + appName + '.json');
    }
}
appName       = getAppName();
controllerDir = getControllerDir(typeof process !== 'undefined' && process.argv && process.argv.indexOf('--install') !== -1);
const adapter = require(path.join(controllerDir, 'lib/adapter.js'));

exports.controllerDir = controllerDir;
exports.getConfig =     getConfig;
exports.Adapter =       adapter;
exports.appName =       appName;
<|endoftext|>


<commit_before>*A module containing several utilities*

//  # Setup
//
var fs = require('graceful-fs'),
    async = require('async'),
    glob = require('glob'),
    tool = {};

// # Utilities
//
// ## searchFs
//
// function to search the filesystem (using `glob` module)
tool.searchFs = function (string, directory, callback) {

  glob(string,

    //
    // - Options as per the [`glob` manual](https://npmjs.org/package/glob)
    //
    {
      //    > "The current working directory in which to search.
      'cwd': directory,
      //    > no dotfiles
      'dot': false,
      //    > Add a / character to directory matches.
      'mark': true,
      //    > Perform a case-insensitive match. (…)"
      'nocase': true,
      'nonegate': true
    },

    function globResult(err, res) {
      callback(err || null, res || null);
  });

};

// ## isNotDir
//
// function to check if a `path` is **not** a directory
tool.isNotDir = function (path, callback) {
  fs.stat(path, function (err, stats) {
    if (err) {
      callback(err)
    }
    callback(!stats.isDirectory());
  });
};

tool.isDir = function (path, callback) {
  dirCheck(path, true, callback)
};

tool.isNotDir = function (path, callback) {
  dirCheck(path, false, callback)
};

// ## dirCheck
//
function dirCheck(path, bool, callback) {
  fs.stat(path, function (err, stats) {
    if (err) { callback(err); }

    res = !!stats.isDirectory();
    if (!bool) { res = !res; }

    callback(null, res);
  });
};

// # readJSONfile
//
// function to safely read JSON file from `path` in file system
tool.readJSONfile = function (path, callback) {

  fs.readFile(path, 'utf8', function (err, data) {

    // catch file read error and `callback` with it
    if (err) { return callback(err); }

    // carefully parse the JSON data
    try {
      result = JSON.parse(data);
    } catch (err) {
      // catch any parsing error and callback with it
      return callback(err);
    }

    // callback with no error and the result
    callback(null, result);

  });

}

// # cleanDirectories
//
// function to clean directories from a `list` of paths
tool.cleanDirectories = function (list, callback) {
  // run `isNotDir` for every item in `list`, then `callback` with `result`.
  async.filter(
    list,
    function (item, callback) {
      tool.isNotDir(item, function (err, res) {
        callback(res);
      })
    },
    callback );
};

//
// # Attaching
//

// - `exports.attach` gets called by broadway on `app.use`
tool.attach = function (options) {

  var app = this;

  // Here, we attach some functions directly to the `app` object.

  // `fail()` an `app` with `err`, can be 'string' or 'Error':
  app.fail = function (error, data, cb) {

    var meta, callback;

    // no data? use as callback.
    if (typeof data === 'function') {
      callback = data;
    } else {
      meta = data;
    }

    if (error.constructor.name !== 'Error') {

      if (typeof error === 'string') {
        error = new Error(error);
      }
      else {
        meta = error;
        error = new Error("");
      }
    }

    // no meta? use as callback.
    if (typeof meta === 'function') {
      callback = meta;
      app.log.error(error);
    } else {
      app.log.error(error, meta);
    }
    return callback(error);

  };

};

// `exports.init` gets called by broadway on `app.init`,
tool.init = function (done) {
  // but this plugin doesn't require any initialization steps)
  return done();
};

// export `tool` as module
module.exports = tool;<commit_msg>utils: fix typo<commit_after>// *A module containing several utilities*

//  # Setup
//
var fs = require('graceful-fs'),
    async = require('async'),
    glob = require('glob'),
    tool = {};

// # Utilities
//
// ## searchFs
//
// function to search the filesystem (using `glob` module)
tool.searchFs = function (string, directory, callback) {

  glob(string,

    //
    // - Options as per the [`glob` manual](https://npmjs.org/package/glob)
    //
    {
      //    > "The current working directory in which to search.
      'cwd': directory,
      //    > no dotfiles
      'dot': false,
      //    > Add a / character to directory matches.
      'mark': true,
      //    > Perform a case-insensitive match. (…)"
      'nocase': true,
      'nonegate': true
    },

    function globResult(err, res) {
      callback(err || null, res || null);
  });

};

// ## isNotDir
//
// function to check if a `path` is **not** a directory
tool.isNotDir = function (path, callback) {
  fs.stat(path, function (err, stats) {
    if (err) {
      callback(err)
    }
    callback(!stats.isDirectory());
  });
};

tool.isDir = function (path, callback) {
  dirCheck(path, true, callback)
};

tool.isNotDir = function (path, callback) {
  dirCheck(path, false, callback)
};

// ## dirCheck
//
function dirCheck(path, bool, callback) {
  fs.stat(path, function (err, stats) {
    if (err) { callback(err); }

    res = !!stats.isDirectory();
    if (!bool) { res = !res; }

    callback(null, res);
  });
};

// # readJSONfile
//
// function to safely read JSON file from `path` in file system
tool.readJSONfile = function (path, callback) {

  fs.readFile(path, 'utf8', function (err, data) {

    // catch file read error and `callback` with it
    if (err) { return callback(err); }

    // carefully parse the JSON data
    try {
      result = JSON.parse(data);
    } catch (err) {
      // catch any parsing error and callback with it
      return callback(err);
    }

    // callback with no error and the result
    callback(null, result);

  });

}

// # cleanDirectories
//
// function to clean directories from a `list` of paths
tool.cleanDirectories = function (list, callback) {
  // run `isNotDir` for every item in `list`, then `callback` with `result`.
  async.filter(
    list,
    function (item, callback) {
      tool.isNotDir(item, function (err, res) {
        callback(res);
      })
    },
    callback );
};

//
// # Attaching
//

// - `exports.attach` gets called by broadway on `app.use`
tool.attach = function (options) {

  var app = this;

  // Here, we attach some functions directly to the `app` object.

  // `fail()` an `app` with `err`, can be 'string' or 'Error':
  app.fail = function (error, data, cb) {

    var meta, callback;

    // no data? use as callback.
    if (typeof data === 'function') {
      callback = data;
    } else {
      meta = data;
    }

    if (error.constructor.name !== 'Error') {

      if (typeof error === 'string') {
        error = new Error(error);
      }
      else {
        meta = error;
        error = new Error("");
      }
    }

    // no meta? use as callback.
    if (typeof meta === 'function') {
      callback = meta;
      app.log.error(error);
    } else {
      app.log.error(error, meta);
    }
    return callback(error);

  };

};

// `exports.init` gets called by broadway on `app.init`,
tool.init = function (done) {
  // but this plugin doesn't require any initialization steps)
  return done();
};

// export `tool` as module
module.exports = tool;<|endoftext|>


<commit_before>
const Utils = {
    /**
     * Shuffle the elements in a given array.
     *
     * @param Array arr
     * @return Array
     */
    shuffleArray: function( arr ) {
        // KFY shuffle algorithm - start at the end, pick a random element,
        //  and swap; continue until we hit the start.
        for ( let i = arr.length - 1; i > 0; i-- ) {
            var j = Math.floor(Math.random() * (i + 1));
            var temp = arr[i];
            arr[i] = arr[j];
            arr[j] = temp;
        }

        return arr;
    },
    /**
     * Convert a given integer into an array representing the integer in binary,
     *  in the specified number of bits.
     *
     * @param Int integer
     * @param Int bytesize
     * @return Array
     */
    convertIntToBinary: function( integer, bytesize = 8 ) {
        // Make sure we're working with an integer type
        integer = parseInt(integer) || 0;
        let binary = integer.toString(2);

        while ( binary.length < bytesize ) {
            // Pad zeros on the left if needed
            binary = 0 + binary;
        }
        if ( binary.length > bytesize ) {
            // Trim down to the given bytesize if needed
            binary = binary.slice(-bytesize);
        }

        return Utils.convertStringToArray(binary, { mapFn: (x) => { return parseInt(x); } });
    },
    /**
     * Take a binary array or string, and convert it to an integer.
     *
     * @param Array|String binary
     * @return Int
     */
    convertBinaryToInt: function( binary ) {
        // Make sure we're working with a string type
        if ( Array.isArray(binary) ) {
            binary = binary.join('');
        }
        return ( parseInt(binary, 2) || 0 );
    },
    /**
     * Split a given string into an array of elements, based on the options provided.
     *  Options:
     *      - delim: Character to split the string; defaults to '', splitting each character
     *      - mapFn: Function to call on each resulting element of the array
     *
     * @param String stringIn
     * @param Object options
     * @return Array
     */
    convertStringToArray: function( stringIn, options = {} ) {
        const emptyFn = (x) => { return x; };
        options.delim = ( options.delim || '' );
        options.mapFn = ( options.mapFn || emptyFn );
        //console.log(`Converting "${stringIn.toString().split(options.delim)}"`);
        return stringIn.toString().split(options.delim).map(options.mapFn);
    },
    /**
     * Simple function to normalise a value between 0 and 1
     *
     * @param Int|Float value
     * @param Int|Float max
     * @return Float
     */
    normalise: function( value, max ) {
        return ( value / max );
    },
    /**
     * Take the given float and return a percentage rounded to the given number
     *  of decimal places (default of two).
     *
     * @param Int|Float value
     * @param Int decimals
     * @return Int|Float
     */
    formatAsPercentage: function( value, decimals = 2 ) {
        decimals = ( decimals >= 0 ) ? decimals : 0;
        let tens = Math.pow(10, decimals);

        return Math.round( value * 100 * tens ) / tens;
    }
};

module.exports = Utils;
<commit_msg>Updated utility functions<commit_after>
const Utils = {
    /**
     * Shuffle the elements in a given array.
     *
     * @param Array arr
     * @return Array
     */
    shuffleArray: function( arr ) {
        // KFY shuffle algorithm - start at the end, pick a random element,
        //  and swap; continue until we hit the start.
        for ( let i = arr.length - 1; i > 0; i-- ) {
            var j = Math.floor(Math.random() * (i + 1));
            var temp = arr[i];
            arr[i] = arr[j];
            arr[j] = temp;
        }

        return arr;
    },
    /**
     * Convert a given integer into an array representing the integer in binary,
     *  in the specified number of bits.
     *
     * @param Int integer
     * @param Int bytesize
     * @return Array
     */
    convertIntToBinary: function( integer, bytesize = 8 ) {
        // Make sure we're working with an integer type
        integer = parseInt(integer) || 0;
        let binary = integer.toString(2);

        while ( binary.length < bytesize ) {
            // Pad zeros on the left if needed
            binary = 0 + binary;
        }
        if ( binary.length > bytesize ) {
            // Trim down to the given bytesize if needed
            binary = binary.slice(-bytesize);
        }

        return Utils.convertStringToArray(binary, { mapFn: (x) => { return parseInt(x); } });
    },
    /**
     * Take a binary array or string, and convert it to an integer.
     *
     * @param Array|String binary
     * @return Int
     */
    convertBinaryToInt: function( binary ) {
        // Make sure we're working with a string type
        if ( Array.isArray(binary) ) {
            binary = binary.join('');
        }
        return ( parseInt(binary, 2) || 0 );
    },
    /**
     * Split a given string into an array of elements, based on the options provided.
     *  Options:
     *      - delim: Character to split the string; defaults to '', splitting each character
     *      - mapFn: Function to call on each resulting element of the array
     *
     * @param String stringIn
     * @param Object options
     * @return Array
     */
    convertStringToArray: function( stringIn, options = {} ) {
        const emptyFn = (x) => { return x; };
        options.delim = ( options.delim || '' );
        options.mapFn = ( options.mapFn || emptyFn );
        //console.log(`Converting "${stringIn.toString().split(options.delim)}"`);
        return stringIn.toString().split(options.delim).map(options.mapFn);
    },
    /**
     * Simple function to normalise a value between 0 and 1
     *
     * @param Int|Float value
     * @param Int|Float max
     * @return Float
     */
    normalise: function( value, max ) {
        return ( value / max );
    },
    /**
     * Take the given float and return a percentage rounded to the given number
     *  of decimal places (default of two).
     *
     * @param Int|Float value
     * @param Int decimals
     * @return Int|Float
     */
    formatAsPercentage: function( value, decimals = 2 ) {
        decimals = ( decimals >= 0 ) ? decimals : 0;
        let tens = Math.pow(10, decimals);

        return Math.round( value * 100 * tens ) / tens;
    },
    /**
     * Sort the given output into labels based on the confidence reported.
     *
     * @param Array responses
     * @param Array labels
     * @return Array
     */
    sortByConfidence: function( responses, labels ) {
        // First make sure we're working with arrays
        if ( !Array.isArray(responses) ) {
            responses = [responses];
        }
        if ( !Array.isArray(labels) ) {
            labels = [labels];
        }
        // Next, ensure there's the same number of items in each
        if ( responses.length !== labels.length ) {
            throw 'Number of responses and number of labels do not match.';
        }

        // Now the actual work - first, by combining the responses with their
        //  corresponding labels
        let results = [];
        for ( let i = 0, ln = responses.length; i < ln; i++ ) {
            results.push({
                label: labels[i],
                value: responses[i]
            });
        }
        // Then, sort by largest value first
        results = results.sort(( a, b ) => {
            if ( a.value == b.value ) {
                return 0;
            }
            if ( a.value > b.value ) {
                return -1;
            }
            return 1;
        });

        return results;
    }
};

module.exports = Utils;
<|endoftext|>


<commit_before>export let styles = {
  item: {
    padding: '2px 6px',
    cursor: 'default'
  },

  highlightedItem: {
    color: 'white',
    background: 'hsl(200, 50%, 50%)',
    padding: '2px 6px',
    cursor: 'default'
  },

  menu: {
    border: 'solid 1px #ccc'
  }
}

export function matchStateToTerm(state, value) {
  return (
    state.name.toLowerCase().indexOf(value.toLowerCase()) !== -1 ||
    state.abbr.toLowerCase().indexOf(value.toLowerCase()) !== -1
  )
}

/**
 * An example of how to implement a relevancy-based sorting method. States are
 * sorted based on the location of the match - For example, a search for "or"
 * will return "Oregon" before "North Carolina" even though "North Carolina"
 * would normally sort above Oregon. Strings where the match is in the same
 * location (or there is no match) will be sorted alphabetically - For example,
 * a search for "or" would return "North Carolina" above "North Dakota".
 */
export function sortStates(a, b, value) {
  const aLower = a.name.toLowerCase()
  const bLower = b.name.toLowerCase()
  const valueLower = value.toLowerCase()
  const queryPosA = aLower.indexOf(valueLower)
  const queryPosB = bLower.indexOf(valueLower)
  if (queryPosA !== queryPosB) {
    return queryPosA - queryPosB
  }
  return aLower < bLower ? -1 : 1
}

export function fakeRequest(value, cb) {
  if (value === '')
    return getStates()
  const items = getStates().filter((state) => {
    return matchStateToTerm(state, value)
  })
  setTimeout(() => {
    cb(items)
  }, 500)
}

export function getStates() {
  return [
    { abbr: 'AL', name: 'Alabama' },
    { abbr: 'AK', name: 'Alaska' },
    { abbr: 'AZ', name: 'Arizona' },
    { abbr: 'AR', name: 'Arkansas' },
    { abbr: 'CA', name: 'California' },
    { abbr: 'CO', name: 'Colorado' },
    { abbr: 'CT', name: 'Connecticut' },
    { abbr: 'DE', name: 'Delaware' },
    { abbr: 'FL', name: 'Florida' },
    { abbr: 'GA', name: 'Georgia' },
    { abbr: 'HI', name: 'Hawaii' },
    { abbr: 'ID', name: 'Idaho' },
    { abbr: 'IL', name: 'Illinois' },
    { abbr: 'IN', name: 'Indiana' },
    { abbr: 'IA', name: 'Iowa' },
    { abbr: 'KS', name: 'Kansas' },
    { abbr: 'KY', name: 'Kentucky' },
    { abbr: 'LA', name: 'Louisiana' },
    { abbr: 'ME', name: 'Maine' },
    { abbr: 'MD', name: 'Maryland' },
    { abbr: 'MA', name: 'Massachusetts' },
    { abbr: 'MI', name: 'Michigan' },
    { abbr: 'MN', name: 'Minnesota' },
    { abbr: 'MS', name: 'Mississippi' },
    { abbr: 'MO', name: 'Missouri' },
    { abbr: 'MT', name: 'Montana' },
    { abbr: 'NE', name: 'Nebraska' },
    { abbr: 'NV', name: 'Nevada' },
    { abbr: 'NH', name: 'New Hampshire' },
    { abbr: 'NJ', name: 'New Jersey' },
    { abbr: 'NM', name: 'New Mexico' },
    { abbr: 'NY', name: 'New York' },
    { abbr: 'NC', name: 'North Carolina' },
    { abbr: 'ND', name: 'North Dakota' },
    { abbr: 'OH', name: 'Ohio' },
    { abbr: 'OK', name: 'Oklahoma' },
    { abbr: 'OR', name: 'Oregon' },
    { abbr: 'PA', name: 'Pennsylvania' },
    { abbr: 'RI', name: 'Rhode Island' },
    { abbr: 'SC', name: 'South Carolina' },
    { abbr: 'SD', name: 'South Dakota' },
    { abbr: 'TN', name: 'Tennessee' },
    { abbr: 'TX', name: 'Texas' },
    { abbr: 'UT', name: 'Utah' },
    { abbr: 'VT', name: 'Vermont' },
    { abbr: 'VA', name: 'Virginia' },
    { abbr: 'WA', name: 'Washington' },
    { abbr: 'WV', name: 'West Virginia' },
    { abbr: 'WI', name: 'Wisconsin' },
    { abbr: 'WY', name: 'Wyoming' }
  ]
}

<commit_msg>Make utils.fakeRequest work when `value` is empty<commit_after>export let styles = {
  item: {
    padding: '2px 6px',
    cursor: 'default'
  },

  highlightedItem: {
    color: 'white',
    background: 'hsl(200, 50%, 50%)',
    padding: '2px 6px',
    cursor: 'default'
  },

  menu: {
    border: 'solid 1px #ccc'
  }
}

export function matchStateToTerm(state, value) {
  return (
    state.name.toLowerCase().indexOf(value.toLowerCase()) !== -1 ||
    state.abbr.toLowerCase().indexOf(value.toLowerCase()) !== -1
  )
}

/**
 * An example of how to implement a relevancy-based sorting method. States are
 * sorted based on the location of the match - For example, a search for "or"
 * will return "Oregon" before "North Carolina" even though "North Carolina"
 * would normally sort above Oregon. Strings where the match is in the same
 * location (or there is no match) will be sorted alphabetically - For example,
 * a search for "or" would return "North Carolina" above "North Dakota".
 */
export function sortStates(a, b, value) {
  const aLower = a.name.toLowerCase()
  const bLower = b.name.toLowerCase()
  const valueLower = value.toLowerCase()
  const queryPosA = aLower.indexOf(valueLower)
  const queryPosB = bLower.indexOf(valueLower)
  if (queryPosA !== queryPosB) {
    return queryPosA - queryPosB
  }
  return aLower < bLower ? -1 : 1
}

export function fakeRequest(value, cb) {
  setTimeout(cb, 500, value ?
    getStates().filter(state => matchStateToTerm(state, value)) :
    getStates()
  )
}

export function getStates() {
  return [
    { abbr: 'AL', name: 'Alabama' },
    { abbr: 'AK', name: 'Alaska' },
    { abbr: 'AZ', name: 'Arizona' },
    { abbr: 'AR', name: 'Arkansas' },
    { abbr: 'CA', name: 'California' },
    { abbr: 'CO', name: 'Colorado' },
    { abbr: 'CT', name: 'Connecticut' },
    { abbr: 'DE', name: 'Delaware' },
    { abbr: 'FL', name: 'Florida' },
    { abbr: 'GA', name: 'Georgia' },
    { abbr: 'HI', name: 'Hawaii' },
    { abbr: 'ID', name: 'Idaho' },
    { abbr: 'IL', name: 'Illinois' },
    { abbr: 'IN', name: 'Indiana' },
    { abbr: 'IA', name: 'Iowa' },
    { abbr: 'KS', name: 'Kansas' },
    { abbr: 'KY', name: 'Kentucky' },
    { abbr: 'LA', name: 'Louisiana' },
    { abbr: 'ME', name: 'Maine' },
    { abbr: 'MD', name: 'Maryland' },
    { abbr: 'MA', name: 'Massachusetts' },
    { abbr: 'MI', name: 'Michigan' },
    { abbr: 'MN', name: 'Minnesota' },
    { abbr: 'MS', name: 'Mississippi' },
    { abbr: 'MO', name: 'Missouri' },
    { abbr: 'MT', name: 'Montana' },
    { abbr: 'NE', name: 'Nebraska' },
    { abbr: 'NV', name: 'Nevada' },
    { abbr: 'NH', name: 'New Hampshire' },
    { abbr: 'NJ', name: 'New Jersey' },
    { abbr: 'NM', name: 'New Mexico' },
    { abbr: 'NY', name: 'New York' },
    { abbr: 'NC', name: 'North Carolina' },
    { abbr: 'ND', name: 'North Dakota' },
    { abbr: 'OH', name: 'Ohio' },
    { abbr: 'OK', name: 'Oklahoma' },
    { abbr: 'OR', name: 'Oregon' },
    { abbr: 'PA', name: 'Pennsylvania' },
    { abbr: 'RI', name: 'Rhode Island' },
    { abbr: 'SC', name: 'South Carolina' },
    { abbr: 'SD', name: 'South Dakota' },
    { abbr: 'TN', name: 'Tennessee' },
    { abbr: 'TX', name: 'Texas' },
    { abbr: 'UT', name: 'Utah' },
    { abbr: 'VT', name: 'Vermont' },
    { abbr: 'VA', name: 'Virginia' },
    { abbr: 'WA', name: 'Washington' },
    { abbr: 'WV', name: 'West Virginia' },
    { abbr: 'WI', name: 'Wisconsin' },
    { abbr: 'WY', name: 'Wyoming' }
  ]
}

<|endoftext|>


<commit_before>"use strict";

var _             = require("../lodash.custom");
var devIp         = require("dev-ip")();
var Immutable     = require("immutable");
var portScanner   = require("portscanner");
var path          = require("path");
var List          = require("immutable").List;
var UAParser      = require("ua-parser-js");
var parser        = new UAParser();

var utils = {
    /**
     * @param {Object} options
     * @returns {String|boolean} - the IP address
     * @param devIp
     */
    getHostIp: function (options, devIp) {

        if (options) {
            var host = options.get("host");
            if (host && host !== "localhost") {
                return host;
            }
            if (options.get("detect") === false || !devIp.length) {
                return false;
            }
        }

        return devIp.length ? devIp[0] : false;
    },
    /**
     * Set URL Options
     */
    getUrlOptions: function (options) {

        var scheme = options.get("scheme");

        var port   = options.get("port");
        var urls   = {};

        if (options.get("online") === false) {
            urls.local = utils.getUrl(scheme + "://localhost:" + port, options);
            return Immutable.fromJS(urls);
        }

        var external  = utils.xip(utils.getHostIp(options, devIp), options);
        var localhost = "localhost";

        if (options.get("xip")) {
            localhost = "127.0.0.1";
        }

        localhost = utils.xip(localhost, options);

        return Immutable.fromJS(utils.getUrls(external, localhost, scheme, options));
    },
    /**
     * Append a start path if given in options
     * @param {String} url
     * @param {Object} options
     * @returns {String}
     */
    getUrl: function (url, options) {

        var prefix = "/";
        var startPath = options.get("startPath");

        if (startPath) {
            if (startPath.charAt(0) === "/") {
                prefix = "";
            }
            url = url + prefix + startPath;
        }

        return url;
    },
    /**
     * @param {String} external
     * @param {String} local
     * @param {String} scheme
     * @param {Object} options
     * @returns {{local: string, external: string}}
     */
    getUrls: function (external, local, scheme, options) {

        var urls = {
            local: utils.getUrl(utils._makeUrl(scheme, local, options.get("port")), options)
        };

        if (external !== local) {
            urls.external = utils.getUrl(utils._makeUrl(scheme, external, options.get("port")), options);
        }

        return urls;
    },
    /**
     * @param {String} scheme
     * @param {String} host
     * @param {Number} port
     * @returns {String}
     * @private
     */
    _makeUrl: function (scheme, host, port) {
        return scheme + "://" + host + ":" + port;
    },
    /**
     * Get ports
     * @param {Object} options
     * @param {Function} cb
     */
    getPorts: function (options, cb) {

        var port  = options.get("port");
        var ports = options.get("ports"); // backwards compatibility
        var max;

        if (ports) {
            port = ports.get("min");
            max  = ports.get("max") || null;
        }

        utils.getPort(port, max, cb);
    },
    getPort: function (port, max, cb) {
        portScanner.findAPortNotInUse(port, max, {
            host: "localhost",
            timeout: 1000
        }, cb);
    },
    /**
     * @param {String} ua
     * @returns {Object}
     */
    getUaString: function (ua) {
        return parser.setUA(ua).getBrowser();
    },
    /**
     * Open the page in browser
     * @param {String} url
     * @param {Object} options
     */
    openBrowser: function (url, options) {

        var open    = options.get("open");
        var browser = options.get("browser");

        if (_.isString(open)) {
            if (options.getIn(["urls", open])) {
                url = options.getIn(["urls", open]);
            }
        }

        if (open) {
            if (browser !== "default") {
                if (utils.isList(browser)) {
                    browser.forEach(function (browser) {
                        utils.open(url, browser);
                    });
                } else {
                    utils.open(url, browser); // single
                }
            } else {
                utils.open(url);
            }
        }
    },
    /**
     * Wrapper for open module - for easier stubbin'
     * @param url
     * @param name
     */
    open: function (url, name) {
        require("opn")(url, {app: name || null});
    },
    /**
     * @param {Boolean} kill
     * @param {String|Error} [errMessage]
     * @param {Function} [cb]
     */
    fail: function (kill, errMessage, cb) {
        if (kill) {
            if (_.isFunction(cb)) {
                if (errMessage.message) { // Is this an error object?
                    cb(errMessage);
                } else {
                    cb(new Error(errMessage));
                }
            }
            process.exit(1);
        }
    },
    /**
     * Add support for xip.io urls
     * @param {String} host
     * @param {Object} options
     * @returns {String}
     */
    xip: function (host, options) {
        var suffix = options.get("hostnameSuffix");
        if (options.get("xip")) {
            return host + ".xip.io";
        }
        if (suffix) {
            return host + suffix;
        }
        return host;
    },
    /**
     * Determine if an array of file paths will cause a full page reload.
     * @param {Array} needles - filepath such as ["core.css", "index.html"]
     * @param {Array} haystack
     * @returns {Boolean}
     */
    willCauseReload: function (needles, haystack) {
        return needles.some(function (needle) {
            return !_.includes(haystack, path.extname(needle).replace(".", ""));
        });
    },
    isList: Immutable.List.isList,
    isMap: Immutable.List.isMap,
    /**
     * @param {Map} options
     * @returns {Array}
     */
    getConfigErrors: function (options) {

        var messages = require("./config").errors;

        var errors = [];

        if (options.get("server") && options.get("proxy")) {
            errors.push(messages["server+proxy"]);
        }

        if (options.get("https") && options.get("proxy")) {
            if (options.getIn(["proxy", "url", "protocol"]) !== "https:") {
                errors.push([messages["proxy+https"], options.getIn(["proxy", "target"])].join(" "));
            }
        }

        return errors;
    },
    /**
     * @param {Map} options
     * @param {Function} [cb]
     */
    verifyConfig: function (options, cb) {
        var errors = utils.getConfigErrors(options);
        if (errors.length) {
            utils.fail(true, errors.join("\n"), cb);
            return false;
        }
        return true;
    },
    /**
     * @param err
     */
    defaultCallback: function (err) {
        if (err && err.message) {
            console.error(err.message);
        }
    },
    eachSeries: function (arr, iterator, callback) {
        callback = callback || function () {};
        var completed = 0;
        var iterate = function () {
            iterator(arr[completed], function (err) {
                if (err) {
                    callback(err);
                    callback = function () {};
                } else {
                    ++completed;
                    if (completed >= arr.length) {
                        callback();
                    } else {
                        iterate();
                    }
                }
            });
        };
        iterate();
    },
    /**
     * @param {Immutable.List|Array|String} incoming
     * @returns {Array}
     */
    arrayify: function (incoming) {
        if (List.isList(incoming)) {
            return incoming.toArray();
        }
        return [].concat(incoming).filter(Boolean);
    }
};

module.exports              = utils;
module.exports.portscanner  = portScanner;
module.exports.UAParser     = UAParser;
module.exports.connect      = require("connect");
module.exports.devIp        = devIp;
module.exports.serveStatic  = require("serve-static");
module.exports.easyExtender = require("easy-extender");
<commit_msg>support opn's options for loading a browser with arguments<commit_after>"use strict";

var _             = require("../lodash.custom");
var devIp         = require("dev-ip")();
var Immutable     = require("immutable");
var portScanner   = require("portscanner");
var path          = require("path");
var List          = require("immutable").List;
var UAParser      = require("ua-parser-js");
var parser        = new UAParser();

var utils = {
    /**
     * @param {Object} options
     * @returns {String|boolean} - the IP address
     * @param devIp
     */
    getHostIp: function (options, devIp) {

        if (options) {
            var host = options.get("host");
            if (host && host !== "localhost") {
                return host;
            }
            if (options.get("detect") === false || !devIp.length) {
                return false;
            }
        }

        return devIp.length ? devIp[0] : false;
    },
    /**
     * Set URL Options
     */
    getUrlOptions: function (options) {

        var scheme = options.get("scheme");

        var port   = options.get("port");
        var urls   = {};

        if (options.get("online") === false) {
            urls.local = utils.getUrl(scheme + "://localhost:" + port, options);
            return Immutable.fromJS(urls);
        }

        var external  = utils.xip(utils.getHostIp(options, devIp), options);
        var localhost = "localhost";

        if (options.get("xip")) {
            localhost = "127.0.0.1";
        }

        localhost = utils.xip(localhost, options);

        return Immutable.fromJS(utils.getUrls(external, localhost, scheme, options));
    },
    /**
     * Append a start path if given in options
     * @param {String} url
     * @param {Object} options
     * @returns {String}
     */
    getUrl: function (url, options) {

        var prefix = "/";
        var startPath = options.get("startPath");

        if (startPath) {
            if (startPath.charAt(0) === "/") {
                prefix = "";
            }
            url = url + prefix + startPath;
        }

        return url;
    },
    /**
     * @param {String} external
     * @param {String} local
     * @param {String} scheme
     * @param {Object} options
     * @returns {{local: string, external: string}}
     */
    getUrls: function (external, local, scheme, options) {

        var urls = {
            local: utils.getUrl(utils._makeUrl(scheme, local, options.get("port")), options)
        };

        if (external !== local) {
            urls.external = utils.getUrl(utils._makeUrl(scheme, external, options.get("port")), options);
        }

        return urls;
    },
    /**
     * @param {String} scheme
     * @param {String} host
     * @param {Number} port
     * @returns {String}
     * @private
     */
    _makeUrl: function (scheme, host, port) {
        return scheme + "://" + host + ":" + port;
    },
    /**
     * Get ports
     * @param {Object} options
     * @param {Function} cb
     */
    getPorts: function (options, cb) {

        var port  = options.get("port");
        var ports = options.get("ports"); // backwards compatibility
        var max;

        if (ports) {
            port = ports.get("min");
            max  = ports.get("max") || null;
        }

        utils.getPort(port, max, cb);
    },
    getPort: function (port, max, cb) {
        portScanner.findAPortNotInUse(port, max, {
            host: "localhost",
            timeout: 1000
        }, cb);
    },
    /**
     * @param {String} ua
     * @returns {Object}
     */
    getUaString: function (ua) {
        return parser.setUA(ua).getBrowser();
    },
    /**
     * Open the page in browser
     * @param {String} url
     * @param {Object} options
     */
    openBrowser: function (url, options) {

        var open    = options.get("open");
        var browser = options.get("browser");

        if (_.isString(open)) {
            if (options.getIn(["urls", open])) {
                url = options.getIn(["urls", open]);
            }
        }

        if (open) {
            if (browser !== "default") {
                if (utils.isList(browser)) {
                    browser.forEach(function (browser) {
                        utils.open(url, browser);
                    });
                } else {
                    utils.open(url, browser); // single
                }
            } else {
                utils.open(url);
            }
        }
    },

    // https://browsersync.io/docs/options
    //https://www.npmjs.com/package/opn
    //
    /**
     * Wrapper for open module - for easier stubbin'
     * @param url
     * @param browser
     */
    open: function (url, browser) {
        var options = null;
        if(_.isString(browser)) {
            options = {app: browser};
        } else if(Immutable.Map.isMap(browser)) {
            options = {app: browser.toJS().app};
        }
        require("opn")(url, options);
    },
    /**
     * @param {Boolean} kill
     * @param {String|Error} [errMessage]
     * @param {Function} [cb]
     */
    fail: function (kill, errMessage, cb) {
        if (kill) {
            if (_.isFunction(cb)) {
                if (errMessage.message) { // Is this an error object?
                    cb(errMessage);
                } else {
                    cb(new Error(errMessage));
                }
            }
            process.exit(1);
        }
    },
    /**
     * Add support for xip.io urls
     * @param {String} host
     * @param {Object} options
     * @returns {String}
     */
    xip: function (host, options) {
        var suffix = options.get("hostnameSuffix");
        if (options.get("xip")) {
            return host + ".xip.io";
        }
        if (suffix) {
            return host + suffix;
        }
        return host;
    },
    /**
     * Determine if an array of file paths will cause a full page reload.
     * @param {Array} needles - filepath such as ["core.css", "index.html"]
     * @param {Array} haystack
     * @returns {Boolean}
     */
    willCauseReload: function (needles, haystack) {
        return needles.some(function (needle) {
            return !_.includes(haystack, path.extname(needle).replace(".", ""));
        });
    },
    isList: Immutable.List.isList,
    isMap: Immutable.List.isMap,
    /**
     * @param {Map} options
     * @returns {Array}
     */
    getConfigErrors: function (options) {

        var messages = require("./config").errors;

        var errors = [];

        if (options.get("server") && options.get("proxy")) {
            errors.push(messages["server+proxy"]);
        }

        if (options.get("https") && options.get("proxy")) {
            if (options.getIn(["proxy", "url", "protocol"]) !== "https:") {
                errors.push([messages["proxy+https"], options.getIn(["proxy", "target"])].join(" "));
            }
        }

        return errors;
    },
    /**
     * @param {Map} options
     * @param {Function} [cb]
     */
    verifyConfig: function (options, cb) {
        var errors = utils.getConfigErrors(options);
        if (errors.length) {
            utils.fail(true, errors.join("\n"), cb);
            return false;
        }
        return true;
    },
    /**
     * @param err
     */
    defaultCallback: function (err) {
        if (err && err.message) {
            console.error(err.message);
        }
    },
    eachSeries: function (arr, iterator, callback) {
        callback = callback || function () {};
        var completed = 0;
        var iterate = function () {
            iterator(arr[completed], function (err) {
                if (err) {
                    callback(err);
                    callback = function () {};
                } else {
                    ++completed;
                    if (completed >= arr.length) {
                        callback();
                    } else {
                        iterate();
                    }
                }
            });
        };
        iterate();
    },
    /**
     * @param {Immutable.List|Array|String} incoming
     * @returns {Array}
     */
    arrayify: function (incoming) {
        if (List.isList(incoming)) {
            return incoming.toArray();
        }
        return [].concat(incoming).filter(Boolean);
    }
};

module.exports              = utils;
module.exports.portscanner  = portScanner;
module.exports.UAParser     = UAParser;
module.exports.connect      = require("connect");
module.exports.devIp        = devIp;
module.exports.serveStatic  = require("serve-static");
module.exports.easyExtender = require("easy-extender");
<|endoftext|>


<commit_before>'use strict';

// core modules
const Path = require('path');

// dep modules
const _ = require('lodash');
const Promise = require('bluebird');
const fs = require('fs-extra');
const marked = require('marked');
const uglify = require('uglify-js');
const stripJsonComments = require('strip-json-comments');
const Less = require('less');
const LessCleanCssPlugin = require('less-plugin-clean-css');
const gzipSize = require('gzip-size');

const GLOB = Promise.promisify(require('glob'));
const lessCleanCssPlugin = new LessCleanCssPlugin({ advanced: true });

let utils = null;

// ---------------------------
// Helpers
// ---------------------------

// used for utils._findDocmaConfigFileSync()
function getFileSync(filename, basePath = null) {
    const cPath = Path.resolve(Path.join(basePath || process.cwd(), filename));
    return fs.pathExistsSync(cPath) ? cPath : null;
}

// ---------------------------
// path
// ---------------------------

const path = {
    // filename without the extension
    basename(filepath) {
        const ext = Path.extname(filepath);
        return Path.basename(filepath, ext);
    },
    parent(p) {
        p = Path.normalize(p);
        const arr = p.split(/[\\/]+/);
        // remove last item
        arr.splice(-1, 1);
        return arr.join(Path.sep);
    },
    ensureLeadSlash(p, slash) {
        if (slash === undefined) slash = '/';
        return /^[\\/]/.test(p) === false ? `${slash}${p}` : p;
    },
    ensureEndSlash(p, slash) {
        if (slash === undefined) slash = '/';
        return /[\\/]$/.test(p) === false ? `${p}${slash}` : p;
    },
    removeEndSlash(p) {
        return p.replace(/[\\/]$/, '');
    },
    removeLeadSlash(p) {
        return p.replace(/^[\\/]/, '');
    },
    parentOfBase(p, base) {
        p = Path.normalize(path.removeEndSlash(p));
        if (!base) return p;
        base = Path.normalize(path.removeEndSlash(base));
        return p.slice(-base.length) === base
            ? p.slice(0, -base.length)
            : p;
    }
};

// ---------------------------
// fs
// ---------------------------

const _fs = {
    // currently, this method is not used.
    // mergeFiles(fileList) {
    //     const merged = [];
    //     // using Promise.each instead of Promise.map, to ensure order guarantee
    //     // in sequential execution.
    //     return Promise
    //         .each(fileList, filePath => {
    //             return fs.readFile(filePath, 'utf8')
    //                 .then(content => {
    //                     merged.push(content);
    //                 });
    //         })
    //         .then(() => { // (fileList)
    //             return merged.join('\n');
    //         });
    // },
    isEmptyDir(dirPath) {
        return Promise.resolve(fs.readdir(dirPath)) // wrap with Bluebird
            .then(files => !files.length);
    }
};

// ---------------------------
// json
// ---------------------------

const json = {
    read(filePath) {
        return fs.readFile(filePath, 'utf8')
            .then(string => JSON.parse(stripJsonComments(string)));
    },
    readSync(filePath) {
        const string = fs.readFileSync(filePath, 'utf8');
        return JSON.parse(stripJsonComments(string));
    },
    write(object, filePath) {
        const json = JSON.stringify(object, null, '  ');
        return fs.writeFile(filePath, json, 'utf8');
    },
    removeComments(string) {
        return stripJsonComments(string);
    }
};

// ---------------------------
// script / js
// ---------------------------

// https://github.com/mishoo/UglifyJS2
const defaultMinifyOpts = {
    compress: {},
    output: {
        // only output the commets with a @license directive
        comments: /@license/g
    }
};

const js = {
    minify(strScript, options = {}) {
        const opts = _.extend({}, defaultMinifyOpts, options);
        const minified = uglify.minify(strScript, opts);
        if (minified.error instanceof Error) throw minified.error;
        return minified;
        // returns { code, map }
    },
    minifyFile(filePath, options = {}) {
        const opts = _.extend({}, defaultMinifyOpts, options);
        return fs.readFile(filePath, 'utf8')
            .then(strScript => js.minify(strScript, opts)); // returns { code, map }
    },
    /**
     *  Gets the file path for the minified version of the given file.
     *
     *  @param {String} filePath - Path of the original (unminified) file.
     *
     *  @returns {String} - Path of the minified version.
     */
    getMinPath(filePath) {
        const fPath = filePath.toLowerCase();
        if (_.endsWith(fPath, '.min.js')) return fPath;
        return Path.join(Path.dirname(fPath), `${Path.basename(fPath, '.js')}.min.js`);
    },
    /**
     *  Reads the given JS file into memory.
     *
     *  @param {String} filePath
     *         Path of the JS file.
     *  @param {Boolean} [noMinify=false]
     *         Whether not to minify the JS content.
     *  @param {Boolean} [checkMinFile=false]
     *         Whether to first check for an existing minified file (with a
     *         `min.js` suffix).
     *
     *  @returns {Promise} - Promise that returns JS code content.
     */
    readFile(filePath, noMinify = false, checkMinFile = false) {
        if (noMinify) return fs.readFile(filePath, 'utf8');

        if (!checkMinFile) {
            return js.minifyFile(filePath).then(minified => minified.code);
        }
        const minFilePath = js.getMinPath(filePath);
        return fs.pathExists(minFilePath)
            .then(exists => {
                if (exists) return fs.readFile(minFilePath, 'utf8');
                return js.minifyFile(filePath).then(minified => minified.code);
            });
    }
};

// ---------------------------
// Markdown
// ---------------------------

const md = {
    /**
     *  Parses the given markdown content.
     *
     *  @param {String} string - Markdown content.
     *  @param {Object} [options] - Markdown options for `marked`.
     *
     *  @returns {Promise} - Promise that returns the markdown, parsed to HTML.
     */
    parse(string, options = {}) {
        return new Promise((resolve, reject) => {
            marked(string, options, (err, content) => {
                if (err) return reject(err);
                resolve(content);
            });
        });
    }
};

// ---------------------------
// glob
// ---------------------------

const glob = {
    // glob uses forward slash only. (in Windows too)
    // base is optional.
    normalize(base, globPath) {
        const gpSet = utils.isset(globPath);
        if (!gpSet) {
            globPath = base;
            base = '';
        } else {
            base += '/';
        }
        return Path.normalize(base + globPath).replace(/\\+/g, '/');
    },

    isNegated(pattern) {
        return pattern.slice(0, 1) === '!';
    },

    inspect(pattern, options = {}) {
        const negated = glob.isNegated(pattern);
        pattern = negated ? pattern.slice(1) : pattern;
        pattern = glob.normalize(pattern);
        return {
            hasMagic: GLOB.hasMagic(pattern, options),
            negated,
            pattern
        };
    },

    /**
     *  Expands the given glob pattern(s) into file-paths array.
     *  This uses the node-glob library but unlike it, this also accepts an
     *  Array input and negated globs are allowed.
     *  @param {String|Array} patterns - One or more patterns to be expanded.
     *  @param {Object} [options] - Glob options.
     *  @returns {Array} - Expanded list of file paths.
     */
    match(patterns, options = {}) {
        patterns = utils.ensureArray(patterns);

        // normalize ignored globs (glob paths sep should be `/` even in
        // windows.)
        options.ignore = (options.ignore || []).map(item => {
            return glob.normalize(item);
        });

        let files = [];
        const properPatterns = [];

        // We'll pick negated patterns into the ignore list and proper patterns
        // into a separate array. Also, we'll add non-glob patters (normal file
        // paths) to the final output array; bec. glob() will not process
        // non-glob patterns.
        _.each(patterns, p => {
            const g = glob.inspect(p);
            if (g.negated) {
                options.ignore.push(g.pattern);
            } else if (!g.hasMagic) {
                files.push(g.pattern);
            } else {
                properPatterns.push(g.pattern);
            }
        });

        return Promise.each(properPatterns, p => {
            return GLOB(p, options) // eslint-disable-line
                .then(expandedFiles => {
                    files = files.concat(expandedFiles);
                });
        }).then(() => files);
        // .thenReturn(files); // wrong! returns the result of Promise.each
    }
};

// ---------------------------
// less
// ---------------------------

// http://lesscss.org/usage/#programmatic-usage
// http://onedayitwillmake.com/blog/2013/03/compiling-less-from-a-node-js-script/
// http://lesscss.org/#using-less-command-line-usage

const defaultLessOpts = {
    // root .less file name (not the path).
    // filename: 'styles.less',
    // optimization level, higher is better but more volatile - 1 is a
    // good value.
    optimization: 1,
    // minify output css
    compress: true,
    // use YUI compressor?
    yuicompress: true,
    // additional paths to look for imported less files.
    paths: [],
    // less plugins
    plugins: []
};

const less = {
    updateOptions(options = {}, filePath = null) {
        const opts = _.defaults(options, defaultLessOpts);
        // also pass lessCleanCssPlugin if compress enabled.
        if (options.compress) options.plugins.push(lessCleanCssPlugin);
        if (filePath) {
            const parentDir = Path.resolve(Path.dirname(filePath));
            // http://lesscss.org/usage/#programmatic-usage
            opts.filename = Path.basename(filePath); // less file name not the path
            opts.paths = (opts.paths || []).concat([parentDir]);
        }
        return opts;
    },
    compile(strLess, options) {
        // convert promise to Bluebird promise.
        return Promise.resolve(Less.render(strLess, options));
        // resolves { css, map, imports }
    },
    // filePath: root .less file path.
    compileFile(filePath, options) {
        return fs.readFile(filePath, 'utf8')
            .then(strLess => {
                return less.compile(strLess, options);
            });
        // resolves { css, map, imports }
    }
};

// ---------------------------
// utils export
// ---------------------------

utils = {
    isset(value) {
        return value !== undefined && value !== null;
    },
    type(obj) {
        return Object.prototype.toString.call(obj).match(/\s(\w+)/i)[1].toLowerCase();
    },
    // e.g.
    // let symbol = { code: { meta: { type: "MethodDefinition" } } };
    // notate(symbol, "code.meta.type") => "MethodDefinition"
    // See https://github.com/onury/notation for an advanced library.
    notate(obj, notation) {
        if (typeof obj !== 'object') return;
        // console.log('n', notation);
        const props = !Array.isArray(notation)
            ? notation.split('.')
            : notation;
        const prop = props[0];
        if (!prop) return;
        const o = obj[prop];
        if (props.length > 1) {
            props.shift();
            return utils.notate(o, props);
        }
        return o;
    },
    ensureArray(o) {
        if (o === undefined || o === null) return o;
        return Array.isArray(o) ? o : [o];
    },
    round(num, decimals = 0) {
        if (!decimals) return Math.round(num);
        const factor = decimals * 10;
        return Math.round(num * factor) / factor;
    },
    // in KB
    getContentSize(str, gzipped = false) {
        const bytes = gzipped
            ? gzipSize.sync(str)
            : Buffer.byteLength(str, 'utf8');
        return utils.round(bytes / 1024, 1);
    },
    safeRequire(name) {
        try {
            return require(name);
        } catch (err) {
            if (err.code === 'MODULE_NOT_FOUND' && err.message.indexOf(name) !== -1) {
                return undefined;
            }
            throw err;
        }
    },
    removeNewLines(string, replaceWith = '') {
        return (string || '').replace(/[\r\n]+/, replaceWith);
    },
    /**
     *  Gets a safe string ID value from the given string.
     *
     *  @param {String} [str] - String to be converted.
     *
     *  @returns {String}
     */
    idify(str) {
        return str.trim()
            .replace(/[.,;'"`]/g, '')
            .replace(/[^a-z0-9$_]/gi, '-')
            .toLowerCase();
    },
    /**
     *  Used by CLI. Attempts to find a Docma config file with one of the
     *  default names.
     *  @private
     *
     *  @param {String} [basePath=process.cwd()] - Base directory path to search
     *  in.
     *
     *  @returns {String} - `null` if not found.
     */
    _findDocmaConfigFileSync(basePath = null) {
        return getFileSync('docma.json', basePath)
            || getFileSync('.docma.json', basePath)
            || getFileSync('.docma', basePath);
    },
    path,
    fs: _fs,
    js,
    json,
    md,
    glob,
    less
};

module.exports = utils;
<commit_msg>add utils.hasValidName()<commit_after>'use strict';

// core modules
const Path = require('path');

// dep modules
const _ = require('lodash');
const Promise = require('bluebird');
const fs = require('fs-extra');
const marked = require('marked');
const uglify = require('uglify-js');
const stripJsonComments = require('strip-json-comments');
const Less = require('less');
const LessCleanCssPlugin = require('less-plugin-clean-css');
const gzipSize = require('gzip-size');

const GLOB = Promise.promisify(require('glob'));
const lessCleanCssPlugin = new LessCleanCssPlugin({ advanced: true });

const INVALID = {
    // https://docs.microsoft.com/en-us/windows/desktop/FileIO/naming-a-file
    WINDOWS: {
        CHARS: /[<>:"\\|?*]/, // char / is left out (allowed in Docma as path separator)
        NAME: /^(nul|prn|aux|con|lpt[0-9]|com[0-9]|(clock|keybd|screen|idle|config)\$)(\.|$)/i,
        MAX_LEN: 260 - 12
    },
    // https://en.wikipedia.org/wiki/Filename#Reserved_characters_and_words
    // https://blog.josephscott.org/2007/02/12/things-that-shouldnt-be-in-file-names-for-1000-alex/
    UNIX: {
        CHARS: /[\\|?*]/, // char / is left out (allowed in Docma as path separator)
        NAME: /^[.]+$/,
        MAX_LEN: 255
    }
};

let utils = null;

// ---------------------------
// Helpers
// ---------------------------

// used for utils._findDocmaConfigFileSync()
function getFileSync(filename, basePath = null) {
    const cPath = Path.resolve(Path.join(basePath || process.cwd(), filename));
    return fs.pathExistsSync(cPath) ? cPath : null;
}

// ---------------------------
// path
// ---------------------------

const path = {
    // filename without the extension
    basename(filepath) {
        const ext = Path.extname(filepath);
        return Path.basename(filepath, ext);
    },
    parent(p) {
        p = Path.normalize(p);
        const arr = p.split(/[\\/]+/);
        // remove last item
        arr.splice(-1, 1);
        return arr.join(Path.sep);
    },
    ensureLeadSlash(p, slash) {
        if (slash === undefined) slash = '/';
        return /^[\\/]/.test(p) === false ? `${slash}${p}` : p;
    },
    ensureEndSlash(p, slash) {
        if (slash === undefined) slash = '/';
        return /[\\/]$/.test(p) === false ? `${p}${slash}` : p;
    },
    removeEndSlash(p) {
        return p.replace(/[\\/]$/, '');
    },
    removeLeadSlash(p) {
        return p.replace(/^[\\/]/, '');
    },
    parentOfBase(p, base) {
        p = Path.normalize(path.removeEndSlash(p));
        if (!base) return p;
        base = Path.normalize(path.removeEndSlash(base));
        return p.slice(-base.length) === base
            ? p.slice(0, -base.length)
            : p;
    },
    hasValidName(p, windows) {
        const re = windows ? INVALID.WINDOWS : INVALID.UNIX;
        return !re.CHARS.test(p) && !re.NAME.test(p) && p.length <= re.MAX_LEN;
    }
};

// ---------------------------
// fs
// ---------------------------

const _fs = {
    // currently, this method is not used.
    // mergeFiles(fileList) {
    //     const merged = [];
    //     // using Promise.each instead of Promise.map, to ensure order guarantee
    //     // in sequential execution.
    //     return Promise
    //         .each(fileList, filePath => {
    //             return fs.readFile(filePath, 'utf8')
    //                 .then(content => {
    //                     merged.push(content);
    //                 });
    //         })
    //         .then(() => { // (fileList)
    //             return merged.join('\n');
    //         });
    // },
    isEmptyDir(dirPath) {
        return Promise.resolve(fs.readdir(dirPath)) // wrap with Bluebird
            .then(files => !files.length);
    }
};

// ---------------------------
// json
// ---------------------------

const json = {
    read(filePath) {
        return fs.readFile(filePath, 'utf8')
            .then(string => JSON.parse(stripJsonComments(string)));
    },
    readSync(filePath) {
        const string = fs.readFileSync(filePath, 'utf8');
        return JSON.parse(stripJsonComments(string));
    },
    write(object, filePath) {
        const json = JSON.stringify(object, null, '  ');
        return fs.writeFile(filePath, json, 'utf8');
    },
    removeComments(string) {
        return stripJsonComments(string);
    }
};

// ---------------------------
// script / js
// ---------------------------

// https://github.com/mishoo/UglifyJS2
const defaultMinifyOpts = {
    compress: {},
    output: {
        // only output the commets with a @license directive
        comments: /@license/g
    }
};

const js = {
    minify(strScript, options = {}) {
        const opts = _.extend({}, defaultMinifyOpts, options);
        const minified = uglify.minify(strScript, opts);
        if (minified.error instanceof Error) throw minified.error;
        return minified;
        // returns { code, map }
    },
    minifyFile(filePath, options = {}) {
        const opts = _.extend({}, defaultMinifyOpts, options);
        return fs.readFile(filePath, 'utf8')
            .then(strScript => js.minify(strScript, opts)); // returns { code, map }
    },
    /**
     *  Gets the file path for the minified version of the given file.
     *
     *  @param {String} filePath - Path of the original (unminified) file.
     *
     *  @returns {String} - Path of the minified version.
     */
    getMinPath(filePath) {
        const fPath = filePath.toLowerCase();
        if (_.endsWith(fPath, '.min.js')) return fPath;
        return Path.join(Path.dirname(fPath), `${Path.basename(fPath, '.js')}.min.js`);
    },
    /**
     *  Reads the given JS file into memory.
     *
     *  @param {String} filePath
     *         Path of the JS file.
     *  @param {Boolean} [noMinify=false]
     *         Whether not to minify the JS content.
     *  @param {Boolean} [checkMinFile=false]
     *         Whether to first check for an existing minified file (with a
     *         `min.js` suffix).
     *
     *  @returns {Promise} - Promise that returns JS code content.
     */
    readFile(filePath, noMinify = false, checkMinFile = false) {
        if (noMinify) return fs.readFile(filePath, 'utf8');

        if (!checkMinFile) {
            return js.minifyFile(filePath).then(minified => minified.code);
        }
        const minFilePath = js.getMinPath(filePath);
        return fs.pathExists(minFilePath)
            .then(exists => {
                if (exists) return fs.readFile(minFilePath, 'utf8');
                return js.minifyFile(filePath).then(minified => minified.code);
            });
    }
};

// ---------------------------
// Markdown
// ---------------------------

const md = {
    /**
     *  Parses the given markdown content.
     *
     *  @param {String} string - Markdown content.
     *  @param {Object} [options] - Markdown options for `marked`.
     *
     *  @returns {Promise} - Promise that returns the markdown, parsed to HTML.
     */
    parse(string, options = {}) {
        return new Promise((resolve, reject) => {
            marked(string, options, (err, content) => {
                if (err) return reject(err);
                resolve(content);
            });
        });
    }
};

// ---------------------------
// glob
// ---------------------------

const glob = {
    // glob uses forward slash only. (in Windows too)
    // base is optional.
    normalize(base, globPath) {
        const gpSet = utils.isset(globPath);
        if (!gpSet) {
            globPath = base;
            base = '';
        } else {
            base += '/';
        }
        return Path.normalize(base + globPath).replace(/\\+/g, '/');
    },

    isNegated(pattern) {
        return pattern.slice(0, 1) === '!';
    },

    inspect(pattern, options = {}) {
        const negated = glob.isNegated(pattern);
        pattern = negated ? pattern.slice(1) : pattern;
        pattern = glob.normalize(pattern);
        return {
            hasMagic: GLOB.hasMagic(pattern, options),
            negated,
            pattern
        };
    },

    /**
     *  Expands the given glob pattern(s) into file-paths array.
     *  This uses the node-glob library but unlike it, this also accepts an
     *  Array input and negated globs are allowed.
     *  @param {String|Array} patterns - One or more patterns to be expanded.
     *  @param {Object} [options] - Glob options.
     *  @returns {Array} - Expanded list of file paths.
     */
    match(patterns, options = {}) {
        patterns = utils.ensureArray(patterns);

        // normalize ignored globs (glob paths sep should be `/` even in
        // windows.)
        options.ignore = (options.ignore || []).map(item => {
            return glob.normalize(item);
        });

        let files = [];
        const properPatterns = [];

        // We'll pick negated patterns into the ignore list and proper patterns
        // into a separate array. Also, we'll add non-glob patters (normal file
        // paths) to the final output array; bec. glob() will not process
        // non-glob patterns.
        _.each(patterns, p => {
            const g = glob.inspect(p);
            if (g.negated) {
                options.ignore.push(g.pattern);
            } else if (!g.hasMagic) {
                files.push(g.pattern);
            } else {
                properPatterns.push(g.pattern);
            }
        });

        return Promise.each(properPatterns, p => {
            return GLOB(p, options) // eslint-disable-line
                .then(expandedFiles => {
                    files = files.concat(expandedFiles);
                });
        }).then(() => files);
        // .thenReturn(files); // wrong! returns the result of Promise.each
    }
};

// ---------------------------
// less
// ---------------------------

// http://lesscss.org/usage/#programmatic-usage
// http://onedayitwillmake.com/blog/2013/03/compiling-less-from-a-node-js-script/
// http://lesscss.org/#using-less-command-line-usage

const defaultLessOpts = {
    // root .less file name (not the path).
    // filename: 'styles.less',
    // optimization level, higher is better but more volatile - 1 is a
    // good value.
    optimization: 1,
    // minify output css
    compress: true,
    // use YUI compressor?
    yuicompress: true,
    // additional paths to look for imported less files.
    paths: [],
    // less plugins
    plugins: []
};

const less = {
    updateOptions(options = {}, filePath = null) {
        const opts = _.defaults(options, defaultLessOpts);
        // also pass lessCleanCssPlugin if compress enabled.
        if (options.compress) options.plugins.push(lessCleanCssPlugin);
        if (filePath) {
            const parentDir = Path.resolve(Path.dirname(filePath));
            // http://lesscss.org/usage/#programmatic-usage
            opts.filename = Path.basename(filePath); // less file name not the path
            opts.paths = (opts.paths || []).concat([parentDir]);
        }
        return opts;
    },
    compile(strLess, options) {
        // convert promise to Bluebird promise.
        return Promise.resolve(Less.render(strLess, options));
        // resolves { css, map, imports }
    },
    // filePath: root .less file path.
    compileFile(filePath, options) {
        return fs.readFile(filePath, 'utf8')
            .then(strLess => {
                return less.compile(strLess, options);
            });
        // resolves { css, map, imports }
    }
};

// ---------------------------
// utils export
// ---------------------------

utils = {
    isset(value) {
        return value !== undefined && value !== null;
    },
    type(obj) {
        return Object.prototype.toString.call(obj).match(/\s(\w+)/i)[1].toLowerCase();
    },
    // e.g.
    // let symbol = { code: { meta: { type: "MethodDefinition" } } };
    // notate(symbol, "code.meta.type") => "MethodDefinition"
    // See https://github.com/onury/notation for an advanced library.
    notate(obj, notation) {
        if (typeof obj !== 'object') return;
        // console.log('n', notation);
        const props = !Array.isArray(notation)
            ? notation.split('.')
            : notation;
        const prop = props[0];
        if (!prop) return;
        const o = obj[prop];
        if (props.length > 1) {
            props.shift();
            return utils.notate(o, props);
        }
        return o;
    },
    ensureArray(o) {
        if (o === undefined || o === null) return o;
        return Array.isArray(o) ? o : [o];
    },
    round(num, decimals = 0) {
        if (!decimals) return Math.round(num);
        const factor = decimals * 10;
        return Math.round(num * factor) / factor;
    },
    // in KB
    getContentSize(str, gzipped = false) {
        const bytes = gzipped
            ? gzipSize.sync(str)
            : Buffer.byteLength(str, 'utf8');
        return utils.round(bytes / 1024, 1);
    },
    safeRequire(name) {
        try {
            return require(name);
        } catch (err) {
            if (err.code === 'MODULE_NOT_FOUND' && err.message.indexOf(name) !== -1) {
                return undefined;
            }
            throw err;
        }
    },
    removeNewLines(string, replaceWith = '') {
        return (string || '').replace(/[\r\n]+/, replaceWith);
    },
    /**
     *  Gets a safe string ID value from the given string.
     *
     *  @param {String} [str] - String to be converted.
     *
     *  @returns {String}
     */
    idify(str) {
        return str.trim()
            .replace(/[.,;'"`]/g, '')
            .replace(/[^a-z0-9$_]/gi, '-')
            .toLowerCase();
    },
    /**
     *  Used by CLI. Attempts to find a Docma config file with one of the
     *  default names.
     *  @private
     *
     *  @param {String} [basePath=process.cwd()] - Base directory path to search
     *  in.
     *
     *  @returns {String} - `null` if not found.
     */
    _findDocmaConfigFileSync(basePath = null) {
        return getFileSync('docma.json', basePath)
            || getFileSync('.docma.json', basePath)
            || getFileSync('.docma', basePath);
    },
    path,
    fs: _fs,
    js,
    json,
    md,
    glob,
    less
};

module.exports = utils;
<|endoftext|>


<commit_before>'use strict';

const https = require('https');

exports.createReportTable = function(deps) {
  let table = '';

  for (const { name, found, needs, type } of deps) {
    let needs2 = needs;
    if (type === 'github') { needs2 += ' (on github)'; }

    table += `${name}|needs ${needs2}|found ${found}\n`;
  }

  return exports.formatReportTable(table.trim());
};

exports.formatReportTable = function(text) {
  const lines       = text.split('\n');
  let maxLenghtCol1 = 0;
  let maxLenghtCol2 = 0;

  for (const line of lines) {
    const idxSeparator1 = line.indexOf('|');
    if (idxSeparator1 === -1) { continue; }
    maxLenghtCol1 = Math.max(maxLenghtCol1, idxSeparator1);

    const idxSeparator2 = line.indexOf('|', idxSeparator1 + 1);
    if (idxSeparator2 === -1) { continue; }
    maxLenghtCol2 = Math.max(maxLenghtCol2, idxSeparator2 - idxSeparator1 - 1);
  }

  for (let i = 0; i < lines.length; ++i) {
    const parts = lines[i].split('|');
    if (parts.length !== 3) { continue; }

    const filler1 = new Array(maxLenghtCol1 - parts[0].length + 1).join(' ');
    const filler2 = new Array(maxLenghtCol2 - parts[1].length + 1).join(' ');

    lines[i] = parts[0] + filler1 + ' | ' + parts[1] + filler2 + ' | ' + parts[2];
  }

  return lines.join('\n');
};

exports.downloadGithubPackage = function(repo, token, done) {
  const parts = repo.split(/[\/#]/);

  if (parts.length < 2) {
    return done(new Error(`Invalid github repository: "${repo}"`));
  }

  if (parts.length === 2) {
    parts.push('master');
  }

  const path    = parts.join('/');
  const request = {
    hostname : 'raw.githubusercontent.com',
    path     : `/${path}/package.json`,
    headers  : {}
  };

  if (token) {
    request.headers.Authorization = `token ${token}`;
  }

  https.get(request, res => {
    if (res.statusCode !== 200) {
      return done(new Error(`Github returned status ${res.statusCode} for repo "${repo}"`));
    }

    let content = '';

    res.on('data', chunk => {
      content += chunk.toString();
    });

    res.on('end', () => {
      let json;
      try {
        json = JSON.parse(content);
      } catch (err) {
        return done(err);
      }
      return done(null, json);
    });

  }).on('error', done);
};
<commit_msg>Prevent cache of remote package.json files<commit_after>'use strict';

const https = require('https');

exports.createReportTable = function(deps) {
  let table = '';

  for (const { name, found, needs, type } of deps) {
    let needs2 = needs;
    if (type === 'github') { needs2 += ' (on github)'; }

    table += `${name}|needs ${needs2}|found ${found}\n`;
  }

  return exports.formatReportTable(table.trim());
};

exports.formatReportTable = function(text) {
  const lines       = text.split('\n');
  let maxLenghtCol1 = 0;
  let maxLenghtCol2 = 0;

  for (const line of lines) {
    const idxSeparator1 = line.indexOf('|');
    if (idxSeparator1 === -1) { continue; }
    maxLenghtCol1 = Math.max(maxLenghtCol1, idxSeparator1);

    const idxSeparator2 = line.indexOf('|', idxSeparator1 + 1);
    if (idxSeparator2 === -1) { continue; }
    maxLenghtCol2 = Math.max(maxLenghtCol2, idxSeparator2 - idxSeparator1 - 1);
  }

  for (let i = 0; i < lines.length; ++i) {
    const parts = lines[i].split('|');
    if (parts.length !== 3) { continue; }

    const filler1 = new Array(maxLenghtCol1 - parts[0].length + 1).join(' ');
    const filler2 = new Array(maxLenghtCol2 - parts[1].length + 1).join(' ');

    lines[i] = parts[0] + filler1 + ' | ' + parts[1] + filler2 + ' | ' + parts[2];
  }

  return lines.join('\n');
};

exports.downloadGithubPackage = function(repo, token, done) {
  const parts = repo.split(/[\/#]/);

  if (parts.length < 2) {
    return done(new Error(`Invalid github repository: "${repo}"`));
  }

  if (parts.length === 2) {
    parts.push('master');
  }

  const path    = parts.join('/');
  const date    = new Date().getTime();
  const request = {
    hostname : 'raw.githubusercontent.com',
    path     : `/${path}/package.json?nocache=${date}`,
    headers  : {}
  };

  if (token) {
    request.headers.Authorization = `token ${token}`;
  }

  https.get(request, res => {
    if (res.statusCode !== 200) {
      return done(new Error(`Github returned status ${res.statusCode} for repo "${repo}"`));
    }

    let content = '';

    res.on('data', chunk => {
      content += chunk.toString();
    });

    res.on('end', () => {
      let json;
      try {
        json = JSON.parse(content);
      } catch (err) {
        return done(err);
      }
      return done(null, json);
    });

  }).on('error', done);
};
<|endoftext|>