filigree 0.3.3 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +1 -5
- data/Rakefile +16 -12
- data/lib/filigree.rb +1 -1
- data/lib/filigree/abstract_class.rb +2 -2
- data/lib/filigree/application.rb +4 -9
- data/lib/filigree/boolean.rb +24 -21
- data/lib/filigree/class.rb +32 -29
- data/lib/filigree/class_methods_module.rb +4 -4
- data/lib/filigree/commands.rb +8 -6
- data/lib/filigree/configuration.rb +6 -8
- data/lib/filigree/match.rb +174 -163
- data/lib/filigree/object.rb +14 -11
- data/lib/filigree/request_file.rb +4 -4
- data/lib/filigree/string.rb +30 -28
- data/lib/filigree/types.rb +9 -10
- data/lib/filigree/version.rb +2 -2
- data/lib/filigree/visitor.rb +5 -5
- data/test/tc_abstract_class.rb +1 -1
- data/test/tc_application.rb +5 -5
- data/test/tc_boolean.rb +4 -1
- data/test/tc_class.rb +10 -6
- data/test/tc_class_methods_module.rb +1 -1
- data/test/tc_commands.rb +4 -4
- data/test/tc_configuration.rb +3 -3
- data/test/tc_match.rb +22 -22
- data/test/tc_object.rb +6 -7
- data/test/tc_string.rb +6 -4
- data/test/tc_types.rb +19 -19
- data/test/tc_visitor.rb +6 -4
- data/test/ts_filigree.rb +5 -5
- metadata +14 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7c7696d87311fb00c3506b41c1f8b50e944bb63b
|
4
|
+
data.tar.gz: eb00162a46ccfbb5742e56498233f44e36b5d760
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 783be7b5c305016fe61287d6c41c7f49f7e15b380cf199cfcf0633ef5e2b4cd073cae6608879655d65d134b69554bf3d9c24195fccc908d6b8dd4a14c1425e34
|
7
|
+
data.tar.gz: 98e5f988abc878f3f6da8e48faf8e29b547488a69f76bd3124d6541d0f321a87e7490c9a2f275068ca07dc91d35178cf090f91699ab4ab2ebcc5bc1f81f8b604
|
data/README.md
CHANGED
@@ -164,11 +164,7 @@ Filigree's implementation of the visitor pattern is built on the pattern matchin
|
|
164
164
|
```Ruby
|
165
165
|
class Binary < Struct.new(:x, :y)
|
166
166
|
extend Filigree::Destructurable
|
167
|
-
include Filigree::
|
168
|
-
|
169
|
-
def children
|
170
|
-
[x, y]
|
171
|
-
end
|
167
|
+
include Filigree::Visitor
|
172
168
|
|
173
169
|
def destructure(_)
|
174
170
|
[x, y]
|
data/Rakefile
CHANGED
@@ -1,15 +1,19 @@
|
|
1
|
-
# Author:
|
2
|
-
# Project:
|
3
|
-
# Date:
|
4
|
-
# Description:
|
1
|
+
# Author: Chris Wailes <chris.wailes@gmail.com>
|
2
|
+
# Project: Filigree
|
3
|
+
# Date: 2013/4/19
|
4
|
+
# Description: Filigree's Rakefile.
|
5
5
|
|
6
6
|
############
|
7
7
|
# Requires #
|
8
8
|
############
|
9
9
|
|
10
|
+
# Add the Filigree source directory to the load path.
|
11
|
+
lib_dir = File.expand_path("./lib/", File.dirname(__FILE__))
|
12
|
+
$LOAD_PATH << lib_dir unless $LOAD_PATH.include?(lib_dir)
|
13
|
+
|
10
14
|
# Filigree
|
11
|
-
require
|
12
|
-
require
|
15
|
+
require 'filigree/request_file'
|
16
|
+
require 'filigree/version'
|
13
17
|
|
14
18
|
###########
|
15
19
|
# Bundler #
|
@@ -36,12 +40,12 @@ end
|
|
36
40
|
# Flog #
|
37
41
|
########
|
38
42
|
|
39
|
-
request_file('
|
43
|
+
request_file('flog_task', 'Flog is not installed.') do
|
40
44
|
desc 'Analyze code complexity with Flog'
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
+
FlogTask.new do |t|
|
46
|
+
t.dirs = ['lib']
|
47
|
+
t.method = :max_method
|
48
|
+
t.verbose = true
|
45
49
|
end
|
46
50
|
end
|
47
51
|
|
@@ -94,7 +98,7 @@ request_file('yard', 'Yard is not installed.') do
|
|
94
98
|
'-M', 'redcarpet',
|
95
99
|
'--private'
|
96
100
|
]
|
97
|
-
|
101
|
+
|
98
102
|
t.files = Dir['lib/**/*.rb']
|
99
103
|
end
|
100
104
|
end
|
data/lib/filigree.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Author: Chris Wailes <chris.wailes@gmail.com>
|
1
|
+
# Author: Chris Wailes <chris.wailes+filigree@gmail.com>
|
2
2
|
# Project: Filigree
|
3
3
|
# Date: 2013/4/19
|
4
4
|
# Description: An implementation of an AbstractClass module.
|
@@ -69,7 +69,7 @@ module Filigree
|
|
69
69
|
#
|
70
70
|
# @raise [AbstractClassError]
|
71
71
|
def new(*args)
|
72
|
-
if @abstract_class == self
|
72
|
+
if self.instance_variable_defined?(:'@abstract_class') and @abstract_class == self
|
73
73
|
raise AbstractClassError, self.name
|
74
74
|
else
|
75
75
|
super
|
data/lib/filigree/application.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-
# Author:
|
2
|
-
# Project:
|
3
|
-
# Date:
|
4
|
-
# Description:
|
1
|
+
# Author: Chris Wailes <chris.wailes+filigree@gmail.com>
|
2
|
+
# Project: Filigree
|
3
|
+
# Date: 2013/05/14
|
4
|
+
# Description: Simple application framework.
|
5
5
|
|
6
6
|
############
|
7
7
|
# Requires #
|
@@ -36,8 +36,6 @@ module Filigree
|
|
36
36
|
#############
|
37
37
|
|
38
38
|
REQUIRED_METHODS = [
|
39
|
-
:kill,
|
40
|
-
:pause,
|
41
39
|
:resume,
|
42
40
|
:run,
|
43
41
|
:stop
|
@@ -59,10 +57,7 @@ module Filigree
|
|
59
57
|
Signal.trap('QUIT') { self.stop }
|
60
58
|
Signal.trap('TERM') { self.stop }
|
61
59
|
|
62
|
-
Signal.trap('KILL') { self.kill }
|
63
|
-
|
64
60
|
Signal.trap('CONT') { self.resume }
|
65
|
-
Signal.trap('STOP') { self.pause }
|
66
61
|
end
|
67
62
|
|
68
63
|
#################
|
data/lib/filigree/boolean.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-
# Author:
|
2
|
-
# Project:
|
3
|
-
# Date:
|
4
|
-
# Description:
|
1
|
+
# Author: Chris Wailes <chris.wailes+filigree@gmail.com>
|
2
|
+
# Project: Filigree
|
3
|
+
# Date: 2013/05/04
|
4
|
+
# Description: Class extensions for dealing with integers and booleans.
|
5
5
|
|
6
6
|
############
|
7
7
|
# Requires #
|
@@ -15,26 +15,29 @@
|
|
15
15
|
# Classes and Modules #
|
16
16
|
#######################
|
17
17
|
|
18
|
-
|
19
|
-
|
20
|
-
#
|
21
|
-
|
22
|
-
|
18
|
+
module Filigree
|
19
|
+
|
20
|
+
# Extra boolean support for the Integer class.
|
21
|
+
refine Integer do
|
22
|
+
# @return [Boolean] This Integer as a Boolean value.
|
23
|
+
def to_bool
|
24
|
+
self != 0
|
25
|
+
end
|
23
26
|
end
|
24
|
-
end
|
25
27
|
|
26
|
-
# Extra boolean support for the TrueClass class.
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
28
|
+
# Extra boolean support for the TrueClass class.
|
29
|
+
refine TrueClass do
|
30
|
+
# @return [1]
|
31
|
+
def to_i
|
32
|
+
1
|
33
|
+
end
|
31
34
|
end
|
32
|
-
end
|
33
35
|
|
34
|
-
# Extra boolean support for the FalseClass class.
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
36
|
+
# Extra boolean support for the FalseClass class.
|
37
|
+
refine FalseClass do
|
38
|
+
# @return [0]
|
39
|
+
def to_i
|
40
|
+
0
|
41
|
+
end
|
39
42
|
end
|
40
43
|
end
|
data/lib/filigree/class.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-
# Author:
|
2
|
-
# Project:
|
3
|
-
# Date:
|
4
|
-
# Description:
|
1
|
+
# Author: Chris Wailes <chris.wailes+filigree@gmail.com>
|
2
|
+
# Project: Filigree
|
3
|
+
# Date: 2013/05/04
|
4
|
+
# Description: Class extensions for the Class class.
|
5
5
|
|
6
6
|
############
|
7
7
|
# Requires #
|
@@ -16,33 +16,36 @@ require 'filigree/types'
|
|
16
16
|
# Classes and Modules #
|
17
17
|
#######################
|
18
18
|
|
19
|
-
|
20
|
-
# Checks for module inclusion.
|
21
|
-
#
|
22
|
-
# @param [Module] mod Module to check the inclusion of.
|
23
|
-
#
|
24
|
-
# @return [Boolean] If the module was included
|
25
|
-
def includes_module?(mod)
|
26
|
-
self.included_modules.include?(mod)
|
27
|
-
end
|
19
|
+
module Filigree
|
28
20
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
21
|
+
refine Class do
|
22
|
+
# Checks for module inclusion.
|
23
|
+
#
|
24
|
+
# @param [Module] mod Module to check the inclusion of.
|
25
|
+
#
|
26
|
+
# @return [Boolean] If the module was included
|
27
|
+
def includes_module?(mod)
|
28
|
+
self.included_modules.include?(mod)
|
29
|
+
end
|
30
|
+
|
31
|
+
# @return [String] Name of class without the namespace.
|
32
|
+
def short_name
|
33
|
+
self.name.split('::').last
|
34
|
+
end
|
33
35
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
36
|
+
# Checks to see if a Class object is a subclass of the given class.
|
37
|
+
#
|
38
|
+
# @param [Class] klass Class we are checking if this is a subclass of.
|
39
|
+
#
|
40
|
+
# @return [Boolean] If self is a subclass of klass
|
41
|
+
def subclass_of?(klass)
|
42
|
+
check_type(klass, Class, blame: 'klass')
|
43
|
+
|
44
|
+
if (superklass = self.superclass)
|
45
|
+
superklass == klass or superklass.subclass_of?(klass)
|
46
|
+
else
|
47
|
+
false
|
48
|
+
end
|
46
49
|
end
|
47
50
|
end
|
48
51
|
end
|
@@ -1,7 +1,7 @@
|
|
1
|
-
# Author:
|
2
|
-
# Project:
|
3
|
-
# Date:
|
4
|
-
# Description:
|
1
|
+
# Author: Chris Wailes <chris.wailes+filigree@gmail.com>
|
2
|
+
# Project: Filigree
|
3
|
+
# Date: 2013/05/15
|
4
|
+
# Description: A module to automatically extend classes with an inner module.
|
5
5
|
|
6
6
|
############
|
7
7
|
# Requires #
|
data/lib/filigree/commands.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-
# Author:
|
2
|
-
# Project:
|
3
|
-
# Date:
|
4
|
-
# Description:
|
1
|
+
# Author: Chris Wailes <chris.wailes+filigree@gmail.com>
|
2
|
+
# Project: Filigree
|
3
|
+
# Date: 2013/05/14
|
4
|
+
# Description: Easy application configuration.
|
5
5
|
|
6
6
|
############
|
7
7
|
# Requires #
|
@@ -12,12 +12,13 @@
|
|
12
12
|
# Filigree
|
13
13
|
require 'filigree/class_methods_module'
|
14
14
|
require 'filigree/configuration'
|
15
|
+
require 'filigree/string'
|
15
16
|
|
16
17
|
##########
|
17
18
|
# Errors #
|
18
19
|
##########
|
19
20
|
|
20
|
-
class CommandNotFoundError
|
21
|
+
class CommandNotFoundError < RuntimeError
|
21
22
|
def initialize(line)
|
22
23
|
super "No command found for '#{line}'"
|
23
24
|
end
|
@@ -117,7 +118,7 @@ module Filigree
|
|
117
118
|
# @return [void]
|
118
119
|
def config(&block)
|
119
120
|
@config = Class.new { include Filigree::Configuration }
|
120
|
-
@config.instance_exec
|
121
|
+
@config.instance_exec(&block)
|
121
122
|
end
|
122
123
|
|
123
124
|
# Attaches the provided help string to the command that is
|
@@ -217,6 +218,7 @@ module Filigree
|
|
217
218
|
# The default help command. This can be added to your class via
|
218
219
|
# add_command.
|
219
220
|
HELP_COMMAND = Command.new('help', 'Prints this help message.', [], nil, Proc.new do
|
221
|
+
|
220
222
|
puts 'Usage: <command> [options] <args>'
|
221
223
|
puts
|
222
224
|
puts 'Commands:'
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# Author: Chris Wailes <chris.wailes@gmail.com>
|
1
|
+
# Author: Chris Wailes <chris.wailes+filigree@gmail.com>
|
2
2
|
# Project: Filigree
|
3
3
|
# Date: 2013/05/14
|
4
4
|
# Description: Easy application configuration.
|
@@ -137,7 +137,7 @@ module Filigree
|
|
137
137
|
break if str == '--'
|
138
138
|
|
139
139
|
if option = find_option(str)
|
140
|
-
args = argv.shift(option.arity == -1 ? argv.index { |
|
140
|
+
args = argv.shift(option.arity == -1 ? argv.index { |sub_str| sub_str[0,1] == '-' } : option.arity)
|
141
141
|
|
142
142
|
case option.handler
|
143
143
|
when Array
|
@@ -165,7 +165,7 @@ module Filigree
|
|
165
165
|
def handle_serialized_options(overloaded, set_opts)
|
166
166
|
options =
|
167
167
|
if overloaded.is_a? String
|
168
|
-
if File.
|
168
|
+
if File.exist? overloaded
|
169
169
|
YAML.load_file overloaded
|
170
170
|
else
|
171
171
|
YAML.load overloaded
|
@@ -267,8 +267,6 @@ module Filigree
|
|
267
267
|
# @return [void]
|
268
268
|
def option(long, short = nil, conversions: nil, &block)
|
269
269
|
|
270
|
-
attr_accessor long.to_sym
|
271
|
-
|
272
270
|
long = long.to_s
|
273
271
|
short = short.to_s if short
|
274
272
|
|
@@ -352,7 +350,7 @@ module Filigree
|
|
352
350
|
# Print the option information out as a string.
|
353
351
|
#
|
354
352
|
# Layout:
|
355
|
-
# | ||--`long`,|| ||-`short`||
|
353
|
+
# | ||--`long`,|| ||-`short`|| ~ |
|
356
354
|
# |_______||_________||_||________||___|
|
357
355
|
# indent max_l+3 1 max_s+1 3
|
358
356
|
#
|
@@ -366,9 +364,9 @@ module Filigree
|
|
366
364
|
segmented_help = self.help.segment(segment_indent)
|
367
365
|
|
368
366
|
if self.short
|
369
|
-
sprintf "#{' ' * indent}%-#{max_long + 3}s %-#{max_short + 1}s
|
367
|
+
sprintf "#{' ' * indent}%-#{max_long + 3}s %-#{max_short + 1}s ~ %s", "--#{self.long},", '-' + self.short, segmented_help
|
370
368
|
else
|
371
|
-
sprintf "#{' ' * indent}%-#{max_long + max_short + 5}s
|
369
|
+
sprintf "#{' ' * indent}%-#{max_long + max_short + 5}s ~ %s", '--' + self.long, segmented_help
|
372
370
|
end
|
373
371
|
end
|
374
372
|
|
data/lib/filigree/match.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Author: Chris Wailes <chris.wailes@gmail.com>
|
1
|
+
# Author: Chris Wailes <chris.wailes+filigree@gmail.com>
|
2
2
|
# Project: Filigree
|
3
3
|
# Date: 2013/05/04
|
4
4
|
# Description: Pattern matching for Ruby.
|
@@ -22,152 +22,158 @@ require 'filigree/class'
|
|
22
22
|
# An error that indicates that no pattern matched a given object.
|
23
23
|
class MatchError < RuntimeError; end
|
24
24
|
|
25
|
+
###########
|
26
|
+
# Methods #
|
27
|
+
###########
|
28
|
+
|
29
|
+
# This is an implementation of pattern matching. The objects passed to match
|
30
|
+
# are tested against the patterns defined inside the match block. The return
|
31
|
+
# value of `match` will be the result of evaluating the block given to `with`.
|
32
|
+
|
33
|
+
# The most basic pattern is the literal. Here, the object or objects being
|
34
|
+
# matched will be tested for equality with value passed to `with`. In the
|
35
|
+
# example below, the call to `match` will return `:one`. Similar to the
|
36
|
+
# literal pattern is the wildcard pattern `_`. This will match any object.
|
37
|
+
|
38
|
+
# You may also match against variables. This can sometimes conflict with the
|
39
|
+
# next kind of pattern, which is a binding pattern. Here, the pattern will
|
40
|
+
# match any object, and then make the object it matched available to the with
|
41
|
+
# block via an attribute reader. This is accomplished using the method_missing
|
42
|
+
# callback, so if there is a variable or function with that name you might
|
43
|
+
# accidentally compare against a variable. To bind to a name that is already
|
44
|
+
# in scope you can use the {Filigree::MatchEnvironment#Bind} method. In
|
45
|
+
# addition, class and destructuring pattern results (see bellow) can be bound
|
46
|
+
# to a variable by using the {Filigree::BasicPattern#as} method.
|
47
|
+
|
48
|
+
# If you wish to match string patterns you may use regular expressions. Any
|
49
|
+
# object that isn't a string will fail to match against a regular expression.
|
50
|
+
# If the object being matched is a string then the regular expressions `match?`
|
51
|
+
# method is used. The result of the regular expression match is available
|
52
|
+
# inside the with block via the match_data accessor.
|
53
|
+
|
54
|
+
# When a class is used in a pattern it will match any object that is an
|
55
|
+
# instance of that class. If you wish to compare one regular expression to
|
56
|
+
# another, or one class to another, you can force the comparison using the
|
57
|
+
# {Filigree::MatchEnvironment#Literal} method.
|
58
|
+
#
|
59
|
+
# Destructuring patterns allow you to match against an instance of a class,
|
60
|
+
# while simultaneously binding values stored inside the object to variables
|
61
|
+
# in the context of the with block. A class that is destructurable must
|
62
|
+
# include the {Filigree::Destructurable} module. You can then destructure an
|
63
|
+
# object as shown bellow.
|
64
|
+
|
65
|
+
# Both `match` and `with` can take multiple arguments. When this happens, each
|
66
|
+
# object is paired up with the corresponding pattern. If they all match, then
|
67
|
+
# the `with` clause matches. In this way you can match against tuples.
|
68
|
+
|
69
|
+
# Any with clause can be given a guard clause by passing a lambda as the last
|
70
|
+
# argument to `with`. These are evaluated after the pattern is matched, and
|
71
|
+
# any bindings made in the pattern are available to the guard clause.
|
72
|
+
|
73
|
+
# If you wish to evaluate the same body on matching any of several patterns you
|
74
|
+
# may list them in order and then specify the body for the last pattern in the
|
75
|
+
# group.
|
76
|
+
|
77
|
+
# Patterns are evaluated in the order in which they are defined and the first
|
78
|
+
# pattern to match is the one chosen. You may define helper methods inside the
|
79
|
+
# match block. They will be re-defined every time the match statement is
|
80
|
+
# evaluated, so you should move any definitions outside any match calls that
|
81
|
+
# are being evaluated often.
|
82
|
+
|
83
|
+
# @example The literal pattern
|
84
|
+
# def foo(n)
|
85
|
+
# match 1 do
|
86
|
+
# with(1) { :one }
|
87
|
+
# with(2) { :two }
|
88
|
+
# with(_) { :other }
|
89
|
+
# end
|
90
|
+
# end
|
91
|
+
#
|
92
|
+
# foo(1)
|
93
|
+
|
94
|
+
# @example Matching against variables
|
95
|
+
# var = 42
|
96
|
+
# match 42 do
|
97
|
+
# with(var) { :hoopy }
|
98
|
+
# with(0) { :zero }
|
99
|
+
# end
|
100
|
+
|
101
|
+
# @example Binding patterns
|
102
|
+
# # Returns 42
|
103
|
+
# match 42 do
|
104
|
+
# with(x) { x }
|
105
|
+
# end
|
106
|
+
|
107
|
+
# x = 3
|
108
|
+
# # Returns 42
|
109
|
+
# match 42 do
|
110
|
+
# with(Bind(:x)) { x }
|
111
|
+
# with(42) { :hoopy }
|
112
|
+
# end
|
113
|
+
|
114
|
+
# @example Regular expression and class instance pattern
|
115
|
+
# def matcher(object)
|
116
|
+
# match object do
|
117
|
+
# with(/hoopy/) { 42 }
|
118
|
+
# with(Integer) { 'hoopy' }
|
119
|
+
# end
|
120
|
+
# end
|
121
|
+
|
122
|
+
# # Returns 42
|
123
|
+
# matcher('hoopy')
|
124
|
+
# # Returns 'hoopy'
|
125
|
+
# matcher(42)
|
126
|
+
|
127
|
+
# @example Destructuring an object
|
128
|
+
# class Foo
|
129
|
+
# include Filigree::Destructurable
|
130
|
+
# def initialize(a, b)
|
131
|
+
# @a = a
|
132
|
+
# @b = b
|
133
|
+
# end
|
134
|
+
#
|
135
|
+
# def destructure(_)
|
136
|
+
# [@a, @b]
|
137
|
+
# end
|
138
|
+
# end
|
139
|
+
|
140
|
+
# # Returns true
|
141
|
+
# match Foo.new(4, 2) do
|
142
|
+
# with(Foo.(4, 2)) { true }
|
143
|
+
# with(_) { false }
|
144
|
+
# end
|
145
|
+
|
146
|
+
# @example Using guard clauses
|
147
|
+
# match o do
|
148
|
+
# with(n, -> { n < 0 }) { :NEG }
|
149
|
+
# with(0) { :ZERO }
|
150
|
+
# with(n, -> { n > 0 }) { :POS }
|
151
|
+
# end
|
152
|
+
#
|
153
|
+
# @param [Object] objects Objects to be matched
|
154
|
+
# @param [Proc] block Block containing with clauses.
|
155
|
+
#
|
156
|
+
# @return [Object] Result of evaluating the matched pattern's block
|
157
|
+
def match(*objects, &block)
|
158
|
+
me = Filigree::MatchEnvironment.new
|
159
|
+
|
160
|
+
me.instance_exec(&block)
|
161
|
+
|
162
|
+
me.find_match(objects)
|
163
|
+
end
|
164
|
+
|
25
165
|
#######################
|
26
166
|
# Classes and Modules #
|
27
167
|
#######################
|
28
168
|
|
29
169
|
module Filigree
|
30
170
|
|
171
|
+
using Filigree
|
172
|
+
|
31
173
|
###########
|
32
174
|
# Methods #
|
33
175
|
###########
|
34
176
|
|
35
|
-
# This is an implementation of pattern matching. The objects passed to match
|
36
|
-
# are tested against the patterns defined inside the match block. The return
|
37
|
-
# value of `match` will be the result of evaluating the block given to `with`.
|
38
|
-
|
39
|
-
# The most basic pattern is the literal. Here, the object or objects being
|
40
|
-
# matched will be tested for equality with value passed to `with`. In the
|
41
|
-
# example below, the call to `match` will return `:one`. Similar to the
|
42
|
-
# literal pattern is the wildcard pattern `_`. This will match any object.
|
43
|
-
|
44
|
-
# You may also match against variables. This can sometimes conflict with the
|
45
|
-
# next kind of pattern, which is a binding pattern. Here, the pattern will
|
46
|
-
# match any object, and then make the object it matched available to the with
|
47
|
-
# block via an attribute reader. This is accomplished using the method_missing
|
48
|
-
# callback, so if there is a variable or function with that name you might
|
49
|
-
# accidentally compare against a variable. To bind to a name that is already
|
50
|
-
# in scope you can use the {Filigree::MatchEnvironment#Bind} method. In
|
51
|
-
# addition, class and destructuring pattern results (see bellow) can be bound
|
52
|
-
# to a variable by using the {Filigree::BasicPattern#as} method.
|
53
|
-
|
54
|
-
# If you wish to match string patterns you may use regular expressions. Any
|
55
|
-
# object that isn't a string will fail to match against a regular expression.
|
56
|
-
# If the object being matched is a string then the regular expressions `match?`
|
57
|
-
# method is used. The result of the regular expression match is available
|
58
|
-
# inside the with block via the match_data accessor.
|
59
|
-
|
60
|
-
# When a class is used in a pattern it will match any object that is an
|
61
|
-
# instance of that class. If you wish to compare one regular expression to
|
62
|
-
# another, or one class to another, you can force the comparison using the
|
63
|
-
# {Filigree::MatchEnvironment#Literal} method.
|
64
|
-
#
|
65
|
-
# Destructuring patterns allow you to match against an instance of a class,
|
66
|
-
# while simultaneously binding values stored inside the object to variables
|
67
|
-
# in the context of the with block. A class that is destructurable must
|
68
|
-
# include the {Filigree::Destructurable} module. You can then destructure an
|
69
|
-
# object as shown bellow.
|
70
|
-
|
71
|
-
# Both `match` and `with` can take multiple arguments. When this happens, each
|
72
|
-
# object is paired up with the corresponding pattern. If they all match, then
|
73
|
-
# the `with` clause matches. In this way you can match against tuples.
|
74
|
-
|
75
|
-
# Any with clause can be given a guard clause by passing a lambda as the last
|
76
|
-
# argument to `with`. These are evaluated after the pattern is matched, and
|
77
|
-
# any bindings made in the pattern are available to the guard clause.
|
78
|
-
|
79
|
-
# If you wish to evaluate the same body on matching any of several patterns you
|
80
|
-
# may list them in order and then specify the body for the last pattern in the
|
81
|
-
# group.
|
82
|
-
|
83
|
-
# Patterns are evaluated in the order in which they are defined and the first
|
84
|
-
# pattern to match is the one chosen. You may define helper methods inside the
|
85
|
-
# match block. They will be re-defined every time the match statement is
|
86
|
-
# evaluated, so you should move any definitions outside any match calls that
|
87
|
-
# are being evaluated often.
|
88
|
-
|
89
|
-
# @example The literal pattern
|
90
|
-
# def foo(n)
|
91
|
-
# match 1 do
|
92
|
-
# with(1) { :one }
|
93
|
-
# with(2) { :two }
|
94
|
-
# with(_) { :other }
|
95
|
-
# end
|
96
|
-
# end
|
97
|
-
#
|
98
|
-
# foo(1)
|
99
|
-
|
100
|
-
# @example Matching against variables
|
101
|
-
# var = 42
|
102
|
-
# match 42 do
|
103
|
-
# with(var) { :hoopy }
|
104
|
-
# with(0) { :zero }
|
105
|
-
# end
|
106
|
-
|
107
|
-
# @example Binding patterns
|
108
|
-
# # Returns 42
|
109
|
-
# match 42 do
|
110
|
-
# with(x) { x }
|
111
|
-
# end
|
112
|
-
|
113
|
-
# x = 3
|
114
|
-
# # Returns 42
|
115
|
-
# match 42 do
|
116
|
-
# with(Bind(:x)) { x }
|
117
|
-
# with(42) { :hoopy }
|
118
|
-
# end
|
119
|
-
|
120
|
-
# @example Regular expression and class instance pattern
|
121
|
-
# def matcher(object)
|
122
|
-
# match object do
|
123
|
-
# with(/hoopy/) { 42 }
|
124
|
-
# with(Integer) { 'hoopy' }
|
125
|
-
# end
|
126
|
-
# end
|
127
|
-
|
128
|
-
# # Returns 42
|
129
|
-
# matcher('hoopy')
|
130
|
-
# # Returns 'hoopy'
|
131
|
-
# matcher(42)
|
132
|
-
|
133
|
-
# @example Destructuring an object
|
134
|
-
# class Foo
|
135
|
-
# include Filigree::Destructurable
|
136
|
-
# def initialize(a, b)
|
137
|
-
# @a = a
|
138
|
-
# @b = b
|
139
|
-
# end
|
140
|
-
#
|
141
|
-
# def destructure(_)
|
142
|
-
# [@a, @b]
|
143
|
-
# end
|
144
|
-
# end
|
145
|
-
|
146
|
-
# # Returns true
|
147
|
-
# match Foo.new(4, 2) do
|
148
|
-
# with(Foo.(4, 2)) { true }
|
149
|
-
# with(_) { false }
|
150
|
-
# end
|
151
|
-
|
152
|
-
# @example Using guard clauses
|
153
|
-
# match o do
|
154
|
-
# with(n, -> { n < 0 }) { :NEG }
|
155
|
-
# with(0) { :ZERO }
|
156
|
-
# with(n, -> { n > 0 }) { :POS }
|
157
|
-
# end
|
158
|
-
#
|
159
|
-
# @param [Object] objects Objects to be matched
|
160
|
-
# @param [Proc] block Block containing with clauses.
|
161
|
-
#
|
162
|
-
# @return [Object] Result of evaluating the matched pattern's block
|
163
|
-
def match(*objects, &block)
|
164
|
-
me = Filigree::MatchEnvironment.new
|
165
|
-
|
166
|
-
me.instance_exec &block
|
167
|
-
|
168
|
-
me.find_match(objects)
|
169
|
-
end
|
170
|
-
|
171
177
|
# Wrap non-pattern objects in pattern objects so they can all be treated
|
172
178
|
# in the same way during pattern sorting and matching.
|
173
179
|
#
|
@@ -263,7 +269,7 @@ module Filigree
|
|
263
269
|
@patterns << (mp = OuterPattern.new(pattern, guard, block))
|
264
270
|
|
265
271
|
if block
|
266
|
-
@deferred.each { |
|
272
|
+
@deferred.each { |deferred_pattern| deferred_pattern.block = block }
|
267
273
|
@deferred.clear
|
268
274
|
|
269
275
|
else
|
@@ -613,10 +619,11 @@ module Filigree
|
|
613
619
|
end
|
614
620
|
end
|
615
621
|
|
616
|
-
|
617
|
-
# Standard Library
|
618
|
-
|
622
|
+
################################
|
623
|
+
# Standard Library Refinements #
|
624
|
+
################################
|
619
625
|
|
626
|
+
# TODO: Figure out how to put this into a refinement.
|
620
627
|
class Array
|
621
628
|
extend Filigree::Destructurable
|
622
629
|
|
@@ -627,7 +634,7 @@ class Array
|
|
627
634
|
# second elements, and then an array containing the remainder of the
|
628
635
|
# values.
|
629
636
|
#
|
630
|
-
# @param [
|
637
|
+
# @param [Integer] num_elems Number of sub-pattern elements
|
631
638
|
#
|
632
639
|
# @return [Array<Object>]
|
633
640
|
def destructure(num_elems)
|
@@ -635,30 +642,34 @@ class Array
|
|
635
642
|
end
|
636
643
|
end
|
637
644
|
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
binding_pattern
|
645
|
+
module Filigree
|
646
|
+
|
647
|
+
refine Class do
|
648
|
+
# Causes an instance of a class to be bound the the given name.
|
649
|
+
#
|
650
|
+
# @param [BindingPattern] binding_pattern Name to bind the instance to
|
651
|
+
def as(binding_pattern)
|
652
|
+
binding_pattern.tap { |bp| bp.pattern_elem = Filigree::InstancePattern.new(self) }
|
653
|
+
end
|
644
654
|
end
|
645
|
-
end
|
646
655
|
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
656
|
+
refine Regexp do
|
657
|
+
# Causes a string matching the regular expression to be bound the the
|
658
|
+
# given name.
|
659
|
+
#
|
660
|
+
# @param [BindingPattern] binding_pattern Name to bind the instance to
|
661
|
+
def as(binding_pattern)
|
662
|
+
binding_pattern.tap { |bp| bp.pattern_elem = Filigree::RegexpPattern.new(self) }
|
663
|
+
end
|
654
664
|
end
|
655
|
-
end
|
656
665
|
|
657
|
-
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
666
|
+
refine Symbol do
|
667
|
+
# Turns a symbol into a binding pattern.
|
668
|
+
#
|
669
|
+
# @return [Filigree::BindingPattern]
|
670
|
+
def ~
|
671
|
+
Filigree::BindingPattern.new(self)
|
672
|
+
end
|
663
673
|
end
|
674
|
+
|
664
675
|
end
|