perennial 1.0.1 → 1.0.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.
- data/lib/perennial/core_ext/ansi_formatter.rb +142 -0
- data/lib/perennial/core_ext/hash_key_conversions.rb +10 -6
- data/lib/perennial/core_ext.rb +2 -1
- data/lib/perennial/daemon.rb +34 -16
- data/lib/perennial/dispatchable.rb +0 -1
- data/lib/perennial/generator.rb +18 -10
- data/lib/perennial/loader.rb +1 -1
- data/lib/perennial/logger.rb +17 -14
- data/lib/perennial/reloading.rb +2 -1
- data/lib/perennial/settings.rb +1 -0
- data/lib/perennial.rb +1 -1
- data/test/argument_parser_test.rb +56 -0
- data/test/delegateable_test.rb +2 -2
- data/test/dispatchable_test.rb +10 -12
- data/test/generator_test.rb +94 -0
- data/test/logger_test.rb +38 -39
- data/test/option_parser_test.rb +52 -0
- data/test/reloading_test.rb +75 -0
- data/test/settings_test.rb +78 -78
- data/test/test_helper.rb +12 -3
- metadata +17 -2
@@ -0,0 +1,142 @@
|
|
1
|
+
module Perennial
|
2
|
+
class ANSIFormatter
|
3
|
+
|
4
|
+
TAGS = {
|
5
|
+
:bold => 1,
|
6
|
+
:italics => 3,
|
7
|
+
:underline => 4,
|
8
|
+
:inverse => 7,
|
9
|
+
:strikethrough => 9
|
10
|
+
}
|
11
|
+
|
12
|
+
COLOURS = {
|
13
|
+
:black => 30,
|
14
|
+
:red => 31,
|
15
|
+
:green => 32,
|
16
|
+
:yellow => 33,
|
17
|
+
:blue => 34,
|
18
|
+
:magenta => 35,
|
19
|
+
:cyan => 36,
|
20
|
+
:white => 37
|
21
|
+
}
|
22
|
+
|
23
|
+
BACKGROUND_COLOURS = {
|
24
|
+
:bg_black => 40,
|
25
|
+
:bg_red => 41,
|
26
|
+
:bg_green => 42,
|
27
|
+
:bg_yellow => 43,
|
28
|
+
:bg_blue => 44,
|
29
|
+
:bg_magenta => 45,
|
30
|
+
:bg_cyan => 46,
|
31
|
+
:bg_white => 47
|
32
|
+
}
|
33
|
+
|
34
|
+
|
35
|
+
UNDO_TAGS = {
|
36
|
+
:all => 0,
|
37
|
+
:bold => 22,
|
38
|
+
:italics => 23,
|
39
|
+
:underline => 24,
|
40
|
+
:inverse => 27,
|
41
|
+
:strikethrough => 29,
|
42
|
+
:colour => 39,
|
43
|
+
:bg_colour => 49
|
44
|
+
}
|
45
|
+
|
46
|
+
MATCH_REGEXP = /\<f\:(.+)\>(.*)\<\/f\:\1>/
|
47
|
+
ESCAPE_TAG = /\<\/?f\:escape\>/
|
48
|
+
ANSI_CODE = /\033\[(?:\d+\;?)+m/
|
49
|
+
|
50
|
+
cattr_accessor :formatted
|
51
|
+
@@formatted = true
|
52
|
+
|
53
|
+
def initialize(string)
|
54
|
+
@string = string
|
55
|
+
end
|
56
|
+
|
57
|
+
def to_s
|
58
|
+
@@formatted ? to_formatted_s : to_normal_s
|
59
|
+
end
|
60
|
+
|
61
|
+
def to_formatted_s
|
62
|
+
format_tags
|
63
|
+
end
|
64
|
+
|
65
|
+
def to_normal_s
|
66
|
+
remove_tags
|
67
|
+
end
|
68
|
+
|
69
|
+
class << self
|
70
|
+
|
71
|
+
def format(tag, text)
|
72
|
+
tag = tag.to_s.gsub(/[\-\:]/, "_").to_sym
|
73
|
+
"\033[#{lookup_tag(tag)}m#{text}\033[#{lookup_end_tag(tag)}m"
|
74
|
+
end
|
75
|
+
|
76
|
+
def lookup_tag(tag)
|
77
|
+
TAGS[tag] || COLOURS[tag] || BACKGROUND_COLOURS[tag]
|
78
|
+
end
|
79
|
+
|
80
|
+
def lookup_end_tag(tag)
|
81
|
+
UNDO_TAGS[lookup_tag_type(tag)] || UNDO_TAGS[:all]
|
82
|
+
end
|
83
|
+
|
84
|
+
def lookup_tag_type(tag)
|
85
|
+
if COLOURS.has_key?(tag)
|
86
|
+
tag = :colour
|
87
|
+
elsif BACKGROUND_COLOURS.has_key?(tag)
|
88
|
+
tag = :bg_colour
|
89
|
+
end
|
90
|
+
tag
|
91
|
+
end
|
92
|
+
|
93
|
+
def clean(text)
|
94
|
+
text = text.gsub(ESCAPE_TAG, "") while text =~ ESCAPE_TAG
|
95
|
+
text = text.gsub(ANSI_CODE, "") while text =~ ANSI_CODE
|
96
|
+
text
|
97
|
+
end
|
98
|
+
|
99
|
+
def process(string)
|
100
|
+
new(string).to_s
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
|
105
|
+
protected
|
106
|
+
|
107
|
+
def format_tags(string = @string, tag_stack = nil, mapping = nil)
|
108
|
+
tag_stack ||= []
|
109
|
+
mapping ||= Hash.new { |h,k| h[k] = [] }
|
110
|
+
string.gsub(MATCH_REGEXP) do
|
111
|
+
inner_text = $2
|
112
|
+
tag = $1.to_s.gsub(/[\-\:]/, "_").to_sym
|
113
|
+
type = self.class.lookup_tag_type(tag)
|
114
|
+
start_tag = self.class.lookup_tag(tag)
|
115
|
+
end_tag = UNDO_TAGS[type]
|
116
|
+
if tag == :escape
|
117
|
+
inner_text
|
118
|
+
elsif end_tag.present?
|
119
|
+
mapping[type] << start_tag
|
120
|
+
text = "\033[#{start_tag}m#{format_tags(inner_text, (tag_stack + [:type]), mapping)}\033[#{end_tag}m"
|
121
|
+
mapping[type].pop
|
122
|
+
text << "\033[#{mapping[type].join(";")}m"
|
123
|
+
else
|
124
|
+
format_tags(inner_text, tag_stack, mapping)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def remove_tags(string = @string)
|
130
|
+
string.gsub(MATCH_REGEXP) { remove_tags($2) }
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
class String
|
137
|
+
|
138
|
+
def to_ansi
|
139
|
+
Perennial::ANSIFormatter.process(self)
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
@@ -6,15 +6,11 @@ class Hash
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def symbolize_keys!
|
9
|
-
|
10
|
-
self.each_pair { |k,v| hash[k.to_sym] = v }
|
11
|
-
replace hash
|
9
|
+
convert_keys_via :to_sym
|
12
10
|
end
|
13
11
|
|
14
12
|
def stringify_keys!
|
15
|
-
|
16
|
-
self.each_pair { |k, v| hash[k.to_s] = v }
|
17
|
-
replace hash
|
13
|
+
convert_keys_via :to_s
|
18
14
|
end
|
19
15
|
|
20
16
|
def stringify_keys
|
@@ -23,4 +19,12 @@ class Hash
|
|
23
19
|
return hash
|
24
20
|
end
|
25
21
|
|
22
|
+
protected
|
23
|
+
|
24
|
+
def convert_keys_via(method)
|
25
|
+
hash = {}
|
26
|
+
self.each_pair { |k,v| hash[k.send(method)] = v }
|
27
|
+
replace hash
|
28
|
+
end
|
29
|
+
|
26
30
|
end
|
data/lib/perennial/core_ext.rb
CHANGED
@@ -4,4 +4,5 @@ require 'perennial/core_ext/blank'
|
|
4
4
|
require 'perennial/core_ext/misc'
|
5
5
|
require 'perennial/core_ext/hash_key_conversions'
|
6
6
|
require 'perennial/core_ext/inflections'
|
7
|
-
require 'perennial/core_ext/proxy'
|
7
|
+
require 'perennial/core_ext/proxy'
|
8
|
+
require 'perennial/core_ext/ansi_formatter'
|
data/lib/perennial/daemon.rb
CHANGED
@@ -33,6 +33,8 @@ module Perennial
|
|
33
33
|
# used to deal with checking the status of and killing
|
34
34
|
# processes associated with a given loader type.
|
35
35
|
class Daemon
|
36
|
+
include Perennial::Loggable
|
37
|
+
|
36
38
|
class << self
|
37
39
|
|
38
40
|
def any_alive?(type = :all)
|
@@ -42,9 +44,12 @@ module Perennial
|
|
42
44
|
# Returns true / false depending on whether
|
43
45
|
# a process with the given pid exists.
|
44
46
|
def alive?(pid)
|
45
|
-
|
47
|
+
Process.kill(0, pid)
|
48
|
+
return true
|
46
49
|
rescue Errno::ESRCH
|
47
50
|
return false
|
51
|
+
rescue SystemCallError => e
|
52
|
+
return true
|
48
53
|
end
|
49
54
|
|
50
55
|
# Kills all processes associated with a certain app type.
|
@@ -61,16 +66,20 @@ module Perennial
|
|
61
66
|
# Converts the current process into a Unix-daemon using
|
62
67
|
# the double fork approach. Also, changes process file
|
63
68
|
# mask to 000 and reopens STDIN / OUT to /dev/null
|
64
|
-
def daemonize!
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
69
|
+
def daemonize!(should_exit = true)
|
70
|
+
if detached_fork { exit if should_exit }
|
71
|
+
Process.setsid
|
72
|
+
detached_fork { exit }
|
73
|
+
reinitialize_stdio
|
74
|
+
yield if block_given?
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def daemonize_current_type!
|
79
|
+
daemonize! do
|
80
|
+
write_pid
|
81
|
+
Perennial::Settings.verbose = false
|
82
|
+
end
|
74
83
|
end
|
75
84
|
|
76
85
|
# Cleans up processes for the current application type
|
@@ -87,12 +96,19 @@ module Perennial
|
|
87
96
|
|
88
97
|
protected
|
89
98
|
|
99
|
+
def reinitialize_stdio
|
100
|
+
File.umask 0000
|
101
|
+
STDIN.reopen "/dev/null"
|
102
|
+
STDOUT.reopen "/dev/null", "a"
|
103
|
+
STDERR.reopen STDOUT
|
104
|
+
end
|
105
|
+
|
90
106
|
def kill_all_from(file)
|
91
107
|
pids = pids_from(file)
|
92
108
|
pids.each { |p| Process.kill("TERM", p) unless p == Process.pid }
|
93
109
|
FileUtils.rm_f(file)
|
94
110
|
rescue => e
|
95
|
-
|
111
|
+
Perennial::Logger.log_exception(e)
|
96
112
|
end
|
97
113
|
|
98
114
|
def pid_file_for(type)
|
@@ -105,7 +121,7 @@ module Perennial
|
|
105
121
|
Dir[files].each do |file|
|
106
122
|
pids += File.read(file).split("\n").map { |l| l.strip.to_i(10) }
|
107
123
|
end
|
108
|
-
|
124
|
+
pids.uniq.select { |p| alive?(p) }
|
109
125
|
end
|
110
126
|
|
111
127
|
def write_pid
|
@@ -118,11 +134,13 @@ module Perennial
|
|
118
134
|
File.open(f, "w+") { |f| f.puts pids.join("\n") }
|
119
135
|
end
|
120
136
|
|
121
|
-
def
|
122
|
-
|
137
|
+
def detached_fork
|
138
|
+
pid = fork
|
139
|
+
unless pid.nil?
|
123
140
|
Process.detach(pid)
|
124
|
-
|
141
|
+
yield if block_given?
|
125
142
|
end
|
143
|
+
pid
|
126
144
|
end
|
127
145
|
|
128
146
|
end
|
@@ -59,7 +59,6 @@ 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
62
|
pre_dispatching
|
64
63
|
begin
|
65
64
|
# The full handler name is the method we call given it exists.
|
data/lib/perennial/generator.rb
CHANGED
@@ -6,6 +6,22 @@ require 'readline'
|
|
6
6
|
module Perennial
|
7
7
|
class Generator
|
8
8
|
|
9
|
+
class HashBinding
|
10
|
+
|
11
|
+
def initialize(hash)
|
12
|
+
hash.each_pair { |k, v| instance_variable_set("@#{k}", v) }
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_binding
|
16
|
+
return binding()
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.for(h)
|
20
|
+
new(h).to_binding
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
9
25
|
class CommandEnv < Perennial::Application::CommandEnv
|
10
26
|
|
11
27
|
def initialize
|
@@ -93,21 +109,13 @@ module Perennial
|
|
93
109
|
|
94
110
|
def template(source, destination, environment = {}, append = false)
|
95
111
|
describe "Processing template #{source}"
|
96
|
-
raw_template = File.read(expand_template_path(source))
|
97
|
-
processed_template = ERB.new(raw_template).result(
|
112
|
+
raw_template = File.read(expand_template_path(source.to_s))
|
113
|
+
processed_template = ERB.new(raw_template).result(HashBinding.for(environment))
|
98
114
|
file destination, processed_template, append
|
99
115
|
end
|
100
116
|
|
101
117
|
protected
|
102
118
|
|
103
|
-
def binding_for(hash = {})
|
104
|
-
object = Object.new
|
105
|
-
hash.each_pair do |k, v|
|
106
|
-
object.instance_variable_set("@#{k}", v)
|
107
|
-
end
|
108
|
-
return object.send(:binding)
|
109
|
-
end
|
110
|
-
|
111
119
|
def expand_template_path(p)
|
112
120
|
File.expand_path(p, @template_path)
|
113
121
|
end
|
data/lib/perennial/loader.rb
CHANGED
@@ -33,7 +33,7 @@ module Perennial
|
|
33
33
|
def run!(options = {})
|
34
34
|
self.register_signals
|
35
35
|
self.class.invoke_hooks! :before_setup
|
36
|
-
Daemon.
|
36
|
+
Daemon.daemonize_current_type! if Settings.daemon?
|
37
37
|
Logger.log_name = "#{@@current_type.to_s}.log"
|
38
38
|
Logger.setup
|
39
39
|
Settings.setup
|
data/lib/perennial/logger.rb
CHANGED
@@ -40,6 +40,11 @@ module Perennial
|
|
40
40
|
@@logger.send(name, *args, &blk)
|
41
41
|
end
|
42
42
|
|
43
|
+
def warn(message)
|
44
|
+
self.setup
|
45
|
+
@@logger.warn(message)
|
46
|
+
end
|
47
|
+
|
43
48
|
def respond_to?(symbol, include_private = false)
|
44
49
|
self.setup
|
45
50
|
super(symbol, include_private) || @@logger.respond_to?(symbol, include_private)
|
@@ -57,14 +62,14 @@ module Perennial
|
|
57
62
|
|
58
63
|
PREFIXES = {}
|
59
64
|
|
60
|
-
LEVELS.each { |k,v| PREFIXES[k] = "[#{k.to_s.upcase}]".
|
65
|
+
LEVELS.each { |k,v| PREFIXES[k] = "[#{k.to_s.upcase}]".rjust 7 }
|
61
66
|
|
62
67
|
COLOURS = {
|
63
|
-
:fatal =>
|
64
|
-
:error =>
|
65
|
-
:warn =>
|
66
|
-
:info =>
|
67
|
-
:debug =>
|
68
|
+
:fatal => "red",
|
69
|
+
:error => "yellow",
|
70
|
+
:warn => "magenta",
|
71
|
+
:info => "green",
|
72
|
+
:debug => "blue"
|
68
73
|
}
|
69
74
|
|
70
75
|
attr_accessor :level, :file, :verbose
|
@@ -74,6 +79,7 @@ module Perennial
|
|
74
79
|
@verbose = verbose
|
75
80
|
FileUtils.mkdir_p(File.dirname(path))
|
76
81
|
@file = File.open(path, "a+")
|
82
|
+
@file.sync = true if @file.respond_to?(:sync=)
|
77
83
|
end
|
78
84
|
|
79
85
|
def close!
|
@@ -82,7 +88,7 @@ module Perennial
|
|
82
88
|
|
83
89
|
LEVELS.each do |name, value|
|
84
90
|
define_method(name) do |message|
|
85
|
-
write(
|
91
|
+
write(message.to_s, name) if LEVELS[@level] <= value
|
86
92
|
end
|
87
93
|
|
88
94
|
define_method(:"#{name}?") do
|
@@ -105,13 +111,10 @@ module Perennial
|
|
105
111
|
private
|
106
112
|
|
107
113
|
def write(message, level = self.level)
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
def colourize(message, level)
|
114
|
-
"\033[1;#{COLOURS[level]}m#{message}\033[0m"
|
114
|
+
c = COLOURS[level]
|
115
|
+
message = ANSIFormatter.new("<f:#{c}>#{PREFIXES[level]}</f:#{c}> #{ANSIFormatter.clean(message)}")
|
116
|
+
@file.puts message.to_normal_s
|
117
|
+
$stdout.puts message.to_formatted_s if verbose?
|
115
118
|
end
|
116
119
|
|
117
120
|
|
data/lib/perennial/reloading.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
module Perennial
|
2
2
|
class Reloading
|
3
|
+
include Perennial::Loggable
|
3
4
|
|
4
5
|
cattr_accessor :mapping, :mtimes
|
5
6
|
self.mapping = {}
|
@@ -9,7 +10,7 @@ module Perennial
|
|
9
10
|
file = File.expand_path(file)
|
10
11
|
raise ArgumentError, "You must provide the path to a file" unless File.file?(file)
|
11
12
|
relative = file.gsub(/^#{File.expand_path(relative_to)}\//, '')
|
12
|
-
name = relative.gsub(/\.rb$/, '').split("/").map { |part|part.camelize }.join("::")
|
13
|
+
name = relative.gsub(/\.rb$/, '').split("/").map { |part| part.camelize }.join("::")
|
13
14
|
self.mapping[name] = file
|
14
15
|
self.mtimes[file] = File.mtime(file)
|
15
16
|
end
|
data/lib/perennial/settings.rb
CHANGED
@@ -79,6 +79,7 @@ module Perennial
|
|
79
79
|
def update!(attributes = {})
|
80
80
|
return if attributes.blank?
|
81
81
|
settings_file = self.default_settings_path
|
82
|
+
FileUtils.mkdir_p(File.dirname(settings_file))
|
82
83
|
settings = File.exist?(settings_file) ? YAML.load(File.read(settings_file)) : {}
|
83
84
|
namespaced_settings = lookup_settings_from(settings)
|
84
85
|
namespaced_settings.merge! attributes.stringify_keys
|
data/lib/perennial.rb
CHANGED
@@ -0,0 +1,56 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "test_helper")
|
2
|
+
|
3
|
+
require 'shellwords'
|
4
|
+
|
5
|
+
class ArgumentParserTest < Test::Unit::TestCase
|
6
|
+
|
7
|
+
context 'simple argument parsing' do
|
8
|
+
|
9
|
+
should 'correct parse arguments using no options' do
|
10
|
+
args, hash = parse("a b c")
|
11
|
+
assert_equal({}, hash)
|
12
|
+
assert_equal ["a", "b", "c"], args
|
13
|
+
end
|
14
|
+
|
15
|
+
should 'correct parse arguments using only short formats' do
|
16
|
+
args, hash = parse("-a -b=1 -c 3")
|
17
|
+
assert_equal [], args
|
18
|
+
assert_equal({"a" => true, "b" => "1", "c" => "3"}, hash)
|
19
|
+
end
|
20
|
+
|
21
|
+
should 'correct parse arguments using only long format' do
|
22
|
+
args, hash = parse("--ninjas --rockn=roll --awesome sauce")
|
23
|
+
assert_equal [], args
|
24
|
+
assert_equal({
|
25
|
+
"ninjas" => true,
|
26
|
+
"rockn" => "roll",
|
27
|
+
"awesome" => "sauce"
|
28
|
+
}, hash)
|
29
|
+
end
|
30
|
+
|
31
|
+
should 'correct parse arguments using mixed arguments' do
|
32
|
+
args, hash = parse("client --verbose --l=debug --user sutto another-arg")
|
33
|
+
assert_equal ["client", "another-arg"], args
|
34
|
+
assert_equal({
|
35
|
+
"verbose" => true,
|
36
|
+
"l" => "debug",
|
37
|
+
"user" => "sutto"
|
38
|
+
}, hash)
|
39
|
+
end
|
40
|
+
|
41
|
+
should 'correct differentiate between --value=a and --value b' do
|
42
|
+
args, hash = parse("--felafel value beat")
|
43
|
+
assert_equal ["beat"], args
|
44
|
+
assert_equal({"felafel" => "value"}, hash)
|
45
|
+
args, hash = parse("--felafel=value beat")
|
46
|
+
assert_equal ["beat"], args
|
47
|
+
assert_equal({"felafel" => "value"}, hash)
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
def parse(s)
|
53
|
+
Perennial::ArgumentParser.parse Shellwords.shellwords(s)
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
data/test/delegateable_test.rb
CHANGED
@@ -28,8 +28,8 @@ class DelegateableTest < Test::Unit::TestCase
|
|
28
28
|
should 'let you get the real target of the delegate' do
|
29
29
|
@delegateable.delegate_to :awesome
|
30
30
|
assert real = @delegateable.real_delegate
|
31
|
-
assert_raises(NoMethodError) {
|
32
|
-
assert_raises(NoMethodError) {
|
31
|
+
assert_raises(NoMethodError) { real.awesomesauce }
|
32
|
+
assert_raises(NoMethodError) { real.ninja_party }
|
33
33
|
assert_equal "awesome", real.to_s
|
34
34
|
end
|
35
35
|
|
data/test/dispatchable_test.rb
CHANGED
@@ -132,30 +132,28 @@ class DispatchableTest < Test::Unit::TestCase
|
|
132
132
|
end
|
133
133
|
|
134
134
|
should 'attempt to call handle_[event_name] on itself' do
|
135
|
-
|
136
|
-
|
135
|
+
@dispatcher.expects(:respond_to?).with(:handle_sample_event).returns(true)
|
136
|
+
@dispatcher.expects(:handle_sample_event).with(:awesome => true, :sauce => 2)
|
137
137
|
@dispatcher.dispatch :sample_event, :awesome => true, :sauce => 2
|
138
138
|
end
|
139
139
|
|
140
140
|
should 'attempt to call handle_[event_name] on each handler' do
|
141
|
-
|
142
|
-
|
141
|
+
@handler.expects(:respond_to?).with(:handle_sample_event).returns(true)
|
142
|
+
@handler.expects(:handle_sample_event).with(:awesome => true, :sauce => 2)
|
143
143
|
@dispatcher.dispatch :sample_event, :awesome => true, :sauce => 2
|
144
144
|
end
|
145
145
|
|
146
|
-
should 'call handle on each handler if handle_[event_name] isn\'t defined' do
|
147
|
-
|
148
|
-
|
146
|
+
should 'XXX call handle on each handler if handle_[event_name] isn\'t defined' do
|
147
|
+
@handler.expects(:respond_to?).with(:handle_sample_event).returns(false)
|
148
|
+
@handler.expects(:handle).with(:sample_event, :awesome => true, :sauce => 2)
|
149
149
|
@dispatcher.dispatch :sample_event, :awesome => true, :sauce => 2
|
150
150
|
end
|
151
151
|
|
152
152
|
should 'let you halt handler processing if you raise HaltHandlerProcessing' do
|
153
153
|
handler_two = ExampleHandlerB.new
|
154
154
|
@dispatcher.class.register_handler handler_two
|
155
|
-
|
156
|
-
|
157
|
-
end
|
158
|
-
dont_allow(handler_two).handle(:sample_event, :awesome => true, :sauce => 2)
|
155
|
+
@handler.expects(:handle).with(:sample_event, :awesome => true, :sauce => 2).raises(Perennial::HaltHandlerProcessing)
|
156
|
+
handler_two.expects(:handle).with(:sample_event, :awesome => true, :sauce => 2).never
|
159
157
|
@dispatcher.dispatch :sample_event, :awesome => true, :sauce => 2
|
160
158
|
end
|
161
159
|
|
@@ -203,7 +201,7 @@ class DispatchableTest < Test::Unit::TestCase
|
|
203
201
|
end
|
204
202
|
|
205
203
|
should 'call registered= on the handler' do
|
206
|
-
|
204
|
+
@handler.expects(:registered=).with(true)
|
207
205
|
@dispatcher.class.register_handler @handler
|
208
206
|
end
|
209
207
|
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "test_helper")
|
2
|
+
|
3
|
+
class GeneratorTest < Test::Unit::TestCase
|
4
|
+
context 'a basic generator' do
|
5
|
+
|
6
|
+
setup do
|
7
|
+
@generator_dir = Pathname(__FILE__).dirname.join("..", "generator-test").expand_path
|
8
|
+
FileUtils.mkdir_p(@generator_dir)
|
9
|
+
@generator = Perennial::Generator.new(@generator_dir, :silent => true)
|
10
|
+
end
|
11
|
+
|
12
|
+
should 'let you create folders' do
|
13
|
+
assert !File.directory?(File.join(@generator_dir, "test-a"))
|
14
|
+
assert !File.directory?(File.join(@generator_dir, "test-b"))
|
15
|
+
assert !File.directory?(File.join(@generator_dir, "test-c"))
|
16
|
+
assert !File.directory?(File.join(@generator_dir, "test-c/subdir"))
|
17
|
+
@generator.folders 'test-a', 'test-b', 'test-c/subdir'
|
18
|
+
assert File.directory?(File.join(@generator_dir, "test-a"))
|
19
|
+
assert File.directory?(File.join(@generator_dir, "test-b"))
|
20
|
+
assert File.directory?(File.join(@generator_dir, "test-c"))
|
21
|
+
assert File.directory?(File.join(@generator_dir, "test-c/subdir"))
|
22
|
+
end
|
23
|
+
|
24
|
+
should 'define a shortcut for FileUtils' do
|
25
|
+
assert_equal FileUtils, @generator.fu
|
26
|
+
end
|
27
|
+
|
28
|
+
should 'let you chmod a file' do
|
29
|
+
test_file = Pathname(@generator_dir).join("test-file").expand_path
|
30
|
+
File.open(test_file, "w+") { |f| f.puts "Some Simple File" }
|
31
|
+
old_permissions = File.stat(test_file).mode
|
32
|
+
@generator.chmod 0755, "test-file"
|
33
|
+
new_permissions = File.stat(test_file).mode
|
34
|
+
assert_not_equal old_permissions, new_permissions
|
35
|
+
assert_equal 0100755, new_permissions
|
36
|
+
end
|
37
|
+
|
38
|
+
should 'let you easily check if a file exists' do
|
39
|
+
assert !@generator.file?("test-file")
|
40
|
+
test_file = Pathname(@generator_dir).join("test-file").expand_path
|
41
|
+
File.open(test_file, "w+") { |f| f.puts "Some Simple File" }
|
42
|
+
assert @generator.file?("test-file")
|
43
|
+
end
|
44
|
+
|
45
|
+
should 'let you easily check if a directory exists' do
|
46
|
+
assert !@generator.directory?("test-dir")
|
47
|
+
FileUtils.mkdir Pathname(@generator_dir).join("test-dir").expand_path
|
48
|
+
assert @generator.directory?("test-dir")
|
49
|
+
end
|
50
|
+
|
51
|
+
should 'let you easily check if a file is executable' do
|
52
|
+
test_file = Pathname(@generator_dir).join("test-file").expand_path
|
53
|
+
File.open(test_file, "w+") { |f| f.puts "#!/bin/sh" }
|
54
|
+
assert !@generator.executable?("test-file")
|
55
|
+
File.chmod 0755, test_file
|
56
|
+
assert @generator.executable?("test-file")
|
57
|
+
end
|
58
|
+
|
59
|
+
should 'let you generate a file with contents' do
|
60
|
+
test_file = Pathname(@generator_dir).join("test-file").expand_path
|
61
|
+
@generator.file("test-file", "Example File")
|
62
|
+
assert_equal "Example File", File.read(test_file)
|
63
|
+
@generator.file("test-file", "Example File 2")
|
64
|
+
assert_equal "Example File 2", File.read(test_file)
|
65
|
+
end
|
66
|
+
|
67
|
+
should 'let you generate a file with contents, possibly appending' do
|
68
|
+
test_file = Pathname(@generator_dir).join("test-file").expand_path
|
69
|
+
@generator.file("test-file", "Example File")
|
70
|
+
assert_equal "Example File", File.read(test_file)
|
71
|
+
@generator.file("test-file", " 2", true)
|
72
|
+
assert_equal "Example File 2", File.read(test_file)
|
73
|
+
end
|
74
|
+
|
75
|
+
should 'let you render a template' do
|
76
|
+
test_file = @generator_dir.join("test-file").expand_path
|
77
|
+
template_dir = @generator_dir.join("templates")
|
78
|
+
FileUtils.mkdir_p template_dir
|
79
|
+
File.open(template_dir.join("sample.erb"), "w+") { |f| f.puts "Hello <%= @name %>" }
|
80
|
+
@generator.template_path = template_dir.to_s
|
81
|
+
assert !File.file?(test_file)
|
82
|
+
@generator.template "sample.erb", "test-file", :name => "Darcy"
|
83
|
+
assert File.file?(test_file)
|
84
|
+
assert_equal "Hello Darcy", File.read(test_file).strip
|
85
|
+
end
|
86
|
+
|
87
|
+
should 'let you download file and save it'
|
88
|
+
|
89
|
+
teardown do
|
90
|
+
FileUtils.rm_rf(@generator_dir) if File.directory?(@generator_dir)
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
end
|
data/test/logger_test.rb
CHANGED
@@ -1,59 +1,58 @@
|
|
1
1
|
require File.join(File.dirname(__FILE__), "test_helper")
|
2
2
|
|
3
3
|
class LoggerTest < Test::Unit::TestCase
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
4
|
+
with_fakefs do
|
5
|
+
context 'logger tests' do
|
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
11
|
|
12
|
-
|
12
|
+
context 'setting up a logger' do
|
13
13
|
|
14
|
-
|
14
|
+
setup { Perennial::Logger.setup! }
|
15
15
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
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
20
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
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
|
26
27
|
end
|
27
|
-
end
|
28
28
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
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
33
|
|
34
|
-
|
34
|
+
should 'let you configure a dir that logs are loaded from'
|
35
35
|
|
36
|
-
|
36
|
+
end
|
37
37
|
|
38
|
-
|
38
|
+
context 'writing to the log' do
|
39
39
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
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
|
46
47
|
end
|
47
48
|
end
|
48
|
-
end
|
49
49
|
|
50
|
-
|
50
|
+
end
|
51
51
|
|
52
|
-
|
53
|
-
|
54
|
-
|
52
|
+
teardown do
|
53
|
+
log_path = @root_path / "example.log"
|
54
|
+
FileUtils.rm_rf(log_path) if File.exist?(log_path)
|
55
|
+
end
|
55
56
|
end
|
56
|
-
|
57
57
|
end
|
58
|
-
|
59
58
|
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "test_helper")
|
2
|
+
require 'shellwords'
|
3
|
+
|
4
|
+
class OptionParserTest < Test::Unit::TestCase
|
5
|
+
context 'basic option parsing' do
|
6
|
+
|
7
|
+
setup do
|
8
|
+
@options = {}
|
9
|
+
@option_parser = Perennial::OptionParser.new
|
10
|
+
@option_parser.add(:age, "Your age") { |v| @options[:age] = v.to_i }
|
11
|
+
@option_parser.add(:name, "Your name") { |v| @options[:name] = v.to_s }
|
12
|
+
@option_parser.add(:ninja, "Are you a ninja") { |v| @options[:ninjas] = v.present? }
|
13
|
+
@option_parser.add(:felafel, "Do you enjoy felafel?", :shortcut => "X") { |v| @options[:felafel] = v.present? }
|
14
|
+
end
|
15
|
+
|
16
|
+
should 'correct recognize short options' do
|
17
|
+
parse! "-a 18 -n \"Darcy Laycock\" -N -X"
|
18
|
+
assert_equal({
|
19
|
+
:age => 18,
|
20
|
+
:name => "Darcy Laycock",
|
21
|
+
:ninjas => true,
|
22
|
+
:felafel => true
|
23
|
+
}, @options)
|
24
|
+
end
|
25
|
+
|
26
|
+
should 'correctly recognize long options' do
|
27
|
+
parse! "--age 21 --name \"Darcy Laycock\" --ninja --felafel"
|
28
|
+
assert_equal({
|
29
|
+
:age => 21,
|
30
|
+
:name => "Darcy Laycock",
|
31
|
+
:ninjas => true,
|
32
|
+
:felafel => true
|
33
|
+
}, @options)
|
34
|
+
end
|
35
|
+
|
36
|
+
should 'correctly recognize mixed options' do
|
37
|
+
parse! "-a 21 --name \"Darcy Laycock\" -N --felafel"
|
38
|
+
assert_equal({
|
39
|
+
:age => 21,
|
40
|
+
:name => "Darcy Laycock",
|
41
|
+
:ninjas => true,
|
42
|
+
:felafel => true
|
43
|
+
}, @options)
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
def parse!(l)
|
49
|
+
@option_parser.parse(Shellwords.shellwords(l))
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "test_helper")
|
2
|
+
|
3
|
+
class ReloadingTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
context 'basic reloading file' do
|
6
|
+
|
7
|
+
setup do
|
8
|
+
@file = File.join(File.dirname(__FILE__), "tmp", "reloading_test.rb")
|
9
|
+
FileUtils.mkdir_p File.dirname(@file)
|
10
|
+
write_reloadable_klass(:initial)
|
11
|
+
load @file
|
12
|
+
Perennial::Reloading.watch @file
|
13
|
+
ReloadingTest.count = 10
|
14
|
+
end
|
15
|
+
|
16
|
+
should 'call reloading on the old class if defined' do
|
17
|
+
ReloadingTest.expects(:respond_to?).with(:reloading!).returns(true)
|
18
|
+
ReloadingTest.expects(:reloading!)
|
19
|
+
write_reloadable_klass :reloaded
|
20
|
+
Perennial::Reloading.reload!
|
21
|
+
end
|
22
|
+
|
23
|
+
should 'call reloaded if defined' do
|
24
|
+
assert !ReloadingTest.was_reloaded
|
25
|
+
# Reload without the method
|
26
|
+
write_reloadable_klass :reloaded
|
27
|
+
Perennial::Reloading.reload!
|
28
|
+
assert !ReloadingTest.was_reloaded
|
29
|
+
# finally, define the method and reload.
|
30
|
+
write_reloadable_klass :reloaded, true
|
31
|
+
Perennial::Reloading.reload!
|
32
|
+
assert ReloadingTest.was_reloaded
|
33
|
+
end
|
34
|
+
|
35
|
+
should 'reload if changed' do
|
36
|
+
assert_equal 10, ReloadingTest.count
|
37
|
+
assert_equal :initial, ReloadingTest::RELOADED_VALUE
|
38
|
+
write_reloadable_klass :reloaded
|
39
|
+
Perennial::Reloading.reload!
|
40
|
+
assert_equal 0, ReloadingTest.count
|
41
|
+
assert_equal :reloaded, ReloadingTest::RELOADED_VALUE
|
42
|
+
end
|
43
|
+
|
44
|
+
should 'not reload if unchanged' do
|
45
|
+
assert_equal 10, ReloadingTest.count
|
46
|
+
assert_equal :initial, ReloadingTest::RELOADED_VALUE
|
47
|
+
Perennial::Reloading.reload!
|
48
|
+
assert_equal 10, ReloadingTest.count
|
49
|
+
assert_equal :initial, ReloadingTest::RELOADED_VALUE
|
50
|
+
end
|
51
|
+
|
52
|
+
teardown do
|
53
|
+
File.delete @file if File.exist?(@file)
|
54
|
+
Object.send(:remove_const, :ReloadingTest) if defined?(ReloadingTest)
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
def write_reloadable_klass(value, include_reloaded = false)
|
60
|
+
u, a = Time.now, Time.now
|
61
|
+
u, a = File.atime(@file), File.mtime(@file) if File.exist?(@file)
|
62
|
+
File.open(@file, "w+") do |f|
|
63
|
+
f.puts "class ReloadingTest"
|
64
|
+
f.puts " RELOADED_VALUE = #{value.inspect}"
|
65
|
+
f.puts " cattr_accessor :count"
|
66
|
+
f.puts " self.count = 0"
|
67
|
+
f.puts " cattr_reader :was_reloaded"
|
68
|
+
f.puts " def self.reloaded!; @@was_reloaded = true; end" if include_reloaded
|
69
|
+
f.puts "end"
|
70
|
+
end
|
71
|
+
# We fake the mtime to simulate a proper, delay
|
72
|
+
File.utime(u + 3, a + 3, File.expand_path(@file))
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
data/test/settings_test.rb
CHANGED
@@ -1,103 +1,103 @@
|
|
1
1
|
require File.join(File.dirname(__FILE__), "test_helper")
|
2
2
|
|
3
3
|
class SettingsTest < Test::Unit::TestCase
|
4
|
-
|
5
|
-
|
4
|
+
with_fakefs do
|
5
|
+
context 'default settings' do
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
7
|
+
setup do
|
8
|
+
Perennial::Settings.setup!
|
9
|
+
end
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
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
17
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
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
25
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
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
31
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
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
39
|
|
40
|
-
|
40
|
+
end
|
41
41
|
|
42
|
-
|
42
|
+
context 'loading settings' do
|
43
43
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
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
|
+
}
|
52
53
|
}
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
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!
|
57
59
|
end
|
58
|
-
Perennial::Settings.setup!
|
59
|
-
end
|
60
60
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
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
65
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
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
|
73
74
|
end
|
74
|
-
end
|
75
75
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
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
|
81
82
|
end
|
82
|
-
end
|
83
83
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
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
|
94
95
|
end
|
95
|
-
end
|
96
96
|
|
97
|
-
|
97
|
+
should 'let you configure the lookup key path'
|
98
98
|
|
99
|
-
|
99
|
+
should 'let you configure the file settings are loaded from'
|
100
100
|
|
101
|
+
end
|
101
102
|
end
|
102
|
-
|
103
103
|
end
|
data/test/test_helper.rb
CHANGED
@@ -3,17 +3,26 @@ require 'rubygems'
|
|
3
3
|
# Testing dependencies
|
4
4
|
require 'test/unit'
|
5
5
|
require 'shoulda'
|
6
|
-
require '
|
6
|
+
require 'mocha'
|
7
7
|
# RedGreen doesn't seem to be needed under 1.9
|
8
8
|
require 'redgreen' if RUBY_VERSION < "1.9"
|
9
9
|
|
10
10
|
require 'pathname'
|
11
11
|
root_directory = Pathname.new(__FILE__).dirname.join("..").expand_path
|
12
12
|
require root_directory.join("lib", "perennial")
|
13
|
-
|
13
|
+
# Require fakefs
|
14
|
+
$:.unshift root_directory.join("vendor", "fakefs", "lib").to_s
|
15
|
+
require "fakefs/safe"
|
14
16
|
|
15
17
|
class Test::Unit::TestCase
|
16
|
-
|
18
|
+
|
19
|
+
def self.with_fakefs(&blk)
|
20
|
+
context '' do
|
21
|
+
setup { FakeFS.activate! }
|
22
|
+
context('', &blk)
|
23
|
+
teardown { FakeFS.deactivate! }
|
24
|
+
end
|
25
|
+
end
|
17
26
|
|
18
27
|
protected
|
19
28
|
|
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.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Darcy Laycock
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-10-
|
12
|
+
date: 2009-10-09 00:00:00 +08:00
|
13
13
|
default_executable: perennial
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -22,6 +22,16 @@ dependencies:
|
|
22
22
|
- !ruby/object:Gem::Version
|
23
23
|
version: "0"
|
24
24
|
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: mocha
|
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:
|
25
35
|
- !ruby/object:Gem::Dependency
|
26
36
|
name: yard
|
27
37
|
type: :development
|
@@ -46,6 +56,7 @@ files:
|
|
46
56
|
- lib/perennial/application.rb
|
47
57
|
- lib/perennial/argument_parser.rb
|
48
58
|
- lib/perennial/core_ext.rb
|
59
|
+
- lib/perennial/core_ext/ansi_formatter.rb
|
49
60
|
- lib/perennial/core_ext/attribute_accessors.rb
|
50
61
|
- lib/perennial/core_ext/blank.rb
|
51
62
|
- lib/perennial/core_ext/hash_key_conversions.rb
|
@@ -103,12 +114,16 @@ signing_key:
|
|
103
114
|
specification_version: 3
|
104
115
|
summary: A simple (generally event-oriented) application library for Ruby
|
105
116
|
test_files:
|
117
|
+
- test/argument_parser_test.rb
|
106
118
|
- test/delegateable_test.rb
|
107
119
|
- test/dispatchable_test.rb
|
120
|
+
- test/generator_test.rb
|
108
121
|
- test/hookable_test.rb
|
109
122
|
- test/loader_test.rb
|
110
123
|
- test/loggable_test.rb
|
111
124
|
- test/logger_test.rb
|
125
|
+
- test/option_parser_test.rb
|
112
126
|
- test/proxy_test.rb
|
127
|
+
- test/reloading_test.rb
|
113
128
|
- test/settings_test.rb
|
114
129
|
- test/test_helper.rb
|