mallow 0.0.2 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -6,10 +6,10 @@ An example of Mallow's versatility is Graham, a tiny testing library powered by
6
6
 
7
7
  ## Papa teach me to mallow ##
8
8
 
9
- To mallow is very simple little boy: first marshal, then mallow!
9
+ To mallow it is very easy little boy. First marshal, then mallow:
10
10
 
11
11
  ```ruby
12
- mallow = Mallow::Core.build do |match|
12
+ mallow = Mallow.build do |match|
13
13
  match.a_hash.to {"#{keys.first} #{values.first}"}
14
14
  end
15
15
  ```
@@ -20,7 +20,7 @@ Now feed your mallow some iterable data:
20
20
  ```
21
21
  Mallow's DSL has a moderately rich vocabulary of built-in helpers (with complementary method_missing magic if that's yr thing):
22
22
  ```ruby
23
- Mallow.fluff { |match|
23
+ Mallow.build { |match|
24
24
  match.a(Float).to &:to_i
25
25
  match.tuple(3).where{last != 0}.to {|a,b,c| (a + b) / c}
26
26
  match.an(Array).and_hashify_with( :name, :age ).and_make_a( Person ).and &:save!
@@ -34,7 +34,7 @@ Mallow's DSL has a moderately rich vocabulary of built-in helpers (with compleme
34
34
 
35
35
  A mallow is stateless, so it can't supply internal metadata (like index or match statistics) to rules. But that is not necessary for two reasons. First:
36
36
  ```ruby
37
- Mallow.fluff do |match|
37
+ Mallow.build do |match|
38
38
  line = 0
39
39
  match.a(Fixnum).to {"Found a fixnum on line #{line+=1}"}
40
40
  match.*.to {|e| line+=1;e}
@@ -44,9 +44,9 @@ But that is just awful, and will betray you if you forget to increment the line
44
44
 
45
45
  Luckily the second reason is that this should be done as part of some kind of post-processing anyway. To aid in such an undertaking, Mallow wraps a matched element in its _own_ metadata, which can be accessed transparently at any point in the transformer chain once a match has succeeded:
46
46
  ```ruby
47
- doubler = Mallow.fluff do |m|
48
- m.a(Fixnum).with_metadata(type: Fixnum).to {|n| n*2}
49
- m.anything.to {nil}.^(matched: false) # alias
47
+ doubler = Mallow.build do |m|
48
+ m.a(Fixnum).^(type: Fixnum).to {|n| n*2}
49
+ m.anything.to {nil}.^(matched: false)
50
50
  end
51
51
 
52
52
  data = doubler.fluff [1,2,:moo] #=> [2, 4, nil]
@@ -58,7 +58,7 @@ Luckily the second reason is that this should be done as part of some kind of po
58
58
 
59
59
  When a matcher is passed a parameter-less block, Mallow evaluates that block in the context of the element running against the matcher:
60
60
  ```ruby
61
- Mallow.fluff {|m| m.*.to {self} }.fluff1(1) #=> 1
61
+ Mallow.build {|m| m.*.to {self} }.fluff1(1) #=> 1
62
62
  ```
63
63
  In most cases this helps to make code less verbose and more semantic without having to rely on dispatch-via-method_missing (hooray!). If you're sticking side-effecting code in these blocks, though, weird things could potentially happen unless you're careful. If you want to avoid this behaviour, just be sure to give parameters to your blocks.
64
64
 
@@ -0,0 +1,50 @@
1
+ module Mallow
2
+ class BasicDSL
3
+ attr_reader :core, :actions, :conditions
4
+
5
+ def self.build_core
6
+ yield (dsl = new)
7
+ dsl.finish!
8
+ end
9
+
10
+ def initialize
11
+ @core = Core.new
12
+ reset!
13
+ end
14
+
15
+ def where(&b); push b, :conditions end
16
+ def to(&b); push b, :actions end
17
+ def and(&b); push b end
18
+
19
+ def finish!
20
+ in_conds? ? to{self}.finish! : rule!.core
21
+ end
22
+
23
+ alias such_that where
24
+
25
+ private
26
+ def in_conds?
27
+ actions.empty?
28
+ end
29
+
30
+ def rule!
31
+ core << Rule::Builder[conditions, actions]
32
+ reset!
33
+ end
34
+
35
+ def push(p, loc = in_conds? ? :conditions : :actions)
36
+ rule! if loc == :conditions and not in_conds?
37
+ send(loc) << preproc(p)
38
+ self
39
+ end
40
+
41
+ def preproc(p)
42
+ p.parameters.empty? ? proc {|e| e.instance_eval &p} : p
43
+ end
44
+
45
+ def reset!
46
+ @conditions, @actions = Matcher.new, Transformer.new
47
+ self
48
+ end
49
+ end
50
+ end
data/lib/mallow/dsl.rb CHANGED
@@ -1,104 +1,42 @@
1
+ require 'mallow/basic_dsl'
1
2
  module Mallow
2
- class DSL
3
- attr_reader :core, :actions, :conditions
4
-
5
- def self.build
6
- yield (dsl = new)
7
- dsl.finish!
8
- end
9
-
10
- def initialize
11
- @core = Core.new
12
- reset!
13
- end
14
-
15
- def where(&b); push b, :conditions end
16
- def to(&b); push b, :actions end
17
- def and(&b); push b end
18
-
19
- def *; where {true} end
20
- def a(c); where {|e| e.is_a? c} end
21
- def this(o); where {|e| e == o} end
22
- def size(n); where {|e| e.size==n rescue false} end
23
- def with_key(k); where {|e| e.has_key?(k) rescue false} end
24
-
25
- def and_hashify_with_keys(*ks); to {|e| Hash[ks.zip e]} end
26
- def and_hashify_with_values(*vs); to {|e| Hash[e.zip vs]} end
27
- def with_metadata(d={}); to {|e| Meta.new e, d} end
28
-
29
- def to_nil; to{nil} end
30
- def to_true; to{true} end
31
- def to_false; to{false} end
32
- def to_self; to{self} end
33
-
34
- def tuple(n); a(Array).size(n) end
35
- def and_make(o,s=false); and_send(:new,o,s) end
36
-
37
- def and_send(msg, obj, splat = false)
38
- to {|e| splat ? obj.send(msg, *e) : obj.send(msg, e)}
39
- end
40
-
41
- alias an a
42
- alias ^ with_metadata
43
- alias anything *
44
- alias and_hashify_with and_hashify_with_keys
45
- alias and_make_a and_make
46
- alias and_make_an and_make
47
- alias a_tuple tuple
48
- alias such_that where
49
- alias of_size size
50
- alias to_itself to_self
51
-
52
- def finish!
53
- in_conds? ? to_self.finish! : rule!.core
54
- end
55
-
56
- # Checks for three forms:
57
- # * (a|an)_(<thing>) with no args
58
- # * (with|of)_(<msg>) with one arg, which tests <match>.send(<msg>) == arg
59
- # * to_(<msg>) with any args, which resolves to <match>.send(<msg>) *args
60
- def method_missing(msg, *args)
61
- case msg.to_s
62
- when /^(a|an)_(.+)$/
63
- args.empty??
64
- (a(Object.const_get $2.split(?_).map(&:capitalize).join) rescue super) :
65
- super
66
- when /^(with|of)_(.+)$/
67
- args.size == 1 ?
68
- where {|e| e.send($2) == args.first rescue false} :
69
- super
70
- when /^to_(.+)$/
71
- to {|e| e.send $1, *args}
72
- else
73
- super
3
+ class DSL < BasicDSL
4
+ module Matchers
5
+ def *; where {true} end
6
+ def a(c); where {|e| c===e} end
7
+ def this(o); where {|e| o== e} end
8
+ def size(n); where {|e| e.size==n rescue false} end
9
+ def with_key(k); where {|e| e.has_key?(k) rescue false} end
10
+
11
+ def tuple(n)
12
+ a(Array).size(n)
74
13
  end
14
+ alias an a
15
+ alias anything *
16
+ alias of_size size
17
+ alias a_tuple tuple
75
18
  end
76
19
 
77
- private
78
-
79
- def in_conds?
80
- actions.empty?
81
- end
20
+ module Transformers
21
+ def and_hashify_with_keys(*ks); to {|e| Hash[ks.zip e]} end
22
+ def and_hashify_with_values(*vs); to {|e| Hash[e.zip vs]} end
23
+ def ^(d={}); to {|e| Meta.new e, d } end
82
24
 
83
- def rule!
84
- core << Rule::Builder[conditions, actions]
85
- reset!
86
- end
25
+ def and_make(o,s=false)
26
+ and_send(o,:new,s)
27
+ end
87
28
 
88
- def push(p, loc = in_conds? ? :conditions : :actions)
89
- rule! if loc == :conditions and not in_conds?
90
- send(loc) << preproc(p)
91
- self
29
+ def and_send(obj, mgs, splat = false)
30
+ to {|e| splat ? obj.send(msg, *e) : obj.send(msg, e)}
31
+ end
32
+ alias and_hashify_with and_hashify_with_keys
33
+ alias and_make_a and_make
34
+ alias and_make_an and_make
92
35
  end
93
36
 
94
- def preproc(p)
95
- p.parameters.empty? ? proc {|e| e.instance_eval &p} : p
96
- end
37
+ include Matchers
38
+ include Transformers
97
39
 
98
- def reset!
99
- @conditions, @actions = Matcher.new, Transformer.new
100
- self
101
- end
102
40
  end
103
41
  end
104
42
 
data/lib/mallow/monads.rb CHANGED
@@ -2,9 +2,7 @@ module Mallow
2
2
  # Rule monad(ish) encapsulating "execute the first rule whose conditions
3
3
  # pass" logic.
4
4
  class Rule < Struct.new :matcher, :transformer, :val
5
- class << self
6
- def return(v); new Matcher.new, Transformer.new, v end
7
- end
5
+ def self.return(v); new Matcher.new, Transformer.new, v end
8
6
  # Curried proc for building procs for binding rules. If this were Haskell
9
7
  # its type signature might vaguely resemble:
10
8
  # Elt e => [e -> Bool] -> [e -> e] -> e -> Maybe (Meta e)
@@ -16,7 +14,7 @@ module Mallow
16
14
  def unwrap!; matcher === val ? transformer >> val : dx end
17
15
  private
18
16
  def dx
19
- raise DeserializationException, "No rule matches #{val}:#{val.class}"
17
+ raise MatchException, "No rule matches #{val}:#{val.class}"
20
18
  end
21
19
  end
22
20
  # Wrapper monad(ish) for successful matches that allows the user to
@@ -48,8 +46,6 @@ module Mallow
48
46
  def >>(e); reduce(Meta.return(e),:bind) end
49
47
  # Wraps argument using Meta::proc
50
48
  def <<(p); super Meta::Builder[p] end
51
- alias push <<
52
49
  end
53
-
54
50
  end
55
51
 
@@ -1,4 +1,3 @@
1
1
  module Mallow
2
- # Mainly for show.
3
- VERSION = '0.0.2'
2
+ VERSION = '0.0.4'
4
3
  end
data/lib/mallow.rb CHANGED
@@ -3,38 +3,14 @@ require 'mallow/version'
3
3
  require 'mallow/monads'
4
4
  require 'mallow/dsl'
5
5
  module Mallow
6
- class DeserializationException < StandardError; end
7
-
6
+ class MatchException < StandardError; end
8
7
  class Core < Array
9
- def fluff(es)
10
- _fluff(es).map &:val
11
- end
12
- def fluff1(e)
13
- _fluff1(e).val
14
- end
15
- def _fluff(es)
16
- es.map {|e| _fluff1 e}
17
- end
18
- def _fluff1(e)
19
- reduce(Rule.return(e),:bind).unwrap!
20
- end
21
- class << self
22
- # aka Mallow::DSL::build
23
- def build(&b); DSL.build &b end
24
- end
25
- end
26
-
27
- class << self
28
- # aka Mallow::Core::build
29
- def fluff(&b); Core.build(&b) end
30
- # Defines a class method <sym> on <klass> to deserialize its argument (as
31
- # with Core#fluff) using the Core generated by passing the supplied block
32
- # to Mallow.fluff
33
- def engulf(klass, sym=:fluff, &b)
34
- mtd, mod = Mallow.fluff(&b), Module.new
35
- mod.send(:define_method, sym) {|d| mtd.fluff d}
36
- klass.extend mod
37
- end
8
+ def fluff(es); _fluff(es).map &:val end
9
+ def fluff1(e); _fluff1(e).val end
10
+ def _fluff(es); es.map {|e| _fluff1 e} end
11
+ def _fluff1(e); reduce(Rule.return(e),:bind).unwrap! end
38
12
  end
13
+ # see DSL#build_core
14
+ def self.build(&b); DSL.build_core &b end
39
15
  end
40
16
 
data/mallow.gemspec CHANGED
@@ -2,7 +2,7 @@ $LOAD_PATH << File.expand_path("../lib", __FILE__)
2
2
  require 'rake'
3
3
  require 'mallow/version'
4
4
 
5
- mallow = Gem::Specification.new do |spec|
5
+ Gem::Specification.new do |spec|
6
6
  spec.name = 'mallow'
7
7
  spec.version = Mallow::VERSION
8
8
  spec.author = 'feivel jellyfish'
@@ -12,6 +12,6 @@ mallow = Gem::Specification.new do |spec|
12
12
  spec.homepage = 'http://github.com/gwentacle/mallow'
13
13
  spec.summary = 'Tiny universal data pattern matcher / dispatcher'
14
14
  spec.description = 'Tiny universal data pattern matcher / dispatcher'
15
- spec.add_development_dependency 'graham'
15
+ spec.add_development_dependency 'graham', '>=0.0.2'
16
16
  end
17
17
 
@@ -0,0 +1,44 @@
1
+ class Cases
2
+ def initialize
3
+ @mallow = Mallow.build do |match|
4
+ match.a(String).to{upcase}
5
+ match.a_tuple(3).to {|a,b,c| a+b+c}
6
+ match.a(Fixnum).to {self*2}
7
+ end
8
+ end
9
+
10
+ def test_case_1
11
+ @mallow.fluff [ 99, 'asdf', 'qwer', [5,5,5], 47, %w{cool story bro} ]
12
+ end
13
+
14
+ end
15
+
16
+ class Exceptions
17
+ def initialize
18
+ @mallow = Mallow.build do |match|
19
+ match.a(Fixnum).to {self/0}
20
+ match.a(String).to {self/0}
21
+ end
22
+ end
23
+ def division_by_zero
24
+ @mallow.fluff1 1
25
+ end
26
+ def calling_a_nonexistent_method
27
+ @mallow.fluff1 ?1
28
+ end
29
+ def an_unmatched_element
30
+ @mallow.fluff1 :ok
31
+ end
32
+
33
+ end
34
+
35
+ Graham.pp(Cases) {|that|
36
+ that.test_case_1.returns_an_array.that_is [ 198, 'ASDF', 'QWER', 15, 94, 'coolstorybro' ]
37
+ }
38
+
39
+ Graham.pp(Exceptions) {|that|
40
+ that.division_by_zero.raises_a ZeroDivisionError
41
+ that.calling_a_nonexistent_method.raises_a NoMethodError
42
+ that.an_unmatched_element.raises_a Mallow::MatchException
43
+ }
44
+
data/test/unit/meta.rb ADDED
@@ -0,0 +1,11 @@
1
+ class MetaTests
2
+ def binding_behaviour_with_common_keys
3
+ meta = Mallow::Meta.return 1, meta: :data, any: :body?
4
+ meta.bind ->(v){Mallow::Meta.return v, meta: :data!}
5
+ end
6
+ end
7
+
8
+ Graham.pp(MetaTests) do |that|
9
+ that.binding_behaviour_with_common_keys.returns meta: :data!, any: :body?
10
+ end
11
+
@@ -1,4 +1,4 @@
1
- class Graham::MonadLaws
1
+ class MonadLaws
2
2
  def RuleLeftIdentity
3
3
  rule = Mallow::Rule.return 350_000_000
4
4
  f = Mallow::Rule::Builder[Mallow::Matcher.new][Mallow::Transformer.new]
@@ -36,7 +36,7 @@ class Graham::MonadLaws
36
36
  end
37
37
  end
38
38
 
39
- Graham.pp(:MonadLaws) {|that|
39
+ Graham.pp(MonadLaws) {|that|
40
40
  that.RuleLeftIdentity.is true
41
41
  that.RuleRightIdentity.is true
42
42
  that.RuleAssociativity.is true
data/test/unit/rule.rb ADDED
@@ -0,0 +1,52 @@
1
+ class RuleTests
2
+ def initialize
3
+ (@fmatcher = Mallow::Matcher.new)[0] = ->(e){false}
4
+ (@pmatcher = Mallow::Matcher.new)[0] = ->(e){true}
5
+ @fbuilder = Mallow::Rule::Builder[@fmatcher, Mallow::Transformer.new]
6
+ @pbuilder = Mallow::Rule::Builder[@pmatcher, Mallow::Transformer.new]
7
+ end
8
+
9
+ def unwrapping_an_empty_rule
10
+ Mallow::Rule.return(nil).unwrap!
11
+ end
12
+
13
+ def unwrapping_a_passing_rule
14
+ @pbuilder[:value].unwrap!
15
+ end
16
+
17
+ def unwrapping_a_failing_rule
18
+ @fbuilder[:value].unwrap!
19
+ end
20
+
21
+ def binding_a_failing_rule_in_a_passing_rule
22
+ @fbuilder[1].bind(@pbuilder)
23
+ end
24
+
25
+ def binding_a_passing_rule_in_a_failing_rule
26
+ @pbuilder[//].bind(@fbuilder)
27
+ end
28
+
29
+ def binding_a_passing_rule_in_a_passing_rule
30
+ (passing_matcher = Mallow::Matcher.new)[0] = ->(e){1}
31
+ passing_builder = Mallow::Rule::Builder[passing_matcher, Mallow::Transformer.new]
32
+ @pbuilder[1].bind passing_builder
33
+ end
34
+
35
+ def binding_a_failing_rule_in_a_failing_rule
36
+ (failing_matcher = Mallow::Matcher.new)[0] = ->(e){nil}
37
+ failing_builder = Mallow::Rule::Builder[failing_matcher, Mallow::Transformer.new]
38
+ @fbuilder[1].bind failing_builder
39
+ end
40
+ end
41
+
42
+ Graham.pp(RuleTests) do |that|
43
+ that.unwrapping_an_empty_rule.raises_a Mallow::MatchException
44
+ that.unwrapping_a_failing_rule.raises_a Mallow::MatchException
45
+ that.unwrapping_a_passing_rule.returns_a(Mallow::Meta).such_that { val == :value }
46
+
47
+ that.binding_a_failing_rule_in_a_passing_rule.returns_a(Mallow::Rule).such_that { matcher === val }
48
+ that.binding_a_passing_rule_in_a_passing_rule.returns_a(Mallow::Rule).such_that { matcher === val }
49
+ that.binding_a_passing_rule_in_a_failing_rule.returns_a(Mallow::Rule).such_that { matcher === val }
50
+ that.binding_a_failing_rule_in_a_failing_rule.returns_a(Mallow::Rule).such_that { not matcher === val }
51
+ end
52
+
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mallow
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.4
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-11-25 00:00:00.000000000 Z
12
+ date: 2012-11-28 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: graham
@@ -18,7 +18,7 @@ dependencies:
18
18
  requirements:
19
19
  - - ! '>='
20
20
  - !ruby/object:Gem::Version
21
- version: '0'
21
+ version: 0.0.2
22
22
  type: :development
23
23
  prerelease: false
24
24
  version_requirements: !ruby/object:Gem::Requirement
@@ -26,7 +26,7 @@ dependencies:
26
26
  requirements:
27
27
  - - ! '>='
28
28
  - !ruby/object:Gem::Version
29
- version: '0'
29
+ version: 0.0.2
30
30
  description: Tiny universal data pattern matcher / dispatcher
31
31
  email: feivel@sdf.org
32
32
  executables: []
@@ -36,12 +36,15 @@ files:
36
36
  - mallow.gemspec
37
37
  - README.md
38
38
  - lib/mallow.rb
39
+ - lib/mallow/basic_dsl.rb
39
40
  - lib/mallow/version.rb
40
41
  - lib/mallow/dsl.rb
41
42
  - lib/mallow/monads.rb
42
43
  - Rakefile
43
- - test/case/mallow.rb
44
- - test/unit/monad.rb
44
+ - test/case/control.rb
45
+ - test/unit/monad_laws.rb
46
+ - test/unit/meta.rb
47
+ - test/unit/rule.rb
45
48
  homepage: http://github.com/gwentacle/mallow
46
49
  licenses: []
47
50
  post_install_message:
@@ -68,5 +71,7 @@ specification_version: 3
68
71
  summary: Tiny universal data pattern matcher / dispatcher
69
72
  test_files:
70
73
  - Rakefile
71
- - test/case/mallow.rb
72
- - test/unit/monad.rb
74
+ - test/case/control.rb
75
+ - test/unit/monad_laws.rb
76
+ - test/unit/meta.rb
77
+ - test/unit/rule.rb
data/test/case/mallow.rb DELETED
@@ -1,38 +0,0 @@
1
- class Graham::Cases
2
- def initialize
3
- @mallow1 = Mallow.fluff do |match|
4
- match.a_string.to_upcase
5
- match.a_tuple(3).to {|a,b,c| a+b+c}
6
- match.a_fixnum.to {self*2}
7
- end
8
-
9
- @xmallow = Mallow.fluff do |match|
10
- match.a(Fixnum).to {self/0}
11
- match.a(String).to {self/self}
12
- match.*.to {}
13
- match.a_symbol.to {UNDEFINED}
14
- end
15
- end
16
-
17
- def Case1
18
- @mallow1.fluff [ 99, 'asdf', 'qwer', [5,5,5], 47, %w{cool story bro} ]
19
- end
20
-
21
- def XCase1
22
- @xmallow.fluff1 1
23
- end
24
- def XCase2
25
- @xmallow.fluff1 ?1
26
- end
27
- def XCase3
28
- @xmallow.fluff1 :ok
29
- end
30
- end
31
-
32
- Graham.pp {|that|
33
- that.Case1.returns_an(Array).that_is [ 198, 'ASDF', 'QWER', 15, 94, 'coolstorybro' ]
34
- that.XCase1.raises_a ZeroDivisionError
35
- that.XCase2.raises_a NoMethodError
36
- that.XCase3.does_not_raise_an_exception
37
- }
38
-