perennial 0.2.2.2 → 1.0.0.1

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