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.
@@ -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,89 @@
1
+ require 'open-uri'
2
+ require 'fileutils'
3
+ require 'erb'
4
+
5
+ module Perennial
6
+ class Generator
7
+
8
+ class CommandEnv < Perennial::Application::CommandEnv
9
+
10
+ def initialize
11
+ @generator = nil
12
+ end
13
+
14
+ protected
15
+
16
+ def setup_generator(*args)
17
+ @generator = Generator.new(*args)
18
+ end
19
+
20
+ def method_missing(name, *args, &blk)
21
+ if @generator && @generator.respond_to?(name)
22
+ @generator.send(name, *args, &blk)
23
+ else
24
+ super
25
+ end
26
+ end
27
+
28
+ end
29
+
30
+ attr_accessor :template_path, :destination_path
31
+
32
+ def initialize(destination, opts = {})
33
+ @destination_path = destination
34
+ @template_path = opts[:template_path] || File.join(Settings.library_root, "templates")
35
+ puts "Starting generator for #{destination}"
36
+ end
37
+
38
+ def download(from, to)
39
+ describe "Downloading #{from}"
40
+ file to, open(from).read
41
+ end
42
+
43
+ def folders(*args)
44
+ args.each do |f|
45
+ describe "Creating folder #{f}"
46
+ FileUtils.mkdir_p(expand_destination_path(f))
47
+ end
48
+ end
49
+
50
+ def file(name, contents)
51
+ dest_folder = File.dirname(name)
52
+ folders(dest_folder) unless File.directory?(expand_destination_path(dest_folder))
53
+ describe "Creating file #{name}"
54
+ File.open(expand_destination_path(name), "w+") do |f|
55
+ f.write(contents)
56
+ end
57
+ end
58
+
59
+ def template(source, destination, environment = {})
60
+ describe "Processing template #{source}"
61
+ raw_template = File.read(expand_template_path(source))
62
+ processed_template = ERB.new(raw_template).result(binding_for(environment))
63
+ file destination, processed_template
64
+ end
65
+
66
+ protected
67
+
68
+ def binding_for(hash = {})
69
+ object = Object.new
70
+ hash.each_pair do |k, v|
71
+ object.instance_variable_set("@#{k}", v)
72
+ end
73
+ return object.send(:binding)
74
+ end
75
+
76
+ def expand_template_path(p)
77
+ File.expand_path(p, @template_path)
78
+ end
79
+
80
+ def expand_destination_path(p)
81
+ File.expand_path(p, @destination_path)
82
+ end
83
+
84
+ def describe(action)
85
+ puts "- #{action}"
86
+ end
87
+
88
+ end
89
+ 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
@@ -0,0 +1,103 @@
1
+ require 'singleton'
2
+
3
+ module Perennial
4
+ class Loader
5
+ include Singleton
6
+ include Perennial::Hookable
7
+
8
+ cattr_accessor :controllers, :current_type, :default_type
9
+ @@controllers = {}
10
+
11
+ define_hook :before_run, :after_stop
12
+
13
+ def self.register_controller(name, controller)
14
+ return if name.blank? || controller.blank?
15
+ name = name.to_sym
16
+ @@controllers[name] = controller
17
+ metaclass.class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
18
+ def #{name}? # def client?
19
+ @@current_type == :#{name} # @@current_type == :client
20
+ end # end
21
+ RUBY
22
+ end
23
+
24
+ def self.run!(type = self.default_type, options = {})
25
+ @@current_type = type.to_sym
26
+ self.instance.run!(options)
27
+ end
28
+
29
+ def self.stop!(force = false)
30
+ self.instance.stop!(force)
31
+ end
32
+
33
+ def run!(options = {})
34
+ self.register_signals
35
+ self.class.invoke_hooks! :before_setup
36
+ Daemon.daemonize! if Settings.daemon?
37
+ Logger.log_name = "#{@@current_type.to_s}.log"
38
+ Logger.setup
39
+ Settings.setup
40
+ self.load_custom_code
41
+ self.class.invoke_hooks! :before_run
42
+ self.attempt_controller_action! :run, options
43
+ end
44
+
45
+ def stop!(force = false)
46
+ if force || !@attempted_stop
47
+ self.class.invoke_hooks! :before_stop
48
+ self.attempt_controller_action! :stop
49
+ self.class.invoke_hooks! :after_stop
50
+ @attempted_stop = true
51
+ end
52
+ Daemon.cleanup! if Settings.daemon?
53
+ end
54
+
55
+ def current_controller
56
+ @current_controller ||= @@controllers[@@current_type.to_sym]
57
+ end
58
+
59
+ protected
60
+
61
+ def load_custom_code
62
+ # Attempt to load a setup file given it exists.
63
+ begin
64
+ config_dir = Settings.root / "config"
65
+ setup_file = config_dir / "setup.rb"
66
+ require(setup_file) if File.directory?(config_dir) && File.exist?(setup_file)
67
+ rescue LoadError
68
+ end
69
+ # Load any existing handlers assuming we can find the folder
70
+ handler_directory = Settings.root / "handlers"
71
+ if File.directory?(handler_directory)
72
+ Dir[handler_directory / "**" / "*.rb"].each do |handler|
73
+ require handler
74
+ end
75
+ end
76
+ end
77
+
78
+ def register_signals
79
+ loader = self.class
80
+ %w(INT TERM).each do |signal|
81
+ trap(signal) do
82
+ loader.stop!
83
+ exit
84
+ end
85
+ end
86
+ end
87
+
88
+ def attempt_controller_action!(action, *args)
89
+ action = action.to_sym
90
+ unless current_controller.blank? || !current_controller.respond_to?(action)
91
+ method = current_controller.method(action)
92
+ if method.arity == 0
93
+ method.call
94
+ elsif method.arity < 0 || method.arity == args.size
95
+ method.call(*args)
96
+ else
97
+ raise ArgumentError, "controller action #{action} requires #{method.arity} arguments, provided #{args.size}"
98
+ end
99
+ end
100
+ end
101
+
102
+ end
103
+ end
@@ -0,0 +1,15 @@
1
+ module Perennial
2
+ # A mixin that provides logger instance and
3
+ # class methods
4
+ module Loggable
5
+
6
+ def self.included(parent)
7
+ parent.extend self
8
+ end
9
+
10
+ def logger
11
+ Logger
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1,109 @@
1
+ require 'fileutils'
2
+
3
+ module Perennial
4
+ class Logger
5
+
6
+ cattr_accessor :logger, :log_name
7
+
8
+ @@log_name = "perennial.log"
9
+ @@setup = false
10
+
11
+ class << self
12
+
13
+ def setup?
14
+ !!@@setup
15
+ end
16
+
17
+ def setup
18
+ return if setup?
19
+ setup!
20
+ end
21
+
22
+ def setup!
23
+ log_path = Settings.root / "log" / @@log_name.to_str
24
+ @@logger = new(log_path, Settings.log_level, Settings.verbose?)
25
+ @@setup = true
26
+ end
27
+
28
+ def method_missing(name, *args, &blk)
29
+ self.setup # Ensure the logger is setup
30
+ @@logger.send(name, *args, &blk)
31
+ end
32
+
33
+ def respond_to?(symbol, include_private = false)
34
+ self.setup
35
+ super(symbol, include_private) || @@logger.respond_to?(symbol, include_private)
36
+ end
37
+
38
+ end
39
+
40
+ LEVELS = {
41
+ :fatal => 7,
42
+ :error => 6,
43
+ :warn => 4,
44
+ :info => 3,
45
+ :debug => 0
46
+ }
47
+
48
+ PREFIXES = {}
49
+
50
+ LEVELS.each { |k,v| PREFIXES[k] = "[#{k.to_s.upcase}]".ljust 7 }
51
+
52
+ COLOURS = {
53
+ :fatal => 31, # red
54
+ :error => 33, # yellow
55
+ :warn => 35, # magenta
56
+ :info => 32, # green
57
+ :debug => 34 # white
58
+ }
59
+
60
+ attr_accessor :level, :file, :verbose
61
+
62
+ def initialize(path, level = :info, verbose = Settings.verbose?)
63
+ @level = level.to_sym
64
+ @verbose = verbose
65
+ FileUtils.mkdir_p(File.dirname(path))
66
+ @file = File.open(path, "a+")
67
+ end
68
+
69
+ def close!
70
+ @file.close
71
+ end
72
+
73
+ LEVELS.each do |name, value|
74
+ define_method(name) do |message|
75
+ write("#{PREFIXES[name]} #{message}", name) if LEVELS[@level] <= value
76
+ end
77
+
78
+ define_method(:"#{name}?") do
79
+ LEVELS[@level] <= value
80
+ end
81
+
82
+ end
83
+
84
+ def log_exception(exception)
85
+ error "Exception: #{exception}"
86
+ exception.backtrace.each do |l|
87
+ error ">> #{l}"
88
+ end
89
+ end
90
+
91
+ def verbose?
92
+ !!@verbose
93
+ end
94
+
95
+ private
96
+
97
+ def write(message, level = self.level)
98
+ @file.puts message
99
+ @file.flush
100
+ $stdout.puts colourize(message, level) if verbose?
101
+ end
102
+
103
+ def colourize(message, level)
104
+ "\033[1;#{COLOURS[level]}m#{message}\033[0m"
105
+ end
106
+
107
+
108
+ end
109
+ end
@@ -0,0 +1,34 @@
1
+ module Perennial
2
+ class Manifest
3
+
4
+ class_inheritable_accessor :app_name, :namespace
5
+ self.app_name = :perennial
6
+ self.namespace = Perennial
7
+
8
+ def self.inspect
9
+ "#<#{self.name} app_name: #{self.app_name.inspect}, namespace: #{self.namespace.inspect}>"
10
+ end
11
+
12
+ module Mixin
13
+ # Called in your application to set the default
14
+ # namespace and app_name. Also, if a block is
15
+ # provided it yields first with Manifest and then
16
+ # with the Loader class, making it simpler to setup.
17
+ def manifest(&blk)
18
+ Manifest.namespace = self
19
+ Manifest.app_name = self.name.to_s.underscore
20
+ parent_folder = File.expand_path(File.dirname(__DIR__(0)))
21
+ Settings.library_root = parent_folder
22
+ libary_folder = parent_folder / 'lib'/ Manifest.app_name
23
+ attempt_require((libary_folder / 'core_ext'), (libary_folder / 'exceptions'))
24
+ unless blk.nil?
25
+ args = []
26
+ args << Manifest if blk.arity != 0
27
+ args << Loader if blk.arity > 1 || blk.arity < 0
28
+ blk.call(*args)
29
+ end
30
+ end
31
+ end
32
+
33
+ end
34
+ end
@@ -0,0 +1,105 @@
1
+ module Perennial
2
+ class OptionParser
3
+
4
+ attr_reader :arguments
5
+
6
+ def initialize
7
+ @parsed_values = {}
8
+ @arguments = []
9
+ @callbacks = {}
10
+ @descriptions = {}
11
+ @shortcuts = {}
12
+ end
13
+
14
+ def add(name, description, opts = {}, &blk)
15
+ name = name.to_sym
16
+ @callbacks[name] = blk
17
+ @descriptions[name] = description
18
+ shortcut = opts.has_key?(:shortcut) ? opts[:shortcut] : generate_default_shortcut(name)
19
+ @shortcuts[shortcut] = name unless shortcut.blank?
20
+ end
21
+
22
+ def summary
23
+ output = []
24
+ max_length = 0
25
+ @callbacks.each_key do |name|
26
+ shortcuts = []
27
+ @shortcuts.each_pair { |k,v| shortcuts << k if v == name }
28
+ text = "--#{name.to_s.gsub("_", "-")}"
29
+ text << ", #{shortcuts.map { |sc| "-#{sc}" }.join(", ")}" unless shortcuts.empty?
30
+ max_length = [text.size, max_length].max
31
+ output << [text, @descriptions[name]]
32
+ end
33
+ output.map { |text, description| "#{text.ljust(max_length)} - #{description}" }.join("\n")
34
+ end
35
+
36
+ def parse(arguments = ARGV)
37
+ arguments, options = ArgumentParser.parse(arguments)
38
+ @arguments = arguments
39
+ options.each_pair do |name, value|
40
+ name = name.gsub("-", "_")
41
+ expanded_name = @shortcuts[name] || name.to_sym
42
+ callback = @callbacks[expanded_name]
43
+ callback.call(value) if callback.present?
44
+ end
45
+ return nil
46
+ end
47
+
48
+ # Over ride with your apps custom banner
49
+ def self.print_banner
50
+ end
51
+
52
+ def add_defaults!
53
+ return if defined?(@defaults_added) && @defaults_added
54
+ logger_levels = Logger::LEVELS.keys.map { |k| k.to_s }
55
+ add(:daemon, 'Runs this application as a daemon') { Settings.daemon = true }
56
+ add(:verbose, 'Runs this application verbosely, writing to STDOUT') { Settings.verbose = true }
57
+ add(:log_level, "Sets this applications log level, one of: #{logger_levels.join(", ")}") do |level|
58
+ if logger_levels.include?(level)
59
+ Settings.log_level = level.to_sym
60
+ else
61
+ puts "The provided log level must be one of #{logger_levels.join(", ")} (Given #{level})"
62
+ exit!
63
+ end
64
+ end
65
+ add(:help, "Shows this help message") do
66
+ self.print_banner
67
+ $stdout.puts "Usage: #{$0} [options]"
68
+ $stdout.puts "\nOptions:"
69
+ $stdout.puts self.summary
70
+ exit!
71
+ end
72
+ @defaults_added = true
73
+ end
74
+
75
+ def self.default
76
+ return @default if defined?(@default) && @default.present?
77
+ @default = setup_default!
78
+ end
79
+
80
+ def self.parse_argv(with = default)
81
+ with.parse
82
+ ARGV.replace with.arguments
83
+ end
84
+
85
+ def self.setup_default!
86
+ opts = self.new
87
+ opts.add_defaults!
88
+ return opts
89
+ end
90
+
91
+ protected
92
+
93
+ def generate_default_shortcut(name)
94
+ raw = name.to_s[0, 1]
95
+ if !@shortcuts.has_key?(raw)
96
+ return raw
97
+ elsif !@shortcuts.has_key?(raw.upcase)
98
+ return raw.upcase
99
+ else
100
+ raise "No shortcut option could generate for '#{name}' - Please specify :short (possibly as nil) to override"
101
+ end
102
+ end
103
+
104
+ end
105
+ end