shacl-js
Version:
TopQuadrant's SHACL API in JavaScript
1,567 lines (1,434 loc) • 1.34 MB
JavaScript
!function(e){if("object"==typeof exports)module.exports=e();else if("function"==typeof define&&define.amd)define(e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.SHACLValidator=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){
/**
* Created by antoniogarrote on 08/05/2017.
*/
var jsonld = _dereq_("jsonld");
var ValidationReport = _dereq_("./src/validation-report");
var debug = _dereq_("debug")("index");
var error = _dereq_("debug")("index::error");
var $rdf = _dereq_("rdflib");
var TermFactory = _dereq_("./src/rdfquery/term-factory");
var RDFQuery = _dereq_("./src/rdfquery");
var T = RDFQuery.T;
var ShapesGraph = _dereq_("./src/shapes-graph");
var ValidationEngine = _dereq_("./src/validation-engine");
var rdflibgraph = _dereq_("./src/rdflib-graph");
var RDFLibGraph = rdflibgraph.RDFLibGraph;
var fs = _dereq_("fs");
var ValidationEngineConfiguration = _dereq_("./src/validation-engine-configuration");
/********************************/
/* Vocabularies */
/********************************/
var vocabs = _dereq_("./src/vocabularies");
var shapesGraphURI = "urn:x-shacl:shapesGraph";
var dataGraphURI = "urn:x-shacl:dataGraph";
var shaclFile = vocabs.shacl;
var dashFile = vocabs.dash;
/********************************/
/********************************/
// List utility
var createRDFListNode = function(store, items, index) {
if (index >= items.length) {
return T("rdf:nil");
}
else {
var bnode = TermFactory.blankNode();
store.add(bnode, T("rdf:first"), items[index]);
store.add(bnode, T("rdf:rest"), createRDFListNode(store, items, index + 1));
return bnode;
}
};
// SHACL Interface
/**
* SHACL Validator.
* Main interface with the library
*/
var SHACLValidator = function() {
this.$data = new RDFLibGraph();
this.$shapes = new RDFLibGraph();
this.depth = 0;
this.results = null;
this.validationEngine = null;
this.validationError = null;
this.sequence = null;
this.shapesGraph = new ShapesGraph(this);
this.configuration = new ValidationEngineConfiguration();
this.functionsRegistry = _dereq_("./src/libraries");
};
SHACLValidator.prototype.compareNodes = function(node1, node2) {
// TODO: Does not handle the case where nodes cannot be compared
if (node1 && node2 && node1.isLiteral() && node2.isLiteral()) {
if ((node1.datatype != null) !== (node2.datatype != null)) {
return null;
} else if (node1.datatype && node2.datatype && node1.datatype.value !== node2.datatype.value) {
return null;
}
}
return RDFQuery.compareTerms(node1, node2);
};
SHACLValidator.prototype.getConfiguration = function () {
return this.configuration;
};
SHACLValidator.prototype.nodeConformsToShape = function(focusNode, shapeNode) {
var shape = this.shapesGraph.getShape(shapeNode);
try {
this.depth++;
var foundViolations = this.validationEngine.validateNodeAgainstShape(focusNode, shape, this.$data);
return !foundViolations;
}
finally {
this.depth--;
}
}
// Data graph and Shapes graph logic
SHACLValidator.prototype.parseDataGraph = function(text, mediaType, andThen) {
this.$data.clear();
this.$data.loadGraph(text, dataGraphURI, mediaType, function () {
andThen();
}, function (ex) {
error(ex);
});
};
/**
* Reloads the shapes graph.
* It will load SHACL and DASH shapes constraints.
*/
SHACLValidator.prototype.loadDataGraph = function(rdfGraph, andThen) {
this.$data.clear();
this.$data.loadMemoryGraph(dataGraphURI, rdfGraph, function () {
andThen();
}, function(ex) {
error(ex);
});
};
/**
* Validates the data graph against the shapes graph using the validation engine
*/
SHACLValidator.prototype.updateValidationEngine = function() {
results = [];
this.validationEngine = new ValidationEngine(this);
this.validationEngine.setConfiguration(this.configuration);
try {
this.validationError = null;
if (this.sequence) {
this.sequence = [];
}
this.validationEngine.validateAll(this.$data);
}
catch (ex) {
this.validationError = ex;
}
};
/**
* Checks for a validation error or results in the validation
* engine to build the RDF graph with the validation report.
* It returns a ValidationReport object wrapping the RDF graph
*/
SHACLValidator.prototype.showValidationResults = function(cb) {
if (this.validationError) {
error("Validation Failure: " + this.validationError);
throw (this.validationError);
}
else {
var resultGraph = $rdf.graph();
var reportNode = TermFactory.blankNode("report");
resultGraph.add(reportNode, T("rdf:type"), T("sh:ValidationReport"));
resultGraph.add(reportNode, T("sh:conforms"), T("" + (this.validationEngine.results.length == 0)));
var nodes = {};
for (var i = 0; i < this.validationEngine.results.length; i++) {
var result = this.validationEngine.results[i];
if (nodes[result[0].toString()] == null) {
nodes[result[0].toString()] = true;
resultGraph.add(reportNode, T("sh:result"), result[0]);
}
resultGraph.add(result[0], result[1], result[2]);
}
// Unsupported bug in JSON parser bug workaround
var oldToString = resultGraph.toString;
resultGraph.toString = function () {
var text = oldToString.call(resultGraph);
text = text.replace(/^\{/, "").replace(/\}$/, "");
return text;
};
//////////////////
jsonld.fromRDF(resultGraph.toNT(), {}, function (err, doc) {
if (err != null) {
cb(err);
} else {
jsonld.flatten(doc, function (err, result) {
if (err != null) {
cb(err);
} else {
cb(null, new ValidationReport(result));
}
});
}
});
}
};
/**
* Reloads the shapes graph.
* It will load SHACL and DASH shapes constraints.
*/
SHACLValidator.prototype.parseShapesGraph = function(text, mediaType, andThen) {
var handleError = function (ex) {
error(ex);
};
var that = this;
this.$shapes.clear();
this.$shapes.loadGraph(text, shapesGraphURI, mediaType, function () {
that.$shapes.loadGraph(shaclFile, "http://shacl.org", "text/turtle", function () {
that.$shapes.loadGraph(dashFile, "http://datashapes.org/dash", "text/turtle", function () {
andThen();
});
}, handleError);
}, handleError);
};
/**
* Reloads the shapes graph.
* It will load SHACL and DASH shapes constraints.
*/
SHACLValidator.prototype.loadShapesGraph = function(rdfGraph, andThen) {
var handleError = function (ex) {
error(ex);
};
var that = this;
this.$shapes.clear();
this.$shapes.loadMemoryGraph(shapesGraphURI, rdfGraph, function () {
that.$shapes.loadGraph(shaclFile, "http://shacl.org", "text/turtle", function () {
that.$shapes.loadGraph(dashFile, "http://datashapes.org/dash", "text/turtle", function () {
andThen();
});
}, handleError);
}, handleError);
};
// Update validations
/**
* Updates the data graph and validate it against the current data shapes
*/
SHACLValidator.prototype.updateDataGraph = function(text, mediaType, cb) {
var startTime = new Date().getTime();
this.parseDataGraph(text, mediaType, this.onDataGraphChange(startTime, cb));
};
/**
* Updates the data graph and validate it against the current data shapes
*/
SHACLValidator.prototype.updateDataGraphRdfModel = function(dataRdfGraph, cb) {
var startTime = new Date().getTime();
this.loadDataGraph(dataRdfGraph, this.onDataGraphChange(startTime, cb));
};
SHACLValidator.prototype.onDataGraphChange = function(startTime, cb) {
var that = this;
return function() {
var midTime = new Date().getTime();
that.updateValidationEngine();
var endTime = new Date().getTime();
debug("Parsing took " + (midTime - startTime) + " ms. Validating the data took " + (endTime - midTime) + " ms.");
try {
that.showValidationResults(cb);
} catch (e) {
cb(e, null);
}
}
}
/**
* Updates the shapes graph and validates it against the current data graph
*/
SHACLValidator.prototype.updateShapesGraph = function(shapes, mediaType, cb) {
var startTime = new Date().getTime();
this.parseShapesGraph(shapes, mediaType, this.onShapesGraphChange(startTime, cb));
};
/**
* Updates the shapes graph from a memory model, and validates it against the current data graph
*/
SHACLValidator.prototype.updateShapesGraphRdfModel = function(shapesRdfGraph, cb) {
var startTime = new Date().getTime();
this.loadShapesGraph(shapesRdfGraph, this.onShapesGraphChange(startTime, cb));
};
SHACLValidator.prototype.onShapesGraphChange = function(startTime, cb) {
var that = this;
return function() {
var midTime = new Date().getTime();
that.shapesGraph = new ShapesGraph(that);
var midTime2 = new Date().getTime();
that.shapesGraph.loadJSLibraries(function (err) {
if (err) {
cb(err, null);
} else {
that.updateValidationEngine();
var endTime = new Date().getTime();
debug("Parsing took " + (midTime - startTime) + " ms. Preparing the shapes took " + (midTime2 - midTime)
+ " ms. Validation the data took " + (endTime - midTime2) + " ms.");
try {
that.showValidationResults(cb);
} catch (e) {
cb(e, null);
}
}
});
}
}
/**
* Validates the provided data graph against the provided shapes graph
*/
SHACLValidator.prototype.validate = function (data, dataMediaType, shapes, shapesMediaType, cb) {
var that = this;
this.updateDataGraph(data, dataMediaType, function (e) {
if (e != null) {
cb(e, null);
} else {
that.updateShapesGraph(shapes, shapesMediaType, function (e, result) {
if (e) {
cb(e, null);
} else {
cb(null, result);
}
});
}
});
};
/**
* Validates the provided data graph against the provided shapes graph
*/
SHACLValidator.prototype.validateFromModels = function (dataRdfGraph, shapesRdfGraph, cb) {
var that = this;
this.updateDataGraphRdfModel(dataRdfGraph, function (e) {
if (e != null) {
cb(e, null);
} else {
that.updateShapesGraphRdfModel(shapesRdfGraph, function (e, result) {
if (e) {
cb(e, null);
} else {
cb(null, result);
}
});
}
});
};
/**
* Saves a cached version of a remote JS file used during validation
* @param url URL of the library to cache
* @param localFile path to a local version of the file identified by url
* @param cb invoked with an optional error when registration of the cached function has finished
*/
SHACLValidator.prototype.registerJSLibrary = function(url, localFile, cb){
var that = this;
fs.readFile(localFile, function(error, buffer) {
if (error != null) {
cb(error)
} else {
that.functionsRegistry[url] = buffer.toString();
cb(null)
}
});
};
/**
* Saves a some JS library code using the provided URL that can be used during validation
* @param url URL of the library to register
* @param libraryCode JS code for the library being registered
*/
SHACLValidator.prototype.registerJSCode = function(url, jsCode){
this.functionsRegistry[url] = jsCode;
};
// Expose the RDF interface
SHACLValidator.$rdf = $rdf;
module.exports = SHACLValidator;
},{"./src/libraries":82,"./src/rdflib-graph":83,"./src/rdfquery":84,"./src/rdfquery/term-factory":85,"./src/shapes-graph":86,"./src/validation-engine":88,"./src/validation-engine-configuration":87,"./src/validation-report":90,"./src/vocabularies":91,"debug":12,"fs":5,"jsonld":20,"rdflib":43}],2:[function(_dereq_,module,exports){
;(function () {
var object = typeof exports != 'undefined' ? exports : this; // #8: web workers
var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
function InvalidCharacterError(message) {
this.message = message;
}
InvalidCharacterError.prototype = new Error;
InvalidCharacterError.prototype.name = 'InvalidCharacterError';
// encoder
// [https://gist.github.com/999166] by [https://github.com/nignag]
object.btoa || (
object.btoa = function (input) {
for (
// initialize result and counter
var block, charCode, idx = 0, map = chars, output = '';
// if the next input index does not exist:
// change the mapping table to "="
// check if d has no fractional digits
input.charAt(idx | 0) || (map = '=', idx % 1);
// "8 - idx % 1 * 8" generates the sequence 2, 4, 6, 8
output += map.charAt(63 & block >> 8 - idx % 1 * 8)
) {
charCode = input.charCodeAt(idx += 3/4);
if (charCode > 0xFF) {
throw new InvalidCharacterError("'btoa' failed: The string to be encoded contains characters outside of the Latin1 range.");
}
block = block << 8 | charCode;
}
return output;
});
// decoder
// [https://gist.github.com/1020396] by [https://github.com/atk]
object.atob || (
object.atob = function (input) {
input = input.replace(/=+$/, '');
if (input.length % 4 == 1) {
throw new InvalidCharacterError("'atob' failed: The string to be decoded is not correctly encoded.");
}
for (
// initialize result and counters
var bc = 0, bs, buffer, idx = 0, output = '';
// get next character
buffer = input.charAt(idx++);
// character found in table? initialize bit storage and add its ascii value;
~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer,
// and if not first of each 4 characters,
// convert the first 8 bits to one ascii character
bc++ % 4) ? output += String.fromCharCode(255 & bs >> (-2 * bc & 6)) : 0
) {
// try to find character in table (0-63, not found => -1)
buffer = chars.indexOf(buffer);
}
return output;
});
}());
},{}],3:[function(_dereq_,module,exports){
(function (process){
/*!
* async
* https://github.com/caolan/async
*
* Copyright 2010-2014 Caolan McMahon
* Released under the MIT license
*/
/*jshint onevar: false, indent:4 */
/*global setImmediate: false, setTimeout: false, console: false */
(function () {
var async = {};
// global on the server, window in the browser
var root, previous_async;
root = this;
if (root != null) {
previous_async = root.async;
}
async.noConflict = function () {
root.async = previous_async;
return async;
};
function only_once(fn) {
var called = false;
return function() {
if (called) throw new Error("Callback was already called.");
called = true;
fn.apply(root, arguments);
}
}
//// cross-browser compatiblity functions ////
var _toString = Object.prototype.toString;
var _isArray = Array.isArray || function (obj) {
return _toString.call(obj) === '[object Array]';
};
var _each = function (arr, iterator) {
for (var i = 0; i < arr.length; i += 1) {
iterator(arr[i], i, arr);
}
};
var _map = function (arr, iterator) {
if (arr.map) {
return arr.map(iterator);
}
var results = [];
_each(arr, function (x, i, a) {
results.push(iterator(x, i, a));
});
return results;
};
var _reduce = function (arr, iterator, memo) {
if (arr.reduce) {
return arr.reduce(iterator, memo);
}
_each(arr, function (x, i, a) {
memo = iterator(memo, x, i, a);
});
return memo;
};
var _keys = function (obj) {
if (Object.keys) {
return Object.keys(obj);
}
var keys = [];
for (var k in obj) {
if (obj.hasOwnProperty(k)) {
keys.push(k);
}
}
return keys;
};
//// exported async module functions ////
//// nextTick implementation with browser-compatible fallback ////
if (typeof process === 'undefined' || !(process.nextTick)) {
if (typeof setImmediate === 'function') {
async.nextTick = function (fn) {
// not a direct alias for IE10 compatibility
setImmediate(fn);
};
async.setImmediate = async.nextTick;
}
else {
async.nextTick = function (fn) {
setTimeout(fn, 0);
};
async.setImmediate = async.nextTick;
}
}
else {
async.nextTick = process.nextTick;
if (typeof setImmediate !== 'undefined') {
async.setImmediate = function (fn) {
// not a direct alias for IE10 compatibility
setImmediate(fn);
};
}
else {
async.setImmediate = async.nextTick;
}
}
async.each = function (arr, iterator, callback) {
callback = callback || function () {};
if (!arr.length) {
return callback();
}
var completed = 0;
_each(arr, function (x) {
iterator(x, only_once(done) );
});
function done(err) {
if (err) {
callback(err);
callback = function () {};
}
else {
completed += 1;
if (completed >= arr.length) {
callback();
}
}
}
};
async.forEach = async.each;
async.eachSeries = function (arr, iterator, callback) {
callback = callback || function () {};
if (!arr.length) {
return callback();
}
var completed = 0;
var iterate = function () {
iterator(arr[completed], function (err) {
if (err) {
callback(err);
callback = function () {};
}
else {
completed += 1;
if (completed >= arr.length) {
callback();
}
else {
iterate();
}
}
});
};
iterate();
};
async.forEachSeries = async.eachSeries;
async.eachLimit = function (arr, limit, iterator, callback) {
var fn = _eachLimit(limit);
fn.apply(null, [arr, iterator, callback]);
};
async.forEachLimit = async.eachLimit;
var _eachLimit = function (limit) {
return function (arr, iterator, callback) {
callback = callback || function () {};
if (!arr.length || limit <= 0) {
return callback();
}
var completed = 0;
var started = 0;
var running = 0;
(function replenish () {
if (completed >= arr.length) {
return callback();
}
while (running < limit && started < arr.length) {
started += 1;
running += 1;
iterator(arr[started - 1], function (err) {
if (err) {
callback(err);
callback = function () {};
}
else {
completed += 1;
running -= 1;
if (completed >= arr.length) {
callback();
}
else {
replenish();
}
}
});
}
})();
};
};
var doParallel = function (fn) {
return function () {
var args = Array.prototype.slice.call(arguments);
return fn.apply(null, [async.each].concat(args));
};
};
var doParallelLimit = function(limit, fn) {
return function () {
var args = Array.prototype.slice.call(arguments);
return fn.apply(null, [_eachLimit(limit)].concat(args));
};
};
var doSeries = function (fn) {
return function () {
var args = Array.prototype.slice.call(arguments);
return fn.apply(null, [async.eachSeries].concat(args));
};
};
var _asyncMap = function (eachfn, arr, iterator, callback) {
arr = _map(arr, function (x, i) {
return {index: i, value: x};
});
if (!callback) {
eachfn(arr, function (x, callback) {
iterator(x.value, function (err) {
callback(err);
});
});
} else {
var results = [];
eachfn(arr, function (x, callback) {
iterator(x.value, function (err, v) {
results[x.index] = v;
callback(err);
});
}, function (err) {
callback(err, results);
});
}
};
async.map = doParallel(_asyncMap);
async.mapSeries = doSeries(_asyncMap);
async.mapLimit = function (arr, limit, iterator, callback) {
return _mapLimit(limit)(arr, iterator, callback);
};
var _mapLimit = function(limit) {
return doParallelLimit(limit, _asyncMap);
};
// reduce only has a series version, as doing reduce in parallel won't
// work in many situations.
async.reduce = function (arr, memo, iterator, callback) {
async.eachSeries(arr, function (x, callback) {
iterator(memo, x, function (err, v) {
memo = v;
callback(err);
});
}, function (err) {
callback(err, memo);
});
};
// inject alias
async.inject = async.reduce;
// foldl alias
async.foldl = async.reduce;
async.reduceRight = function (arr, memo, iterator, callback) {
var reversed = _map(arr, function (x) {
return x;
}).reverse();
async.reduce(reversed, memo, iterator, callback);
};
// foldr alias
async.foldr = async.reduceRight;
var _filter = function (eachfn, arr, iterator, callback) {
var results = [];
arr = _map(arr, function (x, i) {
return {index: i, value: x};
});
eachfn(arr, function (x, callback) {
iterator(x.value, function (v) {
if (v) {
results.push(x);
}
callback();
});
}, function (err) {
callback(_map(results.sort(function (a, b) {
return a.index - b.index;
}), function (x) {
return x.value;
}));
});
};
async.filter = doParallel(_filter);
async.filterSeries = doSeries(_filter);
// select alias
async.select = async.filter;
async.selectSeries = async.filterSeries;
var _reject = function (eachfn, arr, iterator, callback) {
var results = [];
arr = _map(arr, function (x, i) {
return {index: i, value: x};
});
eachfn(arr, function (x, callback) {
iterator(x.value, function (v) {
if (!v) {
results.push(x);
}
callback();
});
}, function (err) {
callback(_map(results.sort(function (a, b) {
return a.index - b.index;
}), function (x) {
return x.value;
}));
});
};
async.reject = doParallel(_reject);
async.rejectSeries = doSeries(_reject);
var _detect = function (eachfn, arr, iterator, main_callback) {
eachfn(arr, function (x, callback) {
iterator(x, function (result) {
if (result) {
main_callback(x);
main_callback = function () {};
}
else {
callback();
}
});
}, function (err) {
main_callback();
});
};
async.detect = doParallel(_detect);
async.detectSeries = doSeries(_detect);
async.some = function (arr, iterator, main_callback) {
async.each(arr, function (x, callback) {
iterator(x, function (v) {
if (v) {
main_callback(true);
main_callback = function () {};
}
callback();
});
}, function (err) {
main_callback(false);
});
};
// any alias
async.any = async.some;
async.every = function (arr, iterator, main_callback) {
async.each(arr, function (x, callback) {
iterator(x, function (v) {
if (!v) {
main_callback(false);
main_callback = function () {};
}
callback();
});
}, function (err) {
main_callback(true);
});
};
// all alias
async.all = async.every;
async.sortBy = function (arr, iterator, callback) {
async.map(arr, function (x, callback) {
iterator(x, function (err, criteria) {
if (err) {
callback(err);
}
else {
callback(null, {value: x, criteria: criteria});
}
});
}, function (err, results) {
if (err) {
return callback(err);
}
else {
var fn = function (left, right) {
var a = left.criteria, b = right.criteria;
return a < b ? -1 : a > b ? 1 : 0;
};
callback(null, _map(results.sort(fn), function (x) {
return x.value;
}));
}
});
};
async.auto = function (tasks, callback) {
callback = callback || function () {};
var keys = _keys(tasks);
var remainingTasks = keys.length
if (!remainingTasks) {
return callback();
}
var results = {};
var listeners = [];
var addListener = function (fn) {
listeners.unshift(fn);
};
var removeListener = function (fn) {
for (var i = 0; i < listeners.length; i += 1) {
if (listeners[i] === fn) {
listeners.splice(i, 1);
return;
}
}
};
var taskComplete = function () {
remainingTasks--
_each(listeners.slice(0), function (fn) {
fn();
});
};
addListener(function () {
if (!remainingTasks) {
var theCallback = callback;
// prevent final callback from calling itself if it errors
callback = function () {};
theCallback(null, results);
}
});
_each(keys, function (k) {
var task = _isArray(tasks[k]) ? tasks[k]: [tasks[k]];
var taskCallback = function (err) {
var args = Array.prototype.slice.call(arguments, 1);
if (args.length <= 1) {
args = args[0];
}
if (err) {
var safeResults = {};
_each(_keys(results), function(rkey) {
safeResults[rkey] = results[rkey];
});
safeResults[k] = args;
callback(err, safeResults);
// stop subsequent errors hitting callback multiple times
callback = function () {};
}
else {
results[k] = args;
async.setImmediate(taskComplete);
}
};
var requires = task.slice(0, Math.abs(task.length - 1)) || [];
var ready = function () {
return _reduce(requires, function (a, x) {
return (a && results.hasOwnProperty(x));
}, true) && !results.hasOwnProperty(k);
};
if (ready()) {
task[task.length - 1](taskCallback, results);
}
else {
var listener = function () {
if (ready()) {
removeListener(listener);
task[task.length - 1](taskCallback, results);
}
};
addListener(listener);
}
});
};
async.retry = function(times, task, callback) {
var DEFAULT_TIMES = 5;
var attempts = [];
// Use defaults if times not passed
if (typeof times === 'function') {
callback = task;
task = times;
times = DEFAULT_TIMES;
}
// Make sure times is a number
times = parseInt(times, 10) || DEFAULT_TIMES;
var wrappedTask = function(wrappedCallback, wrappedResults) {
var retryAttempt = function(task, finalAttempt) {
return function(seriesCallback) {
task(function(err, result){
seriesCallback(!err || finalAttempt, {err: err, result: result});
}, wrappedResults);
};
};
while (times) {
attempts.push(retryAttempt(task, !(times-=1)));
}
async.series(attempts, function(done, data){
data = data[data.length - 1];
(wrappedCallback || callback)(data.err, data.result);
});
}
// If a callback is passed, run this as a controll flow
return callback ? wrappedTask() : wrappedTask
};
async.waterfall = function (tasks, callback) {
callback = callback || function () {};
if (!_isArray(tasks)) {
var err = new Error('First argument to waterfall must be an array of functions');
return callback(err);
}
if (!tasks.length) {
return callback();
}
var wrapIterator = function (iterator) {
return function (err) {
if (err) {
callback.apply(null, arguments);
callback = function () {};
}
else {
var args = Array.prototype.slice.call(arguments, 1);
var next = iterator.next();
if (next) {
args.push(wrapIterator(next));
}
else {
args.push(callback);
}
async.setImmediate(function () {
iterator.apply(null, args);
});
}
};
};
wrapIterator(async.iterator(tasks))();
};
var _parallel = function(eachfn, tasks, callback) {
callback = callback || function () {};
if (_isArray(tasks)) {
eachfn.map(tasks, function (fn, callback) {
if (fn) {
fn(function (err) {
var args = Array.prototype.slice.call(arguments, 1);
if (args.length <= 1) {
args = args[0];
}
callback.call(null, err, args);
});
}
}, callback);
}
else {
var results = {};
eachfn.each(_keys(tasks), function (k, callback) {
tasks[k](function (err) {
var args = Array.prototype.slice.call(arguments, 1);
if (args.length <= 1) {
args = args[0];
}
results[k] = args;
callback(err);
});
}, function (err) {
callback(err, results);
});
}
};
async.parallel = function (tasks, callback) {
_parallel({ map: async.map, each: async.each }, tasks, callback);
};
async.parallelLimit = function(tasks, limit, callback) {
_parallel({ map: _mapLimit(limit), each: _eachLimit(limit) }, tasks, callback);
};
async.series = function (tasks, callback) {
callback = callback || function () {};
if (_isArray(tasks)) {
async.mapSeries(tasks, function (fn, callback) {
if (fn) {
fn(function (err) {
var args = Array.prototype.slice.call(arguments, 1);
if (args.length <= 1) {
args = args[0];
}
callback.call(null, err, args);
});
}
}, callback);
}
else {
var results = {};
async.eachSeries(_keys(tasks), function (k, callback) {
tasks[k](function (err) {
var args = Array.prototype.slice.call(arguments, 1);
if (args.length <= 1) {
args = args[0];
}
results[k] = args;
callback(err);
});
}, function (err) {
callback(err, results);
});
}
};
async.iterator = function (tasks) {
var makeCallback = function (index) {
var fn = function () {
if (tasks.length) {
tasks[index].apply(null, arguments);
}
return fn.next();
};
fn.next = function () {
return (index < tasks.length - 1) ? makeCallback(index + 1): null;
};
return fn;
};
return makeCallback(0);
};
async.apply = function (fn) {
var args = Array.prototype.slice.call(arguments, 1);
return function () {
return fn.apply(
null, args.concat(Array.prototype.slice.call(arguments))
);
};
};
var _concat = function (eachfn, arr, fn, callback) {
var r = [];
eachfn(arr, function (x, cb) {
fn(x, function (err, y) {
r = r.concat(y || []);
cb(err);
});
}, function (err) {
callback(err, r);
});
};
async.concat = doParallel(_concat);
async.concatSeries = doSeries(_concat);
async.whilst = function (test, iterator, callback) {
if (test()) {
iterator(function (err) {
if (err) {
return callback(err);
}
async.whilst(test, iterator, callback);
});
}
else {
callback();
}
};
async.doWhilst = function (iterator, test, callback) {
iterator(function (err) {
if (err) {
return callback(err);
}
var args = Array.prototype.slice.call(arguments, 1);
if (test.apply(null, args)) {
async.doWhilst(iterator, test, callback);
}
else {
callback();
}
});
};
async.until = function (test, iterator, callback) {
if (!test()) {
iterator(function (err) {
if (err) {
return callback(err);
}
async.until(test, iterator, callback);
});
}
else {
callback();
}
};
async.doUntil = function (iterator, test, callback) {
iterator(function (err) {
if (err) {
return callback(err);
}
var args = Array.prototype.slice.call(arguments, 1);
if (!test.apply(null, args)) {
async.doUntil(iterator, test, callback);
}
else {
callback();
}
});
};
async.queue = function (worker, concurrency) {
if (concurrency === undefined) {
concurrency = 1;
}
function _insert(q, data, pos, callback) {
if (!q.started){
q.started = true;
}
if (!_isArray(data)) {
data = [data];
}
if(data.length == 0) {
// call drain immediately if there are no tasks
return async.setImmediate(function() {
if (q.drain) {
q.drain();
}
});
}
_each(data, function(task) {
var item = {
data: task,
callback: typeof callback === 'function' ? callback : null
};
if (pos) {
q.tasks.unshift(item);
} else {
q.tasks.push(item);
}
if (q.saturated && q.tasks.length === q.concurrency) {
q.saturated();
}
async.setImmediate(q.process);
});
}
var workers = 0;
var q = {
tasks: [],
concurrency: concurrency,
saturated: null,
empty: null,
drain: null,
started: false,
paused: false,
push: function (data, callback) {
_insert(q, data, false, callback);
},
kill: function () {
q.drain = null;
q.tasks = [];
},
unshift: function (data, callback) {
_insert(q, data, true, callback);
},
process: function () {
if (!q.paused && workers < q.concurrency && q.tasks.length) {
var task = q.tasks.shift();
if (q.empty && q.tasks.length === 0) {
q.empty();
}
workers += 1;
var next = function () {
workers -= 1;
if (task.callback) {
task.callback.apply(task, arguments);
}
if (q.drain && q.tasks.length + workers === 0) {
q.drain();
}
q.process();
};
var cb = only_once(next);
worker(task.data, cb);
}
},
length: function () {
return q.tasks.length;
},
running: function () {
return workers;
},
idle: function() {
return q.tasks.length + workers === 0;
},
pause: function () {
if (q.paused === true) { return; }
q.paused = true;
},
resume: function () {
if (q.paused === false) { return; }
q.paused = false;
// Need to call q.process once per concurrent
// worker to preserve full concurrency after pause
for (var w = 1; w <= q.concurrency; w++) {
async.setImmediate(q.process);
}
}
};
return q;
};
async.priorityQueue = function (worker, concurrency) {
function _compareTasks(a, b){
return a.priority - b.priority;
};
function _binarySearch(sequence, item, compare) {
var beg = -1,
end = sequence.length - 1;
while (beg < end) {
var mid = beg + ((end - beg + 1) >>> 1);
if (compare(item, sequence[mid]) >= 0) {
beg = mid;
} else {
end = mid - 1;
}
}
return beg;
}
function _insert(q, data, priority, callback) {
if (!q.started){
q.started = true;
}
if (!_isArray(data)) {
data = [data];
}
if(data.length == 0) {
// call drain immediately if there are no tasks
return async.setImmediate(function() {
if (q.drain) {
q.drain();
}
});
}
_each(data, function(task) {
var item = {
data: task,
priority: priority,
callback: typeof callback === 'function' ? callback : null
};
q.tasks.splice(_binarySearch(q.tasks, item, _compareTasks) + 1, 0, item);
if (q.saturated && q.tasks.length === q.concurrency) {
q.saturated();
}
async.setImmediate(q.process);
});
}
// Start with a normal queue
var q = async.queue(worker, concurrency);
// Override push to accept second parameter representing priority
q.push = function (data, priority, callback) {
_insert(q, data, priority, callback);
};
// Remove unshift function
delete q.unshift;
return q;
};
async.cargo = function (worker, payload) {
var working = false,
tasks = [];
var cargo = {
tasks: tasks,
payload: payload,
saturated: null,
empty: null,
drain: null,
drained: true,
push: function (data, callback) {
if (!_isArray(data)) {
data = [data];
}
_each(data, function(task) {
tasks.push({
data: task,
callback: typeof callback === 'function' ? callback : null
});
cargo.drained = false;
if (cargo.saturated && tasks.length === payload) {
cargo.saturated();
}
});
async.setImmediate(cargo.process);
},
process: function process() {
if (working) return;
if (tasks.length === 0) {
if(cargo.drain && !cargo.drained) cargo.drain();
cargo.drained = true;
return;
}
var ts = typeof payload === 'number'
? tasks.splice(0, payload)
: tasks.splice(0, tasks.length);
var ds = _map(ts, function (task) {
return task.data;
});
if(cargo.empty) cargo.empty();
working = true;
worker(ds, function () {
working = false;
var args = arguments;
_each(ts, function (data) {
if (data.callback) {
data.callback.apply(null, args);
}
});
process();
});
},
length: function () {
return tasks.length;
},
running: function () {
return working;
}
};
return cargo;
};
var _console_fn = function (name) {
return function (fn) {
var args = Array.prototype.slice.call(arguments, 1);
fn.apply(null, args.concat([function (err) {
var args = Array.prototype.slice.call(arguments, 1);
if (typeof console !== 'undefined') {
if (err) {
if (console.error) {
console.error(err);
}
}
else if (console[name]) {
_each(args, function (x) {
console[name](x);
});
}
}
}]));
};
};
async.log = _console_fn('log');
async.dir = _console_fn('dir');
/*async.info = _console_fn('info');
async.warn = _console_fn('warn');
async.error = _console_fn('error');*/
async.memoize = function (fn, hasher) {
var memo = {};
var queues = {};
hasher = hasher || function (x) {
return x;
};
var memoized = function () {
var args = Array.prototype.slice.call(arguments);
var callback = args.pop();
var key = hasher.apply(null, args);
if (key in memo) {
async.nextTick(function () {
callback.apply(null, memo[key]);
});
}
else if (key in queues) {
queues[key].push(callback);
}
else {
queues[key] = [callback];
fn.apply(null, args.concat([function () {
memo[key] = arguments;
var q = queues[key];
delete queues[key];
for (var i = 0, l = q.length; i < l; i++) {
q[i].apply(null, arguments);
}
}]));
}
};
memoized.memo = memo;
memoized.unmemoized = fn;
return memoized;
};
async.unmemoize = function (fn) {
return function () {
return (fn.unmemoized || fn).apply(null, arguments);
};
};
async.times = function (count, iterator, callback) {
var counter = [];
for (var i = 0; i < count; i++) {
counter.push(i);
}
return async.map(counter, iterator, callback);
};
async.timesSeries = function (count, iterator, callback) {
var counter = [];
for (var i = 0; i < count; i++) {
counter.push(i);
}
return async.mapSeries(counter, iterator, callback);
};
async.seq = function (/* functions... */) {
var fns = arguments;
return function () {
var that = this;
var args = Array.prototype.slice.call(arguments);
var callback = args.pop();
async.reduce(fns, args, function (newargs, fn, cb) {
fn.apply(that, newargs.concat([function () {
var err = arguments[0];
var nextargs = Array.prototype.slice.call(arguments, 1);
cb(err, nextargs);
}]))
},
function (err, results) {
callback.apply(that, [err].concat(results));
});
};
};
async.compose = function (/* functions... */) {
return async.seq.apply(null, Array.prototype.reverse.call(arguments));
};
var _applyEach = function (eachfn, fns /*args...*/) {
var go = function () {
var that = this;
var args = Array.prototype.slice.call(arguments);
var callback = args.pop();
return eachfn(fns, function (fn, cb) {
fn.apply(that, args.concat([cb]));
},
callback);
};
if (arguments.length > 2) {
var args = Array.prototype.slice.call(arguments, 2);
return go.apply(this, args);
}
else {
return go;
}
};
async.applyEach = doParallel(_applyEach);
async.applyEachSeries = doSeries(_applyEach);
async.forever = function (fn, callback) {
function next(err) {
if (err) {
if (callback) {
return callback(err);
}
throw err;
}
fn(next);
}
next();
};
// Node.js
if (typeof module !== 'undefined' && module.exports) {
module.exports = async;
}
// AMD / RequireJS
else if (typeof define !== 'undefined' && define.amd) {
define([], function () {
return async;
});
}
// included directly via <script> tag
else {
root.async = async;
}
}());
}).call(this,_dereq_("g5I+bs"))
},{"g5I+bs":30}],4: