1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704 |
- // This file is the concatenation of many js files.
- // See http://github.com/jimhigson/oboe.js for the raw source
-
- // having a local undefined, window, Object etc allows slightly better minification:
- (function (window, Object, Array, Error, JSON, undefined ) {
-
- // v2.1.1-1-gb70a959
-
- /*
-
- Copyright (c) 2013, Jim Higson
-
- All rights reserved.
-
- Redistribution and use in source and binary forms, with or without
- modification, are permitted provided that the following conditions are
- met:
-
- 1. Redistributions of source code must retain the above copyright
- notice, this list of conditions and the following disclaimer.
-
- 2. Redistributions in binary form must reproduce the above copyright
- notice, this list of conditions and the following disclaimer in the
- documentation and/or other materials provided with the distribution.
-
- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
- IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
- TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
- PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
- TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
- PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
- LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
- NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
- */
-
- /**
- * 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 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, args) {
-
- if( fnList ) {
- head(fnList).apply(null, args);
-
- applyEach(tail(fnList), args);
- }
- }
-
- /**
- * 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)));
- }
-
- /*
- This is a slightly hacked-up browser only version of clarinet
-
- * some features removed to help keep browser Oboe under
- the 5k micro-library limit
- * plug directly into event bus
-
- For the original go here:
- https://github.com/dscape/clarinet
-
- We receive the events:
- STREAM_DATA
- STREAM_END
-
- We emit the events:
- SAX_KEY
- SAX_VALUE_OPEN
- SAX_VALUE_CLOSE
- FAIL_EVENT
- */
-
- function clarinet(eventBus) {
- "use strict";
-
- var
- // shortcut some events on the bus
- emitSaxKey = eventBus(SAX_KEY).emit,
- emitValueOpen = eventBus(SAX_VALUE_OPEN).emit,
- emitValueClose = eventBus(SAX_VALUE_CLOSE).emit,
- emitFail = eventBus(FAIL_EVENT).emit,
-
- MAX_BUFFER_LENGTH = 64 * 1024
- , stringTokenPattern = /[\\"\n]/g
- , _n = 0
-
- // states
- , BEGIN = _n++
- , VALUE = _n++ // general stuff
- , OPEN_OBJECT = _n++ // {
- , CLOSE_OBJECT = _n++ // }
- , OPEN_ARRAY = _n++ // [
- , CLOSE_ARRAY = _n++ // ]
- , STRING = _n++ // ""
- , OPEN_KEY = _n++ // , "a"
- , CLOSE_KEY = _n++ // :
- , TRUE = _n++ // r
- , TRUE2 = _n++ // u
- , TRUE3 = _n++ // e
- , FALSE = _n++ // a
- , FALSE2 = _n++ // l
- , FALSE3 = _n++ // s
- , FALSE4 = _n++ // e
- , NULL = _n++ // u
- , NULL2 = _n++ // l
- , NULL3 = _n++ // l
- , NUMBER_DECIMAL_POINT = _n++ // .
- , NUMBER_DIGIT = _n // [0-9]
-
- // setup initial parser values
- , bufferCheckPosition = MAX_BUFFER_LENGTH
- , latestError
- , c
- , p
- , textNode = ""
- , numberNode = ""
- , slashed = false
- , closed = false
- , state = BEGIN
- , stack = []
- , unicodeS = null
- , unicodeI = 0
- , depth = 0
- , position = 0
- , column = 0 //mostly for error reporting
- , line = 1
- ;
-
- function checkBufferLength () {
-
- var maxActual = 0;
-
- if (textNode.length > MAX_BUFFER_LENGTH) {
- emitError("Max buffer length exceeded: textNode");
- maxActual = Math.max(maxActual, textNode.length);
- }
- if (numberNode.length > MAX_BUFFER_LENGTH) {
- emitError("Max buffer length exceeded: numberNode");
- maxActual = Math.max(maxActual, numberNode.length);
- }
-
- bufferCheckPosition = (MAX_BUFFER_LENGTH - maxActual)
- + position;
- }
-
- eventBus(STREAM_DATA).on(handleData);
-
- /* At the end of the http content close the clarinet
- This will provide an error if the total content provided was not
- valid json, ie if not all arrays, objects and Strings closed properly */
- eventBus(STREAM_END).on(handleStreamEnd);
-
- function emitError (errorString) {
- if (textNode) {
- emitValueOpen(textNode);
- emitValueClose();
- textNode = "";
- }
-
- latestError = Error(errorString + "\nLn: "+line+
- "\nCol: "+column+
- "\nChr: "+c);
-
- emitFail(errorReport(undefined, undefined, latestError));
- }
-
- function handleStreamEnd() {
- if( state == BEGIN ) {
- // Handle the case where the stream closes without ever receiving
- // any input. This isn't an error - response bodies can be blank,
- // particularly for 204 http responses
-
- // Because of how Oboe is currently implemented, we parse a
- // completely empty stream as containing an empty object.
- // This is because Oboe's done event is only fired when the
- // root object of the JSON stream closes.
-
- // This should be decoupled and attached instead to the input stream
- // from the http (or whatever) resource ending.
- // If this decoupling could happen the SAX parser could simply emit
- // zero events on a completely empty input.
- emitValueOpen({});
- emitValueClose();
-
- closed = true;
- return;
- }
-
- if (state !== VALUE || depth !== 0)
- emitError("Unexpected end");
-
- if (textNode) {
- emitValueOpen(textNode);
- emitValueClose();
- textNode = "";
- }
-
- closed = true;
- }
-
- function whitespace(c){
- return c == '\r' || c == '\n' || c == ' ' || c == '\t';
- }
-
- function handleData (chunk) {
-
- // this used to throw the error but inside Oboe we will have already
- // gotten the error when it was emitted. The important thing is to
- // not continue with the parse.
- if (latestError)
- return;
-
- if (closed) {
- return emitError("Cannot write after close");
- }
-
- var i = 0;
- c = chunk[0];
-
- while (c) {
- p = c;
- c = chunk[i++];
- if(!c) break;
-
- position ++;
- if (c == "\n") {
- line ++;
- column = 0;
- } else column ++;
- switch (state) {
-
- case BEGIN:
- if (c === "{") state = OPEN_OBJECT;
- else if (c === "[") state = OPEN_ARRAY;
- else if (!whitespace(c))
- return emitError("Non-whitespace before {[.");
- continue;
-
- case OPEN_KEY:
- case OPEN_OBJECT:
- if (whitespace(c)) continue;
- if(state === OPEN_KEY) stack.push(CLOSE_KEY);
- else {
- if(c === '}') {
- emitValueOpen({});
- emitValueClose();
- state = stack.pop() || VALUE;
- continue;
- } else stack.push(CLOSE_OBJECT);
- }
- if(c === '"')
- state = STRING;
- else
- return emitError("Malformed object key should start with \" ");
- continue;
-
- case CLOSE_KEY:
- case CLOSE_OBJECT:
- if (whitespace(c)) continue;
-
- if(c===':') {
- if(state === CLOSE_OBJECT) {
- stack.push(CLOSE_OBJECT);
-
- if (textNode) {
- // was previously (in upstream Clarinet) one event
- // - object open came with the text of the first
- emitValueOpen({});
- emitSaxKey(textNode);
- textNode = "";
- }
- depth++;
- } else {
- if (textNode) {
- emitSaxKey(textNode);
- textNode = "";
- }
- }
- state = VALUE;
- } else if (c==='}') {
- if (textNode) {
- emitValueOpen(textNode);
- emitValueClose();
- textNode = "";
- }
- emitValueClose();
- depth--;
- state = stack.pop() || VALUE;
- } else if(c===',') {
- if(state === CLOSE_OBJECT)
- stack.push(CLOSE_OBJECT);
- if (textNode) {
- emitValueOpen(textNode);
- emitValueClose();
- textNode = "";
- }
- state = OPEN_KEY;
- } else
- return emitError('Bad object');
- continue;
-
- case OPEN_ARRAY: // after an array there always a value
- case VALUE:
- if (whitespace(c)) continue;
- if(state===OPEN_ARRAY) {
- emitValueOpen([]);
- depth++;
- state = VALUE;
- if(c === ']') {
- emitValueClose();
- depth--;
- state = stack.pop() || VALUE;
- continue;
- } else {
- stack.push(CLOSE_ARRAY);
- }
- }
- if(c === '"') state = STRING;
- else if(c === '{') state = OPEN_OBJECT;
- else if(c === '[') state = OPEN_ARRAY;
- else if(c === 't') state = TRUE;
- else if(c === 'f') state = FALSE;
- else if(c === 'n') state = NULL;
- else if(c === '-') { // keep and continue
- numberNode += c;
- } else if(c==='0') {
- numberNode += c;
- state = NUMBER_DIGIT;
- } else if('123456789'.indexOf(c) !== -1) {
- numberNode += c;
- state = NUMBER_DIGIT;
- } else
- return emitError("Bad value");
- continue;
-
- case CLOSE_ARRAY:
- if(c===',') {
- stack.push(CLOSE_ARRAY);
- if (textNode) {
- emitValueOpen(textNode);
- emitValueClose();
- textNode = "";
- }
- state = VALUE;
- } else if (c===']') {
- if (textNode) {
- emitValueOpen(textNode);
- emitValueClose();
- textNode = "";
- }
- emitValueClose();
- depth--;
- state = stack.pop() || VALUE;
- } else if (whitespace(c))
- continue;
- else
- return emitError('Bad array');
- continue;
-
- case STRING:
- // thanks thejh, this is an about 50% performance improvement.
- var starti = i-1;
-
- STRING_BIGLOOP: while (true) {
-
- // zero means "no unicode active". 1-4 mean "parse some more". end after 4.
- while (unicodeI > 0) {
- unicodeS += c;
- c = chunk.charAt(i++);
- if (unicodeI === 4) {
- // TODO this might be slow? well, probably not used too often anyway
- textNode += String.fromCharCode(parseInt(unicodeS, 16));
- unicodeI = 0;
- starti = i-1;
- } else {
- unicodeI++;
- }
- // we can just break here: no stuff we skipped that still has to be sliced out or so
- if (!c) break STRING_BIGLOOP;
- }
- if (c === '"' && !slashed) {
- state = stack.pop() || VALUE;
- textNode += chunk.substring(starti, i-1);
- if(!textNode) {
- emitValueOpen("");
- emitValueClose();
- }
- break;
- }
- if (c === '\\' && !slashed) {
- slashed = true;
- textNode += chunk.substring(starti, i-1);
- c = chunk.charAt(i++);
- if (!c) break;
- }
- if (slashed) {
- slashed = false;
- if (c === 'n') { textNode += '\n'; }
- else if (c === 'r') { textNode += '\r'; }
- else if (c === 't') { textNode += '\t'; }
- else if (c === 'f') { textNode += '\f'; }
- else if (c === 'b') { textNode += '\b'; }
- else if (c === 'u') {
- // \uxxxx. meh!
- unicodeI = 1;
- unicodeS = '';
- } else {
- textNode += c;
- }
- c = chunk.charAt(i++);
- starti = i-1;
- if (!c) break;
- else continue;
- }
-
- stringTokenPattern.lastIndex = i;
- var reResult = stringTokenPattern.exec(chunk);
- if (!reResult) {
- i = chunk.length+1;
- textNode += chunk.substring(starti, i-1);
- break;
- }
- i = reResult.index+1;
- c = chunk.charAt(reResult.index);
- if (!c) {
- textNode += chunk.substring(starti, i-1);
- break;
- }
- }
- continue;
-
- case TRUE:
- if (!c) continue; // strange buffers
- if (c==='r') state = TRUE2;
- else
- return emitError( 'Invalid true started with t'+ c);
- continue;
-
- case TRUE2:
- if (!c) continue;
- if (c==='u') state = TRUE3;
- else
- return emitError('Invalid true started with tr'+ c);
- continue;
-
- case TRUE3:
- if (!c) continue;
- if(c==='e') {
- emitValueOpen(true);
- emitValueClose();
- state = stack.pop() || VALUE;
- } else
- return emitError('Invalid true started with tru'+ c);
- continue;
-
- case FALSE:
- if (!c) continue;
- if (c==='a') state = FALSE2;
- else
- return emitError('Invalid false started with f'+ c);
- continue;
-
- case FALSE2:
- if (!c) continue;
- if (c==='l') state = FALSE3;
- else
- return emitError('Invalid false started with fa'+ c);
- continue;
-
- case FALSE3:
- if (!c) continue;
- if (c==='s') state = FALSE4;
- else
- return emitError('Invalid false started with fal'+ c);
- continue;
-
- case FALSE4:
- if (!c) continue;
- if (c==='e') {
- emitValueOpen(false);
- emitValueClose();
- state = stack.pop() || VALUE;
- } else
- return emitError('Invalid false started with fals'+ c);
- continue;
-
- case NULL:
- if (!c) continue;
- if (c==='u') state = NULL2;
- else
- return emitError('Invalid null started with n'+ c);
- continue;
-
- case NULL2:
- if (!c) continue;
- if (c==='l') state = NULL3;
- else
- return emitError('Invalid null started with nu'+ c);
- continue;
-
- case NULL3:
- if (!c) continue;
- if(c==='l') {
- emitValueOpen(null);
- emitValueClose();
- state = stack.pop() || VALUE;
- } else
- return emitError('Invalid null started with nul'+ c);
- continue;
-
- case NUMBER_DECIMAL_POINT:
- if(c==='.') {
- numberNode += c;
- state = NUMBER_DIGIT;
- } else
- return emitError('Leading zero not followed by .');
- continue;
-
- case NUMBER_DIGIT:
- if('0123456789'.indexOf(c) !== -1) numberNode += c;
- else if (c==='.') {
- if(numberNode.indexOf('.')!==-1)
- return emitError('Invalid number has two dots');
- numberNode += c;
- } else if (c==='e' || c==='E') {
- if(numberNode.indexOf('e')!==-1 ||
- numberNode.indexOf('E')!==-1 )
- return emitError('Invalid number has two exponential');
- numberNode += c;
- } else if (c==="+" || c==="-") {
- if(!(p==='e' || p==='E'))
- return emitError('Invalid symbol in number');
- numberNode += c;
- } else {
- if (numberNode) {
- emitValueOpen(parseFloat(numberNode));
- emitValueClose();
- numberNode = "";
- }
- i--; // go back one
- state = stack.pop() || VALUE;
- }
- continue;
-
- default:
- return emitError("Unknown state: " + state);
- }
- }
- if (position >= bufferCheckPosition)
- checkBufferLength();
- }
- }
-
-
- /**
- * 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:
- *
- * ascentManager( clarinet, {} )
- */
- function ascentManager(oboeBus, handlers){
- "use strict";
-
- var listenerId = {},
- ascent;
-
- function stateAfter(handler) {
- return function(param){
- ascent = handler( ascent, param);
- }
- }
-
- for( var eventName in handlers ) {
-
- oboeBus(eventName).on(stateAfter(handlers[eventName]), listenerId);
- }
-
- oboeBus(NODE_SWAP).on(function(newNode) {
-
- var oldHead = head(ascent),
- key = keyOf(oldHead),
- ancestors = tail(ascent),
- parentNode;
-
- if( ancestors ) {
- parentNode = nodeOf(head(ancestors));
- parentNode[key] = newNode;
- }
- });
-
- oboeBus(NODE_DROP).on(function() {
-
- var oldHead = head(ascent),
- key = keyOf(oldHead),
- ancestors = tail(ascent),
- parentNode;
-
- if( ancestors ) {
- parentNode = nodeOf(head(ancestors));
-
- delete parentNode[key];
- }
- });
-
- oboeBus(ABORTING).on(function(){
-
- for( var eventName in handlers ) {
- oboeBus(eventName).un(listenerId);
- }
- });
- }
-
- // based on gist https://gist.github.com/monsur/706839
-
- /**
- * XmlHttpRequest's getAllResponseHeaders() method returns a string of response
- * headers according to the format described here:
- * http://www.w3.org/TR/XMLHttpRequest/#the-getallresponseheaders-method
- * This method parses that string into a user-friendly key/value pair object.
- */
- function parseResponseHeaders(headerStr) {
- var headers = {};
-
- headerStr && headerStr.split('\u000d\u000a')
- .forEach(function(headerPair){
-
- // Can't use split() here because it does the wrong thing
- // if the header value has the string ": " in it.
- var index = headerPair.indexOf('\u003a\u0020');
-
- headers[headerPair.substring(0, index)]
- = headerPair.substring(index + 2);
- });
-
- return headers;
- }
-
- /**
- * Detect if a given URL is cross-origin in the scope of the
- * current page.
- *
- * Browser only (since cross-origin has no meaning in Node.js)
- *
- * @param {Object} pageLocation - as in window.location
- * @param {Object} ajaxHost - an object like window.location describing the
- * origin of the url that we want to ajax in
- */
- function isCrossOrigin(pageLocation, ajaxHost) {
-
- /*
- * NB: defaultPort only knows http and https.
- * Returns undefined otherwise.
- */
- function defaultPort(protocol) {
- return {'http:':80, 'https:':443}[protocol];
- }
-
- function portOf(location) {
- // pageLocation should always have a protocol. ajaxHost if no port or
- // protocol is specified, should use the port of the containing page
-
- return location.port || defaultPort(location.protocol||pageLocation.protocol);
- }
-
- // if ajaxHost doesn't give a domain, port is the same as pageLocation
- // it can't give a protocol but not a domain
- // it can't give a port but not a domain
-
- return !!( (ajaxHost.protocol && (ajaxHost.protocol != pageLocation.protocol)) ||
- (ajaxHost.host && (ajaxHost.host != pageLocation.host)) ||
- (ajaxHost.host && (portOf(ajaxHost) != portOf(pageLocation)))
- );
- }
-
- /* turn any url into an object like window.location */
- function parseUrlOrigin(url) {
- // url could be domain-relative
- // url could give a domain
-
- // cross origin means:
- // same domain
- // same port
- // some protocol
- // so, same everything up to the first (single) slash
- // if such is given
- //
- // can ignore everything after that
-
- var URL_HOST_PATTERN = /(\w+:)?(?:\/\/)([\w.-]+)?(?::(\d+))?\/?/,
-
- // if no match, use an empty array so that
- // subexpressions 1,2,3 are all undefined
- // and will ultimately return all empty
- // strings as the parse result:
- urlHostMatch = URL_HOST_PATTERN.exec(url) || [];
-
- return {
- protocol: urlHostMatch[1] || '',
- host: urlHostMatch[2] || '',
- port: urlHostMatch[3] || ''
- };
- }
-
- function httpTransport(){
- return new XMLHttpRequest();
- }
-
- /**
- * 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} xhr the xhr to use as the transport. Under normal
- * operation, will have been created using httpTransport() above
- * but for tests a stub can be provided instead.
- * @param {String} method one of 'GET' 'POST' 'PUT' 'PATCH' 'DELETE'
- * @param {String} url the url to make a request to
- * @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
- * @param {boolean} withCredentials the XHR withCredentials property will be
- * set to this value
- */
- function streamingHttp(oboeBus, xhr, method, url, data, headers, withCredentials) {
-
- "use strict";
-
- var emitStreamData = oboeBus(STREAM_DATA).emit,
- emitFail = oboeBus(FAIL_EVENT).emit,
- numberOfCharsAlreadyGivenToCallback = 0,
- stillToSendStartEvent = true;
-
- // When an ABORTING message is put on the event bus abort
- // the ajax request
- oboeBus( ABORTING ).on( function(){
-
- // if we keep the onreadystatechange while aborting the XHR gives
- // a callback like a successful call so first remove this listener
- // by assigning null:
- xhr.onreadystatechange = null;
-
- xhr.abort();
- });
-
- /**
- * Handle input from the underlying xhr: either a state change,
- * the progress event or the request being complete.
- */
- function handleProgress() {
-
- var textSoFar = xhr.responseText,
- newText = textSoFar.substr(numberOfCharsAlreadyGivenToCallback);
-
-
- /* Raise the event for new text.
-
- On older browsers, the new text is the whole response.
- On newer/better ones, the fragment part that we got since
- last progress. */
-
- if( newText ) {
- emitStreamData( newText );
- }
-
- numberOfCharsAlreadyGivenToCallback = len(textSoFar);
- }
-
-
- if('onprogress' in xhr){ // detect browser support for progressive delivery
- xhr.onprogress = handleProgress;
- }
-
- xhr.onreadystatechange = function() {
-
- function sendStartIfNotAlready() {
- // Internet Explorer is very unreliable as to when xhr.status etc can
- // be read so has to be protected with try/catch and tried again on
- // the next readyState if it fails
- try{
- stillToSendStartEvent && oboeBus( HTTP_START ).emit(
- xhr.status,
- parseResponseHeaders(xhr.getAllResponseHeaders()) );
- stillToSendStartEvent = false;
- } catch(e){/* do nothing, will try again on next readyState*/}
- }
-
- switch( xhr.readyState ) {
-
- case 2: // HEADERS_RECEIVED
- case 3: // LOADING
- return sendStartIfNotAlready();
-
- case 4: // DONE
- sendStartIfNotAlready(); // if xhr.status hasn't been available yet, it must be NOW, huh IE?
-
- // is this a 2xx http code?
- var successful = String(xhr.status)[0] == 2;
-
- if( successful ) {
- // In Chrome 29 (not 28) no onprogress is emitted when a response
- // is complete before the onload. We need to always do handleInput
- // in case we get the load but have not had a final progress event.
- // This looks like a bug and may change in future but let's take
- // the safest approach and assume we might not have received a
- // progress event for each part of the response
- handleProgress();
-
- oboeBus(STREAM_END).emit();
- } else {
-
- emitFail( errorReport(
- xhr.status,
- xhr.responseText
- ));
- }
- }
- };
-
- try{
-
- xhr.open(method, url, true);
-
- for( var headerName in headers ){
- xhr.setRequestHeader(headerName, headers[headerName]);
- }
-
- if( !isCrossOrigin(window.location, parseUrlOrigin(url)) ) {
- xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
- }
-
- xhr.withCredentials = withCredentials;
-
- xhr.send(data);
-
- } catch( e ) {
-
- // To keep a consistent interface with Node, we can't emit an event here.
- // Node's streaming http adaptor receives the error as an asynchronous
- // event rather than as an exception. If we emitted now, the Oboe user
- // has had no chance to add a .fail listener so there is no way
- // the event could be useful. For both these reasons defer the
- // firing to the next JS frame.
- window.setTimeout(
- partialComplete(emitFail, errorReport(undefined, undefined, e))
- , 0
- );
- }
- }
-
- 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
- * ascentManager.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 emitNodeOpened = oboeBus(NODE_OPENED).emit,
- emitNodeClosed = oboeBus(NODE_CLOSED).emit,
- emitRootOpened = oboeBus(ROOT_PATH_FOUND).emit,
- emitRootClosed = oboeBus(ROOT_NODE_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)
- ?
- keyFound( possiblyInconsistentAscent,
- len(parentNode),
- newDeepestNode
- )
- :
- // nothing needed, return unchanged
- possiblyInconsistentAscent
- ;
- }
-
- function nodeOpened( ascent, newDeepestNode ) {
-
- if( !ascent ) {
- // we discovered the root node,
- emitRootOpened( newDeepestNode);
-
- return keyFound( 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 keyFound(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
- );
-
- emitNodeOpened( ascentWithNewPath);
-
- return ascentWithNewPath;
- }
-
-
- /**
- * For when the current node ends.
- */
- function nodeClosed( ascent ) {
-
- emitNodeClosed( ascent);
-
- return tail( ascent) ||
- // If there are no nodes left in the ascent the root node
- // just closed. Emit a special event for this:
- emitRootClosed(nodeOf(head(ascent)));
- }
-
- var contentBuilderHandlers = {};
- contentBuilderHandlers[SAX_VALUE_OPEN] = nodeOpened;
- contentBuilderHandlers[SAX_VALUE_CLOSE] = nodeClosed;
- contentBuilderHandlers[SAX_KEY] = keyFound;
- return contentBuilderHandlers;
- }
-
- /**
- * 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(function(ascent) {
- return cases(ascent);
- }),
-
- cases = lazyUnion(
- terminalCaseWhenArrivingAtRoot
- , terminalCaseWhenPreviousExpressionIsSatisfied
- , recursiveCase
- );
-
- return cases;
- }
-
- /**
- * 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 new node starts in the JSON stream:
- NODE_OPENED = _S++,
-
- // fired whenever a node closes in the JSON stream:
- NODE_CLOSED = _S++,
-
- // called if a .node callback returns a value -
- NODE_SWAP = _S++,
- NODE_DROP = _S++,
-
- FAIL_EVENT = 'fail',
-
- ROOT_NODE_FOUND = _S++,
- ROOT_PATH_FOUND = _S++,
-
- HTTP_START = 'start',
- STREAM_DATA = 'data',
- STREAM_END = 'end',
- ABORTING = _S++,
-
- // SAX events butchered from Clarinet
- SAX_KEY = _S++,
- SAX_VALUE_OPEN = _S++,
- SAX_VALUE_CLOSE = _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_CLOSED)
- , path:oboeBus(NODE_OPENED)
- };
-
- 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_CLOSED and NODE_OPENED and, if
- * matching the specified pattern, propagate to pattern-match events such as
- * oboeBus('node:!')
- *
- *
- *
- * @param {Function} predicateEvent
- * either oboeBus(NODE_CLOSED) or oboeBus(NODE_OPENED).
- * @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, contentSource){
-
- var oboeApi,
- fullyQualifiedNamePattern = /^(node|path):./,
- rootNodeFinishedEvent = oboeBus(ROOT_NODE_FOUND),
- emitNodeDrop = oboeBus(NODE_DROP).emit,
- emitNodeSwap = oboeBus(NODE_SWAP).emit,
-
- /**
- * 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, listenerId) {
-
- // listenerId is optional and if not given, the original
- // callback will be used
- listenerId = listenerId || 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(listenerId);
- }
- }, listenerId);
-
- return oboeApi; // chaining
- }
-
- /**
- * wrap a callback so that if it throws, Oboe.js doesn't crash but instead
- * handles it like a normal error
- */
- function protectedCallback( callback ) {
- return function() {
- try{
- return 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);
- }
-
- function wrapCallbackToSwapNodeIfSomethingReturned( callback ) {
- return function() {
- var returnValueFromCallback = callback.apply(this, arguments);
-
- if( defined(returnValueFromCallback) ) {
-
- if( returnValueFromCallback == oboe.drop ) {
- emitNodeDrop();
- } else {
- emitNodeSwap(returnValueFromCallback);
- }
- }
- }
- }
-
- function addSingleNodeOrPathListener(eventId, pattern, callback) {
-
- var effectiveCallback;
-
- if( eventId == 'node' ) {
- effectiveCallback = wrapCallbackToSwapNodeIfSomethingReturned(callback);
- } else {
- effectiveCallback = callback;
- }
-
- addForgettableCallback(
- fullyQualifiedPatternMatchEvent(eventId, pattern),
- effectiveCallback,
- callback
- );
- }
-
- /**
- * Add several listeners at a time, from a map
- */
- function addMultipleNodeOrPathListeners(eventId, listenerMap) {
-
- for( var pattern in listenerMap ) {
- addSingleNodeOrPathListener(eventId, pattern, listenerMap[pattern]);
- }
- }
-
- /**
- * implementation behind .onPath() and .onNode()
- */
- function addNodeOrPathListenerApi( eventId, jsonPathOrListenerMap, callback ){
-
- if( isString(jsonPathOrListenerMap) ) {
- addSingleNodeOrPathListener(eventId, jsonPathOrListenerMap, callback);
-
- } else {
- addMultipleNodeOrPathListeners(eventId, jsonPathOrListenerMap);
- }
-
- return oboeApi; // chaining
- }
-
-
- // some interface methods are only filled in after we receive
- // values and are noops before that:
- oboeBus(ROOT_PATH_FOUND).on( function(rootNode) {
- oboeApi.root = functor(rootNode);
- });
-
- /**
- * 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,
-
- source : contentSource
- };
- }
-
-
- /**
- * 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, withCredentials){
-
- var oboeBus = pubSub();
-
- // 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,
- withCredentials
- );
- }
-
- clarinet(oboeBus);
-
- ascentManager(oboeBus, incrementalContentBuilder(oboeBus));
-
- patternAdapter(oboeBus, jsonPathCompiler);
-
- return instanceApi(oboeBus, contentSource);
- }
-
- function applyDefaults( passthrough, url, httpMethodName, body, headers, withCredentials, cached ){
-
- 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;
- }
-
- // support cache busting like jQuery.ajax({cache:false})
- function modifiedUrl(baseUrl, cached) {
-
- if( cached === false ) {
-
- if( baseUrl.indexOf('?') == -1 ) {
- baseUrl += '?';
- } else {
- baseUrl += '&';
- }
-
- baseUrl += '_=' + new Date().getTime();
- }
- return baseUrl;
- }
-
- return passthrough( httpMethodName || 'GET', modifiedUrl(url, cached), body, headers, withCredentials || false );
- }
-
- // export public API
- function oboe(arg1) {
-
- // We use duck-typing to detect if the parameter given is a stream, with the
- // below list of parameters.
- // Unpipe and unshift would normally be present on a stream but this breaks
- // compatibility with Request streams.
- // See https://github.com/jimhigson/oboe.js/issues/65
-
- var nodeStreamMethodNames = list('resume', 'pause', 'pipe'),
- isStream = partialComplete(
- hasAllProperties
- , nodeStreamMethodNames
- );
-
- if( arg1 ) {
- if (isStream(arg1) || isString(arg1)) {
-
- // simple version for GETs. Signature is:
- // oboe( url )
- // or, under node:
- // oboe( readableStream )
- return applyDefaults(
- wire,
- arg1 // url
- );
-
- } else {
-
- // method signature is:
- // oboe({method:m, url:u, body:b, headers:{...}})
-
- return applyDefaults(
- wire,
- arg1.url,
- arg1.method,
- arg1.body,
- arg1.headers,
- arg1.withCredentials,
- arg1.cached
- );
-
- }
- } else {
- // wire up a no-AJAX, no-stream Oboe. Will have to have content
- // fed in externally and using .emit.
- return wire();
- }
- }
-
- /* oboe.drop is a special value. If a node callback returns this value the
- parsed node is deleted from the JSON
- */
- oboe.drop = function() {
- return oboe.drop;
- };
-
-
- if ( typeof define === "function" && define.amd ) {
- define( "oboe", [], function () { return oboe; } );
- } else if (typeof exports === 'object') {
- module.exports = oboe;
- } else {
- window.oboe = oboe;
- }
- })((function(){
- // Access to the window object throws an exception in HTML5 web workers so
- // point it to "self" if it runs in a web worker
- try {
- return window;
- } catch (e) {
- return self;
- }
- }()), Object, Array, Error, JSON);
|