MODULE_LINK = 'node_modules/kraken'
EXPRESS_DEP_MIME = 'node_modules/express/node_modules/mime'
+
+
+
task \link 'Link package source to node_modules so the name resolves correctly' ->
# Browser-based require doens't support relative requires, but things
# like `require 'kraken/utils'` rarely work in node without this hack.
task \setup 'Ensure project is set up for development.' ->
invoke \install
invoke \link
- invoke \update_version
+
task \server 'Start dev server' ->
invoke \setup
+ invoke \update_version
say ''
run 'lib/server/server.co'
+
task \build 'Build coco sources' ->
invoke \setup
coco <[ -bjc package.co ]>
+
task \test 'Rebuild test files and run tests' ->
invoke \setup
- tests = glob.globSync 'test/*.co'
- tests.forEach (file) ->
- js = file.replace '.co', '.js'
- fs.unlinkSync js if exists js
- coco [ '-bc', file ]
- sh 'expresso'
+ invoke \cleanup_tests
+
+ # Compile tests to JS so Expresso finds them
+ say "Compiling tests...".white.bold
+ glob 'test/**/*.co', {+sync} .forEach -> coco [ '-bc', it ]
+ say 'ok.\n'
+
+ say "Running tests...".white.bold
+ err <- sh 'expresso', {-die}
+ say "#{err and 'yep' or 'ok'}.\n"
+
+ # Clean up JS turds
+ invoke \cleanup_tests
+
+
+task \cleanup_tests 'Removes compiled tests' ->
+ # XXX: erp. only works if no .js files by default :P
+ # say "Cleaning up old test files...".white.bold
+ # js_files = glob 'test/**/*.js', {+sync}
+ # for js of js_files.filter( -> exists it )
+ # say "unlink #js"
+ # fs.unlinkSync js
+ # say 'ok.\n'
+
+ glob 'test/**/*.co', {+sync}
+ .map -> it.replace('.co', '.js')
+ .filter exists
+ .forEach fs.unlinkSync
task \clean 'Clean up environment and artifacts' ->
- remove [MODULE_LINK, EXPRESS_DEP_MIME, 'var', 'tmp/dest'], true
+ invoke \cleanup_tests
+ remove [MODULE_LINK, EXPRESS_DEP_MIME, 'var', 'tmp/dist'], true
+
+
task \source_list 'Print a list of the source file paths.' ->
- invoke \link
+ invoke \setup
{sources} = require 'kraken/server/view-helpers'
say sources("www/modules.yaml", 'dev').join '\n'
# task \dist 'Assemble a distribution package for deploy' ->
+# invoke \cleanup_tests
# ...
* @constructor
*/
(data={}, lookups=[]) ->
- @_data = {} import data
- @_lookups = [@_data].concat lookups.slice()
+ @_data = data
+ @_lookups = [@_data].concat lookups
/**
- * @returns {Cascade} A copy of the lookup chain.
+ * @returns {Cascade} A copy of the data and lookup chain.
*/
clone: ->
- new Cascade @_data, @_lookups
+ new Cascade {} import @_data, @_lookups.slice()
- ### Lookups ###
+ ### Data & Lookups ###
+
+ setData: (data) ->
+ @_data = @_lookups[0] = data
+ this
/**
* Adds a new lookup dictionary to the chain.
* @returns {this}
*/
addLookup: (dict) ->
+ return this unless dict?
+ throw new Error "Lookup dictionary must be an object! dict=#dict" unless dict
@_lookups.push dict
this
* @returns {this}
*/
removeLookup: (dict) ->
- _.remove @_lookups, dict
+ _.remove @_lookups, dict if dict
this
+ /**
+ * @returns {Boolean} Whether the value at `key` belongs to this object or exists in the cascade.
+ */
+ isOwnValue: (key) ->
+ @_data[key] is not void
+
+ /**
+ * @returns {Number} Number of lookup dictionaries.
+ */
+ size: ->
+ @_lookups.length
* @returns {Boolean} Whether there is a value at the given key.
*/
has : (key) ->
- (@get key, undefined) is not undefined
-
+ (@get key, void) is not void
/**
* @returns {*} First value for the given key found in the lookup chain,
*/
get : (key, def) ->
for data of @_lookups
- val = _.getNested data, key, undefined
- return val if val is not undefined
+ if typeof data.get is 'function'
+ val = data.get key, void
+ else
+ val = _.getNested data, key, void
+ return val if val is not void
def
/**
+ * Sets a key to a value, accepting nested keys and creating intermediary objects as necessary.
+ * @public
+ * @name set
* @param {String} key Key to set.
* @param {*} val Non-`undefined` value to set.
* @returns {this}
*/
- set : (key, val) ->
- throw new Error("Value and key cannot be undefined!") unless key and val is not undefined
- _.setNested @_data, key, val
+ /**
+ * @public
+ * @name set
+ * @param {Object} values Map of KV pairs to set. No value may be `undefined`.
+ * @returns {this}
+ */
+ set : (values) ->
+ # Handle @set(k, val)
+ if arguments.length > 1 and typeof values is 'string'
+ [key, val] = arguments
+ throw new Error("Value and key cannot be undefined!") if not key or val is void
+ values = { "#key": val }
+
+ # Trailing `true` in call to `set()` and `setNested()` is to ensure the
+ # creation of missing intermediate objects.
+ if typeof @_data.set is 'function'
+ for key, val in values then @_data.set key, val, true
+ else
+ for key, val in values then _.setNested @_data, key, val, true
+
this
* does not cascade.
* @returns {undefined|*} If found, returns the old value, and otherwise `undefined`.
*/
- del: (key) ->
- _.unsetNested @_data, key, false
+ unset: (key) ->
+ _.unsetNested @_data, key
extend : (...args) ->
for o of args
- for k,v in o then @set k, v
+ for k, v in o then @set k, v
this
toObject: ->
parser = require 'kraken/util/parser'
-## Debug
-_.dump = (o, label='dump') ->
- if not _.isArray(o) and _.isObject(o)
- console.group label
- for k, v in o
- console.log "#k:", v
- console.groupEnd()
- else
- console.log label, o
- o
-
-
exports import { root, _, op, backbone, parser, }
# exports import { root, _, op, HashSet, BitString, crc32, parser, }
--- /dev/null
+coco = require 'coco'
+util = require './util'
+
+exports import util
--- /dev/null
+assert = require 'assert'
+
+_ = require 'kraken/underscore'
+Cascade = require 'kraken/util/cascade'
+
+
+assertEqual = (got, expected, msg) ->
+ assert.equal got, expected, "#msg:\t Expected: #expected;\tGot: #got"
+
+exports.basicCascade = ->
+ a = { a:1 }
+ b = { b:2 }
+ c = { c:3 }
+
+ c1 = new Cascade
+ assertEqual c1.get('a'), void, "[c1] Primary data lookup before set"
+ c1.set 'a', 1
+ assertEqual c1.get('a'), 1, "[c1] Primary data lookup (after set)"
+ assertEqual c1.get('b'), void, "[c1] Cascade lookup (depth=1, before set)"
+ c1.addLookup b
+ assertEqual c1.get('a'), 1, "[c1] Primary data lookup (after lookup added)"
+ assertEqual c1.get('b'), 2, "[c1] Cascade lookup (depth=1, first)"
+ assertEqual c1.get('c'), void, "[c1] Cascade lookup (depth=2, unset)"
+ c1.addLookup c
+ assert.ok c1.has('a'), "[c1] Cascade has 'a' key (depth=0)"
+ assert.ok c1.has('b'), "[c1] Cascade has 'b' key (depth=1)"
+ assert.ok c1.has('c'), "[c1] Cascade has 'c' key (depth=2)"
+ assertEqual c1.get('a'), 1, "[c1] Primary data lookup (after lookup #2 added)"
+ assertEqual c1.get('b'), 2, "[c1] Cascade lookup (depth=1, second)"
+ assertEqual c1.get('c'), 3, "[c1] Cascade lookup (depth=2, first)"
+ c1.removeLookup b
+ assertEqual c1.get('a'), 1, "[c1] Primary data lookup (after lookup b removed)"
+ assertEqual c1.get('b'), void, "[c1] Cascade lookup (depth=1, third)"
+ assertEqual c1.get('c'), 3, "[c1] Cascade lookup (depth=2, removed)"
+ c1.removeLookup c
+ assertEqual c1.get('a'), 1, "[c1] Primary data lookup (after lookup c removed)"
+ assertEqual c1.get('b'), void, "[c1] Cascade lookup (depth=1, removed)"
+ c1.unset 'a'
+ assertEqual c1.get('a'), void, "[c1] Primary data lookup (after removed)"
+
+ c2 = new Cascade a, [b, c]
+ assert.notStrictEqual c1, c2, "[c2] Different Cascades should differ"
+
+ assert.ok c2.has('a'), "[c2] Cascade has 'a' key (depth=0)"
+ assert.ok c2.has('b'), "[c2] Cascade has 'b' key (depth=1)"
+ assert.ok c2.has('c'), "[c2] Cascade has 'c' key (depth=2)"
+ assertEqual c2.get('a'), 1, "[c2] Primary data lookup"
+ assertEqual c2.get('b'), 2, "[c2] Cascade lookup (depth=1, first)"
+ assertEqual c2.get('c'), 3, "[c2] Cascade lookup (depth=2, first)"
+ c2.removeLookup b
+ assertEqual c2.get('a'), 1, "[c2] Primary data lookup (after lookup b removed)"
+ assertEqual c2.get('b'), void, "[c2] Cascade lookup (depth=1, second)"
+ assertEqual c2.get('c'), 3, "[c2] Cascade lookup (depth=2, removed)"
+ c2.removeLookup c
+ assertEqual c2.get('a'), 1, "[c2] Primary data lookup (after lookup c removed)"
+ assertEqual c2.get('b'), void, "[c2] Cascade lookup (depth=1, removed)"
+ c2.unset 'a'
+ assertEqual c2.get('a'), void, "[c2] Primary data lookup (after removed)"
+
+
+exports.nestedKeys = ->
+ o = {}
+ a =
+ lol: 'cats'
+ hat: false
+ foo: bar:1
+ b =
+ lol: 'clowns'
+ hat: fez:true
+ baz: feh:2
+ bats: 13
+
+ c = new Cascade o, [a, b]
+
+ assertEqual c.get('lol'), 'cats', "Shadow c.get('lol')"
+ assertEqual c.get('bats'), 13, "Simple cascade c.get('bats')"
+ assertEqual c.get('foo.bar'), 1, "Nested c.get('foo.bar')"
+ assertEqual c.get('baz.feh'), 2, "Nested cascade c.get('bar.feh')"
+
+ assertEqual c.get('hat'), false, "Shadow non-cascade c.get('hat')"
+ assertEqual c.get('hat.fez'), true, "Unshadow due to cascade c.get('hat.fez')"
+
+ c.set 'hat.fez', 'red'
+ assertEqual c.get('hat.fez'), 'red', "After nested set c.get('hat.fez')"
+ assertEqual o.hat?.fez, 'red', "After nested set o.hat?.fez"
+ assertEqual b.hat?.fez, true, "After nested set b.hat?.fez"
+
+ c.unset 'hat.fez'
+ assertEqual c.get('hat.fez'), true, "After unset c.get('hat.fez')"
+ assertEqual o.hat?.fez, void, "After unset o.hat?.fez"
+
+
+
+
--- /dev/null
+cascade = require './cascade-test'
+exports import cascade
- es5-shim.min
- modernizr.min
- json2.min
- - jquery.min
+
+ - jquery
- jquery.history.min
- jquery.hotkeys.min
- jquery.isotope.min
- # - jquery.tipsy.min # handled by bootstrap now?
- - spin.min
- - jquery.spin.min
+
+ # handled by bootstrap now?
+ # - jquery.tipsy.min
+
+ # - spin.min
+ # - jquery.spin.min
- bootstrap.min
+
+ # Browserify must come before any .mod files
- browserify
- # - require
- - underscore.mod.min
- - underscore.string.mod.min
- - backbone.mod.min
- - backbone.nested.mod.min
+
+ - underscore.mod
+ - underscore.string.mod
+
+ - backbone.mod
+ - backbone.nested.mod
+ - synapse.mod
+
- showdown.mod.min
- jade.runtime.min
- - dygraph.min
+
+ - dygraph
- suffix: .mod.js
paths: