From: dsc Date: Fri, 9 Mar 2012 19:19:27 +0000 (-0800) Subject: Refactors our parsers. X-Git-Url: http://git.less.ly:3516/?a=commitdiff_plain;h=a10026c6ad8d112b6c225de97886216b4bd5c9e4;p=limn-bak.git Refactors our parsers. --- diff --git a/lib/chart/chart-library.co b/lib/chart/chart-library.co new file mode 100644 index 0000000..04c053c --- /dev/null +++ b/lib/chart/chart-library.co @@ -0,0 +1,118 @@ +_ = require 'kraken/underscore' +op = require 'kraken/util/op' +{EventEmitter} = require 'events' +{Parsers, ParserMixin} = require 'kraken/util/parser' + + +/** + * @class Specification for an option. + */ +class exports.ChartOption + SPEC_KEYS : <[ name type default desc tags examples ]> + + name : null + type : 'String' + default : null + desc : '' + tags : null + examples : null + + + (@library, @spec) -> + throw new Error('Each ChartOption requires a name!') unless @spec.name + + for k of @SPEC_KEYS + v = @spec[k] + @[k] = v if v? + @tags or= [] + @parse = @library.getParser @type + + parse : Parsers.parseString + + isDefault: (v) -> + @default is v + + toString: -> "(#{@name}: #{@type})" + + +/** + * @class Abstraction of a charting library, encapsulating its logic and options. + */ +class exports.ChartLibrary extends EventEmitter + /** + * Ordered ChartOption objects. + * @type ChartOption[] + */ + options_ordered : null + + /** + * Map of option name to ChartOption objects. + * @type { name:ChartOption, ... } + */ + options : null + + + + /** + * @constructor + * @param {String} name Library name. + * @param {Array} options List of options objects, each specifying the + * name, type, default, description (etc) of a chart library option. + */ + (@name, options) -> + @options_ordered = _.map options, (opt) ~> new ChartOption this, opt + @options = _.synthesize @options, -> [it.name, it] + + + /** + * @returns {ChartOption} Get an option's spec by name. + */ + get: (name, def) -> + @options[name] or def + + /** + * @returns {Array} List of values found at the given attr on each + * option spec object. + */ + pluck: (attr) -> + _.pluck @spec, attr + + /** + * @returns {Object} An object, mapping from option.name to the + * result of the supplied function. + */ + map: (fn, context=this) -> + _.synthesize @spec, ~> [it.name, fn.call(context, it, it.name, this)] + + + /** + * @returns {Boolean} Whether the supplied value is the same as + * the default value for the given key. + */ + isDefault: (name, value) -> + @get name .isDefault value + + + serialize: (v, k) -> + # if v!? + # v = '' + if _.isBoolean v + v = Number v + else if _.isObject v + v = JSON.stringify v + String v + + + /** + * When implementing a ChartLibrary, you can add or override parsers + * merely by subclassing. + */ + this:: import ParserMixin:: + + getParserFor: (name) -> + @getParser @get(name).type + + + + + diff --git a/lib/chart/dygraphs-library.co b/lib/chart/dygraphs-library.co new file mode 100644 index 0000000..fc80796 --- /dev/null +++ b/lib/chart/dygraphs-library.co @@ -0,0 +1,13 @@ +_ = require 'kraken/underscore' +{ ChartLibrary, ChartOption, +} = require 'kraken/chart/chart-library' + + +class exports.DygraphsLibrary extends ChartLibrary + + (name, options) -> + super ... + + render: -> + ... + diff --git a/lib/chart/index.co b/lib/chart/index.co new file mode 100644 index 0000000..fe6f0b8 --- /dev/null +++ b/lib/chart/index.co @@ -0,0 +1,3 @@ +library = require 'kraken/chart/chart-library' +dygraphs = require 'kraken/chart/dygraphs-library' +exports import library import dygraphs diff --git a/lib/main.co b/lib/main.co index e9e6960..e2d6793 100644 --- a/lib/main.co +++ b/lib/main.co @@ -46,7 +46,7 @@ main = -> if match = /\/graph\/(?!view)([^\/?]+)/i.exec loc data.slug = match[1] - vis = root.vis = new VisModel data + vis = root.vis = new VisModel data, {+parse} graph = root.graph = new VisView do graph_spec : root.CHART_OPTIONS_SPEC model : vis diff --git a/lib/underscore/array.co b/lib/underscore/array.co index 0d5f1d3..76e078a 100644 --- a/lib/underscore/array.co +++ b/lib/underscore/array.co @@ -17,7 +17,7 @@ _array = do _.reduce do o (acc, [k, v], idx) -> - if k and filter(v, k) + if k and (not filter or filter(v, k)) acc[k] = v acc {} @@ -28,10 +28,11 @@ _array = do * @param {Function} [fn=I] Transformation function. Defaults to the identity transform. * @param {Function} [filter=defined] Optional filter function. If omitted, will * exclude `undefined` and `null` values. + * @param {Object} [context=o] Function context. * @return {Object} Transformed result. */ - synthesize : (o, fn=I, filter=defined) -> - _array.generate _.map(o, fn), filter + synthesize : (o, fn=I, filter=defined, context) -> + _array.generate _.map(o, fn, context), filter /** diff --git a/lib/util/aliasdict.co b/lib/util/aliasdict.co new file mode 100644 index 0000000..966c000 --- /dev/null +++ b/lib/util/aliasdict.co @@ -0,0 +1,158 @@ +_ = require 'kraken/underscore' + +/** + * @class A mapping of key-value pairs supporting key-aliases. + */ +class AliasDict + + /** + * Data store. + * @type Object + * @private + */ + _data : null + + /** + * Mapping from keys to an array of [potentially nested] alias-keys. + * @type Object> + * @private + */ + _aliases : null + + + /** + * @constructor + */ + -> + @_data = {} + @_aliases = {} + @extend ... + + + /** + * @returns {Number} Number of real keys in the Dict. + */ + size : -> + _.keys @_data .length + + /** + * @returns {AliasDict} A copy of the AliasDict, including aliases as well as data. + */ + clone: -> + d = new AliasDict @_data + _.each @_aliases, (v, k) -> + d.setAlias k, v.slice() + d + + + + ### Value Accessors ### + + /** + * @returns {Boolean} Whether there is a value at the given key. + */ + has : (key) -> + (@get key)? + + /** + * @returns {*} Ignores aliases, returning the value at key or `undefined`. + */ + getValue : (key) -> + prop = _.getNested @_data, key + prop.value if prop? + + get : (key, def) -> + aliases = @_aliases[key] or [key] + val = aliases.reduce do + (val, alias) -> + return val if val? is not undefined + prop = _.getNested @_data, alias + prop.value if prop? + undefined + + if val is not undefined + val + else + def + + set : (key, val) -> + _.setNested @_data, key, val, true + val + + del : (key) -> + prop = _.getNestedMeta key + if prop + delete prop.obj[prop.key] + prop.value + + + + ### Alias Methods ### + + hasAlias : (key) -> + @_aliases[key]? + + getAlias : (key, def=[]) -> + @_aliases[key] or def + + setAlias: (key, aliases) -> + @_aliases[key] = if _.isArray aliases then aliases else [aliases] + this + + addAlias : (key, ...aliases) -> + @_aliases[key] = _.flatten @getAlias(key, [key]).concat(aliases) + this + + delAlias : (key) -> + delete @_aliases[key] + + + + ### Collection Methods ### + + toObject: -> + _.clone @_data + + keys: -> + _.keys @_data + + values: -> + _.values @_data + + extend : (...args) -> + for o of args + for k,v in o then @set k, v + this + + reduce : (fn, acc, context=this) -> + _.reduce @_data, fn, acc, context + + map : (fn, context=this) -> + _.map @_data, fn, context + + filter: (fn, context=this) -> + _.filter @_data, fn, context + + each : (fn, context=this) -> + _.each @_data, fn, context + this + + invoke : (name, ...args) -> + _.invoke @_data, name, ...args + + pluck : (attr) -> + _.pluck @_data, attr + + find: (fn, context=this) -> + _.find @_data, fn, context + + + toString: -> + Cls = @.constructor + "#{Cls.displayName or Cls.name}()" + + +module.exports = exports = AliasDict + + + diff --git a/lib/util/backbone.co b/lib/util/backbone.co index 4dbe1ed..8246d1d 100644 --- a/lib/util/backbone.co +++ b/lib/util/backbone.co @@ -7,9 +7,9 @@ _backbone = do /** * @returns {Array} The list of all superclasses for this class or object. */ - getSuperClasses: function getSuperClasses(cls) - cls .= constructor if cls and typeof cls is not 'function' - if superclass = cls?.__super__?.constructor + getSuperClasses: function getSuperClasses(Cls) + Cls .= constructor if cls and typeof Cls is not 'function' + if superclass = Cls?.__super__?.constructor [superclass].concat getSuperClasses superclass else [] diff --git a/lib/util/cascade.co b/lib/util/cascade.co new file mode 100644 index 0000000..6258ade --- /dev/null +++ b/lib/util/cascade.co @@ -0,0 +1,130 @@ +_ = require 'kraken/underscore' + +/** + * @class A mapping of key-value pairs supporting lookup fallback across multiple objects. + */ +class Cascade + + /** + * List of objects for lookups. + * @type Array + * @private + */ + _dicts : null + + + /** + * @constructor + */ + -> + @_dicts = [] + @extend ... + + + /** + * @returns {Number} Number of real keys in the Dict. + */ + size : -> + _.keys @_dicts .length + + /** + * @returns {Cascade} A copy of the dict, including fallbacks as well as data. + */ + clone: -> + d = new Cascade + _.each @_dicts, (v, k) -> + d.setAlias k, v.slice() + d + + + + ### Value Accessors ### + + /** + * @returns {Boolean} Whether there is a value at the given key. + */ + has : (key) -> + (@get key, null)? + + /** + * @returns {*} Ignores aliases, returning the value at key or `undefined`. + */ + getValue : (key) -> + prop = _.getNested @_dicts, key + prop.value if prop? + + get : (key, def) -> + aliases = @_aliases[key] or [key] + val = aliases.reduce do + (val, alias) -> + return val if val? is not undefined + prop = _.getNested @_dicts, alias + prop.value if prop? + undefined + + if val is not undefined + val + else + def + + set : (key, val) -> + _.setNested @_dicts, key, val, true + val + + del : (key) -> + prop = _.getNestedMeta key + if prop + delete prop.obj[prop.key] + prop.value + + + + + ### Collection Methods ### + + toObject: -> + _.extend {}, ...@_dicts + + # XXX: Merge keys from all objects? + keys: -> + _.keys @_data + + values: -> + _.values @_data + + extend : (...args) -> + for o of args + for k,v in o then @set k, v + this + + reduce : (fn, acc, context=this) -> + _.reduce @_data, fn, acc, context + + map : (fn, context=this) -> + _.map @_data, fn, context + + filter: (fn, context=this) -> + _.filter @_data, fn, context + + each : (fn, context=this) -> + _.each @_data, fn, context + this + + invoke : (name, ...args) -> + _.invoke @_data, name, ...args + + pluck : (attr) -> + _.pluck @_data, attr + + find: (fn, context=this) -> + _.find @_data, fn, context + + + + toString: -> + Cls = @.constructor + "#{Cls.displayName or Cls.name}()" + + + +module.exports = exports = Cascade diff --git a/lib/util/index.co b/lib/util/index.co index 745238e..285a8f2 100644 --- a/lib/util/index.co +++ b/lib/util/index.co @@ -13,6 +13,7 @@ backbone = require 'kraken/util/backbone' # HashSet = require 'kraken/util/hashset' # BitString = require 'kraken/util/bitstring' # {crc32} = require 'kraken/util/crc' +parser = require 'kraken/util/parser' ## Debug @@ -27,5 +28,5 @@ _.dump = (o, label='dump') -> o -exports import { root, _, op, backbone } -# exports import { root, _, op, HashSet, BitString, crc32, } +exports import { root, _, op, backbone, parser, } +# exports import { root, _, op, HashSet, BitString, crc32, parser, } diff --git a/lib/util/parser.co b/lib/util/parser.co new file mode 100644 index 0000000..e1f9744 --- /dev/null +++ b/lib/util/parser.co @@ -0,0 +1,71 @@ +_ = require 'kraken/underscore' +op = require 'kraken/util/op' + + +/** + * @namespace Parsers by type. + */ +Parsers = exports.Parsers = + + parseBoolean: (v) -> + op.toBool v + + parseInteger: (v) -> + r = op.toInt v + unless isNaN r then r else null + + parseFloat: (v) -> + r = op.toFloat v + unless isNaN r then r else null + + parseString: (v) -> + if v? then op.toStr v else null + + parseArray: (v) -> + if v then op.toObject v else null + + parseObject: (v) -> + if v then op.toObject v else null + + parseFunction: (v) -> + if v and _.startswith String(v), 'function' + try eval "(#v)" catch err then null + else + null + + + +class exports.ParserMixin + this:: import Parsers + + + parse: (v, type) -> + @getParser(type)(v) + + getParser: (type='String') -> + # If this is a known type and we have a parser for it, return that + fn = @["parse#type"] + return fn if typeof fn is 'function' + + # Handle compound/optional types + # XXX: handle 'or' by returning an array of parsers? + type = _ String(type).toLowerCase() + for t of <[ Integer Float Boolean Object Array Function ]> + if type.startsWith t.toLowerCase() + return @["parse#t"] + @defaultParser or @parseString + + getParserFromExample: (v) -> + return null unless v? + type = typeof v + + if type is not 'object' + @getParser type + else if _.isArray v + @getParser 'Array' + else + @getParser 'Object' + + + + diff --git a/www/modules.yaml b/www/modules.yaml index 4dfc388..5510f5c 100644 --- a/www/modules.yaml +++ b/www/modules.yaml @@ -42,8 +42,13 @@ dev: - util: - op - backbone + - parser - index - base + - chart: + - chart-library + - dygraphs-library + - index - template: - graph.jade - graph-option.jade