filigree 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: a1d6915cb3d2543b432be0c5edbf4a3217ab373e
|
4
|
+
data.tar.gz: 4855e4e67ea643c57890627905bb4be09d0d784d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 79fcfefdb28a5ce107c5568a6110a1e4d9bfcde37e95663fe4292b9ebc9cda0d5057d0efbdfbcaada9158856a39075d01875b56f0e88a7e023192f64ba10750e
|
7
|
+
data.tar.gz: 76d3b55b506b2b37e1a801d8e6795cc044a05ad47f008d22a9cbf83000ab8b148117e1577040484f2a22abd3de24757ca20e7a985a4f0b6e60ec1c98ea412e7c
|
data/AUTHORS
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
Chris Wailes <chris.wailes@gmail.com>
|
data/LICENSE
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
Copyright © 2013 Chris Wailes. All rights reserved.
|
2
|
+
|
3
|
+
Developed by: Chris Wailes
|
4
|
+
http://chris.wailes.name
|
5
|
+
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal with the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
7
|
+
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimers.
|
8
|
+
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimers in the documentation and/or other materials provided with the distribution.
|
9
|
+
3. Neither the names of the Filigree development team, nor the names of its contributors may be used to endorse or promote products derived from this Software without specific prior written permission.
|
10
|
+
|
11
|
+
|
12
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,336 @@
|
|
1
|
+
Filigree: For more beautiful Ruby
|
2
|
+
=================================
|
3
|
+
|
4
|
+
Filigree is a collection of classes, modules, and functions that I found myself re-writing in each of my projects. In addition, I have thrown in a couple of other features that I've always wanted. Here are some of those features:
|
5
|
+
|
6
|
+
* Abstract classes and methods
|
7
|
+
* An implementation of pattern matching
|
8
|
+
* An implementation of the visitor pattern
|
9
|
+
* Module for defining class methods in a mixin
|
10
|
+
* Modules for configuration and command handling
|
11
|
+
* Easy dynamic type checking
|
12
|
+
* Small extensions to standard library classes
|
13
|
+
|
14
|
+
I'm going to go over some of the more important features below, but I won't be able to cover everything. Explore the rest of the documentation to discover additional features.
|
15
|
+
|
16
|
+
Abstract Classes and Methods
|
17
|
+
----------------------------
|
18
|
+
|
19
|
+
Abstract classes as methods can be defined as follows:
|
20
|
+
|
21
|
+
```Ruby
|
22
|
+
class Foo
|
23
|
+
extend Filigree::AbstractClass
|
24
|
+
|
25
|
+
abstract_method :must_implement
|
26
|
+
end
|
27
|
+
|
28
|
+
class Bar < Foo;
|
29
|
+
|
30
|
+
# Raises an AbstractClassError
|
31
|
+
Foo.new
|
32
|
+
|
33
|
+
# Returns a new instance of Bar
|
34
|
+
Bar.new
|
35
|
+
|
36
|
+
# Raieses an AbstractMethodError
|
37
|
+
Bar.new.must_implement
|
38
|
+
```
|
39
|
+
|
40
|
+
Pattern Matching
|
41
|
+
----------------
|
42
|
+
|
43
|
+
Filigree provides an implementation of pattern matching. When performing a match objects are tested against patterns defined inside the *match block*:
|
44
|
+
|
45
|
+
```Ruby
|
46
|
+
def fib(n)
|
47
|
+
match n do
|
48
|
+
with(1)
|
49
|
+
with(2) { 1 }
|
50
|
+
with(_) { fib(n-1) + fib(n-2) }
|
51
|
+
end
|
52
|
+
end
|
53
|
+
```
|
54
|
+
|
55
|
+
The most basic pattern is the literal. Here, the object or objects being matched will be tested for equality with the value passed to `with`. Another simple pattern is the wildcard pattern. It will match any value; you can think of it as the default case.
|
56
|
+
|
57
|
+
```Ruby
|
58
|
+
def foo(n)
|
59
|
+
match n do
|
60
|
+
with(1) { :one }
|
61
|
+
with(2) { :two }
|
62
|
+
with(_) { :other }
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
foo(1) # => :one
|
67
|
+
foo(42) # => :other
|
68
|
+
```
|
69
|
+
|
70
|
+
You may also match against variables. This can sometimes conflict with the next kind of pattern, which is a binding pattern. Here, the pattern will match any object, and then make the object it matched available to the *with block* via an attribute reader. This is accomplished using the method_missing callback, so if there is a variable or function with that name you might accidentally compare against a variable or returned value. To bind to a name that is already in scope you can use the {Filigree::MatchEnvironment#Bind} method. In addition, class and destructuring pattern results (see bellow) can be bound to a variable by using the {Filigree::BasicPattern#as} method.
|
71
|
+
|
72
|
+
```Ruby
|
73
|
+
var = 42
|
74
|
+
|
75
|
+
# Returns :hoopy
|
76
|
+
match 42 do
|
77
|
+
with(var) { :hoopy }
|
78
|
+
with(0) { :zero }
|
79
|
+
end
|
80
|
+
|
81
|
+
# Returns 42
|
82
|
+
match 42 do
|
83
|
+
with(x) { x }
|
84
|
+
end
|
85
|
+
|
86
|
+
x = 3
|
87
|
+
# Returns 42
|
88
|
+
match 42 do
|
89
|
+
with(Bind(:x)) { x }
|
90
|
+
with(42) { :hoopy }
|
91
|
+
end
|
92
|
+
```
|
93
|
+
|
94
|
+
If you wish to match string patterns you can use regular expressions. Any object that isn't a string will fail to match against a regular expression. If the object being matched is a string then the regular expressions `match?` method is used. The result of the regular expression match is available inside the *with block* via the match_data accessor.
|
95
|
+
|
96
|
+
```Ruby
|
97
|
+
def matcher(object)
|
98
|
+
match object do
|
99
|
+
with(/hoopy/) { 42 }
|
100
|
+
with(Integer) { 'hoopy' }
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
matcher('hoopy') # => 42
|
105
|
+
matcher(42) # => 'hoopy'
|
106
|
+
```
|
107
|
+
|
108
|
+
When a class is used in a pattern it will match any object that is an instance of that class. If you wish to compare one regular expression to
|
109
|
+
another, or one class to another, you can force the comparison using the {Filigree::MatchEnvironment#Literal} method.
|
110
|
+
|
111
|
+
Destructuring patterns allow you to match against an instance of a class, while simultaneously binding values stored inside the object to variables in the context of the *with block*. A class that is destructurable must include the {Filigree::Destructurable} module. You can then destructure an object like this:
|
112
|
+
|
113
|
+
```Ruby
|
114
|
+
class Foo
|
115
|
+
include Filigree::Destructurable
|
116
|
+
def initialize(a, b)
|
117
|
+
@a = a
|
118
|
+
@b = b
|
119
|
+
end
|
120
|
+
|
121
|
+
def destructure(_)
|
122
|
+
[@a, @b]
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# Returns true
|
127
|
+
match Foo.new(4, 2) do
|
128
|
+
with(Foo.(4, 2)) { true }
|
129
|
+
with(_) { false }
|
130
|
+
end
|
131
|
+
```
|
132
|
+
|
133
|
+
Of particular note is the destructuring of arrays. When an array is destructured like so, `Array.(xs)`, the array is bound to `xs`. If an additional pattern is added, `Array.(x, xs)`, then `x` will hold the first element of the array and `xs` will hold the remailing characters. As more patterns are added more elements will be pulled off of the front of the array. You can match an array with a specific number of elements by using an empty array litteral: `Array.(x, [])`
|
134
|
+
|
135
|
+
Both `match` and `with` can take multiple arguments. When this happens, each object is paired up with the corresponding pattern. If they all match, then the `with` clause matches. In this way you can match against tuples.
|
136
|
+
|
137
|
+
Any with clause can be given a guard clause by passing a lambda as the last argument to `with`. These are evaluated after the pattern is matched, and any bindings made in the pattern are available to the guard clause.
|
138
|
+
|
139
|
+
```Ruby
|
140
|
+
match o do
|
141
|
+
with(n, -> { n < 0 }) { :NEG }
|
142
|
+
with(0) { :ZERO }
|
143
|
+
with(n, -> { n > 0 }) { :POS }
|
144
|
+
end
|
145
|
+
```
|
146
|
+
|
147
|
+
If you wish to evaluate the same body on matching any of several patterns you may list them in order and then specify the body for the last pattern in the group.
|
148
|
+
|
149
|
+
Patterns are evaluated in the order in which they are defined and the first pattern to match is the one chosen. You may define helper methods inside the match block. They will be re-defined every time the match statement is evaluated, so you should move any definitions outside any match calls that are being evaluated often.
|
150
|
+
|
151
|
+
A Visitor Pattern
|
152
|
+
-----------------
|
153
|
+
|
154
|
+
Filigree's implementation of the visitor pattern is built on the pattern matching functionality described above. It's usage is pretty simple:
|
155
|
+
|
156
|
+
```Ruby
|
157
|
+
class Binary < Struct.new(:x, :y)
|
158
|
+
extend Filigree::Destructurable
|
159
|
+
include Filigree::Visitor
|
160
|
+
|
161
|
+
def destructure(_)
|
162
|
+
[x, y]
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
class Add < Binary; end
|
167
|
+
class Mul < Binary; end
|
168
|
+
|
169
|
+
class MathVisitor
|
170
|
+
include Filigree::Visitor
|
171
|
+
|
172
|
+
on(Add.(x, y)) do
|
173
|
+
x + y
|
174
|
+
end
|
175
|
+
|
176
|
+
on(Mul.(x, y)) do
|
177
|
+
x * y
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
mv = MathVisitor.new
|
182
|
+
|
183
|
+
mv.visit(Add.new(6, 8)) # => 14
|
184
|
+
mv.visit(Mul.new(7, 6)) # => 42
|
185
|
+
```
|
186
|
+
|
187
|
+
Class Methods
|
188
|
+
-------------
|
189
|
+
|
190
|
+
{Filigree::ClassMethodsModule} makes it easy to add class methods to mixins:
|
191
|
+
|
192
|
+
```Ruby
|
193
|
+
module Foo
|
194
|
+
include Filigree::ClassMethodsModule
|
195
|
+
|
196
|
+
def foo
|
197
|
+
:foo
|
198
|
+
end
|
199
|
+
|
200
|
+
module ClassMethods
|
201
|
+
def bar
|
202
|
+
:bar
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
class Baz
|
208
|
+
include Foo
|
209
|
+
end
|
210
|
+
|
211
|
+
Baz.new.foo # => :foo
|
212
|
+
Ba.bar # => :bar
|
213
|
+
```
|
214
|
+
|
215
|
+
Configuration Handling
|
216
|
+
----------------------
|
217
|
+
|
218
|
+
{Filigree::Configuration} will help you parse command line options:
|
219
|
+
|
220
|
+
```Ruby
|
221
|
+
class MyConfig
|
222
|
+
include Filigree::Configuration
|
223
|
+
|
224
|
+
add_option Filigree::Configuration::HELP_OPTION
|
225
|
+
|
226
|
+
help 'Sets the target'
|
227
|
+
required
|
228
|
+
string_option 'target', 't'
|
229
|
+
|
230
|
+
help 'Set the port for the target'
|
231
|
+
default 1025
|
232
|
+
option 'port', 'p', conversions: [:to_i]
|
233
|
+
|
234
|
+
help 'Set credentials'
|
235
|
+
default ['user', 'password']
|
236
|
+
option 'credentials', 'c', conversions: [:to_s, :to_s]
|
237
|
+
|
238
|
+
help 'Be verbose'
|
239
|
+
bool_option 'verbose', 'v'
|
240
|
+
|
241
|
+
auto 'next_port' { self.port + 1 }
|
242
|
+
|
243
|
+
help 'load data from file'
|
244
|
+
option 'file', 'f' do |f|
|
245
|
+
process_file f
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
# Defaults to parsing ARGV
|
250
|
+
conf = MyConfig.new(['-t', 'localhost', '-v'])
|
251
|
+
|
252
|
+
conf.target # => 'localhost'
|
253
|
+
conf.next_port # => 1026
|
254
|
+
|
255
|
+
# You can searialize configurations to a strings, file, or IO objects
|
256
|
+
serialized_config = conf.dump
|
257
|
+
# And then load the configuration from the serialized version
|
258
|
+
conf = MyConfig.new serialized_config
|
259
|
+
```
|
260
|
+
|
261
|
+
Command Handling
|
262
|
+
----------------
|
263
|
+
|
264
|
+
Now that we can parse configuration options, how about we handle commands?
|
265
|
+
|
266
|
+
```Ruby
|
267
|
+
class MyCommands
|
268
|
+
include Filigree::Commands
|
269
|
+
|
270
|
+
help 'Adds two numbers together'
|
271
|
+
param 'x', 'The first number to add'
|
272
|
+
param 'y', 'The second number to add'
|
273
|
+
command 'add' do |x, y|
|
274
|
+
x.to_i + y.to_i
|
275
|
+
end
|
276
|
+
|
277
|
+
help 'Say hello from the command handler'
|
278
|
+
config do
|
279
|
+
default 'world'
|
280
|
+
string_option 'subject', 's'
|
281
|
+
end
|
282
|
+
command 'hello' do
|
283
|
+
"hello #{subject}"
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
mc = MyCommands.new
|
288
|
+
|
289
|
+
mc.('add 35 7') # => 42
|
290
|
+
mc.('hello') # => 'hello world'
|
291
|
+
mc.('hello -s chris') # => 'hello chris'
|
292
|
+
```
|
293
|
+
|
294
|
+
Type Checking
|
295
|
+
-------------
|
296
|
+
|
297
|
+
Filigree provides two ways to perform basic type checking at run time:
|
298
|
+
|
299
|
+
1. {check_type} and {check_array_type}
|
300
|
+
2. {Filigree::TypedClass}
|
301
|
+
|
302
|
+
The first option will simply check the type of an object or an array of objects. Optionally, you can assign blame to a named variable, allow the value to be nil, or perform strict checking. Strict checking uses the `instance_of?` method while non-strict checking uses `is_a?`.
|
303
|
+
|
304
|
+
The second option works like so:
|
305
|
+
|
306
|
+
```Ruby
|
307
|
+
class Foo
|
308
|
+
include Filigree::TypedClass
|
309
|
+
|
310
|
+
typed_ivar :bar, Integer
|
311
|
+
typed_ivar :baz, String
|
312
|
+
|
313
|
+
default_constructor
|
314
|
+
end
|
315
|
+
|
316
|
+
var = Foo.new(42, '42')
|
317
|
+
var.bar = '42' # Raises a TypeError
|
318
|
+
```
|
319
|
+
|
320
|
+
Array#map
|
321
|
+
---------
|
322
|
+
|
323
|
+
The Array class has been monkey patched so that it takes an optional symbol argument. If it is provided, the symbol is sent to each of the objects in the array and the result is used for the new array.
|
324
|
+
|
325
|
+
```Ruby
|
326
|
+
[1, 2, 3, 4].map :to_sym # => [:1, :2, :3, :4]
|
327
|
+
```
|
328
|
+
|
329
|
+
Contributing
|
330
|
+
------------
|
331
|
+
|
332
|
+
Do you have bits of code that you use in all of your projects but arn't big enough for theirn own gem? Well, maybe your code could find a home in Filigree! Send me a patch that includes the useful bits and some tests and I'll se about adding it.
|
333
|
+
|
334
|
+
Other than that, what Filigree really needs is uses. Add it to your project and let me know what features you use and which you don't; where you would like to see improvements, and what pieces you really liked. Above all, submit issues if you encountere any bugs!
|
335
|
+
|
336
|
+
[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/chriswailes/filigree/trend.png)](https://bitdeli.com/free "Bitdeli Badge")
|
data/Rakefile
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
# Author: Chris Wailes <chris.wailes@gmail.com>
|
2
|
+
# Project: Filigree
|
3
|
+
# Date: 2013/4/19
|
4
|
+
# Description: Filigree's Rakefile.
|
5
|
+
|
6
|
+
############
|
7
|
+
# Requires #
|
8
|
+
############
|
9
|
+
|
10
|
+
# Filigree
|
11
|
+
require File.expand_path("../lib/filigree/request_file", __FILE__)
|
12
|
+
require File.expand_path("../lib/filigree/version", __FILE__)
|
13
|
+
|
14
|
+
###########
|
15
|
+
# Bundler #
|
16
|
+
###########
|
17
|
+
|
18
|
+
request_file('bundler', 'Bundler is not installed.') do
|
19
|
+
Bundler::GemHelper.install_tasks
|
20
|
+
end
|
21
|
+
|
22
|
+
########
|
23
|
+
# Flay #
|
24
|
+
########
|
25
|
+
|
26
|
+
request_file('flay', 'Flay is not installed.') do
|
27
|
+
desc 'Analyze code for similarities with Flay'
|
28
|
+
task :flay do
|
29
|
+
flay = Flay.new
|
30
|
+
flay.process(*Dir['lib/**/*.rb'])
|
31
|
+
flay.report
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
########
|
36
|
+
# Flog #
|
37
|
+
########
|
38
|
+
|
39
|
+
request_file('flog_cli', 'Flog is not installed.') do
|
40
|
+
desc 'Analyze code complexity with Flog'
|
41
|
+
task :flog do
|
42
|
+
whip = FlogCLI.new
|
43
|
+
whip.flog('lib')
|
44
|
+
whip.report
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
############
|
49
|
+
# MiniTest #
|
50
|
+
############
|
51
|
+
|
52
|
+
request_file('rake/testtask', 'Minitest is not installed.') do
|
53
|
+
Rake::TestTask.new do |t|
|
54
|
+
t.libs << 'test'
|
55
|
+
t.test_files = FileList['test/ts_filigree.rb']
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
#########
|
60
|
+
# Notes #
|
61
|
+
#########
|
62
|
+
|
63
|
+
request_file('rake/notes/rake_task', 'Rake-notes is not installed.')
|
64
|
+
|
65
|
+
########
|
66
|
+
# Reek #
|
67
|
+
########
|
68
|
+
|
69
|
+
request_file('reek/rake/task', 'Reek is not installed.') do
|
70
|
+
Reek::Rake::Task.new do |t|
|
71
|
+
t.fail_on_error = false
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
##################
|
76
|
+
# Rubygems Tasks #
|
77
|
+
##################
|
78
|
+
|
79
|
+
request_file('rubygems/tasks', 'Rubygems-tasks is not installed.') do
|
80
|
+
Gem::Tasks.new do |t|
|
81
|
+
t.console.command = 'pry'
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
########
|
86
|
+
# YARD #
|
87
|
+
########
|
88
|
+
|
89
|
+
request_file('yard', 'Yard is not installed.') do
|
90
|
+
YARD::Rake::YardocTask.new do |t|
|
91
|
+
t.options = [
|
92
|
+
'--title', 'Filigree',
|
93
|
+
'-m', 'markdown',
|
94
|
+
'-M', 'redcarpet',
|
95
|
+
'-c', '.yardoc/cache',
|
96
|
+
'--private'
|
97
|
+
]
|
98
|
+
|
99
|
+
t.files = Dir['lib/**/*.rb']
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# Author: Chris Wailes <chris.wailes@gmail.com>
|
2
|
+
# Project: Filigree
|
3
|
+
# Date: 2013/4/19
|
4
|
+
# Description: An implementation of an AbstractClass module.
|
5
|
+
|
6
|
+
############
|
7
|
+
# Requires #
|
8
|
+
############
|
9
|
+
|
10
|
+
# Standard Library
|
11
|
+
|
12
|
+
# Filigree
|
13
|
+
|
14
|
+
##########
|
15
|
+
# Errors #
|
16
|
+
##########
|
17
|
+
|
18
|
+
# An error representing an erroneous instantiation of an abstract class.
|
19
|
+
class AbstractClassError < RuntimeError
|
20
|
+
def initialize(class_name)
|
21
|
+
super "Instantiating abstract class #{class_name} is not allowed."
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# An error representing a call to an unimplemented abstract method.
|
26
|
+
class AbstractMethodError < RuntimeError
|
27
|
+
def initialize(method_name, abstract_class_name)
|
28
|
+
super "Abstract method #{method_name}, defined in #{abstract_class_name}, must be overridden by a subclass."
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
#######################
|
33
|
+
# Classes and Modules #
|
34
|
+
#######################
|
35
|
+
|
36
|
+
module Filigree
|
37
|
+
# A module the implements the abstract class and abstract method patterns.
|
38
|
+
module AbstractClass
|
39
|
+
|
40
|
+
####################
|
41
|
+
# Instance Methods #
|
42
|
+
####################
|
43
|
+
|
44
|
+
# Declares a method with the given name. If it is called it will raise
|
45
|
+
# an AbstractMethodError.
|
46
|
+
#
|
47
|
+
# @param [Symbol] name The name of the abstract method you with to declare
|
48
|
+
#
|
49
|
+
# @return [void]
|
50
|
+
def abstract_method(name)
|
51
|
+
abstract_class_name = @abstract_class.name
|
52
|
+
|
53
|
+
define_method name do
|
54
|
+
raise AbstractMethodError.new name, abstract_class_name
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Install instance class variables in the extended class.
|
59
|
+
#
|
60
|
+
# @return [void]
|
61
|
+
def install_icvars
|
62
|
+
@abstract_class = self
|
63
|
+
end
|
64
|
+
|
65
|
+
# Raise an AbstractClassError if someone attempts to instantiate an
|
66
|
+
# abstract class.
|
67
|
+
#
|
68
|
+
# @param [Object] args The arguments to initialize.
|
69
|
+
#
|
70
|
+
# @raise [AbstractClassError]
|
71
|
+
def new(*args)
|
72
|
+
if @abstract_class == self
|
73
|
+
raise AbstractClassError, self.name
|
74
|
+
else
|
75
|
+
super
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
#############
|
80
|
+
# Callbacks #
|
81
|
+
#############
|
82
|
+
|
83
|
+
# Tell the extended class to install its instance class variables.
|
84
|
+
#
|
85
|
+
# @return [void]
|
86
|
+
def self.extended(klass)
|
87
|
+
klass.install_icvars
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# Author: Chris Wailes <chris.wailes@gmail.com>
|
2
|
+
# Project: Filigree
|
3
|
+
# Date: 2013/05/14
|
4
|
+
# Description: Simple application framework.
|
5
|
+
|
6
|
+
############
|
7
|
+
# Requires #
|
8
|
+
############
|
9
|
+
|
10
|
+
# Standard Library
|
11
|
+
|
12
|
+
# Filigree
|
13
|
+
require 'filigree/class_methods_module'
|
14
|
+
require 'filigree/configuration'
|
15
|
+
|
16
|
+
##########
|
17
|
+
# Errors #
|
18
|
+
##########
|
19
|
+
|
20
|
+
###########
|
21
|
+
# Methods #
|
22
|
+
###########
|
23
|
+
|
24
|
+
#######################
|
25
|
+
# Classes and Modules #
|
26
|
+
#######################
|
27
|
+
|
28
|
+
module Filigree
|
29
|
+
# The beginnings of a general purpose application module. The aim is to provide
|
30
|
+
# the basic framework for larger desktop and command line applications.
|
31
|
+
module Application
|
32
|
+
include ClassMethodsModule
|
33
|
+
|
34
|
+
#############
|
35
|
+
# Constants #
|
36
|
+
#############
|
37
|
+
|
38
|
+
REQUIRED_METHODS = [
|
39
|
+
:kill,
|
40
|
+
:pause,
|
41
|
+
:resume,
|
42
|
+
:run,
|
43
|
+
:stop
|
44
|
+
]
|
45
|
+
|
46
|
+
####################
|
47
|
+
# Instance Methods #
|
48
|
+
####################
|
49
|
+
|
50
|
+
attr_accessor :configuration
|
51
|
+
alias :config :configuration
|
52
|
+
|
53
|
+
def initialize
|
54
|
+
@configuration = self.class::Configuration.new
|
55
|
+
|
56
|
+
# Set up signal handlers.
|
57
|
+
Signal.trap('ABRT') { self.stop }
|
58
|
+
Signal.trap('INT') { self.stop }
|
59
|
+
Signal.trap('QUIT') { self.stop }
|
60
|
+
Signal.trap('TERM') { self.stop }
|
61
|
+
|
62
|
+
Signal.trap('KILL') { self.kill }
|
63
|
+
|
64
|
+
Signal.trap('CONT') { self.resume }
|
65
|
+
Signal.trap('STOP') { self.pause }
|
66
|
+
end
|
67
|
+
|
68
|
+
#################
|
69
|
+
# Class Methods #
|
70
|
+
#################
|
71
|
+
|
72
|
+
module ClassMethods
|
73
|
+
# Check to make sure all of the required methods are defined.
|
74
|
+
#
|
75
|
+
# @raise [NoMethodError]
|
76
|
+
#
|
77
|
+
# @return [void]
|
78
|
+
def finalize
|
79
|
+
REQUIRED_METHODS.each do |method|
|
80
|
+
if not self.instance_methods.include?(method)
|
81
|
+
raise(NoMethodError, "Application #{self.name} missing method: #{method}")
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Create a new instance of this application and run it.
|
87
|
+
#
|
88
|
+
# @return [Object]
|
89
|
+
def run
|
90
|
+
self.new.run
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
#############
|
95
|
+
# Callbacks #
|
96
|
+
#############
|
97
|
+
|
98
|
+
class << self
|
99
|
+
alias :old_included :included
|
100
|
+
|
101
|
+
def included(klass)
|
102
|
+
old_included(klass)
|
103
|
+
klass.const_set(:Configuration, Class.new { include Filigree::Configuration })
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# Author: Chris Wailes <chris.wailes@gmail.com>
|
2
|
+
# Project: Filigree
|
3
|
+
# Date: 2013/05/04
|
4
|
+
# Description: Additional features for Arrays.
|
5
|
+
|
6
|
+
############
|
7
|
+
# Requires #
|
8
|
+
############
|
9
|
+
|
10
|
+
# Standard Library
|
11
|
+
|
12
|
+
# Filigree
|
13
|
+
|
14
|
+
#######################
|
15
|
+
# Classes and Modules #
|
16
|
+
#######################
|
17
|
+
|
18
|
+
class Array
|
19
|
+
alias :aliased_map :map
|
20
|
+
|
21
|
+
# Map now takes an optional symbol argument. If a symbol is provided the
|
22
|
+
# specified method is invoked on each of the objects in the array.
|
23
|
+
#
|
24
|
+
# @param [Symbol] method Method to be invoked on the array elements.
|
25
|
+
# @param [Proc] block Normal Array#each block.
|
26
|
+
#
|
27
|
+
# @return [Array<Object>]
|
28
|
+
def map(method = nil, &block)
|
29
|
+
if method
|
30
|
+
self.aliased_map { |obj| obj.send(method) }
|
31
|
+
else
|
32
|
+
self.aliased_map(&block)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|