squeel 0.5.5 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.yardopts +3 -0
- data/Gemfile +8 -3
- data/README.md +368 -0
- data/lib/core_ext/hash.rb +8 -8
- data/lib/core_ext/symbol.rb +7 -6
- data/lib/squeel.rb +2 -0
- data/lib/squeel/adapters/active_record.rb +25 -20
- data/lib/squeel/adapters/active_record/3.0/compat.rb +1 -2
- data/lib/squeel/adapters/active_record/3.0/context.rb +6 -7
- data/lib/squeel/adapters/active_record/3.0/join_dependency.rb +5 -5
- data/lib/squeel/adapters/active_record/context.rb +6 -7
- data/lib/squeel/adapters/active_record/join_dependency.rb +5 -5
- data/lib/squeel/configuration.rb +29 -0
- data/lib/squeel/constants.rb +1 -0
- data/lib/squeel/context.rb +36 -7
- data/lib/squeel/dsl.rb +57 -2
- data/lib/squeel/nodes.rb +6 -0
- data/lib/squeel/nodes/and.rb +1 -0
- data/lib/squeel/nodes/binary.rb +11 -2
- data/lib/squeel/nodes/function.rb +30 -48
- data/lib/squeel/nodes/join.rb +56 -12
- data/lib/squeel/nodes/key_path.rb +68 -2
- data/lib/squeel/nodes/nary.rb +12 -2
- data/lib/squeel/nodes/not.rb +1 -0
- data/lib/squeel/nodes/operation.rb +9 -0
- data/lib/squeel/nodes/operators.rb +16 -0
- data/lib/squeel/nodes/or.rb +1 -0
- data/lib/squeel/nodes/order.rb +19 -1
- data/lib/squeel/nodes/predicate.rb +25 -3
- data/lib/squeel/nodes/predicate_operators.rb +12 -0
- data/lib/squeel/nodes/stub.rb +55 -48
- data/lib/squeel/nodes/unary.rb +7 -1
- data/lib/squeel/predicate_methods.rb +2 -10
- data/lib/squeel/version.rb +1 -1
- data/lib/squeel/visitors/attribute_visitor.rb +80 -4
- data/lib/squeel/visitors/base.rb +70 -4
- data/lib/squeel/visitors/predicate_visitor.rb +28 -9
- data/lib/squeel/visitors/symbol_visitor.rb +1 -1
- data/spec/core_ext/symbol_spec.rb +2 -2
- data/spec/spec_helper.rb +6 -1
- data/spec/squeel/adapters/active_record/context_spec.rb +0 -7
- data/spec/squeel/adapters/active_record/relation_spec.rb +27 -0
- data/spec/squeel/dsl_spec.rb +20 -1
- data/spec/squeel/nodes/join_spec.rb +11 -4
- data/spec/squeel/nodes/key_path_spec.rb +1 -1
- data/spec/squeel/nodes/predicate_spec.rb +0 -42
- data/spec/squeel/nodes/stub_spec.rb +9 -8
- data/spec/squeel/visitors/predicate_visitor_spec.rb +34 -9
- data/squeel.gemspec +6 -9
- metadata +8 -10
- data/README.rdoc +0 -117
- data/lib/squeel/predicate_methods/function.rb +0 -9
- data/lib/squeel/predicate_methods/predicate.rb +0 -11
- data/lib/squeel/predicate_methods/stub.rb +0 -9
- data/lib/squeel/predicate_methods/symbol.rb +0 -9
@@ -3,35 +3,56 @@ require 'squeel/nodes/predicate_operators'
|
|
3
3
|
|
4
4
|
module Squeel
|
5
5
|
module Nodes
|
6
|
+
# This node is essentially a container that will result in ARel predicate nodes
|
7
|
+
# once visited. It stores the expression (normally an attribute name, function, or
|
8
|
+
# operation), the ARel predicate method name, and a value. these are then interpreted
|
9
|
+
# when visited by the PredicateVisitor to generate a condition against the appropriate
|
10
|
+
# columns.
|
6
11
|
class Predicate
|
7
12
|
|
8
|
-
include PredicateMethods
|
9
13
|
include PredicateOperators
|
10
14
|
|
15
|
+
# @return The right-hand value being considered in this predicate.
|
11
16
|
attr_accessor :value
|
12
|
-
attr_reader :expr, :method_name
|
13
17
|
|
18
|
+
# @return The expression on the left side of this predicate.
|
19
|
+
attr_reader :expr
|
20
|
+
|
21
|
+
# @return [Symbol] The ARel "predication" method name, such as eq, matches, etc.
|
22
|
+
attr_reader :method_name
|
23
|
+
|
24
|
+
# Create a new Predicate node with the given expression, method name, and value
|
25
|
+
# @param expr The expression for the left hand side of the predicate.
|
26
|
+
# @param [Symbol] method_name The ARel predication method
|
27
|
+
# @param value An optional value. If not given, one will need to be supplied
|
28
|
+
# before the node can be visited properly.
|
14
29
|
def initialize(expr, method_name = :eq, value = :__undefined__)
|
15
30
|
@expr, @method_name, @value = expr, method_name, value
|
16
31
|
end
|
17
32
|
|
33
|
+
# Object comparison
|
18
34
|
def eql?(other)
|
19
35
|
self.class.eql?(other.class) &&
|
20
36
|
self.expr.eql?(other.expr) &&
|
21
37
|
self.method_name.eql?(other.method_name) &&
|
22
38
|
self.value.eql?(other.value)
|
23
39
|
end
|
24
|
-
|
25
40
|
alias :== :eql?
|
26
41
|
|
42
|
+
# Implemented for equality testing
|
27
43
|
def hash
|
28
44
|
[self.class, expr, method_name, value].hash
|
29
45
|
end
|
30
46
|
|
47
|
+
# Whether the value has been assigned yet.
|
48
|
+
# @return [Boolean] Has the value been set?
|
31
49
|
def value?
|
32
50
|
@value != :__undefined__
|
33
51
|
end
|
34
52
|
|
53
|
+
# Set the value for this predicate
|
54
|
+
# @param val The value to be set
|
55
|
+
# @return [Predicate] This predicate, with its new value set
|
35
56
|
def %(val)
|
36
57
|
@value = val
|
37
58
|
self
|
@@ -40,6 +61,7 @@ module Squeel
|
|
40
61
|
# expand_hash_conditions_for_aggregates assumes our hash keys can be
|
41
62
|
# converted to symbols, so this has to be implemented, but it doesn't
|
42
63
|
# really have to do anything useful.
|
64
|
+
# @return [NilClass] Just to avoid bombing out on expand_hash_conditions_for_aggregates
|
43
65
|
def to_sym
|
44
66
|
nil
|
45
67
|
end
|
@@ -1,17 +1,29 @@
|
|
1
1
|
module Squeel
|
2
2
|
module Nodes
|
3
|
+
# Operators that act as factories for Or, And, and Not nodes for inclusion
|
4
|
+
# in classes which can be contained inside these nodes.
|
3
5
|
module PredicateOperators
|
6
|
+
|
7
|
+
# Create a new Or node, with this node as its left-hand node.
|
8
|
+
# @param other The right-hand node for the Or
|
9
|
+
# @return [Or] The new Or node
|
4
10
|
def |(other)
|
5
11
|
Or.new(self, other)
|
6
12
|
end
|
7
13
|
|
14
|
+
# Create a new And node, with this node as its left-hand node.
|
15
|
+
# @param other The right-hand node for the And
|
16
|
+
# @return [And] The new And node
|
8
17
|
def &(other)
|
9
18
|
And.new([self, other])
|
10
19
|
end
|
11
20
|
|
21
|
+
# Create a new Not node, with this node as its expression
|
22
|
+
# @return [Not] The new Not node
|
12
23
|
def -@
|
13
24
|
Not.new(self)
|
14
25
|
end
|
26
|
+
|
15
27
|
end
|
16
28
|
end
|
17
29
|
end
|
data/lib/squeel/nodes/stub.rb
CHANGED
@@ -3,111 +3,118 @@ require 'squeel/nodes/operators'
|
|
3
3
|
|
4
4
|
module Squeel
|
5
5
|
module Nodes
|
6
|
+
# Stub nodes are basically a container for a Symbol that can have handy predicate
|
7
|
+
# methods and operators defined on it since doing so on Symbol will incur the
|
8
|
+
# nerdrage of many.
|
6
9
|
class Stub
|
7
10
|
|
8
11
|
include PredicateMethods
|
9
12
|
include Operators
|
10
13
|
|
14
|
+
alias :== :eq
|
15
|
+
alias :'^' :not_eq
|
16
|
+
alias :'!=' :not_eq if respond_to?(:'!=')
|
17
|
+
alias :>> :in
|
18
|
+
alias :<< :not_in
|
19
|
+
alias :=~ :matches
|
20
|
+
alias :'!~' :does_not_match if respond_to?(:'!~')
|
21
|
+
alias :> :gt
|
22
|
+
alias :>= :gteq
|
23
|
+
alias :< :lt
|
24
|
+
alias :<= :lteq
|
25
|
+
|
26
|
+
undef_method :id if method_defined?(:id)
|
27
|
+
|
28
|
+
# @return [Symbol] The symbol contained by this stub
|
11
29
|
attr_reader :symbol
|
12
30
|
|
31
|
+
# Create a new Stub.
|
32
|
+
# @param [Symbol] symbol The symbol that this Stub contains
|
13
33
|
def initialize(symbol)
|
14
34
|
@symbol = symbol
|
15
35
|
end
|
16
36
|
|
37
|
+
# Object comparison
|
17
38
|
def eql?(other)
|
18
39
|
self.class == other.class &&
|
19
40
|
self.symbol == other.symbol
|
20
41
|
end
|
21
42
|
|
43
|
+
# To support object equality tests
|
22
44
|
def hash
|
23
45
|
symbol.hash
|
24
46
|
end
|
25
47
|
|
48
|
+
# @return [Symbol] The symbol this Stub contains.
|
26
49
|
def to_sym
|
27
50
|
symbol
|
28
51
|
end
|
29
52
|
|
53
|
+
# @return [String] The Stub's String equivalent.
|
30
54
|
def to_s
|
31
55
|
symbol.to_s
|
32
56
|
end
|
33
57
|
|
58
|
+
# Create a KeyPath when any undefined method is called on a Stub.
|
59
|
+
# @overload node_name
|
60
|
+
# Creates a new KeyPath with this Stub as the base and the method_name as the endpoint
|
61
|
+
# @return [KeyPath] The new keypath
|
62
|
+
# @overload node_name(klass)
|
63
|
+
# Creates a new KeyPath with this Stub as the base and a polymorphic belongs_to join as the endpoint
|
64
|
+
# @param [Class] klass The polymorphic class for the join
|
65
|
+
# @return [KeyPath] The new keypath
|
34
66
|
def method_missing(method_id, *args)
|
35
67
|
super if method_id == :to_ary
|
36
|
-
if
|
68
|
+
if args.empty?
|
69
|
+
KeyPath.new(self, method_id)
|
70
|
+
elsif (args.size == 1) && (Class === args[0])
|
37
71
|
KeyPath.new(self, Join.new(method_id, Arel::InnerJoin, args[0]))
|
38
72
|
else
|
39
|
-
KeyPath.new(self, method_id)
|
73
|
+
KeyPath.new(self, Nodes::Function.new(method_id, args))
|
40
74
|
end
|
41
75
|
end
|
42
76
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
Predicate.new(self.symbol, :not_eq, value)
|
50
|
-
end if respond_to?('!=')
|
51
|
-
|
52
|
-
def ^(value)
|
53
|
-
Predicate.new self.symbol, :not_eq, value
|
54
|
-
end
|
55
|
-
|
56
|
-
def >>(value)
|
57
|
-
Predicate.new self.symbol, :in, value
|
58
|
-
end
|
59
|
-
|
60
|
-
def <<(value)
|
61
|
-
Predicate.new self.symbol, :not_in, value
|
62
|
-
end
|
63
|
-
|
64
|
-
def =~(value)
|
65
|
-
Predicate.new self.symbol, :matches, value
|
66
|
-
end
|
67
|
-
|
68
|
-
# Won't work on Ruby 1.8.x so need to do this conditionally
|
69
|
-
define_method('!~') do |value|
|
70
|
-
Predicate.new(self.symbol, :does_not_match, value)
|
71
|
-
end if respond_to?('!~')
|
72
|
-
|
73
|
-
def >(value)
|
74
|
-
Predicate.new self.symbol, :gt, value
|
75
|
-
end
|
76
|
-
|
77
|
-
def >=(value)
|
78
|
-
Predicate.new self.symbol, :gteq, value
|
79
|
-
end
|
80
|
-
|
81
|
-
def <(value)
|
82
|
-
Predicate.new self.symbol, :lt, value
|
83
|
-
end
|
84
|
-
|
85
|
-
def <=(value)
|
86
|
-
Predicate.new self.symbol, :lteq, value
|
77
|
+
# Return a KeyPath containing only this Stub, but flagged as absolute.
|
78
|
+
# This helps Stubs behave more like a KeyPath, as anyone using the Squeel
|
79
|
+
# DSL is likely to think of them as such.
|
80
|
+
# @return [KeyPath] An absolute KeyPath, containing only this Stub
|
81
|
+
def ~
|
82
|
+
KeyPath.new [], self, true
|
87
83
|
end
|
88
84
|
|
85
|
+
# Create an ascending Order node with this Stub's symbol as its expression
|
86
|
+
# @return [Order] The new Order node
|
89
87
|
def asc
|
90
88
|
Order.new self.symbol, 1
|
91
89
|
end
|
92
90
|
|
91
|
+
# Create a descending Order node with this Stub's symbol as its expression
|
92
|
+
# @return [Order] The new Order node
|
93
93
|
def desc
|
94
94
|
Order.new self.symbol, -1
|
95
95
|
end
|
96
96
|
|
97
|
+
# Create a Function node for a function named the same as this Stub and with the given arguments
|
98
|
+
# @return [Function] The new Function node
|
97
99
|
def func(*args)
|
98
100
|
Function.new(self.symbol, args)
|
99
101
|
end
|
100
102
|
|
101
|
-
|
102
|
-
|
103
|
+
# Create an inner Join node for the association named by this Stub
|
104
|
+
# @return [Join] The new inner Join node
|
103
105
|
def inner
|
104
106
|
Join.new(self.symbol, Arel::InnerJoin)
|
105
107
|
end
|
106
108
|
|
109
|
+
# Create an outer Join node for the association named by this Stub
|
110
|
+
# @return [Join] The new outer Join node
|
107
111
|
def outer
|
108
112
|
Join.new(self.symbol, Arel::OuterJoin)
|
109
113
|
end
|
110
114
|
|
115
|
+
# Create a polymorphic Join node for the association named by this Stub,
|
116
|
+
# @param [Class] klass The polymorphic belongs_to class for this Join
|
117
|
+
# @return [Join] The new polymorphic Join node
|
111
118
|
def of_class(klass)
|
112
119
|
Join.new(self.symbol, Arel::InnerJoin, klass)
|
113
120
|
end
|
data/lib/squeel/nodes/unary.rb
CHANGED
@@ -2,21 +2,27 @@ require 'squeel/nodes/predicate_operators'
|
|
2
2
|
|
3
3
|
module Squeel
|
4
4
|
module Nodes
|
5
|
+
# A node that contains a single expression.
|
5
6
|
class Unary
|
7
|
+
|
6
8
|
include PredicateOperators
|
7
9
|
|
10
|
+
# @return The expression contained in the node
|
8
11
|
attr_reader :expr
|
9
12
|
|
13
|
+
# Create a new Unary node.
|
14
|
+
# @param expr The expression to contain inside the node.
|
10
15
|
def initialize(expr)
|
11
16
|
@expr = expr
|
12
17
|
end
|
13
18
|
|
19
|
+
# Object comparison
|
14
20
|
def eql?(other)
|
15
21
|
self.class == other.class &&
|
16
22
|
self.expr == other.expr
|
17
23
|
end
|
18
|
-
|
19
24
|
alias :== :eql?
|
25
|
+
|
20
26
|
end
|
21
27
|
end
|
22
28
|
end
|
@@ -1,19 +1,11 @@
|
|
1
|
-
require 'squeel/predicate_methods/symbol'
|
2
|
-
require 'squeel/predicate_methods/stub'
|
3
|
-
require 'squeel/predicate_methods/predicate'
|
4
|
-
require 'squeel/predicate_methods/function'
|
5
|
-
|
6
1
|
module Squeel
|
2
|
+
# Defines Nodes::Predicate factories named for each of the ARel predication methods
|
7
3
|
module PredicateMethods
|
8
4
|
|
9
|
-
def self.included(base)
|
10
|
-
base.send :include, const_get(base.name.split(/::/)[-1].to_sym)
|
11
|
-
end
|
12
|
-
|
13
5
|
Constants::PREDICATES.each do |method_name|
|
14
6
|
class_eval <<-RUBY
|
15
7
|
def #{method_name}(value = :__undefined__)
|
16
|
-
|
8
|
+
Nodes::Predicate.new self, :#{method_name}, value
|
17
9
|
end
|
18
10
|
RUBY
|
19
11
|
end
|
data/lib/squeel/version.rb
CHANGED
@@ -2,8 +2,18 @@ require 'squeel/visitors/base'
|
|
2
2
|
|
3
3
|
module Squeel
|
4
4
|
module Visitors
|
5
|
+
# A visitor that tries to convert visited nodes into Arel::Attributes
|
6
|
+
# or other nodes that can be used for grouping, ordering, and the like.
|
5
7
|
class AttributeVisitor < Base
|
6
8
|
|
9
|
+
private
|
10
|
+
|
11
|
+
# Visit a Hash. This entails iterating through each key and value and
|
12
|
+
# visiting each value in turn.
|
13
|
+
#
|
14
|
+
# @param [Hash] o The Hash to visit
|
15
|
+
# @param parent The current parent object in the context
|
16
|
+
# @return [Array] An array of values for use in an ordering, grouping, etc.
|
7
17
|
def visit_Hash(o, parent)
|
8
18
|
o.map do |k, v|
|
9
19
|
if implies_context_change?(v)
|
@@ -14,11 +24,19 @@ module Squeel
|
|
14
24
|
end.flatten
|
15
25
|
end
|
16
26
|
|
27
|
+
# @return [Boolean] Whether the given value implies a context change
|
28
|
+
# @param v The value to consider
|
17
29
|
def implies_context_change?(v)
|
18
|
-
|
19
|
-
(Array === v && !v.empty? && v.all? {|val| can_accept?(val)})
|
30
|
+
can_accept?(v)
|
20
31
|
end
|
21
32
|
|
33
|
+
# Change context (by setting the new parent to the result of a #find or
|
34
|
+
# #traverse on the key), then accept the given value.
|
35
|
+
#
|
36
|
+
# @param k The hash key
|
37
|
+
# @param v The hash value
|
38
|
+
# @param parent The current parent object in the context
|
39
|
+
# @return The visited value
|
22
40
|
def visit_with_context_change(k, v, parent)
|
23
41
|
parent = case k
|
24
42
|
when Nodes::KeyPath
|
@@ -34,32 +52,84 @@ module Squeel
|
|
34
52
|
end
|
35
53
|
end
|
36
54
|
|
55
|
+
# If there is no context change, we'll just return the value unchanged,
|
56
|
+
# currently. Is this really the right behavior? I don't think so, but
|
57
|
+
# it works in this case.
|
58
|
+
#
|
59
|
+
# @param k The hash key
|
60
|
+
# @param v The hash value
|
61
|
+
# @param parent The current parent object in the context
|
62
|
+
# @return The same value we just received. Yeah, this method's pretty pointless,
|
63
|
+
# for now, and only here for consistency's sake.
|
37
64
|
def visit_without_context_change(k, v, parent)
|
38
65
|
v
|
39
66
|
end
|
40
67
|
|
68
|
+
# Visit elements of an array that it's possible to visit -- leave other
|
69
|
+
# elements untouched.
|
70
|
+
#
|
71
|
+
# @param [Array] o The array to visit
|
72
|
+
# @param parent The array's parent within the context
|
73
|
+
# @return [Array] The flattened array with elements visited
|
41
74
|
def visit_Array(o, parent)
|
42
75
|
o.map { |v| can_accept?(v) ? accept(v, parent) : v }.flatten
|
43
76
|
end
|
44
77
|
|
78
|
+
# Visit a symbol. This will return an attribute named after the symbol against
|
79
|
+
# the current parent's contextualized table.
|
80
|
+
#
|
81
|
+
# @param [Symbol] o The symbol to visit
|
82
|
+
# @param parent The symbol's parent within the context
|
83
|
+
# @return [Arel::Attribute] An attribute on the contextualized parent table
|
45
84
|
def visit_Symbol(o, parent)
|
46
85
|
contextualize(parent)[o]
|
47
86
|
end
|
48
87
|
|
88
|
+
# Visit a stub. This will return an attribute named after the stub against
|
89
|
+
# the current parent's contextualized table.
|
90
|
+
#
|
91
|
+
# @param [Nodes::Stub] o The stub to visit
|
92
|
+
# @param parent The stub's parent within the context
|
93
|
+
# @return [Arel::Attribute] An attribute on the contextualized parent table
|
49
94
|
def visit_Squeel_Nodes_Stub(o, parent)
|
50
95
|
contextualize(parent)[o.symbol]
|
51
96
|
end
|
52
97
|
|
98
|
+
# Visit a keypath. This will traverse the keypath's "path", setting a new
|
99
|
+
# parent as though the keypath's endpoint was in a deeply-nested hash,
|
100
|
+
# then visit the endpoint with the new parent.
|
101
|
+
#
|
102
|
+
# @param [Nodes::KeyPath] o The keypath to visit
|
103
|
+
# @param parent The keypath's parent within the context
|
104
|
+
# @return The visited endpoint, with the parent from the KeyPath's path.
|
53
105
|
def visit_Squeel_Nodes_KeyPath(o, parent)
|
54
106
|
parent = traverse(o, parent)
|
55
107
|
|
56
108
|
accept(o.endpoint, parent)
|
57
109
|
end
|
58
110
|
|
111
|
+
# Visit an Order node.
|
112
|
+
#
|
113
|
+
# @param [Nodes::Order] o The order node to visit
|
114
|
+
# @param parent The node's parent within the context
|
115
|
+
# @return [Arel::Nodes::Ordering] An ascending or desending ordering
|
59
116
|
def visit_Squeel_Nodes_Order(o, parent)
|
60
117
|
accept(o.expr, parent).send(o.descending? ? :desc : :asc)
|
61
118
|
end
|
62
119
|
|
120
|
+
# Visit a Function node. Each function argument will be accepted or
|
121
|
+
# contextualized if appropriate. Keep in mind that this occurs with
|
122
|
+
# the current parent within the context.
|
123
|
+
#
|
124
|
+
# @example A function as the endpoint of a keypath
|
125
|
+
# Person.joins{children}.order{children.coalesce(name, '<no name>')}
|
126
|
+
# # => SELECT "people".* FROM "people"
|
127
|
+
# INNER JOIN "people" "children_people"
|
128
|
+
# ON "children_people"."parent_id" = "people"."id"
|
129
|
+
# ORDER BY coalesce("children_people"."name", '<no name>')
|
130
|
+
#
|
131
|
+
# @param [Nodes::Function] o The function node to visit
|
132
|
+
# @param parent The node's parent within the context
|
63
133
|
def visit_Squeel_Nodes_Function(o, parent)
|
64
134
|
args = o.args.map do |arg|
|
65
135
|
case arg
|
@@ -68,12 +138,18 @@ module Squeel
|
|
68
138
|
when Symbol, Nodes::Stub
|
69
139
|
Arel.sql(arel_visitor.accept contextualize(parent)[arg.to_sym])
|
70
140
|
else
|
71
|
-
|
141
|
+
quote arg
|
72
142
|
end
|
73
143
|
end
|
74
144
|
Arel::Nodes::NamedFunction.new(o.name, args, o.alias)
|
75
145
|
end
|
76
146
|
|
147
|
+
# Visit an Operation node. Each operand will be accepted or
|
148
|
+
# contextualized if appropriate. Keep in mind that this occurs with
|
149
|
+
# the current parent within the context.
|
150
|
+
#
|
151
|
+
# @param [Nodes::Operation] o The operation node to visit
|
152
|
+
# @param parent The node's parent within the context
|
77
153
|
def visit_Squeel_Nodes_Operation(o, parent)
|
78
154
|
args = o.args.map do |arg|
|
79
155
|
case arg
|
@@ -82,7 +158,7 @@ module Squeel
|
|
82
158
|
when Symbol, Nodes::Stub
|
83
159
|
Arel.sql(arel_visitor.accept contextualize(parent)[arg.to_sym])
|
84
160
|
else
|
85
|
-
|
161
|
+
quote arg
|
86
162
|
end
|
87
163
|
end
|
88
164
|
|