filigree 0.3.3 → 0.4.0
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 +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
|