snowyu/property-manager.js

View on GitHub
src/abstract.coffee

Summary

Maintainability
Test Coverage
defineProperty  = require 'util-ex/lib/defineProperty'
isFunction      = require 'util-ex/lib/is/type/function'
isObject        = require 'util-ex/lib/is/type/object'
isString        = require 'util-ex/lib/is/type/string'
isBoolean       = require 'util-ex/lib/is/type/boolean'
isArray         = require 'util-ex/lib/is/type/array'
createObject    = require 'inherits-ex/lib/createObject'
deepEqual       = require 'deep-equal'
getkeys         = Object.keys

module.exports  = class AbstractPropertyManager

  # the property with prefix '$' will not be exported.
  nonExported1stChar: '$'

  constrcutor: ->
    @initialize.apply @, arguments

  assignPropertyTo: (dest, src, name, value, attrs, options)->
  getProperties: ->
  defineProperties: (aProperties)->

  initialize: (src)->
    src?={}
    # defineProperty @, 'defaultOptions', {}
    @defineProperties(src.attributes)
    @assign(src)

  clone: (options)-> @cloneTo {}, options

  cloneTo: (dest, options)->
    dest = @mergeTo(dest, options)
    result = createObject @Class || @constructor
    result.assign(dest)

  assign: (src, aOptions)->
    @__assign(src, aOptions)

  __assign: (src, aOptions)->
    defaultOptions = @defaultOptions
    aOptions = (defaultOptions && defaultOptions.assign) || {} unless aOptions
    aOptions.overwrite ?= true
    aOptions.isExported = false

    {exclude, overwrite} = aOptions
    if isString exclude
      exclude = [exclude]
    else if not isArray exclude
      exclude = []

    vAttrs = @getProperties()
    # sometime the assignment order is very important
    # so we must use the defined properties as the assignment order.
    for k,v of vAttrs
      continue if k in exclude
      if src.hasOwnProperty(k)
        vName = k
      else if (vName = v && v.name) and
          (!src.hasOwnProperty(vName) or (vName in exclude))
        vName = null
      else if !vName and (vAlias = v && v.alias)
        if isString(vAlias)
          vName = vAlias if src.hasOwnProperty(vAlias) and !(vAlias in exclude)
        else if isArray(vAlias)
          for n in vAlias
            if src.hasOwnProperty(n) and !(n in exclude)
              vName = n
              break
      continue unless vName
      v = src[vName]
      if overwrite || @[k] == undefined
        @assignPropertyTo(@, src, k, v, vAttrs, aOptions)

    @_assign(src, aOptions) if isFunction @_assign
    @

  assignProperty: (src, name, value, attrs, options)->
    defaultOptions = @defaultOptions
    options = options || (defaultOptions && defaultOptions.assign) || {}
    options.overwrite ?= true
    options.isExported = false
    @assignPropertyTo(@, src, name, value, attrs, options)
    return

  mergeTo: (dest, aOptions = {})->
    { overwrite, exclude,
      skipDefault, skipReadOnly, isExported, skipNull, skipUndefined
    } = aOptions

    if isString exclude
      exclude = [exclude]
    else if not isArray exclude
      exclude = []

    dest?= {}

    vAttrs = @getProperties()
    # sometime the assignment order is very important
    # so we must use the defined properties as the assignment order.
    for k,v of vAttrs
      continue if k in exclude
      continue if v and v.name and (v.name in exclude)
      continue if skipNull and @[k] is null
      continue if skipUndefined and @[k] is undefined
      vIsReadonly = v.writable == false || (v.get && !v.set)
      # the default is skip readonly unless exported is true.
      # but the SimplePM can not set the exported attribute.
      continue if skipReadOnly and v.exported != true && vIsReadonly
      if overwrite || dest[k] is undefined
        val = aOptions[k]
        val = @[k] unless val?
        @assignPropertyTo(dest, @, k, val, vAttrs, aOptions)
    dest

  exportTo: (dest, aOptions)->
    defaultOptions = @defaultOptions
    aOptions = (defaultOptions && defaultOptions.export) || {} unless aOptions
    aOptions.skipDefault ?= true
    aOptions.skipUndefined ?= true
    aOptions.isExported = true
    @mergeTo(dest, aOptions)

  toObject: (options)->
    result = {}
    @exportTo(result, options)

  toJSON: ->@toObject()

  assignTo: (dest, aOptions)->
    defaultOptions = @defaultOptions
    aOptions = (defaultOptions && defaultOptions.assign) || {} unless aOptions
    aOptions.isExported = false
    aOptions.overwrite = true
    dest = this.mergeTo dest, aOptions
    dest._assign(@, aOptions) if isFunction dest._assign
    dest

  # TODO: deeply compare options
  #   need ignore redundant properties in aOptions,
  #   skip some properties, custom filter.
  isSame: (src, aOptions)->
    for k,v of @assignTo({}, aOptions)
      # continue if k in aExclude
      return false unless deepEqual src[k], v
    return true

module.exports.default = module.exports;