filigree 0.1.2
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 +7 -0
- data/AUTHORS +1 -0
- data/LICENSE +12 -0
- data/README.md +336 -0
- data/Rakefile +101 -0
- data/lib/filigree/abstract_class.rb +90 -0
- data/lib/filigree/application.rb +107 -0
- data/lib/filigree/array.rb +35 -0
- data/lib/filigree/boolean.rb +40 -0
- data/lib/filigree/class.rb +48 -0
- data/lib/filigree/class_methods_module.rb +40 -0
- data/lib/filigree/commands.rb +261 -0
- data/lib/filigree/configuration.rb +411 -0
- data/lib/filigree/match.rb +499 -0
- data/lib/filigree/object.rb +40 -0
- data/lib/filigree/request_file.rb +33 -0
- data/lib/filigree/string.rb +52 -0
- data/lib/filigree/types.rb +159 -0
- data/lib/filigree/version.rb +8 -0
- data/lib/filigree/visitor.rb +195 -0
- data/lib/filigree.rb +27 -0
- data/test/tc_abstract_class.rb +74 -0
- data/test/tc_application.rb +53 -0
- data/test/tc_array.rb +28 -0
- data/test/tc_boolean.rb +38 -0
- data/test/tc_class.rb +45 -0
- data/test/tc_class_methods_module.rb +71 -0
- data/test/tc_commands.rb +78 -0
- data/test/tc_configuration.rb +173 -0
- data/test/tc_match.rb +307 -0
- data/test/tc_object.rb +43 -0
- data/test/tc_string.rb +36 -0
- data/test/tc_types.rb +116 -0
- data/test/tc_visitor.rb +236 -0
- data/test/ts_filigree.rb +33 -0
- metadata +247 -0
@@ -0,0 +1,499 @@
|
|
1
|
+
# Author: Chris Wailes <chris.wailes@gmail.com>
|
2
|
+
# Project: Filigree
|
3
|
+
# Date: 2013/05/04
|
4
|
+
# Description: Pattern matching for Ruby.
|
5
|
+
|
6
|
+
############
|
7
|
+
# Requires #
|
8
|
+
############
|
9
|
+
|
10
|
+
# Standard Library
|
11
|
+
require 'ostruct'
|
12
|
+
require 'singleton'
|
13
|
+
|
14
|
+
# Filigree
|
15
|
+
require 'filigree/abstract_class'
|
16
|
+
|
17
|
+
##########
|
18
|
+
# Errors #
|
19
|
+
##########
|
20
|
+
|
21
|
+
# An error that indicates that no pattern matched a given object.
|
22
|
+
class MatchError < RuntimeError; end
|
23
|
+
|
24
|
+
###########
|
25
|
+
# Methods #
|
26
|
+
###########
|
27
|
+
|
28
|
+
# This is an implementation of pattern matching. The objects passed to match
|
29
|
+
# are tested against the patterns defined inside the match block. The return
|
30
|
+
# value of `match` will be the result of evaluating the block given to `with`.
|
31
|
+
|
32
|
+
# The most basic pattern is the literal. Here, the object or objects being
|
33
|
+
# matched will be tested for equality with value passed to `with`. In the
|
34
|
+
# example below, the call to `match` will return `:one`. Similar to the
|
35
|
+
# literal pattern is the wildcard pattern `_`. This will match any object.
|
36
|
+
|
37
|
+
# You may also match against variables. This can sometimes conflict with the
|
38
|
+
# next kind of pattern, which is a binding pattern. Here, the pattern will
|
39
|
+
# match any object, and then make the object it matched available to the with
|
40
|
+
# block via an attribute reader. This is accomplished using the method_missing
|
41
|
+
# callback, so if there is a variable or function with that name you might
|
42
|
+
# accidentally compare against a variable. To bind to a name that is already
|
43
|
+
# in scope you can use the {Filigree::MatchEnvironment#Bind} method. In
|
44
|
+
# addition, class and destructuring pattern results (see bellow) can be bound
|
45
|
+
# to a variable by using the {Filigree::BasicPattern#as} method.
|
46
|
+
|
47
|
+
# If you wish to match string patterns you may use regular expressions. Any
|
48
|
+
# object that isn't a string will fail to match against a regular expression.
|
49
|
+
# If the object being matched is a string then the regular expressions `match?`
|
50
|
+
# method is used. The result of the regular expression match is available
|
51
|
+
# inside the with block via the match_data accessor.
|
52
|
+
|
53
|
+
# When a class is used in a pattern it will match any object that is an
|
54
|
+
# instance of that class. If you wish to compare one regular expression to
|
55
|
+
# another, or one class to another, you can force the comparison using the
|
56
|
+
# {Filigree::MatchEnvironment#Literal} method.
|
57
|
+
#
|
58
|
+
# Destructuring patterns allow you to match against an instance of a class,
|
59
|
+
# while simultaneously binding values stored inside the object to variables
|
60
|
+
# in the context of the with block. A class that is destructurable must
|
61
|
+
# include the {Filigree::Destructurable} module. You can then destructure an
|
62
|
+
# object as shown bellow.
|
63
|
+
|
64
|
+
# Both `match` and `with` can take multiple arguments. When this happens, each
|
65
|
+
# object is paired up with the corresponding pattern. If they all match, then
|
66
|
+
# the `with` clause matches. In this way you can match against tuples.
|
67
|
+
|
68
|
+
# Any with clause can be given a guard clause by passing a lambda as the last
|
69
|
+
# argument to `with`. These are evaluated after the pattern is matched, and
|
70
|
+
# any bindings made in the pattern are available to the guard clause.
|
71
|
+
|
72
|
+
# If you wish to evaluate the same body on matching any of several patterns you
|
73
|
+
# may list them in order and then specify the body for the last pattern in the
|
74
|
+
# group.
|
75
|
+
|
76
|
+
# Patterns are evaluated in the order in which they are defined and the first
|
77
|
+
# pattern to match is the one chosen. You may define helper methods inside the
|
78
|
+
# match block. They will be re-defined every time the match statement is
|
79
|
+
# evaluated, so you should move any definitions outside any match calls that
|
80
|
+
# are being evaluated often.
|
81
|
+
|
82
|
+
# @example The literal pattern
|
83
|
+
# def foo(n)
|
84
|
+
# match 1 do
|
85
|
+
# with(1) { :one }
|
86
|
+
# with(2) { :two }
|
87
|
+
# with(_) { :other }
|
88
|
+
# end
|
89
|
+
# end
|
90
|
+
#
|
91
|
+
# foo(1)
|
92
|
+
|
93
|
+
# @example Matching against variables
|
94
|
+
# var = 42
|
95
|
+
# match 42 do
|
96
|
+
# with(var) { :hoopy }
|
97
|
+
# with(0) { :zero }
|
98
|
+
# end
|
99
|
+
|
100
|
+
# @example Binding patterns
|
101
|
+
# # Returns 42
|
102
|
+
# match 42 do
|
103
|
+
# with(x) { x }
|
104
|
+
# end
|
105
|
+
|
106
|
+
# x = 3
|
107
|
+
# # Returns 42
|
108
|
+
# match 42 do
|
109
|
+
# with(Bind(:x)) { x }
|
110
|
+
# with(42) { :hoopy }
|
111
|
+
# end
|
112
|
+
|
113
|
+
# @example Regular expression and class instance pattern
|
114
|
+
# def matcher(object)
|
115
|
+
# match object do
|
116
|
+
# with(/hoopy/) { 42 }
|
117
|
+
# with(Integer) { 'hoopy' }
|
118
|
+
# end
|
119
|
+
# end
|
120
|
+
|
121
|
+
# # Returns 42
|
122
|
+
# matcher('hoopy')
|
123
|
+
# # Returns 'hoopy'
|
124
|
+
# matcher(42)
|
125
|
+
|
126
|
+
# @example Destructuring an object
|
127
|
+
# class Foo
|
128
|
+
# include Filigree::Destructurable
|
129
|
+
# def initialize(a, b)
|
130
|
+
# @a = a
|
131
|
+
# @b = b
|
132
|
+
# end
|
133
|
+
#
|
134
|
+
# def destructure(_)
|
135
|
+
# [@a, @b]
|
136
|
+
# end
|
137
|
+
# end
|
138
|
+
|
139
|
+
# # Returns true
|
140
|
+
# match Foo.new(4, 2) do
|
141
|
+
# with(Foo.(4, 2)) { true }
|
142
|
+
# with(_) { false }
|
143
|
+
# end
|
144
|
+
|
145
|
+
# @example Using guard clauses
|
146
|
+
# match o do
|
147
|
+
# with(n, -> { n < 0 }) { :NEG }
|
148
|
+
# with(0) { :ZERO }
|
149
|
+
# with(n, -> { n > 0 }) { :POS }
|
150
|
+
# end
|
151
|
+
#
|
152
|
+
# @param [Object] objects Objects to be matched
|
153
|
+
# @param [Proc] block Block containing with clauses.
|
154
|
+
#
|
155
|
+
# @return [Object] Result of evaluating the matched pattern's block
|
156
|
+
def match(*objects, &block)
|
157
|
+
me = Filigree::MatchEnvironment.new
|
158
|
+
|
159
|
+
me.instance_exec &block
|
160
|
+
|
161
|
+
me.find_match(objects)
|
162
|
+
end
|
163
|
+
|
164
|
+
#######################
|
165
|
+
# Classes and Modules #
|
166
|
+
#######################
|
167
|
+
|
168
|
+
module Filigree
|
169
|
+
|
170
|
+
# A module indicating that an object may be destructured. The including
|
171
|
+
# class must define the `destructure` instance method, which takes one
|
172
|
+
# argument specifying the number of pattern elements it is being matched
|
173
|
+
# against.
|
174
|
+
module Destructurable
|
175
|
+
# The instance method that generates a destructuring pattern.
|
176
|
+
#
|
177
|
+
# @param [Object] pattern Sub-patterns used to match the destructured elements of the object.
|
178
|
+
#
|
179
|
+
# @return [DestructuringPattern]
|
180
|
+
def call(*pattern)
|
181
|
+
DestructuringPattern.new(self, *pattern)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
# Match blocks are evaluated inside an instance of MatchEnvironment.
|
186
|
+
class MatchEnvironment
|
187
|
+
# Force binding to the given name
|
188
|
+
#
|
189
|
+
# @param [Symbol] name Name to bind the value to
|
190
|
+
#
|
191
|
+
# @return [BindingPattern]
|
192
|
+
def Bind(name)
|
193
|
+
BindingPattern.new(name)
|
194
|
+
end
|
195
|
+
|
196
|
+
# Force a literal comparison
|
197
|
+
#
|
198
|
+
# @param [Object] obj Object to test equality with
|
199
|
+
#
|
200
|
+
# @return [LiteralPattern]
|
201
|
+
def Literal(obj)
|
202
|
+
LiteralPattern.new(obj)
|
203
|
+
end
|
204
|
+
|
205
|
+
def initialize
|
206
|
+
@patterns = Array.new
|
207
|
+
@deferred = Array.new
|
208
|
+
end
|
209
|
+
|
210
|
+
# Find a match for the given objects among the defined patterns.
|
211
|
+
#
|
212
|
+
# @param [Array<Object>] objects Objects to be matched
|
213
|
+
#
|
214
|
+
# @return [Object] Result of evaluating the matching pattern's block
|
215
|
+
#
|
216
|
+
# @raise [MatchError] Raised if no pattern matches the objects
|
217
|
+
def find_match(objects)
|
218
|
+
@patterns.each do |pattern|
|
219
|
+
env = OpenStruct.new
|
220
|
+
|
221
|
+
return pattern.(env, objects) if pattern.match?(objects, env)
|
222
|
+
end
|
223
|
+
|
224
|
+
# If we didn't find anything we raise a MatchError.
|
225
|
+
raise MatchError
|
226
|
+
end
|
227
|
+
|
228
|
+
# Define a pattern in this match call.
|
229
|
+
#
|
230
|
+
# @see match Documentation on pattern matching
|
231
|
+
#
|
232
|
+
# @param [Object] pattern Objects defining the pattern
|
233
|
+
# @param [Proc] block Block to be executed if the pattern matches
|
234
|
+
#
|
235
|
+
# @return [void]
|
236
|
+
def with(*pattern, &block)
|
237
|
+
guard = if pattern.last.is_a?(Proc) then pattern.pop end
|
238
|
+
|
239
|
+
@patterns << (mp = OuterPattern.new(pattern, guard, block))
|
240
|
+
|
241
|
+
if block
|
242
|
+
@deferred.each { |pattern| pattern.block = block }
|
243
|
+
@deferred.clear
|
244
|
+
|
245
|
+
else
|
246
|
+
@deferred << mp
|
247
|
+
end
|
248
|
+
end
|
249
|
+
alias :w :with
|
250
|
+
|
251
|
+
#############
|
252
|
+
# Callbacks #
|
253
|
+
#############
|
254
|
+
|
255
|
+
# Callback used to generate wildcard and binding patterns
|
256
|
+
def method_missing(name, *args)
|
257
|
+
if args.empty?
|
258
|
+
if name == :_ then WildcardPattern.instance else BindingPattern.new(name) end
|
259
|
+
else
|
260
|
+
super(name, *args)
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
# This class provides the basis for all match patterns.
|
266
|
+
class BasicPattern
|
267
|
+
extend AbstractClass
|
268
|
+
|
269
|
+
# Wraps this pattern in a {BindingPattern}, causing the object that
|
270
|
+
# this pattern matches to be bound to this name in the with block.
|
271
|
+
#
|
272
|
+
# @param [BindingPattern] binding_pattern Binding pattern containing the name
|
273
|
+
def as(binding_pattern)
|
274
|
+
binding_pattern.tap { |bp| bp.pattern_elem = self }
|
275
|
+
end
|
276
|
+
|
277
|
+
# Test to see if a single object matches a single pattern, using the
|
278
|
+
# given environment to store bindings.
|
279
|
+
#
|
280
|
+
# @param [Object] pattern_elem Object representing the pattern
|
281
|
+
# @param [Object] object Object to be matched
|
282
|
+
# @param [Object] env Environment in which to store bindings
|
283
|
+
#
|
284
|
+
# @return [Boolean] If the pattern element matched
|
285
|
+
def match_pattern_element(pattern_elem, object, env)
|
286
|
+
case pattern_elem
|
287
|
+
when Class
|
288
|
+
object.is_a?(pattern_elem)
|
289
|
+
|
290
|
+
when Regexp
|
291
|
+
(object.is_a?(String) and (md = pattern_elem.match(object))).tap do |match|
|
292
|
+
env.send("match_data=", md) if match
|
293
|
+
end
|
294
|
+
|
295
|
+
when BasicPattern
|
296
|
+
pattern_elem.match?(object, env)
|
297
|
+
|
298
|
+
else
|
299
|
+
object == pattern_elem
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
# A pattern that matches any object
|
305
|
+
class WildcardPattern < BasicPattern
|
306
|
+
include Singleton
|
307
|
+
|
308
|
+
# Return true for any object and don't create any bindings.
|
309
|
+
#
|
310
|
+
# @return [true]
|
311
|
+
def match?(_, _)
|
312
|
+
true
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
# An abstract class that matches only a single object to a single pattern.
|
317
|
+
class SingleObjectPattern < BasicPattern
|
318
|
+
extend AbstractClass
|
319
|
+
|
320
|
+
# Create a new pattern with a single element.
|
321
|
+
#
|
322
|
+
# @param [Object] pattern_elem Object representing the pattern
|
323
|
+
def initialize(pattern_elem)
|
324
|
+
@pattern_elem = pattern_elem
|
325
|
+
end
|
326
|
+
|
327
|
+
# Testing the pattern only involves testing the single pattern
|
328
|
+
# element.
|
329
|
+
#
|
330
|
+
# @param [Object] object Object to test pattern against
|
331
|
+
# @param [Object] env Binding environment
|
332
|
+
#
|
333
|
+
# @return [Boolean]
|
334
|
+
def match?(object, env)
|
335
|
+
match_pattern_element(@pattern_elem, object, env)
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
# A pattern that forces an equality comparison
|
340
|
+
class LiteralPattern < SingleObjectPattern
|
341
|
+
# Test the object for equality to the pattern element.
|
342
|
+
#
|
343
|
+
# @param [Object] object Object to test pattern against
|
344
|
+
#
|
345
|
+
# @return [Boolean]
|
346
|
+
def match?(object, _)
|
347
|
+
object == @pattern_elem
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
# A pattern that binds a sub-pattern's matching object to a name in the
|
352
|
+
# binding environment.
|
353
|
+
class BindingPattern < SingleObjectPattern
|
354
|
+
attr_accessor :pattern_elem
|
355
|
+
|
356
|
+
# Create a new binding pattern.
|
357
|
+
#
|
358
|
+
# @param [Symbol] name Name to bind to
|
359
|
+
# @param [Object] pattern_elem Sub-pattern
|
360
|
+
def initialize(name, pattern_elem = nil)
|
361
|
+
@name = name
|
362
|
+
super(pattern_elem)
|
363
|
+
end
|
364
|
+
|
365
|
+
# Test the object for equality to the pattern element. Binds the
|
366
|
+
# object to the binding pattern's name if it does match.
|
367
|
+
#
|
368
|
+
# @param [Object] object Object to test pattern against
|
369
|
+
# @param [Object] env Binding environment
|
370
|
+
#
|
371
|
+
# @return [Boolean]
|
372
|
+
def match?(object, env)
|
373
|
+
(@pattern_elem.nil? or super).tap do |match|
|
374
|
+
env.send("#{@name}=", object) if match
|
375
|
+
end
|
376
|
+
end
|
377
|
+
end
|
378
|
+
|
379
|
+
# An abstract class that matches multiple objects to multiple patterns.
|
380
|
+
class MultipleObjectPattern < BasicPattern
|
381
|
+
extend AbstractClass
|
382
|
+
|
383
|
+
# Create a new pattern with multiple elements.
|
384
|
+
#
|
385
|
+
# @param [Array<Object>] pattern Array of pattern elements
|
386
|
+
def initialize(pattern)
|
387
|
+
@pattern = pattern
|
388
|
+
end
|
389
|
+
|
390
|
+
# Test multiple objects against multiple pattern elements.
|
391
|
+
#
|
392
|
+
# @param [Object] objects Object to test pattern against
|
393
|
+
#
|
394
|
+
# @return [Boolean]
|
395
|
+
def match?(objects, env)
|
396
|
+
if objects.length == @pattern.length
|
397
|
+
@pattern.zip(objects).each do |pattern_elem, object|
|
398
|
+
return false unless match_pattern_element(pattern_elem, object, env)
|
399
|
+
end
|
400
|
+
|
401
|
+
true
|
402
|
+
|
403
|
+
else
|
404
|
+
(@pattern.length == 1 and @pattern.first == WildcardPattern.instance)
|
405
|
+
end
|
406
|
+
end
|
407
|
+
end
|
408
|
+
|
409
|
+
# The that contains all of the pattern elements passed to a with clause.
|
410
|
+
class OuterPattern < MultipleObjectPattern
|
411
|
+
attr_writer :block
|
412
|
+
|
413
|
+
# Create a new outer pattern with the given pattern elements, guard,
|
414
|
+
# and block.
|
415
|
+
#
|
416
|
+
# @param [Array<Object>] pattern Pattern elements
|
417
|
+
# @param [Proc] guard Guard clause that is tested if the pattern matches
|
418
|
+
# @param [Proc] block Block to be evaluated if the pattern matches
|
419
|
+
def initialize(pattern, guard, block)
|
420
|
+
super(pattern)
|
421
|
+
@guard = guard
|
422
|
+
@block = block
|
423
|
+
end
|
424
|
+
|
425
|
+
# Call the pattern's block, passing the given objects to the block.
|
426
|
+
#
|
427
|
+
# @param [Object] env Environment in which to evaluate the block
|
428
|
+
# @param [Array<Object>] objects Arguments to the block
|
429
|
+
def call(env, objects = [])
|
430
|
+
if @block then env.instance_exec(*objects, &@block) else nil end
|
431
|
+
end
|
432
|
+
|
433
|
+
# Test the objects for equality to the pattern elements.
|
434
|
+
#
|
435
|
+
# @param [Object] objects Objects to test pattern elements against
|
436
|
+
# @param [Object] env Binding environment
|
437
|
+
#
|
438
|
+
# @return [Boolean]
|
439
|
+
def match?(objects, env)
|
440
|
+
super && (@guard.nil? or env.instance_exec(&@guard))
|
441
|
+
end
|
442
|
+
end
|
443
|
+
|
444
|
+
# A pattern that matches an instance of a class and destructures it so
|
445
|
+
# that the values contained by the object may be matched upon.
|
446
|
+
class DestructuringPattern < MultipleObjectPattern
|
447
|
+
# Create a new destructuring pattern.
|
448
|
+
#
|
449
|
+
# @param [Class] klass Class to match instances of. It must be destructurable.
|
450
|
+
# @param [Object] pattern Pattern elements to use in matching the object's values
|
451
|
+
def initialize(klass, *pattern)
|
452
|
+
@klass = klass
|
453
|
+
super(pattern)
|
454
|
+
end
|
455
|
+
|
456
|
+
# Test to see if the object is an instance of the appropriate class,
|
457
|
+
# and if so destructure it and test it's values against the
|
458
|
+
# sub-pattern elements.
|
459
|
+
#
|
460
|
+
# @param [Object] object Object to test pattern against
|
461
|
+
# @param [Object] env Binding environment
|
462
|
+
#
|
463
|
+
# @return [Boolean]
|
464
|
+
def match?(object, env)
|
465
|
+
object.is_a?(@klass) and super(object.destructure(@pattern.length), env)
|
466
|
+
end
|
467
|
+
end
|
468
|
+
end
|
469
|
+
|
470
|
+
###################################
|
471
|
+
# Standard Library Deconstructors #
|
472
|
+
###################################
|
473
|
+
|
474
|
+
class Array
|
475
|
+
extend Filigree::Destructurable
|
476
|
+
|
477
|
+
# Destructuring for the array class. If the array is being matched
|
478
|
+
# against two patterns the destructuring of the array will be the first
|
479
|
+
# element and then an array containing the rest of the values. If there
|
480
|
+
# are three patterns the destructuring of the array will be the first and
|
481
|
+
# second elements, and then an array containing the remainder of the
|
482
|
+
# values.
|
483
|
+
#
|
484
|
+
# @param [Fixnum] num_elems Number of sub-pattern elements
|
485
|
+
#
|
486
|
+
# @return [Array<Object>]
|
487
|
+
def destructure(num_elems)
|
488
|
+
[*self.first(num_elems - 1), self[(num_elems - 1)..-1]]
|
489
|
+
end
|
490
|
+
end
|
491
|
+
|
492
|
+
class Class
|
493
|
+
# Causes an instance of a class to be bound the the given name.
|
494
|
+
#
|
495
|
+
# @param [BindingPattern] binding_pattern Name to bind the instance to
|
496
|
+
def as(binding_pattern)
|
497
|
+
binding_pattern.tap { |bp| bp.pattern_elem = self }
|
498
|
+
end
|
499
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# Author: Chris Wailes <chris.wailes@gmail.com>
|
2
|
+
# Project: Filigree
|
3
|
+
# Date: 2013/05/04
|
4
|
+
# Description: Additional features for all objects.
|
5
|
+
|
6
|
+
############
|
7
|
+
# Requires #
|
8
|
+
############
|
9
|
+
|
10
|
+
# Standard Library
|
11
|
+
|
12
|
+
# Filigree
|
13
|
+
|
14
|
+
###########
|
15
|
+
# Methods #
|
16
|
+
###########
|
17
|
+
|
18
|
+
# Simple implementation of the Y combinator.
|
19
|
+
#
|
20
|
+
# @param [Object] value Value to be returned after executing the provided block.
|
21
|
+
#
|
22
|
+
# @return [Object] The object passed in parameter value.
|
23
|
+
def returning(value)
|
24
|
+
yield(value)
|
25
|
+
value
|
26
|
+
end
|
27
|
+
|
28
|
+
#######################
|
29
|
+
# Classes and Modules #
|
30
|
+
#######################
|
31
|
+
|
32
|
+
# Object class extras.
|
33
|
+
class Object
|
34
|
+
# A copy and modification helper.
|
35
|
+
#
|
36
|
+
# @return [Object] A copy of the object with the block evaluated in the context of the copy.
|
37
|
+
def clone_with(&block)
|
38
|
+
self.clone.tap { |clone| clone.instance_exec(&block) }
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# Author: Chris Wailes <chris.wailes@gmail.com>
|
2
|
+
# Project: Filigree
|
3
|
+
# Date: 2014/1/20
|
4
|
+
# Description: A helper for require.
|
5
|
+
|
6
|
+
############
|
7
|
+
# Requires #
|
8
|
+
############
|
9
|
+
|
10
|
+
# Standard Library
|
11
|
+
|
12
|
+
# Filigree
|
13
|
+
|
14
|
+
###########
|
15
|
+
# Methods #
|
16
|
+
###########
|
17
|
+
|
18
|
+
# Require a file, but fail gracefully if it isn't found.
|
19
|
+
#
|
20
|
+
# @param [String] file File to be requested
|
21
|
+
# @param [Boolean] print_failure To print a message on failure or not
|
22
|
+
def request_file(file, print_failure = false)
|
23
|
+
begin
|
24
|
+
require file
|
25
|
+
yield if block_given?
|
26
|
+
rescue LoadError
|
27
|
+
if print_warning.is_a?(String)
|
28
|
+
puts print_failure
|
29
|
+
elsif print_failure
|
30
|
+
puts "Unable to require file: #{file}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# Author: Chris Wailes <chris.wailes@gmail.com>
|
2
|
+
# Project: Filigree
|
3
|
+
# Date: 2014/01/20
|
4
|
+
# Description: Class extensions for the String class.
|
5
|
+
|
6
|
+
############
|
7
|
+
# Requires #
|
8
|
+
############
|
9
|
+
|
10
|
+
# Standard Library
|
11
|
+
|
12
|
+
# Filigree
|
13
|
+
|
14
|
+
#######################
|
15
|
+
# Classes and Modules #
|
16
|
+
#######################
|
17
|
+
|
18
|
+
class String
|
19
|
+
# Chop up the string into multiple lines so that no line is longer than
|
20
|
+
# the specified number of characters.
|
21
|
+
#
|
22
|
+
# @param [Fixnum] indent Indentation to put before each line; it is
|
23
|
+
# assumed that this indentation is already present for the first line
|
24
|
+
# @param [Fixnum] max_length Maximum length per line
|
25
|
+
def segment(indent, max_length = 80)
|
26
|
+
lines = Array.new
|
27
|
+
|
28
|
+
words = self.split(/\s/)
|
29
|
+
line = words.shift
|
30
|
+
|
31
|
+
line_length = indent + line.length
|
32
|
+
|
33
|
+
words.each do |word|
|
34
|
+
new_length = line_length + 1 + word.length
|
35
|
+
|
36
|
+
if new_length < max_length
|
37
|
+
line += " #{word}"
|
38
|
+
line_length = new_length
|
39
|
+
|
40
|
+
else
|
41
|
+
lines << line
|
42
|
+
|
43
|
+
line = word
|
44
|
+
line_length = indent + word.length
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
lines << line unless line.empty?
|
49
|
+
|
50
|
+
lines.join("\n" + (' ' * indent))
|
51
|
+
end
|
52
|
+
end
|