defunkt-ambition 0.5.3

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.
Files changed (44) hide show
  1. data/LICENSE +18 -0
  2. data/Manifest +42 -0
  3. data/README +24 -0
  4. data/ambition.gemspec +120 -0
  5. data/app_generators/ambition_adapter/USAGE +1 -0
  6. data/app_generators/ambition_adapter/ambition_adapter_generator.rb +66 -0
  7. data/app_generators/ambition_adapter/templates/LICENSE +18 -0
  8. data/app_generators/ambition_adapter/templates/README +6 -0
  9. data/app_generators/ambition_adapter/templates/Rakefile +31 -0
  10. data/app_generators/ambition_adapter/templates/lib/adapter/base.rb.erb +12 -0
  11. data/app_generators/ambition_adapter/templates/lib/adapter/query.rb.erb +52 -0
  12. data/app_generators/ambition_adapter/templates/lib/adapter/select.rb.erb +100 -0
  13. data/app_generators/ambition_adapter/templates/lib/adapter/slice.rb.erb +19 -0
  14. data/app_generators/ambition_adapter/templates/lib/adapter/sort.rb.erb +43 -0
  15. data/app_generators/ambition_adapter/templates/lib/init.rb.erb +22 -0
  16. data/app_generators/ambition_adapter/templates/test/helper.rb.erb +9 -0
  17. data/app_generators/ambition_adapter/templates/test/select_test.rb.erb +157 -0
  18. data/app_generators/ambition_adapter/templates/test/slice_test.rb.erb +36 -0
  19. data/app_generators/ambition_adapter/templates/test/sort_test.rb.erb +53 -0
  20. data/bin/ambition_adapter +13 -0
  21. data/lib/ambition/api.rb +98 -0
  22. data/lib/ambition/context.rb +62 -0
  23. data/lib/ambition/core_ext.rb +7 -0
  24. data/lib/ambition/enumerable.rb +6 -0
  25. data/lib/ambition/processors/base.rb +134 -0
  26. data/lib/ambition/processors/ruby.rb +26 -0
  27. data/lib/ambition/processors/select.rb +105 -0
  28. data/lib/ambition/processors/slice.rb +15 -0
  29. data/lib/ambition/processors/sort.rb +51 -0
  30. data/lib/ambition/sexp_translator.rb +16 -0
  31. data/lib/ambition.rb +11 -0
  32. data/test/adapters/exemplar/association_test.rb +34 -0
  33. data/test/adapters/exemplar/count_test.rb +0 -0
  34. data/test/adapters/exemplar/detect_test.rb +9 -0
  35. data/test/adapters/exemplar/enumerable_test.rb +0 -0
  36. data/test/adapters/exemplar/helper.rb +3 -0
  37. data/test/adapters/exemplar/index_operator.rb +6 -0
  38. data/test/adapters/exemplar/reject_test.rb +0 -0
  39. data/test/adapters/exemplar/select_test.rb +151 -0
  40. data/test/adapters/exemplar/slice_test.rb +0 -0
  41. data/test/adapters/exemplar/sort_test.rb +0 -0
  42. data/test/debug +9 -0
  43. data/test/helper.rb +4 -0
  44. metadata +139 -0
@@ -0,0 +1,157 @@
1
+ require File.dirname(__FILE__) + '/helper'
2
+
3
+ context "<%= adapter_module %> Adapter :: Select" do
4
+ setup do
5
+ @klass = User
6
+ end
7
+
8
+ xspecify "==" do
9
+ translator = @klass.select { |m| m.name == 'jon' }
10
+ translator.to_s.should == %Q(foo)
11
+ end
12
+
13
+ xspecify "!=" do
14
+ translator = @klass.select { |m| m.name != 'jon' }
15
+ translator.to_s.should == %Q(foo)
16
+ end
17
+
18
+ xspecify "== && ==" do
19
+ translator = @klass.select { |m| m.name == 'jon' && m.age == 21 }
20
+ translator.to_s.should == %Q(foo)
21
+ end
22
+
23
+ xspecify "== || ==" do
24
+ translator = @klass.select { |m| m.name == 'jon' || m.age == 21 }
25
+ translator.to_s.should == %Q(foo)
26
+ end
27
+
28
+ xspecify "mixed && and ||" do
29
+ translator = @klass.select { |m| m.name == 'jon' || m.age == 21 && m.password == 'pass' }
30
+ translator.to_s.should == %Q(foo)
31
+ end
32
+
33
+ xspecify "grouped && and ||" do
34
+ translator = @klass.select { |m| (m.name == 'jon' || m.name == 'rick') && m.age == 21 }
35
+ translator.to_s.should == %Q(foo)
36
+ end
37
+
38
+ xspecify ">/<" do
39
+ translator = @klass.select { |m| m.age > 21 }
40
+ translator.to_s.should == %Q(foo)
41
+
42
+ translator = @klass.select { |m| m.age >= 21 }
43
+ translator.to_s.should == %Q(foo)
44
+
45
+ translator = @klass.select { |m| m.age < 21 }
46
+ translator.to_s.should == %Q(foo)
47
+ end
48
+
49
+ xspecify "array.include? item" do
50
+ translator = @klass.select { |m| [1, 2, 3, 4].include? m.id }
51
+ translator.to_s.should == %Q(foo)
52
+ end
53
+
54
+ xspecify "variabled array.include? item" do
55
+ array = [1, 2, 3, 4]
56
+ translator = @klass.select { |m| array.include? m.id }
57
+ translator.to_s.should == %Q(foo)
58
+ end
59
+
60
+ xspecify "== with variables" do
61
+ me = 'chris'
62
+ translator = @klass.select { |m| m.name == me }
63
+ translator.to_s.should == %Q(foo)
64
+ end
65
+
66
+ xspecify "== with method arguments" do
67
+ def test_it(name)
68
+ translator = @klass.select { |m| m.name == name }
69
+ translator.to_s.should == %Q(foo)
70
+ end
71
+
72
+ test_it('chris')
73
+ end
74
+
75
+ xspecify "== with instance variables" do
76
+ @me = 'chris'
77
+ translator = @klass.select { |m| m.name == @me }
78
+ translator.to_s.should == %Q(foo)
79
+ end
80
+
81
+ xspecify "== with instance variable method call" do
82
+ require 'ostruct'
83
+ @person = OpenStruct.new(:name => 'chris')
84
+
85
+ translator = @klass.select { |m| m.name == @person.name }
86
+ translator.to_s.should == %Q(foo)
87
+ end
88
+
89
+ xspecify "== with global variables" do
90
+ $my_name = 'boston'
91
+ translator = @klass.select { |m| m.name == $my_name }
92
+ translator.to_s.should == %Q(foo)
93
+ end
94
+
95
+ xspecify "== with method call" do
96
+ def band
97
+ 'skinny puppy'
98
+ end
99
+
100
+ translator = @klass.select { |m| m.name == band }
101
+ translator.to_s.should == %Q(foo)
102
+ end
103
+
104
+ xspecify "=~ with string" do
105
+ translator = @klass.select { |m| m.name =~ 'chris' }
106
+ translator.to_s.should == %Q(foo)
107
+
108
+ translator = @klass.select { |m| m.name =~ 'chri%' }
109
+ translator.to_s.should == %Q(foo)
110
+ end
111
+
112
+ xspecify "!~ with string" do
113
+ translator = @klass.select { |m| m.name !~ 'chris' }
114
+ translator.to_s.should == %Q(foo)
115
+
116
+ translator = @klass.select { |m| !(m.name =~ 'chris') }
117
+ translator.to_s.should == %Q(foo)
118
+ end
119
+
120
+ xspecify "=~ with regexp" do
121
+ translator = @klass.select { |m| m.name =~ /chris/ }
122
+ translator.to_s.should == %Q(foo)
123
+ end
124
+
125
+ xspecify "=~ with regexp flags" do
126
+ translator = @klass.select { |m| m.name =~ /chris/i }
127
+ translator.to_s.should == %Q(foo)
128
+ end
129
+
130
+ xspecify "downcase" do
131
+ translator = @klass.select { |m| m.name.downcase =~ 'chris%' }
132
+ translator.to_s.should == %Q(foo)
133
+ end
134
+
135
+ xspecify "upcase" do
136
+ translator = @klass.select { |m| m.name.upcase =~ 'chris%' }
137
+ translator.to_s.should == %Q(foo)
138
+ end
139
+
140
+ xspecify "undefined equality symbol" do
141
+ should.raise { @klass.select { |m| m.name =* /chris/ } }
142
+ end
143
+
144
+ xspecify "block variable / assigning variable conflict" do
145
+ m = @klass.select { |m| m.name == 'chris' }
146
+ m.should == %Q(foo)
147
+ end
148
+
149
+ xspecify "== with inline ruby" do
150
+ translator = @klass.select { |m| m.created_at == Time.now.to_s }
151
+ translator.to_s.should == %Q(foo)
152
+ end
153
+
154
+ xspecify "inspect" do
155
+ @klass.select { |u| u.name }.inspect.should.match %r(call #to_s or #to_hash)
156
+ end
157
+ end
@@ -0,0 +1,36 @@
1
+ require File.dirname(__FILE__) + '/helper'
2
+
3
+ context "<%= adapter_module %> Adapter :: Slice" do
4
+ setup do
5
+ @klass = User
6
+ @block = @klass.select { |m| m.name == 'jon' }
7
+ end
8
+
9
+ xspecify "first" do
10
+ @klass.expects(:find).with(:limit => 1, :name => 'jon')
11
+ @block.first
12
+ end
13
+
14
+ xspecify "first with argument" do
15
+ @klass.expects(:find).with(:limit => 5, :name => 'jon')
16
+ @block.first(5).entries
17
+ end
18
+
19
+ xspecify "[] with two elements" do
20
+ @klass.expects(:find).with(:limit => 20, :offset => 10, :name => 'jon')
21
+ @block[10, 20].entries
22
+
23
+ @klass.expects(:find).with(:limit => 20, :offset => 20, :name => 'jon')
24
+ @block[20, 20].entries
25
+ end
26
+
27
+ xspecify "slice is an alias of []" do
28
+ @klass.expects(:find).with(:limit => 20, :offset => 10, :name => 'jon')
29
+ @block.slice(10, 20).entries
30
+ end
31
+
32
+ xspecify "[] with range" do
33
+ @klass.expects(:find).with(:limit => 10, :offset => 10, :name => 'jon')
34
+ @block[11..20].entries
35
+ end
36
+ end
@@ -0,0 +1,53 @@
1
+ require File.dirname(__FILE__) + '/helper'
2
+
3
+ context "<%= adapter_module %> Adapter :: Sort" do
4
+ setup do
5
+ @klass = User
6
+ @block = @klass.select { |m| m.name == 'jon' }
7
+ end
8
+
9
+ xspecify "order" do
10
+ string = @block.sort_by { |m| m.name }.to_s
11
+ string.should == "foo"
12
+ end
13
+
14
+ xspecify "combined order" do
15
+ string = @block.sort_by { |m| [ m.name, m.age ] }.to_s
16
+ string.should == "foo"
17
+ end
18
+
19
+ xspecify "combined order with single reverse" do
20
+ string = @block.sort_by { |m| [ m.name, -m.age ] }.to_s
21
+ string.should == "foo"
22
+ end
23
+
24
+ xspecify "combined order with two reverses" do
25
+ string = @block.sort_by { |m| [ -m.name, -m.age ] }.to_s
26
+ string.should == "foo"
27
+ end
28
+
29
+ xspecify "reverse order with -" do
30
+ string = @block.sort_by { |m| -m.age }.to_s
31
+ string.should == "foo"
32
+ end
33
+
34
+ xspecify "reverse order with #reverse" do
35
+ # TODO: not implemented
36
+ string = @block.sort_by { |m| m.age }.reverse.to_s
37
+ string.should == "foo"
38
+ end
39
+
40
+ xspecify "random order" do
41
+ string = @block.sort_by { rand }.to_s
42
+ string.should == "foo"
43
+ end
44
+
45
+ xspecify "non-existent method to sort by" do
46
+ should.raise(NoMethodError) { @block.sort_by { foo }.to_s }
47
+ end
48
+
49
+ xspecify "Symbol#to_proc" do
50
+ string = @klass.sort_by(&:name).to_s
51
+ string.should == "foo"
52
+ end
53
+ end
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubygems'
3
+ require 'rubigen'
4
+
5
+ if %w(-v --version).include? ARGV.first
6
+ version = File.read('./Rakefile').scan(/Version = '(.+)'/).first.first
7
+ puts "#{File.basename($0)} #{version}"
8
+ exit(0)
9
+ end
10
+
11
+ require 'rubigen/scripts/generate'
12
+ RubiGen::Base.use_application_sources! :ambition_adapter
13
+ RubiGen::Scripts::Generate.new.run(ARGV, :generator => 'ambition_adapter')
@@ -0,0 +1,98 @@
1
+ module Ambition #:nodoc:
2
+ # Module that you will extend from in your adapters in your toplevel file.
3
+ #
4
+ # For example, for ambitious_sphinx in lib/ambition/adapters/ambitious_sphinx.rb, we have:
5
+ # ActiveRecord::Base.extend Ambition::API
6
+ module API
7
+ include Enumerable
8
+
9
+ ##
10
+ # Entry methods
11
+ def select(&block)
12
+ context = ambition_context
13
+ context << Processors::Select.new(context, block)
14
+ end
15
+
16
+ def sort_by(&block)
17
+ context = ambition_context
18
+ context << Processors::Sort.new(context, block)
19
+ end
20
+
21
+ # Entries that our context is able to find.
22
+ def entries
23
+ ambition_context.kick
24
+ end
25
+ alias_method :to_a, :entries
26
+
27
+ def size
28
+ ambition_context == self ? super : ambition_context.size
29
+ end
30
+
31
+ def slice(start, length = nil)
32
+ context = ambition_context
33
+ context << Processors::Slice.new(context, start, length)
34
+ end
35
+ alias_method :[], :slice
36
+
37
+ ##
38
+ # Convenience methods
39
+
40
+ # See Enumerable#detect
41
+ def detect(&block)
42
+ select(&block).first
43
+ end
44
+
45
+ # See Array#first
46
+ def first(count = 1)
47
+ sliced = slice(0, count)
48
+ count == 1 ? Array(sliced.kick).first : sliced
49
+ end
50
+
51
+ # See Array#each, applied to +entries+
52
+ def each(&block)
53
+ entries.each(&block)
54
+ end
55
+
56
+ # See Enumerable#any?
57
+ def any?(&block)
58
+ select(&block).size > 0
59
+ end
60
+
61
+ # See Enumerable#all?
62
+ def all?(&block)
63
+ size == select(&block).size
64
+ end
65
+
66
+ # See Array#empty?
67
+ def empty?
68
+ size.zero?
69
+ end
70
+
71
+ # Builds a new +Context+.
72
+ def ambition_context
73
+ Context.new(self)
74
+ end
75
+
76
+ # Gives you the current ambitious adapter.
77
+ def ambition_adapter
78
+ name = respond_to?(:name) ? name : self.class.name
79
+ parent = respond_to?(:superclass) ? superclass : self.class.superclass
80
+ @@ambition_adapter[name] || @@ambition_adapter[parent.name]
81
+ end
82
+
83
+ # Assign the ambition adapter. Typically, you use this in the toplevel file of your adapter.
84
+ #
85
+ # For example, for ambitious_sphinx, in our lib/ambition/adapters/ambitious_sphinx.rb:
86
+ #
87
+ # ActiveRecord::Base.ambition_adapter = Ambition::Adapters::AmbitiousSphinx
88
+ def ambition_adapter=(klass)
89
+ @@ambition_adapter ||= {}
90
+ # should this be doing the same check for respond_to?(:name) like above?
91
+ @@ambition_adapter[name] = klass
92
+ end
93
+
94
+ def ambition_owner
95
+ @owner || self
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,62 @@
1
+ module Ambition #:nodoc:
2
+ # This class includes several methods you will likely want to be accessing through your
3
+ # Query and Translator classes:
4
+ #
5
+ # * +clauses+
6
+ # * +owner+
7
+ # * +stash+
8
+ class Context
9
+ undef_method :to_s
10
+ include API
11
+
12
+ # A hash of arrays, one key per processor.
13
+ # So, if someone called User.select, your
14
+ # +clauses+ hash would have a :select key with
15
+ # an array of translated strings via your Select
16
+ # class.
17
+ #
18
+ # This is accessible from your Query and Translator classes.
19
+ attr_reader :clauses
20
+
21
+ # The class everything was called on. Like `User`
22
+ #
23
+ # This is accessible from your Query and Translator classes.
24
+ attr_reader :owner
25
+
26
+ # A place for you to stick stuff. Available to all Translators and your Query class.
27
+ #
28
+ # This is accessible from your Query and Translator classes.
29
+ attr_reader :stash
30
+
31
+ def initialize(owner)
32
+ @owner = owner
33
+ @clauses = {}
34
+ @stash = {}
35
+ end
36
+
37
+ # Gets the ambition_context. From a Ambition::Context, this is actually +self+.
38
+ def ambition_context
39
+ self
40
+ end
41
+
42
+ # Adds a clause to this context.
43
+ def <<(clause)
44
+ @clauses[clause.key] ||= []
45
+ @clauses[clause.key] << clause.to_s
46
+ self
47
+ end
48
+
49
+ def adapter_query
50
+ Processors::Base.translator(self, :Query)
51
+ end
52
+
53
+ def method_missing(method, *args, &block)
54
+ return super unless adapter_query.respond_to? method
55
+ adapter_query.send(method, *args, &block)
56
+ end
57
+
58
+ def inspect
59
+ "(Query object: call #to_s or #to_hash to inspect, call an Enumerable (such as #each or #first) to request data)"
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,7 @@
1
+ # Object extensions to make metaprogramming a little easier.
2
+ class Object
3
+ def metaclass; (class << self; self end) end
4
+ def meta_eval(&blk) metaclass.instance_eval(&blk) end
5
+ def meta_def(name, &blk) meta_eval { define_method name, &blk } end
6
+ def class_def(name, &blk) class_eval { define_method name, &blk } end
7
+ end
@@ -0,0 +1,6 @@
1
+ module Ambition #:nodoc:
2
+ Enumerable = ::Enumerable.dup
3
+ Enumerable.class_eval do
4
+ remove_method :find, :find_all
5
+ end
6
+ end
@@ -0,0 +1,134 @@
1
+ module Ambition #:nodoc:
2
+ module Processors #:nodoc:
3
+ class Base
4
+ ##
5
+ # Processing methods
6
+ def process_proc(exp)
7
+ # puts "=> #{exp.inspect}"
8
+ receiver, body = process(exp.shift), exp.shift
9
+ process(body)
10
+ end
11
+
12
+ def process_dasgn_curr(exp)
13
+ (@receiver = exp.first).to_s
14
+ end
15
+ alias_method :process_dasgn, :process_dasgn_curr
16
+
17
+ def process_array(exp)
18
+ # Branch on whether this is straight Ruby or a real array
19
+ rubify(exp) || exp.map { |m| process(m) }
20
+ end
21
+
22
+ def process_str(exp)
23
+ exp.first
24
+ end
25
+
26
+ def process_lit(exp)
27
+ exp.first
28
+ end
29
+
30
+ def process_nil(exp)
31
+ nil
32
+ end
33
+
34
+ def process_true(exp)
35
+ true
36
+ end
37
+
38
+ def process_false(exp)
39
+ false
40
+ end
41
+
42
+ def process_dvar(exp)
43
+ target = exp.shift
44
+ value(target.to_s[0..-1])
45
+ end
46
+
47
+ def process_ivar(exp)
48
+ value(exp.shift.to_s[0..-1])
49
+ end
50
+
51
+ def process_lvar(exp)
52
+ value(exp.shift.to_s)
53
+ end
54
+
55
+ def process_vcall(exp)
56
+ value(exp.shift.to_s)
57
+ end
58
+
59
+ def process_gvar(exp)
60
+ value(exp.shift.to_s)
61
+ end
62
+
63
+ def process(node)
64
+ node ||= []
65
+
66
+ if node.is_a? Symbol
67
+ node
68
+ elsif respond_to?(method = "process_#{node.first}")
69
+ send(method, node[1..-1])
70
+ elsif node.blank?
71
+ ''
72
+ else
73
+ raise "Missing process method for sexp: #{node.inspect}"
74
+ end
75
+ end
76
+
77
+ ##
78
+ # Helper methods
79
+ def to_s
80
+ process SexpTranslator.translate(@block)
81
+ end
82
+
83
+ def key
84
+ self.class.name.split('::').last.downcase.intern
85
+ end
86
+
87
+ def value(variable)
88
+ eval variable, @block
89
+ end
90
+
91
+ # Gives you the current translator. Uses +self.translator+ to look it up,
92
+ # if it isn't known yet.
93
+ def translator
94
+ @translator ||= self.class.translator(@context)
95
+ end
96
+
97
+ def self.translator(context, name = nil)
98
+ # Grok the adapter name
99
+ name ||= self.name.split('::').last
100
+
101
+ # Get the module for it
102
+ klass = context.owner.ambition_adapter.const_get(name)
103
+ instance = klass.new
104
+
105
+ # Make sure that the instance has everything it will need:
106
+ #
107
+ # * context
108
+ # * owner
109
+ # * clauses
110
+ # * stash
111
+ # * negated?
112
+ unless instance.respond_to? :context
113
+ klass.class_eval do
114
+ attr_accessor :context, :negated
115
+ def owner; @context.owner end
116
+ def clauses; @context.clauses end
117
+ def stash; @context.stash end
118
+ def negated?; @negated end
119
+ end
120
+ end
121
+
122
+ instance.context = context
123
+ instance
124
+ end
125
+
126
+ def rubify(exp)
127
+ # TODO: encapsulate this check in Ruby.should_process?(exp)
128
+ if exp.first.first == :call && exp.first[1].last != @receiver && Array(exp.first[1][1]).last != @receiver
129
+ value Ruby.process(exp.first)
130
+ end
131
+ end
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,26 @@
1
+ require 'ruby2ruby'
2
+
3
+ module Ambition #:nodoc:
4
+ module Processors #:nodoc:
5
+ class Ruby < RubyToRuby
6
+ def self.process(node)
7
+ @processor ||= new
8
+ @processor.process node
9
+ end
10
+
11
+ ##
12
+ # This is not DRY, and I don't care.
13
+ def process(node)
14
+ node ||= []
15
+
16
+ if respond_to?(method = "process_#{node.first}")
17
+ send(method, node[1..-1])
18
+ elsif node.blank?
19
+ ''
20
+ else
21
+ raise "Missing process method for sexp: #{node.inspect}"
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,105 @@
1
+ module Ambition #:nodoc:
2
+ module Processors #:nodoc:
3
+ class Select < Base
4
+ def initialize(context, block)
5
+ @context = context
6
+ @block = block
7
+ end
8
+
9
+ def process_call(args)
10
+ # Operation (m.name == 'chris')
11
+ # [[:call, [:dvar, :m], :name], :==, [:array, [:str, "chris"]]]
12
+ if args.size == 3
13
+ left, operator, right = args
14
+
15
+ # params are passed as an array, even when only one element:
16
+ # abc(1)
17
+ # => [:fcall, :abc, [:array, [:lit, 1]]
18
+ # abc([1])
19
+ # => [:fcall, :abc, [:array, [:array, [:lit, 1]]]]
20
+ if right.first == :array
21
+ right = process(right)
22
+ right = right.is_a?(Array) ? right.first : right
23
+ else
24
+ right = process(right)
25
+ end
26
+
27
+ translator.send(process_operator(operator), process(left), right)
28
+
29
+ # Property of passed arg:
30
+ # [[:dvar, :m], :name]
31
+ elsif args.first.last == @receiver
32
+ translator.call(*args[1..-1])
33
+
34
+ # Method call:
35
+ # [[:call, [:dvar, :m], :name], :upcase]
36
+ elsif args.first.first == :call && args.first[1].last == @receiver
37
+ receiver, method = args
38
+ translator.chained_call(receiver.last, method)
39
+
40
+ # Deep, chained call:
41
+ # [[:call, [:call, [:call, [:dvar, :m], :created_at], :something], :else], :perhaps]
42
+ elsif args.flatten.include? @receiver
43
+ calls = []
44
+
45
+ until args.empty?
46
+ args = args.last if args.last.is_a?(Array)
47
+ break if args.last == @receiver
48
+ calls << args.pop
49
+ end
50
+
51
+ translator.chained_call(*calls.reverse)
52
+
53
+ else
54
+ raise args.inspect
55
+ end
56
+ end
57
+
58
+ def process_match3(exp)
59
+ right, left = exp
60
+ process_call [ left, :=~, right ]
61
+ end
62
+
63
+ def process_and(exp)
64
+ joined_expressions exp, :both
65
+ end
66
+
67
+ def process_or(exp)
68
+ joined_expressions exp, :either
69
+ end
70
+
71
+ def joined_expressions(exp, with = nil)
72
+ expressions = []
73
+
74
+ while expression = exp.shift
75
+ expressions << process(expression)
76
+ end
77
+
78
+ translator.send(with, *expressions)
79
+ end
80
+
81
+ def process_not(args)
82
+ negate { process(args.first) }
83
+ end
84
+
85
+ def process_operator(operator)
86
+ @negated ? negate_operator(operator) : operator
87
+ end
88
+
89
+ def negate_operator(operator)
90
+ case operator
91
+ when :== then :not_equal
92
+ when :=~ then :not_regexp
93
+ else raise "Missing negated operator definition: #{operator}"
94
+ end
95
+ end
96
+
97
+ def negate
98
+ @negated = translator.negated = true
99
+ yield
100
+ ensure
101
+ @negated = translator.negated = false
102
+ end
103
+ end
104
+ end
105
+ end