perennial 1.0.1 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- hash = {}
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
- hash = {}
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
@@ -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'
@@ -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
- return Process.getpgid(pid) != -1
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
- fork_off_and_die
66
- Process.setsid
67
- fork_off_and_die
68
- self.write_pid
69
- File.umask 0000
70
- STDIN.reopen "/dev/null"
71
- STDOUT.reopen "/dev/null", "a"
72
- STDERR.reopen STDOUT
73
- Perennial::Settings.verbose = false
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
- STDOUT.puts e.inspect
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
- return pids.uniq.select { |p| alive?(p) }
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 fork_off_and_die
122
- if pid = fork
137
+ def detached_fork
138
+ pid = fork
139
+ unless pid.nil?
123
140
  Process.detach(pid)
124
- exit
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.
@@ -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(binding_for(environment))
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
@@ -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.daemonize! if Settings.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
@@ -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}]".ljust 7 }
65
+ LEVELS.each { |k,v| PREFIXES[k] = "[#{k.to_s.upcase}]".rjust 7 }
61
66
 
62
67
  COLOURS = {
63
- :fatal => 31, # red
64
- :error => 33, # yellow
65
- :warn => 35, # magenta
66
- :info => 32, # green
67
- :debug => 34 # white
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("#{PREFIXES[name]} #{message}", name) if LEVELS[@level] <= value
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
- @file.puts message
109
- @file.flush
110
- $stdout.puts colourize(message, level) if verbose?
111
- end
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
 
@@ -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
@@ -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
@@ -9,7 +9,7 @@ require 'perennial/exceptions'
9
9
 
10
10
  module Perennial
11
11
 
12
- VERSION = [1, 0, 1, 0]
12
+ VERSION = [1, 0, 2, 0]
13
13
 
14
14
  has_library :dispatchable, :hookable, :loader, :logger, :nash,
15
15
  :loggable, :manifest, :settings, :argument_parser,
@@ -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
@@ -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) { proxy.awesomesauce }
32
- assert_raises(NoMethodError) { proxy.ninja_party }
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
 
@@ -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
- mock(@dispatcher).respond_to?(:handle_sample_event) { true }
136
- mock(@dispatcher).handle_sample_event(:awesome => true, :sauce => 2)
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
- mock(@handler).respond_to?(:handle_sample_event) { true }
142
- mock(@handler).handle_sample_event(:awesome => true, :sauce => 2)
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
- mock(@handler).respond_to?(:handle_sample_event) { false }
148
- mock(@handler).handle(:sample_event, :awesome => true, :sauce => 2)
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
- 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)
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
- mock(@handler).registered = true
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
- 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
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
- context 'setting up a logger' do
12
+ context 'setting up a logger' do
13
13
 
14
- setup { Perennial::Logger.setup! }
14
+ setup { Perennial::Logger.setup! }
15
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
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
- 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
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
- 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
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
- should 'let you configure a dir that logs are loaded from'
34
+ should 'let you configure a dir that logs are loaded from'
35
35
 
36
- end
36
+ end
37
37
 
38
- context 'writing to the log' do
38
+ context 'writing to the log' do
39
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")
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
- end
50
+ end
51
51
 
52
- teardown do
53
- log_path = @root_path / "example.log"
54
- FileUtils.rm_rf(log_path) if File.exist?(log_path)
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
@@ -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
- context 'default settings' do
4
+ with_fakefs do
5
+ context 'default settings' do
6
6
 
7
- setup do
8
- Perennial::Settings.setup!
9
- end
7
+ setup do
8
+ Perennial::Settings.setup!
9
+ end
10
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
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
- 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
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
- 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
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
- 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
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
- end
40
+ end
41
41
 
42
- context 'loading settings' do
42
+ context 'loading settings' do
43
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"]
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
- FileUtils.mkdir_p(config_folder)
55
- File.open(config_folder / "settings.yml", "w+") do |file|
56
- file.write(@default_settings.to_yaml)
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
- 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
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
- 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)
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
- 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]
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
- 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)
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
- should 'let you configure the lookup key path'
97
+ should 'let you configure the lookup key path'
98
98
 
99
- should 'let you configure the file settings are loaded from'
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 'rr'
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
- require root_directory.join("vendor", "fakefs", "lib", "fakefs")
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
- include RR::Adapters::TestUnit
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.1
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-05 00:00:00 +08:00
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