perennial 0.2.2.2

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