Sutto-perennial 0.1.0

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,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