perennial 1.0.1 → 1.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|