obfusk-data 0.0.2.SNAPSHOT.20130211214657

Sign up to get free protection for your applications and to get access to all the features.
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: