perennial 0.2.2.2 → 1.0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/bin/perennial CHANGED
@@ -15,8 +15,7 @@ Perennial::Application.processing(ARGV) do |a|
15
15
  path = File.expand_path(path)
16
16
  # Check if the folder exists
17
17
  if File.exist?(path) && !opts[:force]
18
- puts "The path you tried to use, #{path}, already exists. Please try another or pass --force"
19
- exit
18
+ die! "The path you tried to use, #{path}, already exists. Please try another or pass --force"
20
19
  end
21
20
  # Convert the name and class name.
22
21
  app_path = app_name.underscore
@@ -15,6 +15,29 @@ module Perennial
15
15
  klass.new.apply(*arguments)
16
16
  end
17
17
 
18
+ def yes?(question)
19
+ result = Readline.readline("#{question.to_s.strip} (y/n) ")
20
+ result.downcase[0] == ?y
21
+ end
22
+
23
+ def ask(question, default)
24
+ result = Readline.readline("#{question.to_s.strip} (default: #{default}) ")
25
+ result.blank? ? default : result
26
+ end
27
+
28
+ def ask_password(question)
29
+ system "stty -echo"
30
+ line = Readline.readline("#{question.to_s.strip} ").strip
31
+ system "stty echo"
32
+ print "\n"
33
+ return line
34
+ end
35
+
36
+ def die!(message)
37
+ $stderr.puts message
38
+ exit! 1
39
+ end
40
+
18
41
  end
19
42
 
20
43
  attr_accessor :options, :banner, :command_env
@@ -30,8 +53,8 @@ module Perennial
30
53
  @banners = {}
31
54
  end
32
55
 
33
- def option(name, description = nil)
34
- option_parser.add(name, description) { |v| @command_options[name] = v }
56
+ def option(name, description = nil, opts = {})
57
+ option_parser.add(name, description, opts) { |v| @command_options[name] = v }
35
58
  end
36
59
 
37
60
  def add_default_options!
@@ -44,19 +67,21 @@ module Perennial
44
67
  end
45
68
  end
46
69
 
47
- def controller!(controller, description)
70
+ def controller!(controller, description, opts = {})
48
71
  return unless defined?(Loader)
49
72
  add_default_options!
50
73
  option :kill, "Kill any runninng instances"
51
74
  controller_name = controller.to_s.underscore
52
75
  controller = controller.to_sym
53
76
  command_name = controller_name.gsub("_", "-")
54
- add("#{command_name} [PATH]", description) do |*args|
77
+ parent = self
78
+ add("#{command_name} #{"[PATH]" if !opts[:skip_path]}".strip, description) do |*args|
55
79
  options = args.extract_options!
56
80
  path = File.expand_path(args[0] || ".")
57
81
  Settings.root = path
58
82
  if options.delete(:kill)
59
- puts "Attempting to kill processess..."
83
+ parent.attempt_showing_banner
84
+ puts "Attempting to kill processess for #{command_name}"
60
85
  Daemon.kill_all(controller)
61
86
  else
62
87
  Loader.run!(controller, options)
@@ -86,35 +111,32 @@ module Perennial
86
111
  if @commands.has_key?(command)
87
112
  execute_command(command, arguments)
88
113
  else
89
- puts "Unknown command '#{command}', please try again."
90
- return usage
114
+ show_error "Unknown command '#{command}', please try again."
115
+ usage(true)
91
116
  end
92
117
  end
93
118
 
94
- def usage
95
- if banner.present?
96
- puts banner
97
- puts ""
98
- end
119
+ def usage(skip_banner = false)
120
+ attempt_showing_banner unless skip_banner
99
121
  puts "Usage:"
100
122
  max_width = @banners.values.map { |b| b.length }.max
101
123
  @commands.keys.sort.each do |command|
102
124
  next unless @descriptions.has_key?(command)
103
125
  formatted_command = "#{@banners[command]} [OPTIONS]".ljust(max_width + 10)
104
- command = "%s - %s" % [formatted_command, @descriptions[command]]
126
+ command = " %s - %s" % [formatted_command, @descriptions[command]]
105
127
  puts command
106
128
  end
129
+ puts ""
130
+ puts "Please note: you can pass -h / --help to any command for more specific help"
107
131
  end
108
132
 
109
- def help_for(command)
110
- if banner.present?
111
- puts banner
112
- puts ""
113
- end
133
+ def help_for(command, skip_banner = false)
134
+ attempt_showing_banner unless skip_banner
114
135
  puts @descriptions[command]
136
+ puts ""
115
137
  puts "Usage: #{$0} #{@banners[command]} [options]"
116
138
  puts "Options:"
117
- puts @option_parsers[command].summary
139
+ puts pad_left(@option_parsers[command].summary)
118
140
  exit
119
141
  end
120
142
 
@@ -127,6 +149,13 @@ module Perennial
127
149
  end
128
150
  application.execute args
129
151
  end
152
+
153
+ def attempt_showing_banner
154
+ if banner.present?
155
+ puts banner
156
+ puts ""
157
+ end
158
+ end
130
159
 
131
160
  protected
132
161
 
@@ -137,7 +166,7 @@ module Perennial
137
166
  args << opts
138
167
  @command_env.execute(command_proc, args)
139
168
  else
140
- usage
169
+ help_for(command, true)
141
170
  end
142
171
  end
143
172
 
@@ -160,14 +189,28 @@ module Perennial
160
189
  needed_count = blk.arity - 1
161
190
  provided_count = arguments.size
162
191
  if needed_count > 0 && needed_count != provided_count
163
- puts "You didn't provide the correct number of arguments (needed #{needed_count}, provided #{provided_count})"
192
+ attempt_showing_banner
193
+ show_error "You didn't provide the correct number of arguments (needed #{needed_count}, provided #{provided_count})"
164
194
  elsif needed_count < 0 && (-needed_count - 2) > provided_count
165
- puts "You didn't provide enough arguments - a minimum of #{-needed_count} are needed."
195
+ show_error "You didn't provide enough arguments - a minimum of #{-needed_count} are needed."
166
196
  else
167
197
  return true
168
198
  end
169
199
 
170
200
  end
171
201
 
202
+ def show_error(text)
203
+ attempt_showing_banner
204
+ text = "Error: #{text}".strip
205
+ puts "--#{"-" * text.length}"
206
+ puts " #{text} "
207
+ puts "--#{"-" * text.length}"
208
+ puts ""
209
+ end
210
+
211
+ def pad_left(text, spacing = 2)
212
+ text.split("\n").map { |l| "#{" " * spacing}#{l}" }.join("\n")
213
+ end
214
+
172
215
  end
173
216
  end
@@ -1,6 +1,31 @@
1
1
  # cattr_* and class_inheritable_* are taken from
2
2
  # ActiveSupport. Included here to help keep the
3
3
  # codebase simple / clean.
4
+
5
+ class Object
6
+
7
+ unless respond_to?(:instance_variable_defined?)
8
+ def instance_variable_defined?(variable)
9
+ instance_variables.include?(variable.to_s)
10
+ end
11
+ end
12
+
13
+ def instance_values #:nodoc:
14
+ instance_variables.inject({}) do |values, name|
15
+ values[name.to_s[1..-1]] = instance_variable_get(name)
16
+ values
17
+ end
18
+ end
19
+
20
+ if RUBY_VERSION >= '1.9'
21
+ def instance_variable_names
22
+ instance_variables.map { |var| var.to_s }
23
+ end
24
+ else
25
+ alias_method :instance_variable_names, :instance_variables
26
+ end
27
+ end
28
+
4
29
  class Class
5
30
  def cattr_reader(*syms)
6
31
  syms.flatten.each do |sym|
@@ -0,0 +1,26 @@
1
+ class Hash
2
+ def symbolize_keys
3
+ hash = self.dup
4
+ hash.symbolize_keys!
5
+ return hash
6
+ end
7
+
8
+ def symbolize_keys!
9
+ hash = {}
10
+ self.each_pair { |k,v| hash[k.to_sym] = v }
11
+ replace hash
12
+ end
13
+
14
+ def stringify_keys!
15
+ hash = {}
16
+ self.each_pair { |k, v| hash[k.to_s] = v }
17
+ replace hash
18
+ end
19
+
20
+ def stringify_keys
21
+ hash = self.dup
22
+ hash.stringify_keys!
23
+ return hash
24
+ end
25
+
26
+ end
@@ -0,0 +1,33 @@
1
+ module Perennial
2
+ class Inflector
3
+ class << self
4
+
5
+ def underscore(camel_cased_word)
6
+ camel_cased_word.to_s.gsub(/::/, '/').
7
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
8
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
9
+ tr("-", "_").
10
+ downcase
11
+ end
12
+
13
+ def camelize(lower_case_and_underscored_word, first_letter_in_uppercase = true)
14
+ if first_letter_in_uppercase
15
+ lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
16
+ else
17
+ lower_case_and_underscored_word.first.downcase + camelize(lower_case_and_underscored_word)[1..-1]
18
+ end
19
+ end
20
+
21
+ end
22
+ end
23
+ end
24
+
25
+ class String
26
+ def underscore
27
+ Perennial::Inflector.underscore(self)
28
+ end
29
+
30
+ def camelize(capitalize_first_letter = true)
31
+ Perennial::Inflector.camelize(self, capitalize_first_letter)
32
+ end
33
+ end
@@ -3,30 +3,14 @@ class Object
3
3
  def metaclass
4
4
  class << self; self; end
5
5
  end
6
+
7
+ def meta_def(name, &blk)
8
+ metaclass.define_method(name, &blk)
9
+ end
6
10
 
7
11
  end
8
12
 
9
- class Inflector
10
- class << self
11
13
 
12
- def underscore(camel_cased_word)
13
- camel_cased_word.to_s.gsub(/::/, '/').
14
- gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
15
- gsub(/([a-z\d])([A-Z])/,'\1_\2').
16
- tr("-", "_").
17
- downcase
18
- end
19
-
20
- def camelize(lower_case_and_underscored_word, first_letter_in_uppercase = true)
21
- if first_letter_in_uppercase
22
- lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
23
- else
24
- lower_case_and_underscored_word.first.downcase + camelize(lower_case_and_underscored_word)[1..-1]
25
- end
26
- end
27
-
28
- end
29
- end
30
14
 
31
15
  module Kernel
32
16
 
@@ -53,14 +37,6 @@ class String
53
37
  File.join(self, *args)
54
38
  end
55
39
 
56
- def underscore
57
- Inflector.underscore(self)
58
- end
59
-
60
- def camelize(capitalize_first_letter = true)
61
- Inflector.camelize(self, capitalize_first_letter)
62
- end
63
-
64
40
  def to_pathname
65
41
  Pathname.new(self)
66
42
  end
@@ -72,23 +48,9 @@ class Array
72
48
  end
73
49
  end
74
50
 
75
- class Hash
76
- def symbolize_keys
77
- hash = self.dup
78
- hash.symbolize_keys!
79
- return hash
80
- end
81
-
82
- def symbolize_keys!
83
- hash = {}
84
- self.each_pair { |k,v| hash[k.to_sym] = v }
85
- replace hash
86
- end
87
- end
88
-
89
51
  class Module
90
52
 
91
- def has_libary(*items)
53
+ def has_library(*items)
92
54
  namespace = self.to_s.underscore
93
55
  items.each do |item|
94
56
  require File.join(namespace, item.to_s.underscore)
@@ -106,6 +68,19 @@ class Module
106
68
  end
107
69
  end
108
70
 
71
+ def add_extension(name, &blk)
72
+ item = name.to_s.camelize.to_sym
73
+ target = const_get(item) rescue nil
74
+ raise "Didn't find library for #{name}" if target.nil?
75
+ if target.is_a?(Class)
76
+ target.class_eval(&blk)
77
+ elsif target.is_a?(Module)
78
+ target.module_eval(&blk)
79
+ else
80
+ raise "Unable to extend #{target.inspect}"
81
+ end
82
+ end
83
+
109
84
  def attempt_require(*files)
110
85
  files.each do |file|
111
86
  begin
@@ -115,4 +90,19 @@ class Module
115
90
  end
116
91
  end
117
92
 
93
+ end
94
+
95
+ class Class
96
+
97
+ def is(*mixins)
98
+ ns = Perennial::Manifest.namespace
99
+ return if ns.blank?
100
+ mixins.each do |mixin|
101
+ begin
102
+ include ns.const_get(mixin.to_s.camelize)
103
+ rescue NameError
104
+ end
105
+ end
106
+ end
107
+
118
108
  end
@@ -0,0 +1,16 @@
1
+ module Perennial
2
+ # a super simple proxy class.
3
+ class Proxy
4
+
5
+ instance_methods.each { |m| undef_method m unless m =~ /(^__|^send$|^object_id$)/ }
6
+
7
+ attr_accessor :__proxy_target__
8
+
9
+ protected
10
+
11
+ def method_missing(m, *args, &blk)
12
+ __proxy_target__.send(m, *args, &blk)
13
+ end
14
+
15
+ end
16
+ end
@@ -1,4 +1,7 @@
1
1
  # Require each individial core extension
2
2
  require 'perennial/core_ext/attribute_accessors'
3
3
  require 'perennial/core_ext/blank'
4
- require 'perennial/core_ext/misc'
4
+ require 'perennial/core_ext/misc'
5
+ require 'perennial/core_ext/hash_key_conversions'
6
+ require 'perennial/core_ext/inflections'
7
+ require 'perennial/core_ext/proxy'
@@ -62,9 +62,9 @@ module Perennial
62
62
  # the double fork approach. Also, changes process file
63
63
  # mask to 000 and reopens STDIN / OUT to /dev/null
64
64
  def daemonize!
65
- exit if fork
65
+ fork_off_and_die
66
66
  Process.setsid
67
- exit if fork
67
+ fork_off_and_die
68
68
  self.write_pid
69
69
  File.umask 0000
70
70
  STDIN.reopen "/dev/null"
@@ -118,6 +118,13 @@ module Perennial
118
118
  File.open(f, "w+") { |f| f.puts pids.join("\n") }
119
119
  end
120
120
 
121
+ def fork_off_and_die
122
+ if pid = fork
123
+ Process.detach(pid)
124
+ exit
125
+ end
126
+ end
127
+
121
128
  end
122
129
  end
123
130
  end
@@ -0,0 +1,48 @@
1
+ module Perennial
2
+ # Objective-C / Cocoa style 'delegates'.
3
+ # Essentially proxies which dispatch only
4
+ # when an object responds to the method.
5
+ class DelegateProxy < Proxy
6
+
7
+ def initialize(t)
8
+ @__proxy_target__ = t
9
+ end
10
+
11
+ def respond_to?(method, inc_super = false)
12
+ true
13
+ end
14
+
15
+ protected
16
+
17
+ def method_missing(method, *args, &blk)
18
+ @__proxy_target__.send(method, *args, &blk) if @__proxy_target__.respond_to?(method)
19
+ end
20
+
21
+ end
22
+
23
+ module Delegateable
24
+
25
+ def self.included(parent)
26
+ parent.send(:include, InstanceMethods)
27
+ end
28
+
29
+ module InstanceMethods
30
+
31
+ def delegate=(value)
32
+ @delegate = DelegateProxy.new(value)
33
+ end
34
+
35
+ alias delegate_to delegate=
36
+
37
+ def delegate
38
+ @delegate ||= DelegateProxy.new(nil)
39
+ end
40
+
41
+ def real_delegate
42
+ @delegate && @delegate.__proxy_target__
43
+ end
44
+
45
+ end
46
+
47
+ end
48
+ end
@@ -21,6 +21,10 @@ module Perennial
21
21
  @@handler_mapping ||= Hash.new { |h,k| h[k] = [] }
22
22
  end
23
23
 
24
+ def self.reloading_mapping
25
+ @@reloading_mapping ||= Hash.new { |h,k| h[k] = [] }
26
+ end
27
+
24
28
  def self.included(parent)
25
29
  parent.class_eval do
26
30
  include InstanceMethods
@@ -36,6 +40,14 @@ module Perennial
36
40
  self.class.handlers
37
41
  end
38
42
 
43
+ def dispatch_queue
44
+ @dispatch_queue ||= []
45
+ end
46
+
47
+ def dispatching?
48
+ @dispatching ||= false
49
+ end
50
+
39
51
  # Dispatch an 'event' with a given name to the handlers
40
52
  # registered on the current class. Used as a nicer way of defining
41
53
  # behaviours that should occur under a given set of circumstances.
@@ -43,32 +55,44 @@ module Perennial
43
55
  # +name+: The name of the current event
44
56
  # +opts+: an optional hash of options to pass
45
57
  def dispatch(name, opts = {})
46
- # The full handler name is the method we call given it exists.
47
- full_handler_name = :"handle_#{name.to_s.underscore}"
48
- # First, dispatch locally if the method is defined.
49
- self.send(full_handler_name, opts) if self.respond_to?(full_handler_name)
50
- # Iterate through all of the registered handlers,
51
- # If there is a method named handle_<event_name>
52
- # defined we sent that otherwise we call the handle
53
- # method on the handler. Note that the handle method
54
- # is the only required aspect of a handler. An improved
55
- # version of this would likely cache the respond_to?
56
- # call.
57
- self.handlers.each do |handler|
58
- if handler.respond_to?(full_handler_name)
59
- handler.send(full_handler_name, opts)
60
- else
61
- handler.handle name, opts
58
+ if !dispatching?
59
+ Logger.debug "Dispatching #{name} event (#{dispatch_queue.size} queued - on #{self.class.name})"
60
+ # Add ourselves to the queue
61
+ @dispatching = true
62
+ begin
63
+ # The full handler name is the method we call given it exists.
64
+ full_handler_name = :"handle_#{name.to_s.underscore}"
65
+ # First, dispatch locally if the method is defined.
66
+ self.send(full_handler_name, opts) if self.respond_to?(full_handler_name)
67
+ # Iterate through all of the registered handlers,
68
+ # If there is a method named handle_<event_name>
69
+ # defined we sent that otherwise we call the handle
70
+ # method on the handler. Note that the handle method
71
+ # is the only required aspect of a handler. An improved
72
+ # version of this would likely cache the respond_to?
73
+ # call.
74
+ self.handlers.each do |handler|
75
+ if handler.respond_to?(full_handler_name)
76
+ handler.send(full_handler_name, opts)
77
+ else
78
+ handler.handle name, opts
79
+ end
80
+ end
81
+ # If we get the HaltHandlerProcessing exception, we
82
+ # catch it and continue on our way. In essence, we
83
+ # stop the dispatch of events to the next set of the
84
+ # handlers.
85
+ rescue HaltHandlerProcessing => e
86
+ Logger.info "Halting processing chain"
87
+ rescue Exception => e
88
+ Logger.log_exception(e)
62
89
  end
90
+ @dispatching = false
91
+ dispatch(*@dispatch_queue.shift) unless dispatch_queue.empty?
92
+ else
93
+ Logger.debug "Adding #{name} event to the end of the queue (on #{self.class.name})"
94
+ dispatch_queue << [name, opts]
63
95
  end
64
- # If we get the HaltHandlerProcessing exception, we
65
- # catch it and continue on our way. In essence, we
66
- # stop the dispatch of events to the next set of the
67
- # handlers.
68
- rescue HaltHandlerProcessing => e
69
- Logger.info "Halting processing chain"
70
- rescue Exception => e
71
- Logger.log_exception(e)
72
96
  end
73
97
 
74
98
  end
@@ -100,10 +124,29 @@ module Perennial
100
124
  # Handlers are called in the order they are registered.
101
125
  def register_handler(handler)
102
126
  unless handler.blank? || !handler.respond_to?(:handle)
127
+ handler.registered = true if handler.respond_to?(:registered=)
103
128
  Dispatchable.handler_mapping[self] << handler
104
129
  end
105
130
  end
106
131
 
132
+ def delete_handler(handler)
133
+ return if handler.blank?
134
+ handler.registered = false if handler.respond_to?(:registered=)
135
+ Dispatchable.handler_mapping[self].delete(handler)
136
+ end
137
+
138
+ def reloading!
139
+ handlers = Dispatchable.handler_mapping.delete(self)
140
+ Dispatchable.reloading_mapping[self.name] = handlers
141
+ end
142
+
143
+ def reloaded!
144
+ if Dispatchable.reloading_mapping.has_key?(self.name)
145
+ handlers = Dispatchable.reloading_mapping.delete(self.name)
146
+ handlers.each { |h| register_handler(h) }
147
+ end
148
+ end
149
+
107
150
  end
108
151
 
109
152
  end