filigree 0.3.0 → 0.3.1
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.
- checksums.yaml +4 -4
- data/README.md +20 -18
- data/lib/filigree/abstract_class.rb +7 -7
- data/lib/filigree/application.rb +12 -12
- data/lib/filigree/class.rb +3 -3
- data/lib/filigree/class_methods_module.rb +5 -1
- data/lib/filigree/commands.rb +40 -40
- data/lib/filigree/configuration.rb +72 -70
- data/lib/filigree/match.rb +82 -71
- data/lib/filigree/string.rb +8 -8
- data/lib/filigree/types.rb +10 -10
- data/lib/filigree/version.rb +1 -1
- data/lib/filigree/visitor.rb +80 -45
- data/test/tc_abstract_class.rb +16 -16
- data/test/tc_application.rb +7 -7
- data/test/tc_boolean.rb +4 -4
- data/test/tc_class.rb +9 -9
- data/test/tc_class_methods_module.rb +69 -11
- data/test/tc_commands.rb +12 -12
- data/test/tc_configuration.rb +43 -43
- data/test/tc_match.rb +72 -58
- data/test/tc_object.rb +7 -7
- data/test/tc_string.rb +3 -3
- data/test/tc_types.rb +29 -29
- data/test/tc_visitor.rb +108 -58
- metadata +54 -54
@@ -20,7 +20,7 @@ require 'filigree/string'
|
|
20
20
|
module Filigree
|
21
21
|
module Configuration
|
22
22
|
include ClassMethodsModule
|
23
|
-
|
23
|
+
|
24
24
|
#############
|
25
25
|
# Constants #
|
26
26
|
#############
|
@@ -28,10 +28,10 @@ module Filigree
|
|
28
28
|
####################
|
29
29
|
# Instance Methods #
|
30
30
|
####################
|
31
|
-
|
31
|
+
|
32
32
|
# @return [Array<String>] Remaining strings that weren't used in configuration
|
33
33
|
attr_accessor :rest
|
34
|
-
|
34
|
+
|
35
35
|
# Dump the state of the Configuration object. This will dump the
|
36
36
|
# state, encoded in YAML, to different destinations depending on the
|
37
37
|
# io parameter.
|
@@ -54,25 +54,25 @@ module Filigree
|
|
54
54
|
# @return [void]
|
55
55
|
def dump(io = nil, *fields)
|
56
56
|
require 'yaml'
|
57
|
-
|
57
|
+
|
58
58
|
vals =
|
59
59
|
if fields.empty? then self.class.options_long.keys else fields end.inject(Hash.new) do |hash, field|
|
60
60
|
hash.tap { hash[field.to_s] = self.send(field) }
|
61
61
|
end
|
62
|
-
|
62
|
+
|
63
63
|
case io
|
64
64
|
when nil
|
65
65
|
YAML.dump vals
|
66
|
-
|
66
|
+
|
67
67
|
when String
|
68
68
|
File.open(io, 'w') { |file| YAML.dump vals, file }
|
69
|
-
|
69
|
+
|
70
70
|
when IO
|
71
71
|
YAML.dump vals, io
|
72
72
|
end
|
73
73
|
end
|
74
74
|
alias :serialize :dump
|
75
|
-
|
75
|
+
|
76
76
|
# Configures the object based on the overloaded parameter.
|
77
77
|
#
|
78
78
|
# @overload initialize(args)
|
@@ -89,28 +89,28 @@ module Filigree
|
|
89
89
|
# @return [void]
|
90
90
|
def initialize(overloaded = ARGV.clone)
|
91
91
|
set_opts = Array.new
|
92
|
-
|
92
|
+
|
93
93
|
case overloaded
|
94
94
|
when Array
|
95
95
|
handle_array_options(overloaded, set_opts)
|
96
|
-
|
96
|
+
|
97
97
|
when String, IO
|
98
98
|
handle_serialized_options(overloaded, set_opts)
|
99
99
|
end
|
100
|
-
|
100
|
+
|
101
101
|
(self.class.options_long.keys - set_opts).each do |opt_name|
|
102
102
|
default = self.class.options_long[opt_name].default
|
103
103
|
default = self.instance_exec(&default) if default.is_a? Proc
|
104
|
-
|
104
|
+
|
105
105
|
self.send("#{opt_name}=", default)
|
106
106
|
end
|
107
|
-
|
107
|
+
|
108
108
|
# Check to make sure all the required options are set.
|
109
109
|
self.class.required_options.each do |option|
|
110
110
|
raise ArgumentError, "Option #{option} not set." if self.send(option).nil?
|
111
111
|
end
|
112
112
|
end
|
113
|
-
|
113
|
+
|
114
114
|
# Find the appropriate option object given a string.
|
115
115
|
#
|
116
116
|
# @param [String] str Search string
|
@@ -119,49 +119,49 @@ module Filigree
|
|
119
119
|
def find_option(str)
|
120
120
|
if str[0,2] == '--'
|
121
121
|
self.class.options_long[str[2..-1]]
|
122
|
-
|
122
|
+
|
123
123
|
elsif str[0,1] == '-'
|
124
124
|
self.class.options_short[str[1..-1]]
|
125
125
|
end
|
126
126
|
end
|
127
|
-
|
127
|
+
|
128
128
|
# Configure the object from an array of strings.
|
129
129
|
#
|
130
130
|
# @param [Array<String>] argv String options
|
131
131
|
# @param [Array<String>] set_opts List of names of options already added
|
132
132
|
#
|
133
|
-
# @return [void]
|
133
|
+
# @return [void]
|
134
134
|
def handle_array_options(argv, set_opts)
|
135
135
|
while str = argv.shift
|
136
|
-
|
136
|
+
|
137
137
|
break if str == '--'
|
138
|
-
|
138
|
+
|
139
139
|
if option = find_option(str)
|
140
140
|
args = argv.shift(option.arity == -1 ? argv.index { |str| str[0,1] == '-' } : option.arity)
|
141
|
-
|
141
|
+
|
142
142
|
case option.handler
|
143
143
|
when Array
|
144
144
|
tmp = args.zip(option.handler).map { |arg, sym| arg.send sym }
|
145
145
|
self.send("#{option.long}=", (option.arity == 1 and tmp.length == 1) ? tmp.first : tmp)
|
146
|
-
|
146
|
+
|
147
147
|
when Proc
|
148
148
|
self.send("#{option.long}=", self.instance_exec(*args, &option.handler))
|
149
149
|
end
|
150
|
-
|
150
|
+
|
151
151
|
set_opts << option.long
|
152
152
|
end
|
153
153
|
end
|
154
|
-
|
154
|
+
|
155
155
|
# Save the rest of the command line for later.
|
156
156
|
self.rest = argv
|
157
157
|
end
|
158
|
-
|
158
|
+
|
159
159
|
# Configure the object from a serialization source.
|
160
160
|
#
|
161
161
|
# @param [String, IO] overloaded Serialization source
|
162
162
|
# @param [Array<String>] set_opts List of names of options already added
|
163
163
|
#
|
164
|
-
# @return [void]
|
164
|
+
# @return [void]
|
165
165
|
def handle_serialized_options(overloaded, set_opts)
|
166
166
|
options =
|
167
167
|
if overloaded.is_a? String
|
@@ -173,33 +173,35 @@ module Filigree
|
|
173
173
|
else
|
174
174
|
YAML.load overloaded
|
175
175
|
end
|
176
|
-
|
176
|
+
|
177
177
|
options.each do |option, val|
|
178
178
|
set_opts << option
|
179
179
|
self.send "#{option}=", val
|
180
180
|
end
|
181
181
|
end
|
182
|
-
|
182
|
+
|
183
183
|
#################
|
184
184
|
# Class Methods #
|
185
185
|
#################
|
186
|
-
|
186
|
+
|
187
187
|
module ClassMethods
|
188
188
|
# @return [Hash<String, Option>] Hash of options with long names used as keys
|
189
189
|
attr_reader :options_long
|
190
190
|
# @return [Hash<String, Option>] hash of options with short name used as keys
|
191
191
|
attr_reader :options_short
|
192
|
-
|
192
|
+
|
193
193
|
# Add an option to the necessary data structures.
|
194
194
|
#
|
195
195
|
# @param [Option] opt Option to add
|
196
196
|
#
|
197
197
|
# @return [void]
|
198
198
|
def add_option(opt)
|
199
|
+
attr_accessor opt.long
|
200
|
+
|
199
201
|
@options_long[opt.long] = opt
|
200
202
|
@options_short[opt.short] = opt unless opt.short.nil?
|
201
203
|
end
|
202
|
-
|
204
|
+
|
203
205
|
# Define an automatic configuration variable.
|
204
206
|
#
|
205
207
|
# @param [Symbol] name Name of the configuration variable
|
@@ -209,7 +211,7 @@ module Filigree
|
|
209
211
|
def auto(name, &block)
|
210
212
|
define_method(name, &block)
|
211
213
|
end
|
212
|
-
|
214
|
+
|
213
215
|
# Define a boolean option. The variable will be set to true if
|
214
216
|
# the flag is seen and be false otherwise.
|
215
217
|
#
|
@@ -221,7 +223,7 @@ module Filigree
|
|
221
223
|
@next_default = false
|
222
224
|
option(long, short) { true }
|
223
225
|
end
|
224
|
-
|
226
|
+
|
225
227
|
# Sets the default value for the next command. If a block is
|
226
228
|
# provided it will be used. If not, the val parameter will be.
|
227
229
|
#
|
@@ -232,7 +234,7 @@ module Filigree
|
|
232
234
|
def default(val = nil, &block)
|
233
235
|
@next_default = block ? block : val
|
234
236
|
end
|
235
|
-
|
237
|
+
|
236
238
|
# Sets the help string for the next command.
|
237
239
|
#
|
238
240
|
# @param [String] str Command help string
|
@@ -241,20 +243,20 @@ module Filigree
|
|
241
243
|
def help(str)
|
242
244
|
@help_string = str
|
243
245
|
end
|
244
|
-
|
246
|
+
|
245
247
|
# Install the instance class variables in the including class.
|
246
248
|
#
|
247
249
|
# @return [void]
|
248
250
|
def install_icvars
|
249
|
-
@help_string
|
250
|
-
@next_default
|
251
|
-
@next_required
|
252
|
-
@options_long
|
253
|
-
@options_short
|
254
|
-
@required
|
255
|
-
@usage
|
251
|
+
@help_string = ''
|
252
|
+
@next_default = nil
|
253
|
+
@next_required = false
|
254
|
+
@options_long = Hash.new
|
255
|
+
@options_short = Hash.new
|
256
|
+
@required = Array.new
|
257
|
+
@usage = ''
|
256
258
|
end
|
257
|
-
|
259
|
+
|
258
260
|
# Define a new option.
|
259
261
|
#
|
260
262
|
# @param [String] long Long option name
|
@@ -264,23 +266,23 @@ module Filigree
|
|
264
266
|
#
|
265
267
|
# @return [void]
|
266
268
|
def option(long, short = nil, conversions: nil, &block)
|
267
|
-
|
269
|
+
|
268
270
|
attr_accessor long.to_sym
|
269
|
-
|
271
|
+
|
270
272
|
long = long.to_s
|
271
273
|
short = short.to_s if short
|
272
|
-
|
274
|
+
|
273
275
|
add_option Option.new(long, short, @help_string, @next_default,
|
274
|
-
|
275
|
-
|
276
|
+
conversions.nil? ? block : conversions)
|
277
|
+
|
276
278
|
@required << long.to_sym if @next_required
|
277
|
-
|
279
|
+
|
278
280
|
# Reset state between option declarations.
|
279
|
-
@help_string
|
280
|
-
@next_default
|
281
|
+
@help_string = ''
|
282
|
+
@next_default = nil
|
281
283
|
@next_required = false
|
282
284
|
end
|
283
|
-
|
285
|
+
|
284
286
|
# Mark some options as required. If no names are provided then
|
285
287
|
# the next option to be defined is required; if names are
|
286
288
|
# provided they are all marked as required.
|
@@ -295,12 +297,12 @@ module Filigree
|
|
295
297
|
@required += names
|
296
298
|
end
|
297
299
|
end
|
298
|
-
|
300
|
+
|
299
301
|
# @return [Array<Symbol>] Options that need to be marked as required
|
300
302
|
def required_options
|
301
303
|
@required
|
302
304
|
end
|
303
|
-
|
305
|
+
|
304
306
|
# Define an option that takes a single string argument.
|
305
307
|
#
|
306
308
|
# @param [String] long Long option name
|
@@ -310,7 +312,7 @@ module Filigree
|
|
310
312
|
def string_option(long, short = nil)
|
311
313
|
option(long, short) { |str| str }
|
312
314
|
end
|
313
|
-
|
315
|
+
|
314
316
|
# Add's a usage string to the entire configuration object. If
|
315
317
|
# no string is provided the current usage string is returned.
|
316
318
|
#
|
@@ -318,22 +320,22 @@ module Filigree
|
|
318
320
|
#
|
319
321
|
# @return [String] Current or new usage string
|
320
322
|
def usage(str = nil)
|
321
|
-
if str then @usage = str else @usage end
|
323
|
+
if str then @usage = str else @usage end
|
322
324
|
end
|
323
|
-
|
325
|
+
|
324
326
|
#############
|
325
327
|
# Callbacks #
|
326
328
|
#############
|
327
|
-
|
329
|
+
|
328
330
|
def self.extended(klass)
|
329
331
|
klass.install_icvars
|
330
332
|
end
|
331
333
|
end
|
332
|
-
|
334
|
+
|
333
335
|
#################
|
334
336
|
# Inner Classes #
|
335
337
|
#################
|
336
|
-
|
338
|
+
|
337
339
|
# This class represents an option that can appear in the
|
338
340
|
# configuration.
|
339
341
|
class Option < Struct.new(:long, :short, :help, :default, :handler)
|
@@ -342,11 +344,11 @@ module Filigree
|
|
342
344
|
# @return [Fixnum] Number of arguments the option takes
|
343
345
|
def arity
|
344
346
|
case self.handler
|
345
|
-
when Array
|
346
|
-
when Proc
|
347
|
+
when Array then self.handler.length
|
348
|
+
when Proc then self.handler.arity
|
347
349
|
end
|
348
350
|
end
|
349
|
-
|
351
|
+
|
350
352
|
# Print the option information out as a string.
|
351
353
|
#
|
352
354
|
# Layout:
|
@@ -362,14 +364,14 @@ module Filigree
|
|
362
364
|
def to_s(max_long, max_short, indent = 0)
|
363
365
|
segment_indent = indent + max_long + max_short + 8
|
364
366
|
segmented_help = self.help.segment(segment_indent)
|
365
|
-
|
367
|
+
|
366
368
|
if self.short
|
367
369
|
sprintf "#{' ' * indent}%-#{max_long + 3}s %-#{max_short + 1}s - %s", "--#{self.long},", '-' + self.short, segmented_help
|
368
370
|
else
|
369
371
|
sprintf "#{' ' * indent}%-#{max_long + max_short + 5}s - %s", '--' + self.long, segmented_help
|
370
372
|
end
|
371
373
|
end
|
372
|
-
|
374
|
+
|
373
375
|
# Helper method used to print out information on a set of options.
|
374
376
|
#
|
375
377
|
# @param [Array<Option>] options Options to be printed
|
@@ -378,29 +380,29 @@ module Filigree
|
|
378
380
|
# @return [String]
|
379
381
|
def self.to_s(options, indent = 0)
|
380
382
|
lines = []
|
381
|
-
|
383
|
+
|
382
384
|
max_long = options.lazy.map { |opt| opt.long.length }.max
|
383
385
|
max_short = options.lazy.map(&:short).reject { |opt| opt.nil? }.map(&:length).max
|
384
|
-
|
386
|
+
|
385
387
|
options.each do |opt|
|
386
388
|
lines << opt.to_s(max_long, max_short, indent)
|
387
389
|
end
|
388
|
-
|
390
|
+
|
389
391
|
lines.join("\n")
|
390
392
|
end
|
391
393
|
end
|
392
|
-
|
394
|
+
|
393
395
|
#######################
|
394
396
|
# Pre-defined Options #
|
395
397
|
#######################
|
396
|
-
|
398
|
+
|
397
399
|
# The default help option. This can be added to your class via
|
398
400
|
# add_option.
|
399
401
|
HELP_OPTION = Option.new('help', 'h', 'Prints this help message.', nil, Proc.new do
|
400
402
|
puts "Usage: #{self.class.usage}"
|
401
403
|
puts
|
402
404
|
puts 'Options:'
|
403
|
-
|
405
|
+
|
404
406
|
options = self.class.options_long.values.sort { |a, b| a.long <=> b.long }
|
405
407
|
puts Option.to_s(options, 2)
|
406
408
|
|
data/lib/filigree/match.rb
CHANGED
@@ -43,7 +43,7 @@ class MatchError < RuntimeError; end
|
|
43
43
|
# accidentally compare against a variable. To bind to a name that is already
|
44
44
|
# in scope you can use the {Filigree::MatchEnvironment#Bind} method. In
|
45
45
|
# addition, class and destructuring pattern results (see bellow) can be bound
|
46
|
-
# to a variable by using the {Filigree::BasicPattern#as} method.
|
46
|
+
# to a variable by using the {Filigree::BasicPattern#as} method.
|
47
47
|
|
48
48
|
# If you wish to match string patterns you may use regular expressions. Any
|
49
49
|
# object that isn't a string will fail to match against a regular expression.
|
@@ -55,7 +55,7 @@ class MatchError < RuntimeError; end
|
|
55
55
|
# instance of that class. If you wish to compare one regular expression to
|
56
56
|
# another, or one class to another, you can force the comparison using the
|
57
57
|
# {Filigree::MatchEnvironment#Literal} method.
|
58
|
-
#
|
58
|
+
#
|
59
59
|
# Destructuring patterns allow you to match against an instance of a class,
|
60
60
|
# while simultaneously binding values stored inside the object to variables
|
61
61
|
# in the context of the with block. A class that is destructurable must
|
@@ -131,7 +131,7 @@ class MatchError < RuntimeError; end
|
|
131
131
|
# @a = a
|
132
132
|
# @b = b
|
133
133
|
# end
|
134
|
-
#
|
134
|
+
#
|
135
135
|
# def destructure(_)
|
136
136
|
# [@a, @b]
|
137
137
|
# end
|
@@ -156,9 +156,9 @@ class MatchError < RuntimeError; end
|
|
156
156
|
# @return [Object] Result of evaluating the matched pattern's block
|
157
157
|
def match(*objects, &block)
|
158
158
|
me = Filigree::MatchEnvironment.new
|
159
|
-
|
159
|
+
|
160
160
|
me.instance_exec &block
|
161
|
-
|
161
|
+
|
162
162
|
me.find_match(objects)
|
163
163
|
end
|
164
164
|
|
@@ -167,11 +167,11 @@ end
|
|
167
167
|
#######################
|
168
168
|
|
169
169
|
module Filigree
|
170
|
-
|
170
|
+
|
171
171
|
###########
|
172
172
|
# Methods #
|
173
173
|
###########
|
174
|
-
|
174
|
+
|
175
175
|
# Wrap non-pattern objects in pattern objects so they can all be treated
|
176
176
|
# in the same way during pattern sorting and matching.
|
177
177
|
#
|
@@ -188,11 +188,11 @@ module Filigree
|
|
188
188
|
end
|
189
189
|
end
|
190
190
|
end
|
191
|
-
|
191
|
+
|
192
192
|
#######################
|
193
193
|
# Modules and Classes #
|
194
194
|
#######################
|
195
|
-
|
195
|
+
|
196
196
|
# A module indicating that an object may be destructured. The including
|
197
197
|
# class must define the `destructure` instance method, which takes one
|
198
198
|
# argument specifying the number of pattern elements it is being matched
|
@@ -207,7 +207,7 @@ module Filigree
|
|
207
207
|
DestructuringPattern.new(self, Filigree::wrap_pattern_elements(pattern))
|
208
208
|
end
|
209
209
|
end
|
210
|
-
|
210
|
+
|
211
211
|
# Match blocks are evaluated inside an instance of MatchEnvironment.
|
212
212
|
class MatchEnvironment
|
213
213
|
# Force binding to the given name
|
@@ -218,21 +218,21 @@ module Filigree
|
|
218
218
|
def Bind(name)
|
219
219
|
BindingPattern.new(name)
|
220
220
|
end
|
221
|
-
|
221
|
+
|
222
222
|
# Force a literal comparison
|
223
223
|
#
|
224
224
|
# @param [Object] obj Object to test equality with
|
225
225
|
#
|
226
|
-
# @return [LiteralPattern]
|
226
|
+
# @return [LiteralPattern]
|
227
227
|
def Literal(obj)
|
228
228
|
LiteralPattern.new(obj)
|
229
229
|
end
|
230
|
-
|
230
|
+
|
231
231
|
def initialize
|
232
232
|
@patterns = Array.new
|
233
233
|
@deferred = Array.new
|
234
234
|
end
|
235
|
-
|
235
|
+
|
236
236
|
# Find a match for the given objects among the defined patterns.
|
237
237
|
#
|
238
238
|
# @param [Array<Object>] objects Objects to be matched
|
@@ -242,15 +242,15 @@ module Filigree
|
|
242
242
|
# @raise [MatchError] Raised if no pattern matches the objects
|
243
243
|
def find_match(objects)
|
244
244
|
@patterns.each do |pattern|
|
245
|
-
env = OpenStruct.new
|
246
|
-
|
245
|
+
env = OpenStruct.new
|
246
|
+
|
247
247
|
return pattern.(env, objects) if pattern.match?(objects, env)
|
248
248
|
end
|
249
|
-
|
249
|
+
|
250
250
|
# If we didn't find anything we raise a MatchError.
|
251
251
|
raise MatchError
|
252
252
|
end
|
253
|
-
|
253
|
+
|
254
254
|
# Define a pattern in this match call.
|
255
255
|
#
|
256
256
|
# @see match Documentation on pattern matching
|
@@ -260,26 +260,26 @@ module Filigree
|
|
260
260
|
#
|
261
261
|
# @return [void]
|
262
262
|
def with(*pattern, &block)
|
263
|
-
guard = if pattern.last.is_a?(Proc) then pattern.pop end
|
264
|
-
|
263
|
+
guard = if pattern.last.is_a?(Proc) then pattern.pop end
|
264
|
+
|
265
265
|
pattern = Filigree::wrap_pattern_elements(pattern)
|
266
|
-
|
266
|
+
|
267
267
|
@patterns << (mp = OuterPattern.new(pattern, guard, block))
|
268
|
-
|
268
|
+
|
269
269
|
if block
|
270
270
|
@deferred.each { |pattern| pattern.block = block }
|
271
271
|
@deferred.clear
|
272
|
-
|
272
|
+
|
273
273
|
else
|
274
274
|
@deferred << mp
|
275
275
|
end
|
276
276
|
end
|
277
277
|
alias :w :with
|
278
|
-
|
278
|
+
|
279
279
|
#############
|
280
280
|
# Callbacks #
|
281
281
|
#############
|
282
|
-
|
282
|
+
|
283
283
|
# Callback used to generate wildcard and binding patterns
|
284
284
|
def method_missing(name, *args)
|
285
285
|
if args.empty?
|
@@ -289,12 +289,12 @@ module Filigree
|
|
289
289
|
end
|
290
290
|
end
|
291
291
|
end
|
292
|
-
|
292
|
+
|
293
293
|
# This class provides the basis for all match patterns.
|
294
294
|
class BasicPattern
|
295
295
|
extend AbstractClass
|
296
296
|
include Comparable
|
297
|
-
|
297
|
+
|
298
298
|
# Base implementation of bi-directional comparison for patterns.
|
299
299
|
#
|
300
300
|
# @param [BasicPattern] other Right-hand side of the comparison
|
@@ -302,9 +302,11 @@ module Filigree
|
|
302
302
|
# @return [Integer] Value corresponding to less than, equal to, or
|
303
303
|
# greater than the right-hand side pattern.
|
304
304
|
def <=>(other)
|
305
|
-
|
305
|
+
# This is performed in the non-intuitive order due to
|
306
|
+
# higher-priority patterns having lower weights.
|
307
|
+
other.weight - self.weight
|
306
308
|
end
|
307
|
-
|
309
|
+
|
308
310
|
# Wraps this pattern in a {BindingPattern}, causing the object that
|
309
311
|
# this pattern matches to be bound to this name in the with block.
|
310
312
|
#
|
@@ -313,30 +315,30 @@ module Filigree
|
|
313
315
|
binding_pattern.tap { |bp| bp.pattern_elem = self }
|
314
316
|
end
|
315
317
|
end
|
316
|
-
|
318
|
+
|
317
319
|
# A pattern that matches any object
|
318
320
|
class WildcardPattern < BasicPattern
|
319
321
|
include Singleton
|
320
|
-
|
322
|
+
|
321
323
|
# Return true for any object and don't create any bindings.
|
322
324
|
#
|
323
325
|
# @return [true]
|
324
326
|
def match?(_, _)
|
325
327
|
true
|
326
328
|
end
|
327
|
-
|
329
|
+
|
328
330
|
def weight
|
329
331
|
4
|
330
332
|
end
|
331
333
|
end
|
332
|
-
|
334
|
+
|
333
335
|
# An abstract class that matches only a single object to a single pattern.
|
334
336
|
class SingleObjectPattern < BasicPattern
|
335
337
|
extend AbstractClass
|
336
|
-
|
338
|
+
|
337
339
|
# @return [BasicPattern]
|
338
340
|
attr_reader :pattern_elem
|
339
|
-
|
341
|
+
|
340
342
|
# Create a new pattern with a single element.
|
341
343
|
#
|
342
344
|
# @param [Object] pattern_elem Object representing the pattern
|
@@ -344,7 +346,7 @@ module Filigree
|
|
344
346
|
@pattern_elem = pattern_elem
|
345
347
|
end
|
346
348
|
end
|
347
|
-
|
349
|
+
|
348
350
|
# A pattern for checking to see if an object is an instance of a given
|
349
351
|
# class.
|
350
352
|
class InstancePattern < SingleObjectPattern
|
@@ -361,10 +363,10 @@ module Filigree
|
|
361
363
|
else -1
|
362
364
|
end
|
363
365
|
else
|
364
|
-
super
|
366
|
+
super(other)
|
365
367
|
end
|
366
368
|
end
|
367
|
-
|
369
|
+
|
368
370
|
# Test the object to see if the object is an instance of the given
|
369
371
|
# class.
|
370
372
|
#
|
@@ -374,12 +376,12 @@ module Filigree
|
|
374
376
|
def match?(object, _)
|
375
377
|
object.is_a?(@pattern_elem)
|
376
378
|
end
|
377
|
-
|
379
|
+
|
378
380
|
def weight
|
379
381
|
3
|
380
382
|
end
|
381
383
|
end
|
382
|
-
|
384
|
+
|
383
385
|
# A pattern that forces an equality comparison
|
384
386
|
class LiteralPattern < SingleObjectPattern
|
385
387
|
# Test the object for equality to the pattern element.
|
@@ -390,12 +392,12 @@ module Filigree
|
|
390
392
|
def match?(object, _)
|
391
393
|
object == @pattern_elem
|
392
394
|
end
|
393
|
-
|
395
|
+
|
394
396
|
def weight
|
395
397
|
0
|
396
398
|
end
|
397
399
|
end
|
398
|
-
|
400
|
+
|
399
401
|
# A pattern that tests a string against a regular expression.
|
400
402
|
class RegexpPattern < SingleObjectPattern
|
401
403
|
# Test the object to see if it matches the wrapped regular
|
@@ -410,18 +412,18 @@ module Filigree
|
|
410
412
|
env.send("match_data=", md) if match
|
411
413
|
end
|
412
414
|
end
|
413
|
-
|
415
|
+
|
414
416
|
def weight
|
415
417
|
2
|
416
418
|
end
|
417
419
|
end
|
418
|
-
|
420
|
+
|
419
421
|
# A pattern that binds a sub-pattern's matching object to a name in the
|
420
422
|
# binding environment.
|
421
423
|
class BindingPattern < SingleObjectPattern
|
422
|
-
|
424
|
+
|
423
425
|
attr_writer :pattern_elem
|
424
|
-
|
426
|
+
|
425
427
|
# Create a new binding pattern.
|
426
428
|
#
|
427
429
|
# @param [Symbol] name Name to bind to
|
@@ -430,12 +432,12 @@ module Filigree
|
|
430
432
|
@name = name
|
431
433
|
super(pattern_elem)
|
432
434
|
end
|
433
|
-
|
435
|
+
|
434
436
|
# Overridden method to prevent binding BindingPattern objects.
|
435
437
|
def as(_, _)
|
436
438
|
raise 'Binding a BindingPattern is not allowed.'
|
437
439
|
end
|
438
|
-
|
440
|
+
|
439
441
|
# Test the object for equality to the pattern element. Binds the
|
440
442
|
# object to the binding pattern's name if it does match.
|
441
443
|
#
|
@@ -446,26 +448,26 @@ module Filigree
|
|
446
448
|
def match?(object, env)
|
447
449
|
@pattern_elem.match?(object, env).tap { |match| env.send("#{@name}=", object) if match }
|
448
450
|
end
|
449
|
-
|
451
|
+
|
450
452
|
def weight
|
451
453
|
@pattern_elem.weight
|
452
454
|
end
|
453
455
|
end
|
454
|
-
|
456
|
+
|
455
457
|
# An abstract class that matches multiple objects to multiple patterns.
|
456
458
|
class MultipleObjectPattern < BasicPattern
|
457
459
|
extend AbstractClass
|
458
|
-
|
460
|
+
|
459
461
|
# @return [Array<BasicPattern>]
|
460
462
|
attr_reader :pattern
|
461
|
-
|
463
|
+
|
462
464
|
# Create a new pattern with multiple elements.
|
463
465
|
#
|
464
466
|
# @param [Array<Object>] pattern Array of pattern elements
|
465
467
|
def initialize(pattern)
|
466
468
|
@pattern = pattern
|
467
469
|
end
|
468
|
-
|
470
|
+
|
469
471
|
# A wrapper method to sort MultipleObjectPattern objects by their
|
470
472
|
# arity.
|
471
473
|
#
|
@@ -480,7 +482,7 @@ module Filigree
|
|
480
482
|
self.pattern.length - other.pattern.length
|
481
483
|
end
|
482
484
|
end
|
483
|
-
|
485
|
+
|
484
486
|
# Test multiple objects against multiple pattern elements.
|
485
487
|
#
|
486
488
|
# @param [Object] objects Object to test pattern against
|
@@ -491,20 +493,20 @@ module Filigree
|
|
491
493
|
@pattern.zip(objects).each do |pattern_elem, object|
|
492
494
|
return false unless pattern_elem.match?(object, env)
|
493
495
|
end
|
494
|
-
|
496
|
+
|
495
497
|
true
|
496
|
-
|
498
|
+
|
497
499
|
else
|
498
500
|
(@pattern.length == 1 and @pattern.first == WildcardPattern.instance)
|
499
501
|
end
|
500
502
|
end
|
501
503
|
end
|
502
|
-
|
504
|
+
|
503
505
|
# The class that contains all of the pattern elements passed to a with clause.
|
504
506
|
class OuterPattern < MultipleObjectPattern
|
505
507
|
attr_writer :block
|
506
508
|
attr_reader :guard
|
507
|
-
|
509
|
+
|
508
510
|
# Specialized version of the bi-directional comparison operator.
|
509
511
|
#
|
510
512
|
# @param [BasicPattern] other Right-hand side of the comparison
|
@@ -517,7 +519,7 @@ module Filigree
|
|
517
519
|
self.pattern.zip(other.pattern).inject(0) do |total, pair|
|
518
520
|
total + (pair.first <=> pair.last)
|
519
521
|
end <=> 0
|
520
|
-
|
522
|
+
|
521
523
|
if comp_res == 0
|
522
524
|
self.guard ? (other.guard ? 0 : 1) : (other.guard ? -1 : comp_res)
|
523
525
|
else
|
@@ -525,7 +527,7 @@ module Filigree
|
|
525
527
|
end
|
526
528
|
end
|
527
529
|
end
|
528
|
-
|
530
|
+
|
529
531
|
# Create a new outer pattern with the given pattern elements, guard,
|
530
532
|
# and block.
|
531
533
|
#
|
@@ -537,7 +539,7 @@ module Filigree
|
|
537
539
|
@guard = guard
|
538
540
|
@block = block
|
539
541
|
end
|
540
|
-
|
542
|
+
|
541
543
|
# Call the pattern's block, passing the given objects to the block.
|
542
544
|
#
|
543
545
|
# @param [Object] env Environment in which to evaluate the block
|
@@ -545,7 +547,7 @@ module Filigree
|
|
545
547
|
def call(env, objects = [])
|
546
548
|
if @block then env.instance_exec(*objects, &@block) else nil end
|
547
549
|
end
|
548
|
-
|
550
|
+
|
549
551
|
# Test the objects for equality to the pattern elements.
|
550
552
|
#
|
551
553
|
# @param [Object] objects Objects to test pattern elements against
|
@@ -556,14 +558,14 @@ module Filigree
|
|
556
558
|
super && (@guard.nil? or env.instance_exec(&@guard))
|
557
559
|
end
|
558
560
|
end
|
559
|
-
|
561
|
+
|
560
562
|
# A pattern that matches an instance of a class and destructures it so
|
561
563
|
# that the values contained by the object may be matched upon.
|
562
564
|
class DestructuringPattern < MultipleObjectPattern
|
563
|
-
|
565
|
+
|
564
566
|
# @return [Class]
|
565
567
|
attr_reader :klass
|
566
|
-
|
568
|
+
|
567
569
|
# Specialized version of the bi-directional comparison operator.
|
568
570
|
#
|
569
571
|
# @param [BasicPattern] other Right-hand side of the comparison
|
@@ -578,16 +580,16 @@ module Filigree
|
|
578
580
|
total + (pair.first <=> pair.last)
|
579
581
|
end / self.pattern.length
|
580
582
|
end
|
581
|
-
|
583
|
+
|
582
584
|
elsif self.klass.subclass_of?(other.klass) then 1
|
583
585
|
else -1
|
584
586
|
end
|
585
|
-
|
587
|
+
|
586
588
|
else
|
587
589
|
super
|
588
590
|
end
|
589
591
|
end
|
590
|
-
|
592
|
+
|
591
593
|
# Create a new destructuring pattern.
|
592
594
|
#
|
593
595
|
# @param [Class] klass Class to match instances of. It must be destructurable.
|
@@ -596,7 +598,7 @@ module Filigree
|
|
596
598
|
@klass = klass
|
597
599
|
super(pattern)
|
598
600
|
end
|
599
|
-
|
601
|
+
|
600
602
|
# Test to see if the object is an instance of the appropriate class,
|
601
603
|
# and if so destructure it and test it's values against the
|
602
604
|
# sub-pattern elements.
|
@@ -608,7 +610,7 @@ module Filigree
|
|
608
610
|
def match?(object, env)
|
609
611
|
object.is_a?(@klass) and super(object.destructure(@pattern.length), env)
|
610
612
|
end
|
611
|
-
|
613
|
+
|
612
614
|
def weight
|
613
615
|
1
|
614
616
|
end
|
@@ -621,7 +623,7 @@ end
|
|
621
623
|
|
622
624
|
class Array
|
623
625
|
extend Filigree::Destructurable
|
624
|
-
|
626
|
+
|
625
627
|
# Destructuring for the array class. If the array is being matched
|
626
628
|
# against two patterns the destructuring of the array will be the first
|
627
629
|
# element and then an array containing the rest of the values. If there
|
@@ -655,3 +657,12 @@ class Regexp
|
|
655
657
|
binding_pattern.tap { |bp| bp.pattern_elem = Filigree::RegexpPattern.new(self) }
|
656
658
|
end
|
657
659
|
end
|
660
|
+
|
661
|
+
class Symbol
|
662
|
+
# Turns a symbol into a binding pattern.
|
663
|
+
#
|
664
|
+
# @return [Filigree::BindingPattern]
|
665
|
+
def !
|
666
|
+
Filigree::BindingPattern.new(self)
|
667
|
+
end
|
668
|
+
end
|