Sutto-perennial 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,60 @@
1
+ require 'shellwords'
2
+
3
+ module Perennial
4
+ class ArgumentParser
5
+
6
+ attr_reader :arguments
7
+
8
+ def initialize(array)
9
+ @raw_arguments = array.dup
10
+ @arguments = []
11
+ @results = {}
12
+ end
13
+
14
+ def to_hash
15
+ @results ||= {}
16
+ end
17
+
18
+ def parse!
19
+ while !@raw_arguments.empty?
20
+ current = @raw_arguments.shift
21
+ if option?(current)
22
+ process_option(current, @raw_arguments.first)
23
+ else
24
+ @arguments.push current
25
+ end
26
+ end
27
+ return nil
28
+ end
29
+
30
+ def self.parse(args = ARGV)
31
+ parser = self.new(args)
32
+ parser.parse!
33
+ return parser.arguments, parser.to_hash
34
+ end
35
+
36
+ protected
37
+
38
+ def process_option(current, next_arg)
39
+ name = clean(current)
40
+ if name.include?("=")
41
+ real_name, raw_value = name.split("=", 2)
42
+ @results[real_name] = Shellwords.shellwords(raw_value).join(" ")
43
+ elsif !(next_arg.nil? || option?(next_arg))
44
+ @raw_arguments.shift
45
+ @results[name] = next_arg
46
+ else
47
+ @results[name] = true
48
+ end
49
+ end
50
+
51
+ def clean(name)
52
+ name.gsub /^\-+/, ''
53
+ end
54
+
55
+ def option?(argument)
56
+ argument.strip =~ /^\-+/
57
+ end
58
+
59
+ end
60
+ end
@@ -0,0 +1,129 @@
1
+ # cattr_* and class_inheritable_* are taken from
2
+ # ActiveSupport. Included here to help keep the
3
+ # codebase simple / clean.
4
+ class Class
5
+ def cattr_reader(*syms)
6
+ syms.flatten.each do |sym|
7
+ next if sym.is_a?(Hash)
8
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
9
+ unless defined? @@#{sym} # unless defined? @@hair_colors
10
+ @@#{sym} = nil # @@hair_colors = nil
11
+ end # end
12
+ #
13
+ def self.#{sym} # def self.hair_colors
14
+ @@#{sym} # @@hair_colors
15
+ end # end
16
+ #
17
+ def #{sym} # def hair_colors
18
+ @@#{sym} # @@hair_colors
19
+ end # end
20
+ EOS
21
+ end
22
+ end
23
+
24
+ def cattr_writer(*syms)
25
+ options = syms.extract_options!
26
+ syms.flatten.each do |sym|
27
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
28
+ unless defined? @@#{sym} # unless defined? @@hair_colors
29
+ @@#{sym} = nil # @@hair_colors = nil
30
+ end # end
31
+ #
32
+ def self.#{sym}=(obj) # def self.hair_colors=(obj)
33
+ @@#{sym} = obj # @@hair_colors = obj
34
+ end # end
35
+ #
36
+ #{" #
37
+ def #{sym}=(obj) # def hair_colors=(obj)
38
+ @@#{sym} = obj # @@hair_colors = obj
39
+ end # end
40
+ " unless options[:instance_writer] == false } # # instance writer above is generated unless options[:instance_writer] == false
41
+ EOS
42
+ end
43
+ end
44
+
45
+ def cattr_accessor(*syms)
46
+ cattr_reader(*syms)
47
+ cattr_writer(*syms)
48
+ end
49
+
50
+ # Defines class-level inheritable attribute reader. Attributes are available to subclasses,
51
+ # each subclass has a copy of parent's attribute.
52
+ #
53
+ # @param *syms<Array[#to_s]> Array of attributes to define inheritable reader for.
54
+ # @return <Array[#to_s]> Array of attributes converted into inheritable_readers.
55
+ #
56
+ # @api public
57
+ #
58
+ # @todo Do we want to block instance_reader via :instance_reader => false
59
+ # @todo It would be preferable that we do something with a Hash passed in
60
+ # (error out or do the same as other methods above) instead of silently
61
+ # moving on). In particular, this makes the return value of this function
62
+ # less useful.
63
+ def class_inheritable_reader(*ivars)
64
+ instance_reader = ivars.pop[:reader] if ivars.last.is_a?(Hash)
65
+
66
+ ivars.each do |ivar|
67
+ self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
68
+ def self.#{ivar}
69
+ return @#{ivar} if self.object_id == #{self.object_id} || defined?(@#{ivar})
70
+ ivar = superclass.#{ivar}
71
+ return nil if ivar.nil? && !#{self}.instance_variable_defined?("@#{ivar}")
72
+ @#{ivar} = ivar && !ivar.is_a?(Module) && !ivar.is_a?(Numeric) && !ivar.is_a?(TrueClass) && !ivar.is_a?(FalseClass) ? ivar.dup : ivar
73
+ end
74
+ RUBY
75
+ unless instance_reader == false
76
+ self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
77
+ def #{ivar}
78
+ self.class.#{ivar}
79
+ end
80
+ RUBY
81
+ end
82
+ end
83
+ end
84
+
85
+ # Defines class-level inheritable attribute writer. Attributes are available to subclasses,
86
+ # each subclass has a copy of parent's attribute.
87
+ #
88
+ # @param *syms<Array[*#to_s, Hash{:instance_writer => Boolean}]> Array of attributes to
89
+ # define inheritable writer for.
90
+ # @option syms :instance_writer<Boolean> if true, instance-level inheritable attribute writer is defined.
91
+ # @return <Array[#to_s]> An Array of the attributes that were made into inheritable writers.
92
+ #
93
+ # @api public
94
+ #
95
+ # @todo We need a style for class_eval <<-HEREDOC. I'd like to make it
96
+ # class_eval(<<-RUBY, __FILE__, __LINE__), but we should codify it somewhere.
97
+ def class_inheritable_writer(*ivars)
98
+ instance_writer = ivars.pop[:writer] if ivars.last.is_a?(Hash)
99
+ ivars.each do |ivar|
100
+ self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
101
+ def self.#{ivar}=(obj)
102
+ @#{ivar} = obj
103
+ end
104
+ RUBY
105
+ unless instance_writer == false
106
+ self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
107
+ def #{ivar}=(obj) self.class.#{ivar} = obj end
108
+ RUBY
109
+ end
110
+
111
+ self.send("#{ivar}=", yield) if block_given?
112
+ end
113
+ end
114
+
115
+ # Defines class-level inheritable attribute accessor. Attributes are available to subclasses,
116
+ # each subclass has a copy of parent's attribute.
117
+ #
118
+ # @param *syms<Array[*#to_s, Hash{:instance_writer => Boolean}]> Array of attributes to
119
+ # define inheritable accessor for.
120
+ # @option syms :instance_writer<Boolean> if true, instance-level inheritable attribute writer is defined.
121
+ # @return <Array[#to_s]> An Array of attributes turned into inheritable accessors.
122
+ #
123
+ # @api public
124
+ def class_inheritable_accessor(*syms, &block)
125
+ class_inheritable_reader(*syms)
126
+ class_inheritable_writer(*syms, &block)
127
+ end
128
+
129
+ end
@@ -0,0 +1,51 @@
1
+ # blank? parts taken from the active support source
2
+ # code, used under the MIT License.
3
+
4
+ class Object
5
+ def blank?
6
+ respond_to?(:empty?) ? empty? : !self
7
+ end
8
+
9
+ def present?
10
+ !blank?
11
+ end
12
+
13
+ end
14
+
15
+ class NilClass #:nodoc:
16
+ def blank?
17
+ true
18
+ end
19
+ end
20
+
21
+ class FalseClass #:nodoc:
22
+ def blank?
23
+ true
24
+ end
25
+ end
26
+
27
+ class TrueClass #:nodoc:
28
+ def blank?
29
+ false
30
+ end
31
+ end
32
+
33
+ class Array #:nodoc:
34
+ alias_method :blank?, :empty?
35
+ end
36
+
37
+ class Hash #:nodoc:
38
+ alias_method :blank?, :empty?
39
+ end
40
+
41
+ class String #:nodoc:
42
+ def blank?
43
+ self !~ /\S/
44
+ end
45
+ end
46
+
47
+ class Numeric #:nodoc:
48
+ def blank?
49
+ false
50
+ end
51
+ end
@@ -0,0 +1,118 @@
1
+ class Object
2
+
3
+ def metaclass
4
+ class << self; self; end
5
+ end
6
+
7
+ end
8
+
9
+ class Inflector
10
+ class << self
11
+
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
+
31
+ module Kernel
32
+
33
+ # From http://oldrcrs.rubypal.com/rcr/show/309
34
+ def __DIR__(offset = 0)
35
+ (/^(.+)?:\d+/ =~ caller[offset + 1]) ? File.dirname($1) : nil
36
+ end
37
+
38
+ # Shorthand for lambda
39
+ # e.g. L{|r| puts r}
40
+ def L(&blk)
41
+ lambda(&blk)
42
+ end
43
+
44
+ # Shorthand for Proc.new
45
+ # e.g. P{|r| puts r}
46
+ def P(&blk)
47
+ Proc.new(&blk)
48
+ end
49
+ end
50
+
51
+ class String
52
+ def /(*args)
53
+ File.join(self, *args)
54
+ end
55
+
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
+ def to_pathname
65
+ Pathname.new(self)
66
+ end
67
+ end
68
+
69
+ class Array
70
+ def extract_options!
71
+ last.is_a?(Hash) ? pop : {}
72
+ end
73
+ end
74
+
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
+ class Module
90
+
91
+ def has_libary(*items)
92
+ namespace = self.to_s.underscore
93
+ items.each do |item|
94
+ require File.join(namespace, item.to_s.underscore)
95
+ end
96
+ end
97
+
98
+ def extends_library(*items)
99
+ namespace = self.to_s.underscore
100
+ items.each do |item|
101
+ klass = item.to_s.camelize.to_sym
102
+ # Load if it isn't loaded already.
103
+ const_get(klass) unless const_defined?(klass)
104
+ # And finally load the file.
105
+ require File.join(namespace, item.to_s.underscore)
106
+ end
107
+ end
108
+
109
+ def attempt_require(*files)
110
+ files.each do |file|
111
+ begin
112
+ require file
113
+ rescue LoadError
114
+ end
115
+ end
116
+ end
117
+
118
+ end
@@ -0,0 +1,4 @@
1
+ # Require each individial core extension
2
+ require 'perennial/core_ext/attribute_accessors'
3
+ require 'perennial/core_ext/blank'
4
+ require 'perennial/core_ext/misc'
@@ -0,0 +1,120 @@
1
+ module Perennial
2
+ # = Perennial::Daemon provides a relatively simple
3
+ # interface to creating unix daemons from a given process.
4
+ # Namely, it focuses on three providing tools for
5
+ # a couple of simple purposes.
6
+ #
7
+ # == Turning a process into a daemon
8
+ #
9
+ # This approach is as as simple as calling
10
+ # Perennial::Daemon.daemonize! - the current process
11
+ # will then be a daemon, the non-daemon processes
12
+ # will be killed and if started by the loader, it will
13
+ # also write a pid out.
14
+ #
15
+ # == Checking whether a process is alive
16
+ #
17
+ # Perennial::Daemon.alive?(pid) is a super simple way
18
+ # to check if a pid (as an integer) is current active / alive,
19
+ #
20
+ # Perennial::Daemon.any_alive? takes a type and tells you if
21
+ # any processes with the associated type are alive.
22
+ #
23
+ # == Storing / Retrieving Pids for a Given Loader Type
24
+ #
25
+ # Perennial::Daemon.daemonize! will store the pid for
26
+ # the current process if Perennial::Loader.current_type
27
+ # is present.
28
+ #
29
+ # Perennial::Daemon.any_alive?(type = :all) and
30
+ # Perennial::Daemon.kill_all(type = :all) can be
31
+ # used to deal with checking the status of and killing
32
+ # processes associated with a given loader type.
33
+ class Daemon
34
+ class << self
35
+
36
+ def any_alive?(type = :all)
37
+ !pid_file_for(type).empty?
38
+ end
39
+
40
+ # Returns true / false depending on whether
41
+ # a process with the given pid exists.
42
+ def alive?(pid)
43
+ return Process.getpgid(pid) != -1
44
+ rescue Errno::ESRCH
45
+ return false
46
+ end
47
+
48
+ # Kills all processes associated with a certain app type.
49
+ # E.g. Given a loader is starting a :client, kill_all(:client)
50
+ # would kill all associated processes (usually after daemonizing)
51
+ # and kill_all would do the same - but if we also started
52
+ # a process for :server, kill_all(:client wouldn't kill the
53
+ # the process where as kill_all would.
54
+ def kill_all(type = :all)
55
+ kill_all_from(pid_file_for(type))
56
+ return false
57
+ end
58
+
59
+ # Converts the current process into a Unix-daemon using
60
+ # the double fork approach. Also, changes process file
61
+ # mask to 000 and reopens STDIN / OUT to /dev/null
62
+ def daemonize!
63
+ exit if fork
64
+ Process.setsid
65
+ exit if fork
66
+ self.write_pid
67
+ File.umask 0000
68
+ STDIN.reopen "/dev/null"
69
+ STDOUT.reopen "/dev/null", "a"
70
+ STDERR.reopen STDOUT
71
+ Perennial::Settings.verbose = false
72
+ end
73
+
74
+ # Cleans up processes for the current application type
75
+ # (deteremined by the loader) and then removes the pid file.
76
+ def cleanup!
77
+ f = pids_file_for(Loader.current_type)
78
+ FileUtils.rm_f(f) if (pids_from(f) - Process.pid).blank?
79
+ end
80
+
81
+ # Returns an array of pid's associated with a given type.
82
+ def pids_for_type(type)
83
+ pids_from(pid_file_for(type))
84
+ end
85
+
86
+ protected
87
+
88
+ def kill_all_from(file)
89
+ pids = pids_from(file)
90
+ pids.each { |p| Process.kill("TERM", p) unless p == Process.pid }
91
+ FileUtils.rm_f(file)
92
+ rescue => e
93
+ STDOUT.puts e.inspect
94
+ end
95
+
96
+ def pid_file_for(type)
97
+ type = "*" if type == :all
98
+ Settings.root / "tmp" / "pids" / "#{type.to_s.underscore}.pid"
99
+ end
100
+
101
+ def pids_from(files)
102
+ pids = []
103
+ Dir[files].each do |file|
104
+ pids += File.read(file).split("\n").map { |l| l.strip.to_i(10) }
105
+ end
106
+ return pids.uniq.select { |p| alive?(p) }
107
+ end
108
+
109
+ def write_pid
110
+ type = Loader.current_type
111
+ return if type.blank?
112
+ f = pid_file_for(type)
113
+ pids = pids_from(f)
114
+ pids << Process.pid unless pids.include?(Process.pid)
115
+ File.open(f, "w+") { |f| f.puts pids.join("\n") }
116
+ end
117
+
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,110 @@
1
+ module Perennial
2
+
3
+ # = Perennial::Dispatchable
4
+ # A Generic mixin which lets you define an object
5
+ # Which accepts handlers which can have arbitrary
6
+ # events dispatched.
7
+ # == Usage
8
+ #
9
+ # class X
10
+ # include Perennial::Dispatchable
11
+ # self.handlers << SomeHandler.new
12
+ # end
13
+ # X.new.dispatch(:name, {:args => "Values"})
14
+ #
15
+ # Will first check if SomeHandler#handle_name exists,
16
+ # calling handle_name({:args => "Values"}) if it does,
17
+ # otherwise calling SomeHandler#handle(:name, {:args => "Values"})
18
+ module Dispatchable
19
+
20
+ def self.handler_mapping
21
+ @@handler_mapping ||= Hash.new { |h,k| h[k] = [] }
22
+ end
23
+
24
+ def self.included(parent)
25
+ parent.class_eval do
26
+ include InstanceMethods
27
+ extend ClassMethods
28
+ end
29
+ end
30
+
31
+ module InstanceMethods
32
+
33
+ # Returns the handlers registered on this class,
34
+ # used inside +dispatch+.
35
+ def handlers
36
+ self.class.handlers
37
+ end
38
+
39
+ # Dispatch an 'event' with a given name to the handlers
40
+ # registered on the current class. Used as a nicer way of defining
41
+ # behaviours that should occur under a given set of circumstances.
42
+ # == Params
43
+ # +name+: The name of the current event
44
+ # +opts+: an optional hash of options to pass
45
+ 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
62
+ end
63
+ 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
+ end
73
+
74
+ end
75
+
76
+ module ClassMethods
77
+
78
+ # Return an array of all registered handlers, ordered
79
+ # by their class and then the order of insertion. Please
80
+ # note that this will include ALL handlers up the inheritance
81
+ # chain unless false is passed as the only argument.
82
+ def handlers(recursive = true)
83
+ handlers = []
84
+ if recursive && superclass.respond_to?(:handlers)
85
+ handlers += superclass.handlers(recursive)
86
+ end
87
+ handlers += Dispatchable.handler_mapping[self]
88
+ return handlers
89
+ end
90
+
91
+ # Assigns a new array of handlers and assigns each - Note that
92
+ # this will only set this classes handlers, it will not override
93
+ # those for others above / below it in the inheritance chain.
94
+ def handlers=(new_value)
95
+ Dispatchable.handler_mapping.delete self
96
+ [*new_value].each { |h| register_handler h }
97
+ end
98
+
99
+ # Appends a handler to the list of handlers for this object.
100
+ # Handlers are called in the order they are registered.
101
+ def register_handler(handler)
102
+ unless handler.blank? || !handler.respond_to?(:handle)
103
+ Dispatchable.handler_mapping[self] << handler
104
+ end
105
+ end
106
+
107
+ end
108
+
109
+ end
110
+ end
@@ -0,0 +1,6 @@
1
+ module Perennial
2
+ # General Perennial exceptions.
3
+ class Error < StandardError; end
4
+ # Called to halt handler processing.
5
+ class HaltHandlerProcessing < Error; end
6
+ end
@@ -0,0 +1,63 @@
1
+ module Perennial
2
+ # = Perennial::Hookable
3
+ #
4
+ # Perennial::Hookable provides a generic set of functionality
5
+ # for implementing simple block-based hooks / callbacks. On
6
+ # a class level, this makes it easy for you to do event driven
7
+ # programming in that code can be registered to be run
8
+ # when something happens.
9
+ #
10
+ # Hookable differs from Perennial::Dispatchable in that it is
11
+ # designed to be lightweight / used for things like setting things
12
+ # up without all of the overhead of defining handlers / dispatching
13
+ # messages.
14
+ module Hookable
15
+
16
+ def self.included(parent)
17
+ parent.class_eval do
18
+ extend ClassMethods
19
+ cattr_accessor :hooks
20
+ self.hooks = Hash.new { |h,k| h[k] = [] }
21
+ end
22
+ end
23
+
24
+ module ClassMethods
25
+
26
+ # Append a hook for a given type of hook in order
27
+ # to be called later on via invoke_hooks!
28
+ def append_hook(type, &blk)
29
+ self.hooks_for(type) << blk unless blk.blank?
30
+ end
31
+
32
+ # Return all of the existing hooks or an empty
33
+ # for a given hook type.
34
+ def hooks_for(type)
35
+ self.hooks[type.to_sym]
36
+ end
37
+
38
+ # Invoke (call) all of the hooks for a given
39
+ # type.
40
+ def invoke_hooks!(type)
41
+ hooks_for(type).each { |hook| hook.call }
42
+ end
43
+
44
+ # Defines a set of handy methods to make it
45
+ # easy to define simplistic block based hooks
46
+ # on an arbitrary class.
47
+ def define_hook(*args)
48
+ klass = self.metaclass
49
+ args.map { |a| a.to_sym }.each do |name|
50
+
51
+ klass.class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
52
+ def #{name}(&blk) # def before_run(&blk)
53
+ append_hook(:#{name}, &blk) # append_hook(:before_run, &blk)
54
+ end # end
55
+ RUBY
56
+
57
+ end
58
+ end
59
+
60
+ end
61
+
62
+ end
63
+ end