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