obfusk-data 0.0.2.SNAPSHOT.20130211214657
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.yardopts +1 -0
- data/README.md +96 -0
- data/Rakefile +32 -0
- data/lib/obfusk/data/version.rb +6 -0
- data/lib/obfusk/data.rb +221 -0
- data/obfusk-data.gemspec +29 -0
- data/spec/obfusk/data_spec.rb +149 -0
- metadata +90 -0
    
        data/.yardopts
    ADDED
    
    | @@ -0,0 +1 @@ | |
| 1 | 
            +
            --markup markdown
         | 
    
        data/README.md
    ADDED
    
    | @@ -0,0 +1,96 @@ | |
| 1 | 
            +
            []: {{{1
         | 
| 2 | 
            +
             | 
| 3 | 
            +
                File        : README.md
         | 
| 4 | 
            +
                Maintainer  : Felix C. Stegerman <flx@obfusk.net>
         | 
| 5 | 
            +
                Date        : 2013-02-11
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                Copyright   : Copyright (C) 2013  Felix C. Stegerman
         | 
| 8 | 
            +
                Version     : 0.0.2.SNAPSHOT
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            []: }}}1
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            ## Description
         | 
| 13 | 
            +
            []: {{{1
         | 
| 14 | 
            +
             | 
| 15 | 
            +
              [rb-]obfusk-data - data validation combinator library for ruby
         | 
| 16 | 
            +
             | 
| 17 | 
            +
              ...
         | 
| 18 | 
            +
             | 
| 19 | 
            +
              https://github.com/obfusk/clj-obfusk-data in ruby.
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            ```ruby
         | 
| 22 | 
            +
            isa       = ->(cls, obj) { cls === obj } .curry
         | 
| 23 | 
            +
            is_string = isa[String]
         | 
| 24 | 
            +
            is_email  = ->(x) { %r{^.*@.*\.[a-z]+$}.match x }
         | 
| 25 | 
            +
             | 
| 26 | 
            +
            address = Obfusk::Data.data do
         | 
| 27 | 
            +
              field [:street, :number, :postal_code, :town], [is_string]
         | 
| 28 | 
            +
              field :country, [is_string], optional: true
         | 
| 29 | 
            +
            end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
            person = Obfusk::Data.data do
         | 
| 32 | 
            +
              field [:first_name, :last_name, :phone_number], [is_string]
         | 
| 33 | 
            +
              field :email, [is_string, is_email]
         | 
| 34 | 
            +
              field :address, [], isa: [address]
         | 
| 35 | 
            +
            end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
            tree = Obfusk::Data.union :type do |_tree|
         | 
| 38 | 
            +
              data :empty
         | 
| 39 | 
            +
              data :leaf do
         | 
| 40 | 
            +
                field :value, []
         | 
| 41 | 
            +
              end
         | 
| 42 | 
            +
              data :node do
         | 
| 43 | 
            +
                field [:left, :right], [], isa: [_tree]
         | 
| 44 | 
            +
              end
         | 
| 45 | 
            +
            end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
            Obfusk::Data.valid? tree,
         | 
| 48 | 
            +
              { type: :node,
         | 
| 49 | 
            +
                left: { type: :empty },
         | 
| 50 | 
            +
                right: { type: :leaf, value: "spam!" } }
         | 
| 51 | 
            +
            # => true
         | 
| 52 | 
            +
            ```
         | 
| 53 | 
            +
             | 
| 54 | 
            +
            []: }}}1
         | 
| 55 | 
            +
             | 
| 56 | 
            +
            ## Specs & Docs
         | 
| 57 | 
            +
            []: {{{1
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                $ rake spec
         | 
| 60 | 
            +
                $ rake docs
         | 
| 61 | 
            +
             | 
| 62 | 
            +
            []: }}}1
         | 
| 63 | 
            +
             | 
| 64 | 
            +
            ## TODO
         | 
| 65 | 
            +
            []: {{{1
         | 
| 66 | 
            +
             | 
| 67 | 
            +
              * add more ruby-ish api ?
         | 
| 68 | 
            +
             | 
| 69 | 
            +
            #
         | 
| 70 | 
            +
             | 
| 71 | 
            +
              * write more specs
         | 
| 72 | 
            +
              * write more docs
         | 
| 73 | 
            +
              * show isa errors
         | 
| 74 | 
            +
              * ...
         | 
| 75 | 
            +
             | 
| 76 | 
            +
            []: }}}1
         | 
| 77 | 
            +
             | 
| 78 | 
            +
            ## License
         | 
| 79 | 
            +
            []: {{{1
         | 
| 80 | 
            +
             | 
| 81 | 
            +
              GPLv2 [1] or EPLv1 [2].
         | 
| 82 | 
            +
             | 
| 83 | 
            +
            []: }}}1
         | 
| 84 | 
            +
             | 
| 85 | 
            +
            ## References
         | 
| 86 | 
            +
            []: {{{1
         | 
| 87 | 
            +
             | 
| 88 | 
            +
              [1] GNU General Public License, version 2
         | 
| 89 | 
            +
              --- http://www.opensource.org/licenses/GPL-2.0
         | 
| 90 | 
            +
             | 
| 91 | 
            +
              [2] Eclipse Public License, version 1
         | 
| 92 | 
            +
              --- http://www.opensource.org/licenses/EPL-1.0
         | 
| 93 | 
            +
             | 
| 94 | 
            +
            []: }}}1
         | 
| 95 | 
            +
             | 
| 96 | 
            +
            []: ! ( vim: set tw=70 sw=2 sts=2 et fdm=marker : )
         | 
    
        data/Rakefile
    ADDED
    
    | @@ -0,0 +1,32 @@ | |
| 1 | 
            +
            desc 'Run specs'
         | 
| 2 | 
            +
            task :spec do
         | 
| 3 | 
            +
              sh 'rspec -cfd'
         | 
| 4 | 
            +
            end
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            desc 'Generate docs'
         | 
| 7 | 
            +
            task :docs do
         | 
| 8 | 
            +
              sh 'yardoc | cat'
         | 
| 9 | 
            +
            end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            desc 'List undocumented objects'
         | 
| 12 | 
            +
            task :undocumented do
         | 
| 13 | 
            +
              sh 'yard stats --list-undoc'
         | 
| 14 | 
            +
            end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            desc 'Cleanup'
         | 
| 17 | 
            +
            task :clean do
         | 
| 18 | 
            +
              sh 'rm -rf .yardoc/ doc/ *.gem'
         | 
| 19 | 
            +
            end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            desc 'Build SNAPSHOT gem'
         | 
| 22 | 
            +
            task :snapshot do
         | 
| 23 | 
            +
              v = Time.new.strftime '%Y%m%d%H%M%S'
         | 
| 24 | 
            +
              f = 'lib/obfusk/data/version.rb'
         | 
| 25 | 
            +
              sh "sed -ri~ 's!(SNAPSHOT)!\\1.#{v}!' #{f}"
         | 
| 26 | 
            +
              sh 'gem build obfusk-data.gemspec'
         | 
| 27 | 
            +
            end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
            desc 'Undo SNAPSHOT gem'
         | 
| 30 | 
            +
            task 'snapshot-undo' do
         | 
| 31 | 
            +
              sh 'git checkout -- lib/obfusk/data/version.rb'
         | 
| 32 | 
            +
            end
         | 
    
        data/lib/obfusk/data.rb
    ADDED
    
    | @@ -0,0 +1,221 @@ | |
| 1 | 
            +
            # --                                                            ; {{{1
         | 
| 2 | 
            +
            #
         | 
| 3 | 
            +
            # File        : obfusk/data.rb
         | 
| 4 | 
            +
            # Maintainer  : Felix C. Stegerman <flx@obfusk.net>
         | 
| 5 | 
            +
            # Date        : 2013-02-11
         | 
| 6 | 
            +
            #
         | 
| 7 | 
            +
            # Copyright   : Copyright (C) 2013  Felix C. Stegerman
         | 
| 8 | 
            +
            # Licence     : GPLv2 or EPLv1
         | 
| 9 | 
            +
            #
         | 
| 10 | 
            +
            # --                                                            ; }}}1
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            require 'hamster'
         | 
| 13 | 
            +
            require 'set'
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            module Obfusk
         | 
| 16 | 
            +
              module Data
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                # this proxy allows us to define recursive types
         | 
| 19 | 
            +
                PROXY = ->(; f, bind, proxy) {                              # {{{1
         | 
| 20 | 
            +
                  proxy = ->(*args) {
         | 
| 21 | 
            +
                    raise 'unbound PROXY called!' unless f                  # TODO
         | 
| 22 | 
            +
                    f[*args]
         | 
| 23 | 
            +
                  }
         | 
| 24 | 
            +
                  bind = ->(g) {
         | 
| 25 | 
            +
                    raise 'PROXY can only be bound once' if f               # TODO
         | 
| 26 | 
            +
                    f = g
         | 
| 27 | 
            +
                  }
         | 
| 28 | 
            +
                  [proxy, bind]
         | 
| 29 | 
            +
                }                                                           # }}}1
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                # this helper allows us to run field in the block passed to data
         | 
| 32 | 
            +
                class FieldsHelper                                          # {{{1
         | 
| 33 | 
            +
                  # init
         | 
| 34 | 
            +
                  def initialize
         | 
| 35 | 
            +
                    @_fields = []
         | 
| 36 | 
            +
                  end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                  # add field
         | 
| 39 | 
            +
                  def field (*args)
         | 
| 40 | 
            +
                    @_fields << Obfusk::Data.field(*args)
         | 
| 41 | 
            +
                  end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                  # get fields
         | 
| 44 | 
            +
                  def _fields
         | 
| 45 | 
            +
                    @_fields
         | 
| 46 | 
            +
                  end
         | 
| 47 | 
            +
                end                                                         # }}}1
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                # this helper allows us to run data in the block passed to union
         | 
| 50 | 
            +
                class DatasHelper                                           # {{{1
         | 
| 51 | 
            +
                  # init
         | 
| 52 | 
            +
                  def initialize
         | 
| 53 | 
            +
                    @_datas = {}
         | 
| 54 | 
            +
                  end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                  # add data
         | 
| 57 | 
            +
                  def data (value, &block)
         | 
| 58 | 
            +
                    @_datas[value] = Obfusk::Data._blk_flds block
         | 
| 59 | 
            +
                  end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                  # get datas
         | 
| 62 | 
            +
                  def _datas
         | 
| 63 | 
            +
                    @_datas
         | 
| 64 | 
            +
                  end
         | 
| 65 | 
            +
                end                                                         # }}}1
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                # --
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                # empty sequence?
         | 
| 70 | 
            +
                def self._blank? (x)
         | 
| 71 | 
            +
                  String === x || Enumerable === x ? x.empty? : false
         | 
| 72 | 
            +
                end
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                # add error
         | 
| 75 | 
            +
                def self._error (st, *msg)
         | 
| 76 | 
            +
                  e1 = st[:errors]
         | 
| 77 | 
            +
                  e2 = e1.add msg.join
         | 
| 78 | 
            +
                  st.put :errors, e2
         | 
| 79 | 
            +
                end
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                # --
         | 
| 82 | 
            +
             | 
| 83 | 
            +
                # if only we had clojure's :keys ;-(
         | 
| 84 | 
            +
                def self._get_keys (x, keys)
         | 
| 85 | 
            +
                  x.values_at *keys.map(&:to_sym)
         | 
| 86 | 
            +
                end
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                # run block in instance of helper
         | 
| 89 | 
            +
                def self._blk_hlp (cls, block, *args)
         | 
| 90 | 
            +
                  x = cls.new
         | 
| 91 | 
            +
                  x.instance_exec *args, &block
         | 
| 92 | 
            +
                  x
         | 
| 93 | 
            +
                end
         | 
| 94 | 
            +
             | 
| 95 | 
            +
                # turn block into fields using FieldsHelper
         | 
| 96 | 
            +
                def self._blk_flds (block, *args)
         | 
| 97 | 
            +
                  block ? _blk_hlp(FieldsHelper, block, *args)._fields : []
         | 
| 98 | 
            +
                end
         | 
| 99 | 
            +
             | 
| 100 | 
            +
                # --
         | 
| 101 | 
            +
             | 
| 102 | 
            +
                # data validator w/o block magic
         | 
| 103 | 
            +
                def self.data_ (fields, opts = {})                          # {{{1
         | 
| 104 | 
            +
                  o_flds  = opts[:other_fields]
         | 
| 105 | 
            +
                  isa     = opts.fetch :isa, []
         | 
| 106 | 
            +
                  st      = Hamster.hash errors: Hamster.vector,
         | 
| 107 | 
            +
                              processed: Hamster.set
         | 
| 108 | 
            +
             | 
| 109 | 
            +
                  ->(x ; st_, ks, pks, eks) {
         | 
| 110 | 
            +
                    if isa.any? { |x| validate x }
         | 
| 111 | 
            +
                      _error st '[data] has failed isa'                     # TODO
         | 
| 112 | 
            +
                    else
         | 
| 113 | 
            +
                      st_ = fields.reduce(st) { |s, f| f[x, s] }
         | 
| 114 | 
            +
                      ks  = x.keys.to_set
         | 
| 115 | 
            +
                      pks = st_[:processed]
         | 
| 116 | 
            +
                      eks = ks - pks
         | 
| 117 | 
            +
             | 
| 118 | 
            +
                      if !o_flds && !eks.empty?
         | 
| 119 | 
            +
                        _error st_, '[data] has extraneous fields'
         | 
| 120 | 
            +
                      elsif o_flds.respond_to?(:to_proc) && !eks.all?(&o_flds)
         | 
| 121 | 
            +
                        _error st_, '[data] has invalid other fields'
         | 
| 122 | 
            +
                      else
         | 
| 123 | 
            +
                        st_
         | 
| 124 | 
            +
                      end
         | 
| 125 | 
            +
                    end
         | 
| 126 | 
            +
                  }
         | 
| 127 | 
            +
                end                                                         # }}}1
         | 
| 128 | 
            +
             | 
| 129 | 
            +
                # A data validator.  ...
         | 
| 130 | 
            +
                def self.data (opts = {}, &block)
         | 
| 131 | 
            +
                  proxy, bind = PROXY[]
         | 
| 132 | 
            +
                  data        = data_ _blk_flds(block, proxy), opts
         | 
| 133 | 
            +
                  bind[data]
         | 
| 134 | 
            +
                  data
         | 
| 135 | 
            +
                end
         | 
| 136 | 
            +
             | 
| 137 | 
            +
                # --
         | 
| 138 | 
            +
             | 
| 139 | 
            +
                # validate a field
         | 
| 140 | 
            +
                def self._validate_field (name, predicates, opts, x, st)    # {{{1
         | 
| 141 | 
            +
                  field = x[name]
         | 
| 142 | 
            +
                  preds = predicates.map(&:to_proc)
         | 
| 143 | 
            +
                  isa   = opts.fetch :isa, []
         | 
| 144 | 
            +
             | 
| 145 | 
            +
                  st_   = st.put :processed, st[:processed].add(name)
         | 
| 146 | 
            +
                  err   = ->(*msg) { _error st_, (opts[:message] || msg.join) }
         | 
| 147 | 
            +
             | 
| 148 | 
            +
                  optional, o_nil, blank, o_if, o_if_not =
         | 
| 149 | 
            +
                    _get_keys opts, %w{ optional o_nil blank o_if o_if_not }
         | 
| 150 | 
            +
             | 
| 151 | 
            +
                  if (o_if && !o_if[x]) || (o_if_not && o_if_not[x])
         | 
| 152 | 
            +
                    st_
         | 
| 153 | 
            +
                  elsif !x.has_key? name
         | 
| 154 | 
            +
                    optional ? st_ : err['[field] not found: ', name]
         | 
| 155 | 
            +
                  elsif field.nil?
         | 
| 156 | 
            +
                    o_nil || optional ? st_ : err['[field] is nil: ', name]
         | 
| 157 | 
            +
                  elsif _blank?(field) && !(blank || optional)
         | 
| 158 | 
            +
                    err['[field] is blank: ', name]
         | 
| 159 | 
            +
                  elsif !preds.all? { |p| p[field] }
         | 
| 160 | 
            +
                    err['[field] has failed predicates: ', name]
         | 
| 161 | 
            +
                  elsif isa.any? { |x| validate x, field }
         | 
| 162 | 
            +
                    err['[field] has failed isa: ', name]                   # TODO
         | 
| 163 | 
            +
                  else
         | 
| 164 | 
            +
                    st_
         | 
| 165 | 
            +
                  end
         | 
| 166 | 
            +
                end                                                         # }}}1
         | 
| 167 | 
            +
             | 
| 168 | 
            +
                # A data field.  Predicates are functions that are invoked with
         | 
| 169 | 
            +
                # the field's value, and must all return true.  ...
         | 
| 170 | 
            +
                def self.field (names, predicates, opts = {})
         | 
| 171 | 
            +
                  f   = ->(n, x, s) { _validate_field n, predicates, opts, x, s }
         | 
| 172 | 
            +
                  ns  = Enumerable === names ? names : [names]
         | 
| 173 | 
            +
             | 
| 174 | 
            +
                  ->(x, st) { ns.reduce(st) { |s, name| f[name, x, s] } }
         | 
| 175 | 
            +
                end
         | 
| 176 | 
            +
             | 
| 177 | 
            +
                # --
         | 
| 178 | 
            +
             | 
| 179 | 
            +
                # union w/o block magic and data wrapping
         | 
| 180 | 
            +
                def self.union__ (key, datas)
         | 
| 181 | 
            +
                  f = field key, [->(x) { Symbol === x }]
         | 
| 182 | 
            +
             | 
| 183 | 
            +
                  ->(x, st ; fields) {
         | 
| 184 | 
            +
                    fields = datas.fetch(x.fetch(key)) + [f]
         | 
| 185 | 
            +
                    fields.reduce(st) { |s, field| field[x, s] }
         | 
| 186 | 
            +
                  }
         | 
| 187 | 
            +
                end
         | 
| 188 | 
            +
             | 
| 189 | 
            +
                # union w/o block magic
         | 
| 190 | 
            +
                def self.union_ (key, datas, opts = {})
         | 
| 191 | 
            +
                  data_ [union__(key, datas)], opts
         | 
| 192 | 
            +
                end
         | 
| 193 | 
            +
             | 
| 194 | 
            +
                # Union of data fields.  ...
         | 
| 195 | 
            +
                def self.union (key, opts = {}, &block)
         | 
| 196 | 
            +
                  proxy, bind = PROXY[]
         | 
| 197 | 
            +
                  datas       = _blk_hlp(DatasHelper, block, proxy)._datas
         | 
| 198 | 
            +
                  data        = union_ key, datas, opts
         | 
| 199 | 
            +
                  bind[data]
         | 
| 200 | 
            +
                  data
         | 
| 201 | 
            +
                end
         | 
| 202 | 
            +
             | 
| 203 | 
            +
                # --
         | 
| 204 | 
            +
             | 
| 205 | 
            +
                # Validate.
         | 
| 206 | 
            +
                # @return [nil]       if valid
         | 
| 207 | 
            +
                # @return [<String>]  errors otherwise
         | 
| 208 | 
            +
                def self.validate (f, x)
         | 
| 209 | 
            +
                  e = f[x][:errors]
         | 
| 210 | 
            +
                  e.empty? ? nil : e.to_a
         | 
| 211 | 
            +
                end
         | 
| 212 | 
            +
             | 
| 213 | 
            +
                # Validate.
         | 
| 214 | 
            +
                def self.valid? (f, x)
         | 
| 215 | 
            +
                  validate(f, x).nil?
         | 
| 216 | 
            +
                end
         | 
| 217 | 
            +
             | 
| 218 | 
            +
              end
         | 
| 219 | 
            +
            end
         | 
| 220 | 
            +
             | 
| 221 | 
            +
            # vim: set tw=70 sw=2 sts=2 et fdm=marker :
         | 
    
        data/obfusk-data.gemspec
    ADDED
    
    | @@ -0,0 +1,29 @@ | |
| 1 | 
            +
            require File.expand_path('../lib/obfusk/data/version', __FILE__)
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            Gem::Specification.new do |s|
         | 
| 4 | 
            +
              s.name        = 'obfusk-data'
         | 
| 5 | 
            +
              s.homepage    = 'https://github.com/obfusk/rb-obfusk-data'
         | 
| 6 | 
            +
              s.summary     = 'data validation combinator library for ruby'
         | 
| 7 | 
            +
             | 
| 8 | 
            +
              s.description = <<-END.gsub(/^ {4}/, '')                      # TODO
         | 
| 9 | 
            +
                ...
         | 
| 10 | 
            +
              END
         | 
| 11 | 
            +
             | 
| 12 | 
            +
              s.version     = Obfusk::Data::VERSION
         | 
| 13 | 
            +
              s.date        = Obfusk::Data::DATE
         | 
| 14 | 
            +
             | 
| 15 | 
            +
              s.authors     = [ 'Felix C. Stegerman' ]
         | 
| 16 | 
            +
              s.email       = %w{ flx@obfusk.net }
         | 
| 17 | 
            +
             | 
| 18 | 
            +
              s.licenses    = %w{ GPLv2 EPLv1 }
         | 
| 19 | 
            +
             | 
| 20 | 
            +
              s.files       = %w{ .yardopts README.md Rakefile } \
         | 
| 21 | 
            +
                            + Dir[*%w{ {lib,spec}/**/*.rb *.gemspec }]
         | 
| 22 | 
            +
             | 
| 23 | 
            +
              s.add_runtime_dependency      'hamster'
         | 
| 24 | 
            +
             | 
| 25 | 
            +
              s.add_development_dependency  'rake'
         | 
| 26 | 
            +
              s.add_development_dependency  'rspec'
         | 
| 27 | 
            +
             | 
| 28 | 
            +
              s.required_ruby_version = '>= 1.9.1'
         | 
| 29 | 
            +
            end
         | 
| @@ -0,0 +1,149 @@ | |
| 1 | 
            +
            # --                                                            ; {{{1
         | 
| 2 | 
            +
            #
         | 
| 3 | 
            +
            # File        : obfusk/data.rb
         | 
| 4 | 
            +
            # Maintainer  : Felix C. Stegerman <flx@obfusk.net>
         | 
| 5 | 
            +
            # Date        : 2013-02-08
         | 
| 6 | 
            +
            #
         | 
| 7 | 
            +
            # Copyright   : Copyright (C) 2013  Felix C. Stegerman
         | 
| 8 | 
            +
            # Licence     : GPLv2 or EPLv1
         | 
| 9 | 
            +
            #
         | 
| 10 | 
            +
            # --                                                            ; }}}1
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            require 'obfusk/data'
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            # --
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            isa       = ->(cls, obj) { cls === obj } .curry
         | 
| 17 | 
            +
            is_string = isa[String]
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            is_email  = ->(x) { %r{^.*@.*\.[a-z]+$}.match x }
         | 
| 20 | 
            +
            is_url    = ->(x) { %r{^https?://.*$}.match x }
         | 
| 21 | 
            +
             | 
| 22 | 
            +
            is_object_id = ->(x) {
         | 
| 23 | 
            +
              is_string[x] && x.length == 16 &&
         | 
| 24 | 
            +
                x.chars.all? { |c| %{^[a-zA-Z0-9]$}.match c }
         | 
| 25 | 
            +
            }
         | 
| 26 | 
            +
             | 
| 27 | 
            +
            is_id_seq = ->(x) { isa[Enumberable, x] && x.all?(&is_object_id) }
         | 
| 28 | 
            +
            is_id_map = ->(m) {
         | 
| 29 | 
            +
              isa[Hash, m] && m.all? { |k,v| is_string[k] && is_object_id[v] }
         | 
| 30 | 
            +
            }
         | 
| 31 | 
            +
             | 
| 32 | 
            +
            # --
         | 
| 33 | 
            +
             | 
| 34 | 
            +
            foo = Obfusk::Data.data other_fields: ->(x) { %r{^data_}.match x }
         | 
| 35 | 
            +
             | 
| 36 | 
            +
            tree = Obfusk::Data.union :type do |_tree|
         | 
| 37 | 
            +
              data :empty
         | 
| 38 | 
            +
              data :leaf do
         | 
| 39 | 
            +
                field :value, []
         | 
| 40 | 
            +
              end
         | 
| 41 | 
            +
              data :node do
         | 
| 42 | 
            +
                field [:left, :right], [], isa: [_tree]
         | 
| 43 | 
            +
              end
         | 
| 44 | 
            +
            end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
            # --
         | 
| 47 | 
            +
             | 
| 48 | 
            +
            address = Obfusk::Data.data do
         | 
| 49 | 
            +
              field [:street, :number, :postal_code, :town], [is_string]
         | 
| 50 | 
            +
              field :country, [is_string], optional: true
         | 
| 51 | 
            +
            end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
            person = Obfusk::Data.data do
         | 
| 54 | 
            +
              field [:first_name, :last_name, :phone_number], [is_string]
         | 
| 55 | 
            +
              field :email, [is_string, is_email]
         | 
| 56 | 
            +
              field :address, [], isa: [address]
         | 
| 57 | 
            +
            end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
            # --
         | 
| 60 | 
            +
             | 
| 61 | 
            +
            collection = Obfusk::Data.data do
         | 
| 62 | 
            +
              field :_id  , [is_object_id]
         | 
| 63 | 
            +
              field :app  , [is_string]
         | 
| 64 | 
            +
              field :icon , [is_object_id]
         | 
| 65 | 
            +
              field :items, [is_id_seq]
         | 
| 66 | 
            +
              field :title, [is_string], optional: true
         | 
| 67 | 
            +
            end
         | 
| 68 | 
            +
             | 
| 69 | 
            +
            item = Obfusk::Data.data do
         | 
| 70 | 
            +
              field :_id            , [is_object_id]
         | 
| 71 | 
            +
              field :type           , [is_string]
         | 
| 72 | 
            +
              field :icon           , [is_object_id], nil: true
         | 
| 73 | 
            +
              field :data           , []            , optional: true
         | 
| 74 | 
            +
              field :title          , [is_string]   , optional: true
         | 
| 75 | 
            +
              field :url            , [is_url]      , optional: true
         | 
| 76 | 
            +
              field [:refs, :files] , [is_id_map]   , optional: true
         | 
| 77 | 
            +
             | 
| 78 | 
            +
              # (not= (contains? x :url)
         | 
| 79 | 
            +
              #       (contains? (get x :files {}) :url))                   ; TODO
         | 
| 80 | 
            +
            end
         | 
| 81 | 
            +
             | 
| 82 | 
            +
            item_files = Obfusk::Data.data do
         | 
| 83 | 
            +
              field :url, [is_url], optional: true
         | 
| 84 | 
            +
            end
         | 
| 85 | 
            +
             | 
| 86 | 
            +
            image_item = Obfusk::Data.data isa: [item] do
         | 
| 87 | 
            +
              field :icon , [:nil?]
         | 
| 88 | 
            +
              field :data , [:nil?], optional: true
         | 
| 89 | 
            +
              field :files, isa: [item_files]
         | 
| 90 | 
            +
            end
         | 
| 91 | 
            +
             | 
| 92 | 
            +
            # --
         | 
| 93 | 
            +
             | 
| 94 | 
            +
            describe Obfusk::Data do
         | 
| 95 | 
            +
             | 
| 96 | 
            +
              context 'foo' do                                              # {{{1
         | 
| 97 | 
            +
                it 'valid empty foo' do
         | 
| 98 | 
            +
                  should be_valid foo, {}
         | 
| 99 | 
            +
                end
         | 
| 100 | 
            +
                it 'valid foo' do
         | 
| 101 | 
            +
                  should be_valid foo, { data_bar: 37 }
         | 
| 102 | 
            +
                end
         | 
| 103 | 
            +
                it 'invalid foo' do
         | 
| 104 | 
            +
                  should_not be_valid foo, { baz: 42 }
         | 
| 105 | 
            +
                end
         | 
| 106 | 
            +
              end                                                           # }}}1
         | 
| 107 | 
            +
             | 
| 108 | 
            +
              context 'tree' do                                             # {{{1
         | 
| 109 | 
            +
                it 'valid empty tree' do
         | 
| 110 | 
            +
                  should be_valid tree, { type: :empty }
         | 
| 111 | 
            +
                end
         | 
| 112 | 
            +
                it 'invalid empty tree' do
         | 
| 113 | 
            +
                  should_not be_valid tree, { type: :empty, foo: 'hi!' }
         | 
| 114 | 
            +
                end
         | 
| 115 | 
            +
                it 'valid tree leaf' do
         | 
| 116 | 
            +
                  should be_valid tree, { type: :leaf, value: 3.14 }
         | 
| 117 | 
            +
                end
         | 
| 118 | 
            +
                it 'invalid tree leaf' do
         | 
| 119 | 
            +
                  should_not be_valid tree, { type: :leaf }
         | 
| 120 | 
            +
                end
         | 
| 121 | 
            +
                it 'valid tree node' do
         | 
| 122 | 
            +
                  should be_valid tree,
         | 
| 123 | 
            +
                    { type: :node,
         | 
| 124 | 
            +
                      left: { type: :empty },
         | 
| 125 | 
            +
                      right: { type: :leaf, value: 'spam!' } }
         | 
| 126 | 
            +
                end
         | 
| 127 | 
            +
                it 'invalid tree node' do
         | 
| 128 | 
            +
                  should_not be_valid tree,
         | 
| 129 | 
            +
                    { type: :node, left: { type: :empty }, right: nil }
         | 
| 130 | 
            +
                end
         | 
| 131 | 
            +
              end                                                           # }}}1
         | 
| 132 | 
            +
             | 
| 133 | 
            +
              context 'address' do                                          # {{{1
         | 
| 134 | 
            +
                it 'valid address' do
         | 
| 135 | 
            +
                  should be_valid address,
         | 
| 136 | 
            +
                    { street: 'baker street', number: '221b',
         | 
| 137 | 
            +
                      town: 'london', postal_code: '???', country: 'uk' }
         | 
| 138 | 
            +
                end
         | 
| 139 | 
            +
                it 'invalid address' do
         | 
| 140 | 
            +
                  should_not be_valid address,
         | 
| 141 | 
            +
                    { street: 'baker street', number: 404 }
         | 
| 142 | 
            +
                end
         | 
| 143 | 
            +
              end                                                           # }}}1
         | 
| 144 | 
            +
             | 
| 145 | 
            +
              # ... TODO ...
         | 
| 146 | 
            +
             | 
| 147 | 
            +
            end
         | 
| 148 | 
            +
             | 
| 149 | 
            +
            # vim: set tw=70 sw=2 sts=2 et fdm=marker :
         | 
    
        metadata
    ADDED
    
    | @@ -0,0 +1,90 @@ | |
| 1 | 
            +
            --- !ruby/object:Gem::Specification
         | 
| 2 | 
            +
            name: obfusk-data
         | 
| 3 | 
            +
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            +
              version: 0.0.2.SNAPSHOT.20130211214657
         | 
| 5 | 
            +
              prerelease: 6
         | 
| 6 | 
            +
            platform: ruby
         | 
| 7 | 
            +
            authors:
         | 
| 8 | 
            +
            - Felix C. Stegerman
         | 
| 9 | 
            +
            autorequire: 
         | 
| 10 | 
            +
            bindir: bin
         | 
| 11 | 
            +
            cert_chain: []
         | 
| 12 | 
            +
            date: 2013-02-11 00:00:00.000000000 Z
         | 
| 13 | 
            +
            dependencies:
         | 
| 14 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 15 | 
            +
              name: hamster
         | 
| 16 | 
            +
              requirement: &9955760 !ruby/object:Gem::Requirement
         | 
| 17 | 
            +
                none: false
         | 
| 18 | 
            +
                requirements:
         | 
| 19 | 
            +
                - - ! '>='
         | 
| 20 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 21 | 
            +
                    version: '0'
         | 
| 22 | 
            +
              type: :runtime
         | 
| 23 | 
            +
              prerelease: false
         | 
| 24 | 
            +
              version_requirements: *9955760
         | 
| 25 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 26 | 
            +
              name: rake
         | 
| 27 | 
            +
              requirement: &9955240 !ruby/object:Gem::Requirement
         | 
| 28 | 
            +
                none: false
         | 
| 29 | 
            +
                requirements:
         | 
| 30 | 
            +
                - - ! '>='
         | 
| 31 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 32 | 
            +
                    version: '0'
         | 
| 33 | 
            +
              type: :development
         | 
| 34 | 
            +
              prerelease: false
         | 
| 35 | 
            +
              version_requirements: *9955240
         | 
| 36 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 37 | 
            +
              name: rspec
         | 
| 38 | 
            +
              requirement: &9954660 !ruby/object:Gem::Requirement
         | 
| 39 | 
            +
                none: false
         | 
| 40 | 
            +
                requirements:
         | 
| 41 | 
            +
                - - ! '>='
         | 
| 42 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 43 | 
            +
                    version: '0'
         | 
| 44 | 
            +
              type: :development
         | 
| 45 | 
            +
              prerelease: false
         | 
| 46 | 
            +
              version_requirements: *9954660
         | 
| 47 | 
            +
            description: ! '...
         | 
| 48 | 
            +
             | 
| 49 | 
            +
            '
         | 
| 50 | 
            +
            email:
         | 
| 51 | 
            +
            - flx@obfusk.net
         | 
| 52 | 
            +
            executables: []
         | 
| 53 | 
            +
            extensions: []
         | 
| 54 | 
            +
            extra_rdoc_files: []
         | 
| 55 | 
            +
            files:
         | 
| 56 | 
            +
            - .yardopts
         | 
| 57 | 
            +
            - README.md
         | 
| 58 | 
            +
            - Rakefile
         | 
| 59 | 
            +
            - lib/obfusk/data/version.rb
         | 
| 60 | 
            +
            - lib/obfusk/data.rb
         | 
| 61 | 
            +
            - spec/obfusk/data_spec.rb
         | 
| 62 | 
            +
            - obfusk-data.gemspec
         | 
| 63 | 
            +
            homepage: https://github.com/obfusk/rb-obfusk-data
         | 
| 64 | 
            +
            licenses:
         | 
| 65 | 
            +
            - GPLv2
         | 
| 66 | 
            +
            - EPLv1
         | 
| 67 | 
            +
            post_install_message: 
         | 
| 68 | 
            +
            rdoc_options: []
         | 
| 69 | 
            +
            require_paths:
         | 
| 70 | 
            +
            - lib
         | 
| 71 | 
            +
            required_ruby_version: !ruby/object:Gem::Requirement
         | 
| 72 | 
            +
              none: false
         | 
| 73 | 
            +
              requirements:
         | 
| 74 | 
            +
              - - ! '>='
         | 
| 75 | 
            +
                - !ruby/object:Gem::Version
         | 
| 76 | 
            +
                  version: 1.9.1
         | 
| 77 | 
            +
            required_rubygems_version: !ruby/object:Gem::Requirement
         | 
| 78 | 
            +
              none: false
         | 
| 79 | 
            +
              requirements:
         | 
| 80 | 
            +
              - - ! '>'
         | 
| 81 | 
            +
                - !ruby/object:Gem::Version
         | 
| 82 | 
            +
                  version: 1.3.1
         | 
| 83 | 
            +
            requirements: []
         | 
| 84 | 
            +
            rubyforge_project: 
         | 
| 85 | 
            +
            rubygems_version: 1.8.11
         | 
| 86 | 
            +
            signing_key: 
         | 
| 87 | 
            +
            specification_version: 3
         | 
| 88 | 
            +
            summary: data validation combinator library for ruby
         | 
| 89 | 
            +
            test_files: []
         | 
| 90 | 
            +
            has_rdoc: 
         |