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 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
@@ -0,0 +1,6 @@
1
+ module Obfusk
2
+ module Data
3
+ VERSION = '0.0.2.SNAPSHOT.20130211214657'
4
+ DATE = '2013-02-11'
5
+ end
6
+ end
@@ -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 :
@@ -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: