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.
@@ -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