perennial 1.0.0.2 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +23 -0
- data/lib/perennial/dispatchable.rb +10 -1
- data/lib/perennial/jeweler_ext.rb +75 -0
- data/lib/perennial.rb +1 -1
- data/test/delegateable_test.rb +38 -0
- data/test/dispatchable_test.rb +212 -0
- data/test/hookable_test.rb +61 -0
- data/test/loader_test.rb +1 -0
- data/test/loggable_test.rb +38 -0
- data/test/logger_test.rb +59 -0
- data/test/proxy_test.rb +30 -0
- data/test/settings_test.rb +103 -0
- data/test/test_helper.rb +38 -0
- metadata +43 -14
data/README.md
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# Perennial - a Ruby Event-driven application Library #
|
2
|
+
|
3
|
+
Perennial is yet another god damned event library / framework
|
4
|
+
for Ruby, built on top of EventMachine. The whole goal of Perennial
|
5
|
+
is to make it easy to build a particular style / class of application
|
6
|
+
such as [Marvin](http://github.com/Sutto/marvin). Most of the code
|
7
|
+
has been extracted from building Marvin (and later, BirdGrinder - like
|
8
|
+
Marvin but for twitter).
|
9
|
+
|
10
|
+
Applications built for Perennial are devised around the concept of 'clients'
|
11
|
+
and 'handlers'. Clients are things which handle the actual processing (e.g.
|
12
|
+
Marvin's IRC client takes the IRC protocol and converts it into events) and
|
13
|
+
handlers respond to messages. In practice, it's an approach inspired by Rack
|
14
|
+
in that we do the simplest thing's possible.
|
15
|
+
|
16
|
+
Since each event processed by a handler is incredibly simple (a symbol for a name
|
17
|
+
and a hash of associated options / details), and there are typically few requirements
|
18
|
+
(e.g. a handler only typically needs to define the 'handle' method) it
|
19
|
+
makes it relatively easy to build your application how you like.
|
20
|
+
|
21
|
+
In other words, Perennial is mainly a bunch of useful mixings (Dispatchable,
|
22
|
+
Hookable, Delegateable) which fit in with some evented application design
|
23
|
+
along with the framework for building applications around this design.
|
@@ -59,6 +59,8 @@ module Perennial
|
|
59
59
|
Logger.debug "Dispatching #{name} event (#{dispatch_queue.size} queued - on #{self.class.name})"
|
60
60
|
# Add ourselves to the queue
|
61
61
|
@dispatching = true
|
62
|
+
# TODO: improve performance. This should be dispatched per-request cycle.
|
63
|
+
pre_dispatching
|
62
64
|
begin
|
63
65
|
# The full handler name is the method we call given it exists.
|
64
66
|
full_handler_name = :"handle_#{name.to_s.underscore}"
|
@@ -88,13 +90,20 @@ module Perennial
|
|
88
90
|
Logger.log_exception(e)
|
89
91
|
end
|
90
92
|
@dispatching = false
|
91
|
-
dispatch(*@dispatch_queue.shift)
|
93
|
+
dispatch(*@dispatch_queue.shift) if dispatch_queue.present?
|
94
|
+
post_dispatching
|
92
95
|
else
|
93
96
|
Logger.debug "Adding #{name} event to the end of the queue (on #{self.class.name})"
|
94
97
|
dispatch_queue << [name, opts]
|
95
98
|
end
|
96
99
|
end
|
97
100
|
|
101
|
+
def pre_dispatching
|
102
|
+
end
|
103
|
+
|
104
|
+
def post_dispatching
|
105
|
+
end
|
106
|
+
|
98
107
|
end
|
99
108
|
|
100
109
|
module ClassMethods
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module Perennial
|
2
|
+
module JewelerExt
|
3
|
+
# Adds in utility messages to make it easier to manage versioning.
|
4
|
+
|
5
|
+
class << self
|
6
|
+
attr_accessor :version_array, :library_file
|
7
|
+
end
|
8
|
+
|
9
|
+
module Versioning
|
10
|
+
|
11
|
+
def write
|
12
|
+
contents = File.read(main_library_file)
|
13
|
+
contents.gsub!(/(VERSION\s+=\s+)(\[\d+\,\s*\d+\,\s*\d+(?:\,\s*\d+)?\])/) do |m|
|
14
|
+
"#{$1}#{array.inspect}"
|
15
|
+
end
|
16
|
+
File.open(main_library_file, "w+") do |f|
|
17
|
+
f.write(contents)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def array
|
22
|
+
[@major, @minor, @patch, @build].compact
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_s
|
26
|
+
items = array
|
27
|
+
items.pop if items[3] == 0
|
28
|
+
items.join(".")
|
29
|
+
end
|
30
|
+
|
31
|
+
def main_library_file
|
32
|
+
Perennial::JewelerExt.library_file
|
33
|
+
end
|
34
|
+
|
35
|
+
def parse_version
|
36
|
+
parts = Perennial::JewelerExt.version_array
|
37
|
+
@major = parts[0]
|
38
|
+
@minor = parts[1]
|
39
|
+
@patch = parts[2]
|
40
|
+
@build = parts[3]
|
41
|
+
end
|
42
|
+
|
43
|
+
def refresh
|
44
|
+
parse_version
|
45
|
+
end
|
46
|
+
|
47
|
+
def path
|
48
|
+
nil
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
require 'jeweler' unless defined?(Jeweler)
|
56
|
+
|
57
|
+
# Overrides Jeweler to version using our stuff
|
58
|
+
|
59
|
+
def Jeweler.versioning_via(file, version_array)
|
60
|
+
Perennial::JewelerExt.library_file = file
|
61
|
+
Perennial::JewelerExt.version_array = version_array
|
62
|
+
# Extend with the Perennial extensions
|
63
|
+
Jeweler::VersionHelper.class_eval do
|
64
|
+
def initialize(base_dir)
|
65
|
+
extend Perennial::JewelerExt::Versioning
|
66
|
+
parse_version
|
67
|
+
end
|
68
|
+
end
|
69
|
+
# Ensure version exists
|
70
|
+
Jeweler.class_eval do
|
71
|
+
def version_exists?
|
72
|
+
true
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
data/lib/perennial.rb
CHANGED
@@ -0,0 +1,38 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "test_helper")
|
2
|
+
|
3
|
+
class DelegateableTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
context 'basic delegates' do
|
6
|
+
|
7
|
+
setup do
|
8
|
+
@klass = test_class_for(Perennial::Delegateable)
|
9
|
+
@delegateable = @klass.new
|
10
|
+
end
|
11
|
+
|
12
|
+
should 'define a delegate= method' do
|
13
|
+
assert @delegateable.respond_to?(:delegate=)
|
14
|
+
end
|
15
|
+
|
16
|
+
should 'define a delegate_to method' do
|
17
|
+
assert @delegateable.respond_to?(:delegate_to)
|
18
|
+
end
|
19
|
+
|
20
|
+
should 'let you get the delegate proxy' do
|
21
|
+
@delegateable.delegate_to :awesome
|
22
|
+
assert proxy = @delegateable.delegate
|
23
|
+
assert_nothing_raised { proxy.awesomesauce }
|
24
|
+
assert_nothing_raised { proxy.ninja_party }
|
25
|
+
assert_equal "awesome", proxy.to_s
|
26
|
+
end
|
27
|
+
|
28
|
+
should 'let you get the real target of the delegate' do
|
29
|
+
@delegateable.delegate_to :awesome
|
30
|
+
assert real = @delegateable.real_delegate
|
31
|
+
assert_raises(NoMethodError) { proxy.awesomesauce }
|
32
|
+
assert_raises(NoMethodError) { proxy.ninja_party }
|
33
|
+
assert_equal "awesome", real.to_s
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
@@ -0,0 +1,212 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "test_helper")
|
2
|
+
|
3
|
+
class DispatchableTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
class ExampleDispatcher
|
6
|
+
include Perennial::Dispatchable
|
7
|
+
end
|
8
|
+
|
9
|
+
class ExampleHandlerA
|
10
|
+
|
11
|
+
attr_accessor :messages
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@messages = []
|
15
|
+
end
|
16
|
+
|
17
|
+
def handle(name, opts)
|
18
|
+
@messages << [name, opts]
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
class ExampleHandlerB < ExampleHandlerA; end
|
24
|
+
|
25
|
+
class ExampleHandlerC
|
26
|
+
attr_reader :max_count, :messages, :call_stack
|
27
|
+
def initialize(d)
|
28
|
+
@messages = []
|
29
|
+
@max_count = 0
|
30
|
+
@current_count = 0
|
31
|
+
@dispatcher = d
|
32
|
+
@call_stack = []
|
33
|
+
end
|
34
|
+
|
35
|
+
def handle(name, opts)
|
36
|
+
@call_stack << "start-#{name}"
|
37
|
+
@current_count += 1
|
38
|
+
@messages << name
|
39
|
+
@dispatcher.dispatch(:b) if name == :a
|
40
|
+
@max_count = @current_count if @current_count > @max_count
|
41
|
+
@current_count -= 1
|
42
|
+
@call_stack << "end-#{name}"
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
class RegisterableHandler
|
48
|
+
|
49
|
+
def registered=(value)
|
50
|
+
@registered = value
|
51
|
+
end
|
52
|
+
|
53
|
+
def registered?
|
54
|
+
@registered ||= false
|
55
|
+
end
|
56
|
+
|
57
|
+
def handle(name, opts = {})
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
context 'marking a class as dispatchable' do
|
63
|
+
|
64
|
+
setup do
|
65
|
+
@dispatcher = ExampleDispatcher.new
|
66
|
+
end
|
67
|
+
|
68
|
+
should 'define a dispatch method' do
|
69
|
+
assert @dispatcher.respond_to?(:dispatch)
|
70
|
+
end
|
71
|
+
|
72
|
+
should 'require atleast a name for dispatch' do
|
73
|
+
assert_equal -2, @dispatcher.method(:dispatch).arity
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
context 'when registering handlers' do
|
79
|
+
|
80
|
+
setup do
|
81
|
+
@dispatcher = test_class_for(Perennial::Dispatchable)
|
82
|
+
end
|
83
|
+
|
84
|
+
should 'append a handler using register_handler' do
|
85
|
+
assert_equal [], @dispatcher.handlers
|
86
|
+
@dispatcher.register_handler(handler = ExampleHandlerA.new)
|
87
|
+
assert_equal [handler], @dispatcher.handlers
|
88
|
+
end
|
89
|
+
|
90
|
+
should 'batch assign handlers on handlers= using register_handler' do
|
91
|
+
handlers = [ExampleHandlerA.new, ExampleHandlerB.new]
|
92
|
+
assert_equal [], @dispatcher.handlers
|
93
|
+
@dispatcher.handlers = handlers
|
94
|
+
assert_equal handlers, @dispatcher.handlers
|
95
|
+
end
|
96
|
+
|
97
|
+
should 'return all handlers via the handlers class method' do
|
98
|
+
handlers = [ExampleHandlerA.new, ExampleHandlerB.new]
|
99
|
+
@dispatcher.handlers = handlers
|
100
|
+
assert_equal handlers, @dispatcher.handlers
|
101
|
+
end
|
102
|
+
|
103
|
+
should 'make handlers available to myself and all subclasses' do
|
104
|
+
# Set A
|
105
|
+
dispatcher_a = class_via(@dispatcher)
|
106
|
+
dispatcher_a.register_handler(handler_a = ExampleHandlerA.new)
|
107
|
+
# Set B
|
108
|
+
dispatcher_b = class_via(dispatcher_a)
|
109
|
+
dispatcher_b.register_handler(handler_b = ExampleHandlerA.new)
|
110
|
+
# Set C
|
111
|
+
dispatcher_c = class_via(dispatcher_b)
|
112
|
+
dispatcher_c.register_handler(handler_c = ExampleHandlerA.new)
|
113
|
+
# Set D
|
114
|
+
dispatcher_d = class_via(dispatcher_a)
|
115
|
+
dispatcher_d.register_handler(handler_d = ExampleHandlerB.new)
|
116
|
+
# Actual Assertions
|
117
|
+
assert_equal [], @dispatcher.handlers
|
118
|
+
assert_equal [handler_a], dispatcher_a.handlers
|
119
|
+
assert_equal [handler_a, handler_b], dispatcher_b.handlers
|
120
|
+
assert_equal [handler_a, handler_b, handler_c], dispatcher_c.handlers
|
121
|
+
assert_equal [handler_a, handler_d], dispatcher_d.handlers
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
125
|
+
|
126
|
+
context 'dispatching events' do
|
127
|
+
|
128
|
+
setup do
|
129
|
+
@dispatcher = class_via(ExampleDispatcher).new
|
130
|
+
@handler = ExampleHandlerA.new
|
131
|
+
@dispatcher.class.register_handler @handler
|
132
|
+
end
|
133
|
+
|
134
|
+
should 'attempt to call handle_[event_name] on itself' do
|
135
|
+
mock(@dispatcher).respond_to?(:handle_sample_event) { true }
|
136
|
+
mock(@dispatcher).handle_sample_event(:awesome => true, :sauce => 2)
|
137
|
+
@dispatcher.dispatch :sample_event, :awesome => true, :sauce => 2
|
138
|
+
end
|
139
|
+
|
140
|
+
should 'attempt to call handle_[event_name] on each handler' do
|
141
|
+
mock(@handler).respond_to?(:handle_sample_event) { true }
|
142
|
+
mock(@handler).handle_sample_event(:awesome => true, :sauce => 2)
|
143
|
+
@dispatcher.dispatch :sample_event, :awesome => true, :sauce => 2
|
144
|
+
end
|
145
|
+
|
146
|
+
should 'call handle on each handler if handle_[event_name] isn\'t defined' do
|
147
|
+
mock(@handler).respond_to?(:handle_sample_event) { false }
|
148
|
+
mock(@handler).handle(:sample_event, :awesome => true, :sauce => 2)
|
149
|
+
@dispatcher.dispatch :sample_event, :awesome => true, :sauce => 2
|
150
|
+
end
|
151
|
+
|
152
|
+
should 'let you halt handler processing if you raise HaltHandlerProcessing' do
|
153
|
+
handler_two = ExampleHandlerB.new
|
154
|
+
@dispatcher.class.register_handler handler_two
|
155
|
+
mock(@handler).handle(:sample_event, :awesome => true, :sauce => 2) do
|
156
|
+
raise Perennial::HaltHandlerProcessing
|
157
|
+
end
|
158
|
+
dont_allow(handler_two).handle(:sample_event, :awesome => true, :sauce => 2)
|
159
|
+
@dispatcher.dispatch :sample_event, :awesome => true, :sauce => 2
|
160
|
+
end
|
161
|
+
|
162
|
+
should 'log exceptions when encountered and not crash'
|
163
|
+
|
164
|
+
end
|
165
|
+
|
166
|
+
context 'dispatching nested events' do
|
167
|
+
|
168
|
+
setup do
|
169
|
+
@dispatcher = class_via(ExampleDispatcher).new
|
170
|
+
@handler = ExampleHandlerC.new(@dispatcher)
|
171
|
+
@dispatcher.class.register_handler @handler
|
172
|
+
@dispatcher.dispatch :a
|
173
|
+
end
|
174
|
+
|
175
|
+
should 'call them in the correct order' do
|
176
|
+
assert_equal [:a, :b], @handler.messages
|
177
|
+
end
|
178
|
+
|
179
|
+
should 'only call 1 dispatch at a time' do
|
180
|
+
assert_equal 1, @handler.max_count
|
181
|
+
end
|
182
|
+
|
183
|
+
should 'finish a before dispatching b' do
|
184
|
+
assert_equal ["start-a", "end-a", "start-b", "end-b"], @handler.call_stack
|
185
|
+
end
|
186
|
+
|
187
|
+
end
|
188
|
+
|
189
|
+
context 'registering handlers' do
|
190
|
+
|
191
|
+
setup do
|
192
|
+
@dispatcher = class_via(ExampleDispatcher).new
|
193
|
+
@handler = class_via(RegisterableHandler).new
|
194
|
+
end
|
195
|
+
|
196
|
+
should 'default to not being registered' do
|
197
|
+
assert !@handler.registered?
|
198
|
+
end
|
199
|
+
|
200
|
+
should 'set registered on register_handler' do
|
201
|
+
@dispatcher.class.register_handler @handler
|
202
|
+
assert @handler.registered?
|
203
|
+
end
|
204
|
+
|
205
|
+
should 'call registered= on the handler' do
|
206
|
+
mock(@handler).registered = true
|
207
|
+
@dispatcher.class.register_handler @handler
|
208
|
+
end
|
209
|
+
|
210
|
+
end
|
211
|
+
|
212
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "test_helper")
|
2
|
+
|
3
|
+
class HookableTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
context 'Hookable Classes' do
|
6
|
+
|
7
|
+
setup do
|
8
|
+
@hookable_class = test_class_for(Perennial::Hookable)
|
9
|
+
end
|
10
|
+
|
11
|
+
should 'let you append hooks via append_hook' do
|
12
|
+
assert_equal [], @hookable_class.hooks_for(:awesome)
|
13
|
+
@hookable_class.append_hook(:awesome) { puts "Hello!" }
|
14
|
+
assert_equal 1, @hookable_class.hooks_for(:awesome).size
|
15
|
+
end
|
16
|
+
|
17
|
+
should 'only append hooks if they aren\'t blank' do
|
18
|
+
@hookable_class.append_hook(:awesome)
|
19
|
+
assert_equal [], @hookable_class.hooks_for(:awesome)
|
20
|
+
end
|
21
|
+
|
22
|
+
should 'let you get an array of hooks' do
|
23
|
+
@hookable_class.append_hook(:awesome) { puts "A" }
|
24
|
+
@hookable_class.append_hook(:awesome) { puts "B" }
|
25
|
+
assert_equal 2, @hookable_class.hooks_for(:awesome).size
|
26
|
+
end
|
27
|
+
|
28
|
+
should 'let you invoke hooks' do
|
29
|
+
items = []
|
30
|
+
@hookable_class.append_hook(:awesome) { items << :a }
|
31
|
+
@hookable_class.append_hook(:awesome) { items << :b }
|
32
|
+
@hookable_class.append_hook(:awesome) { items << :c }
|
33
|
+
@hookable_class.invoke_hooks!(:awesome)
|
34
|
+
assert_equal [:a, :b, :c], items
|
35
|
+
end
|
36
|
+
|
37
|
+
should 'call them in the order they are appended' do
|
38
|
+
items = []
|
39
|
+
@hookable_class.append_hook(:awesome) { items << :a }
|
40
|
+
@hookable_class.append_hook(:awesome) { items << :b }
|
41
|
+
@hookable_class.append_hook(:awesome) { items << :c }
|
42
|
+
@hookable_class.invoke_hooks!(:awesome)
|
43
|
+
[:a, :b, :c].each_with_index do |value, index|
|
44
|
+
assert_equal value, items[index]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
should 'let you define hook accessors' do
|
49
|
+
assert_equal [], @hookable_class.hooks_for(:awesome)
|
50
|
+
assert !@hookable_class.respond_to?(:awesome)
|
51
|
+
assert !@hookable_class.respond_to?(:sauce)
|
52
|
+
@hookable_class.define_hook :awesome, :sauce
|
53
|
+
assert @hookable_class.respond_to?(:awesome)
|
54
|
+
assert @hookable_class.respond_to?(:sauce)
|
55
|
+
@hookable_class.awesome { puts "A" }
|
56
|
+
assert_equal 1, @hookable_class.hooks_for(:awesome).size
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
data/test/loader_test.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "test_helper")
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "test_helper")
|
2
|
+
|
3
|
+
class LoggableTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
class ExampleLoggable
|
6
|
+
include Perennial::Loggable
|
7
|
+
end
|
8
|
+
|
9
|
+
context "Defining a class as loggable" do
|
10
|
+
|
11
|
+
setup do
|
12
|
+
@example = ExampleLoggable.new
|
13
|
+
end
|
14
|
+
|
15
|
+
should 'define a logger instance method' do
|
16
|
+
assert @example.respond_to?(:logger)
|
17
|
+
end
|
18
|
+
|
19
|
+
should 'define a logger class method' do
|
20
|
+
assert ExampleLoggable.respond_to?(:logger)
|
21
|
+
end
|
22
|
+
|
23
|
+
should 'not define a logger= instance method' do
|
24
|
+
assert !@example.respond_to?(:logger=)
|
25
|
+
end
|
26
|
+
|
27
|
+
should 'not define a logger= class method' do
|
28
|
+
assert !ExampleLoggable.respond_to?(:logger=)
|
29
|
+
end
|
30
|
+
|
31
|
+
should 'define logger to be an instance of Perennial::Logger' do
|
32
|
+
assert_equal Perennial::Logger, ExampleLoggable.logger
|
33
|
+
assert_equal Perennial::Logger, @example.logger
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
data/test/logger_test.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "test_helper")
|
2
|
+
|
3
|
+
class LoggerTest < Test::Unit::TestCase
|
4
|
+
context 'logger tests' do
|
5
|
+
|
6
|
+
setup do
|
7
|
+
@root_path = Perennial::Settings.root / "log"
|
8
|
+
Perennial::Logger.log_name = "example.log"
|
9
|
+
FileUtils.mkdir_p @root_path
|
10
|
+
end
|
11
|
+
|
12
|
+
context 'setting up a logger' do
|
13
|
+
|
14
|
+
setup { Perennial::Logger.setup! }
|
15
|
+
|
16
|
+
should 'create the log file file after writing' do
|
17
|
+
Perennial::Logger.fatal "Blergh."
|
18
|
+
assert File.exist?(@root_path / "example.log")
|
19
|
+
end
|
20
|
+
|
21
|
+
Perennial::Logger::LEVELS.each_key do |level_name|
|
22
|
+
should "define a method for the #{level_name} log level" do
|
23
|
+
assert Perennial::Logger.respond_to?(level_name)
|
24
|
+
assert Perennial::Logger.logger.respond_to?(level_name)
|
25
|
+
assert_equal 1, Perennial::Logger.logger.method(level_name).arity
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
should 'have a log exception method' do
|
30
|
+
assert Perennial::Logger.respond_to?(:log_exception)
|
31
|
+
assert Perennial::Logger.logger.respond_to?(:log_exception)
|
32
|
+
end
|
33
|
+
|
34
|
+
should 'let you configure a dir that logs are loaded from'
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
context 'writing to the log' do
|
39
|
+
|
40
|
+
Perennial::Logger::LEVELS.each_key do |level_name|
|
41
|
+
should "let you write to the #{level_name} log level" do
|
42
|
+
Perennial::Logger.verbose = false
|
43
|
+
Perennial::Logger.level = level_name
|
44
|
+
assert_nothing_raised do
|
45
|
+
Perennial::Logger.logger.send(level_name, "An Example Message No. 1")
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
teardown do
|
53
|
+
log_path = @root_path / "example.log"
|
54
|
+
FileUtils.rm_rf(log_path) if File.exist?(log_path)
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
data/test/proxy_test.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "test_helper")
|
2
|
+
|
3
|
+
class ProxyTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
context 'basic proxies' do
|
6
|
+
|
7
|
+
setup do
|
8
|
+
@proxy = Perennial::Proxy.new
|
9
|
+
@proxy.__proxy_target__ = :awesome
|
10
|
+
end
|
11
|
+
should 'pass through the correct class' do
|
12
|
+
assert_equal Symbol, @proxy.class
|
13
|
+
assert_kind_of Symbol, @proxy
|
14
|
+
end
|
15
|
+
|
16
|
+
should 'not interfere with equals' do
|
17
|
+
assert @proxy == :awesome
|
18
|
+
end
|
19
|
+
|
20
|
+
should 'pass through to_s' do
|
21
|
+
assert_equal "awesome", @proxy.to_s
|
22
|
+
end
|
23
|
+
|
24
|
+
should 'let you send to an object' do
|
25
|
+
assert_equal "awesome", @proxy.send(:to_s)
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "test_helper")
|
2
|
+
|
3
|
+
class SettingsTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
context 'default settings' do
|
6
|
+
|
7
|
+
setup do
|
8
|
+
Perennial::Settings.setup!
|
9
|
+
end
|
10
|
+
|
11
|
+
should "default the application root to the parent folder of perennial" do
|
12
|
+
assert_equal __FILE__.to_pathname.dirname.join("..").expand_path,
|
13
|
+
Perennial::Settings.root.to_pathname
|
14
|
+
Perennial::Settings.root = "/awesome/sauce"
|
15
|
+
assert_equal "/awesome/sauce", Perennial::Settings.root
|
16
|
+
end
|
17
|
+
|
18
|
+
should "default daemonized to false" do
|
19
|
+
assert !Perennial::Settings.daemon?
|
20
|
+
Perennial::Settings.daemon = true
|
21
|
+
assert Perennial::Settings.daemon?
|
22
|
+
Perennial::Settings.daemon = false
|
23
|
+
assert !Perennial::Settings.daemon?
|
24
|
+
end
|
25
|
+
|
26
|
+
should "default the log level to :info" do
|
27
|
+
assert_equal :info, Perennial::Settings.log_level
|
28
|
+
Perennial::Settings.log_level = :debug
|
29
|
+
assert_equal :debug, Perennial::Settings.log_level
|
30
|
+
end
|
31
|
+
|
32
|
+
should "default verbose to false" do
|
33
|
+
assert !Perennial::Settings.verbose?
|
34
|
+
Perennial::Settings.verbose = true
|
35
|
+
assert Perennial::Settings.verbose?
|
36
|
+
Perennial::Settings.verbose = false
|
37
|
+
assert !Perennial::Settings.verbose?
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
context 'loading settings' do
|
43
|
+
|
44
|
+
setup do
|
45
|
+
config_folder = Perennial::Settings.root / "config"
|
46
|
+
@default_settings = {
|
47
|
+
"default" => {
|
48
|
+
"introduction" => true,
|
49
|
+
"description" => "Ninjas are Totally Awesome",
|
50
|
+
"channel" => "#offrails",
|
51
|
+
"users" => ["Sutto", "njero", "zapnap"]
|
52
|
+
}
|
53
|
+
}
|
54
|
+
FileUtils.mkdir_p(config_folder)
|
55
|
+
File.open(config_folder / "settings.yml", "w+") do |file|
|
56
|
+
file.write(@default_settings.to_yaml)
|
57
|
+
end
|
58
|
+
Perennial::Settings.setup!
|
59
|
+
end
|
60
|
+
|
61
|
+
should 'load settings from the file' do
|
62
|
+
assert Perennial::Settings.setup?
|
63
|
+
assert_equal @default_settings["default"].symbolize_keys, Perennial::Settings.to_hash
|
64
|
+
end
|
65
|
+
|
66
|
+
should 'define readers for the settings' do
|
67
|
+
instance = Perennial::Settings.new
|
68
|
+
@default_settings["default"].each_pair do |key, value|
|
69
|
+
assert Perennial::Settings.respond_to?(key.to_sym)
|
70
|
+
assert_equal value, Perennial::Settings.send(key)
|
71
|
+
assert instance.respond_to?(key.to_sym)
|
72
|
+
assert_equal value, instance.send(key)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
should 'let you access settings via hash-style accessors' do
|
77
|
+
@default_settings["default"].each_pair do |key, value|
|
78
|
+
assert_equal value, Perennial::Settings[key]
|
79
|
+
Perennial::Settings[key] = "a-new-value from #{value.inspect}"
|
80
|
+
assert_equal "a-new-value from #{value.inspect}", Perennial::Settings[key]
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
should 'define writers for the settings' do
|
85
|
+
instance = Perennial::Settings.new
|
86
|
+
@default_settings["default"].each_pair do |key, value|
|
87
|
+
setter = :"#{key}="
|
88
|
+
assert Perennial::Settings.respond_to?(setter)
|
89
|
+
Perennial::Settings.send(setter, "value #{value.inspect} on class")
|
90
|
+
assert_equal "value #{value.inspect} on class", Perennial::Settings.send(key)
|
91
|
+
assert instance.respond_to?(setter)
|
92
|
+
instance.send(setter, "value #{value.inspect} on instance")
|
93
|
+
assert_equal "value #{value.inspect} on instance", instance.send(key)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
should 'let you configure the lookup key path'
|
98
|
+
|
99
|
+
should 'let you configure the file settings are loaded from'
|
100
|
+
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
|
3
|
+
# Testing dependencies
|
4
|
+
require 'test/unit'
|
5
|
+
require 'shoulda'
|
6
|
+
require 'rr'
|
7
|
+
# RedGreen doesn't seem to be needed under 1.9
|
8
|
+
require 'redgreen' if RUBY_VERSION < "1.9"
|
9
|
+
|
10
|
+
require 'pathname'
|
11
|
+
root_directory = Pathname.new(__FILE__).dirname.join("..").expand_path
|
12
|
+
require root_directory.join("lib", "perennial")
|
13
|
+
require root_directory.join("vendor", "fakefs", "lib", "fakefs")
|
14
|
+
|
15
|
+
class Test::Unit::TestCase
|
16
|
+
include RR::Adapters::TestUnit
|
17
|
+
|
18
|
+
protected
|
19
|
+
|
20
|
+
# Short hand for creating a class with
|
21
|
+
# a given class_eval block.
|
22
|
+
def class_via(*args, &blk)
|
23
|
+
klass = Class.new(*args)
|
24
|
+
klass.class_eval(&blk) unless blk.blank?
|
25
|
+
return klass
|
26
|
+
end
|
27
|
+
|
28
|
+
# Short hand for creating a test class
|
29
|
+
# for a set of mixins - give it the modules
|
30
|
+
# and it will include them all.
|
31
|
+
def test_class_for(*mods, &blk)
|
32
|
+
klass = Class.new
|
33
|
+
klass.class_eval { include(*mods) }
|
34
|
+
klass.class_eval(&blk) unless blk.blank?
|
35
|
+
return klass
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: perennial
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Darcy Laycock
|
@@ -9,35 +9,56 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-
|
12
|
+
date: 2009-10-05 00:00:00 +08:00
|
13
13
|
default_executable: perennial
|
14
|
-
dependencies:
|
15
|
-
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: thoughtbot-shoulda
|
17
|
+
type: :development
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: yard
|
27
|
+
type: :development
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: "0"
|
34
|
+
version:
|
16
35
|
description: Perennial is a platform for building different applications in Ruby. It uses a controller-based approach with mixins to provide different functionality.
|
17
36
|
email: sutto@sutto.net
|
18
37
|
executables:
|
19
38
|
- perennial
|
20
39
|
extensions: []
|
21
40
|
|
22
|
-
extra_rdoc_files:
|
23
|
-
|
41
|
+
extra_rdoc_files:
|
42
|
+
- README.md
|
24
43
|
files:
|
25
44
|
- bin/perennial
|
45
|
+
- lib/perennial.rb
|
26
46
|
- lib/perennial/application.rb
|
27
47
|
- lib/perennial/argument_parser.rb
|
48
|
+
- lib/perennial/core_ext.rb
|
28
49
|
- lib/perennial/core_ext/attribute_accessors.rb
|
29
50
|
- lib/perennial/core_ext/blank.rb
|
30
51
|
- lib/perennial/core_ext/hash_key_conversions.rb
|
31
52
|
- lib/perennial/core_ext/inflections.rb
|
32
53
|
- lib/perennial/core_ext/misc.rb
|
33
54
|
- lib/perennial/core_ext/proxy.rb
|
34
|
-
- lib/perennial/core_ext.rb
|
35
55
|
- lib/perennial/daemon.rb
|
36
56
|
- lib/perennial/delegateable.rb
|
37
57
|
- lib/perennial/dispatchable.rb
|
38
58
|
- lib/perennial/exceptions.rb
|
39
59
|
- lib/perennial/generator.rb
|
40
60
|
- lib/perennial/hookable.rb
|
61
|
+
- lib/perennial/jeweler_ext.rb
|
41
62
|
- lib/perennial/loader.rb
|
42
63
|
- lib/perennial/loggable.rb
|
43
64
|
- lib/perennial/logger.rb
|
@@ -46,20 +67,20 @@ files:
|
|
46
67
|
- lib/perennial/option_parser.rb
|
47
68
|
- lib/perennial/reloading.rb
|
48
69
|
- lib/perennial/settings.rb
|
49
|
-
- lib/perennial.rb
|
50
70
|
- templates/application.erb
|
51
71
|
- templates/boot.erb
|
52
72
|
- templates/rakefile.erb
|
53
73
|
- templates/setup.erb
|
54
74
|
- templates/test.erb
|
55
75
|
- templates/test_helper.erb
|
56
|
-
|
76
|
+
- README.md
|
77
|
+
has_rdoc: true
|
57
78
|
homepage: http://sutto.net/
|
58
79
|
licenses: []
|
59
80
|
|
60
81
|
post_install_message:
|
61
|
-
rdoc_options:
|
62
|
-
|
82
|
+
rdoc_options:
|
83
|
+
- --charset=UTF-8
|
63
84
|
require_paths:
|
64
85
|
- lib
|
65
86
|
required_ruby_version: !ruby/object:Gem::Requirement
|
@@ -77,9 +98,17 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
77
98
|
requirements: []
|
78
99
|
|
79
100
|
rubyforge_project:
|
80
|
-
rubygems_version: 1.3.
|
101
|
+
rubygems_version: 1.3.5
|
81
102
|
signing_key:
|
82
103
|
specification_version: 3
|
83
104
|
summary: A simple (generally event-oriented) application library for Ruby
|
84
|
-
test_files:
|
85
|
-
|
105
|
+
test_files:
|
106
|
+
- test/delegateable_test.rb
|
107
|
+
- test/dispatchable_test.rb
|
108
|
+
- test/hookable_test.rb
|
109
|
+
- test/loader_test.rb
|
110
|
+
- test/loggable_test.rb
|
111
|
+
- test/logger_test.rb
|
112
|
+
- test/proxy_test.rb
|
113
|
+
- test/settings_test.rb
|
114
|
+
- test/test_helper.rb
|