perennial 0.2.2.2
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/perennial +36 -0
- data/lib/perennial/application.rb +173 -0
- data/lib/perennial/argument_parser.rb +60 -0
- data/lib/perennial/core_ext/attribute_accessors.rb +129 -0
- data/lib/perennial/core_ext/blank.rb +51 -0
- data/lib/perennial/core_ext/misc.rb +118 -0
- data/lib/perennial/core_ext.rb +4 -0
- data/lib/perennial/daemon.rb +123 -0
- data/lib/perennial/dispatchable.rb +110 -0
- data/lib/perennial/exceptions.rb +6 -0
- data/lib/perennial/generator.rb +89 -0
- data/lib/perennial/hookable.rb +63 -0
- data/lib/perennial/loader.rb +103 -0
- data/lib/perennial/loggable.rb +15 -0
- data/lib/perennial/logger.rb +109 -0
- data/lib/perennial/manifest.rb +34 -0
- data/lib/perennial/option_parser.rb +105 -0
- data/lib/perennial/settings.rb +95 -0
- data/lib/perennial.rb +21 -0
- data/templates/application.erb +15 -0
- data/templates/boot.erb +2 -0
- data/templates/rakefile.erb +31 -0
- data/templates/setup.erb +4 -0
- data/templates/test.erb +9 -0
- data/templates/test_helper.erb +35 -0
- data/test/dispatchable_test.rb +129 -0
- data/test/hookable_test.rb +61 -0
- data/test/loader_test.rb +1 -0
- data/test/loggable_test.rb +38 -0
- data/test/logger_test.rb +57 -0
- data/test/settings_test.rb +99 -0
- data/test/test_helper.rb +38 -0
- data/vendor/fakefs/LICENSE +20 -0
- data/vendor/fakefs/README.markdown +37 -0
- data/vendor/fakefs/Rakefile +3 -0
- data/vendor/fakefs/lib/fakefs.rb +448 -0
- data/vendor/fakefs/test/fakefs_test.rb +511 -0
- data/vendor/fakefs/test/verify.rb +27 -0
- metadata +92 -0
data/bin/perennial
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require File.join(File.dirname(__FILE__), "..", "lib", "perennial")
|
3
|
+
|
4
|
+
Perennial::Application.processing(ARGV) do |a|
|
5
|
+
|
6
|
+
a.banner = "Perennial v#{Perennial::VERSION} CLI"
|
7
|
+
|
8
|
+
a.generator!
|
9
|
+
|
10
|
+
a.option(:force, "force the creation of the application")
|
11
|
+
a.add("create PATH [APP-NAME]", "Creates a Perennial-based library with a given PATH and, if provided, APP-NAME.") do |path, *args|
|
12
|
+
# Get the app name, path etc.
|
13
|
+
opts = args.extract_options!
|
14
|
+
app_name = args.empty? ? File.basename(path) : args.shift
|
15
|
+
path = File.expand_path(path)
|
16
|
+
# Check if the folder exists
|
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
|
20
|
+
end
|
21
|
+
# Convert the name and class name.
|
22
|
+
app_path = app_name.underscore
|
23
|
+
app_module = app_name.camelize
|
24
|
+
# Actually do the generation.
|
25
|
+
env = {:application_module => app_module, :application_path => app_path}
|
26
|
+
setup_generator path
|
27
|
+
folders 'tmp', 'config', 'lib', 'handlers', 'test'
|
28
|
+
template 'application.erb', "lib/#{app_path}.rb", env
|
29
|
+
template 'boot.erb', 'config/boot.rb', env
|
30
|
+
template 'setup.erb', 'config/setup.rb', env
|
31
|
+
template 'rakefile.erb', 'Rakefile', env
|
32
|
+
template 'test_helper.erb', 'test/test_helper.rb', env
|
33
|
+
template 'test.erb', "test/#{app_path}_test.rb", env
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
@@ -0,0 +1,173 @@
|
|
1
|
+
module Perennial
|
2
|
+
class Application
|
3
|
+
|
4
|
+
class CommandEnv
|
5
|
+
include Loggable
|
6
|
+
|
7
|
+
# Executes a given block with given
|
8
|
+
# arguments in a specified environment.
|
9
|
+
# Used to provide the logger functionality
|
10
|
+
# as well as the ability to do things
|
11
|
+
# such as easily write generators.
|
12
|
+
def self.execute(blk, arguments)
|
13
|
+
klass = Class.new(self)
|
14
|
+
klass.class_eval { define_method(:apply, &blk) }
|
15
|
+
klass.new.apply(*arguments)
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_accessor :options, :banner, :command_env
|
21
|
+
|
22
|
+
def initialize(opts = {})
|
23
|
+
@options = opts
|
24
|
+
@commands = {}
|
25
|
+
@descriptions = {}
|
26
|
+
@option_parsers = {}
|
27
|
+
@option_parser = nil
|
28
|
+
@command_options = {}
|
29
|
+
@command_env = (opts[:command_env] || CommandEnv)
|
30
|
+
@banners = {}
|
31
|
+
end
|
32
|
+
|
33
|
+
def option(name, description = nil)
|
34
|
+
option_parser.add(name, description) { |v| @command_options[name] = v }
|
35
|
+
end
|
36
|
+
|
37
|
+
def add_default_options!
|
38
|
+
option_parser.add_defaults!
|
39
|
+
end
|
40
|
+
|
41
|
+
def generator!
|
42
|
+
if defined?(Generator)
|
43
|
+
self.command_env = Generator::CommandEnv
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def controller!(controller, description)
|
48
|
+
return unless defined?(Loader)
|
49
|
+
add_default_options!
|
50
|
+
option :kill, "Kill any runninng instances"
|
51
|
+
controller_name = controller.to_s.underscore
|
52
|
+
controller = controller.to_sym
|
53
|
+
command_name = controller_name.gsub("_", "-")
|
54
|
+
add("#{command_name} [PATH]", description) do |*args|
|
55
|
+
options = args.extract_options!
|
56
|
+
path = File.expand_path(args[0] || ".")
|
57
|
+
Settings.root = path
|
58
|
+
if options.delete(:kill)
|
59
|
+
puts "Attempting to kill processess..."
|
60
|
+
Daemon.kill_all(controller)
|
61
|
+
else
|
62
|
+
Loader.run!(controller, options)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
|
69
|
+
def add(raw_command, description = nil, &blk)
|
70
|
+
raise ArgumentError, "You must provide a block with an #{self.class.name}#add" if blk.nil?
|
71
|
+
raise ArgumentError, "Your block must accept atleast one argument (a hash of options)" if blk.arity == 0
|
72
|
+
command, _ = raw_command.split(" ", 2)
|
73
|
+
@banners[command] = raw_command
|
74
|
+
@commands[command] = blk
|
75
|
+
@descriptions[command] = description if description.present?
|
76
|
+
# Add the default help message for a command
|
77
|
+
option_parser.add(:help, "Show this message") { help_for(command) }
|
78
|
+
@option_parsers[command] = option_parser
|
79
|
+
@option_parser = nil
|
80
|
+
end
|
81
|
+
|
82
|
+
def execute(arguments)
|
83
|
+
return usage if arguments.empty?
|
84
|
+
arguments = arguments.dup
|
85
|
+
command = arguments.shift
|
86
|
+
if @commands.has_key?(command)
|
87
|
+
execute_command(command, arguments)
|
88
|
+
else
|
89
|
+
puts "Unknown command '#{command}', please try again."
|
90
|
+
return usage
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def usage
|
95
|
+
if banner.present?
|
96
|
+
puts banner
|
97
|
+
puts ""
|
98
|
+
end
|
99
|
+
puts "Usage:"
|
100
|
+
max_width = @banners.values.map { |b| b.length }.max
|
101
|
+
@commands.keys.sort.each do |command|
|
102
|
+
next unless @descriptions.has_key?(command)
|
103
|
+
formatted_command = "#{@banners[command]} [OPTIONS]".ljust(max_width + 10)
|
104
|
+
command = "%s - %s" % [formatted_command, @descriptions[command]]
|
105
|
+
puts command
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def help_for(command)
|
110
|
+
if banner.present?
|
111
|
+
puts banner
|
112
|
+
puts ""
|
113
|
+
end
|
114
|
+
puts @descriptions[command]
|
115
|
+
puts "Usage: #{$0} #{@banners[command]} [options]"
|
116
|
+
puts "Options:"
|
117
|
+
puts @option_parsers[command].summary
|
118
|
+
exit
|
119
|
+
end
|
120
|
+
|
121
|
+
def self.processing(args, &blk)
|
122
|
+
application = self.new
|
123
|
+
if blk.arity == 1
|
124
|
+
blk.call(application)
|
125
|
+
else
|
126
|
+
application.instance_eval(&blk)
|
127
|
+
end
|
128
|
+
application.execute args
|
129
|
+
end
|
130
|
+
|
131
|
+
protected
|
132
|
+
|
133
|
+
def execute_command(command, arguments)
|
134
|
+
command_proc = @commands[command]
|
135
|
+
args, opts = extract_arguments(command, arguments)
|
136
|
+
if valid_arity?(command_proc, args)
|
137
|
+
args << opts
|
138
|
+
@command_env.execute(command_proc, args)
|
139
|
+
else
|
140
|
+
usage
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def extract_arguments(command, arguments)
|
145
|
+
option_parser = @option_parsers[command]
|
146
|
+
if option_parser.present?
|
147
|
+
option_parser.parse(arguments)
|
148
|
+
return option_parser.arguments, @command_options
|
149
|
+
else
|
150
|
+
return arguments, {}
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def option_parser(reset = false)
|
155
|
+
@option_parser = nil if reset
|
156
|
+
@option_parser ||= OptionParser.new
|
157
|
+
end
|
158
|
+
|
159
|
+
def valid_arity?(blk, arguments)
|
160
|
+
needed_count = blk.arity - 1
|
161
|
+
provided_count = arguments.size
|
162
|
+
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})"
|
164
|
+
elsif needed_count < 0 && (-needed_count - 2) > provided_count
|
165
|
+
puts "You didn't provide enough arguments - a minimum of #{-needed_count} are needed."
|
166
|
+
else
|
167
|
+
return true
|
168
|
+
end
|
169
|
+
|
170
|
+
end
|
171
|
+
|
172
|
+
end
|
173
|
+
end
|
@@ -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,123 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
module Perennial
|
4
|
+
# = Perennial::Daemon provides a relatively simple
|
5
|
+
# interface to creating unix daemons from a given process.
|
6
|
+
# Namely, it focuses on three providing tools for
|
7
|
+
# a couple of simple purposes.
|
8
|
+
#
|
9
|
+
# == Turning a process into a daemon
|
10
|
+
#
|
11
|
+
# This approach is as as simple as calling
|
12
|
+
# Perennial::Daemon.daemonize! - the current process
|
13
|
+
# will then be a daemon, the non-daemon processes
|
14
|
+
# will be killed and if started by the loader, it will
|
15
|
+
# also write a pid out.
|
16
|
+
#
|
17
|
+
# == Checking whether a process is alive
|
18
|
+
#
|
19
|
+
# Perennial::Daemon.alive?(pid) is a super simple way
|
20
|
+
# to check if a pid (as an integer) is current active / alive,
|
21
|
+
#
|
22
|
+
# Perennial::Daemon.any_alive? takes a type and tells you if
|
23
|
+
# any processes with the associated type are alive.
|
24
|
+
#
|
25
|
+
# == Storing / Retrieving Pids for a Given Loader Type
|
26
|
+
#
|
27
|
+
# Perennial::Daemon.daemonize! will store the pid for
|
28
|
+
# the current process if Perennial::Loader.current_type
|
29
|
+
# is present.
|
30
|
+
#
|
31
|
+
# Perennial::Daemon.any_alive?(type = :all) and
|
32
|
+
# Perennial::Daemon.kill_all(type = :all) can be
|
33
|
+
# used to deal with checking the status of and killing
|
34
|
+
# processes associated with a given loader type.
|
35
|
+
class Daemon
|
36
|
+
class << self
|
37
|
+
|
38
|
+
def any_alive?(type = :all)
|
39
|
+
!pid_file_for(type).empty?
|
40
|
+
end
|
41
|
+
|
42
|
+
# Returns true / false depending on whether
|
43
|
+
# a process with the given pid exists.
|
44
|
+
def alive?(pid)
|
45
|
+
return Process.getpgid(pid) != -1
|
46
|
+
rescue Errno::ESRCH
|
47
|
+
return false
|
48
|
+
end
|
49
|
+
|
50
|
+
# Kills all processes associated with a certain app type.
|
51
|
+
# E.g. Given a loader is starting a :client, kill_all(:client)
|
52
|
+
# would kill all associated processes (usually after daemonizing)
|
53
|
+
# and kill_all would do the same - but if we also started
|
54
|
+
# a process for :server, kill_all(:client wouldn't kill the
|
55
|
+
# the process where as kill_all would.
|
56
|
+
def kill_all(type = :all)
|
57
|
+
kill_all_from(pid_file_for(type))
|
58
|
+
return false
|
59
|
+
end
|
60
|
+
|
61
|
+
# Converts the current process into a Unix-daemon using
|
62
|
+
# the double fork approach. Also, changes process file
|
63
|
+
# mask to 000 and reopens STDIN / OUT to /dev/null
|
64
|
+
def daemonize!
|
65
|
+
exit if fork
|
66
|
+
Process.setsid
|
67
|
+
exit if fork
|
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
|
74
|
+
end
|
75
|
+
|
76
|
+
# Cleans up processes for the current application type
|
77
|
+
# (deteremined by the loader) and then removes the pid file.
|
78
|
+
def cleanup!
|
79
|
+
f = pids_file_for(Loader.current_type)
|
80
|
+
FileUtils.rm_f(f) if (pids_from(f) - Process.pid).blank?
|
81
|
+
end
|
82
|
+
|
83
|
+
# Returns an array of pid's associated with a given type.
|
84
|
+
def pids_for_type(type)
|
85
|
+
pids_from(pid_file_for(type))
|
86
|
+
end
|
87
|
+
|
88
|
+
protected
|
89
|
+
|
90
|
+
def kill_all_from(file)
|
91
|
+
pids = pids_from(file)
|
92
|
+
pids.each { |p| Process.kill("TERM", p) unless p == Process.pid }
|
93
|
+
FileUtils.rm_f(file)
|
94
|
+
rescue => e
|
95
|
+
STDOUT.puts e.inspect
|
96
|
+
end
|
97
|
+
|
98
|
+
def pid_file_for(type)
|
99
|
+
type = "*" if type == :all
|
100
|
+
Settings.root / "tmp" / "pids" / "#{type.to_s.underscore}.pid"
|
101
|
+
end
|
102
|
+
|
103
|
+
def pids_from(files)
|
104
|
+
pids = []
|
105
|
+
Dir[files].each do |file|
|
106
|
+
pids += File.read(file).split("\n").map { |l| l.strip.to_i(10) }
|
107
|
+
end
|
108
|
+
return pids.uniq.select { |p| alive?(p) }
|
109
|
+
end
|
110
|
+
|
111
|
+
def write_pid
|
112
|
+
type = Loader.current_type
|
113
|
+
return if type.blank?
|
114
|
+
f = pid_file_for(type)
|
115
|
+
pids = pids_from(f)
|
116
|
+
pids << Process.pid unless pids.include?(Process.pid)
|
117
|
+
FileUtils.mkdir_p(File.dirname(f))
|
118
|
+
File.open(f, "w+") { |f| f.puts pids.join("\n") }
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|