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