<commit_before>// **N3Writer** writes N3 documents.

// Matches a literal as represented in memory by the N3 library
var N3LiteralMatcher = /^"((?:.|\n|\r)*)"(?:\^\^<(.+)>|@([\-a-z]+))?$/i;

// rdf:type predicate (for 'a' abbreviation)
var RDF_PREFIX = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
    RDF_TYPE   = RDF_PREFIX + 'type';

// Characters in literals that require escaping
var literalEscape = /["\\\t\n\r\b\f]/g;
var literalReplacements = {
  '\\': '\\\\',
  '"' : '\\"',
  '\t': '\\t',
  '\n': '\\n',
  '\r': '\\r',
  '\b': '\\b',
  '\f': '\\f',
};

// ## Constructor
function N3Writer(outputStream, prefixes) {
  if (!(this instanceof N3Writer))
    return new N3Writer(outputStream, prefixes);

  // If the first argument is not a stream
  if (!outputStream || !outputStream.write) {
    // Shift arguments
    prefixes = outputStream;
    // Write to a string that will be given through the end callback
    outputStream = this;
    this._output = '';
    this.write = function (chunk, encoding, callback) {
      this._output += chunk;
      callback && callback();
    };
  }

  this._outputStream = outputStream;
  this._prefixURIs = Object.create(null);
  if (prefixes)
    this.addPrefixes(prefixes);
}

N3Writer.prototype = {
  // ## Private methods

  // ### `_write` writes the arguments to the output stream (last argument is callback)
  _write: function () {
    for (var i = 0, l = arguments.length - 2; i <= l; i++)
      this._outputStream.write(arguments[i], 'utf8', i === l ? arguments[l + 1] : null);
  },

  // ### `_writeEntity` writes a URI or literal to the output stream
  _writeEntity: function (entity, done) {
    var literalMatch = N3LiteralMatcher.exec(entity);
    // write a literal
    if (literalMatch)
      this._writeLiteral(literalMatch[1], literalMatch[2], literalMatch[3], done);
    // write a URI
    else
      this._writeUriOrBlankNode(entity, done);
  },

  // ### `_writeUriOrBlankNode` writes a URI or blank node to the output stream
  _writeUriOrBlankNode: function (uri, done) {
    // Write blank node
    if (/^_:/.test(uri))
      return this._write(uri, done);

    // Write QName if possible
    var prefixMatch = uri.match(/^(.*[#\/])([a-z][\-_a-z0-9]*)$/i), prefix;
    if (prefixMatch && (prefix = this._prefixURIs[prefixMatch[1]]))
      return this._write(prefix, prefixMatch[2], done);

    // Otherwise, just write the URI
    this._write('<', uri, '>', done);
  },

  // ### `_writeLiteral` writes a literal to the output stream
  _writeLiteral: function (value, type, language, done) {
    if (literalEscape.test(value)) {
      value = value.replace(literalEscape, function (match) {
        return literalReplacements[match];
      });
    }

    this._write('"', value, '"', type || language ? null : done);
    if (type) {
      this._write('^^', null);
      this._writeUriOrBlankNode(type, done);
    }
    else if (language) {
      this._write('@', language, done);
    }
  },

  // ### `_writeSubject` writes a subject to the output stream
  _writeSubject: function (subject, done) {
    if (subject[0] === '"')
      throw new Error('A literal as subject is not allowed: ' + subject);
    this._writeUriOrBlankNode(subject, done);
  },

  // ### `_writePredicate` writes a predicate to the output stream
  _writePredicate: function (predicate, done) {
    if (predicate[0] === '"')
      throw new Error('A literal as predicate is not allowed: ' + predicate);
    if (predicate === RDF_TYPE)
      this._write('a', done);
    else
      this._writeUriOrBlankNode(predicate, done);
  },

  // ### `_writeObject` writes an object to the output stream
  _writeObject: function (object, done) {
    this._writeEntity(object, done);
  },

  // ### `addTriple` adds the triple to the output stream
  addTriple: function (subject, predicate, object, done) {
    // If the triple was given as a triple object, shift parameters
    if (!object) {
      done = predicate;
      object = subject.object;
      predicate = subject.predicate;
      subject = subject.subject;
    }
    // Write the triple
    try {
      // Don't repeat the subject if it's the same
      if (this._prevSubject === subject) {
        // Don't repeat the predicate if it's the same
        if (this._prevPredicate === predicate) {
          this._write(', ', null);
        }
        // Same subject, different predicate
        else {
          this._write(';\n    ', null);
          this._writePredicate(predicate);
          this._write(' ', null);

          this._prevPredicate = predicate;
        }
      }
      // Different subject; write the whole triple
      else {
        // End a possible previous triple
        if (this._prevSubject)
          this._write('.\n', null);
        this._writeSubject(subject);
        this._write(' ', null);
        this._writePredicate(predicate);
        this._write(' ', null);

        this._prevSubject = subject;
        this._prevPredicate = predicate;
      }
      // In all cases, write the object
      this._writeObject(object, done);
    }
    catch (error) {
      done && done(error);
    }
  },

  // ### `addTriples` adds the triples to the output stream
  addTriples: function (triples) {
    for (var i = 0; i < triples.length; i++)
      this.addTriple(triples[i]);
  },

  // ### `addPrefix` adds the prefixes to the output stream
  addPrefix: function (prefix, uri, done) {
    if (/[#\/]$/.test(uri)) {
      this._prefixURIs[uri] = prefix + ':';
      this._write('@prefix ', prefix, ': <', uri, '>.\n', done);
    }
  },

  // ### `addPrefixes` adds the prefixes to the output stream
  addPrefixes: function (prefixes, done) {
    for (var prefix in prefixes)
      this.addPrefix(prefix, prefixes[prefix]);
    this._write('\n', done);
  },

  // ### `end` signals the end of the output stream
  end: function (done) {
    // Finish pending triple
    if (this._prevSubject) {
      this._write('.\n', null);
      delete this._prevSubject;
    }

    // If writing to a string instead of an actual stream, send the string
    if (this === this._outputStream)
      return done && done(null, this._output);

    // Try to end the underlying stream, ensuring done is called exactly one time
    var singleDone = done && function () { singleDone = null, done(); };
    // Ending a stream can error
    try { this._outputStream.end(singleDone); }
    // Execute the callback if it hasn't been executed
    catch (error) { singleDone && singleDone(); }
  },
};

// ## Exports

// Export the `N3Writer` class as a whole.
module.exports = N3Writer;
<commit_msg>Eliminate single-use _writeEntity method.<commit_after>// **N3Writer** writes N3 documents.

// Matches a literal as represented in memory by the N3 library
var N3LiteralMatcher = /^"((?:.|\n|\r)*)"(?:\^\^<(.+)>|@([\-a-z]+))?$/i;

// rdf:type predicate (for 'a' abbreviation)
var RDF_PREFIX = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
    RDF_TYPE   = RDF_PREFIX + 'type';

// Characters in literals that require escaping
var literalEscape = /["\\\t\n\r\b\f]/g;
var literalReplacements = {
  '\\': '\\\\',
  '"' : '\\"',
  '\t': '\\t',
  '\n': '\\n',
  '\r': '\\r',
  '\b': '\\b',
  '\f': '\\f',
};

// ## Constructor
function N3Writer(outputStream, prefixes) {
  if (!(this instanceof N3Writer))
    return new N3Writer(outputStream, prefixes);

  // If the first argument is not a stream
  if (!outputStream || !outputStream.write) {
    // Shift arguments
    prefixes = outputStream;
    // Write to a string that will be given through the end callback
    outputStream = this;
    this._output = '';
    this.write = function (chunk, encoding, callback) {
      this._output += chunk;
      callback && callback();
    };
  }

  this._outputStream = outputStream;
  this._prefixURIs = Object.create(null);
  if (prefixes)
    this.addPrefixes(prefixes);
}

N3Writer.prototype = {
  // ## Private methods

  // ### `_write` writes the arguments to the output stream (last argument is callback)
  _write: function () {
    for (var i = 0, l = arguments.length - 2; i <= l; i++)
      this._outputStream.write(arguments[i], 'utf8', i === l ? arguments[l + 1] : null);
  },

  // ### `_writeUriOrBlankNode` writes a URI or blank node to the output stream
  _writeUriOrBlankNode: function (uri, done) {
    // Write blank node
    if (/^_:/.test(uri))
      return this._write(uri, done);

    // Write QName if possible
    var prefixMatch = uri.match(/^(.*[#\/])([a-z][\-_a-z0-9]*)$/i), prefix;
    if (prefixMatch && (prefix = this._prefixURIs[prefixMatch[1]]))
      return this._write(prefix, prefixMatch[2], done);

    // Otherwise, just write the URI
    this._write('<', uri, '>', done);
  },

  // ### `_writeLiteral` writes a literal to the output stream
  _writeLiteral: function (value, type, language, done) {
    if (literalEscape.test(value)) {
      value = value.replace(literalEscape, function (match) {
        return literalReplacements[match];
      });
    }

    this._write('"', value, '"', type || language ? null : done);
    if (type) {
      this._write('^^', null);
      this._writeUriOrBlankNode(type, done);
    }
    else if (language) {
      this._write('@', language, done);
    }
  },

  // ### `_writeSubject` writes a subject to the output stream
  _writeSubject: function (subject, done) {
    if (subject[0] === '"')
      throw new Error('A literal as subject is not allowed: ' + subject);
    this._writeUriOrBlankNode(subject, done);
  },

  // ### `_writePredicate` writes a predicate to the output stream
  _writePredicate: function (predicate, done) {
    if (predicate[0] === '"')
      throw new Error('A literal as predicate is not allowed: ' + predicate);
    if (predicate === RDF_TYPE)
      this._write('a', done);
    else
      this._writeUriOrBlankNode(predicate, done);
  },

  // ### `_writeObject` writes an object to the output stream
  _writeObject: function (object, done) {
    var literalMatch = N3LiteralMatcher.exec(object);
    if (literalMatch !== null)
      this._writeLiteral(literalMatch[1], literalMatch[2], literalMatch[3], done);
    else
      this._writeUriOrBlankNode(object, done);
  },

  // ### `addTriple` adds the triple to the output stream
  addTriple: function (subject, predicate, object, done) {
    // If the triple was given as a triple object, shift parameters
    if (!object) {
      done = predicate;
      object = subject.object;
      predicate = subject.predicate;
      subject = subject.subject;
    }
    // Write the triple
    try {
      // Don't repeat the subject if it's the same
      if (this._prevSubject === subject) {
        // Don't repeat the predicate if it's the same
        if (this._prevPredicate === predicate) {
          this._write(', ', null);
        }
        // Same subject, different predicate
        else {
          this._write(';\n    ', null);
          this._writePredicate(predicate);
          this._write(' ', null);

          this._prevPredicate = predicate;
        }
      }
      // Different subject; write the whole triple
      else {
        // End a possible previous triple
        if (this._prevSubject)
          this._write('.\n', null);
        this._writeSubject(subject);
        this._write(' ', null);
        this._writePredicate(predicate);
        this._write(' ', null);

        this._prevSubject = subject;
        this._prevPredicate = predicate;
      }
      // In all cases, write the object
      this._writeObject(object, done);
    }
    catch (error) {
      done && done(error);
    }
  },

  // ### `addTriples` adds the triples to the output stream
  addTriples: function (triples) {
    for (var i = 0; i < triples.length; i++)
      this.addTriple(triples[i]);
  },

  // ### `addPrefix` adds the prefixes to the output stream
  addPrefix: function (prefix, uri, done) {
    if (/[#\/]$/.test(uri)) {
      this._prefixURIs[uri] = prefix + ':';
      this._write('@prefix ', prefix, ': <', uri, '>.\n', done);
    }
  },

  // ### `addPrefixes` adds the prefixes to the output stream
  addPrefixes: function (prefixes, done) {
    for (var prefix in prefixes)
      this.addPrefix(prefix, prefixes[prefix]);
    this._write('\n', done);
  },

  // ### `end` signals the end of the output stream
  end: function (done) {
    // Finish pending triple
    if (this._prevSubject) {
      this._write('.\n', null);
      delete this._prevSubject;
    }

    // If writing to a string instead of an actual stream, send the string
    if (this === this._outputStream)
      return done && done(null, this._output);

    // Try to end the underlying stream, ensuring done is called exactly one time
    var singleDone = done && function () { singleDone = null, done(); };
    // Ending a stream can error
    try { this._outputStream.end(singleDone); }
    // Execute the callback if it hasn't been executed
    catch (error) { singleDone && singleDone(); }
  },
};

// ## Exports

// Export the `N3Writer` class as a whole.
module.exports = N3Writer;
<|endoftext|>


<commit_before>;(function (window) {

    "use strict";

    /*****
     * Global object stuff, borrowed from Lo-Dash
     *****/
    var freeExports = typeof exports === "object" && exports,
        freeModule = typeof module === "object" && module && module.exports === freeExports,
        freeGlobal = typeof global === "object" && global;

    if (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal) {
        window = freeGlobal;
    }

    /*****
     * Regulate
     *****/

    // Private constructor
    function Regulate() {
        this._output = "";
    }

    Regulate.prototype.toString = function () {
        return this._output;
    };

    Regulate.prototype.toRegExp = function () {
        return new RegExp(this._output);
    };

    Regulate.prototype.start = function () {
        this._output += "^";
        return this;
    };

    Regulate.prototype.end = function () {
        this._output += "$";
        return _this;
    };

    Regulate.prototype.string = function (str) {
        this._output += str;
        return this;
    };

    Regulate.prototype.group = function (content, nonCapturing) {
        this._output += "(";
        if (nonCapturing) {
            this._output += "?:";
        }
        if (content instanceof Regulate) {
            this._output += content.toString();
        } else {
            this._output += content;
        }
        this._output += ")";
        return this;
    };

    Regulate.prototype.oneOf = function (chars, negated) {
        this._output += "[";
        if (negated) {
            this._output += "^";
        }
        this._output += chars + "]";
        return this;
    };

    Regulate.prototype.noneOf = function (chars) {
        return this.oneOf(chars, true);
    };

    Regulate.prototype.repeat = function (min, max) {
        if (min < 0) {
            this._output += "*";
            if (max === false) {
                this._output += "?"; // Non-greedy
            }
        } else if (min === 1 && (max < 0 || max === false)) {
            this._output += "+";
            if (max === false) {
                this._output += "?";
            }
        } else {
            this._output += "{" + min;
            if (max !== undefined) {
                this._output += ",";
            }
            if (max > 0) {
                this._output += max;
            }
            this._output += "}";
        }
        return this;
    };

    Regulate.prototype.optional = function () {
        this._output += "?";
        return this;
    };

    Regulate.prototype.notFollowedBy = function (content) {
        this._output += "(?!";
        this._output += content instanceof Regulate ? content.toString() : content;
        this._output += ")";
        return this;
    };

    Regulate.prototype.or = function () {
        this._output += "|";
    };

    // This is all that's exposed publicly
    function expose() {
        return new Regulate();
    }

    /*****
     * Expose globally, again borrowed from Lo-Dash
     *****/
    if (typeof define === "function" && typeof define.amd === "object" && define.amd) {
        window.Regulate = expose;
        define(function () {
            return expose;
        });
    } else if (freeExports && !freeExports.nodeType) {
        if (freeModule) {
            freeModule.exports = expose;
        } else {
            freeExports.Regulate = expose;
        }
    } else {
        window.Regulate = expose;
    }

}(this));<commit_msg>Fix node export<commit_after>;(function (window) {

    "use strict";

    /*****
     * Global object stuff, borrowed from Lo-Dash
     *****/
    var freeExports = typeof exports === "object" && exports,
        freeModule = typeof module === "object" && module && module.exports === freeExports && module,
        freeGlobal = typeof global === "object" && global;

    if (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal) {
        window = freeGlobal;
    }

    /*****
     * Regulate
     *****/

    // Private constructor
    function Regulate() {
        this._output = "";
    }

    Regulate.prototype.toString = function () {
        return this._output;
    };

    Regulate.prototype.toRegExp = function () {
        return new RegExp(this._output);
    };

    Regulate.prototype.start = function () {
        this._output += "^";
        return this;
    };

    Regulate.prototype.end = function () {
        this._output += "$";
        return _this;
    };

    Regulate.prototype.string = function (str) {
        this._output += str;
        return this;
    };

    Regulate.prototype.group = function (content, nonCapturing) {
        this._output += "(";
        if (nonCapturing) {
            this._output += "?:";
        }
        if (content instanceof Regulate) {
            this._output += content.toString();
        } else {
            this._output += content;
        }
        this._output += ")";
        return this;
    };

    Regulate.prototype.oneOf = function (chars, negated) {
        this._output += "[";
        if (negated) {
            this._output += "^";
        }
        this._output += chars + "]";
        return this;
    };

    Regulate.prototype.noneOf = function (chars) {
        return this.oneOf(chars, true);
    };

    Regulate.prototype.repeat = function (min, max) {
        if (min < 0) {
            this._output += "*";
            if (max === false) {
                this._output += "?"; // Non-greedy
            }
        } else if (min === 1 && (max < 0 || max === false)) {
            this._output += "+";
            if (max === false) {
                this._output += "?";
            }
        } else {
            this._output += "{" + min;
            if (max !== undefined) {
                this._output += ",";
            }
            if (max > 0) {
                this._output += max;
            }
            this._output += "}";
        }
        return this;
    };

    Regulate.prototype.optional = function () {
        this._output += "?";
        return this;
    };

    Regulate.prototype.notFollowedBy = function (content) {
        this._output += "(?!";
        this._output += content instanceof Regulate ? content.toString() : content;
        this._output += ")";
        return this;
    };

    Regulate.prototype.or = function () {
        this._output += "|";
    };

    // This is all that's exposed publicly
    function expose() {
        return new Regulate();
    }

    /*****
     * Expose globally, again borrowed from Lo-Dash
     *****/
    if (typeof define === "function" && typeof define.amd === "object" && define.amd) {
        window.Regulate = expose;
        define(function () {
            return expose;
        });
    } else if (freeExports && !freeExports.nodeType) {
        if (freeModule) {
            freeModule.exports = expose;
        } else {
            freeExports.Regulate = expose;
        }
    } else {
        window.Regulate = expose;
    }

}(this));<|endoftext|>


<commit_before>var _ = require('lodash'),
  path = require('path'),
  glob = require('glob'),
  minimatch = require('minimatch'),
  fs = require('fs');

var SCATTER_FILE_NAME = 'particle.json';
var DEFAULT_EXCLUDE = ['.git', 'node_modules', '.svn'];

function Resolver(options) {
  this.components = {};
  this.componentsOrder = [];
  this.isSorted = false;

  this.log = options.log;
}

/**
 * Transforms an array of globbed paths, in an array of scattered roots
 */
Resolver.prototype._expandPaths = function(globs, basePath) {
  if(_.isString(globs)) {
    globs = [globs];
  }

  var paths = [];
  _.each(globs, function(globbedPath) {
    if(basePath) {
      globbedPath = path.join(basePath, globbedPath);
    }
    Array.prototype.push.apply(paths, glob.sync(globbedPath));
  });
  return paths;
};

/**
 * Add components to the list of roots
 * @param componentsPaths
 * @param basePath [optional]
 */
Resolver.prototype.registerParticles = function(componentsPaths, basePath) {
  var self = this;
  componentsPaths = self._expandPaths(componentsPaths, basePath);

  _.each(componentsPaths, function(componentPath) {
    //check the descriptor
    var descriptorFileName = path.join(componentPath, SCATTER_FILE_NAME);
    if(!fs.existsSync(descriptorFileName)) {
      return;
      //throw new Error("Scatter component descriptor ("+SCATTER_FILE_NAME+") not found in " + componentPath);
    }
    var descriptor = require(descriptorFileName);
    if(!descriptor.subparticles && !descriptor.name) {
      throw new Error("Component `name` or `subparticles` required in " + descriptorFileName);
    }

    if(descriptor.subparticles) {
      self.registerParticles(descriptor.subparticles, componentPath);
    } else {
      self.log('verbose', "Registering component at " + componentPath);
      //use the package directory
      self.components[descriptor.name] = {
        root: componentPath,
        descriptor: descriptor
      };
      self.isSorted = false;
    }
  });
};

Resolver.prototype._sortComponents = function() {
  var self = this;
  //keep dependencies somewhere
  var dependencies = {};
  var noDependencies = [];
  //initialize deps
  _.each(self.components, function(component, name) {
    if(_.isEmpty(component.descriptor.overrides)) {
      noDependencies.push(name);
    } else {
      var deps = {};
      _.each(component.descriptor.overrides, function(dep) {
        deps[dep] = true;
      });
      dependencies[name] = deps;
    }
  });
  noDependencies = _.uniq(noDependencies);

  self.componentsOrder = [];
  while(noDependencies.length !== 0) {
    var currentName = noDependencies.shift();
    self.componentsOrder.push(currentName);
    _.each(dependencies, function(deps, name) {
      if(deps[currentName]) {
        delete deps[currentName];
        if(_.isEmpty(deps)) {
          noDependencies.push(name);
          delete dependencies[name];
        }
      }
    });
  }

  if(!_.isEmpty(dependencies)) {
    self.log('warn', "There is a cicle or missing requirement in the components dependencies " + dependencies);
    //add orphaned components
    _.each(dependencies, function(deps, name) {
      self.componentsOrder.push(name);
    });
  }

  //It's a dependent->dependency relationship, but in our list dependent should come BEFORE the dependency!
  self.componentsOrder.reverse();
  self.isSorted = true;
};

/**
 * Set the node_modules dir so it npm modules can be required with npm! and
 * scan the modules to find scatter components
 *
 * @param nodeModulesDir
 * @param exclude a set of modules to exclude (no glob)
 */
Resolver.prototype.setNodeModulesDir = function(nodeModulesDir, exclude) {
  var self = this;
  this.nodeModulesDir = nodeModulesDir;

  //Look into component.json and extract the scatter exports
  var packages = self._expandPaths(path.join(nodeModulesDir, "*"));
  packages = _.filter(packages, function(root) {
    var name = path.basename(root);
    return !_.contains(exclude, name);
  });
  self.registerParticles(packages);
};

Resolver.prototype.isIgnored = function(rootObj, file) {
  var ignores = rootObj.descriptor.exclude || [];
  ignores = ignores.concat(DEFAULT_EXCLUDE);

  return _.any(ignores, function(ignore) {
    return minimatch(file, ignore);
  });
};

Resolver.prototype.iterateComponents = function(callback) {
  var self = this;
  self.isSorted || self._sortComponents();
  _.any(self.componentsOrder, function(componentPosition) {
    return callback(self.components[componentPosition]);
  });
};

/**
 * Resolves a single module
 *
 * @param name
 * @returns {*}
 */
Resolver.prototype.resolveModule = function(name, overrideComponent) {
  var self = this;
  var mod;

  var overrideFound = false;
  self.iterateComponents(function(component) {
    var filename = name + '.js';
    var fullpath = path.join(component.root, filename);
    if(!self.isIgnored(component, filename) &&
      fs.existsSync(fullpath) &&
      fs.statSync(fullpath).isFile())
    {
      if(!overrideFound && overrideComponent) {
        overrideFound = overrideComponent.descriptor.name === component.descriptor.name;
      } else {
        mod = {
          rawModule: require(fullpath),
          component: component
        };
        return true;
      }
    }
  });

  return mod;
};

/**
 * require a module from node_modules
 *
 * @param name
 * @returns The module
 */
Resolver.prototype.requireNpm = function(name) {
  var self = this;
  var file = path.join(self.nodeModulesDir, name + '.js');
  if(fs.existsSync(file)) {
    return require(file);
  }
  file = path.join(self.nodeModulesDir, name, 'index.js');
  if(fs.existsSync(file)) {
    return require(file);
  }
  var packagejson = path.join(self.nodeModulesDir, name, 'package.json');
  if(fs.existsSync(packagejson)) {
    var fileName = path.basename(require(packagejson).main, '.js');
    file = path.join(self.nodeModulesDir, name, fileName+'.js');
    if(fs.existsSync(file)) {
      return require(file);
    }
  }

  throw new Error("Cannot find npm module '" + name + "'");
};

Resolver.prototype.resolveAllInComponent = function(component, subpath) {
  var self = this;
  subpath = subpath || "";
  var modules = {};
  var fullPath = path.join(component.root, subpath);

  if(!self.isIgnored(component, subpath)) {
    if(fs.existsSync(fullPath)) {
      var stat = fs.statSync(fullPath);
      if(stat.isFile()) {
        if(path.extname(fullPath) === ".js") {
          var modName = subpath.substring(0, subpath.lastIndexOf('.js'));
          modules[modName] = {
            rawModule: require(fullPath),
            component: component
          };
        }
      } else if(stat.isDirectory()) {
        var files = fs.readdirSync(fullPath);
        _.each(files, function(file) {
          _.defaults(modules, self.resolveAllInComponent(component, path.join(subpath, file)));
        });
      }
    }
  }

  return modules;
};

/**
 * Resolves all modules in a namespace
 *
 * @param scope
 * @returns {*}
 */
Resolver.prototype.resolveAll = function(scope) {
  var self = this;
  scope = scope || '';
  var modules = {};

  //get the project's files
  self.iterateComponents(function(component) {
    _.defaults(modules, self.resolveAllInComponent(component, scope));
  });
  return modules;
};

module.exports = Resolver;
<commit_msg>Fix requiring npm modules from npm! when main is specified as subdir<commit_after>var _ = require('lodash'),
  path = require('path'),
  glob = require('glob'),
  minimatch = require('minimatch'),
  fs = require('fs');

var SCATTER_FILE_NAME = 'particle.json';
var DEFAULT_EXCLUDE = ['.git', 'node_modules', '.svn'];

function Resolver(options) {
  this.components = {};
  this.componentsOrder = [];
  this.isSorted = false;

  this.log = options.log;
}

/**
 * Transforms an array of globbed paths, in an array of scattered roots
 */
Resolver.prototype._expandPaths = function(globs, basePath) {
  if(_.isString(globs)) {
    globs = [globs];
  }

  var paths = [];
  _.each(globs, function(globbedPath) {
    if(basePath) {
      globbedPath = path.join(basePath, globbedPath);
    }
    Array.prototype.push.apply(paths, glob.sync(globbedPath));
  });
  return paths;
};

/**
 * Add components to the list of roots
 * @param componentsPaths
 * @param basePath [optional]
 */
Resolver.prototype.registerParticles = function(componentsPaths, basePath) {
  var self = this;
  componentsPaths = self._expandPaths(componentsPaths, basePath);

  _.each(componentsPaths, function(componentPath) {
    //check the descriptor
    var descriptorFileName = path.join(componentPath, SCATTER_FILE_NAME);
    if(!fs.existsSync(descriptorFileName)) {
      return;
      //throw new Error("Scatter component descriptor ("+SCATTER_FILE_NAME+") not found in " + componentPath);
    }
    var descriptor = require(descriptorFileName);
    if(!descriptor.subparticles && !descriptor.name) {
      throw new Error("Component `name` or `subparticles` required in " + descriptorFileName);
    }

    if(descriptor.subparticles) {
      self.registerParticles(descriptor.subparticles, componentPath);
    } else {
      self.log('verbose', "Registering component at " + componentPath);
      //use the package directory
      self.components[descriptor.name] = {
        root: componentPath,
        descriptor: descriptor
      };
      self.isSorted = false;
    }
  });
};

Resolver.prototype._sortComponents = function() {
  var self = this;
  //keep dependencies somewhere
  var dependencies = {};
  var noDependencies = [];
  //initialize deps
  _.each(self.components, function(component, name) {
    if(_.isEmpty(component.descriptor.overrides)) {
      noDependencies.push(name);
    } else {
      var deps = {};
      _.each(component.descriptor.overrides, function(dep) {
        deps[dep] = true;
      });
      dependencies[name] = deps;
    }
  });
  noDependencies = _.uniq(noDependencies);

  self.componentsOrder = [];
  while(noDependencies.length !== 0) {
    var currentName = noDependencies.shift();
    self.componentsOrder.push(currentName);
    _.each(dependencies, function(deps, name) {
      if(deps[currentName]) {
        delete deps[currentName];
        if(_.isEmpty(deps)) {
          noDependencies.push(name);
          delete dependencies[name];
        }
      }
    });
  }

  if(!_.isEmpty(dependencies)) {
    self.log('warn', "There is a cicle or missing requirement in the components dependencies " + dependencies);
    //add orphaned components
    _.each(dependencies, function(deps, name) {
      self.componentsOrder.push(name);
    });
  }

  //It's a dependent->dependency relationship, but in our list dependent should come BEFORE the dependency!
  self.componentsOrder.reverse();
  self.isSorted = true;
};

/**
 * Set the node_modules dir so it npm modules can be required with npm! and
 * scan the modules to find scatter components
 *
 * @param nodeModulesDir
 * @param exclude a set of modules to exclude (no glob)
 */
Resolver.prototype.setNodeModulesDir = function(nodeModulesDir, exclude) {
  var self = this;
  this.nodeModulesDir = nodeModulesDir;

  //Look into component.json and extract the scatter exports
  var packages = self._expandPaths(path.join(nodeModulesDir, "*"));
  packages = _.filter(packages, function(root) {
    var name = path.basename(root);
    return !_.contains(exclude, name);
  });
  self.registerParticles(packages);
};

Resolver.prototype.isIgnored = function(rootObj, file) {
  var ignores = rootObj.descriptor.exclude || [];
  ignores = ignores.concat(DEFAULT_EXCLUDE);

  return _.any(ignores, function(ignore) {
    return minimatch(file, ignore);
  });
};

Resolver.prototype.iterateComponents = function(callback) {
  var self = this;
  self.isSorted || self._sortComponents();
  _.any(self.componentsOrder, function(componentPosition) {
    return callback(self.components[componentPosition]);
  });
};

/**
 * Resolves a single module
 *
 * @param name
 * @returns {*}
 */
Resolver.prototype.resolveModule = function(name, overrideComponent) {
  var self = this;
  var mod;

  var overrideFound = false;
  self.iterateComponents(function(component) {
    var filename = name + '.js';
    var fullpath = path.join(component.root, filename);
    if(!self.isIgnored(component, filename) &&
      fs.existsSync(fullpath) &&
      fs.statSync(fullpath).isFile())
    {
      if(!overrideFound && overrideComponent) {
        overrideFound = overrideComponent.descriptor.name === component.descriptor.name;
      } else {
        mod = {
          rawModule: require(fullpath),
          component: component
        };
        return true;
      }
    }
  });

  return mod;
};

/**
 * require a module from node_modules
 *
 * @param name
 * @returns The module
 */
Resolver.prototype.requireNpm = function(name) {
  var self = this;
  var file = path.join(self.nodeModulesDir, name + '.js');
  if(fs.existsSync(file)) {
    return require(file);
  }
  file = path.join(self.nodeModulesDir, name, 'index.js');
  if(fs.existsSync(file)) {
    return require(file);
  }
  var packagejson = path.join(self.nodeModulesDir, name, 'package.json');
  if(fs.existsSync(packagejson)) {
    var main = require(packagejson).main;
    var mainName = path.join(path.dirname(main), path.basename(main, '.js'));
    file = path.join(self.nodeModulesDir, name, mainName+'.js');
    if(fs.existsSync(file)) {
      return require(file);
    }

    file = path.join(self.nodeModulesDir, name, mainName, 'index.js');
    if(fs.existsSync(file)) {
      return require(file);
    }
  }

  throw new Error("Cannot find npm module '" + name + "'");
};

Resolver.prototype.resolveAllInComponent = function(component, subpath) {
  var self = this;
  subpath = subpath || "";
  var modules = {};
  var fullPath = path.join(component.root, subpath);

  if(!self.isIgnored(component, subpath)) {
    if(fs.existsSync(fullPath)) {
      var stat = fs.statSync(fullPath);
      if(stat.isFile()) {
        if(path.extname(fullPath) === ".js") {
          var modName = subpath.substring(0, subpath.lastIndexOf('.js'));
          modules[modName] = {
            rawModule: require(fullPath),
            component: component
          };
        }
      } else if(stat.isDirectory()) {
        var files = fs.readdirSync(fullPath);
        _.each(files, function(file) {
          _.defaults(modules, self.resolveAllInComponent(component, path.join(subpath, file)));
        });
      }
    }
  }

  return modules;
};

/**
 * Resolves all modules in a namespace
 *
 * @param scope
 * @returns {*}
 */
Resolver.prototype.resolveAll = function(scope) {
  var self = this;
  scope = scope || '';
  var modules = {};

  //get the project's files
  self.iterateComponents(function(component) {
    _.defaults(modules, self.resolveAllInComponent(component, scope));
  });
  return modules;
};

module.exports = Resolver;
<|endoftext|>


<commit_before>
var net = require('net');

var EventEmitter = process.EventEmitter;

exports.createProcessor = function(name, kind) {
	return new Processor(name, kind);
}

function Processor(name, kind)
{
	this.name = name;
	this.kind = kind;
	this.processors = [];
}

Processor.prototype.__proto__ = EventEmitter.prototype;

Processor.prototype.addProcessor = function (processor)
{
	this.processors.push(processor);
	processor.parent = this;
}

Processor.prototype.createProcessor = function (name, kind)
{
	var processor = new Processor(name, kind);
	this.addProcessor(processor);
	return processor;
}

Processor.prototype.post = function (message)
{
	if (this.name != null && this.kind != null && message[this.kind] == null)
		message[this.kind] = this.name;

	if (this.parent != null) {
		this.parent.post(message);
		return;
	}

	this.process(message);
}

Processor.prototype.accepts = function (message)
{
	if (this.name != null && this.kind != null) {
		if (message[this.kind] != this.name)
			return false;
	}

	if (this.processors == null || this.processors.length == 0)
		return true;

	for (var processor in this.processors)
		if (this.processors[processor].accepts(message))
			return true;

	return false;
}

Processor.prototype.process = function (message)
{
	if (this.processors == null || this.processors.length == 0) {
		this.emit(message.action, message);
		return;
	}

	for (var processor in this.processors)
		if (this.processors[processor].accepts(message))
		{
			this.processors[processor].process(message);
		}
}

Processor.prototype.getDescriptor = function ()
{
	var descriptor = { };

	if (this.name != null && this.kind != null)
		descriptor[this.kind] = this.name;

	if (this.processors == null || this.processors.length == 0)
		return descriptor;

	descriptor.processors = [];

	for (var processor in this.processors)
		descriptor.processors.push(this.processors[processor].getDescriptor());

	return descriptor;
}

exports.createHost = function() {
	return new Processor();
}

exports.createLocalHost = function () {
	return new LocalHost();
}

function RemoteHost(socket, localhost) {
	this.socket = socket;
	this.localhost = localhost;
}

RemoteHost.prototype.process = function (message) {
	this.socket.post('ajfmessage', message);
};

RemoteHost.prototype.accepts = function (message) {
	if (this.descriptor == null)
		return false;

	for (var processor in this.descriptor.processors) {
		if (accepts(this.descriptor.processors[processor], message))
			return true;
	}

	return false;

	function accepts(descriptor, message) {
		for (var name in descriptor) {
			if (name == 'processors')
				continue;

			if (message[name] == descriptor[name]) {
				if (descriptor.processors == null)
					return true;

				for (var processor in descriptor.processors) {
					if (accepts(descriptor.processors[processor], message))
						return true;
				}
			}
		}

		return false;
	}
};

function LocalHost() {
	Processor.prototype.constructor.call(this);

	var host = this;
	this.remotes = [];

	this.connect = function (channel, isserver) {
		console.log('New Connection');

		if (isserver) {
			channel.remote = new RemoteHost(channel, host);
			addRemote(channel.remote);
			channel.post('ajfconnect', host.getDescriptor());
		}

		channel.on('ajfconnect', function(message) {
			if (channel.remote == null) {
				channel.remote = new RemoteHost(channel, host);
				addRemote(channel.remote);
				channel.post('ajfconnect', host.getDescriptor());
			}

			channel.remote.descriptor = message;
		});

		channel.on('ajfmessage', function(message) {
			host.process(message);
		});

		channel.on('disconnect', function () {
			if (channel.remote != null) {
				removeRemote(channel.remote);
				console.log('Remote Host Disconnected');
			}
		});
	};

	function addRemote(remote) {
		host.remotes.push(remote);
	}

	function removeRemote(remote) {
		for (var i = 0; i < host.remotes.length; i++) {
			if (host.remotes[i] == remote) {
				host.remotes.splice(i, 1);
				return;
			}
		}
	}
}

LocalHost.prototype = new Processor();
LocalHost.prototype.constructor = LocalHost;

LocalHost.prototype.listen = function(port, host) {
	var localhost = this;

	var server = net.createServer(function (socket) {
		localhost.connect(new Channel(socket));
	});

	server.listen(port, host);
};

LocalHost.prototype.post = function (message) {
	var hosts = [ this ];

	for (var remote in this.remotes) {
		if (this.remotes[remote].accepts(message)) {
			hosts.push(this.remotes[remote]);
		}
	}

	var n = Math.floor(Math.random() * hosts.length);

	hosts[n].process(message);
};

function Channel(socket) {
	var events = {};
	this.socket = socket;

	var mythis = this;

	socket.on('data', function(data) {
		var text = data.toString();
		var lines = text.split("\n");

		for (var i in lines) {
			var line = lines[i];

			if (line == null || line.length <= 1)
				continue;

			console.log('Received', line);
			try {
				var message = JSON.parse(line);
			}
			catch(err) {
				console.log("Error in parse", err);
				return;
			}
			mythis.process(message);
		}
	});

	socket.on('close', function() { mythis.process({name: 'disconnect'}); });
	socket.on('error', function() { mythis.process({name: 'disconnect'}); });

	this.on = function (name, fn) {
		events[name] = fn;
	};

	this.process = function (message) {
		if (message.name == null)
			return;

		if (events[message.name] == null)
			return;

		events[message.name](message.message);
	}

	this.post = function (name, message) {
		var msg = { name: name, message: message };
		var text = JSON.stringify(msg);
		console.log("Sending", text);
		this.socket.write(text+"\n");
	};
}

exports.Channel = Channel;
<commit_msg>Channel inherits from EventEmitter<commit_after>
var net = require('net');

var EventEmitter = process.EventEmitter;

exports.createProcessor = function(name, kind) {
	return new Processor(name, kind);
}

function Processor(name, kind)
{
	this.name = name;
	this.kind = kind;
	this.processors = [];
}

Processor.prototype.__proto__ = EventEmitter.prototype;

Processor.prototype.addProcessor = function (processor)
{
	this.processors.push(processor);
	processor.parent = this;
}

Processor.prototype.createProcessor = function (name, kind)
{
	var processor = new Processor(name, kind);
	this.addProcessor(processor);
	return processor;
}

Processor.prototype.post = function (message)
{
	if (this.name != null && this.kind != null && message[this.kind] == null)
		message[this.kind] = this.name;

	if (this.parent != null) {
		this.parent.post(message);
		return;
	}

	this.process(message);
}

Processor.prototype.accepts = function (message)
{
	if (this.name != null && this.kind != null) {
		if (message[this.kind] != this.name)
			return false;
	}

	if (this.processors == null || this.processors.length == 0)
		return true;

	for (var processor in this.processors)
		if (this.processors[processor].accepts(message))
			return true;

	return false;
}

Processor.prototype.process = function (message)
{
	if (this.processors == null || this.processors.length == 0) {
		this.emit(message.action, message);
		return;
	}

	for (var processor in this.processors)
		if (this.processors[processor].accepts(message))
		{
			this.processors[processor].process(message);
		}
}

Processor.prototype.getDescriptor = function ()
{
	var descriptor = { };

	if (this.name != null && this.kind != null)
		descriptor[this.kind] = this.name;

	if (this.processors == null || this.processors.length == 0)
		return descriptor;

	descriptor.processors = [];

	for (var processor in this.processors)
		descriptor.processors.push(this.processors[processor].getDescriptor());

	return descriptor;
}

exports.createHost = function() {
	return new Processor();
}

exports.createLocalHost = function () {
	return new LocalHost();
}

function RemoteHost(socket, localhost) {
	this.socket = socket;
	this.localhost = localhost;
}

RemoteHost.prototype.process = function (message) {
	this.socket.post('ajfmessage', message);
};

RemoteHost.prototype.accepts = function (message) {
	if (this.descriptor == null)
		return false;

	for (var processor in this.descriptor.processors) {
		if (accepts(this.descriptor.processors[processor], message))
			return true;
	}

	return false;

	function accepts(descriptor, message) {
		for (var name in descriptor) {
			if (name == 'processors')
				continue;

			if (message[name] == descriptor[name]) {
				if (descriptor.processors == null)
					return true;

				for (var processor in descriptor.processors) {
					if (accepts(descriptor.processors[processor], message))
						return true;
				}
			}
		}

		return false;
	}
};

function LocalHost() {
	Processor.prototype.constructor.call(this);

	var host = this;
	this.remotes = [];

	this.connect = function (channel, isserver) {
		console.log('New Connection');

		if (isserver) {
			channel.remote = new RemoteHost(channel, host);
			addRemote(channel.remote);
			channel.post('ajfconnect', host.getDescriptor());
		}

		channel.on('ajfconnect', function(message) {
			if (channel.remote == null) {
				channel.remote = new RemoteHost(channel, host);
				addRemote(channel.remote);
				channel.post('ajfconnect', host.getDescriptor());
			}

			channel.remote.descriptor = message;
		});

		channel.on('ajfmessage', function(message) {
			host.process(message);
		});

		channel.on('disconnect', function () {
			if (channel.remote != null) {
				removeRemote(channel.remote);
				console.log('Remote Host Disconnected');
			}
		});
	};

	function addRemote(remote) {
		host.remotes.push(remote);
	}

	function removeRemote(remote) {
		for (var i = 0; i < host.remotes.length; i++) {
			if (host.remotes[i] == remote) {
				host.remotes.splice(i, 1);
				return;
			}
		}
	}
}

LocalHost.prototype = new Processor();
LocalHost.prototype.constructor = LocalHost;

LocalHost.prototype.listen = function(port, host) {
	var localhost = this;

	var server = net.createServer(function (socket) {
		localhost.connect(new Channel(socket));
	});

	server.listen(port, host);
};

LocalHost.prototype.post = function (message) {
	var hosts = [ this ];

	for (var remote in this.remotes) {
		if (this.remotes[remote].accepts(message)) {
			hosts.push(this.remotes[remote]);
		}
	}

	var n = Math.floor(Math.random() * hosts.length);

	hosts[n].process(message);
};

function Channel(socket) {
	var events = {};
	this.socket = socket;

	var mythis = this;

	socket.on('data', function(data) {
		var text = data.toString();
		var lines = text.split("\n");

		for (var i in lines) {
			var line = lines[i];

			if (line == null || line.length <= 1)
				continue;

			console.log('Received', line);
			try {
				var message = JSON.parse(line);
			}
			catch(err) {
				console.log("Error in parse", err);
				return;
			}
			mythis.process(message);
		}
	});

	socket.on('close', function() { mythis.process({name: 'disconnect'}); });
	socket.on('error', function() { mythis.process({name: 'disconnect'}); });

	this.process = function (message) {
		if (message.name == null)
			return;

		this.emit(message.name, message.message);
	};

	this.post = function (name, message) {
		var msg = { name: name, message: message };
		var text = JSON.stringify(msg);
		console.log("Sending", text);
		this.socket.write(text+"\n");
	};
}

Channel.prototype.__proto__ = EventEmitter.prototype;

exports.Channel = Channel;
<|endoftext|>


<commit_before>// Meiosis

import flyd from "flyd";
import m from "mithril";
import * as R from "ramda";

const meiosis = (initialModel, receives) => {
  const propose = flyd.stream();
  const receive = (model, proposal) => receives.reduce((model, rcv) => rcv(model, proposal), model);
  const model = flyd.scan(receive, initialModel, propose);

  return {
    propose,
    model
  };
};

// Util

const pipeIn = function() {
  return R.pipe.apply(R, Array.prototype.slice.call(arguments, 1))(arguments[0]);
};

// Credit: source: https://github.com/jayrbolton/flyd-zip
const zip = sources => {
  var withIdxs = R.addIndex(R.map)(function(s, i) {
    return flyd.map(function(v) {
      return [v, i];
    }, s);
  }, sources);

  var buffer = [];

  return flyd.combine(function() {
    var changes = R.last(arguments);

    R.map(R.apply(function(val, idx) {
      buffer[idx] = val;
    }), R.map(R.call, changes));

    if (R.filter(function(n) {
      return n !== undefined;
    }, buffer).length === sources.length) {
      var _self = arguments[arguments.length - 2];
      _self(buffer);
      buffer = [];
    }
  }, withIdxs);
};

// App

const events = propose => ({
  onIncrease: _evt => propose({ add:  1 }),
  onDecrease: _evt => propose({ add: -1 })
});

const createView = events => model => m("div",
  m("span", "Counter: " + model.counter + " " + (model.even ? "Even" : "Odd")),
  m("button", { onclick: events.onIncrease }, "Increase"),
  m("button", { onclick: events.onDecrease }, "Decrease"),
  m("div", JSON.stringify(model)));

let initialModel = { counter: 0 };

const receive1 = (model, proposal) => {
  if (proposal.add > 0) {
    model.counter += proposal.add;
  }
  return model;
};

const receive2 = (model, proposal) => {
  if (proposal.add < 0) {
    model.counter += proposal.add;
  }
  return model;
};

const { propose, model } = meiosis(initialModel, [receive1, receive2]);

const view = pipeIn(propose, events, createView);

const state = model => Object.assign({}, model, { even: model.counter % 2 === 0 });

const nextAction = (model, _proposal) => {
  if (model.counter % 10 === 0) {
    propose({ add: 2 });
  }
};

flyd.on(pair => nextAction(pair[0], pair[1]), zip([model, propose]));

const element = document.getElementById("app");
flyd.on(model => m.render(element, view(model)), flyd.map(state, model));

<commit_msg>More experimentation.<commit_after>// Meiosis

import flyd from "flyd";
import m from "mithril";
import * as R from "ramda";

const meiosis = (initialModel) => {
  const components = [];
  const propose = flyd.stream();

  const receive = (model, proposal) => components
    .map(R.prop("receive"))
    .reduce((model, rcv) => rcv(model, proposal), model);

  const model = flyd.scan(receive, initialModel, propose);

  return {
    components,
    model,
    propose
  };
};

// Util

const pipeIn = function() {
  return R.pipe.apply(R, Array.prototype.slice.call(arguments, 1))(arguments[0]);
};

// Credit: source: https://github.com/jayrbolton/flyd-zip
const zip = sources => {
  var withIdxs = R.addIndex(R.map)(function(s, i) {
    return flyd.map(function(v) {
      return [v, i];
    }, s);
  }, sources);

  var buffer = [];

  return flyd.combine(function() {
    var changes = R.last(arguments);

    R.map(R.apply(function(val, idx) {
      buffer[idx] = val;
    }), R.map(R.call, changes));

    if (R.filter(function(n) {
      return n !== undefined;
    }, buffer).length === sources.length) {
      var _self = arguments[arguments.length - 2];
      _self(buffer);
      buffer = [];
    }
  }, withIdxs);
};

// Counter

const counterComponent = (propose, id) => {
  id = id || "counter_" + String(new Date().getTime());

  const events = propose => ({
    onIncrease: _evt => propose({ counterId: id, add:  1 }),
    onDecrease: _evt => propose({ counterId: id, add: -1 })
  });

  const createView = events => model => m("div",
    m("span", "Counter: " + model.counter + " " + (model.even ? "Even" : "Odd")),
    m("button", { onclick: events.onIncrease }, "Increase"),
    m("button", { onclick: events.onDecrease }, "Decrease"));

  const initialModel = { counter: 0 };

  const receive = (model, proposal) => {
    if (proposal.counterId === id && proposal.add) {
      model.counter += proposal.add;
    }
    return model;
  };

  const view = pipeIn(propose, events, createView);

  const state = model => Object.assign({}, model, { even: model.counter % 2 === 0 });

  return { initialModel, receive, view, state };
};

const initialModel = { counter: 0, counterIds: [], countersById: {} };
const { propose, model, components } = meiosis(initialModel);
const counter = counterComponent(propose);
components.push(counter);

const nextAction = (model, _proposal) => {
  if (model.counter > 0 && model.counter % 10 === 0) {
    propose({ add: 2 });
  }
};

flyd.on(pair => nextAction(pair[0], pair[1]), zip([model, propose]));

const events = propose => ({
  onAddCounter: _evt => propose({ addCounter: true }),
  onRemoveCounter: _evt => propose({ removeCounter: -1 })
});

const createView = events => model => m("div",
  components.filter(component => component.view).map(component => component.view(model)),
  m("button", { onclick: events.onAddCounter }, "Add Counter"),
  m("div", JSON.stringify(model)));

const view = pipeIn(propose, events, createView);

const counterContainer = {
  receive: (model, proposal) => {
    if (proposal.addCounter) {
      const id = "counter_" + String(new Date().getTime());
      const counter = counterComponent(propose, id);
      model[id] = counter.initialModel;
      components.push(counter);
    }
    return model;
  }
};
components.push(counterContainer);

const element = document.getElementById("app");
flyd.on(model => m.render(element, view(model)), flyd.map(counter.state, model));

<|endoftext|>


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

var walk = require('acorn/dist/walk');

var FastSet = require("collections/fast-set");

var plugin = require('./tern-def-api.js');
var scope = require('./define-path-info.js');
var util = require('./util.js');
var logger = require('./logger.js');

require('tern/plugin/doc_comment');
require('tern/plugin/commonjs');
require('tern/plugin/modules');
require('tern/plugin/node');
require('tern/plugin/node_resolve');

//require('tern/plugin/requirejs');

var localDir = process.cwd();
var ternServer = null;

var localFiles = null;

var allIdents = 0;
var undefinedIdents = 0;

var out = {
    Defs: new FastSet([], function(a, b) {
        return a.Path == b.Path;
    }, function(o) {
        return o.Path;
    }),
    Refs: new FastSet([], function(a, b) {
        return a.DefPath == b.DefPath &&
               a.DefUnitType == b.DefUnitType &&
               a.DefRepo == b.DefRepo &&
               a.Def == b.Def &&
               a.File == b.File &&
               a.Start == b.Start &&
               a.End == b.End;
    }, function(o) {
        return [o.DefPath, o.DefUnitType, o.DefRepo, o.Def, o.File, o.Start, o.End].join("|");
    }),
    Docs: new FastSet([], function(a, b) {
        return a.Path == b.Path;
    }, function(o) {
        return o.Path;
    })
}

initTernServer(process.argv.slice(2));
out.Defs = out.Defs.toArray();
out.Refs = out.Refs.toArray();
out.Docs = out.Docs.toArray();
console.log(JSON.stringify(out, null, 2));

function initTernServer(files) {
    localFiles = files;
    var defsPath = path.join(__dirname, "../node_modules/tern/defs/ecma5.json");
    var defs = JSON.parse(fs.readFileSync(defsPath, "utf8"));
    var browserDefsPath = path.join(__dirname, "../node_modules/tern/defs/browser.json");
    var browserDefs = JSON.parse(fs.readFileSync(browserDefsPath, "utf8"));
    var ternOptions = {
        dependencyBudget: 500000,
        projectDir: localDir,
        defs: [defs, browserDefs],
        async: false,
        getFile: function(file) {
            return fs.readFileSync(path.resolve(localDir, file), "utf8");
        },
        plugins: {
            node: true,
            requirejs: true,
            modules: true,
            es_modules: true,
            commonjs: true,
            doc_comment: true
        }
    };

    ternServer = new tern.Server(ternOptions);

    files.forEach(function(file) {
        ternServer.addFile(file);
    });

    ternServer.flush(function(err) {
        if (err) throw err;
        scope.initLocalFilesScopePaths(ternServer, localFiles);
        analyseAll();
    });

    logger.debug("All ids = ", allIdents, " Undefined ids = ", undefinedIdents);
    if (allIdents != 0) {
        logger.debug("percentage of undefined ids = ", (undefinedIdents / allIdents) * 100, "%");
    }

    return out;
}

function getQueryInfo(file, offset, type, start) {
    var query = {
        type: type,
        start: start,
        end: offset,
        file: file,
        docFormat: "full"
    }

    var res = null;
    ternServer.request({
        query: query,
        offset: offset
    }, function(error, data) {
        if (error) {
            logger.error("Error returned from Tern 'definition' request: " + error);
            return;
        }
        res = data;
    });
    return res;
}

function getType(file, offset, start) {
    return getQueryInfo(file, offset, "type", start);
}

function getDefinition(file, offset, start) {
    var query = {
        type: "definition",
        start: start,
        end: offset,
        file: file.name,
        docFormat: "full"
    }
    return plugin.findDef(ternServer, query, file);
}

function getCompletions(file, offset, start) {
    return getQueryInfo(file, offset, "completions", start);
}

function getDocumentation(file, offset, start) {
    return getQueryInfo(file, offset, "documentation", start);
}

function formPathFromId(id) {
    return util.formPath(id.sourceFile.name, id.start, id.end);
}

function formPathFromData(data, externalRepo) {
    if (data === null) {
        return null;
    }
    if (externalRepo === null) {
        if (data.origin === undefined || data.start === undefined || data.end === undefined) {
            return null;
        } else {
            return util.formPath(data.origin, data.start, data.end);
        }
    } else {
        return util.formPath(externalRepo.filePath, data.start, data.end);
    }
}

function formPathForModule(externalRepo) {
    var res = externalRepo.filePath.split("/");
    res[0] = "module";
    return res.join("/");
}

// TODO refactor this method, bad approach for finding package.json
function getExternalRepoInfo(data) {
    if (!data || !data.origin) {
        return null;
    }

    if (["node", "commonjs", "ecma5"].indexOf(data.origin) >= 0) {
        return null;
    }

    var filePath = util.normalizePath(data.origin);

    if (localFiles.indexOf(filePath) > -1) {
        return null;
    }

    // node_modules/... or .../node_modules/...
    var pos = filePath.indexOf("node_modules/");
    if (pos < 0 || pos > 0 && filePath.charAt(pos - 1) != "/") {
        return null;
    }
    var prefix = filePath.substring(0, pos);
    var suffix = filePath.substring(pos + "node_modules/".length);
    var pathRes = suffix.split("/");
    var packageJsonPath = path.join(prefix,  "node_modules", pathRes[0], "package.json");
    var json = fs.readFileSync(packageJsonPath);
    var packageJson = JSON.parse(json.toString());

    if (!packageJson.repository) {
        logger.debug("No repository defined in", packageJsonPath);
        return null;
    }

    return {
        repo: packageJson.repository.url,
        unit: pathRes[0],
        filePath: suffix
    };
}

function analyseAll() {

    var searchVisitor = walk.make({
        Function: function(node, kind, c) {
            if (node.id) {
                c(node.id, "function");
            }

            for (var i = 0; i < node.params.length; ++i)
                c(node.params[i], "param");
            c(node.body, "");
        },

        // TryStatement: function(node, st, c) {
        //     if (node.handler)
        //         c(node.handler.param, st);
        //     walk.base.TryStatement(node, st, c);

        // },

        VariableDeclaration: function(node, kind, c) {
            for (var i = 0; i < node.declarations.length; ++i) {
                var decl = node.declarations[i];
                c(decl.id, "var");
                if (decl.init) c(decl.init, "");
            }
        },
        MemberExpression: function(node, kind, c) {
            c(node.object, kind);
            c(node.property, "property");
        },
        ObjectExpression: function(node, kind, c) {
            for (var i = 0; i < node.properties.length; ++i) {
                c(node.properties[i].value, kind);
                c(node.properties[i].key, kind);
            }
        },
        CallExpression: function(node, kind, c) {
            if (node.callee.name === 'require') {
                var args = node.arguments;
                var data = getDefinition(node.sourceFile, args[0].end, args[0].start);
                var externalRepo = getExternalRepoInfo(data);

                if (externalRepo) {
                    var ref = {
                        DefPath: formPathForModule(externalRepo),
                        Def: false,
                        File: node.sourceFile.name,
                        Start: args[0].start,
                        End: args[0].end
                    }
                    if (externalRepo != null) {
                        ref.DefRepo = externalRepo.repo;
                        ref.DefUnit = externalRepo.unit;
                    }
                    out.Refs.add(ref);
                } else {
                    if (data.origin !== "node" && data.origin !== "commonjs" && data.origin !== "ecma5" && data.origin !== undefined) {
                        var ref = {
                            DefPath: "module/" + data.origin,
                            Def: false,
                            File: node.sourceFile.name,
                            Start: args[0].start,
                            End: args[0].end
                        }
                        out.Refs.add(ref);
                    } else {
                        if (data.url) {
                            var urlStruct = url.parse(data.url);
                            var envRef = {
                                DefPath: data.url,
                                DefUnitType: "URL",
                                DefRepo: urlStruct.protocol + (urlStruct.slashes ? "//" : "") + urlStruct.host,
                                Def: false,
                                File: node.sourceFile.name,
                                Start: args[0].start,
                                End: args[0].end
                            }
                            out.Refs.add(envRef);
                        }
                    }
                }
            }
            c(node.callee, kind);
            for (var i = 0; i < node.arguments.length; i++) {
                c(node.arguments[i], kind);
            }
        },

        Identifier: function(node, kind, c) {
            allIdents = allIdents + 1;
            var pathForId = formPathFromId(node);
            var data = getDefinition(node.sourceFile, node.end, node.start);
            var externalRepo = getExternalRepoInfo(data);

            var typeInfo = getType(node.sourceFile.name, node.end, node.start);
            var documentation = getDocumentation(node.sourceFile.name, node.end, node.start);
            var pathForDef = formPathFromData(data, externalRepo);

            if (data === null || pathForDef === null && data.url === undefined) {
                undefinedIdents = undefinedIdents + 1;
                logger.debug("EMPTY data for Identifier = ", node.name, "IN FILE = ", node.sourceFile.name);
                return;
            }

            if (pathForDef === null && data.url !== undefined && kind !== "var") {
                // Emit refs to environment variables
                var urlStruct = url.parse(data.url);
                var envRef = {
                    DefPath: data.url,
                    DefUnitType: "URL",
                    DefRepo: urlStruct.protocol + (urlStruct.slashes ? "//" : "") + urlStruct.host,
                    Def: false,
                    File: node.sourceFile.name,
                    Start: node.start,
                    End: node.end
                }
                out.Refs.add(envRef);
                return;
            }

            if (pathForDef === pathForId || ((kind === "var") && (pathForDef !== pathForId))) {
                // Emit definition
                var scopePathForId = scope.mapLinesPathToScopePath(ternServer, {
                    file: node.sourceFile.name,
                    start: node.start,
                    end: node.end
                });
                if (scopePathForId === null || scopePathForId === undefined) {
                    undefinedIdents = undefinedIdents + 1;
                    logger.debug("SOURCE FILE = ", node.sourceFile.name);
                    logger.debug("ERROR OCCURRED IN MAPPING SCOPE ID PATH", pathForId, "NAME=", node.name);
                    return;
                }
                var defData = {
                    Type: typeInfo.type,
                    Keyword: kind,
                    Kind: kind,
                    Separator: " "
                };
                var def = {
                    Path: scopePathForId,
                    Name: node.name,
                    Kind: kind,
                    File: node.sourceFile.name,
                    DefStart: node.start,
                    DefEnd: node.end,
                    Data: defData
                };
                out.Defs.add(def);

                // Emit fake reference
                var ref = {
                    DefPath: scopePathForId,
                    Def: true,
                    File: node.sourceFile.name,
                    Start: node.start,
                    End: node.end
                }
                out.Refs.add(ref);

                // Emit documentation
                if (documentation !== null && documentation.doc !== undefined) {
                    var docData = {
                        Path: scopePathForId,
                        Format: "",
                        Data: documentation.doc
                    }

                    out.Docs.add(docData);
                }

            } else {
                // emit reference
                var scopePathForDef = scope.mapLinesPathToScopePath(ternServer, {
                    file: data.origin,
                    start: data.start,
                    end: data.end
                });
                if (!scopePathForDef) {
                    if (data.url) {
                        var urlStruct = url.parse(data.url);
                        var envRef = {
                            DefPath: data.url,
                            DefUnitType: "URL",
                            DefRepo: urlStruct.protocol + (urlStruct.slashes ? "//" : "") + urlStruct.host,
                            Def: false,
                            File: node.sourceFile.name,
                            Start: node.start,
                            End: node.end
                        }
                        out.Refs.add(envRef);
                        return;
                    };

                    undefinedIdents = undefinedIdents + 1;
                    logger.debug("ERROR OCCURRED IN MAPPING SCOPE DEF PATH for", pathForDef, "NAME=", node.name);
                    logger.debug("SOURCE FILE = ", node.sourceFile.name);
                    logger.debug("DATA = ", data);
                    logger.debug("External repo = ", externalRepo);
                    logger.debug("===================================");
                    return;
                }
                var ref = {
                    DefPath: scopePathForDef,
                    Def: false,
                    File: node.sourceFile.name,
                    Start: node.start,
                    End: node.end
                }
                if (externalRepo != null) {
                    ref.DefRepo = externalRepo.repo;
                    ref.DefUnit = externalRepo.unit;
                }
                out.Refs.add(ref);
            }
        }
    });

    ternServer.files.forEach(function(file) {
        if (file.name.indexOf('node_modules') == -1) {
            walk.recursive(file.ast, "ast", null, searchVisitor);

            //add definition for each file-module representation and fake ref
            var kind = "module";
            var name = file.name;
            var defPath = kind + "/" + name;
            var defData = {
                Type: kind,
                Keyword: kind,
                Kind: kind,
                Separator: " "
            };
            var def = {
                Path: defPath,
                Name: name,
                Kind: kind,
                File: name,
                DefStart: file.ast.start,
                Data: defData
            };
            out.Defs.add(def);

            // Emit fake reference
            var ref = {
                DefPath: defPath,
                Def: true,
                File: name,
                Start: file.ast.start,
            }
            out.Refs.add(ref);

            // console.error("AST = ", file.ast.body);
        }
    });
}
<commit_msg>Small fix in external refs emission<commit_after>var tern = require('tern');
var path = require('path');
var fs = require('fs');
var url = require('url');

var walk = require('acorn/dist/walk');

var FastSet = require("collections/fast-set");

var plugin = require('./tern-def-api.js');
var scope = require('./define-path-info.js');
var util = require('./util.js');
var logger = require('./logger.js');

require('tern/plugin/doc_comment');
require('tern/plugin/commonjs');
require('tern/plugin/modules');
require('tern/plugin/node');
require('tern/plugin/node_resolve');

//require('tern/plugin/requirejs');

var localDir = process.cwd();
var ternServer = null;

var localFiles = null;

var allIdents = 0;
var undefinedIdents = 0;

var out = {
    Defs: new FastSet([], function(a, b) {
        return a.Path == b.Path;
    }, function(o) {
        return o.Path;
    }),
    Refs: new FastSet([], function(a, b) {
        return a.DefPath == b.DefPath &&
               a.DefUnitType == b.DefUnitType &&
               a.DefRepo == b.DefRepo &&
               a.Def == b.Def &&
               a.File == b.File &&
               a.Start == b.Start &&
               a.End == b.End;
    }, function(o) {
        return [o.DefPath, o.DefUnitType, o.DefRepo, o.Def, o.File, o.Start, o.End].join("|");
    }),
    Docs: new FastSet([], function(a, b) {
        return a.Path == b.Path;
    }, function(o) {
        return o.Path;
    })
}

initTernServer(process.argv.slice(2));
out.Defs = out.Defs.toArray();
out.Refs = out.Refs.toArray();
out.Docs = out.Docs.toArray();
console.log(JSON.stringify(out, null, 2));

function initTernServer(files) {
    localFiles = files;
    var defsPath = path.join(__dirname, "../node_modules/tern/defs/ecma5.json");
    var defs = JSON.parse(fs.readFileSync(defsPath, "utf8"));
    var browserDefsPath = path.join(__dirname, "../node_modules/tern/defs/browser.json");
    var browserDefs = JSON.parse(fs.readFileSync(browserDefsPath, "utf8"));
    var ternOptions = {
        dependencyBudget: 500000,
        projectDir: localDir,
        defs: [defs, browserDefs],
        async: false,
        getFile: function(file) {
            return fs.readFileSync(path.resolve(localDir, file), "utf8");
        },
        plugins: {
            node: true,
            requirejs: true,
            modules: true,
            es_modules: true,
            commonjs: true,
            doc_comment: true
        }
    };

    ternServer = new tern.Server(ternOptions);

    files.forEach(function(file) {
        ternServer.addFile(file);
    });

    ternServer.flush(function(err) {
        if (err) throw err;
        scope.initLocalFilesScopePaths(ternServer, localFiles);
        analyseAll();
    });

    logger.debug("All ids = ", allIdents, " Undefined ids = ", undefinedIdents);
    if (allIdents != 0) {
        logger.debug("percentage of undefined ids = ", (undefinedIdents / allIdents) * 100, "%");
    }

    return out;
}

function getQueryInfo(file, offset, type, start) {
    var query = {
        type: type,
        start: start,
        end: offset,
        file: file,
        docFormat: "full"
    }

    var res = null;
    ternServer.request({
        query: query,
        offset: offset
    }, function(error, data) {
        if (error) {
            logger.error("Error returned from Tern 'definition' request: " + error);
            return;
        }
        res = data;
    });
    return res;
}

function getType(file, offset, start) {
    return getQueryInfo(file, offset, "type", start);
}

function getDefinition(file, offset, start) {
    var query = {
        type: "definition",
        start: start,
        end: offset,
        file: file.name,
        docFormat: "full"
    }
    return plugin.findDef(ternServer, query, file);
}

function getCompletions(file, offset, start) {
    return getQueryInfo(file, offset, "completions", start);
}

function getDocumentation(file, offset, start) {
    return getQueryInfo(file, offset, "documentation", start);
}

function formPathFromId(id) {
    return util.formPath(id.sourceFile.name, id.start, id.end);
}

function formPathFromData(data, externalRepo) {
    if (data === null) {
        return null;
    }
    if (externalRepo === null) {
        if (data.origin === undefined || data.start === undefined || data.end === undefined) {
            return null;
        } else {
            return util.formPath(data.origin, data.start, data.end);
        }
    } else {
        return util.formPath(externalRepo.filePath, data.start, data.end);
    }
}

function formPathForModule(externalRepo) {
    var res = externalRepo.filePath.split("/");
    res[0] = "module";
    return res.join("/");
}

// TODO refactor this method, bad approach for finding package.json
function getExternalRepoInfo(data) {
    if (!data || !data.origin) {
        return null;
    }

    if (["node", "commonjs", "ecma5"].indexOf(data.origin) >= 0) {
        return null;
    }

    var filePath = util.normalizePath(data.origin);

    if (localFiles.indexOf(filePath) > -1) {
        return null;
    }

    // node_modules/... or .../node_modules/...
    var pos = filePath.indexOf("node_modules/");
    if (pos < 0 || pos > 0 && filePath.charAt(pos - 1) != "/") {
        return null;
    }
    var prefix = filePath.substring(0, pos);
    var suffix = filePath.substring(pos + "node_modules/".length);
    var pathRes = suffix.split("/");
    var packageJsonPath = path.join(prefix,  "node_modules", pathRes[0], "package.json");
    var json = fs.readFileSync(packageJsonPath);
    var packageJson = JSON.parse(json.toString());

    if (!packageJson.repository) {
        logger.debug("No repository defined in", packageJsonPath);
        return null;
    }

    return {
        repo: packageJson.repository.url,
        unit: pathRes[0],
        filePath: suffix
    };
}

function analyseAll() {

    var searchVisitor = walk.make({
        Function: function(node, kind, c) {
            if (node.id) {
                c(node.id, "function");
            }

            for (var i = 0; i < node.params.length; ++i)
                c(node.params[i], "param");
            c(node.body, "");
        },

        // TryStatement: function(node, st, c) {
        //     if (node.handler)
        //         c(node.handler.param, st);
        //     walk.base.TryStatement(node, st, c);

        // },

        VariableDeclaration: function(node, kind, c) {
            for (var i = 0; i < node.declarations.length; ++i) {
                var decl = node.declarations[i];
                c(decl.id, "var");
                if (decl.init) c(decl.init, "");
            }
        },
        MemberExpression: function(node, kind, c) {
            c(node.object, kind);
            c(node.property, "property");
        },
        ObjectExpression: function(node, kind, c) {
            for (var i = 0; i < node.properties.length; ++i) {
                c(node.properties[i].value, kind);
                c(node.properties[i].key, kind);
            }
        },
        CallExpression: function(node, kind, c) {
            if (node.callee.name === 'require') {
                var args = node.arguments;
                var data = getDefinition(node.sourceFile, args[0].end, args[0].start);
                var externalRepo = getExternalRepoInfo(data);

                if (externalRepo) {
                    var ref = {
                        DefPath: formPathForModule(externalRepo),
                        Def: false,
                        File: node.sourceFile.name,
                        Start: args[0].start,
                        End: args[0].end
                    }
                    ref.DefRepo = externalRepo.repo;
                    ref.DefUnit = externalRepo.unit;
                    out.Refs.add(ref);
                } else {
                    if (data.origin !== "node" && data.origin !== "commonjs" && data.origin !== "ecma5" && data.origin !== undefined) {
                        var ref = {
                            DefPath: "module/" + data.origin,
                            Def: false,
                            File: node.sourceFile.name,
                            Start: args[0].start,
                            End: args[0].end
                        }
                        out.Refs.add(ref);
                    } else {
                        if (data.url) {
                            var urlStruct = url.parse(data.url);
                            var envRef = {
                                DefPath: data.url,
                                DefUnitType: "URL",
                                DefRepo: urlStruct.protocol + (urlStruct.slashes ? "//" : "") + urlStruct.host,
                                Def: false,
                                File: node.sourceFile.name,
                                Start: args[0].start,
                                End: args[0].end
                            }
                            out.Refs.add(envRef);
                        }
                    }
                }
            }
            c(node.callee, kind);
            for (var i = 0; i < node.arguments.length; i++) {
                c(node.arguments[i], kind);
            }
        },

        Identifier: function(node, kind, c) {
            allIdents = allIdents + 1;
            var pathForId = formPathFromId(node);
            var data = getDefinition(node.sourceFile, node.end, node.start);
            var externalRepo = getExternalRepoInfo(data);

            var typeInfo = getType(node.sourceFile.name, node.end, node.start);
            var documentation = getDocumentation(node.sourceFile.name, node.end, node.start);
            var pathForDef = formPathFromData(data, externalRepo);

            if (data === null || pathForDef === null && data.url === undefined) {
                undefinedIdents = undefinedIdents + 1;
                logger.debug("EMPTY data for Identifier = ", node.name, "IN FILE = ", node.sourceFile.name);
                return;
            }

            if (pathForDef === null && data.url !== undefined && kind !== "var") {
                // Emit refs to environment variables
                var urlStruct = url.parse(data.url);
                var envRef = {
                    DefPath: data.url,
                    DefUnitType: "URL",
                    DefRepo: urlStruct.protocol + (urlStruct.slashes ? "//" : "") + urlStruct.host,
                    Def: false,
                    File: node.sourceFile.name,
                    Start: node.start,
                    End: node.end
                }
                out.Refs.add(envRef);
                return;
            }

            if (pathForDef === pathForId || ((kind === "var") && (pathForDef !== pathForId))) {
                // Emit definition
                var scopePathForId = scope.mapLinesPathToScopePath(ternServer, {
                    file: node.sourceFile.name,
                    start: node.start,
                    end: node.end
                });
                if (scopePathForId === null || scopePathForId === undefined) {
                    undefinedIdents = undefinedIdents + 1;
                    logger.debug("SOURCE FILE = ", node.sourceFile.name);
                    logger.debug("ERROR OCCURRED IN MAPPING SCOPE ID PATH", pathForId, "NAME=", node.name);
                    return;
                }
                var defData = {
                    Type: typeInfo.type,
                    Keyword: kind,
                    Kind: kind,
                    Separator: " "
                };
                var def = {
                    Path: scopePathForId,
                    Name: node.name,
                    Kind: kind,
                    File: node.sourceFile.name,
                    DefStart: node.start,
                    DefEnd: node.end,
                    Data: defData
                };
                out.Defs.add(def);

                // Emit fake reference
                var ref = {
                    DefPath: scopePathForId,
                    Def: true,
                    File: node.sourceFile.name,
                    Start: node.start,
                    End: node.end
                }
                out.Refs.add(ref);

                // Emit documentation
                if (documentation !== null && documentation.doc !== undefined) {
                    var docData = {
                        Path: scopePathForId,
                        Format: "",
                        Data: documentation.doc
                    }

                    out.Docs.add(docData);
                }

            } else {
                // emit reference
                var scopePathForDef = scope.mapLinesPathToScopePath(ternServer, {
                    file: data.origin,
                    start: data.start,
                    end: data.end
                });
                if (!scopePathForDef) {
                    if (data.url) {
                        var urlStruct = url.parse(data.url);
                        var envRef = {
                            DefPath: data.url,
                            DefUnitType: "URL",
                            DefRepo: urlStruct.protocol + (urlStruct.slashes ? "//" : "") + urlStruct.host,
                            Def: false,
                            File: node.sourceFile.name,
                            Start: node.start,
                            End: node.end
                        }
                        out.Refs.add(envRef);
                        return;
                    };

                    undefinedIdents = undefinedIdents + 1;
                    logger.debug("ERROR OCCURRED IN MAPPING SCOPE DEF PATH for", pathForDef, "NAME=", node.name);
                    logger.debug("SOURCE FILE = ", node.sourceFile.name);
                    logger.debug("DATA = ", data);
                    logger.debug("External repo = ", externalRepo);
                    logger.debug("===================================");
                    return;
                }
                var ref = {
                    DefPath: scopePathForDef,
                    Def: false,
                    File: node.sourceFile.name,
                    Start: node.start,
                    End: node.end
                }
                if (externalRepo != null) {
                    ref.DefRepo = externalRepo.repo;
                    ref.DefUnit = externalRepo.unit;
                }
                out.Refs.add(ref);
            }
        }
    });

    ternServer.files.forEach(function(file) {
        if (file.name.indexOf('node_modules') == -1) {
            walk.recursive(file.ast, "ast", null, searchVisitor);

            //add definition for each file-module representation and fake ref
            var kind = "module";
            var name = file.name;
            var defPath = kind + "/" + name;
            var defData = {
                Type: kind,
                Keyword: kind,
                Kind: kind,
                Separator: " "
            };
            var def = {
                Path: defPath,
                Name: name,
                Kind: kind,
                File: name,
                DefStart: file.ast.start,
                Data: defData
            };
            out.Defs.add(def);

            // Emit fake reference
            var ref = {
                DefPath: defPath,
                Def: true,
                File: name,
                Start: file.ast.start,
            }
            out.Refs.add(ref);

            // console.error("AST = ", file.ast.body);
        }
    });
}
<|endoftext|>


<commit_before>var fs        = require('fs')
  , path      = require('path')
  , deputy    = require('deputy')
  , utils     = require('./utils')
  , resolver  = require('./resolver')
  , error     = utils.error
  , join      = path.join;

/**
 * Analyzer class
 *
 */
function Analyzer(obj, before, compile, builtIns, serverModules) {
  this.entryPoint = obj.entryPoint;
  this.domains = obj.domains;
  this.ignoreDoms = obj.ignoreDoms;
  this.deputy = deputy(join(__dirname, '..', 'deputy.json'));
  this.before = before;
  this.compile = compile;
  this.npmTree = {
    _builtIns : []
  }; // resolver builds this - makes sanitizing our tree easier as well
  this.resolve = resolver(this.domains, Object.keys(obj.arbiters), obj.exts, this.npmTree, builtIns, serverModules);
  //TODO: need builtIns / serverModules for analysis purposes?
  this.buildTree();
}

/**
 * dependencies
 *
 * Resolves all the dependencies of a file through detective
 *
 * @[in] absReq - absolute path to file
 * @[in] extraPath - path relative to domain path
 * @[in] dom - name of the domain extraPath is relative to
 * @return Array of resolver output
 * i.e. triples of form: [absPath, domain, isReal]
 */
Analyzer.prototype.dependencies = function (absReq, extraPath, dom) {
  // get 'before' sanitized code
  var code = this.before(this.compile(join(this.domains[dom], absReq)))
    , resolve = this.resolve;

  // absolutizes and locates everything immediately so that
  // we have a unique representation of each file

  // detective scanning each file is the most time consuming process on big codebases so we use the deputy caching layer
  //console.log('scanning', absReq)
  return this.deputy(code).map(function (req) {
    return resolve(req, extraPath, dom);
  });
};

/**
 * buildTree
 *
 * Calls this.dependencies recursively from the entry point
 * Stores this.tree in instance of form:
 * {
 *    name      : domain relative fileName
 *    domain    : domain name with entry in this.domains
 *    extraPath : dirname of name
 *    isReal    : if true then join(this.domais[domainName], fileName) exists
 *    deps      : object of {uid : recursive this for each file}
 *    parent    : refers to parent structure one up from deps [root does not have this]
 *    level     : number of levels in (in terms of deps) we are [0 indexed]
  * }
 */
Analyzer.prototype.buildTree = function () {
  this.tree = {
    name      : this.entryPoint
  , domain    : 'app'
  , extraPath : ''
  , deps      : {}
  , fake      : 0
  , level     : 0
  };

  var circularCheck = function (t, uid) {
    var chain = [uid];
    while (true) { // follow the branch up to verify we do not find self
      if (!t.parent) {
        return;
      }
      chain.push(t.domain + '::' + t.name);
      t = t.parent;
      if (chain[chain.length - 1] === chain[0]) {
        error("analysis revealed a circular dependency: " + chain.join(' <- '));
      }
    }
  };

  var build = function (t) {
    var resolveRes = this.dependencies(t.name, t.extraPath, t.domain);
    //console.log("resolved: ", JSON.stringify(resolveRes));

    for (var i = 0; i < resolveRes.length; i += 1) {
      var triple = resolveRes[i]
        , name = triple[0]
        , domain = triple[1]
        , isReal = triple[2]
        , uid = domain + '::' + name;

      t.deps[uid] = {
        name      : name
      , domain    : domain
      , isReal    : isReal
      , extraPath : path.dirname(name)
      , deps      : {}
      , parent    : t
      , level     : t.level + 1
      };
      if (isReal) {
        circularCheck(t, uid);          // throw on circulars
        build.call(this, t.deps[uid]);  // recurse
      }
    }
  };
  build.call(this, this.tree);
};

// print format helper
function formatName(absReq, extSuffix, domPrefix, dom) {
  var n = extSuffix ? absReq : absReq.split('.')[0];
  if (dom === 'npm') {
    n = path.basename(n); //TODO: improve this
  }
  else if (n.indexOf('index') >= 0 && path.basename(n) === n) {
    n = ''; // take out domain index requires to make print more readable
  }
  if (domPrefix) {
    n = dom + '::' + n;
  }
  return n;
}

/**
 * print
 * constructs a prettified dependency tree
 *
 * @[in] extSuffix - true iff show extensions everywhere
 * @[in] domPrefix - true iff show domain prefixes everywhere
 * @return printable string
 */
Analyzer.prototype.print = function (extSuffix, domPrefix) {
  var lines = [formatName(this.entryPoint, extSuffix, domPrefix, 'app')];
  var ignores = this.ignoreDoms; //TODO: node_modules should have special rules

  var objCount = function (o) {
    return Object.keys(o).filter(function (e) {
      return ignores.indexOf(o[e].domain) < 0;
    }).length;
  };

  var print = function (branch, level, parentAry) {
    var idx = 0
      , bSize = objCount(branch.deps);

    Object.keys(branch.deps).forEach(function (uid) {
      var depObj = branch.deps[uid];

      if (ignores.indexOf(depObj.domain) >= 0) {
        return;
      }
      idx += 1;

      var hasChildren = objCount(depObj.deps) > 0
        , forkChar    = hasChildren ? "┬" : "─"
        , isLast      = idx === bSize
        , turnChar    = isLast ? "└" : "├"
        , displayName = formatName(depObj.name, extSuffix, domPrefix, depObj.domain)
        , indent      = [];

      // indent
      for (var i = 0; i < level; i += 1) {
        indent.push((parentAry[i] ? " " : "│") + "  ");
      }

      lines.push(" " + indent.join('') + turnChar + "──" + forkChar + displayName);

      if (hasChildren) {
        // recurse into uid's dependency tree keeping track of parent lines
        print(depObj, level + 1, parentAry.concat(isLast));
      }
    });
  };
  print(this.tree, 0, []);
  return lines.join('\n');
};

/**
 * sort
 * sorts the dependency tree by maximum require level descending
 *
 * @return Array of pairs [domain, name] where join(domain, name) exists
 */
Analyzer.prototype.sort = function () {
  var obj = {};
  obj['app::' + this.entryPoint] = 0;

  var sort = function (t) {
    Object.keys(t.deps).forEach(function (uid) {
      var dep = t.deps[uid];
      if (!dep.isReal) {
        return;
      }
      obj[uid] = Math.max(dep.level, obj[uid] || 0);
      sort(dep); // recurse
    });
  };
  sort(this.tree);

  return Object.keys(obj).map(function (uid) {
    return [uid, obj[uid]];   // convert obj to sortable array of [uid, level] pairs
  }).sort(function (a, b) {
    return b[1] - a[1];       // sort by level descending to get correct insertion order
  }).map(function (e) {
    return e[0].split('::');  // map to pairs of form [domain, name]
  });
};

module.exports = function (obj, before, compile, builtIns, serverModules) {
  var o = new Analyzer(obj, before, compile, builtIns, serverModules);
  return {
    print: function () {
      return o.print.apply(o, arguments);
    }
  , sort: function () {
      return o.sort.apply(o, arguments);
    }
  , npm: function () {
      //console.log(JSON.stringify(o.npmTree));
      return o.npmTree;
    }
  };
};

<commit_msg>rip out tree printing code from analyzer and rely on topiary<commit_after>var fs        = require('fs')
  , path      = require('path')
  , deputy    = require('deputy')
  , utils     = require('./utils')
  , resolver  = require('./resolver')
  , topiary   = require('topiary')
  , error     = utils.error
  , join      = path.join;

/**
 * Analyzer class
 *
 */
function Analyzer(obj, before, compile, builtIns, serverModules) {
  this.entryPoint = obj.entryPoint;
  this.domains = obj.domains;
  this.ignoreDoms = obj.ignoreDoms;
  this.deputy = deputy(join(__dirname, '..', 'deputy.json'));
  this.before = before;
  this.compile = compile;
  this.npmTree = {
    _builtIns : []
  }; // resolver builds this - makes sanitizing our tree easier as well
  this.resolve = resolver(this.domains, Object.keys(obj.arbiters), obj.exts, this.npmTree, builtIns, serverModules);
  //TODO: need builtIns / serverModules for analysis purposes?
  this.buildTree();
}

/**
 * dependencies
 *
 * Resolves all the dependencies of a file through detective
 *
 * @[in] absReq - absolute path to file
 * @[in] extraPath - path relative to domain path
 * @[in] dom - name of the domain extraPath is relative to
 * @return Array of resolver output
 * i.e. triples of form: [absPath, domain, isReal]
 */
Analyzer.prototype.dependencies = function (absReq, extraPath, dom) {
  // get 'before' sanitized code
  var code = this.before(this.compile(join(this.domains[dom], absReq)))
    , resolve = this.resolve;

  // absolutizes and locates everything immediately so that
  // we have a unique representation of each file

  // detective scanning each file is the most time consuming process on big codebases so we use the deputy caching layer
  //console.log('scanning', absReq)
  return this.deputy(code).map(function (req) {
    return resolve(req, extraPath, dom);
  });
};

/**
 * buildTree
 *
 * Calls this.dependencies recursively from the entry point
 * Stores this.tree in instance of form:
 * {
 *    name      : domain relative fileName
 *    domain    : domain name with entry in this.domains
 *    extraPath : dirname of name
 *    isReal    : if true then join(this.domais[domainName], fileName) exists
 *    deps      : object of {uid : recursive this for each file}
 *    parent    : refers to parent structure one up from deps [root does not have this]
 *    level     : number of levels in (in terms of deps) we are [0 indexed]
  * }
 */
Analyzer.prototype.buildTree = function () {
  this.tree = {
    name      : this.entryPoint
  , domain    : 'app'
  , extraPath : ''
  , deps      : {}
  , fake      : 0
  , level     : 0
  };

  var circularCheck = function (t, uid) {
    var chain = [uid];
    while (true) { // follow the branch up to verify we do not find self
      if (!t.parent) {
        return;
      }
      chain.push(t.domain + '::' + t.name);
      t = t.parent;
      if (chain[chain.length - 1] === chain[0]) {
        error("analysis revealed a circular dependency: " + chain.join(' <- '));
      }
    }
  };

  var build = function (t) {
    var resolveRes = this.dependencies(t.name, t.extraPath, t.domain);
    //console.log("resolved: ", JSON.stringify(resolveRes));

    for (var i = 0; i < resolveRes.length; i += 1) {
      var triple = resolveRes[i]
        , name = triple[0]
        , domain = triple[1]
        , isReal = triple[2]
        , uid = domain + '::' + name;

      t.deps[uid] = {
        name      : name
      , domain    : domain
      , isReal    : isReal
      , extraPath : path.dirname(name)
      , deps      : {}
      , parent    : t
      , level     : t.level + 1
      };
      if (isReal) {
        circularCheck(t, uid);          // throw on circulars
        build.call(this, t.deps[uid]);  // recurse
      }
    }
  };
  build.call(this, this.tree);
};

// print format helper
function makeFormatter(extSuffix, domPrefix) {
  return function (ele) {
    var n = extSuffix ? ele.name : ele.name.split('.')[0];
    if (ele.domain === 'npm') {
      n = path.basename(n); //TODO: improve this
    }
    else if (n.indexOf('index') >= 0 && path.basename(n) === n) {
      n = ''; // take out domain index requires to make print more readable
    }
    if (domPrefix) {
      n = ele.domain + '::' + n;
    }
    return n;
  };
}

/**
 * print
 * constructs a prettified dependency tree
 *
 * @[in] extSuffix - true iff show extensions everywhere
 * @[in] domPrefix - true iff show domain prefixes everywhere
 * @return printable string
 */
Analyzer.prototype.print = function (extSuffix, domPrefix) {
  var ignores = this.ignoreDoms;
  var shapeFn = makeFormatter(extSuffix, domPrefix);
  var filterFn = function (el) {
    return (ignores.indexOf(el.domain) < 0)
  }
  return topiary(this.tree, 'deps', shapeFn, filterFn);
};

/**
 * sort
 * sorts the dependency tree by maximum require level descending
 *
 * @return Array of pairs [domain, name] where join(domain, name) exists
 */
Analyzer.prototype.sort = function () {
  var obj = {};
  obj['app::' + this.entryPoint] = 0;

  var sort = function (t) {
    Object.keys(t.deps).forEach(function (uid) {
      var dep = t.deps[uid];
      if (!dep.isReal) {
        return;
      }
      obj[uid] = Math.max(dep.level, obj[uid] || 0);
      sort(dep); // recurse
    });
  };
  sort(this.tree);

  return Object.keys(obj).map(function (uid) {
    return [uid, obj[uid]];   // convert obj to sortable array of [uid, level] pairs
  }).sort(function (a, b) {
    return b[1] - a[1];       // sort by level descending to get correct insertion order
  }).map(function (e) {
    return e[0].split('::');  // map to pairs of form [domain, name]
  });
};

module.exports = function (obj, before, compile, builtIns, serverModules) {
  var o = new Analyzer(obj, before, compile, builtIns, serverModules);
  return {
    print: function () {
      return o.print.apply(o, arguments);
    }
  , sort: function () {
      return o.sort.apply(o, arguments);
    }
  , npm: function () {
      //console.log(JSON.stringify(o.npmTree));
      return o.npmTree;
    }
  };
};

<|endoftext|>