pax_global_header00006660000000000000000000000064127700273370014522gustar00rootroot0000000000000052 comment=c4b66fdd8e70a79e0ee0c5674386736e7cf6d57b hoek-4.1.0/000077500000000000000000000000001277002733700124525ustar00rootroot00000000000000hoek-4.1.0/.gitignore000066400000000000000000000002771277002733700144500ustar00rootroot00000000000000.idea *.iml npm-debug.log dump.rdb node_modules results.tap results.xml npm-shrinkwrap.json config.json .DS_Store */.DS_Store */*/.DS_Store ._* */._* */*/._* coverage.* lib-cov complexity.md hoek-4.1.0/.npmignore000066400000000000000000000000261277002733700144470ustar00rootroot00000000000000* !lib/** !.npmignore hoek-4.1.0/.travis.yml000066400000000000000000000001041277002733700145560ustar00rootroot00000000000000language: node_js node_js: - "4" - "6" - "node" sudo: false hoek-4.1.0/API.md000066400000000000000000000370031277002733700134100ustar00rootroot00000000000000# Table of Contents * [Introduction](#introduction "Introduction") * [Object](#object "Object") * [clone](#cloneobj "clone") * [cloneWithShallow](#clonewithshallowobj-keys "cloneWithShallow") * [merge](#mergetarget-source-isnulloverride-ismergearrays "merge") * [applyToDefaults](#applytodefaultsdefaults-options-isnulloverride "applyToDefaults") * [applyToDefaultsWithShallow](#applytodefaultswithshallowdefaults-options-keys "applyToDefaultsWithShallow") * [deepEqual](#deepequalb-a-options "deepEqual") * [unique](#uniquearray-key "unique") * [mapToObject](#maptoobjectarray-key "mapToObject") * [intersect](#intersectarray1-array2 "intersect") * [contain](#containref-values-options "contain") * [flatten](#flattenarray-target "flatten") * [reach](#reachobj-chain-options "reach") * [reachTemplate](#reachtemplateobj-template-options "reachTemplate") * [transform](#transformobj-transform-options "transform") * [shallow](#shallowobj "shallow") * [stringify](#stringifyobj "stringify") * [Timer](#timer "Timer") * [Bench](#bench "Bench") * [Binary Encoding/Decoding](#binary-encodingdecoding "Binary Encoding/Decoding") * [base64urlEncode](#base64urlencodevalue "binary64urlEncode") * [base64urlDecode](#base64urldecodevalue "binary64urlDecode") * [Escaping Characters](#escaping-characters "Escaping Characters") * [escapeHtml](#escapehtmlstring "escapeHtml") * [escapeHeaderAttribute](#escapeheaderattributeattribute "escapeHeaderAttribute") * [escapeRegex](#escaperegexstring "escapeRegex") * [Errors](#errors "Errors") * [assert](#assertcondition-message "assert") * [abort](#abortmessage "abort") * [displayStack](#displaystackslice "displayStack") * [callStack](#callstackslice "callStack") * [Function](#function "Function") * [nextTick](#nexttickfn "nextTick") * [once](#oncefn "once") * [ignore](#ignore "ignore") * [Miscellaneous](#miscellaneous "Miscellaneous") * [uniqueFilename](#uniquefilenamepath-extension "uniqueFilename") * [isInteger](#isintegervalue "isInteger") ## Object Hoek provides several helpful methods for objects and arrays. ### clone(obj) This method is used to clone an object or an array. A *deep copy* is made (duplicates everything, including values that are objects, as well as non-enumerable properties). ```javascript var nestedObj = { w: /^something$/ig, x: { a: [1, 2, 3], b: 123456, c: new Date() }, y: 'y', z: new Date() }; var copy = Hoek.clone(nestedObj); copy.x.b = 100; console.log(copy.y); // results in 'y' console.log(nestedObj.x.b); // results in 123456 console.log(copy.x.b); // results in 100 ``` ### cloneWithShallow(obj, keys) keys is an array of key names to shallow copy This method is also used to clone an object or array, however any keys listed in the `keys` array are shallow copied while those not listed are deep copied. ```javascript var nestedObj = { w: /^something$/ig, x: { a: [1, 2, 3], b: 123456, c: new Date() }, y: 'y', z: new Date() }; var copy = Hoek.cloneWithShallow(nestedObj, ['x']); copy.x.b = 100; console.log(copy.y); // results in 'y' console.log(nestedObj.x.b); // results in 100 console.log(copy.x.b); // results in 100 ``` ### merge(target, source, isNullOverride, isMergeArrays) isNullOverride, isMergeArrays default to true Merge all the properties of source into target, source wins in conflict, and by default null and undefined from source are applied. Merge is destructive where the target is modified. For non destructive merge, use `applyToDefaults`. ```javascript var target = {a: 1, b : 2}; var source = {a: 0, c: 5}; var source2 = {a: null, c: 5}; Hoek.merge(target, source); // results in {a: 0, b: 2, c: 5} Hoek.merge(target, source2); // results in {a: null, b: 2, c: 5} Hoek.merge(target, source2, false); // results in {a: 1, b: 2, c: 5} var targetArray = [1, 2, 3]; var sourceArray = [4, 5]; Hoek.merge(targetArray, sourceArray); // results in [1, 2, 3, 4, 5] Hoek.merge(targetArray, sourceArray, true, false); // results in [4, 5] ``` ### applyToDefaults(defaults, options, isNullOverride) isNullOverride defaults to false Apply options to a copy of the defaults ```javascript var defaults = { host: "localhost", port: 8000 }; var options = { port: 8080 }; var config = Hoek.applyToDefaults(defaults, options); // results in { host: "localhost", port: 8080 } ``` Apply options with a null value to a copy of the defaults ```javascript var defaults = { host: "localhost", port: 8000 }; var options = { host: null, port: 8080 }; var config = Hoek.applyToDefaults(defaults, options, true); // results in { host: null, port: 8080 } ``` ### applyToDefaultsWithShallow(defaults, options, keys) keys is an array of key names to shallow copy Apply options to a copy of the defaults. Keys specified in the last parameter are shallow copied from options instead of merged. ```javascript var defaults = { server: { host: "localhost", port: 8000 }, name: 'example' }; var options = { server: { port: 8080 } }; var config = Hoek.applyToDefaultsWithShallow(defaults, options, ['server']); // results in { server: { port: 8080 }, name: 'example' } ``` ### deepEqual(b, a, [options]) Performs a deep comparison of the two values including support for circular dependencies, prototype, and properties. To skip prototype comparisons, use `options.prototype = false` ```javascript Hoek.deepEqual({ a: [1, 2], b: 'string', c: { d: true } }, { a: [1, 2], b: 'string', c: { d: true } }); //results in true Hoek.deepEqual(Object.create(null), {}, { prototype: false }); //results in true Hoek.deepEqual(Object.create(null), {}); //results in false ``` ### unique(array, key) Remove duplicate items from Array ```javascript var array = [1, 2, 2, 3, 3, 4, 5, 6]; var newArray = Hoek.unique(array); // results in [1,2,3,4,5,6] array = [{id: 1}, {id: 1}, {id: 2}]; newArray = Hoek.unique(array, "id"); // results in [{id: 1}, {id: 2}] ``` ### mapToObject(array, key) Convert an Array into an Object ```javascript var array = [1,2,3]; var newObject = Hoek.mapToObject(array); // results in {"1": true, "2": true, "3": true} array = [{id: 1}, {id: 2}]; newObject = Hoek.mapToObject(array, "id"); // results in {"1": true, "2": true} ``` ### intersect(array1, array2) Find the common unique items in two arrays ```javascript var array1 = [1, 2, 3]; var array2 = [1, 4, 5]; var newArray = Hoek.intersect(array1, array2); // results in [1] ``` ### contain(ref, values, [options]) Tests if the reference value contains the provided values where: - `ref` - the reference string, array, or object. - `values` - a single or array of values to find within the `ref` value. If `ref` is an object, `values` can be a key name, an array of key names, or an object with key-value pairs to compare. - `options` - an optional object with the following optional settings: - `deep` - if `true`, performed a deep comparison of the values. - `once` - if `true`, allows only one occurrence of each value. - `only` - if `true`, does not allow values not explicitly listed. - `part` - if `true`, allows partial match of the values (at least one must always match). Note: comparing a string to overlapping values will result in failed comparison (e.g. `contain('abc', ['ab', 'bc'])`). Also, if an object key's value does not match the provided value, `false` is returned even when `part` is specified. ```javascript Hoek.contain('aaa', 'a', { only: true }); // true Hoek.contain([{ a: 1 }], [{ a: 1 }], { deep: true }); // true Hoek.contain([1, 2, 2], [1, 2], { once: true }); // false Hoek.contain({ a: 1, b: 2, c: 3 }, { a: 1, d: 4 }, { part: true }); // true ``` ### flatten(array, [target]) Flatten an array ```javascript var array = [1, [2, 3]]; var flattenedArray = Hoek.flatten(array); // results in [1, 2, 3] array = [1, [2, 3]]; target = [4, [5]]; flattenedArray = Hoek.flatten(array, target); // results in [4, [5], 1, 2, 3] ``` ### reach(obj, chain, [options]) Converts an object key chain string to reference - `options` - optional settings - `separator` - string to split chain path on, defaults to '.' - `default` - value to return if the path or value is not present, default is `undefined` - `strict` - if `true`, will throw an error on missing member, default is `false` - `functions` - if `true` allow traversing functions for properties. `false` will throw an error if a function is part of the chain. A chain including negative numbers will work like negative indices on an array. If chain is `null`, `undefined` or `false`, the object itself will be returned. ```javascript var chain = 'a.b.c'; var obj = {a : {b : { c : 1}}}; Hoek.reach(obj, chain); // returns 1 var chain = 'a.b.-1'; var obj = {a : {b : [2,3,6]}}; Hoek.reach(obj, chain); // returns 6 ``` ### reachTemplate(obj, template, [options]) Replaces string parameters (`{name}`) with their corresponding object key values by applying the (`reach()`)[#reachobj-chain-options] method where: - `obj` - the context object used for key lookup. - `template` - a string containing `{}` parameters. - `options` - optional (`reach()`)[#reachobj-chain-options] options. ```javascript var chain = 'a.b.c'; var obj = {a : {b : { c : 1}}}; Hoek.reachTemplate(obj, '1+{a.b.c}=2'); // returns '1+1=2' ``` ### transform(obj, transform, [options]) Transforms an existing object into a new one based on the supplied `obj` and `transform` map. `options` are the same as the `reach` options. The first argument can also be an array of objects. In that case the method will return an array of transformed objects. Note that `options.separator` will be respected for the keys in the transform object as well as values. ```javascript var source = { address: { one: '123 main street', two: 'PO Box 1234' }, title: 'Warehouse', state: 'CA' }; var result = Hoek.transform(source, { 'person.address.lineOne': 'address.one', 'person.address.lineTwo': 'address.two', 'title': 'title', 'person.address.region': 'state' }); // Results in // { // person: { // address: { // lineOne: '123 main street', // lineTwo: 'PO Box 1234', // region: 'CA' // } // }, // title: 'Warehouse' // } ``` ### shallow(obj) Performs a shallow copy by copying the references of all the top level children where: - `obj` - the object to be copied. ```javascript var shallow = Hoek.shallow({ a: { b: 1 } }); ``` ### stringify(obj) Converts an object to string using the built-in `JSON.stringify()` method with the difference that any errors are caught and reported back in the form of the returned string. Used as a shortcut for displaying information to the console (e.g. in error message) without the need to worry about invalid conversion. ```javascript var a = {}; a.b = a; Hoek.stringify(a); // Returns '[Cannot display object: Converting circular structure to JSON]' ``` # Timer A Timer object. Initializing a new timer object sets the ts to the number of milliseconds elapsed since 1 January 1970 00:00:00 UTC. ```javascript var timerObj = new Hoek.Timer(); console.log("Time is now: " + timerObj.ts); console.log("Elapsed time from initialization: " + timerObj.elapsed() + 'milliseconds'); ``` # Bench Same as Timer with the exception that `ts` stores the internal node clock which is not related to `Date.now()` and cannot be used to display human-readable timestamps. More accurate for benchmarking or internal timers. # Binary Encoding/Decoding ### base64urlEncode(value) Encodes value of string or buffer type in Base64 or URL encoding, function will assert input value is correct. ### base64urlDecode(value) Decodes string into Base64 or URL encoding, function returns an error on invalid input and returns a string or buffer depending on encoding provided. Default encoding is binary. # Escaping Characters Hoek provides convenient methods for escaping html characters. The escaped characters are as followed: ```javascript internals.htmlEscaped = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''', '`': '`' }; ``` ### escapeHtml(string) ```javascript var string = ' hey '; var escapedString = Hoek.escapeHtml(string); // returns <html> hey </html> ``` ### escapeHeaderAttribute(attribute) Escape attribute value for use in HTTP header ```javascript var a = Hoek.escapeHeaderAttribute('I said "go w\\o me"'); //returns I said \"go w\\o me\" ``` ### escapeRegex(string) Escape string for Regex construction ```javascript var a = Hoek.escapeRegex('4^f$s.4*5+-_?%=#!:@|~\\/`"(>)[<]d{}s,'); // returns 4\^f\$s\.4\*5\+\-_\?%\=#\!\:@\|~\\\/`"\(>\)\[<\]d\{\}s\, ``` # Errors ### assert(condition, message) ```javascript var a = 1, b = 2; Hoek.assert(a === b, 'a should equal b'); // Throws 'a should equal b' ``` Note that you may also pass an already created Error object as the second parameter, and `assert` will throw that object. ```javascript var a = 1, b = 2; Hoek.assert(a === b, new Error('a should equal b')); // Throws the given error object ``` ### abort(message) First checks if `process.env.NODE_ENV === 'test'`, and if so, throws error message. Otherwise, displays most recent stack and then exits process. ### displayStack(slice) Displays the trace stack ```javascript var stack = Hoek.displayStack(); console.log(stack); // returns something like: [ 'null (/Users/user/Desktop/hoek/test.js:4:18)', 'Module._compile (module.js:449:26)', 'Module._extensions..js (module.js:467:10)', 'Module.load (module.js:356:32)', 'Module._load (module.js:312:12)', 'Module.runMain (module.js:492:10)', 'startup.processNextTick.process._tickCallback (node.js:244:9)' ] ``` ### callStack(slice) Returns a trace stack array. ```javascript var stack = Hoek.callStack(); console.log(stack); // returns something like: [ [ '/Users/user/Desktop/hoek/test.js', 4, 18, null, false ], [ 'module.js', 449, 26, 'Module._compile', false ], [ 'module.js', 467, 10, 'Module._extensions..js', false ], [ 'module.js', 356, 32, 'Module.load', false ], [ 'module.js', 312, 12, 'Module._load', false ], [ 'module.js', 492, 10, 'Module.runMain', false ], [ 'node.js', 244, 9, 'startup.processNextTick.process._tickCallback', false ] ] ``` ## Function ### nextTick(fn) Returns a new function that wraps `fn` in `process.nextTick`. ```javascript var myFn = function () { console.log('Do this later'); }; var nextFn = Hoek.nextTick(myFn); nextFn(); console.log('Do this first'); // Results in: // // Do this first // Do this later ``` ### once(fn) Returns a new function that can be run multiple times, but makes sure `fn` is only run once. ```javascript var myFn = function () { console.log('Ran myFn'); }; var onceFn = Hoek.once(myFn); onceFn(); // results in "Ran myFn" onceFn(); // results in undefined ``` ### ignore A simple no-op function. It does nothing at all. ## Miscellaneous ### uniqueFilename(path, extension) `path` to prepend with the randomly generated file name. `extension` is the optional file extension, defaults to `''`. Returns a randomly generated file name at the specified `path`. The result is a fully resolved path to a file. ```javascript var result = Hoek.uniqueFilename('./test/modules', 'txt'); // results in "full/path/test/modules/{random}.txt" ``` ### isInteger(value) Check `value` to see if it is an integer. Returns true/false. ```javascript var result = Hoek.isInteger('23') ``` hoek-4.1.0/CONTRIBUTING.md000066400000000000000000000001131277002733700146760ustar00rootroot00000000000000Please view our [hapijs contributing guide](http://hapijs.com/contribute). hoek-4.1.0/LICENSE000066400000000000000000000034541277002733700134650ustar00rootroot00000000000000Copyright (c) 2011-2016, Project contributors Copyright (c) 2011-2014, Walmart Copyright (c) 2011, Yahoo Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * 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. * The names of any contributors may not be used to endorse or promote products derived from this software without specific prior written permission. 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 HOLDERS AND 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. * * * The complete list of contributors can be found at: https://github.com/hapijs/hapi/graphs/contributors Portions of this project were initially based on the Yahoo! Inc. Postmile project, published at https://github.com/yahoo/postmile. hoek-4.1.0/README.md000066400000000000000000000025411277002733700137330ustar00rootroot00000000000000![hoek Logo](https://raw.github.com/hapijs/hoek/master/images/hoek.png) Utility methods for the hapi ecosystem. This module is not intended to solve every problem for everyone, but rather as a central place to store hapi-specific methods. If you're looking for a general purpose utility module, check out [lodash](https://github.com/lodash/lodash) or [underscore](https://github.com/jashkenas/underscore). [![Build Status](https://secure.travis-ci.org/hapijs/hoek.svg)](http://travis-ci.org/hapijs/hoek) Lead Maintainer: [Nathan LaFreniere](https://github.com/nlf) **hoek** is sponsored by [&yet](https://andyet.com) ## Usage The *Hoek* library contains some common functions used within the hapi ecosystem. It comes with useful methods for Arrays (clone, merge, applyToDefaults), Objects (removeKeys, copy), Asserting and more. For example, to use Hoek to set configuration with default options: ```javascript const Hoek = require('hoek'); const default = {url : "www.github.com", port : "8000", debug : true}; const config = Hoek.applyToDefaults(default, {port : "3000", admin : true}); // In this case, config would be { url: 'www.github.com', port: '3000', debug: true, admin: true } ``` ## Documentation [**API Reference**](API.md) hoek-4.1.0/images/000077500000000000000000000000001277002733700137175ustar00rootroot00000000000000hoek-4.1.0/images/hoek.png000066400000000000000000001120631277002733700153560ustar00rootroot00000000000000PNG  IHDR}W pHYs   OiCCPPhotoshop ICC profilexڝSgTS=BKKoR RB&*! J!QEEȠQ, !{kּ> H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3- cHRMz%u0`:o_F^IDATxu\Ggvݲw;:BKү N !nlvn;~LRFнk339G0M>}C>bOЇ>}#>}CI}CH}CG}CЇ>CЇ>DЇ>$Ї>_G;@#>uyCI`͚5456BsK eGS~?쫮!++p(祣234p8J(ᰣ(*iddDJr +֮RSR}MuMHMM{\i+ʓG rݎ̃REi~r66vutWnZj p|nimkC%JJQw/ihJ(f`AHww79H6uude EF# ɳlݻX4᤹6 ώ8Nʢ`2hk@4VmkC ޿%K$Ħf8GWdW3^^$3O(k)?,?7?_ٿ8K EK,0շߴaM,I7%Y ]&}CHsfSTP8CEo/cʌ1C1CTV(hah$TJkg7/wV_sϞƕ_.8~߃/LouWV4l7a m}LЇ> eg~#|QysD*n/}u<`5qL(_<Ꚉ5.;k&%uvv1+*55ea\QA-ׇ>}$)@$m"ۺBAȓDC֫ #,PEQnnE>a((+s/_'€~ٜ~ƌ%1 ñew]5|͡+V6zm>/ݶDRCGn Ra^~f\Q567HMN) ™)@(3`?U1&ߣj_=$KϠ_^&v,˄"ݝJ]0qzv)C]On ?5ջ1r`9_:sC*^ُ\uͥk6n]IJvM.>}$q U{vU{v j$}pK8X=y$Ma剷[_"%GW(.SNF% yk6TUsrdTMG$\;O^_ͨ|,f`QmAj%H([א5L'u2NWwi?ˑWLX(}WЇ>8N'ylx1'z6V1[^RR%Y8#Kj6n (ۿLVz2(q͹3 FfCN[HFTSA\Hyy6|s5~[?ʂ ۺD$ 2J篟~nS'g@y!nL,܍?>O,[-k?ڢ.߲MOVսz2"^Iz_ }CI|"kkdyKnJMrUT/7n#/AM(0 ^Z[>ǪV4DA1&YkΚ$Bͤ$yHLqtK5f ,7Woa8v 2ErorurUgNrDtias7w?kkjym>»߭maA wP.>}$ܷG\~?ÙϼIrya&We@qfRnx͚oG ٗW @@e,۲#(.N<~i/' vAXl |9*yx~vtMyA.?RGe1m ";+ tePy+7l#X&vZ"τETg no( \u瓼z'>P\kN'ZO$Ђ`jVZA?_`{3^^]ce:Bd'sSظe/?WU vz$I7P`1g7E  z=x 'Fݿ#vdg+Dqq; T`&U GpMf}&PBmS.>йbD9$y001 eQ$c 㘦IN$:V[3Vï;ZÏxڦΚ2;̏~.E>>,`(/G\_~ptX囗~~̬MXB!4H@A$`p %N'-IiiSG~xU\}wHMN⽕q8 (-$-Gߋw0'ϋ8/Y}z:i Q@nTo33c=NdI0MNF+f)"^7:oqqB:!tݠ0p^Y闟EgW~Y].!A@ǐnrlZŹ#&f% w.FsGab"RF28fΞ#w|[7߿U~ճ-PTMqa#qzr!=a ?N;gmY{k]٧ cpE1 g׾\{(dƈ2MsE:04 ;fʴ/ {XQkӚsq]G>0MAR?7ˬ@_IHhű_X%T;PqwS1jP9["K)>'VVn͞FFPUcG{"](EQƷrpXR|6"1U70 ,Q,⬉(I#fO}QUū/F̯%깸6bqEՑ%Ok įBlO,06a:N^UMbp,R}ntZZ))*k'SQj(F8e^B\r,r~G +$IE%jpJ@h/,I($}Iͻ`MC/.`=ϯu<Q`˞z6,_]NUu=0bP )혮d!8P0:}EQt/G} 7n|憜7IDbcpAFYp(q_zdg^w9S5[.ɸ~)\1sө,HpP8q86첄im2qQ\1{46'(8veq9?YUEȢĒyff~tG.`}"k|$:e\9gWo'3ًD!Aht.9}u>~9LX,)?> <v3  Ϋo755&Z >P]N~' nDW|]RRRR&*/slugAD⪆je$q7 M-&86^'~MB:I(N%/,eDE>*HOi:I'.4w1v@!a0DI*:xn7S'[;gܠ~)MYK݈R|=3) hIW8Bߍ,I# alfݡ(7m5YW#ϾO[Gwv`M^tD7M"|g/?6zMKĵܧxI<*!~/U4:&${jI5U7x}j@5@D"3%ZGEa&7GMC;,FEnj^qM#h7y0Ӈu+݋bDdIdfvl0 j6~p?s i"Ɍ,`v{iH5Gyme!8*cwLVBr3' ;_\{ x~ M83fa~Lk{&eS)}$p:nxD1#L>";fgQM!/+IYfYŲk u˯^?%e&"iEklx~6ř,d},ڸŒdN$M4 R}n+a ָgp 0Jc_tn+˶qi#F dey[ b[ EDw4ז``q6#s4ejo77%W5o]-੗sޙyqS[hm *¥]dƇ*,Fc{T>&Ϧ ^8{a~A?0蛗,'v˟|X҇/`Lv]Im\"g /wW.(/g篢lX9Gt"ihZ Cv9ظ-'mPM\Qum,ڴCC覀 ƕe0mYD4f6fw9cT9gN Sp%Vn?ڪZBQEխnmYHh۩錢H+˶qcKX$"̃Ƀq;D*c+8H\rWV`rޔ ]8EQut=ƍ#o-ӆ"!r׷Hyg͎?-a|^C}#_I|AHBssY=]Sڹ} 8}0tô"q84bb 4¾}9MS1MNMS'5]!Ҙ3pL8p7vgNf:^^ _;k<!3cT9Y)A$ACGe3rP.lC!"oo%bع;ד"Q:yU|os1<%'fՎZ~k y\V7A3 ~xi /%ƸnX"1U"h; m< EQ:3`ӜDI዆UOgنzlҵ߾lQ4MTMf$WOY8<iBIvH8 L$;G0CDSCh4$jFlr+4~ Yy36r͜1(_?]q%9e|opc7pT.KD4UGKzD7TzDϫ+3g0TUc<ʛ;EdPX.b^N^j(˭opv5DI>{&pSMə1&7#=C gh?X`n 0yXdI$~>•;Hyؽ>s1EcTe>v,䦠h:( "ab("x\3Fl[Ie}OoqMpe$I.Kb;Ol~o0~``7F -頭:{\IZj"&2,#6D3'CDMʙ]= 3u4n]8FeM&S [.Sh6ϛ]Yy!]b_eA^lYeSnwEjjٽÀDS]$}_&L޵\c! PqJm@C[ndPE>}@,yp4ƶ)ͧj7ec!MB& ,DQuˀ"1E%vvؐ%7cC#+8p$LS3sOo_x?~<lquqɹq chDA吹O%Ι>@K-vID7l@\aQhh3 `sU5W1R|DΞ8PTAOxAa08 Qh u-]T73uX 8;t 5ԁMv0s0FܝM͓|ג+WHE9sSR^HAv:)Hi9ft"C7R}͝mՍ"l8)\g=n+ DY% HKII?ǟWI$ˬ]ϐ%y,"Kd:dkwDQ]61DQ4vl+L~t("xwf,{N'8̱t(KCQ5D )7$)5nkW^\">|'>mMtvt[OE+v9XW^~5D5xknY a~ 01ba:k8cP¯MY^%Ab0(*tn&'1tXۻh 3J$IJf6_7^ޖ惕IzÐq\yxE~ C*Q`3[+{ cƨJl)wں{o-M-(z25=7nh8 I}v}Dy' '٧.$ˮi${T](m]i$Uk ?9Mwsbbq 2p-Anp eD*hY;U Q8mD)(*XL\77]4Xȹ16meڨ2U *+^31֚ݘ-54u2_hD<BYqU(LF%$Aa-2ҼSA,S^{wsc[NME~jK̤TEq؊VGQ(QH$f ~],-H'l'岹9{ژW?_Z4i`vڱz'чO_X;$!)>_!C& *FQ5./M!䦳{WM{hh tw0p9.f,1DbVxvR}.ں{\U Q]|8hA("`""1EC8D tA5cDqfde&6YBKI5Md_]NS}'}J ,sQd y鸝v@ϊ+*vA83o o⡷0}xMȘ;y0幖y͗Ϟކ6Tcɼ~77%%!0u`ǹםl*[bQ,ܴFUw$ "O/;vq͜1LVL43"hÝuΦ07sm>䔉,]_ç1=d'ۓH{xk?" 'PzYcJi^"1KKgrg]A^N*.4sq9lV'n>C Úa`%;0yp^1EE 3)K#TնrA0Lր$nvщ)//F{wdY)MC⚎`L֏J%ƶ.vյ6틦0kd9aZτAE*^xksh ]ulW 5g;mnw=Nfmf~Ȓ(G6jg#SqCٗcWwKx IIdkN x߉$Jۀ ܼ3cH(, &nG56_5.;)>8m1U?La$HfFڃ1|}*.nS@8J]k>=`8ݛM;IvTUp&ƍ(CHE#/.ژ5yF,~}%_:k6[v~{k?pSId p|O{C'ғ8 x}o$MIʽ8DQ$h (b2:#LTLLdElfӞ f24iG ch]a^xc%_7 CDp6DA W1 Mt# 'i~751OD\vi`$j 2Yu PȊk/pSϦ |(}>Cwp{H T$짟7y "6ƽ~{۰I.>'E9)vnJS𹝸6beq KټCu7VWH>Q:QThܒ툫:KQK4b&|ds (r1o0..6EY5Mdx[WǂĂ@aco};8v*3HO"- gEtZ?]@(33$W3$,qfN6T4qr6B}[CKLd5rW}ƱI"a:&:aw$ƿ\CťӇr1OC +Ń_W(HGf捪,`[7PFF1qp?73g'v I"6I$SE{ յs8m2yן{u +mI{KgڰJrҐ%X\CQ5 ad&f"UT[jYj=y#JyLZBVTq]]L6Qhٍ( x\N4]'X2uh)0Dn0n:FVfuU-~ѕy\>s$zs Eɀb̝0s' U,ٲ,?:qe{tq)CJhn4x6 k֬aȈteDbuK㲳zA~g%9cPqa~^t%Zyoz}^Ds|!8OxH%w?\5$4* 3Ǩm GYa#/-q )NFӡvX]PٲuOCQuҒPΚf*(g>C+h=X n:N4U7FI ]7>T~c&>74t4-CZ s;hFk+Ya/, ,qqﳋ\¦5lSM}$HjEQ?D( N{̠4Ldte?|5GtTM.o`z(%D%Z:Hv AXU0G&vx`Aʰ$ oqm=E7̘.T+BJa躁(BTQqeZ:#12sVYx]vv M7:l|\q(y{-o^0 l^YVʙ#k{-/W#$*Lr|\{LVn#ow7#d2v`!}i)ϝM͢5D p#ocH^'n Ns)ȒnlۻwI8ꡄ٩258(qU.}Pqpy8slA)LCih u4 wSMSGrHL掩T 3\Dhgaj:0#k ,$Mnb69HnX_C1<^4lTUl\*O.~:E~ۼV@uS'͝a.qQӌy < L;VbR`ZbQt%\vǕk%>ŝ af I* ;)JAuv` k$Db*̔Ř.C#u'vVueA>pLg1In (bƈ2dQ /=TKdT%;Y ]ɕggΤA<EU7~:DC%ܞ\@2u:V뫪Putg޺ 藅&rcپ-YU78}L9%9$QJi ?L>/Nw0 BQΛ7[DzM՜>H\4 $ц/)CL^/h ֱqZ]];Z:v)bJWV;{tO!bPQ&߻l*јA@uCv6Y,;9x$ p'Oxwh1Mไ {(2^kOb%lw|kmh z Z<>]'K"Vڻ6$A+4tU!HN"zs-Ս\ey PLa:Vm;n$'TQI\ALQ*FjKΟ3OˌQVA ںd:\.0-bҥfcSSM$YVe;Uɇ`i#V+´70+?`#y~G' 9X5U?|j8KQϐ0Yހ`js齐u{h¨(}<& vnv7;4r$ܨhqNMh T\Ͱl|n'L,D\i:S1dZ}/,e}>HV5b􈡜3}4ݭBz<SN0GO4}(ަ6}Q*i yTdc檝\?w$"8ԂMHWmPlۋ(qΞ8hoPu$~7vY//@7 UGea&iIm5odΨ lji)/ c%j[֮@55^]+"F6om[vrb=I d'aq%V S/NY8WL> hE=$n>kz 4#n {n⪪dIQL^ڝoGQU2S] Enzv]ρlS`= /#3w|fS5q @Er}X+K4GUSR"1:eh%+=_~rDt:Y]Eja>xulY΍gGu4s8nqh~c2=#+s8 .ac'oKaf p̑4w2_T4!K> 7fj"%U07 Ιě7ݠY_667tL3VP9s枩֛ rchZ,ձdqE֏%!X5'hjHLǪɜM>k@POP\ ;둰&ݚ0܋zt]g&"8v~} &4STE0 U3XpL%-`tÆ!Sxnx^_o'z9,X.DQ*w?>qU&KtuE:ΐ<2\pYz//dNCW{#|Fg2jH)vJ?~~CPSܣד #AEYMUڻ|~}/`ݮet;<0tZêMon?15ʫKii/? 2T8r$1ʜ9uZ$ALxov}#N0A5| x >a+a4ONaL̿Q' f1^MNJ ŠSB=% PB1E FDAP,Ng(J8]x\v)?{-R\<˹p`\N+#Ă d${ZH c˾FINj~ IeƯ0_O}s'6 w(埞gђ_oл**m|)t@4& %??_B0M.^= /cLe 2Ĺ|Λ< %=C4W5z|!{>C˹.F^¶Uʍk.K㫪FAv*Cfվt3HNM0†=%I ؅srzȸ7@Fppbg,ʰVᑠ& ')Td?M UOq,/Ŝ(['F㪺}_(&(,1mx)/,7rLaW= ]d`51Ӓ F1_3_Z rѴa\L P/J%|9 Dha&e Dx位m'7’-XȆ]ycL"T߹h2_:}$^MAzlo_|l2S ЁtL/DLXe7]adOd],ZSnރ 8}p8HIMşL^^scНZ$zb`%">WxʱJ*ep&v'*/lc%: gV>;s-o5J]}<)Ih@ӈmn9FEL jIIeo`SA k`RZCjQ }y3,c y..'N˅+7QPTD;^@'rgC:#_qPYO 7'Q Ě|(&%'p2p9XhI6rw/߻az$b=ĺeq**EY\={ [,ܰyXDA&ˬU-ʯVZG0¹2g~2=iNae)L&3Úu?u{jYan$7k7/_H_Djemqg?O`܈ ]GNF Y$;=Zaٌ,c޸9B|O/H5tL~/a&PF Һw."ajڻa'4z\< ֬eG1uh?d|>I>CCnn.驩t?$}ucVSщ 3$r30x_4cuZ(NT_ pP0=Bh;E$H<ޖ7:aj8@n4vȜ;y l\6l7O.Q4Q}hw. 6n"qFUx> R(Jw""Qn?;q`Fv7kZ\p9}R.7`Œ$"2 OQEuc;ScXi.,Ǚ0_]?_)&f{_;PҵTKX~_̡ 堾_YΟ2$ 1{Vg8P=jjOuoR/$G~{]'@?o'ww'BL{O:KbrEzmzKͧ<7A$@-gŶHLH ұ~/5H,#1VV2sd];7u947VVrkjyi6VK˶sʊNNQFK 6fy6w33 7Q9Ϗq|9t}qhÝuA xv& ,$7O]O-nd`a&]>4O2uH~tW L2eM4w Gnw2N;ם9h\%(ӟTИ̑4b&|r!O~1z'xG1\I`^roy/6>Xe{f`{=Oۓ8!SP  T ڪádAV2v+]d Q {?_7^_n>KΙDg AH{x%Zn:k<{zd)I#vU4dhn8s I~/iiI՞I;UM+FqO[ςI: II|kX4F[[tJ7ᦏ87۬Dh1~W`Ip|;V[w6Vj$&M o;ѓX9]1U9o%f@A:lk 3(Og]U-k0{L%ariXӆYcʞ6i2cD)|W֋nȒA<`Ssna.N%ҿ0\3?&?<EՒ8)In^{5|m~q<ȚP 0&j =muG8o|/eJ>Dq4h/4V[?{3FW0ip1n&nH$U%$򐺻(~ސG5Z blWiGYBƚ7 lh^ű59w'uې, A$t jHU5=$y6w!e_N*˷@ 6i_Ny鼿qFCKs(IlTi:gNMPTP$5g;𽿽7|(XΩCKkf(n:k&AMy):wuʊYtu?d)IVO˯o{H꣘sJ%??wҀb7CZO2Ip)I@4͸la:~ӆlDQa&j[Lbe`;Nqsyy6v$.1>o~jÚˬ_?y~~;|ɌP$  zs 9IxXf /%U>P5pOg0̗dI⺋F>d\S^V=}o\0 {yu ,&K,'r.λRLqv2 04 qᩅs !([!.7l+D,5Y]"qqmtc풐8a,̡މOs۱#F*s]x{$oT?.$RX} sIb1d%N A$L@7MS p4Nzgnnj%Y騺Nf]ư0m -ɡ 3]?3ioi&QtMHr9)sdxeHHdxCoQ_y>)^(v@4IynKE ?'Y3Fօlܼ2-+vT61m©C(I;Wl6^<|ҩtNZA//C1 /yW\EӇdS5Y>Tz|ikMRUUS7Y@#M|@OqN,I DqxwOl..5}5=k8OsE=%C~4H\˒Dke4nH{5$7iP 61(]5 4**qEPT!:xwH.G@` \v;=:i^6& B3 :CQv-z'LHeAqE;\q%qߺ:gM4oWϢ+KK1kt9ƶ&f(K4t8l2E)v⊎$d${ET?;z=xo(&݆bѕL+v0T$I@ Nq.%3c:Nb'ma!>e>'ǍRiO~c?G&ՙ}IZ6AV^'cI%nK rj:nS4 2T̢M]%WuSXWW젲 I~d~_0 3 c2mh)tbɖjf'ѿ p eÄ_YAcG]ӹ`%tvr!/<Β4矷^6 -fʐZ!rR^dU* WUqd"qd09UuT5UQ̗ sճQrNa'_Y}I$3d闝 {Y]UKvlfLFڪ*޸esMiIɱpQS Ia1p}'7Idɒ?&Kēp",7=w $aR gźr7iD(cnPFw(ʛwrH, /eku#~ZBh !/OE~:yiq"qEYNnZڹE ,/.g@ae4ql A x\#kg\ueL֏J)N`kvIdƽoh;Ky^˶9% $]I`$%H='G,19At$ͧ93c3wIxMM*dAHefވ֓u1rRIx]NnKw8Ƭ12n͌T@144{g$#n:6iCDw\!w(hA]K٩^\a~$z=q Z:NR7iEpeDQ`k7WWqݼqH,&QBi^&&6Iw3" nY =rmUm5LZ~Ym27// W"#ٍ$h]O,k7{,ONaC]ٕrI{o(H {4ha<.EYT G ̭a%\bNMqR[O:'Q9 6DHce"w{ LOi$ϫJB?@:H*Vo"8bFs\7[4u2Ĭ * 2hph\E 10MQI"0w\gNih$ tEb65U;s9Haw=210PuYi x p]iO]uQ_8M[Hy CzNBZ%#5 Mӑm2B4u0b`1p4hIF4ie*mǒ-Sil[P @_\;* EY]UKMS^AlNkvtsTEQ`kնr Ih5VWҋ3x lyOd6B͉xkXNQ3lD# 2Aj[$Fq30 I,C+XF}[mݨrRQK,˒D vu4:_O$)L\nia;EDȊ|lϜ1 )aW] ~ah~5EQn:'DDqnU5jh[3uL\N; 8vv.^yo7a[íUΨI>'ֿ3~Q9{*H k {dY0M4u0ܢ-D"Ql ttGƐ^ xkuvl"1A(qECkw/@fPPu(e'+GZ9c*TȼLӄT/9Co7SQe)K'SSByQ6WRnѸ_+o%o%0n>~tb,\Kzn|oE[Nea&I^'߿l:aYqň\ 0IObծڦ]|i@~u'tqH"p%gDr{qܝ,N4½ 0'1{KhnR>=ʱZ*H" kv[v6TLCnNw(Fkgt;Gn3IKKu!vY,];, bƢ(kq<;S~ 31#hIg{UMu$7~MA~"ۻ9 R藝&Q̖7s$fm $7{j"wGea&NE-u\?suQWd7v q\rPE\UTPI"6U';͏!&O?A_jzLIOB䩼'%~F NΏBIc1&b5죷y&3 'qѩ =5xlousxDn6U3ݽAA~%9̘0`8&c%Q8܉db5tUg82nPŅ3modXiZB 00 @IHOr&]2\II{hYe?~.:(O$' ݰJff͖jEᰡ}@qw )L$5Uӭ.lSf0$UKҿ `8MT7ZmGT*n#Id~9d${Ir;y" Hnnl>b]X+SQ3BWd ƒI?3לӛ Wz?!n)Ϝ7G ;]ՍƉZpOEfۛVt@uDAf)Kcێ!cB4'H`0Jj3gbjA.:m(DAV*5 HWuںX%z.,K--ZÃof[W46W7ކlSOC[i~/jY~]!TUCuLL:"vcLN$)ttf@˹sG㭵U,ظ;jhi: fw]+I\XD$Aa)H"dW]u]6vY2 ~|=Sj{@NohWDH>a*ƪRz!S|t!hۛ:PkP${cusnya,QEE7 nA6W`ĠbtE;hh#SNe2/|$'uUzDjb .3DAaJlۺ"ln0L|x]l9c%HL!@sDݠz <*HJ_4%l+cr3iXS;sɄAEdzX\E0&F/@@"q@(FvjN NfuHJfކdɩUd 7 p)Zc+I}*[xKŚq*Ī<7%A$T56I Ѿr Wߖ]w`ENUŬ\! cL]AGgmӆwӿ(WVqax3t 5飭2ܴ$T͠%vqtt\gN򲭜7eav:!-J^A)I; /pIZkg1iA=>k|Wk4>IJ~VGoMMKHnzi!'m^nɴ#4u3,%cǞ:\]QT#Mvzg.:R|N2S<9ʁNtDΟ6Axq⪎nKd v~^xm=?_ZE8ֆF$Z& .fdy$e ݆aC3 v3Trāx]Ón$38[ދqU;a$,aW-vkv/u#=(=N"5'qHQSgOqk?t}H7O -҃s!SD`uiSވ¤~gNL[ $ 2Rkfa5dHCk0t kOl$Ántä܌,8'WAd&{011LKCDmfoC;GSJLUu^ZyrtbD7)I:uK&KgAHHLSDں"Th%j EfM3(:kYۜ)Oq()>\v(io//ݪ޶}ο]~E#I⽷:/},)GXr݄8~qx0=z{iq^5ߜ}XÛ>i18pͫ8/sf'DcnbUlml[vyGʴ+3,}K KTY @, " ŬrW T:xqΚ4/ &U%Ie$joI8pāe0tS064oCC[&qfrpD}:NLUUuȊ< EeKu3}x)4w#K"B(bj:-a2p;m&4uyg.6]tEͲ{q}(EOaO"ScI7 O␱+ٚ))&8rGzOAx9G +9f:]07=~KsS$D\mC4M maRL3,!ˀ]^,Yoہ$x\6\Hzݎioe?qD5Q#I&JMA;N󳭦HDv +BQ"r(M%j.7uQθEA8'U)N@EeQmk1}? N'J,S0dNEocou :'GR~r9 Gߏ@2W|7ᦣU}k.xIM5ǺcUc5FM.L ,"#F&v `UiA((+1 /,qNULN;YҼtf)GIC!/-݊\4m(DTHr;7?UZx>^'#I!vQ+EM4L '-4 AJrSYMB& -* ap5(yhso=7 YJ4%=KWhNJN}ѣs$B'RXTgJܨg;SJՓ mIt>Q|^(w'<+y$ d 6u*6k.Nq3}VU:X @$;'8d IRCqv //E oaRBEvh᝵(NfR!>QhT+C4]lj/ʠ4/ {X$72_Ei^*{Z?MW)H"h4B ."qrm21E,)Cy 4X݄sјA?M>.!OCM&i| -'q*M3x'XSh?ϴ>{s`OONl2]Oz s!ĞcM$ cO?Ր@ccد()箘>!a# `@,4MX\qa$Q4ӒشuUu3eVv(#躁eA4 $Iɇ{,+ee奱nAX@(;jQEih Ya/(4UTdQ Sn0/Ayj"bt0>ni/QX }`{rS7 `ɜ˽%ןU+)ی%qrן"paUj:|r[ rqu`DJ>z(akh N`3T?!!1GF$X[Ըn՞ȲDLW?)}lӀGm%EDQD׌g<ȒŜ3ykwԲe_5H7-XvE%E?;vYB`o}; ݇uTQ$f +eAxoh8Ȓy(@zdbq 1Љ Sث0]w:i`x_""$A Iy 'b߀z_*Nos |8wGW`IioB+iOJ U'GZ)-L.x ;8w߽tʒvuZmK>$NlsvH‘(00e`ZSΔδ3)GCSBL !vl!Ki]ծ>+11Zr$gC{.<4*RsG|k.{u +y{Gb_Vٸsi~tCG79Io\%Fb=[9V&u"'=eԾR׉XEs>` [\"1/%m$3羌X/)\6R|};it"r^/p`ǍϣaYM5}rƦ|0E-kLLNNz\Qp +xrq{jnh>7QNUWqyo[7iq!=C`e"O﹑{n^+|W!rύKU\ g)LƆu-|?͉ᑉ/#Bu.8M}x>%%~)`<l\RBIszScnj1`OD*G揜EUt93݇i SOI) ["H{6eI{trE2-As]5܅%li UQ/:Ӵ+tă(?*/%.`Y V5R PYĩA~l:dT*uH&d2=ޣ*CC ,7CQ%zy"G N >6 +A%v }_<ĕ,چ_0HRM#^Jfcam(,ۅ?{oW"{Cgk_|J{:}});rfߥj HrC[q ˉ|<{\nZ+|7;f},dKM PTj对ٸ9E2hu*!鏽E"xƻ\RMqn3a?(ʬ9J^a=sg Y%G&=-`UEYaUK-lˮt Trwa=|H_jO<(vσAFc9㻏 %UTHr*EX@ ,U/>C[;y\t沈Lvjj%+r$Ó<]45TG`uMCQMu>e!ir|y.sG>!!" 5]+IR@6?} Y:O߽>?u~ XLߍi]{ַZd̽ren)MkgN՘EKW?l':bDdb|hȻMS[70 @-%TSɩΌq˺Vd*/0aYP 4&:1B}-u{e^+gmM=x,Ƀwtvi=y]ǣh P5LK%+4ذ,9)ә)D풠!Dl, d3GNJx{ټ¥*^ 寗0_V)=߲&C`_ ;꣔;\^Ob>"W\}ݾvT`/obMݨ>`P(F\vX<ݶuRWkFVx4,uL%Eve;6qk@o[CM(XNÒ*n73Mrj0³O TQ_SID,d_U!s̓OF^Be4_kw+h$3dgE^W#;%S?)ᓧ@P4M3N떹iܲkZYZۭ,TIer缀X2O01~cS)+4m R# F)z5 U~}*<ϼtŲyKL"̱h?+jlqÊ&NMS]u/ ÓE_N^J^@m07*f9FZ*+%_b t=v"E#O?/EwCZنy rw#d:[y9/c~6pҦ㽵Fbn,s?s"O䲹TxP4VWf:'PU͘% Q(5tjqw 5t*`Z[=Lcm54.)(.YdrGݛi"W00-KGhD`u2j5{?/Zף7ĴcSz1Rr=y=zolڏlbk Wnd,{oүs1hҋFb/A R[;~w3:c(hOg3Ж͕e{nNiInӖhdIU7/ѰLr2IZ*XB`F Oϕ uIslY߆ϫ΍4T/4VS4,6lZjLE^PD-{9](w2 D۾8y$ej xS?"}z[r9_罳+]ZdY`5da{EW# a y[Y.>nq ?^_J.]8fNu{c[cs5 ~KXXucd(E[8HMu9iRYŲ^/UUD:yL /yUO^8»vPö m O%83^@;QЭOڶډw{i:!YB_$^`|*{2 ߳NtQlm7W,O`Fcl^~#ĭiɊ^>6njPl!5*|nEΌO0qE#5UeLRT{ߴ`F3jﮛVƪzc)"IJ!z].SB6KEeu\̓!rhיՀc / 6N2ۉ.ǻxЊD&/Et*f˼/oZsTNShc 0 Rdl4-BJnKvÚVQ S(rv2hdhWwNW;Ӆ7L"@nm"/0^/Fb7B-y,l)}jb ψe 򹃹|r- 7w׷אNdbiHB UU({XRnsE bx"nn:LALMd"g$2=iv%S>Jk2aY2r֮HBtg-ػ,=,z#q 7ugCOǁ*4}nס}V޴_p%膅(6nzG LLx^7,_/4H3Pn TUMR'mc5],!L 7 ͭ9kys!XȤLm0:8\._* .rwl}JL^=z$m f*`Uuˏ{K ͥ*" tb`*\eYX&DR8u켟W ~jEWEMf{K5#I)-%)Ϡem~d[U^P!S tSL4QFXw}ÊR>UE6NvnNwvYX@qY98Na^PfXģ2Ȯ A"c{!q|/EpppUk$^oFNr]Hx id(H888̃ppppp~QK H888888F1ppppppc$#pP'IENDB`hoek-4.1.0/lib/000077500000000000000000000000001277002733700132205ustar00rootroot00000000000000hoek-4.1.0/lib/escape.js000077500000000000000000000051671277002733700150320ustar00rootroot00000000000000'use strict'; // Declare internals const internals = {}; exports.escapeJavaScript = function (input) { if (!input) { return ''; } let escaped = ''; for (let i = 0; i < input.length; ++i) { const charCode = input.charCodeAt(i); if (internals.isSafe(charCode)) { escaped += input[i]; } else { escaped += internals.escapeJavaScriptChar(charCode); } } return escaped; }; exports.escapeHtml = function (input) { if (!input) { return ''; } let escaped = ''; for (let i = 0; i < input.length; ++i) { const charCode = input.charCodeAt(i); if (internals.isSafe(charCode)) { escaped += input[i]; } else { escaped += internals.escapeHtmlChar(charCode); } } return escaped; }; internals.escapeJavaScriptChar = function (charCode) { if (charCode >= 256) { return '\\u' + internals.padLeft('' + charCode, 4); } const hexValue = new Buffer(String.fromCharCode(charCode), 'ascii').toString('hex'); return '\\x' + internals.padLeft(hexValue, 2); }; internals.escapeHtmlChar = function (charCode) { const namedEscape = internals.namedHtml[charCode]; if (typeof namedEscape !== 'undefined') { return namedEscape; } if (charCode >= 256) { return '&#' + charCode + ';'; } const hexValue = new Buffer(String.fromCharCode(charCode), 'ascii').toString('hex'); return '&#x' + internals.padLeft(hexValue, 2) + ';'; }; internals.padLeft = function (str, len) { while (str.length < len) { str = '0' + str; } return str; }; internals.isSafe = function (charCode) { return (typeof internals.safeCharCodes[charCode] !== 'undefined'); }; internals.namedHtml = { '38': '&', '60': '<', '62': '>', '34': '"', '160': ' ', '162': '¢', '163': '£', '164': '¤', '169': '©', '174': '®' }; internals.safeCharCodes = (function () { const safe = {}; for (let i = 32; i < 123; ++i) { if ((i >= 97) || // a-z (i >= 65 && i <= 90) || // A-Z (i >= 48 && i <= 57) || // 0-9 i === 32 || // space i === 46 || // . i === 44 || // , i === 45 || // - i === 58 || // : i === 95) { // _ safe[i] = null; } } return safe; }()); hoek-4.1.0/lib/index.js000077500000000000000000000575731277002733700147110ustar00rootroot00000000000000'use strict'; // Load modules const Crypto = require('crypto'); const Path = require('path'); const Util = require('util'); const Escape = require('./escape'); // Declare internals const internals = {}; // Clone object or array exports.clone = function (obj, seen) { if (typeof obj !== 'object' || obj === null) { return obj; } seen = seen || new Map(); const lookup = seen.get(obj); if (lookup) { return lookup; } let newObj; let cloneDeep = false; if (!Array.isArray(obj)) { if (Buffer.isBuffer(obj)) { newObj = new Buffer(obj); } else if (obj instanceof Date) { newObj = new Date(obj.getTime()); } else if (obj instanceof RegExp) { newObj = new RegExp(obj); } else { const proto = Object.getPrototypeOf(obj); if (proto && proto.isImmutable) { newObj = obj; } else { newObj = Object.create(proto); cloneDeep = true; } } } else { newObj = []; cloneDeep = true; } seen.set(obj, newObj); if (cloneDeep) { const keys = Object.getOwnPropertyNames(obj); for (let i = 0; i < keys.length; ++i) { const key = keys[i]; const descriptor = Object.getOwnPropertyDescriptor(obj, key); if (descriptor && (descriptor.get || descriptor.set)) { Object.defineProperty(newObj, key, descriptor); } else { newObj[key] = exports.clone(obj[key], seen); } } } return newObj; }; // Merge all the properties of source into target, source wins in conflict, and by default null and undefined from source are applied /*eslint-disable */ exports.merge = function (target, source, isNullOverride /* = true */, isMergeArrays /* = true */) { /*eslint-enable */ exports.assert(target && typeof target === 'object', 'Invalid target value: must be an object'); exports.assert(source === null || source === undefined || typeof source === 'object', 'Invalid source value: must be null, undefined, or an object'); if (!source) { return target; } if (Array.isArray(source)) { exports.assert(Array.isArray(target), 'Cannot merge array onto an object'); if (isMergeArrays === false) { // isMergeArrays defaults to true target.length = 0; // Must not change target assignment } for (let i = 0; i < source.length; ++i) { target.push(exports.clone(source[i])); } return target; } const keys = Object.keys(source); for (let i = 0; i < keys.length; ++i) { const key = keys[i]; const value = source[key]; if (value && typeof value === 'object') { if (!target[key] || typeof target[key] !== 'object' || (Array.isArray(target[key]) !== Array.isArray(value)) || value instanceof Date || Buffer.isBuffer(value) || value instanceof RegExp) { target[key] = exports.clone(value); } else { exports.merge(target[key], value, isNullOverride, isMergeArrays); } } else { if (value !== null && value !== undefined) { // Explicit to preserve empty strings target[key] = value; } else if (isNullOverride !== false) { // Defaults to true target[key] = value; } } } return target; }; // Apply options to a copy of the defaults exports.applyToDefaults = function (defaults, options, isNullOverride) { exports.assert(defaults && typeof defaults === 'object', 'Invalid defaults value: must be an object'); exports.assert(!options || options === true || typeof options === 'object', 'Invalid options value: must be true, falsy or an object'); if (!options) { // If no options, return null return null; } const copy = exports.clone(defaults); if (options === true) { // If options is set to true, use defaults return copy; } return exports.merge(copy, options, isNullOverride === true, false); }; // Clone an object except for the listed keys which are shallow copied exports.cloneWithShallow = function (source, keys) { if (!source || typeof source !== 'object') { return source; } const storage = internals.store(source, keys); // Move shallow copy items to storage const copy = exports.clone(source); // Deep copy the rest internals.restore(copy, source, storage); // Shallow copy the stored items and restore return copy; }; internals.store = function (source, keys) { const storage = {}; for (let i = 0; i < keys.length; ++i) { const key = keys[i]; const value = exports.reach(source, key); if (value !== undefined) { storage[key] = value; internals.reachSet(source, key, undefined); } } return storage; }; internals.restore = function (copy, source, storage) { const keys = Object.keys(storage); for (let i = 0; i < keys.length; ++i) { const key = keys[i]; internals.reachSet(copy, key, storage[key]); internals.reachSet(source, key, storage[key]); } }; internals.reachSet = function (obj, key, value) { const path = key.split('.'); let ref = obj; for (let i = 0; i < path.length; ++i) { const segment = path[i]; if (i + 1 === path.length) { ref[segment] = value; } ref = ref[segment]; } }; // Apply options to defaults except for the listed keys which are shallow copied from option without merging exports.applyToDefaultsWithShallow = function (defaults, options, keys) { exports.assert(defaults && typeof defaults === 'object', 'Invalid defaults value: must be an object'); exports.assert(!options || options === true || typeof options === 'object', 'Invalid options value: must be true, falsy or an object'); exports.assert(keys && Array.isArray(keys), 'Invalid keys'); if (!options) { // If no options, return null return null; } const copy = exports.cloneWithShallow(defaults, keys); if (options === true) { // If options is set to true, use defaults return copy; } const storage = internals.store(options, keys); // Move shallow copy items to storage exports.merge(copy, options, false, false); // Deep copy the rest internals.restore(copy, options, storage); // Shallow copy the stored items and restore return copy; }; // Deep object or array comparison exports.deepEqual = function (obj, ref, options, seen) { options = options || { prototype: true }; const type = typeof obj; if (type !== typeof ref) { return false; } if (type !== 'object' || obj === null || ref === null) { if (obj === ref) { // Copied from Deep-eql, copyright(c) 2013 Jake Luer, jake@alogicalparadox.com, MIT Licensed, https://github.com/chaijs/deep-eql return obj !== 0 || 1 / obj === 1 / ref; // -0 / +0 } return obj !== obj && ref !== ref; // NaN } seen = seen || []; if (seen.indexOf(obj) !== -1) { return true; // If previous comparison failed, it would have stopped execution } seen.push(obj); if (Array.isArray(obj)) { if (!Array.isArray(ref)) { return false; } if (!options.part && obj.length !== ref.length) { return false; } for (let i = 0; i < obj.length; ++i) { if (options.part) { let found = false; for (let j = 0; j < ref.length; ++j) { if (exports.deepEqual(obj[i], ref[j], options)) { found = true; break; } } return found; } if (!exports.deepEqual(obj[i], ref[i], options)) { return false; } } return true; } if (Buffer.isBuffer(obj)) { if (!Buffer.isBuffer(ref)) { return false; } if (obj.length !== ref.length) { return false; } for (let i = 0; i < obj.length; ++i) { if (obj[i] !== ref[i]) { return false; } } return true; } if (obj instanceof Date) { return (ref instanceof Date && obj.getTime() === ref.getTime()); } if (obj instanceof RegExp) { return (ref instanceof RegExp && obj.toString() === ref.toString()); } if (options.prototype) { if (Object.getPrototypeOf(obj) !== Object.getPrototypeOf(ref)) { return false; } } const keys = Object.getOwnPropertyNames(obj); if (!options.part && keys.length !== Object.getOwnPropertyNames(ref).length) { return false; } for (let i = 0; i < keys.length; ++i) { const key = keys[i]; const descriptor = Object.getOwnPropertyDescriptor(obj, key); if (descriptor.get) { if (!exports.deepEqual(descriptor, Object.getOwnPropertyDescriptor(ref, key), options, seen)) { return false; } } else if (!exports.deepEqual(obj[key], ref[key], options, seen)) { return false; } } return true; }; // Remove duplicate items from array exports.unique = (array, key) => { let result; if (key) { result = []; const index = new Set(); array.forEach((item) => { const identifier = item[key]; if (!index.has(identifier)) { index.add(identifier); result.push(item); } }); } else { result = Array.from(new Set(array)); } return result; }; // Convert array into object exports.mapToObject = function (array, key) { if (!array) { return null; } const obj = {}; for (let i = 0; i < array.length; ++i) { if (key) { if (array[i][key]) { obj[array[i][key]] = true; } } else { obj[array[i]] = true; } } return obj; }; // Find the common unique items in two arrays exports.intersect = function (array1, array2, justFirst) { if (!array1 || !array2) { return []; } const common = []; const hash = (Array.isArray(array1) ? exports.mapToObject(array1) : array1); const found = {}; for (let i = 0; i < array2.length; ++i) { if (hash[array2[i]] && !found[array2[i]]) { if (justFirst) { return array2[i]; } common.push(array2[i]); found[array2[i]] = true; } } return (justFirst ? null : common); }; // Test if the reference contains the values exports.contain = function (ref, values, options) { /* string -> string(s) array -> item(s) object -> key(s) object -> object (key:value) */ let valuePairs = null; if (typeof ref === 'object' && typeof values === 'object' && !Array.isArray(ref) && !Array.isArray(values)) { valuePairs = values; values = Object.keys(values); } else { values = [].concat(values); } options = options || {}; // deep, once, only, part exports.assert(arguments.length >= 2, 'Insufficient arguments'); exports.assert(typeof ref === 'string' || typeof ref === 'object', 'Reference must be string or an object'); exports.assert(values.length, 'Values array cannot be empty'); let compare; let compareFlags; if (options.deep) { compare = exports.deepEqual; const hasOnly = options.hasOwnProperty('only'); const hasPart = options.hasOwnProperty('part'); compareFlags = { prototype: hasOnly ? options.only : hasPart ? !options.part : false, part: hasOnly ? !options.only : hasPart ? options.part : true }; } else { compare = (a, b) => a === b; } let misses = false; const matches = new Array(values.length); for (let i = 0; i < matches.length; ++i) { matches[i] = 0; } if (typeof ref === 'string') { let pattern = '('; for (let i = 0; i < values.length; ++i) { const value = values[i]; exports.assert(typeof value === 'string', 'Cannot compare string reference to non-string value'); pattern += (i ? '|' : '') + exports.escapeRegex(value); } const regex = new RegExp(pattern + ')', 'g'); const leftovers = ref.replace(regex, ($0, $1) => { const index = values.indexOf($1); ++matches[index]; return ''; // Remove from string }); misses = !!leftovers; } else if (Array.isArray(ref)) { for (let i = 0; i < ref.length; ++i) { let matched = false; for (let j = 0; j < values.length && matched === false; ++j) { matched = compare(values[j], ref[i], compareFlags) && j; } if (matched !== false) { ++matches[matched]; } else { misses = true; } } } else { const keys = Object.getOwnPropertyNames(ref); for (let i = 0; i < keys.length; ++i) { const key = keys[i]; const pos = values.indexOf(key); if (pos !== -1) { if (valuePairs && !compare(valuePairs[key], ref[key], compareFlags)) { return false; } ++matches[pos]; } else { misses = true; } } } let result = false; for (let i = 0; i < matches.length; ++i) { result = result || !!matches[i]; if ((options.once && matches[i] > 1) || (!options.part && !matches[i])) { return false; } } if (options.only && misses) { return false; } return result; }; // Flatten array exports.flatten = function (array, target) { const result = target || []; for (let i = 0; i < array.length; ++i) { if (Array.isArray(array[i])) { exports.flatten(array[i], result); } else { result.push(array[i]); } } return result; }; // Convert an object key chain string ('a.b.c') to reference (object[a][b][c]) exports.reach = function (obj, chain, options) { if (chain === false || chain === null || typeof chain === 'undefined') { return obj; } options = options || {}; if (typeof options === 'string') { options = { separator: options }; } const path = chain.split(options.separator || '.'); let ref = obj; for (let i = 0; i < path.length; ++i) { let key = path[i]; if (key[0] === '-' && Array.isArray(ref)) { key = key.slice(1, key.length); key = ref.length - key; } if (!ref || !((typeof ref === 'object' || typeof ref === 'function') && key in ref) || (typeof ref !== 'object' && options.functions === false)) { // Only object and function can have properties exports.assert(!options.strict || i + 1 === path.length, 'Missing segment', key, 'in reach path ', chain); exports.assert(typeof ref === 'object' || options.functions === true || typeof ref !== 'function', 'Invalid segment', key, 'in reach path ', chain); ref = options.default; break; } ref = ref[key]; } return ref; }; exports.reachTemplate = function (obj, template, options) { return template.replace(/{([^}]+)}/g, ($0, chain) => { const value = exports.reach(obj, chain, options); return (value === undefined || value === null ? '' : value); }); }; exports.formatStack = function (stack) { const trace = []; for (let i = 0; i < stack.length; ++i) { const item = stack[i]; trace.push([item.getFileName(), item.getLineNumber(), item.getColumnNumber(), item.getFunctionName(), item.isConstructor()]); } return trace; }; exports.formatTrace = function (trace) { const display = []; for (let i = 0; i < trace.length; ++i) { const row = trace[i]; display.push((row[4] ? 'new ' : '') + row[3] + ' (' + row[0] + ':' + row[1] + ':' + row[2] + ')'); } return display; }; exports.callStack = function (slice) { // http://code.google.com/p/v8/wiki/JavaScriptStackTraceApi const v8 = Error.prepareStackTrace; Error.prepareStackTrace = function (_, stack) { return stack; }; const capture = {}; Error.captureStackTrace(capture, this); // arguments.callee is not supported in strict mode so we use this and slice the trace of this off the result const stack = capture.stack; Error.prepareStackTrace = v8; const trace = exports.formatStack(stack); return trace.slice(1 + slice); }; exports.displayStack = function (slice) { const trace = exports.callStack(slice === undefined ? 1 : slice + 1); return exports.formatTrace(trace); }; exports.abortThrow = false; exports.abort = function (message, hideStack) { if (process.env.NODE_ENV === 'test' || exports.abortThrow === true) { throw new Error(message || 'Unknown error'); } let stack = ''; if (!hideStack) { stack = exports.displayStack(1).join('\n\t'); } console.log('ABORT: ' + message + '\n\t' + stack); process.exit(1); }; exports.assert = function (condition /*, msg1, msg2, msg3 */) { if (condition) { return; } if (arguments.length === 2 && arguments[1] instanceof Error) { throw arguments[1]; } let msgs = []; for (let i = 1; i < arguments.length; ++i) { if (arguments[i] !== '') { msgs.push(arguments[i]); // Avoids Array.slice arguments leak, allowing for V8 optimizations } } msgs = msgs.map((msg) => { return typeof msg === 'string' ? msg : msg instanceof Error ? msg.message : exports.stringify(msg); }); throw new Error(msgs.join(' ') || 'Unknown error'); }; exports.Timer = function () { this.ts = 0; this.reset(); }; exports.Timer.prototype.reset = function () { this.ts = Date.now(); }; exports.Timer.prototype.elapsed = function () { return Date.now() - this.ts; }; exports.Bench = function () { this.ts = 0; this.reset(); }; exports.Bench.prototype.reset = function () { this.ts = exports.Bench.now(); }; exports.Bench.prototype.elapsed = function () { return exports.Bench.now() - this.ts; }; exports.Bench.now = function () { const ts = process.hrtime(); return (ts[0] * 1e3) + (ts[1] / 1e6); }; // Escape string for Regex construction exports.escapeRegex = function (string) { // Escape ^$.*+-?=!:|\/()[]{}, return string.replace(/[\^\$\.\*\+\-\?\=\!\:\|\\\/\(\)\[\]\{\}\,]/g, '\\$&'); }; // Base64url (RFC 4648) encode exports.base64urlEncode = function (value, encoding) { exports.assert(typeof value === 'string' || Buffer.isBuffer(value), 'value must be string or buffer'); const buf = (Buffer.isBuffer(value) ? value : new Buffer(value, encoding || 'binary')); return buf.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/\=/g, ''); }; // Base64url (RFC 4648) decode exports.base64urlDecode = function (value, encoding) { if (typeof value !== 'string') { return new Error('Value not a string'); } if (!/^[\w\-]*$/.test(value)) { return new Error('Invalid character'); } const buf = new Buffer(value, 'base64'); return (encoding === 'buffer' ? buf : buf.toString(encoding || 'binary')); }; // Escape attribute value for use in HTTP header exports.escapeHeaderAttribute = function (attribute) { // Allowed value characters: !#$%&'()*+,-./:;<=>?@[]^_`{|}~ and space, a-z, A-Z, 0-9, \, " exports.assert(/^[ \w\!#\$%&'\(\)\*\+,\-\.\/\:;<\=>\?@\[\]\^`\{\|\}~\"\\]*$/.test(attribute), 'Bad attribute value (' + attribute + ')'); return attribute.replace(/\\/g, '\\\\').replace(/\"/g, '\\"'); // Escape quotes and slash }; exports.escapeHtml = function (string) { return Escape.escapeHtml(string); }; exports.escapeJavaScript = function (string) { return Escape.escapeJavaScript(string); }; exports.nextTick = function (callback) { return function () { const args = arguments; process.nextTick(() => { callback.apply(null, args); }); }; }; exports.once = function (method) { if (method._hoekOnce) { return method; } let once = false; const wrapped = function () { if (!once) { once = true; method.apply(null, arguments); } }; wrapped._hoekOnce = true; return wrapped; }; exports.isInteger = function (value) { return (typeof value === 'number' && parseFloat(value) === parseInt(value, 10) && !isNaN(value)); }; exports.ignore = function () { }; exports.inherits = Util.inherits; exports.format = Util.format; exports.transform = function (source, transform, options) { exports.assert(source === null || source === undefined || typeof source === 'object' || Array.isArray(source), 'Invalid source object: must be null, undefined, an object, or an array'); const separator = (typeof options === 'object' && options !== null) ? (options.separator || '.') : '.'; if (Array.isArray(source)) { const results = []; for (let i = 0; i < source.length; ++i) { results.push(exports.transform(source[i], transform, options)); } return results; } const result = {}; const keys = Object.keys(transform); for (let i = 0; i < keys.length; ++i) { const key = keys[i]; const path = key.split(separator); const sourcePath = transform[key]; exports.assert(typeof sourcePath === 'string', 'All mappings must be "." delineated strings'); let segment; let res = result; while (path.length > 1) { segment = path.shift(); if (!res[segment]) { res[segment] = {}; } res = res[segment]; } segment = path.shift(); res[segment] = exports.reach(source, sourcePath, options); } return result; }; exports.uniqueFilename = function (path, extension) { if (extension) { extension = extension[0] !== '.' ? '.' + extension : extension; } else { extension = ''; } path = Path.resolve(path); const name = [Date.now(), process.pid, Crypto.randomBytes(8).toString('hex')].join('-') + extension; return Path.join(path, name); }; exports.stringify = function () { try { return JSON.stringify.apply(null, arguments); } catch (err) { return '[Cannot display object: ' + err.message + ']'; } }; exports.shallow = function (source) { const target = {}; const keys = Object.keys(source); for (let i = 0; i < keys.length; ++i) { const key = keys[i]; target[key] = source[key]; } return target; }; hoek-4.1.0/package.json000066400000000000000000000007561277002733700147500ustar00rootroot00000000000000{ "name": "hoek", "description": "General purpose node utilities", "version": "4.1.0", "repository": "git://github.com/hapijs/hoek", "main": "lib/index.js", "keywords": [ "utilities" ], "engines": { "node": ">=4.0.0" }, "dependencies": {}, "devDependencies": { "code": "3.x.x", "lab": "10.x.x" }, "scripts": { "test": "lab -a code -t 100 -L", "test-cov-html": "lab -a code -t 100 -L -r html -o coverage.html" }, "license": "BSD-3-Clause" } hoek-4.1.0/test/000077500000000000000000000000001277002733700134315ustar00rootroot00000000000000hoek-4.1.0/test/escaper.js000077500000000000000000000043561277002733700154240ustar00rootroot00000000000000'use strict'; // Load modules const Code = require('code'); const Hoek = require('../lib'); const Lab = require('lab'); // Declare internals const internals = {}; // Test shortcuts const lab = exports.lab = Lab.script(); const describe = lab.experiment; const it = lab.test; const expect = Code.expect; describe('escapeJavaScript()', () => { it('encodes / characters', (done) => { const encoded = Hoek.escapeJavaScript(''); expect(encoded).to.equal('\\x3cscript\\x3ealert\\x281\\x29\\x3c\\x2fscript\\x3e'); done(); }); it('encodes \' characters', (done) => { const encoded = Hoek.escapeJavaScript('something(\'param\')'); expect(encoded).to.equal('something\\x28\\x27param\\x27\\x29'); done(); }); it('encodes large unicode characters with the correct padding', (done) => { const encoded = Hoek.escapeJavaScript(String.fromCharCode(500) + String.fromCharCode(1000)); expect(encoded).to.equal('\\u0500\\u1000'); done(); }); it('doesn\'t throw an exception when passed null', (done) => { const encoded = Hoek.escapeJavaScript(null); expect(encoded).to.equal(''); done(); }); }); describe('escapeHtml()', () => { it('encodes / characters', (done) => { const encoded = Hoek.escapeHtml(''); expect(encoded).to.equal('<script>alert(1)</script>'); done(); }); it('encodes < and > as named characters', (done) => { const encoded = Hoek.escapeHtml('