| // this file is the concatenation of several js files. See https://github.com/jimhigson/oboe-browser.js/tree/master/src for the unconcatenated source | |
| module.exports = (function () { | |
| var clarinet = require("clarinet"); | |
| // v1.12.3-1-g3e82471 | |
| /** | |
| * Partially complete a function. | |
| * | |
| * var add3 = partialComplete( function add(a,b){return a+b}, 3 ); | |
| * | |
| * add3(4) // gives 7 | |
| * | |
| * function wrap(left, right, cen){return left + " " + cen + " " + right;} | |
| * | |
| * var pirateGreeting = partialComplete( wrap , "I'm", ", a mighty pirate!" ); | |
| * | |
| * pirateGreeting("Guybrush Threepwood"); | |
| * // gives "I'm Guybrush Threepwood, a mighty pirate!" | |
| */ | |
| var partialComplete = varArgs(function( fn, args ) { | |
| // this isn't the shortest way to write this but it does | |
| // avoid creating a new array each time to pass to fn.apply, | |
| // otherwise could just call boundArgs.concat(callArgs) | |
| var numBoundArgs = args.length; | |
| return varArgs(function( callArgs ) { | |
| for (var i = 0; i < callArgs.length; i++) { | |
| args[numBoundArgs + i] = callArgs[i]; | |
| } | |
| args.length = numBoundArgs + callArgs.length; | |
| return fn.apply(this, args); | |
| }); | |
| }), | |
| /** | |
| * Compose zero or more functions: | |
| * | |
| * compose(f1, f2, f3)(x) = f1(f2(f3(x)))) | |
| * | |
| * The last (inner-most) function may take more than one parameter: | |
| * | |
| * compose(f1, f2, f3)(x,y) = f1(f2(f3(x,y)))) | |
| */ | |
| compose = varArgs(function(fns) { | |
| var fnsList = arrayAsList(fns); | |
| function next(params, curFn) { | |
| return [apply(params, curFn)]; | |
| } | |
| return varArgs(function(startParams){ | |
| return foldR(next, startParams, fnsList)[0]; | |
| }); | |
| }); | |
| /** | |
| * A more optimised version of compose that takes exactly two functions | |
| * @param f1 | |
| * @param f2 | |
| */ | |
| function compose2(f1, f2){ | |
| return function(){ | |
| return f1.call(this,f2.apply(this,arguments)); | |
| } | |
| } | |
| /** | |
| * Generic form for a function to get a property from an object | |
| * | |
| * var o = { | |
| * foo:'bar' | |
| * } | |
| * | |
| * var getFoo = attr('foo') | |
| * | |
| * fetFoo(o) // returns 'bar' | |
| * | |
| * @param {String} key the property name | |
| */ | |
| function attr(key) { | |
| return new Function('o', 'return o["' + key + '"]' ); | |
| } | |
| /** | |
| * Call a list of functions with the same args until one returns a | |
| * truthy result. Similar to the || operator. | |
| * | |
| * So: | |
| * lazyUnion([f1,f2,f3 ... fn])( p1, p2 ... pn ) | |
| * | |
| * Is equivalent to: | |
| * apply([p1, p2 ... pn], f1) || | |
| * apply([p1, p2 ... pn], f2) || | |
| * apply([p1, p2 ... pn], f3) ... apply(fn, [p1, p2 ... pn]) | |
| * | |
| * @returns the first return value that is given that is truthy. | |
| */ | |
| var lazyUnion = varArgs(function(fns) { | |
| return varArgs(function(params){ | |
| var maybeValue; | |
| for (var i = 0; i < len(fns); i++) { | |
| maybeValue = apply(params, fns[i]); | |
| if( maybeValue ) { | |
| return maybeValue; | |
| } | |
| } | |
| }); | |
| }); | |
| /** | |
| * This file declares various pieces of functional programming. | |
| * | |
| * This isn't a general purpose functional library, to keep things small it | |
| * has just the parts useful for Oboe.js. | |
| */ | |
| /** | |
| * Call a single function with the given arguments array. | |
| * Basically, a functional-style version of the OO-style Function#apply for | |
| * when we don't care about the context ('this') of the call. | |
| * | |
| * The order of arguments allows partial completion of the arguments array | |
| */ | |
| function apply(args, fn) { | |
| return fn.apply(undefined, args); | |
| } | |
| /** | |
| * Define variable argument functions but cut out all that tedious messing about | |
| * with the arguments object. Delivers the variable-length part of the arguments | |
| * list as an array. | |
| * | |
| * Eg: | |
| * | |
| * var myFunction = varArgs( | |
| * function( fixedArgument, otherFixedArgument, variableNumberOfArguments ){ | |
| * console.log( variableNumberOfArguments ); | |
| * } | |
| * ) | |
| * | |
| * myFunction('a', 'b', 1, 2, 3); // logs [1,2,3] | |
| * | |
| * var myOtherFunction = varArgs(function( variableNumberOfArguments ){ | |
| * console.log( variableNumberOfArguments ); | |
| * }) | |
| * | |
| * myFunction(1, 2, 3); // logs [1,2,3] | |
| * | |
| */ | |
| function varArgs(fn){ | |
| var numberOfFixedArguments = fn.length -1, | |
| slice = Array.prototype.slice; | |
| if( numberOfFixedArguments == 0 ) { | |
| // an optimised case for when there are no fixed args: | |
| return function(){ | |
| return fn.call(this, slice.call(arguments)); | |
| } | |
| } else if( numberOfFixedArguments == 1 ) { | |
| // an optimised case for when there are is one fixed args: | |
| return function(){ | |
| return fn.call(this, arguments[0], slice.call(arguments, 1)); | |
| } | |
| } | |
| // general case | |
| // we know how many arguments fn will always take. Create a | |
| // fixed-size array to hold that many, to be re-used on | |
| // every call to the returned function | |
| var argsHolder = Array(fn.length); | |
| return function(){ | |
| for (var i = 0; i < numberOfFixedArguments; i++) { | |
| argsHolder[i] = arguments[i]; | |
| } | |
| argsHolder[numberOfFixedArguments] = | |
| slice.call(arguments, numberOfFixedArguments); | |
| return fn.apply( this, argsHolder); | |
| } | |
| } | |
| /** | |
| * Swap the order of parameters to a binary function | |
| * | |
| * A bit like this flip: http://zvon.org/other/haskell/Outputprelude/flip_f.html | |
| */ | |
| function flip(fn){ | |
| return function(a, b){ | |
| return fn(b,a); | |
| } | |
| } | |
| /** | |
| * Create a function which is the intersection of two other functions. | |
| * | |
| * Like the && operator, if the first is truthy, the second is never called, | |
| * otherwise the return value from the second is returned. | |
| */ | |
| function lazyIntersection(fn1, fn2) { | |
| return function (param) { | |
| return fn1(param) && fn2(param); | |
| }; | |
| } | |
| /** | |
| * A function which does nothing | |
| */ | |
| function noop(){} | |
| /** | |
| * A function which is always happy | |
| */ | |
| function always(){return true} | |
| /** | |
| * Create a function which always returns the same | |
| * value | |
| * | |
| * var return3 = functor(3); | |
| * | |
| * return3() // gives 3 | |
| * return3() // still gives 3 | |
| * return3() // will always give 3 | |
| */ | |
| function functor(val){ | |
| return function(){ | |
| return val; | |
| } | |
| } | |
| /** | |
| * This file defines some loosely associated syntactic sugar for | |
| * Javascript programming | |
| */ | |
| /** | |
| * Returns true if the given candidate is of type T | |
| */ | |
| function isOfType(T, maybeSomething){ | |
| return maybeSomething && maybeSomething.constructor === T; | |
| } | |
| var len = attr('length'), | |
| isString = partialComplete(isOfType, String); | |
| /** | |
| * I don't like saying this: | |
| * | |
| * foo !=== undefined | |
| * | |
| * because of the double-negative. I find this: | |
| * | |
| * defined(foo) | |
| * | |
| * easier to read. | |
| */ | |
| function defined( value ) { | |
| return value !== undefined; | |
| } | |
| /** | |
| * Returns true if object o has a key named like every property in | |
| * the properties array. Will give false if any are missing, or if o | |
| * is not an object. | |
| */ | |
| function hasAllProperties(fieldList, o) { | |
| return (o instanceof Object) | |
| && | |
| all(function (field) { | |
| return (field in o); | |
| }, fieldList); | |
| } | |
| /** | |
| * Like cons in Lisp | |
| */ | |
| function cons(x, xs) { | |
| /* Internally lists are linked 2-element Javascript arrays. | |
| Ideally the return here would be Object.freeze([x,xs]) | |
| so that bugs related to mutation are found fast. | |
| However, cons is right on the critical path for | |
| performance and this slows oboe-mark down by | |
| ~25%. Under theoretical future JS engines that freeze more | |
| efficiently (possibly even use immutability to | |
| run faster) this should be considered for | |
| restoration. | |
| */ | |
| return [x,xs]; | |
| } | |
| /** | |
| * The empty list | |
| */ | |
| var emptyList = null, | |
| /** | |
| * Get the head of a list. | |
| * | |
| * Ie, head(cons(a,b)) = a | |
| */ | |
| head = attr(0), | |
| /** | |
| * Get the tail of a list. | |
| * | |
| * Ie, head(cons(a,b)) = a | |
| */ | |
| tail = attr(1); | |
| /** | |
| * Converts an array to a list | |
| * | |
| * asList([a,b,c]) | |
| * | |
| * is equivalent to: | |
| * | |
| * cons(a, cons(b, cons(c, emptyList))) | |
| **/ | |
| function arrayAsList(inputArray){ | |
| return reverseList( | |
| inputArray.reduce( | |
| flip(cons), | |
| emptyList | |
| ) | |
| ); | |
| } | |
| /** | |
| * A varargs version of arrayAsList. Works a bit like list | |
| * in LISP. | |
| * | |
| * list(a,b,c) | |
| * | |
| * is equivalent to: | |
| * | |
| * cons(a, cons(b, cons(c, emptyList))) | |
| */ | |
| var list = varArgs(arrayAsList); | |
| /** | |
| * Convert a list back to a js native array | |
| */ | |
| function listAsArray(list){ | |
| return foldR( function(arraySoFar, listItem){ | |
| arraySoFar.unshift(listItem); | |
| return arraySoFar; | |
| }, [], list ); | |
| } | |
| /** | |
| * Map a function over a list | |
| */ | |
| function map(fn, list) { | |
| return list | |
| ? cons(fn(head(list)), map(fn,tail(list))) | |
| : emptyList | |
| ; | |
| } | |
| /** | |
| * foldR implementation. Reduce a list down to a single value. | |
| * | |
| * @pram {Function} fn (rightEval, curVal) -> result | |
| */ | |
| function foldR(fn, startValue, list) { | |
| return list | |
| ? fn(foldR(fn, startValue, tail(list)), head(list)) | |
| : startValue | |
| ; | |
| } | |
| /** | |
| * foldR implementation. Reduce a list down to a single value. | |
| * | |
| * @pram {Function} fn (rightEval, curVal) -> result | |
| */ | |
| function foldR1(fn, list) { | |
| return tail(list) | |
| ? fn(foldR1(fn, tail(list)), head(list)) | |
| : head(list) | |
| ; | |
| } | |
| /** | |
| * Return a list like the one given but with the first instance equal | |
| * to item removed | |
| */ | |
| function without(list, test, removedFn) { | |
| return withoutInner(list, removedFn || noop); | |
| function withoutInner(subList, removedFn) { | |
| return subList | |
| ? ( test(head(subList)) | |
| ? (removedFn(head(subList)), tail(subList)) | |
| : cons(head(subList), withoutInner(tail(subList), removedFn)) | |
| ) | |
| : emptyList | |
| ; | |
| } | |
| } | |
| /** | |
| * Returns true if the given function holds for every item in | |
| * the list, false otherwise | |
| */ | |
| function all(fn, list) { | |
| return !list || | |
| ( fn(head(list)) && all(fn, tail(list)) ); | |
| } | |
| /** | |
| * Call every function in a list of functions with the same arguments | |
| * | |
| * This doesn't make any sense if we're doing pure functional because | |
| * it doesn't return anything. Hence, this is only really useful if the | |
| * functions being called have side-effects. | |
| */ | |
| function applyEach(fnList, arguments) { | |
| if( fnList ) { | |
| head(fnList).apply(null, arguments); | |
| applyEach(tail(fnList), arguments); | |
| } | |
| } | |
| /** | |
| * Reverse the order of a list | |
| */ | |
| function reverseList(list){ | |
| // js re-implementation of 3rd solution from: | |
| // http://www.haskell.org/haskellwiki/99_questions/Solutions/5 | |
| function reverseInner( list, reversedAlready ) { | |
| if( !list ) { | |
| return reversedAlready; | |
| } | |
| return reverseInner(tail(list), cons(head(list), reversedAlready)) | |
| } | |
| return reverseInner(list, emptyList); | |
| } | |
| function first(test, list) { | |
| return list && | |
| (test(head(list)) | |
| ? head(list) | |
| : first(test,tail(list))); | |
| } | |
| /** | |
| * A bridge used to assign stateless functions to listen to clarinet. | |
| * | |
| * As well as the parameter from clarinet, each callback will also be passed | |
| * the result of the last callback. | |
| * | |
| * This may also be used to clear all listeners by assigning zero handlers: | |
| * | |
| * clarinetListenerAdaptor( clarinet, {} ) | |
| */ | |
| function clarinetListenerAdaptor(clarinetParser, handlers){ | |
| var state; | |
| clarinet.EVENTS.forEach(function(eventName){ | |
| var handlerFunction = handlers[eventName]; | |
| clarinetParser['on'+eventName] = handlerFunction && | |
| function(param) { | |
| state = handlerFunction( state, param); | |
| }; | |
| }); | |
| } | |
| function httpTransport(){ | |
| return require('http'); | |
| } | |
| /** | |
| * A wrapper around the browser XmlHttpRequest object that raises an | |
| * event whenever a new part of the response is available. | |
| * | |
| * In older browsers progressive reading is impossible so all the | |
| * content is given in a single call. For newer ones several events | |
| * should be raised, allowing progressive interpretation of the response. | |
| * | |
| * @param {Function} oboeBus an event bus local to this Oboe instance | |
| * @param {XMLHttpRequest} http the http implementation to use as the transport. Under normal | |
| * operation, will have been created using httpTransport() above | |
| * and therefore be Node's http | |
| * but for tests a stub may be provided instead. | |
| * @param {String} method one of 'GET' 'POST' 'PUT' 'PATCH' 'DELETE' | |
| * @param {String} contentSource the url to make a request to, or a stream to read from | |
| * @param {String|Null} data some content to be sent with the request. | |
| * Only valid if method is POST or PUT. | |
| * @param {Object} [headers] the http request headers to send | |
| */ | |
| function streamingHttp(oboeBus, http, method, contentSource, data, headers) { | |
| "use strict"; | |
| function readStreamToEventBus(readableStream) { | |
| // use stream in flowing mode | |
| readableStream.on('data', function (chunk) { | |
| oboeBus(STREAM_DATA).emit( chunk.toString() ); | |
| }); | |
| readableStream.on('end', function() { | |
| oboeBus( STREAM_END ).emit(); | |
| }); | |
| } | |
| function readStreamToEnd(readableStream, callback){ | |
| var content = ''; | |
| readableStream.on('data', function (chunk) { | |
| content += chunk.toString(); | |
| }); | |
| readableStream.on('end', function() { | |
| callback( content ); | |
| }); | |
| } | |
| function fetchHttpUrl( url ) { | |
| if( !contentSource.match(/http:\/\//) ) { | |
| contentSource = 'http://' + contentSource; | |
| } | |
| var parsedUrl = require('url').parse(contentSource); | |
| var req = http.request({ | |
| hostname: parsedUrl.hostname, | |
| port: parsedUrl.port, | |
| path: parsedUrl.pathname, | |
| method: method, | |
| headers: headers | |
| }); | |
| req.on('response', function(res){ | |
| var statusCode = res.statusCode, | |
| sucessful = String(statusCode)[0] == 2; | |
| oboeBus(HTTP_START).emit( res.statusCode, res.headers); | |
| if( sucessful ) { | |
| readStreamToEventBus(res) | |
| } else { | |
| readStreamToEnd(res, function(errorBody){ | |
| oboeBus(FAIL_EVENT).emit( | |
| errorReport( statusCode, errorBody ) | |
| ); | |
| }); | |
| } | |
| }); | |
| req.on('error', function(e) { | |
| oboeBus(FAIL_EVENT).emit( | |
| errorReport(undefined, undefined, e ) | |
| ); | |
| }); | |
| oboeBus(ABORTING).on( function(){ | |
| req.abort(); | |
| }); | |
| if( data ) { | |
| req.write(data); | |
| } | |
| req.end(); | |
| } | |
| if( isString(contentSource) ) { | |
| fetchHttpUrl(contentSource); | |
| } else { | |
| // contentsource is a stream | |
| readStreamToEventBus(contentSource); | |
| } | |
| } | |
| var jsonPathSyntax = (function() { | |
| var | |
| /** | |
| * Export a regular expression as a simple function by exposing just | |
| * the Regex#exec. This allows regex tests to be used under the same | |
| * interface as differently implemented tests, or for a user of the | |
| * tests to not concern themselves with their implementation as regular | |
| * expressions. | |
| * | |
| * This could also be expressed point-free as: | |
| * Function.prototype.bind.bind(RegExp.prototype.exec), | |
| * | |
| * But that's far too confusing! (and not even smaller once minified | |
| * and gzipped) | |
| */ | |
| regexDescriptor = function regexDescriptor(regex) { | |
| return regex.exec.bind(regex); | |
| } | |
| /** | |
| * Join several regular expressions and express as a function. | |
| * This allows the token patterns to reuse component regular expressions | |
| * instead of being expressed in full using huge and confusing regular | |
| * expressions. | |
| */ | |
| , jsonPathClause = varArgs(function( componentRegexes ) { | |
| // The regular expressions all start with ^ because we | |
| // only want to find matches at the start of the | |
| // JSONPath fragment we are inspecting | |
| componentRegexes.unshift(/^/); | |
| return regexDescriptor( | |
| RegExp( | |
| componentRegexes.map(attr('source')).join('') | |
| ) | |
| ); | |
| }) | |
| , possiblyCapturing = /(\$?)/ | |
| , namedNode = /([\w-_]+|\*)/ | |
| , namePlaceholder = /()/ | |
| , nodeInArrayNotation = /\["([^"]+)"\]/ | |
| , numberedNodeInArrayNotation = /\[(\d+|\*)\]/ | |
| , fieldList = /{([\w ]*?)}/ | |
| , optionalFieldList = /(?:{([\w ]*?)})?/ | |
| // foo or * | |
| , jsonPathNamedNodeInObjectNotation = jsonPathClause( | |
| possiblyCapturing, | |
| namedNode, | |
| optionalFieldList | |
| ) | |
| // ["foo"] | |
| , jsonPathNamedNodeInArrayNotation = jsonPathClause( | |
| possiblyCapturing, | |
| nodeInArrayNotation, | |
| optionalFieldList | |
| ) | |
| // [2] or [*] | |
| , jsonPathNumberedNodeInArrayNotation = jsonPathClause( | |
| possiblyCapturing, | |
| numberedNodeInArrayNotation, | |
| optionalFieldList | |
| ) | |
| // {a b c} | |
| , jsonPathPureDuckTyping = jsonPathClause( | |
| possiblyCapturing, | |
| namePlaceholder, | |
| fieldList | |
| ) | |
| // .. | |
| , jsonPathDoubleDot = jsonPathClause(/\.\./) | |
| // . | |
| , jsonPathDot = jsonPathClause(/\./) | |
| // ! | |
| , jsonPathBang = jsonPathClause( | |
| possiblyCapturing, | |
| /!/ | |
| ) | |
| // nada! | |
| , emptyString = jsonPathClause(/$/) | |
| ; | |
| /* We export only a single function. When called, this function injects | |
| into another function the descriptors from above. | |
| */ | |
| return function (fn){ | |
| return fn( | |
| lazyUnion( | |
| jsonPathNamedNodeInObjectNotation | |
| , jsonPathNamedNodeInArrayNotation | |
| , jsonPathNumberedNodeInArrayNotation | |
| , jsonPathPureDuckTyping | |
| ) | |
| , jsonPathDoubleDot | |
| , jsonPathDot | |
| , jsonPathBang | |
| , emptyString | |
| ); | |
| }; | |
| }()); | |
| /** | |
| * Get a new key->node mapping | |
| * | |
| * @param {String|Number} key | |
| * @param {Object|Array|String|Number|null} node a value found in the json | |
| */ | |
| function namedNode(key, node) { | |
| return {key:key, node:node}; | |
| } | |
| /** get the key of a namedNode */ | |
| var keyOf = attr('key'); | |
| /** get the node from a namedNode */ | |
| var nodeOf = attr('node'); | |
| /** | |
| * This file provides various listeners which can be used to build up | |
| * a changing ascent based on the callbacks provided by Clarinet. It listens | |
| * to the low-level events from Clarinet and emits higher-level ones. | |
| * | |
| * The building up is stateless so to track a JSON file | |
| * clarinetListenerAdaptor.js is required to store the ascent state | |
| * between calls. | |
| */ | |
| /** | |
| * A special value to use in the path list to represent the path 'to' a root | |
| * object (which doesn't really have any path). This prevents the need for | |
| * special-casing detection of the root object and allows it to be treated | |
| * like any other object. We might think of this as being similar to the | |
| * 'unnamed root' domain ".", eg if I go to | |
| * http://en.wikipedia.org./wiki/En/Main_page the dot after 'org' deliminates | |
| * the unnamed root of the DNS. | |
| * | |
| * This is kept as an object to take advantage that in Javascript's OO objects | |
| * are guaranteed to be distinct, therefore no other object can possibly clash | |
| * with this one. Strings, numbers etc provide no such guarantee. | |
| **/ | |
| var ROOT_PATH = {}; | |
| /** | |
| * Create a new set of handlers for clarinet's events, bound to the emit | |
| * function given. | |
| */ | |
| function incrementalContentBuilder( oboeBus ) { | |
| var emitNodeFound = oboeBus(NODE_FOUND).emit, | |
| emitRootFound = oboeBus(ROOT_FOUND).emit, | |
| emitPathFound = oboeBus(PATH_FOUND).emit; | |
| function arrayIndicesAreKeys( possiblyInconsistentAscent, newDeepestNode) { | |
| /* for values in arrays we aren't pre-warned of the coming paths | |
| (Clarinet gives no call to onkey like it does for values in objects) | |
| so if we are in an array we need to create this path ourselves. The | |
| key will be len(parentNode) because array keys are always sequential | |
| numbers. */ | |
| var parentNode = nodeOf( head( possiblyInconsistentAscent)); | |
| return isOfType( Array, parentNode) | |
| ? | |
| pathFound( possiblyInconsistentAscent, | |
| len(parentNode), | |
| newDeepestNode | |
| ) | |
| : | |
| // nothing needed, return unchanged | |
| possiblyInconsistentAscent | |
| ; | |
| } | |
| function nodeFound( ascent, newDeepestNode ) { | |
| if( !ascent ) { | |
| // we discovered the root node, | |
| emitRootFound( newDeepestNode); | |
| return pathFound( ascent, ROOT_PATH, newDeepestNode); | |
| } | |
| // we discovered a non-root node | |
| var arrayConsistentAscent = arrayIndicesAreKeys( ascent, newDeepestNode), | |
| ancestorBranches = tail( arrayConsistentAscent), | |
| previouslyUnmappedName = keyOf( head( arrayConsistentAscent)); | |
| appendBuiltContent( | |
| ancestorBranches, | |
| previouslyUnmappedName, | |
| newDeepestNode | |
| ); | |
| return cons( | |
| namedNode( previouslyUnmappedName, newDeepestNode ), | |
| ancestorBranches | |
| ); | |
| } | |
| /** | |
| * Add a new value to the object we are building up to represent the | |
| * parsed JSON | |
| */ | |
| function appendBuiltContent( ancestorBranches, key, node ){ | |
| nodeOf( head( ancestorBranches))[key] = node; | |
| } | |
| /** | |
| * For when we find a new key in the json. | |
| * | |
| * @param {String|Number|Object} newDeepestName the key. If we are in an | |
| * array will be a number, otherwise a string. May take the special | |
| * value ROOT_PATH if the root node has just been found | |
| * | |
| * @param {String|Number|Object|Array|Null|undefined} [maybeNewDeepestNode] | |
| * usually this won't be known so can be undefined. Can't use null | |
| * to represent unknown because null is a valid value in JSON | |
| **/ | |
| function pathFound(ascent, newDeepestName, maybeNewDeepestNode) { | |
| if( ascent ) { // if not root | |
| // If we have the key but (unless adding to an array) no known value | |
| // yet. Put that key in the output but against no defined value: | |
| appendBuiltContent( ascent, newDeepestName, maybeNewDeepestNode ); | |
| } | |
| var ascentWithNewPath = cons( | |
| namedNode( newDeepestName, | |
| maybeNewDeepestNode), | |
| ascent | |
| ); | |
| emitPathFound( ascentWithNewPath); | |
| return ascentWithNewPath; | |
| } | |
| /** | |
| * For when the current node ends | |
| */ | |
| function nodeFinished( ascent ) { | |
| emitNodeFound( ascent); | |
| // pop the complete node and its path off the list: | |
| return tail( ascent); | |
| } | |
| return { | |
| openobject : function (ascent, firstKey) { | |
| var ascentAfterNodeFound = nodeFound(ascent, {}); | |
| /* It is a perculiarity of Clarinet that for non-empty objects it | |
| gives the first key with the openobject event instead of | |
| in a subsequent key event. | |
| firstKey could be the empty string in a JSON object like | |
| {'':'foo'} which is technically valid. | |
| So can't check with !firstKey, have to see if has any | |
| defined value. */ | |
| return defined(firstKey) | |
| ? | |
| /* We know the first key of the newly parsed object. Notify that | |
| path has been found but don't put firstKey permanently onto | |
| pathList yet because we haven't identified what is at that key | |
| yet. Give null as the value because we haven't seen that far | |
| into the json yet */ | |
| pathFound(ascentAfterNodeFound, firstKey) | |
| : | |
| ascentAfterNodeFound | |
| ; | |
| }, | |
| openarray: function (ascent) { | |
| return nodeFound(ascent, []); | |
| }, | |
| // called by Clarinet when keys are found in objects | |
| key: pathFound, | |
| /* Emitted by Clarinet when primitive values are found, ie Strings, | |
| Numbers, and null. | |
| Because these are always leaves in the JSON, we find and finish the | |
| node in one step, expressed as functional composition: */ | |
| value: compose2( nodeFinished, nodeFound ), | |
| // we make no distinction in how we handle object and arrays closing. | |
| // For both, interpret as the end of the current node. | |
| closeobject: nodeFinished, | |
| closearray: nodeFinished | |
| }; | |
| } | |
| /** | |
| * The jsonPath evaluator compiler used for Oboe.js. | |
| * | |
| * One function is exposed. This function takes a String JSONPath spec and | |
| * returns a function to test candidate ascents for matches. | |
| * | |
| * String jsonPath -> (List ascent) -> Boolean|Object | |
| * | |
| * This file is coded in a pure functional style. That is, no function has | |
| * side effects, every function evaluates to the same value for the same | |
| * arguments and no variables are reassigned. | |
| */ | |
| // the call to jsonPathSyntax injects the token syntaxes that are needed | |
| // inside the compiler | |
| var jsonPathCompiler = jsonPathSyntax(function (pathNodeSyntax, | |
| doubleDotSyntax, | |
| dotSyntax, | |
| bangSyntax, | |
| emptySyntax ) { | |
| var CAPTURING_INDEX = 1; | |
| var NAME_INDEX = 2; | |
| var FIELD_LIST_INDEX = 3; | |
| var headKey = compose2(keyOf, head), | |
| headNode = compose2(nodeOf, head); | |
| /** | |
| * Create an evaluator function for a named path node, expressed in the | |
| * JSONPath like: | |
| * foo | |
| * ["bar"] | |
| * [2] | |
| */ | |
| function nameClause(previousExpr, detection ) { | |
| var name = detection[NAME_INDEX], | |
| matchesName = ( !name || name == '*' ) | |
| ? always | |
| : function(ascent){return headKey(ascent) == name}; | |
| return lazyIntersection(matchesName, previousExpr); | |
| } | |
| /** | |
| * Create an evaluator function for a a duck-typed node, expressed like: | |
| * | |
| * {spin, taste, colour} | |
| * .particle{spin, taste, colour} | |
| * *{spin, taste, colour} | |
| */ | |
| function duckTypeClause(previousExpr, detection) { | |
| var fieldListStr = detection[FIELD_LIST_INDEX]; | |
| if (!fieldListStr) | |
| return previousExpr; // don't wrap at all, return given expr as-is | |
| var hasAllrequiredFields = partialComplete( | |
| hasAllProperties, | |
| arrayAsList(fieldListStr.split(/\W+/)) | |
| ), | |
| isMatch = compose2( | |
| hasAllrequiredFields, | |
| headNode | |
| ); | |
| return lazyIntersection(isMatch, previousExpr); | |
| } | |
| /** | |
| * Expression for $, returns the evaluator function | |
| */ | |
| function capture( previousExpr, detection ) { | |
| // extract meaning from the detection | |
| var capturing = !!detection[CAPTURING_INDEX]; | |
| if (!capturing) | |
| return previousExpr; // don't wrap at all, return given expr as-is | |
| return lazyIntersection(previousExpr, head); | |
| } | |
| /** | |
| * Create an evaluator function that moves onto the next item on the | |
| * lists. This function is the place where the logic to move up a | |
| * level in the ascent exists. | |
| * | |
| * Eg, for JSONPath ".foo" we need skip1(nameClause(always, [,'foo'])) | |
| */ | |
| function skip1(previousExpr) { | |
| if( previousExpr == always ) { | |
| /* If there is no previous expression this consume command | |
| is at the start of the jsonPath. | |
| Since JSONPath specifies what we'd like to find but not | |
| necessarily everything leading down to it, when running | |
| out of JSONPath to check against we default to true */ | |
| return always; | |
| } | |
| /** return true if the ascent we have contains only the JSON root, | |
| * false otherwise | |
| */ | |
| function notAtRoot(ascent){ | |
| return headKey(ascent) != ROOT_PATH; | |
| } | |
| return lazyIntersection( | |
| /* If we're already at the root but there are more | |
| expressions to satisfy, can't consume any more. No match. | |
| This check is why none of the other exprs have to be able | |
| to handle empty lists; skip1 is the only evaluator that | |
| moves onto the next token and it refuses to do so once it | |
| reaches the last item in the list. */ | |
| notAtRoot, | |
| /* We are not at the root of the ascent yet. | |
| Move to the next level of the ascent by handing only | |
| the tail to the previous expression */ | |
| compose2(previousExpr, tail) | |
| ); | |
| } | |
| /** | |
| * Create an evaluator function for the .. (double dot) token. Consumes | |
| * zero or more levels of the ascent, the fewest that are required to find | |
| * a match when given to previousExpr. | |
| */ | |
| function skipMany(previousExpr) { | |
| if( previousExpr == always ) { | |
| /* If there is no previous expression this consume command | |
| is at the start of the jsonPath. | |
| Since JSONPath specifies what we'd like to find but not | |
| necessarily everything leading down to it, when running | |
| out of JSONPath to check against we default to true */ | |
| return always; | |
| } | |
| var | |
| // In JSONPath .. is equivalent to !.. so if .. reaches the root | |
| // the match has succeeded. Ie, we might write ..foo or !..foo | |
| // and both should match identically. | |
| terminalCaseWhenArrivingAtRoot = rootExpr(), | |
| terminalCaseWhenPreviousExpressionIsSatisfied = previousExpr, | |
| recursiveCase = skip1(skipManyInner), | |
| cases = lazyUnion( | |
| terminalCaseWhenArrivingAtRoot | |
| , terminalCaseWhenPreviousExpressionIsSatisfied | |
| , recursiveCase | |
| ); | |
| function skipManyInner(ascent) { | |
| if( !ascent ) { | |
| // have gone past the start, not a match: | |
| return false; | |
| } | |
| return cases(ascent); | |
| } | |
| return skipManyInner; | |
| } | |
| /** | |
| * Generate an evaluator for ! - matches only the root element of the json | |
| * and ignores any previous expressions since nothing may precede !. | |
| */ | |
| function rootExpr() { | |
| return function(ascent){ | |
| return headKey(ascent) == ROOT_PATH; | |
| }; | |
| } | |
| /** | |
| * Generate a statement wrapper to sit around the outermost | |
| * clause evaluator. | |
| * | |
| * Handles the case where the capturing is implicit because the JSONPath | |
| * did not contain a '$' by returning the last node. | |
| */ | |
| function statementExpr(lastClause) { | |
| return function(ascent) { | |
| // kick off the evaluation by passing through to the last clause | |
| var exprMatch = lastClause(ascent); | |
| return exprMatch === true ? head(ascent) : exprMatch; | |
| }; | |
| } | |
| /** | |
| * For when a token has been found in the JSONPath input. | |
| * Compiles the parser for that token and returns in combination with the | |
| * parser already generated. | |
| * | |
| * @param {Function} exprs a list of the clause evaluator generators for | |
| * the token that was found | |
| * @param {Function} parserGeneratedSoFar the parser already found | |
| * @param {Array} detection the match given by the regex engine when | |
| * the feature was found | |
| */ | |
| function expressionsReader( exprs, parserGeneratedSoFar, detection ) { | |
| // if exprs is zero-length foldR will pass back the | |
| // parserGeneratedSoFar as-is so we don't need to treat | |
| // this as a special case | |
| return foldR( | |
| function( parserGeneratedSoFar, expr ){ | |
| return expr(parserGeneratedSoFar, detection); | |
| }, | |
| parserGeneratedSoFar, | |
| exprs | |
| ); | |
| } | |
| /** | |
| * If jsonPath matches the given detector function, creates a function which | |
| * evaluates against every clause in the clauseEvaluatorGenerators. The | |
| * created function is propagated to the onSuccess function, along with | |
| * the remaining unparsed JSONPath substring. | |
| * | |
| * The intended use is to create a clauseMatcher by filling in | |
| * the first two arguments, thus providing a function that knows | |
| * some syntax to match and what kind of generator to create if it | |
| * finds it. The parameter list once completed is: | |
| * | |
| * (jsonPath, parserGeneratedSoFar, onSuccess) | |
| * | |
| * onSuccess may be compileJsonPathToFunction, to recursively continue | |
| * parsing after finding a match or returnFoundParser to stop here. | |
| */ | |
| function generateClauseReaderIfTokenFound ( | |
| tokenDetector, clauseEvaluatorGenerators, | |
| jsonPath, parserGeneratedSoFar, onSuccess) { | |
| var detected = tokenDetector(jsonPath); | |
| if(detected) { | |
| var compiledParser = expressionsReader( | |
| clauseEvaluatorGenerators, | |
| parserGeneratedSoFar, | |
| detected | |
| ), | |
| remainingUnparsedJsonPath = jsonPath.substr(len(detected[0])); | |
| return onSuccess(remainingUnparsedJsonPath, compiledParser); | |
| } | |
| } | |
| /** | |
| * Partially completes generateClauseReaderIfTokenFound above. | |
| */ | |
| function clauseMatcher(tokenDetector, exprs) { | |
| return partialComplete( | |
| generateClauseReaderIfTokenFound, | |
| tokenDetector, | |
| exprs | |
| ); | |
| } | |
| /** | |
| * clauseForJsonPath is a function which attempts to match against | |
| * several clause matchers in order until one matches. If non match the | |
| * jsonPath expression is invalid and an error is thrown. | |
| * | |
| * The parameter list is the same as a single clauseMatcher: | |
| * | |
| * (jsonPath, parserGeneratedSoFar, onSuccess) | |
| */ | |
| var clauseForJsonPath = lazyUnion( | |
| clauseMatcher(pathNodeSyntax , list( capture, | |
| duckTypeClause, | |
| nameClause, | |
| skip1 )) | |
| , clauseMatcher(doubleDotSyntax , list( skipMany)) | |
| // dot is a separator only (like whitespace in other languages) but | |
| // rather than make it a special case, use an empty list of | |
| // expressions when this token is found | |
| , clauseMatcher(dotSyntax , list() ) | |
| , clauseMatcher(bangSyntax , list( capture, | |
| rootExpr)) | |
| , clauseMatcher(emptySyntax , list( statementExpr)) | |
| , function (jsonPath) { | |
| throw Error('"' + jsonPath + '" could not be tokenised') | |
| } | |
| ); | |
| /** | |
| * One of two possible values for the onSuccess argument of | |
| * generateClauseReaderIfTokenFound. | |
| * | |
| * When this function is used, generateClauseReaderIfTokenFound simply | |
| * returns the compiledParser that it made, regardless of if there is | |
| * any remaining jsonPath to be compiled. | |
| */ | |
| function returnFoundParser(_remainingJsonPath, compiledParser){ | |
| return compiledParser | |
| } | |
| /** | |
| * Recursively compile a JSONPath expression. | |
| * | |
| * This function serves as one of two possible values for the onSuccess | |
| * argument of generateClauseReaderIfTokenFound, meaning continue to | |
| * recursively compile. Otherwise, returnFoundParser is given and | |
| * compilation terminates. | |
| */ | |
| function compileJsonPathToFunction( uncompiledJsonPath, | |
| parserGeneratedSoFar ) { | |
| /** | |
| * On finding a match, if there is remaining text to be compiled | |
| * we want to either continue parsing using a recursive call to | |
| * compileJsonPathToFunction. Otherwise, we want to stop and return | |
| * the parser that we have found so far. | |
| */ | |
| var onFind = uncompiledJsonPath | |
| ? compileJsonPathToFunction | |
| : returnFoundParser; | |
| return clauseForJsonPath( | |
| uncompiledJsonPath, | |
| parserGeneratedSoFar, | |
| onFind | |
| ); | |
| } | |
| /** | |
| * This is the function that we expose to the rest of the library. | |
| */ | |
| return function(jsonPath){ | |
| try { | |
| // Kick off the recursive parsing of the jsonPath | |
| return compileJsonPathToFunction(jsonPath, always); | |
| } catch( e ) { | |
| throw Error( 'Could not compile "' + jsonPath + | |
| '" because ' + e.message | |
| ); | |
| } | |
| } | |
| }); | |
| /** | |
| * A pub/sub which is responsible for a single event type. A | |
| * multi-event type event bus is created by pubSub by collecting | |
| * several of these. | |
| * | |
| * @param {String} eventType | |
| * the name of the events managed by this singleEventPubSub | |
| * @param {singleEventPubSub} [newListener] | |
| * place to notify of new listeners | |
| * @param {singleEventPubSub} [removeListener] | |
| * place to notify of when listeners are removed | |
| */ | |
| function singleEventPubSub(eventType, newListener, removeListener){ | |
| /** we are optimised for emitting events over firing them. | |
| * As well as the tuple list which stores event ids and | |
| * listeners there is a list with just the listeners which | |
| * can be iterated more quickly when we are emitting | |
| */ | |
| var listenerTupleList, | |
| listenerList; | |
| function hasId(id){ | |
| return function(tuple) { | |
| return tuple.id == id; | |
| }; | |
| } | |
| return { | |
| /** | |
| * @param {Function} listener | |
| * @param {*} listenerId | |
| * an id that this listener can later by removed by. | |
| * Can be of any type, to be compared to other ids using == | |
| */ | |
| on:function( listener, listenerId ) { | |
| var tuple = { | |
| listener: listener | |
| , id: listenerId || listener // when no id is given use the | |
| // listener function as the id | |
| }; | |
| if( newListener ) { | |
| newListener.emit(eventType, listener, tuple.id); | |
| } | |
| listenerTupleList = cons( tuple, listenerTupleList ); | |
| listenerList = cons( listener, listenerList ); | |
| return this; // chaining | |
| }, | |
| emit:function () { | |
| applyEach( listenerList, arguments ); | |
| }, | |
| un: function( listenerId ) { | |
| var removed; | |
| listenerTupleList = without( | |
| listenerTupleList, | |
| hasId(listenerId), | |
| function(tuple){ | |
| removed = tuple; | |
| } | |
| ); | |
| if( removed ) { | |
| listenerList = without( listenerList, function(listener){ | |
| return listener == removed.listener; | |
| }); | |
| if( removeListener ) { | |
| removeListener.emit(eventType, removed.listener, removed.id); | |
| } | |
| } | |
| }, | |
| listeners: function(){ | |
| // differs from Node EventEmitter: returns list, not array | |
| return listenerList; | |
| }, | |
| hasListener: function(listenerId){ | |
| var test = listenerId? hasId(listenerId) : always; | |
| return defined(first( test, listenerTupleList)); | |
| } | |
| }; | |
| } | |
| /** | |
| * pubSub is a curried interface for listening to and emitting | |
| * events. | |
| * | |
| * If we get a bus: | |
| * | |
| * var bus = pubSub(); | |
| * | |
| * We can listen to event 'foo' like: | |
| * | |
| * bus('foo').on(myCallback) | |
| * | |
| * And emit event foo like: | |
| * | |
| * bus('foo').emit() | |
| * | |
| * or, with a parameter: | |
| * | |
| * bus('foo').emit('bar') | |
| * | |
| * All functions can be cached and don't need to be | |
| * bound. Ie: | |
| * | |
| * var fooEmitter = bus('foo').emit | |
| * fooEmitter('bar'); // emit an event | |
| * fooEmitter('baz'); // emit another | |
| * | |
| * There's also an uncurried[1] shortcut for .emit and .on: | |
| * | |
| * bus.on('foo', callback) | |
| * bus.emit('foo', 'bar') | |
| * | |
| * [1]: http://zvon.org/other/haskell/Outputprelude/uncurry_f.html | |
| */ | |
| function pubSub(){ | |
| var singles = {}, | |
| newListener = newSingle('newListener'), | |
| removeListener = newSingle('removeListener'); | |
| function newSingle(eventName) { | |
| return singles[eventName] = singleEventPubSub( | |
| eventName, | |
| newListener, | |
| removeListener | |
| ); | |
| } | |
| /** pubSub instances are functions */ | |
| function pubSubInstance( eventName ){ | |
| return singles[eventName] || newSingle( eventName ); | |
| } | |
| // add convenience EventEmitter-style uncurried form of 'emit' and 'on' | |
| ['emit', 'on', 'un'].forEach(function(methodName){ | |
| pubSubInstance[methodName] = varArgs(function(eventName, parameters){ | |
| apply( parameters, pubSubInstance( eventName )[methodName]); | |
| }); | |
| }); | |
| return pubSubInstance; | |
| } | |
| /** | |
| * This file declares some constants to use as names for event types. | |
| */ | |
| var // the events which are never exported are kept as | |
| // the smallest possible representation, in numbers: | |
| _S = 1, | |
| // fired whenever a node is found in the JSON: | |
| NODE_FOUND = _S++, | |
| // fired whenever a path is found in the JSON: | |
| PATH_FOUND = _S++, | |
| FAIL_EVENT = 'fail', | |
| ROOT_FOUND = _S++, | |
| HTTP_START = 'start', | |
| STREAM_DATA = 'content', | |
| STREAM_END = _S++, | |
| ABORTING = _S++; | |
| function errorReport(statusCode, body, error) { | |
| try{ | |
| var jsonBody = JSON.parse(body); | |
| }catch(e){} | |
| return { | |
| statusCode:statusCode, | |
| body:body, | |
| jsonBody:jsonBody, | |
| thrown:error | |
| }; | |
| } | |
| /** | |
| * The pattern adaptor listens for newListener and removeListener | |
| * events. When patterns are added or removed it compiles the JSONPath | |
| * and wires them up. | |
| * | |
| * When nodes and paths are found it emits the fully-qualified match | |
| * events with parameters ready to ship to the outside world | |
| */ | |
| function patternAdapter(oboeBus, jsonPathCompiler) { | |
| var predicateEventMap = { | |
| node:oboeBus(NODE_FOUND) | |
| , path:oboeBus(PATH_FOUND) | |
| }; | |
| function emitMatchingNode(emitMatch, node, ascent) { | |
| /* | |
| We're now calling to the outside world where Lisp-style | |
| lists will not be familiar. Convert to standard arrays. | |
| Also, reverse the order because it is more common to | |
| list paths "root to leaf" than "leaf to root" */ | |
| var descent = reverseList(ascent); | |
| emitMatch( | |
| node, | |
| // To make a path, strip off the last item which is the special | |
| // ROOT_PATH token for the 'path' to the root node | |
| listAsArray(tail(map(keyOf,descent))), // path | |
| listAsArray(map(nodeOf, descent)) // ancestors | |
| ); | |
| } | |
| /* | |
| * Set up the catching of events such as NODE_FOUND and PATH_FOUND and, if | |
| * matching the specified pattern, propagate to pattern-match events such as | |
| * oboeBus('node:!') | |
| * | |
| * | |
| * | |
| * @param {Function} predicateEvent | |
| * either oboeBus(NODE_FOUND) or oboeBus(PATH_FOUND). | |
| * @param {Function} compiledJsonPath | |
| */ | |
| function addUnderlyingListener( fullEventName, predicateEvent, compiledJsonPath ){ | |
| var emitMatch = oboeBus(fullEventName).emit; | |
| predicateEvent.on( function (ascent) { | |
| var maybeMatchingMapping = compiledJsonPath(ascent); | |
| /* Possible values for maybeMatchingMapping are now: | |
| false: | |
| we did not match | |
| an object/array/string/number/null: | |
| we matched and have the node that matched. | |
| Because nulls are valid json values this can be null. | |
| undefined: | |
| we matched but don't have the matching node yet. | |
| ie, we know there is an upcoming node that matches but we | |
| can't say anything else about it. | |
| */ | |
| if (maybeMatchingMapping !== false) { | |
| emitMatchingNode( | |
| emitMatch, | |
| nodeOf(maybeMatchingMapping), | |
| ascent | |
| ); | |
| } | |
| }, fullEventName); | |
| oboeBus('removeListener').on( function(removedEventName){ | |
| // if the fully qualified match event listener is later removed, clean up | |
| // by removing the underlying listener if it was the last using that pattern: | |
| if( removedEventName == fullEventName ) { | |
| if( !oboeBus(removedEventName).listeners( )) { | |
| predicateEvent.un( fullEventName ); | |
| } | |
| } | |
| }); | |
| } | |
| oboeBus('newListener').on( function(fullEventName){ | |
| var match = /(node|path):(.*)/.exec(fullEventName); | |
| if( match ) { | |
| var predicateEvent = predicateEventMap[match[1]]; | |
| if( !predicateEvent.hasListener( fullEventName) ) { | |
| addUnderlyingListener( | |
| fullEventName, | |
| predicateEvent, | |
| jsonPathCompiler( match[2] ) | |
| ); | |
| } | |
| } | |
| }) | |
| } | |
| /** | |
| * The instance API is the thing that is returned when oboe() is called. | |
| * it allows: | |
| * | |
| * - listeners for various events to be added and removed | |
| * - the http response header/headers to be read | |
| */ | |
| function instanceApi(oboeBus){ | |
| var oboeApi, | |
| fullyQualifiedNamePattern = /^(node|path):./, | |
| rootNodeFinishedEvent = oboeBus('node:!'), | |
| /** | |
| * Add any kind of listener that the instance api exposes | |
| */ | |
| addListener = varArgs(function( eventId, parameters ){ | |
| if( oboeApi[eventId] ) { | |
| // for events added as .on(event, callback), if there is a | |
| // .event() equivalent with special behaviour , pass through | |
| // to that: | |
| apply(parameters, oboeApi[eventId]); | |
| } else { | |
| // we have a standard Node.js EventEmitter 2-argument call. | |
| // The first parameter is the listener. | |
| var event = oboeBus(eventId), | |
| listener = parameters[0]; | |
| if( fullyQualifiedNamePattern.test(eventId) ) { | |
| // allow fully-qualified node/path listeners | |
| // to be added | |
| addForgettableCallback(event, listener); | |
| } else { | |
| // the event has no special handling, pass through | |
| // directly onto the event bus: | |
| event.on( listener); | |
| } | |
| } | |
| return oboeApi; // chaining | |
| }), | |
| /** | |
| * Remove any kind of listener that the instance api exposes | |
| */ | |
| removeListener = function( eventId, p2, p3 ){ | |
| if( eventId == 'done' ) { | |
| rootNodeFinishedEvent.un(p2); | |
| } else if( eventId == 'node' || eventId == 'path' ) { | |
| // allow removal of node and path | |
| oboeBus.un(eventId + ':' + p2, p3); | |
| } else { | |
| // we have a standard Node.js EventEmitter 2-argument call. | |
| // The second parameter is the listener. This may be a call | |
| // to remove a fully-qualified node/path listener but requires | |
| // no special handling | |
| var listener = p2; | |
| oboeBus(eventId).un(listener); | |
| } | |
| return oboeApi; // chaining | |
| }; | |
| /** | |
| * Add a callback, wrapped in a try/catch so as to not break the | |
| * execution of Oboe if an exception is thrown (fail events are | |
| * fired instead) | |
| * | |
| * The callback is used as the listener id so that it can later be | |
| * removed using .un(callback) | |
| */ | |
| function addProtectedCallback(eventName, callback) { | |
| oboeBus(eventName).on(protectedCallback(callback), callback); | |
| return oboeApi; // chaining | |
| } | |
| /** | |
| * Add a callback where, if .forget() is called during the callback's | |
| * execution, the callback will be de-registered | |
| */ | |
| function addForgettableCallback(event, callback) { | |
| var safeCallback = protectedCallback(callback); | |
| event.on( function() { | |
| var discard = false; | |
| oboeApi.forget = function(){ | |
| discard = true; | |
| }; | |
| apply( arguments, safeCallback ); | |
| delete oboeApi.forget; | |
| if( discard ) { | |
| event.un(callback); | |
| } | |
| }, callback) | |
| return oboeApi; // chaining | |
| } | |
| function protectedCallback( callback ) { | |
| return function() { | |
| try{ | |
| callback.apply(oboeApi, arguments); | |
| }catch(e) { | |
| // An error occured during the callback, publish it on the event bus | |
| oboeBus(FAIL_EVENT).emit( errorReport(undefined, undefined, e)); | |
| } | |
| } | |
| } | |
| /** | |
| * Return the fully qualified event for when a pattern matches | |
| * either a node or a path | |
| * | |
| * @param type {String} either 'node' or 'path' | |
| */ | |
| function fullyQualifiedPatternMatchEvent(type, pattern) { | |
| return oboeBus(type + ':' + pattern); | |
| } | |
| /** | |
| * Add several listeners at a time, from a map | |
| */ | |
| function addListenersMap(eventId, listenerMap) { | |
| for( var pattern in listenerMap ) { | |
| addForgettableCallback( | |
| fullyQualifiedPatternMatchEvent(eventId, pattern), | |
| listenerMap[pattern] | |
| ); | |
| } | |
| } | |
| /** | |
| * implementation behind .onPath() and .onNode() | |
| */ | |
| function addNodeOrPathListenerApi( eventId, jsonPathOrListenerMap, callback ){ | |
| if( isString(jsonPathOrListenerMap) ) { | |
| addForgettableCallback( | |
| fullyQualifiedPatternMatchEvent(eventId, jsonPathOrListenerMap), | |
| callback | |
| ); | |
| } else { | |
| addListenersMap(eventId, jsonPathOrListenerMap); | |
| } | |
| return oboeApi; // chaining | |
| } | |
| // some interface methods are only filled in after we recieve | |
| // values and are noops before that: | |
| oboeBus(ROOT_FOUND).on( function(root) { | |
| oboeApi.root = functor(root); | |
| }); | |
| /** | |
| * When content starts make the headers readable through the | |
| * instance API | |
| */ | |
| oboeBus(HTTP_START).on( function(_statusCode, headers) { | |
| oboeApi.header = function(name) { | |
| return name ? headers[name] | |
| : headers | |
| ; | |
| } | |
| }); | |
| /** | |
| * Construct and return the public API of the Oboe instance to be | |
| * returned to the calling application | |
| */ | |
| return oboeApi = { | |
| on : addListener, | |
| addListener : addListener, | |
| removeListener : removeListener, | |
| emit : oboeBus.emit, | |
| node : partialComplete(addNodeOrPathListenerApi, 'node'), | |
| path : partialComplete(addNodeOrPathListenerApi, 'path'), | |
| done : partialComplete(addForgettableCallback, rootNodeFinishedEvent), | |
| start : partialComplete(addProtectedCallback, HTTP_START ), | |
| // fail doesn't use protectedCallback because | |
| // could lead to non-terminating loops | |
| fail : oboeBus(FAIL_EVENT).on, | |
| // public api calling abort fires the ABORTING event | |
| abort : oboeBus(ABORTING).emit, | |
| // initially return nothing for header and root | |
| header : noop, | |
| root : noop | |
| }; | |
| } | |
| /** | |
| * This file implements a light-touch central controller for an instance | |
| * of Oboe which provides the methods used for interacting with the instance | |
| * from the calling app. | |
| */ | |
| function instanceController( oboeBus, | |
| clarinetParser, contentBuilderHandlers) { | |
| oboeBus(STREAM_DATA).on( clarinetParser.write.bind(clarinetParser)); | |
| /* At the end of the http content close the clarinet parser. | |
| This will provide an error if the total content provided was not | |
| valid json, ie if not all arrays, objects and Strings closed properly */ | |
| oboeBus(STREAM_END).on( clarinetParser.close.bind(clarinetParser)); | |
| /* If we abort this Oboe's request stop listening to the clarinet parser. | |
| This prevents more tokens being found after we abort in the case where | |
| we aborted during processing of an already filled buffer. */ | |
| oboeBus(ABORTING).on( function() { | |
| clarinetListenerAdaptor(clarinetParser, {}); | |
| }); | |
| clarinetListenerAdaptor(clarinetParser, contentBuilderHandlers); | |
| // react to errors by putting them on the event bus | |
| clarinetParser.onerror = function(e) { | |
| oboeBus(FAIL_EVENT).emit( | |
| errorReport(undefined, undefined, e) | |
| ); | |
| // note: don't close clarinet here because if it was not expecting | |
| // end of the json it will throw an error | |
| }; | |
| } | |
| /** | |
| * This file sits just behind the API which is used to attain a new | |
| * Oboe instance. It creates the new components that are required | |
| * and introduces them to each other. | |
| */ | |
| function wire (httpMethodName, contentSource, body, headers){ | |
| var oboeBus = pubSub(); | |
| headers = headers ? | |
| // Shallow-clone the headers array. This allows it to be | |
| // modified without side effects to the caller. We don't | |
| // want to change objects that the user passes in. | |
| JSON.parse(JSON.stringify(headers)) | |
| : {}; | |
| if( body ) { | |
| if( !isString(body) ) { | |
| // If the body is not a string, stringify it. This allows objects to | |
| // be given which will be sent as JSON. | |
| body = JSON.stringify(body); | |
| // Default Content-Type to JSON unless given otherwise. | |
| headers['Content-Type'] = headers['Content-Type'] || 'application/json'; | |
| } | |
| } else { | |
| body = null; | |
| } | |
| // Wire the input stream in if we are given a content source. | |
| // This will usually be the case. If not, the instance created | |
| // will have to be passed content from an external source. | |
| if( contentSource ) { | |
| streamingHttp( oboeBus, | |
| httpTransport(), | |
| httpMethodName, | |
| contentSource, | |
| body, | |
| headers | |
| ); | |
| } | |
| instanceController( | |
| oboeBus, | |
| clarinet.parser(), | |
| incrementalContentBuilder(oboeBus) | |
| ); | |
| patternAdapter(oboeBus, jsonPathCompiler); | |
| return new instanceApi(oboeBus); | |
| } | |
| // export public API | |
| function oboe(arg1, arg2) { | |
| if( arg1 ) { | |
| if (arg1.url) { | |
| // method signature is: | |
| // oboe({method:m, url:u, body:b, headers:{...}}) | |
| return wire( | |
| (arg1.method || 'GET'), | |
| url(arg1.url, arg1.cached), | |
| arg1.body, | |
| arg1.headers | |
| ); | |
| } else { | |
| // simple version for GETs. Signature is: | |
| // oboe( url ) | |
| // | |
| return wire( | |
| 'GET', | |
| arg1, // url | |
| arg2 // body. Deprecated, use {url:u, body:b} instead | |
| ); | |
| } | |
| } else { | |
| // wire up a no-AJAX Oboe. Will have to have content | |
| // fed in externally and using .emit. | |
| return wire(); | |
| } | |
| // support cache busting like jQuery.ajax({cache:false}) | |
| function url(baseUrl, cached) { | |
| if( cached === false ) { | |
| if( baseUrl.indexOf('?') == -1 ) { | |
| baseUrl += '?'; | |
| } else { | |
| baseUrl += '&'; | |
| } | |
| baseUrl += '_=' + new Date().getTime(); | |
| } | |
| return baseUrl; | |
| } | |
| } | |
| ;return oboe;})(); |