perennial 0.2.2.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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