Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Huge upgrade to handle directory destinations, and refactor mess

  • Loading branch information...
commit 9dbd61062f0de90813ab91515cdf0804ba59254f 1 parent 5264414
@joewalker joewalker authored
Showing with 901 additions and 570 deletions.
  1. +899 −568 lib/dryice/index.js
  2. +2 −2 package.json
View
1,467 lib/dryice/index.js
@@ -45,109 +45,591 @@ var ujs = require("uglify-js");
* See https://github.com/mozilla/dryice for usage instructions.
*/
function copy(obj) {
- // Gather a list of all the input sources
- addSource(obj, obj.source);
+ var filters = copy.filterFactory(obj.filter);
+ var source = copy.sourceFactory(obj.source, filters);
+ var dest = copy.destFactory(obj.dest, filters);
+ dest.processSource(source);
+}
- // Concatenate all the input sources
- var value = '';
- obj.sources.forEach(function(source) {
- value += source.value;
- }, this);
+/**
+ * A Location is a base and a path which together point to a file or directory.
+ * It's useful to be able to know in copy operations relative to some project
+ * root to be able to remember where in a destination the file should go
+ */
+function Location(base, somePath) {
+ if (base == null) {
+ throw new Error('base == null');
+ }
+ this.base = base;
+ this.path = somePath;
+}
- // Run filters where onRead=false
- value = runFilters(value, obj.filter, false);
+Location.prototype.isLocation = true;
- // Output
- // TODO: for now we're ignoring the concept of directory destinations.
- if (typeof obj.dest.value === 'string') {
- obj.dest.value += value;
- }
- else if (typeof obj.dest === 'string') {
- fs.writeFileSync(obj.dest, value);
+Object.defineProperty(Location.prototype, 'fullname', {
+ get: function() {
+ return path.join(this.base, this.path);
+ }
+});
+
+Object.defineProperty(Location.prototype, 'dirname', {
+ get: function() {
+ return path.dirname(this.fulllname);
+ }
+});
+
+/**
+ * Select the correct implementation of Source for the given source property
+ */
+copy.sourceFactory = function(source, filters) {
+ if (source == null) {
+ throw new Error('Missing source');
+ }
+
+ if (source.isSource) {
+ return source;
+ }
+
+ if (typeof source === 'string') {
+ if (copy.isDirectory(source)) {
+ return new copy.DirectorySource(source, filters);
}
else {
- throw new Error('Can\'t handle type of dest: ' + typeof obj.dest);
+ return new copy.FileSource(new Location('', source), filters);
}
-}
+ }
-function addName(currentName, newName) {
- return currentName === null ? currentName : newName;
-}
+ if (Array.isArray(source)) {
+ return new copy.ArraySource(source, filters);
+ }
+
+ if (typeof source === 'function') {
+ return new copy.FunctionSource(source, filters);
+ }
-function addSource(obj, source) {
- if (!obj.sources) {
- obj.sources = [];
+ if (source.root != null) {
+ if (source.require != null) {
+ var project = new CommonJsProject([ source.root ]);
+ return new copy.CommonJsSource(project, source.require, filters);
}
- if (typeof source === 'function') {
- addSource(obj, source());
+ return new copy.DirectorySource(source.root, source.include, source.exclude, filters);
+ }
+
+ if (source.base != null && source.path != null) {
+ return new copy.FileSource(new Location(source.base, source.path), filters);
+ }
+
+ if (typeof source.value === 'string') {
+ return new copy.ValueSource(source.value, null, filters);
+ }
+
+ if (source.project != null && source.require != null) {
+ return new copy.CommonJsSource(source.project, source.require, filters);
+ }
+
+ throw new Error('Can\'t handle type of source: ' + typeof source);
+};
+
+copy.debug = false;
+
+/**
+ * Abstract Source.
+ * Concrete implementations of Source should define the 'get' property.
+ */
+copy.Source = function() {
+};
+
+/**
+ * @return Either another source, an array of other sources or a string value
+ * when there is nothing else to dig into
+ */
+Object.defineProperty(copy.Source.prototype, 'get', {
+ get: function() {
+ throw new Error('Source.get is not implemented');
+ }
+});
+
+copy.Source.prototype.isSource = true;
+
+copy.Source.prototype._runFilters = function(value, location) {
+ this._filters.forEach(function(filter) {
+ if (filter.onRead) {
+ value = filter(value, location);
}
- else if (Array.isArray(source)) {
- source.forEach(function(s) {
- addSource(obj, s);
- }, this);
+ }, this);
+ return value;
+};
+
+/**
+ * Default encoding for all sources
+ */
+copy.Source.prototype.encoding = 'utf8';
+
+/**
+ * An ArraySource is simply an array containing things that can resolve to
+ * implementations of Source when passed to copy.sourceFactory()
+ */
+copy.ArraySource = function(array, filters) {
+ copy.Source.call(this);
+ this._array = array;
+ this._filters = filters;
+};
+
+copy.ArraySource.prototype = Object.create(copy.Source.prototype);
+
+Object.defineProperty(copy.ArraySource.prototype, 'get', {
+ get: function() {
+ return this._array.map(function(member) {
+ return copy.sourceFactory(member, this._filters);
+ }, this);
+ }
+});
+
+/**
+ * A FunctionSource is something that can be called to resolve to another
+ * Source implementation
+ */
+copy.FunctionSource = function(func, filters) {
+ copy.Source.call(this);
+ this._func = func;
+ this._filters = filters;
+};
+
+copy.FunctionSource.prototype = Object.create(copy.Source.prototype);
+
+Object.defineProperty(copy.FunctionSource.prototype, 'get', {
+ get: function() {
+ return copy.sourceFactory(this._func(), this._filters);
+ }
+});
+
+/**
+ * A Source that finds files under a given directory with specified include /
+ * exclude patterns.
+ * @param root The root in the filesystem under which the files exist
+ * @param filterOrInclude
+ */
+copy.DirectorySource = function(root, filterOrInclude, exclude, filters) {
+ copy.Source.call(this);
+ this._filters = filters;
+
+ this.root = root;
+ if (this.root instanceof CommonJsProject) {
+ this.root = this.root.roots;
+ }
+
+ if (Array.isArray(this.root)) {
+ this.root.map(function(r) {
+ return ensureTrailingSlash(r);
+ });
+ }
+
+ if (typeof include === 'function') {
+ this._searchFilter = filterOrInclude;
+ }
+ else {
+ this._searchFilter = this._createFilter(filterOrInclude, exclude);
+ }
+};
+
+copy.DirectorySource.prototype = Object.create(copy.Source.prototype);
+
+Object.defineProperty(copy.DirectorySource.prototype, 'get', {
+ get: function() {
+ return this._findMatches(this.root, '/');
+ }
+});
+
+copy.DirectorySource.prototype._findMatches = function(root, path) {
+ var sources = [];
+
+ if (Array.isArray(root)) {
+ root.forEach(function(r) {
+ var matches = this._findMatches(r, path);
+ sources.push.apply(sources, matches);
+ }, this);
+ return sources;
+ }
+
+ root = ensureTrailingSlash(root);
+ path = ensureTrailingSlash(path);
+
+ if (copy.isDirectory(root + path)) {
+ fs.readdirSync(root + path).forEach(function(entry) {
+ var stat = fs.statSync(root + path + entry);
+ if (stat.isFile()) {
+ if (this._searchFilter(path + entry)) {
+ var location = new Location(root, path + entry);
+ sources.push(new copy.FileSource(location, this._filters));
+ }
+ }
+ else if (stat.isDirectory()) {
+ var matches = this._findMatches(root, path + entry);
+ sources.push.apply(sources, matches);
+ }
+ }, this);
+ }
+
+ return sources;
+};
+
+copy.DirectorySource.prototype._createFilter = function(include, exclude) {
+ return function(pathname) {
+ function noPathMatch(pattern) {
+ return !pattern.test(pathname);
}
- else if (source instanceof NullModule) {
- source.value = source.toString();
- source.filtered = true;
- obj.sources.push(source);
+ if (include instanceof RegExp) {
+ if (noPathMatch(include)) {
+ return false;
+ }
}
- else if (source.root) {
- copy.findFiles(obj, source);
+ if (typeof include === 'string') {
+ if (noPathMatch(new RegExp(include))) {
+ return false;
+ }
}
- else if (source.base) {
- addSourceBase(obj, source);
+ if (Array.isArray(include)) {
+ if (include.every(noPathMatch)) {
+ return false;
+ }
}
- else if (typeof source === 'string') {
- addSourceFile(obj, source);
+
+ function pathMatch(pattern) {
+ return pattern.test(pathname);
}
- else if (typeof source.value === 'string') {
- if (!source.filtered) {
- source.value = runFilters(source.value, obj.filter, true, source.name);
- source.filtered = true;
- }
- obj.sources.push(source);
+ if (exclude instanceof RegExp) {
+ if (pathMatch(exclude)) {
+ return false;
+ }
}
- else {
- throw new Error('Can\'t handle type of source: ' + typeof source);
+ if (typeof exclude === 'string') {
+ if (pathMatch(new RegExp(exclude))) {
+ return false;
+ }
+ }
+ if (Array.isArray(exclude)) {
+ if (exclude.some(pathMatch)) {
+ return false;
+ }
}
-}
-function addSourceFile(obj, filename) {
- var read = fs.readFileSync(filename);
- obj.sources.push({
- name: filename,
- value: runFilters(read, obj.filter, true, filename)
- });
-}
+ return true;
+ };
+};
-function addSourceBase(obj, baseObj) {
- var read = fs.readFileSync(path.join(baseObj.base, baseObj.path));
- obj.sources.push({
- name: baseObj,
- value: runFilters(read, obj.filter, true, baseObj)
- });
-}
+/**
+ * A FileSource gets data directly from a file. It has 2 parts to the filename,
+ * a base and path members, where filename = base + path.
+ * FileSources are important when using CommonJS filters, because it tells the
+ * filter where the root of the hierarchy is, which lets us know the module
+ * name.
+ * If there is no base to the filename, use a base of ''.
+ */
+copy.FileSource = function(location, filters) {
+ copy.Source.call(this);
+ this.location = location;
+ this.name = location.fullname;
+ this._filters = filters;
+};
-function runFilters(value, filter, reading, name) {
- if (!filter) {
- return value;
- }
+copy.FileSource.prototype = Object.create(copy.Source.prototype);
- if (Array.isArray(filter)) {
- filter.forEach(function(f) {
- value = runFilters(value, f, reading, name);
- }, this);
- return value;
- }
+Object.defineProperty(copy.FileSource.prototype, 'get', {
+ get: function() {
+ var read = fs.readFileSync(this.name);
+ return this._runFilters(read, this.location);
+ }
+});
+
+/**
+ *
+ */
+copy.ValueSource = function(value, location, filters) {
+ copy.Source.call(this);
+ this._value = value;
+ this._location = location;
+ this._filters = filters;
+};
+
+copy.ValueSource.prototype = Object.create(copy.Source.prototype);
+
+Object.defineProperty(copy.ValueSource.prototype, 'get', {
+ get: function() {
+ return this._runFilters(this._value, this._location);
+ }
+});
+
+/**
+ * Read modules from a CommonJS Project using a require property.
+ */
+copy.CommonJsSource = function(project, require, filters) {
+ copy.Source.call(this);
+ this._project = project;
+ this._filters = filters;
+
+ if (!project instanceof CommonJsProject) {
+ throw new Error('commonjs project should be a CommonJsProject');
+ }
+
+ if (typeof require === 'string') {
+ this._require = [ require ];
+ }
+ else if (Array.isArray(require)) {
+ this._require = require;
+ }
+ else {
+ throw new Error('Expected commonjs args to have string/array require.');
+ }
+};
+
+copy.CommonJsSource.prototype = Object.create(copy.Source.prototype);
- if (!!filter.onRead == reading) {
- return filter(value, name);
+Object.defineProperty(copy.CommonJsSource.prototype, 'get', {
+ get: function() {
+ this._require.forEach(function(moduleName) {
+ this._project.require(moduleName);
+ }, this);
+ return this._project.getCurrentModules().map(function(location) {
+ return new copy.FileSource(location, this._filters);
+ }.bind(this));
+ }
+});
+
+
+////////////////////////////////////////////////////////////////////////////////
+
+copy.filterFactory = function(filter) {
+ if (filter == null) {
+ return [];
+ }
+
+ if (typeof filter === 'function') {
+ return [ filter ];
+ }
+
+ if (Array.isArray(filter)) {
+ return filter;
+ }
+};
+
+
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Select the correct implementation of Destination for the given dest property
+ */
+copy.destFactory = function(dest, filters) {
+ if (dest == null) {
+ throw new Error('Missing dest');
+ }
+
+ if (dest.isDestination) {
+ return dest;
+ }
+
+ if (dest.value != null) {
+ return new copy.ValueDestination(dest, filters);
+ }
+
+ if (typeof dest === 'string') {
+ if (copy.isDirectory(dest)) {
+ return new copy.DirectoryDestination(dest, filters);
}
else {
- return value;
+ return new copy.FileDestination(dest, filters);
}
-}
+ }
+
+ if (Array.isArray(dest)) {
+ return new copy.ArrayDestination(dest, filters);
+ }
+
+ throw new Error('Can\'t handle type of dest: ' + typeof dest);
+};
+
+/**
+ * Abstract Destination.
+ * Concrete implementations of Destination should define the 'processSource'
+ * function.
+ */
+copy.Destination = function() {
+};
+
+copy.Destination.prototype.isDestination = true;
+
+/**
+ * @return Either another dest, an array of other sources or a string value
+ * when there is nothing else to dig into
+ */
+copy.Destination.prototype.processSource = function(source) {
+ throw new Error('Destination.processSource() is not implemented');
+};
+
+/**
+ * Helper function to convert an input source to a single string value
+ */
+copy.Destination.prototype._sourceToOutput = function(source) {
+ var data = source.get;
+
+ if (data.isSource) {
+ return this._sourceToOutput(data);
+ }
+
+ if (Array.isArray(data)) {
+ var value = '';
+ data.forEach(function(s) {
+ value += this._sourceToOutput(s);
+ }, this);
+ return value;
+ }
+
+ if (typeof data === 'string') {
+ return data;
+ }
+
+ // i.e. a Node Buffer
+ if (typeof data.toString === 'function') {
+ return data.toString();
+ }
+
+ throw new Error('Unexpected value from source.get');
+};
+
+copy.Destination.prototype._runFilters = function(value) {
+ this._filters.forEach(function(filter) {
+ if (!filter.onRead) {
+ value = filter(value);
+ }
+ }, this);
+ return value;
+};
+
+/**
+ * A Destination that concatenates the sources and writes them to a single
+ * output file.
+ */
+copy.FileDestination = function(filename, filters) {
+ this._filename = filename;
+ this._filters = filters;
+};
+
+copy.FileDestination.prototype = Object.create(copy.Destination.prototype);
+
+copy.FileDestination.prototype.processSource = function(source) {
+ var data = this._sourceToOutput(source);
+ data = this._runFilters(data);
+ copy._writeToFile(this._filename, data);
+};
+
+/**
+ * A Destination that copies the sources to new files in an alternate directory
+ * structure.
+ */
+copy.DirectoryDestination = function(dirname, filters) {
+ this.name = dirname;
+ this._filters = filters;
+};
+
+copy.DirectoryDestination.prototype = Object.create(copy.Destination.prototype);
+
+copy.DirectoryDestination.prototype.processSource = function(source) {
+ var data = source.get;
+ if (typeof data === 'string') {
+ throw new Error('Can\'t write raw data to a directory');
+ }
+ else if (data.isSource) {
+ var destfile = path.join(this.name, data.location.path);
+ var output = this._runFilters(data.get);
+ copy._writeToFile(destfile, output, data.encoding);
+ }
+ else if (Array.isArray(data)) {
+ data.forEach(function(s) {
+ var destfile = path.join(this.name, s.location.path);
+ var output = this._runFilters(s.get);
+ copy._writeToFile(destfile, output, s.encoding);
+ }, this);
+ }
+ else {
+ throw new Error('data is not a source, string, nor can it be converted');
+ }
+};
+
+/**
+ * ArrayDestination is a Destination that can feed sources to a number of child
+ * Destinations.
+ */
+copy.ArrayDestination = function(array, filters) {
+ this._array = array;
+ this._filters = filters;
+};
+
+copy.ArrayDestination.prototype = Object.create(copy.Destination.prototype);
+
+copy.ArrayDestination.prototype.processSource = function(source) {
+ this._array.forEach(function(member) {
+ var dest = copy.destFactory(member, this._filters);
+ dest.processSource(source);
+ }, this);
+};
+
+/**
+ * A Destination that concatenates the sources and writes them to a single
+ * value object.
+ */
+copy.ValueDestination = function(value, filters) {
+ this._value = value;
+ this._filters = filters;
+};
+
+copy.ValueDestination.prototype = Object.create(copy.Destination.prototype);
+
+copy.ValueDestination.prototype.processSource = function(source) {
+ var data = this._sourceToOutput(source);
+ data = this._runFilters(data);
+ this._value.value += data;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Check to see if fullPath refers to a directory
+ */
+copy.isDirectory = function(fullPath) {
+ return path.existsSync(fullPath) && fs.statSync(fullPath).isDirectory();
+};
+
+copy._writeToFile = function(filename, data, encoding) {
+ if (path.existsSync(filename)) {
+ if (!fs.statSync(filename).isFile()) {
+ throw new Error('Refusing to remove non file: ' + filename);
+ }
+ fs.unlinkSync(filename);
+ }
+ var parent = path.dirname(filename);
+ if (!path.existsSync(parent)) {
+ copy.mkdirSync(parent, 0755);
+ }
+ fs.writeFileSync(filename, data, encoding);
+ if (copy.debug) {
+ console.log('- wrote ' + data.length + ' bytes to ' + filename);
+ }
+};
+
+copy.mkdirSync = function(dirname, mode) {
+ if (copy.isDirectory(dirname)) {
+ return;
+ }
+ var parent = path.dirname(dirname);
+ if (!path.existsSync(parent)) {
+ copy.mkdirSync(parent, mode);
+ }
+ if (!path.existsSync(dirname)) {
+ fs.mkdirSync(dirname, mode);
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
/**
* A holder is an in-memory store of a result of a copy operation.
@@ -159,411 +641,299 @@ function runFilters(value, filter, reading, name) {
* </pre>
*/
copy.createDataObject = function() {
- return { value: '' };
+ return { value: '' };
};
/**
* Read mini_require.js to go with the required modules.
*/
copy.getMiniRequire = function() {
- return {
- value: fs.readFileSync(__dirname + '/mini_require.js').toString('utf8')
- };
+ return {
+ value: fs.readFileSync(__dirname + '/mini_require.js').toString('utf8')
+ };
};
/**
- * An object that contains include and exclude object
+ * Keep track of the files in a project
*/
-copy.findFiles = function(obj, findObj) {
- if (!findObj.filter) {
- findObj.filter = createFilterFromRegex(findObj);
- }
-
- if (findObj.root instanceof CommonJsProject) {
- findObj.root = findObj.root.roots;
- }
- if (Array.isArray(findObj.root)) {
- findObj.root.forEach(function(root) {
- copy.findFiles(obj, {
- root: ensureTrailingSlash(root),
- filter: findObj.filter
- });
- });
- return;
- }
-
- if (!findObj.path) {
- findObj.path = '';
- }
+function CommonJsProject(opts) {
+ this.roots = opts.roots;
+ this.textPluginPattern = opts.textPluginPattern || /^text!/;
- var path = ensureTrailingSlash(findObj.path);
- var root = ensureTrailingSlash(findObj.root);
-
- if (isDirectory(root + path)) {
- fs.readdirSync(root + path).forEach(function(entry) {
- var stat = fs.statSync(root + path + entry);
- if (stat.isFile()) {
- if (findObj.filter(path + entry)) {
- addSourceBase(obj, {
- base: root,
- path: path + entry
- });
- }
- }
- else if (stat.isDirectory()) {
- copy.findFiles(obj, {
- root: root,
- path: path + entry,
- filter: findObj.filter
- });
- }
- }, this);
+ opts.roots = this.roots.map(function(root) {
+ if (!copy.isDirectory(root)) {
+ throw new Error('Each commonjs root should be a directory: ' + root);
}
-};
+ return ensureTrailingSlash(root);
+ }, this);
-function createFilterFromRegex(obj) {
- return function(path) {
- function noPathMatch(pattern) {
- return !pattern.test(path);
- }
- if (obj.include instanceof RegExp) {
- if (noPathMatch(obj.include)) {
- return false;
- }
- }
- if (typeof obj.include === 'string') {
- if (noPathMatch(new RegExp(obj.include))) {
- return false;
- }
- }
- if (Array.isArray(obj.include)) {
- if (obj.include.every(noPathMatch)) {
- return false;
- }
- }
+ // A module is a Location that also has dep
+ this.currentModules = {};
+ this.ignoredModules = {};
+}
- function pathMatch(pattern) {
- return pattern.test(path);
- }
- if (obj.exclude instanceof RegExp) {
- if (pathMatch(obj.exclude)) {
- return false;
- }
- }
- if (typeof obj.exclude === 'string') {
- if (pathMatch(new RegExp(obj.exclude))) {
- return false;
- }
- }
- if (Array.isArray(obj.exclude)) {
- if (obj.exclude.some(pathMatch)) {
- return false;
- }
- }
+CommonJsProject.prototype.report = function() {
+ var reply = 'CommonJS project at ' + this.roots.join(', ') + '\n';
- return true;
- };
-}
+ reply += '- Required modules:\n';
+ var moduleNames = Object.keys(this.currentModules);
+ if (moduleNames.length > 0) {
+ moduleNames.forEach(function(module) {
+ var deps = Object.keys(this.currentModules[module].deps).length;
+ reply += ' - ' + module + ' (' + deps +
+ (deps === 1 ? ' dependency' : ' dependencies') + ')\n';
+ }, this);
+ }
+ else {
+ reply += ' - None\n';
+ }
+
+ reply += '- Ignored modules:\n';
+ var ignoredNames = Object.keys(this.ignoredModules);
+ if (ignoredNames.length > 0) {
+ ignoredNames.forEach(function(moduleName) {
+ reply += ' - ' + moduleName + '\n';
+ }, this);
+ }
+ else {
+ reply += ' - None\n';
+ }
-/**
- * This represents a module which has been ignored, but must still be defined so
- * that things which require it do not throw errors.
- */
-function NullModule(path) {
- this.path = path;
- this.deps = {};
-}
-NullModule.prototype.toString = function() {
- return 'define("' + this.path + '", [], void 0);\n';
+ return reply;
};
/**
- * Keep track of the files in a project
+ * Create an experimental GraphML string declaring the node dependencies.
*/
-function CommonJsProject(opts) {
- this.roots = opts.roots;
- this.ignoreRequires = opts.ignores || [];
- this.textPluginPattern = opts.textPluginPattern || /^text!/;
+CommonJsProject.prototype.getDependencyGraphML = function() {
+ var nodes = '';
+ var edges = '';
+ var moduleNames = Object.keys(this.currentModules);
+ moduleNames.forEach(function(moduleName) {
+ nodes += ' <node id="' + moduleName + '">\n';
+ nodes += ' <data key="d0">\n';
+ nodes += ' <y:ShapeNode>\n';
+ nodes += ' <y:NodeLabel textColor="#000000">' + moduleName + '</y:NodeLabel>\n';
+ nodes += ' </y:ShapeNode>\n';
+ nodes += ' </data>\n';
+ nodes += ' </node>\n';
+ var deps = Object.keys(this.currentModules[moduleName].deps);
+ deps.forEach(function(dep) {
+ edges += ' <edge source="' + moduleName + '" target="' + dep + '"/>\n';
+ });
+ }, this);
+
+ var reply = '<?xml version="1.0" encoding="UTF-8"?>\n';
+ reply += '<graphml\n';
+ reply += ' xmlns="http://graphml.graphdrawing.org/xmlns/graphml"\n';
+ reply += ' xmlns:y="http://www.yworks.com/xml/graphml"\n';
+ reply += ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\n';
+ reply += ' xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns/graphml http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd"\n';
+ reply += ' >\n';
+ reply += ' <key id="d0" for="node" yfiles.type="nodegraphics"/>\n';
+ reply += ' <key id="d1" for="edge" yfiles.type="edgegraphics"/>\n';
+ reply += ' <graph id="commonjs" edgedefault="undirected">\n';
+ reply += nodes;
+ reply += edges;
+ reply += ' </graph>\n';
+ reply += '</graphml>\n';
+ return reply;
+};
- this.currentFiles = {};
- this.ignoredFiles = {};
-}
+CommonJsProject.prototype.assumeAllFilesLoaded = function() {
+ Object.keys(this.currentModules).forEach(function(moduleName) {
+ this.ignoredModules[moduleName] = this.currentModules[moduleName];
+ }, this);
+ this.currentModules = {};
+};
-(function() {
- CommonJsProject.prototype.report = function() {
- var reply = 'CommonJS project at ' + this.roots.join(', ') + '\n';
-
- reply += '- Required modules:\n';
- var moduleNames = Object.keys(this.currentFiles);
- if (moduleNames.length > 0) {
- moduleNames.forEach(function(module) {
- var deps = Object.keys(this.currentFiles[module].deps).length;
- reply += ' - ' + module + ' (' + deps +
- (deps === 1 ? ' dependency' : ' dependencies') + ')\n';
- }, this);
- }
- else {
- reply += ' - None\n';
- }
+CommonJsProject.prototype.clone = function() {
+ var clone = new CommonJsProject({
+ roots: this.roots,
+ textPluginPattern: this.textPluginPattern
+ });
- reply += '- Ignored modules:\n';
- var ignoredNames = Object.keys(this.ignoredFiles);
- if (ignoredNames.length > 0) {
- ignoredNames.forEach(function(module) {
- reply += ' - ' + module + '\n';
- }, this);
- }
- else {
- reply += ' - None\n';
- }
+ Object.keys(this.currentModules).forEach(function(moduleName) {
+ clone.currentModules[moduleName] = this.currentModules[moduleName];
+ }, this);
- reply += '- Ignored requires:\n';
- if (this.ignoreRequires.length > 0) {
- reply += ' - ' + this.ignoreRequires.join('\n - ') + '\n';
- }
- else {
- reply += ' - None\n';
- }
+ Object.keys(this.ignoredModules).forEach(function(moduleName) {
+ clone.ignoredModules[moduleName] = this.ignoredModules[moduleName];
+ }, this);
- return reply;
- };
-
- /**
- * Create an experimental GraphML string declaring the node dependencies.
- */
- CommonJsProject.prototype.getDependencyGraphML = function() {
- var nodes = '';
- var edges = '';
- var moduleNames = Object.keys(this.currentFiles);
- moduleNames.forEach(function(module) {
- nodes += ' <node id="' + module + '">\n';
- nodes += ' <data key="d0">\n';
- nodes += ' <y:ShapeNode>\n';
- nodes += ' <y:NodeLabel textColor="#000000">' + module + '</y:NodeLabel>\n';
- nodes += ' </y:ShapeNode>\n';
- nodes += ' </data>\n';
- nodes += ' </node>\n';
- var deps = Object.keys(this.currentFiles[module].deps);
- deps.forEach(function(dep) {
- edges += ' <edge source="' + module + '" target="' + dep + '"/>\n';
- });
- }, this);
-
- var reply = '<?xml version="1.0" encoding="UTF-8"?>\n';
- reply += '<graphml\n';
- reply += ' xmlns="http://graphml.graphdrawing.org/xmlns/graphml"\n';
- reply += ' xmlns:y="http://www.yworks.com/xml/graphml"\n';
- reply += ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\n';
- reply += ' xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns/graphml http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd"\n';
- reply += ' >\n';
- reply += ' <key id="d0" for="node" yfiles.type="nodegraphics"/>\n';
- reply += ' <key id="d1" for="edge" yfiles.type="edgegraphics"/>\n';
- reply += ' <graph id="commonjs" edgedefault="undirected">\n';
- reply += nodes;
- reply += edges;
- reply += ' </graph>\n';
- reply += '</graphml>\n';
- return reply;
- };
-
- CommonJsProject.prototype.assumeAllFilesLoaded = function() {
- Object.keys(this.currentFiles).forEach(function(module) {
- this.ignoredFiles[module] = this.currentFiles[module];
- }, this);
- this.currentFiles = {};
- };
-
- CommonJsProject.prototype.clone = function() {
- var clone = new CommonJsProject({
- roots: this.roots,
- ignores: this.ignoreRequires,
- textPluginPattern: this.textPluginPattern
- });
-
- Object.keys(this.currentFiles).forEach(function(module) {
- clone.currentFiles[module] = this.currentFiles[module];
- }, this);
-
- Object.keys(this.ignoredFiles).forEach(function(module) {
- clone.ignoredFiles[module] = this.ignoredFiles[module];
- }, this);
-
- return clone;
- };
-
- CommonJsProject.prototype.addRoot = function(root) {
- this.roots.push(root);
- };
-
- function findModuleAt(baseObj, base, somePath) {
- // Checking for absolute requires and relative requires that previously
- // had been resolved to absolute paths.
- if (/^\//.test(somePath)) {
- if (isFile(somePath)) {
- baseObj = { base: '/', path: somePath };
- return baseObj;
- }
- }
+ return clone;
+};
- if (isFile(path.join(base, somePath))) {
- if (baseObj) {
- console.log('- Found several matches for ' + somePath +
- ' (ignoring 2nd)');
- console.log(' - ' + path.join(baseObj.base, baseObj.path));
- console.log(' - ' + path.join(base, somePath));
- }
- else {
- baseObj = { base: base, path: somePath };
- }
- }
+CommonJsProject.prototype.addRoot = function(root) {
+ this.roots.push(root);
+};
- return baseObj;
+function findModuleAt(module, base, somePath) {
+ if (base == null) {
+ throw new Error('base == null for ' + somePath);
+ }
+ // Checking for absolute requires and relative requires that previously
+ // had been resolved to absolute paths.
+ if (/^\//.test(somePath)) {
+ if (isFile(somePath)) {
+ console.log('Warning - using location with base = "/"');
+ return new Location('/', somePath);
+ }
+ }
+
+ if (isFile(path.join(base, somePath))) {
+ if (module) {
+ console.log('- Found several matches for ' + somePath +
+ ' (ignoring 2nd)');
+ console.log(' - ' + module.fullname);
+ console.log(' - ' + path.join(base, somePath));
+ }
+ else {
+ module = new Location(base, somePath);
}
+ }
- function relative(pathA, pathB) {
- pathA = pathA.split('/');
- pathB = pathB.split('/');
- var aLen = pathA.length;
- var bLen = pathB.length;
-
- // Increment i to the first place where the paths diverge.
- for (var i = 0;
- i < aLen && i < bLen && pathA[i] === pathB[i];
- i++)
- ;
-
- // Remove the redundant parts of the paths.
- function isntEmptyString(s) {
- return s !== '';
- }
- pathA = pathA.slice(i).filter(isntEmptyString);
- pathB = pathB.slice(i).filter(isntEmptyString);
+ return module;
+}
- var result = [];
- for (i = 0; i < pathA.length; i++) {
- result.push('..');
- }
- return result.concat(pathB).join('/');
- }
+function relative(pathA, pathB) {
+ pathA = pathA.split('/');
+ pathB = pathB.split('/');
+ var aLen = pathA.length;
+ var bLen = pathB.length;
+
+ // Increment i to the first place where the paths diverge.
+ for (var i = 0; i < aLen && i < bLen && pathA[i] === pathB[i]; i++) {
+ }
+
+ // Remove the redundant parts of the paths.
+ function isntEmptyString(s) {
+ return s !== '';
+ }
+ pathA = pathA.slice(i).filter(isntEmptyString);
+ pathB = pathB.slice(i).filter(isntEmptyString);
+
+ var result = [];
+ for (i = 0; i < pathA.length; i++) {
+ result.push('..');
+ }
+ return result.concat(pathB).join('/');
+}
- function normalizeRequire(baseObj, module) {
- if (module.indexOf("!") !== -1) {
- var chunks = module.split("!");
- return normalizeRequire(baseObj, chunks[0]) + "!" + normalizeRequire(baseObj, chunks[1]);
- }
+function normalizeRequire(module, moduleName) {
+ if (moduleName.indexOf("!") !== -1) {
+ var chunks = moduleName.split("!");
+ return normalizeRequire(module, chunks[0]) + "!" +
+ normalizeRequire(module, chunks[1]);
+ }
+
+ if (moduleName.charAt(0) == ".") {
+ var requirersDirectory = module.dirname;
+ var pathToRequiredModule = path.join(requirersDirectory, moduleName);
+ // The call to `define` which makes the module being
+ // relatively required isn't the full relative path,
+ // but the path relative from the base.
+ return relative(module.base, pathToRequiredModule);
+ }
+ else {
+ return moduleName;
+ }
+}
- if (module.charAt(0) == ".") {
- var requirersDirectory = path.dirname(path.join(baseObj.base, baseObj.path));
- var pathToRequiredModule = path.join(requirersDirectory, module);
- // The call to `define` which makes the module being
- // relatively required isn't the full relative path,
- // but the path relative from the base.
- return relative(baseObj.base, pathToRequiredModule);
+function findRequires(module) {
+ var code = fs.readFileSync(module.fullname).toString();
+ var ast;
+ try {
+ ast = ujs.parser.parse(code, false);
+ }
+ catch (ex) {
+ console.error('- Failed to compile ' + module.path + ': ' + ex);
+ return;
+ }
+
+ var reply = [];
+ var walkers = {
+ call: function(expr, args) {
+ // TODO: bug - if anyone redefines 'require' we won't notice
+ // we should maintain a list of declared variables in the
+ // current scope so we can detect this.
+ // A similar system could have us tracking calls to require
+ // via a different name. that was a useful escape system, but
+ // now we detect computed requires, it's not needed.
+ if (expr[1] === 'require') {
+ if (args[0][0] === 'string') {
+ reply.push(normalizeRequire(module, args[0][1]));
}
else {
- return module;
+ console.log('- ' + module.path + ' has require(...) ' +
+ 'with non-string parameter. Ignoring requirement.');
}
+ }
}
+ };
- function findRequires(baseObj) {
- var code = fs.readFileSync(path.join(baseObj.base, baseObj.path)).toString();
- var ast;
- try {
- ast = ujs.parser.parse(code, false);
- }
- catch (ex) {
- console.error('- Failed to compile ' + baseObj.path + ': ' + ex);
- return;
- }
-
- var reply = [];
- var walkers = {
- 'call': function(expr, args) {
- // TODO: bug - if anyone redefines 'require' we won't notice
- // we should maintain a list of declared variables in the
- // current scope so we can detect this.
- // A similar system could have us tracking calls to require
- // via a different name. that was a useful escape system, but
- // now we detect computed requires, it's not needed.
- if (expr[1] === 'require') {
- if (args[0][0] === 'string') {
- reply.push(normalizeRequire(baseObj, args[0][1]));
- }
- else {
- console.log('- ' + baseObj.path + ' has require(...) ' +
- 'with non-string parameter. Ignoring requirement.');
- }
- }
- }
- };
-
- var walker = ujs.uglify.ast_walker();
- walker.with_walkers(walkers, function() {
- return walker.walk(ast);
- });
-
- return reply;
- }
+ var walker = ujs.uglify.ast_walker();
+ walker.with_walkers(walkers, function() {
+ return walker.walk(ast);
+ });
- CommonJsProject.prototype.require = function(module) {
- var baseObj = this.currentFiles[module];
- if (baseObj) {
- return baseObj;
- }
- baseObj = this.ignoredFiles[module];
- if (baseObj) {
- return baseObj;
- }
+ return reply;
+}
- if (this.ignoreRequires.indexOf(module) > -1) {
- this.currentFiles[module] = new NullModule(module);
- return;
- }
+CommonJsProject.prototype.require = function(moduleName) {
+ var module = this.currentModules[moduleName];
+ if (module) {
+ return module;
+ }
+ module = this.ignoredModules[moduleName];
+ if (module) {
+ return module;
+ }
+
+ // Find which of the packages it is in
+ this.roots.forEach(function(base) {
+ if (this.textPluginPattern.test(moduleName)) {
+ var modulePath = moduleName.replace(this.textPluginPattern, '');
+ module = findModuleAt(module, base, modulePath);
+ if (module) {
+ module.isText = true;
+ }
+ } else {
+ module = findModuleAt(module, base, moduleName + '.js');
+ if (!module) {
+ module = findModuleAt(module, base, moduleName + '/index.js');
+ }
+ }
+ }, this);
- // Find which of the packages it is in
- this.roots.forEach(function(root) {
- var base = ensureTrailingSlash(root);
- if (this.textPluginPattern.test(module)) {
- baseObj = findModuleAt(baseObj, base, module.replace(this.textPluginPattern, ''));
- if (baseObj) {
- baseObj.isText = true;
- }
- } else {
- baseObj = findModuleAt(baseObj, base, module + '.js');
- if (!baseObj)
- baseObj = findModuleAt(baseObj, base, module + '/index.js');
- }
- }, this);
-
- if (!baseObj) {
- console.error('Failed to find module: ' + module);
- return;
- }
+ if (!module) {
+ console.error('Failed to find module: ' + moduleName);
+ return;
+ }
- var deps = baseObj.deps = {};
- this.currentFiles[module] = baseObj;
+ module.deps = {};
+ this.currentModules[moduleName] = module;
- if (!baseObj.isText) {
- // require() all this modules requirements
- findRequires(baseObj).forEach(function(module) {
- deps[module] = 1;
- this.require(module);
- }, this);
- }
- };
+ if (!module.isText) {
+ // require() all this modules requirements
+ findRequires(module).forEach(function(moduleName) {
+ module.deps[moduleName] = 1;
+ this.require(moduleName);
+ }, this);
+ }
+};
- CommonJsProject.prototype.getCurrentSources = function() {
- return Object.keys(this.currentFiles).map(function(module) {
- return this.currentFiles[module];
- }, this);
- };
-})();
+CommonJsProject.prototype.getCurrentModules = function() {
+ return Object.keys(this.currentModules).map(function(moduleName) {
+ return this.currentModules[moduleName];
+ }, this);
+};
/**
*
*/
copy.createCommonJsProject = function(opts) {
- return new CommonJsProject(opts);
+ return new CommonJsProject(opts);
};
/**
@@ -572,36 +942,12 @@ copy.createCommonJsProject = function(opts) {
copy.source = {};
/**
- *
+ * @deprecated
*/
copy.source.commonjs = function(obj) {
- if (!obj.project) {
- if (typeof obj.root !== 'string') {
- throw new Error('Expected commonjs args to have root or project.');
- }
- if (!isDirectory(obj.root)) {
- throw new Error('commonjs root is not a file: ' + obj.root);
- }
- obj.root = ensureTrailingSlash(obj.root);
- obj.project = new CommonJsProject([ obj.root ]);
- }
- else if (!obj.project instanceof CommonJsProject) {
- throw new Error('commonjs project should be a CommonJsProject');
- }
-
- if (typeof obj.require === 'string') {
- obj.require = [ obj.require ];
- }
- if (!Array.isArray(obj.require)) {
- throw new Error('Expected commonjs args to have require.');
- }
-
- return function() {
- obj.require.forEach(function(module) {
- obj.project.require(module);
- });
- return obj.project.getCurrentSources();
- };
+ console.log('copy.source.commonjs is deprecated, ' +
+ 'pass { project:... includes:...} directly as a source');
+ return obj;
};
/**
@@ -610,10 +956,9 @@ copy.source.commonjs = function(obj) {
copy.filter = {};
copy.filter.debug = function(input, source) {
- source = source || 'unknown';
- source = source.path ? source.path : source;
- console.log(source);
- return input;
+ source = source || 'unknown';
+ module = source.path ? source.path : source;
+ return input;
};
copy.filter.debug.onRead = true;
@@ -624,74 +969,72 @@ copy.filter.debug.onRead = true;
* @return string output
*/
copy.filter.uglifyjs = function(input) {
- if (typeof input !== 'string') {
- input = input.toString();
- }
+ if (typeof input !== 'string') {
+ input = input.toString();
+ }
- var opt = copy.filter.uglifyjs.options;
- var ast = ujs.parser.parse(input, opt.parse_strict_semicolons);
+ var opt = copy.filter.uglifyjs.options;
+ var ast = ujs.parser.parse(input, opt.parse_strict_semicolons);
- if (opt.mangle) {
- ast = ujs.uglify.ast_mangle(ast, opt.mangle_toplevel);
- }
+ if (opt.mangle) {
+ ast = ujs.uglify.ast_mangle(ast, opt.mangle_toplevel);
+ }
- if (opt.squeeze) {
- ast = ujs.uglify.ast_squeeze(ast, opt.squeeze_options);
- if (opt.squeeze_more) {
- ast = ujs.uglify.ast_squeeze_more(ast);
- }
+ if (opt.squeeze) {
+ ast = ujs.uglify.ast_squeeze(ast, opt.squeeze_options);
+ if (opt.squeeze_more) {
+ ast = ujs.uglify.ast_squeeze_more(ast);
}
+ }
- return ujs.uglify.gen_code(ast, opt.beautify);
+ return ujs.uglify.gen_code(ast, opt.beautify);
};
copy.filter.uglifyjs.onRead = false;
/**
* UglifyJS filter options.
*/
copy.filter.uglifyjs.options = {
- parse_strict_semicolons: false,
-
- /**
- * The beautify argument used for process.gen_code(). See the UglifyJS
- * documentation.
- */
- beautify: false,
- mangle: true,
- mangle_toplevel: false,
- squeeze: true,
-
- /**
- * The options argument used for process.ast_squeeze(). See the UglifyJS
- * documentation.
- */
- squeeze_options: {},
-
- /**
- * Tells if you want to perform potentially unsafe compression.
- */
- squeeze_more: false
+ parse_strict_semicolons: false,
+
+ /**
+ * The beautify argument used for process.gen_code(). See the UglifyJS
+ * documentation.
+ */
+ beautify: false,
+ mangle: true,
+ mangle_toplevel: false,
+ squeeze: true,
+
+ /**
+ * The options argument used for process.ast_squeeze(). See the UglifyJS
+ * documentation.
+ */
+ squeeze_options: {},
+
+ /**
+ * Tells if you want to perform potentially unsafe compression.
+ */
+ squeeze_more: false
};
/**
* A filter to munge CommonJS headers
*/
copy.filter.addDefines = function(input, source) {
- if (typeof input !== 'string') {
- input = input.toString();
- }
+ if (typeof input !== 'string') {
+ input = input.toString();
+ }
- if (!source) {
- throw new Error('Missing filename for moduleDefines');
- }
+ if (!source) {
+ throw new Error('Missing filename for moduleDefines');
+ }
- if (source.base) {
- source = source.path;
- }
+ var module = source.isLocation ? source.path : source;
- input = input.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
- input = '"' + input.replace(/\n/g, '\\n" +\n "') + '"';
+ input = input.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
+ input = '"' + input.replace(/\n/g, '\\n" +\n "') + '"';
- return 'define("text!' + source.toString() + '", [], ' + input + ');\n\n';
+ return 'define("text!' + module + '", [], ' + input + ');\n\n';
};
copy.filter.addDefines.onRead = true;
@@ -699,29 +1042,27 @@ copy.filter.addDefines.onRead = true;
* Like addDefines, but adds base64 encoding
*/
copy.filter.base64 = function(input, source) {
- if (typeof input === 'string') {
- throw new Error('base64 filter needs to be the first in a filter set');
- }
-
- if (!source) {
- throw new Error('Missing filename for moduleDefines');
- }
-
- if (source.base) {
- source = source.path;
- }
-
- if (source.substr(-4) === '.png') {
- input = 'data:image/png;base64,' + input.toString('base64');
- }
- else if (source.substr(-4) === '.gif') {
- input = 'data:image/gif;base64,' + input.toString('base64');
- }
- else {
- throw new Error('Only gif/png supported by base64 filter: ' + source);
- }
-
- return 'define("text!' + source + '", [], "' + input + '");\n\n';
+ if (typeof input === 'string') {
+ throw new Error('base64 filter needs to be the first in a filter set');
+ }
+
+ if (!source) {
+ throw new Error('Missing filename for moduleDefines');
+ }
+
+ var module = source.isLocation ? source.path : source;
+
+ if (module.substr(-4) === '.png') {
+ input = 'data:image/png;base64,' + input.toString('base64');
+ }
+ else if (module.substr(-4) === '.gif') {
+ input = 'data:image/gif;base64,' + input.toString('base64');
+ }
+ else {
+ throw new Error('Only gif/png supported by base64 filter: ' + source);
+ }
+
+ return 'define("text!' + module + '", [], "' + input + '");\n\n';
};
copy.filter.base64.onRead = true;
@@ -729,30 +1070,28 @@ copy.filter.base64.onRead = true;
* Munge define lines to add module names
*/
copy.filter.moduleDefines = function(input, source) {
- if (!source) {
- console.log('- Source without filename passed to moduleDefines().' +
- ' Skipping addition of define(...) wrapper.');
- return input;
- }
+ if (!source) {
+ console.log('- Source without filename passed to moduleDefines().' +
+ ' Skipping addition of define(...) wrapper.');
+ return input;
+ }
- if (source.isText) {
- return copy.filter.addDefines(input, source);
- }
+ if (source.isText) {
+ return copy.filter.addDefines(input, source);
+ }
- if (typeof input !== 'string') {
- input = input.toString();
- }
+ if (typeof input !== 'string') {
+ input = input.toString();
+ }
- var deps = source.deps ? Object.keys(source.deps) : [];
- deps = deps.length ? (", '" + deps.join("', '") + "'") : "";
+ var deps = source.deps ? Object.keys(source.deps) : [];
+ deps = deps.length ? (", '" + deps.join("', '") + "'") : "";
- if (source.base) {
- source = source.path;
- }
- source = source.replace(/\.js$/, '');
+ var module = source.isLocation ? source.path : source;
+ module = module.replace(/\.js$/, '');
- return input.replace(/\bdefine\s*\(\s*function\s*\(require,\s*exports,\s*module\)\s*\{/,
- "define('" + source + "', ['require', 'exports', 'module' " + deps + "], function(require, exports, module) {");
+ return input.replace(/\bdefine\s*\(\s*function\s*\(require,\s*exports,\s*module\)\s*\{/,
+ "define('" + module + "', ['require', 'exports', 'module' " + deps + "], function(require, exports, module) {");
};
copy.filter.moduleDefines.onRead = true;
@@ -761,25 +1100,17 @@ copy.filter.moduleDefines.onRead = true;
* exists()?
*/
function isFile(fullPath) {
- return path.existsSync(fullPath) && fs.statSync(fullPath).isFile();
-}
-
-/**
- * Why does node throw an exception for statSync(), especially when it has no
- * exists()?
- */
-function isDirectory(fullPath) {
- return path.existsSync(fullPath) && fs.statSync(fullPath).isDirectory();
+ return path.existsSync(fullPath) && fs.statSync(fullPath).isFile();
}
/**
* Add a trailing slash to s directory path if needed
*/
function ensureTrailingSlash(filename) {
- if (filename.length > 0 && filename.substr(-1) !== '/') {
- filename += '/';
- }
- return filename;
+ if (filename.length > 0 && filename.substr(-1) !== '/') {
+ filename += '/';
+ }
+ return filename;
}
View
4 package.json
@@ -1,8 +1,8 @@
{
"name": "dryice",
"description": "A CommonJS/RequireJS packaging tool for browser scripts",
- "keywords": [ "build", "requirejs" ],
- "version": "0.3.1",
+ "keywords": [ "build", "commonjs", "requirejs" ],
+ "version": "0.4.0",
"homepage": "https://github.com/mozilla/dryice",
"author": "Joe Walker <joe@getahead.org>",
"contributors": [ ],
Please sign in to comment.
Something went wrong with that request. Please try again.