ing 0.1.5 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +2 -2
- data/lib/ing.rb +64 -42
- data/lib/ing/command.rb +96 -0
- data/lib/ing/{boot.rb → commands/boot.rb} +21 -21
- data/lib/ing/commands/generate.rb +5 -0
- data/lib/ing/commands/help.rb +10 -5
- data/lib/ing/commands/list.rb +13 -9
- data/lib/ing/files.rb +2 -17
- data/lib/ing/generator.rb +6 -0
- data/lib/ing/option_parsers/trollop.rb +35 -0
- data/lib/ing/task.rb +50 -4
- data/lib/ing/util.rb +10 -11
- data/lib/ing/version.rb +1 -1
- data/test/acceptance/ing_run_tests.rb +22 -1
- data/test/suite.rb +1 -1
- data/test/unit/command_test.rb +334 -0
- data/test/unit/ing_test.rb +67 -0
- metadata +13 -8
- data/lib/ing/dispatcher.rb +0 -143
data/README.md
CHANGED
@@ -32,8 +32,8 @@ The ing command line is generally parsed as
|
|
32
32
|
|
33
33
|
[ing command] [ing command options] [subcommand] [args] [subcommand options]
|
34
34
|
|
35
|
-
But in cases where the first argument isn't
|
36
|
-
simplified to
|
35
|
+
But in cases where the first argument isn't a built-in ing command or options,
|
36
|
+
it's simplified to
|
37
37
|
|
38
38
|
[subcommand] [args] [subcommand options]
|
39
39
|
|
data/lib/ing.rb
CHANGED
@@ -1,25 +1,26 @@
|
|
1
1
|
['ing/version',
|
2
|
+
'ing/util',
|
2
3
|
'ing/lib_trollop',
|
3
4
|
'ing/trollop/parser',
|
4
|
-
'ing/
|
5
|
-
'ing/dispatcher',
|
5
|
+
'ing/option_parsers/trollop',
|
6
6
|
'ing/shell',
|
7
7
|
'ing/common_options',
|
8
|
-
'ing/boot',
|
9
8
|
'ing/files',
|
10
9
|
'ing/task',
|
11
10
|
'ing/generator',
|
11
|
+
'ing/command',
|
12
|
+
'ing/commands/boot',
|
12
13
|
'ing/commands/implicit',
|
14
|
+
'ing/commands/generate',
|
13
15
|
'ing/commands/list',
|
14
|
-
'ing/commands/help'
|
15
|
-
'ing/commands/generate'
|
16
|
+
'ing/commands/help'
|
16
17
|
].each do |f|
|
17
18
|
require_relative f
|
18
19
|
end
|
19
20
|
|
20
21
|
module Ing
|
21
22
|
extend self
|
22
|
-
|
23
|
+
|
23
24
|
Error = Class.new(StandardError)
|
24
25
|
FileNotFoundError = Class.new(Error)
|
25
26
|
|
@@ -29,53 +30,74 @@ module Ing
|
|
29
30
|
@shell_class ||= Shell::Basic
|
30
31
|
end
|
31
32
|
|
32
|
-
|
33
|
-
|
33
|
+
|
34
|
+
class << (Callstack = Object.new)
|
35
|
+
|
36
|
+
def index(klass, meth)
|
37
|
+
stack.index {|e| e == [klass,meth]}
|
38
|
+
end
|
39
|
+
|
40
|
+
def push(klass, meth)
|
41
|
+
stack << [klass, meth]
|
42
|
+
end
|
43
|
+
|
44
|
+
def clear
|
45
|
+
stack.clear
|
46
|
+
end
|
47
|
+
|
48
|
+
def to_a
|
49
|
+
stack.dup
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
def stack
|
54
|
+
@stack ||= []
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
def run(args=ARGV)
|
60
|
+
booter = extract_boot_class!(args) || implicit_booter
|
61
|
+
execute booter, *args
|
34
62
|
end
|
35
63
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
#
|
41
|
-
def run(argv=ARGV)
|
42
|
-
booter = extract_boot_class!(argv) || implicit_booter
|
43
|
-
run_boot booter, "call", *argv
|
64
|
+
def execute(klass, meth=:call, *args, &config)
|
65
|
+
cmd = command.new(klass, meth, *args)
|
66
|
+
_callstack.push(cmd.command_class, cmd.command_meth)
|
67
|
+
cmd.execute(&config)
|
44
68
|
end
|
45
69
|
|
46
|
-
|
47
|
-
|
48
|
-
# if it hasn't been run yet. For example,
|
49
|
-
#
|
50
|
-
# invoke Some::Task, :some_instance_method, some_argument, :some_option => true
|
51
|
-
#
|
52
|
-
# You can skip the method and it will assume +#call+ :
|
53
|
-
#
|
54
|
-
# invoke Some::Task, :some_option => true
|
55
|
-
def invoke(klass, *args)
|
56
|
-
run_boot implicit_booter, "call_invoke", klass, *args
|
70
|
+
def invoke(klass, meth=:call, *args, &config)
|
71
|
+
execute(klass, meth, *args, &config) unless executed?(klass, meth)
|
57
72
|
end
|
58
73
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
run_boot implicit_booter, "call_execute", klass, *args
|
74
|
+
def executed?(klass, meth)
|
75
|
+
!!_callstack.index(klass, meth)
|
76
|
+
end
|
77
|
+
|
78
|
+
def callstack
|
79
|
+
_callstack.to_a
|
66
80
|
end
|
67
81
|
|
68
82
|
private
|
69
83
|
|
70
|
-
def
|
71
|
-
|
84
|
+
def command
|
85
|
+
Command
|
86
|
+
end
|
87
|
+
|
88
|
+
def _callstack
|
89
|
+
Callstack
|
90
|
+
end
|
91
|
+
|
92
|
+
def implicit_booter
|
93
|
+
Commands::Implicit
|
72
94
|
end
|
73
95
|
|
74
96
|
def extract_boot_class!(args)
|
75
|
-
c = Util.
|
76
|
-
|
77
|
-
|
78
|
-
|
97
|
+
c = Util.decode_class(args.first, Ing::Commands)
|
98
|
+
args.shift; c
|
99
|
+
rescue NameError
|
100
|
+
nil
|
79
101
|
end
|
80
|
-
|
81
|
-
end
|
102
|
+
|
103
|
+
end
|
data/lib/ing/command.rb
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
module Ing
|
2
|
+
|
3
|
+
class Command
|
4
|
+
|
5
|
+
class << self
|
6
|
+
attr_writer :parser
|
7
|
+
def parser
|
8
|
+
OptionParsers::Trollop
|
9
|
+
end
|
10
|
+
|
11
|
+
def execute(klass, *args, &config)
|
12
|
+
new(klass, *args).execute(&config)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_accessor :options, :command_class, :command_meth, :args
|
17
|
+
|
18
|
+
def initialize(klass, *args)
|
19
|
+
self.options = (Hash === args.last ? args.pop : {})
|
20
|
+
self.command_class = klass
|
21
|
+
self.command_meth = extract_method!(args, command_class)
|
22
|
+
self.args = args
|
23
|
+
end
|
24
|
+
|
25
|
+
def classy?
|
26
|
+
command_class.respond_to?(:new)
|
27
|
+
end
|
28
|
+
|
29
|
+
def instance
|
30
|
+
@instance ||= build_command
|
31
|
+
end
|
32
|
+
|
33
|
+
def execute
|
34
|
+
yield instance if block_given?
|
35
|
+
classy? ? instance.send(command_meth, *args) :
|
36
|
+
instance.send(command_meth, *args, options)
|
37
|
+
end
|
38
|
+
|
39
|
+
def describe
|
40
|
+
with_option_parser {|p| p.describe}
|
41
|
+
end
|
42
|
+
|
43
|
+
def help
|
44
|
+
with_option_parser {|p| p.help}
|
45
|
+
end
|
46
|
+
|
47
|
+
def with_option_parser
|
48
|
+
return unless command_class.respond_to?(:specify_options)
|
49
|
+
p = self.class.parser.new
|
50
|
+
command_class.specify_options(p.parser)
|
51
|
+
yield p
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def build_command
|
57
|
+
parse_options!
|
58
|
+
classy? ? command_class.new(options) : command_class
|
59
|
+
end
|
60
|
+
|
61
|
+
# Note options merged into parsed options (reverse merge)
|
62
|
+
# so that passed options (in direct invoke or execute) override defaults
|
63
|
+
def parse_options!
|
64
|
+
self.options = (parsed_options_from_args || {}).merge(self.options)
|
65
|
+
end
|
66
|
+
|
67
|
+
# memoized to avoid duplicate args processing
|
68
|
+
def parsed_options_from_args
|
69
|
+
@parsed_options ||= with_option_parser do |p|
|
70
|
+
p.parse! self.args
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def extract_method!(args, klass)
|
75
|
+
return :call if args.empty?
|
76
|
+
if meth = whitelist(args.first, klass)
|
77
|
+
args.shift
|
78
|
+
else
|
79
|
+
meth = :call
|
80
|
+
end
|
81
|
+
meth
|
82
|
+
end
|
83
|
+
|
84
|
+
# Note this currently does no filtering, but basically checks for respond_to
|
85
|
+
def whitelist(meth, klass)
|
86
|
+
finder = Proc.new {|m| m == meth.to_sym}
|
87
|
+
if klass.respond_to?(:new)
|
88
|
+
klass.public_instance_methods(true).find(&finder)
|
89
|
+
else
|
90
|
+
klass.public_methods.find(&finder)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
@@ -8,6 +8,14 @@
|
|
8
8
|
#
|
9
9
|
module Boot
|
10
10
|
|
11
|
+
# Before hook passed unprocessed args, override in target class
|
12
|
+
def before(*args)
|
13
|
+
end
|
14
|
+
|
15
|
+
# After hook, override in target class
|
16
|
+
def after
|
17
|
+
end
|
18
|
+
|
11
19
|
# Configure the command prior to dispatch.
|
12
20
|
# Default behavior is to set the shell of the dispatched command.
|
13
21
|
# Override in target class as needed; if you want to keep the default
|
@@ -15,7 +23,7 @@
|
|
15
23
|
def configure_command(cmd)
|
16
24
|
cmd.shell = Ing.shell_class.new if cmd.respond_to?(:"shell=")
|
17
25
|
end
|
18
|
-
|
26
|
+
|
19
27
|
# Main processing of arguments and dispatch from command line (+Ing.run+)
|
20
28
|
# Note that three hooks are provided for target classes,
|
21
29
|
# +before+:: runs before any processing of arguments or dispatch of command
|
@@ -23,32 +31,24 @@
|
|
23
31
|
# +after+:: runs after command dispatched
|
24
32
|
#
|
25
33
|
def call(*args)
|
26
|
-
before *args
|
27
|
-
|
28
|
-
|
29
|
-
Dispatcher.new(ns, classes, *args).dispatch do |cmd|
|
34
|
+
before *args
|
35
|
+
klass = _extract_class!(args)
|
36
|
+
Ing.execute(klass, *args) do |cmd|
|
30
37
|
configure_command cmd
|
31
38
|
end
|
32
|
-
after
|
39
|
+
after
|
33
40
|
end
|
41
|
+
|
42
|
+
private
|
34
43
|
|
35
|
-
|
36
|
-
|
37
|
-
before *args if respond_to?(:before)
|
38
|
-
Dispatcher.invoke(klass, meth, *args) do |cmd|
|
39
|
-
configure_command cmd
|
40
|
-
end
|
41
|
-
after if respond_to?(:after)
|
44
|
+
def _extract_class!(args)
|
45
|
+
Util.decode_class(args.shift, _namespace_class)
|
42
46
|
end
|
43
47
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
Dispatcher.execute(klass, meth, *args) do |cmd|
|
48
|
-
configure_command cmd
|
49
|
-
end
|
50
|
-
after if respond_to?(:after)
|
48
|
+
def _namespace_class
|
49
|
+
return ::Object unless ns = options[:namespace]
|
50
|
+
Util.decode_class(ns)
|
51
51
|
end
|
52
|
-
|
52
|
+
|
53
53
|
end
|
54
54
|
end
|
@@ -26,6 +26,11 @@ module Ing
|
|
26
26
|
@generator_root ||= File.expand_path(options[:gen_root])
|
27
27
|
end
|
28
28
|
|
29
|
+
attr_accessor :options
|
30
|
+
def initialize(options)
|
31
|
+
self.options = options
|
32
|
+
end
|
33
|
+
|
29
34
|
# Require libs and ing_file, then
|
30
35
|
# locate and require the generator ruby file identified by the first arg,
|
31
36
|
# before dispatching to it.
|
data/lib/ing/commands/help.rb
CHANGED
@@ -36,14 +36,19 @@
|
|
36
36
|
require_ing_file
|
37
37
|
end
|
38
38
|
|
39
|
-
def call(cmd="help")
|
39
|
+
def call(cmd="help")
|
40
40
|
before
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
shell.say help.read
|
41
|
+
klass = Util.decode_class(cmd, _namespace_class)
|
42
|
+
help = Command.new(klass).help
|
43
|
+
shell.say help
|
45
44
|
end
|
46
45
|
|
46
|
+
private
|
47
|
+
def _namespace_class
|
48
|
+
return ::Object unless ns = options[:namespace]
|
49
|
+
Util.decode_class(ns)
|
50
|
+
end
|
51
|
+
|
47
52
|
end
|
48
53
|
|
49
54
|
H = Help
|
data/lib/ing/commands/list.rb
CHANGED
@@ -36,28 +36,32 @@
|
|
36
36
|
require_ing_file
|
37
37
|
end
|
38
38
|
|
39
|
-
def call(
|
39
|
+
def call(ns=nil)
|
40
40
|
before
|
41
|
-
|
42
|
-
mod = Ing::Util.namespaced_const_get(ns)
|
41
|
+
mod = _namespace_class(ns)
|
43
42
|
data = mod.constants.map do |c|
|
44
|
-
|
45
|
-
|
46
|
-
|
43
|
+
klass = mod.const_get(c)
|
44
|
+
desc = (Command.new(klass).describe || '')[/.+$/]
|
45
|
+
[ "ing #{Ing::Util.encode_class(mod)}:#{Ing::Util.encode_class_names([c])}",
|
46
|
+
(desc || '(no description)').chomp
|
47
47
|
]
|
48
48
|
end.sort
|
49
|
-
shell.say desc_lines(
|
49
|
+
shell.say desc_lines(mod, data).join("\n")
|
50
50
|
end
|
51
51
|
|
52
52
|
private
|
53
|
+
def _namespace_class(ns=options[:namespace])
|
54
|
+
return ::Ing::Commands unless ns
|
55
|
+
Util.decode_class(ns)
|
56
|
+
end
|
53
57
|
|
54
|
-
def desc_lines(
|
58
|
+
def desc_lines(mod, data)
|
55
59
|
colwidths = data.inject([0,0]) {|max, (line, desc)|
|
56
60
|
max[0] = line.length if line.length > max[0]
|
57
61
|
max[1] = desc.length if desc.length > max[1]
|
58
62
|
max
|
59
63
|
}
|
60
|
-
["#{
|
64
|
+
["#{mod}: all tasks",
|
61
65
|
"-" * 80
|
62
66
|
] +
|
63
67
|
data.map {|line, desc|
|
data/lib/ing/files.rb
CHANGED
@@ -40,26 +40,11 @@ module Ing
|
|
40
40
|
# attr_reader :source_root, :destination_root
|
41
41
|
# attr_reader :shell, :options
|
42
42
|
#
|
43
|
-
#
|
44
|
-
#
|
43
|
+
# and should provide these options (otherwise all defaulted nil):
|
44
|
+
# verbose, force, pretend, revoke, quiet, skip.
|
45
45
|
#
|
46
46
|
module Files
|
47
47
|
|
48
|
-
# a bit of trickiness to change a singleton method...
|
49
|
-
def self.included(base)
|
50
|
-
meth = base.method(:specify_options) if base.respond_to?(:specify_options)
|
51
|
-
base.send(:define_singleton_method, :specify_options) do |expect|
|
52
|
-
meth.call(expect) if meth
|
53
|
-
expect.text "\nCommon Options:"
|
54
|
-
expect.opt :verbose, "Run verbosely by default", :short => nil
|
55
|
-
expect.opt :force, "Overwrite files that already exist", :short => nil
|
56
|
-
expect.opt :pretend, "Run but do not make any changes", :short => nil
|
57
|
-
expect.opt :revoke, "Revoke action (not available for all generators)", :short => nil
|
58
|
-
expect.opt :quiet, "Suppress status output", :short => nil
|
59
|
-
expect.opt :skip, "Skip files that already exist", :short => nil
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
48
|
def pretend?
|
64
49
|
!!options[:pretend]
|
65
50
|
end
|
data/lib/ing/generator.rb
CHANGED
@@ -3,6 +3,12 @@
|
|
3
3
|
|
4
4
|
opt :dest, "Destination root", :type => :string
|
5
5
|
opt :source, "Source root", :type => :string
|
6
|
+
opt :verbose, "Run verbosely by default"
|
7
|
+
opt :force, "Overwrite files that already exist"
|
8
|
+
opt :pretend, "Run but do not make any changes"
|
9
|
+
opt :revoke, "Revoke action (not available for all generators)"
|
10
|
+
opt :quiet, "Suppress status output"
|
11
|
+
opt :skip, "Skip files that already exist"
|
6
12
|
|
7
13
|
include Ing::Files
|
8
14
|
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
module Ing
|
3
|
+
|
4
|
+
# Classes in this namespace provide a uniform interface to different
|
5
|
+
# option parsers.
|
6
|
+
#
|
7
|
+
module OptionParsers
|
8
|
+
|
9
|
+
class Trollop
|
10
|
+
|
11
|
+
def parser
|
12
|
+
@parser ||= ::Trollop::Parser.new
|
13
|
+
end
|
14
|
+
|
15
|
+
def parse!(args)
|
16
|
+
::Trollop.with_standard_exception_handling(parser) { parser.parse(args) }
|
17
|
+
end
|
18
|
+
|
19
|
+
def describe
|
20
|
+
s=StringIO.new
|
21
|
+
parser.educate_banner s
|
22
|
+
s.rewind; s.read
|
23
|
+
end
|
24
|
+
|
25
|
+
def help
|
26
|
+
s=StringIO.new
|
27
|
+
parser.educate s
|
28
|
+
s.rewind; s.read
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
data/lib/ing/task.rb
CHANGED
@@ -11,8 +11,18 @@ module Ing
|
|
11
11
|
|
12
12
|
class << self
|
13
13
|
|
14
|
+
attr_accessor :inherited_options
|
15
|
+
|
14
16
|
def inherited(subclass)
|
15
|
-
subclass.
|
17
|
+
subclass.inherited_options = self.options.dup
|
18
|
+
end
|
19
|
+
|
20
|
+
def inherited_option?(name)
|
21
|
+
inherited_options.has_key?(name)
|
22
|
+
end
|
23
|
+
|
24
|
+
def option?(name)
|
25
|
+
options.has_key?(name)
|
16
26
|
end
|
17
27
|
|
18
28
|
# Modify the option named +name+ according to +specs+ (Hash).
|
@@ -23,8 +33,13 @@ module Ing
|
|
23
33
|
# modify_option :file, :required => true
|
24
34
|
#
|
25
35
|
def modify_option(name, specs)
|
26
|
-
|
27
|
-
|
36
|
+
if inherited_option?(name)
|
37
|
+
inherited_options[name].opts.merge!(specs)
|
38
|
+
elsif option?(name)
|
39
|
+
options[name].opts.merge!(specs)
|
40
|
+
else
|
41
|
+
opt(name, '', specs)
|
42
|
+
end
|
28
43
|
end
|
29
44
|
|
30
45
|
# Modify the default for option +name+ to +val+.
|
@@ -69,6 +84,12 @@ module Ing
|
|
69
84
|
parser.opt *opt.to_args
|
70
85
|
end
|
71
86
|
end
|
87
|
+
unless inherited_options.empty?
|
88
|
+
parser.text "\nCommon Options:"
|
89
|
+
inherited_options.each do |name, opt|
|
90
|
+
parser.opt *opt.to_args
|
91
|
+
end
|
92
|
+
end
|
72
93
|
end
|
73
94
|
|
74
95
|
# Description lines
|
@@ -82,7 +103,7 @@ module Ing
|
|
82
103
|
end
|
83
104
|
|
84
105
|
# Options hash. Note that in a subclass, options are copied down from
|
85
|
-
# superclass.
|
106
|
+
# superclass into inherited_options.
|
86
107
|
def options
|
87
108
|
@options ||= {}
|
88
109
|
end
|
@@ -105,6 +126,31 @@ module Ing
|
|
105
126
|
given
|
106
127
|
end
|
107
128
|
|
129
|
+
# Build a hash of options that weren't given from command line via +ask+
|
130
|
+
# (i.e., $stdin.gets).
|
131
|
+
#
|
132
|
+
# Note it currently does not cast options to appropriate types.
|
133
|
+
# Also note because the shell is not available until after initialization,
|
134
|
+
# this must be called from command method(s), e.g. +#call+
|
135
|
+
#
|
136
|
+
def ask_unless_given(*opts)
|
137
|
+
opts.inject({}) do |memo, opt|
|
138
|
+
next memo if options[:"#{opt}_given"]
|
139
|
+
msg = self.class.options[opt].desc + "?"
|
140
|
+
df = self.class.options[opt].default
|
141
|
+
memo[opt] = shell.ask(msg, :default => df)
|
142
|
+
memo
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
# Shortcut for:
|
147
|
+
#
|
148
|
+
# options.merge! ask_unless_given :opt1, :opt2
|
149
|
+
#
|
150
|
+
def ask_unless_given!(*opts)
|
151
|
+
self.options.merge! ask_unless_given(*opts)
|
152
|
+
end
|
153
|
+
|
108
154
|
# Use in initialization for option validation (post-parsing).
|
109
155
|
#
|
110
156
|
# Example:
|
data/lib/ing/util.rb
CHANGED
@@ -2,10 +2,17 @@
|
|
2
2
|
module Util
|
3
3
|
extend self
|
4
4
|
|
5
|
-
def
|
5
|
+
def decode_class(str, base=::Object)
|
6
|
+
namespaced_const_get( decode_class_names(str), base )
|
7
|
+
end
|
8
|
+
|
9
|
+
def decode_class_names(str)
|
6
10
|
str.split(':').map {|c| c.gsub(/(?:\A|_+)(\w)/) {$1.upcase} }
|
11
|
+
end
|
12
|
+
|
13
|
+
def encode_class(klass)
|
14
|
+
encode_class_names(klass.to_s.split('::'))
|
7
15
|
end
|
8
|
-
alias decode_class_names to_class_names
|
9
16
|
|
10
17
|
def encode_class_names(list)
|
11
18
|
list.map {|c| c.to_s.gsub(/([A-Z])/) {
|
@@ -13,15 +20,7 @@
|
|
13
20
|
}
|
14
21
|
}.join(':')
|
15
22
|
end
|
16
|
-
|
17
|
-
def encode_class(klass)
|
18
|
-
encode_class_names(klass.to_s.split('::'))
|
19
|
-
end
|
20
|
-
|
21
|
-
def to_classes(str, base=::Object)
|
22
|
-
namespaced_const_get( to_class_names(str), base )
|
23
|
-
end
|
24
|
-
|
23
|
+
|
25
24
|
def namespaced_const_get(list, base=::Object)
|
26
25
|
list.inject(base) {|m, klass| m.const_get(klass, false)}
|
27
26
|
end
|
data/lib/ing/version.rb
CHANGED
@@ -8,9 +8,15 @@ describe Ing do
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def reset
|
11
|
-
Ing::
|
11
|
+
Ing::Callstack.clear
|
12
12
|
end
|
13
13
|
|
14
|
+
def count_executions_of(klass, meth)
|
15
|
+
Ing.callstack.count {|(k, m)|
|
16
|
+
k == klass && m == meth
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
14
20
|
describe "#run" do
|
15
21
|
|
16
22
|
describe "no method or args given" do
|
@@ -150,6 +156,13 @@ describe Ing do
|
|
150
156
|
it 'should run with expected output' do
|
151
157
|
assert_equal "1\n2\n3\n", capture_run(subject)
|
152
158
|
end
|
159
|
+
|
160
|
+
it 'should only list one execution in the call stack for invoked commands' do
|
161
|
+
capture_run(subject)
|
162
|
+
assert_equal 1, count_executions_of(Invoking::Counter, :one)
|
163
|
+
assert_equal 1, count_executions_of(Invoking::Counter, :two)
|
164
|
+
assert_equal 1, count_executions_of(Invoking::Counter, :three)
|
165
|
+
end
|
153
166
|
end
|
154
167
|
|
155
168
|
describe "executing within tasks" do
|
@@ -159,6 +172,14 @@ describe Ing do
|
|
159
172
|
it 'should run with expected output' do
|
160
173
|
assert_equal "1\n2\n3\n3\n", capture_run(subject)
|
161
174
|
end
|
175
|
+
|
176
|
+
it 'should only list each execution in the call stack for executed commands' do
|
177
|
+
capture_run(subject)
|
178
|
+
assert_equal 1, count_executions_of(Executing::Counter, :one)
|
179
|
+
assert_equal 1, count_executions_of(Executing::Counter, :two)
|
180
|
+
assert_equal 2, count_executions_of(Executing::Counter, :three)
|
181
|
+
end
|
182
|
+
|
162
183
|
end
|
163
184
|
|
164
185
|
end
|
data/test/suite.rb
CHANGED
@@ -0,0 +1,334 @@
|
|
1
|
+
require File.expand_path('../test_helper', File.dirname(__FILE__))
|
2
|
+
|
3
|
+
describe Ing::Command do
|
4
|
+
|
5
|
+
def dummy_command_class
|
6
|
+
Class.new do
|
7
|
+
attr_reader :options
|
8
|
+
def initialize(options); @options = options; end
|
9
|
+
def dummy; end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def dummy_command_class_with_specify_options
|
14
|
+
Class.new do
|
15
|
+
def self.specify_options(expect); end
|
16
|
+
attr_reader :options
|
17
|
+
def initialize(options); @options = options; end
|
18
|
+
def dummy; end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def dummy_command_proc
|
23
|
+
x = Proc.new { }
|
24
|
+
def x.dummy; end
|
25
|
+
x
|
26
|
+
end
|
27
|
+
|
28
|
+
#-----------------------------------------------------------------------------
|
29
|
+
describe ".new" do
|
30
|
+
|
31
|
+
describe "when only command class passed" do
|
32
|
+
subject { Ing::Command.new dummy_command_class }
|
33
|
+
|
34
|
+
it "should have command_meth == :call" do
|
35
|
+
assert_equal :call, subject.command_meth
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should have options == {}" do
|
39
|
+
assert_equal Hash.new, subject.options
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should have args == []" do
|
43
|
+
assert_equal [], subject.args
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe "when command class and options hash passed" do
|
48
|
+
subject { Ing::Command.new dummy_command_class, options }
|
49
|
+
let(:options) { {:one => 1, :two => 2} }
|
50
|
+
|
51
|
+
it "should have command_meth == :call" do
|
52
|
+
assert_equal :call, subject.command_meth
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should have options == passed options" do
|
56
|
+
assert_equal options, subject.options
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should have args == []" do
|
60
|
+
assert_equal [], subject.args
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe "when command class and method passed" do
|
65
|
+
subject { Ing::Command.new dummy_command_class, meth, *args }
|
66
|
+
let(:meth) { "dummy" }
|
67
|
+
let(:args) { ["one", "two"] }
|
68
|
+
|
69
|
+
it "should have command_meth == passed method" do
|
70
|
+
assert_equal meth.to_sym, subject.command_meth
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should have options == {}" do
|
74
|
+
assert_equal Hash.new, subject.options
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should have args == remaining args" do
|
78
|
+
assert_equal args, subject.args
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
describe "when command proc and method passed" do
|
83
|
+
subject { Ing::Command.new dummy_command_proc, meth, *args }
|
84
|
+
let(:meth) { "dummy" }
|
85
|
+
let(:args) { ["one", "two"] }
|
86
|
+
|
87
|
+
it "should have command_meth == passed method" do
|
88
|
+
assert_equal meth.to_sym, subject.command_meth
|
89
|
+
end
|
90
|
+
|
91
|
+
it "should have options == {}" do
|
92
|
+
assert_equal Hash.new, subject.options
|
93
|
+
end
|
94
|
+
|
95
|
+
it "should have args == remaining args" do
|
96
|
+
assert_equal args, subject.args
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
describe "when command class and non-public-instance-method arg passed" do
|
101
|
+
subject { Ing::Command.new dummy_command_class, arg, *args }
|
102
|
+
let(:arg) { "remove_instance_variable" }
|
103
|
+
let(:args) { ["one", "two"] }
|
104
|
+
|
105
|
+
it "should have command_meth == :call" do
|
106
|
+
assert_equal :call, subject.command_meth
|
107
|
+
end
|
108
|
+
|
109
|
+
it "should have options == {}" do
|
110
|
+
assert_equal Hash.new, subject.options
|
111
|
+
end
|
112
|
+
|
113
|
+
it "should have args == all passed args" do
|
114
|
+
assert_equal [arg] + args, subject.args
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
describe "when command proc and non-public-method arg passed" do
|
119
|
+
subject { Ing::Command.new dummy_command_proc, arg, *args }
|
120
|
+
let(:arg) { "extended" }
|
121
|
+
let(:args) { ["one", "two"] }
|
122
|
+
|
123
|
+
it "should have command_meth == :call" do
|
124
|
+
assert_equal :call, subject.command_meth
|
125
|
+
end
|
126
|
+
|
127
|
+
it "should have options == {}" do
|
128
|
+
assert_equal Hash.new, subject.options
|
129
|
+
end
|
130
|
+
|
131
|
+
it "should have args == all passed args" do
|
132
|
+
assert_equal [arg] + args, subject.args
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
describe "when command class and option arg passed" do
|
137
|
+
subject { Ing::Command.new dummy_command_class, arg, *args }
|
138
|
+
let(:arg) { "--nonsense" }
|
139
|
+
let(:args) { ["one", "two"] }
|
140
|
+
|
141
|
+
it "should have command_meth == :call" do
|
142
|
+
assert_equal :call, subject.command_meth
|
143
|
+
end
|
144
|
+
|
145
|
+
it "should have options == {}" do
|
146
|
+
assert_equal Hash.new, subject.options
|
147
|
+
end
|
148
|
+
|
149
|
+
it "should have args == all passed args" do
|
150
|
+
assert_equal [arg] + args, subject.args
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
describe "when command proc and option arg passed" do
|
155
|
+
subject { Ing::Command.new dummy_command_proc, arg, *args }
|
156
|
+
let(:arg) { "--help" }
|
157
|
+
let(:args) { ["one", "two"] }
|
158
|
+
|
159
|
+
it "should have command_meth == :call" do
|
160
|
+
assert_equal :call, subject.command_meth
|
161
|
+
end
|
162
|
+
|
163
|
+
it "should have options == {}" do
|
164
|
+
assert_equal Hash.new, subject.options
|
165
|
+
end
|
166
|
+
|
167
|
+
it "should have args == all passed args" do
|
168
|
+
assert_equal [arg] + args, subject.args
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
end
|
173
|
+
|
174
|
+
#-----------------------------------------------------------------------------
|
175
|
+
describe "#instance" do
|
176
|
+
subject { Ing::Command.new command_class }
|
177
|
+
let(:command_class) { dummy_command_class }
|
178
|
+
|
179
|
+
it "should return an instance of the passed class" do
|
180
|
+
assert_kind_of command_class, subject.instance
|
181
|
+
end
|
182
|
+
|
183
|
+
describe "and passed class defines specify_options" do
|
184
|
+
|
185
|
+
def mock_parser_given(args, ret={})
|
186
|
+
parser = MiniTest::Mock.new
|
187
|
+
parser.expect(:parser,nil)
|
188
|
+
parser.expect(:"parse!",ret,[args])
|
189
|
+
parserclass = MiniTest::Mock.new
|
190
|
+
parserclass.expect(:new, parser)
|
191
|
+
parserclass
|
192
|
+
end
|
193
|
+
|
194
|
+
def expecting_parser_given(args, ret={})
|
195
|
+
Ing::Command.stub(:parser, mock_parser_given(args, ret)) do |stub|
|
196
|
+
#puts stub.parser.inspect
|
197
|
+
yield
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
# These don't work because `parse_options!` is indivisible from `instance`
|
202
|
+
# and you need an unstubbed :command_class for `parse_options!`
|
203
|
+
#
|
204
|
+
# def mock_command_constructor_given(opts)
|
205
|
+
# cmd = MiniTest::Mock.new
|
206
|
+
# cmd.expect(:new,nil,[opts])
|
207
|
+
# cmd
|
208
|
+
# end
|
209
|
+
#
|
210
|
+
# def expecting_command_constructor_given(ing_cmd, opts)
|
211
|
+
# ing_cmd.stub(:command_class,
|
212
|
+
# mock_command_constructor_given(opts)) do |stub|
|
213
|
+
# #puts stub.command_class.inspect
|
214
|
+
# yield
|
215
|
+
# end
|
216
|
+
# end
|
217
|
+
|
218
|
+
subject { Ing::Command.new command_class, *args }
|
219
|
+
let(:command_class) { dummy_command_class_with_specify_options }
|
220
|
+
let(:args) { ["one", "two", "three"] }
|
221
|
+
|
222
|
+
it "should interact with parser as expected" do
|
223
|
+
expecting_parser_given(args) do
|
224
|
+
subject.instance
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
describe "and an option hash is passed as the last arg" do
|
229
|
+
subject { Ing::Command.new command_class, *args, options }
|
230
|
+
let(:command_class) { dummy_command_class_with_specify_options }
|
231
|
+
let(:args) { ["--two", "2", "--four", "4", "--three", "1"] }
|
232
|
+
let(:options) { {:one => 1, :two => 2, :three => 3} }
|
233
|
+
let(:parsed_options) { {:two => 2, :four => 4, :three => 1} }
|
234
|
+
|
235
|
+
it "should merge passed options into the options parsed from other args" do
|
236
|
+
expecting_parser_given(args, parsed_options) do
|
237
|
+
merged_options = parsed_options.merge(options)
|
238
|
+
it = subject.instance
|
239
|
+
# note dummy.options returns what it was passed in constructor
|
240
|
+
assert_equal merged_options, it.options
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
end
|
245
|
+
|
246
|
+
end
|
247
|
+
|
248
|
+
end
|
249
|
+
|
250
|
+
#-----------------------------------------------------------------------------
|
251
|
+
describe "#execute" do
|
252
|
+
|
253
|
+
def mock_instance_sent(meth, args=[])
|
254
|
+
inst = MiniTest::Mock.new
|
255
|
+
inst.expect(:send, nil, [meth] + args)
|
256
|
+
inst
|
257
|
+
end
|
258
|
+
|
259
|
+
def expecting_instance_sent(ing_cmd, meth, args=[])
|
260
|
+
ing_cmd.stub(:instance, mock_instance_sent(meth, args)) do |stub|
|
261
|
+
#puts stub.instance.inspect
|
262
|
+
yield
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
describe "when only command class passed" do
|
267
|
+
subject { Ing::Command.new dummy_command_class }
|
268
|
+
|
269
|
+
it "instance should receive :call and no args" do
|
270
|
+
subject.instance
|
271
|
+
expecting_instance_sent(subject, :call) do
|
272
|
+
subject.execute
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
end
|
277
|
+
|
278
|
+
describe "when command class and options hash passed" do
|
279
|
+
subject { Ing::Command.new dummy_command_class, options }
|
280
|
+
let(:options) { {:one => 1, :two => 2} }
|
281
|
+
|
282
|
+
it "instance should receive :call and no args" do
|
283
|
+
subject.instance
|
284
|
+
expecting_instance_sent(subject, :call) do
|
285
|
+
subject.execute
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
end
|
290
|
+
|
291
|
+
describe "when command class and method passed with args" do
|
292
|
+
subject { Ing::Command.new dummy_command_class, meth, *args }
|
293
|
+
let(:meth) { "dummy" }
|
294
|
+
let(:args) { ["one", "two"] }
|
295
|
+
|
296
|
+
it "instance should receive passed method and args" do
|
297
|
+
subject.instance
|
298
|
+
expecting_instance_sent(subject, meth.to_sym, args) do
|
299
|
+
subject.execute
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
end
|
304
|
+
|
305
|
+
describe "when command proc and method passed" do
|
306
|
+
subject { Ing::Command.new dummy_command_proc, meth, *args }
|
307
|
+
let(:meth) { "dummy" }
|
308
|
+
let(:args) { ["one", "two"] }
|
309
|
+
|
310
|
+
it "instance should receive passed method, args, and empty options hash" do
|
311
|
+
subject.instance
|
312
|
+
expecting_instance_sent(subject, meth.to_sym, args + [{}]) do
|
313
|
+
subject.execute
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
end
|
318
|
+
|
319
|
+
describe "when command proc and options hash passed" do
|
320
|
+
subject { Ing::Command.new dummy_command_proc, options }
|
321
|
+
let(:options) { {:one => 1, :two => 2} }
|
322
|
+
|
323
|
+
it "instance should receive :call and passed options hash" do
|
324
|
+
subject.instance
|
325
|
+
expecting_instance_sent(subject, :call, [options]) do
|
326
|
+
subject.execute
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
end
|
331
|
+
|
332
|
+
end
|
333
|
+
|
334
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require File.expand_path('../test_helper', File.dirname(__FILE__))
|
2
|
+
|
3
|
+
# These don't test much, basically just the `extract_boot_class!` logic.
|
4
|
+
# More comprehensive tests under acceptance/ing_run_tests.
|
5
|
+
#
|
6
|
+
describe Ing do
|
7
|
+
|
8
|
+
#-----------------------------------------------------------------------------
|
9
|
+
describe ".run" do
|
10
|
+
|
11
|
+
def mock_command_execute(klass, args)
|
12
|
+
cmd = MiniTest::Mock.new
|
13
|
+
cmd.expect(:execute, nil)
|
14
|
+
cmd.expect(:command_class, nil)
|
15
|
+
cmd.expect(:command_meth, nil)
|
16
|
+
cmdclass = MiniTest::Mock.new
|
17
|
+
cmdclass.expect(:new, cmd, [klass] + args)
|
18
|
+
cmdclass
|
19
|
+
end
|
20
|
+
|
21
|
+
def stubbing_command_execute(klass, args)
|
22
|
+
::Ing.stub(:command, mock_command_execute(klass, args)) do |stub|
|
23
|
+
#puts stub.command.inspect
|
24
|
+
yield
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def mock_callstack_push(klass, meth)
|
29
|
+
callst = MiniTest::Mock.new
|
30
|
+
callst.expect(:push, nil, [klass, meth])
|
31
|
+
callst
|
32
|
+
end
|
33
|
+
|
34
|
+
def stubbing_callstack_push(klass, meth)
|
35
|
+
::Ing.stub(:_callstack, mock_callstack_push(klass, meth)) do |stub|
|
36
|
+
yield
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
describe "when first arg is built-in command" do
|
42
|
+
subject { ["generate"] + args }
|
43
|
+
let(:args) { ["something"] }
|
44
|
+
|
45
|
+
it "should execute with the specified command class and remaining args" do
|
46
|
+
stubbing_command_execute(::Ing::Commands::Generate, args) do
|
47
|
+
Ing.run subject
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
describe "when first arg is not a built-in command" do
|
54
|
+
subject { ["foo:die"] + args }
|
55
|
+
let(:args) { ["do_it"] }
|
56
|
+
|
57
|
+
it "should execute with the implicit command class and whole command line" do
|
58
|
+
stubbing_command_execute(::Ing::Commands::Implicit, subject) do
|
59
|
+
Ing.run subject
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ing
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1
|
4
|
+
version: 0.2.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-09-
|
12
|
+
date: 2012-09-20 00:00:00.000000000Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: minitest
|
16
|
-
requirement: &
|
16
|
+
requirement: &7756700 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :development
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *7756700
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: fakeweb
|
27
|
-
requirement: &
|
27
|
+
requirement: &7756220 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
@@ -32,7 +32,7 @@ dependencies:
|
|
32
32
|
version: '0'
|
33
33
|
type: :development
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *7756220
|
36
36
|
description: ! "\nAn alternative to Rake and Thor, Ing has a command-line syntax similar
|
37
37
|
to \nThor's, and it incorporates Thor's (Rails') generator methods and shell \nconventions.
|
38
38
|
But unlike Thor or Rake, it does not define its own DSL. Your tasks\ncorrespond
|
@@ -62,16 +62,17 @@ files:
|
|
62
62
|
- lib/ing/actions/empty_directory.rb
|
63
63
|
- lib/ing/actions/file_manipulation.rb
|
64
64
|
- lib/ing/actions/inject_into_file.rb
|
65
|
-
- lib/ing/
|
65
|
+
- lib/ing/command.rb
|
66
|
+
- lib/ing/commands/boot.rb
|
66
67
|
- lib/ing/commands/generate.rb
|
67
68
|
- lib/ing/commands/help.rb
|
68
69
|
- lib/ing/commands/implicit.rb
|
69
70
|
- lib/ing/commands/list.rb
|
70
71
|
- lib/ing/common_options.rb
|
71
|
-
- lib/ing/dispatcher.rb
|
72
72
|
- lib/ing/files.rb
|
73
73
|
- lib/ing/generator.rb
|
74
74
|
- lib/ing/lib_trollop.rb
|
75
|
+
- lib/ing/option_parsers/trollop.rb
|
75
76
|
- lib/ing/shell.rb
|
76
77
|
- lib/ing/task.rb
|
77
78
|
- lib/ing/trollop/parser.rb
|
@@ -107,6 +108,8 @@ files:
|
|
107
108
|
- test/spec_helper.rb
|
108
109
|
- test/suite.rb
|
109
110
|
- test/test_helper.rb
|
111
|
+
- test/unit/command_test.rb
|
112
|
+
- test/unit/ing_test.rb
|
110
113
|
- todo.yml
|
111
114
|
homepage: https://github.com/ericgj/ing
|
112
115
|
licenses: []
|
@@ -162,4 +165,6 @@ test_files:
|
|
162
165
|
- test/spec_helper.rb
|
163
166
|
- test/suite.rb
|
164
167
|
- test/test_helper.rb
|
168
|
+
- test/unit/command_test.rb
|
169
|
+
- test/unit/ing_test.rb
|
165
170
|
has_rdoc:
|
data/lib/ing/dispatcher.rb
DELETED
@@ -1,143 +0,0 @@
|
|
1
|
-
require 'stringio'
|
2
|
-
require 'set'
|
3
|
-
|
4
|
-
module Ing
|
5
|
-
|
6
|
-
# Generic router for ing commands (both built-in and user-defined).
|
7
|
-
# Resolves class, parses options with Trollop if target class
|
8
|
-
# defines +specify_options+, then dispatches like (simplifying):
|
9
|
-
#
|
10
|
-
# Target.new(options).send(*args)
|
11
|
-
#
|
12
|
-
# if the target is class-like, i.e. responds to +new+. Otherwise it dispatches
|
13
|
-
# to the target as a callable:
|
14
|
-
#
|
15
|
-
# Target.call(*args, options)
|
16
|
-
#
|
17
|
-
class Dispatcher
|
18
|
-
|
19
|
-
# Global set of dispatched commands as [dispatch_class, dispatch_meth],
|
20
|
-
# updated before dispatch
|
21
|
-
def self.dispatched
|
22
|
-
@dispatched ||= Set.new
|
23
|
-
end
|
24
|
-
|
25
|
-
# +Ing.invoke+
|
26
|
-
def self.invoke(klass, *args, &config)
|
27
|
-
allocate.tap {|d| d.initialize_preloaded(true, klass, *args) }.
|
28
|
-
dispatch(&config)
|
29
|
-
end
|
30
|
-
|
31
|
-
# +Ing.execute+
|
32
|
-
def self.execute(klass, *args, &config)
|
33
|
-
allocate.tap {|d| d.initialize_preloaded(false, klass, *args) }.
|
34
|
-
dispatch(&config)
|
35
|
-
end
|
36
|
-
|
37
|
-
attr_accessor :dispatch_class, :dispatch_meth, :args, :options
|
38
|
-
|
39
|
-
# True if current dispatch class/method has been dispatched before
|
40
|
-
def dispatched?
|
41
|
-
Dispatcher.dispatched.include?([dispatch_class,dispatch_meth])
|
42
|
-
end
|
43
|
-
|
44
|
-
# Default constructor from +Ing.run+ (command line)
|
45
|
-
def initialize(namespaces, classes, *args)
|
46
|
-
ns = Util.namespaced_const_get(namespaces)
|
47
|
-
self.dispatch_class = Util.namespaced_const_get(classes, ns)
|
48
|
-
self.dispatch_meth = extract_method!(args, dispatch_class)
|
49
|
-
self.args = args
|
50
|
-
@invoking = false
|
51
|
-
end
|
52
|
-
|
53
|
-
# Alternate constructor for preloaded object and arguments
|
54
|
-
# i.e. from +invoke+ or +execute+ instead of +run+
|
55
|
-
def initialize_preloaded(invoking, klass, *args)
|
56
|
-
self.options = (Hash === args.last ? args.pop : {})
|
57
|
-
self.dispatch_class = klass
|
58
|
-
self.dispatch_meth = extract_method!(args, dispatch_class)
|
59
|
-
self.args = args
|
60
|
-
@invoking = invoking
|
61
|
-
end
|
62
|
-
|
63
|
-
# Returns stream (StringIO) of description text from specify_options.
|
64
|
-
# Note this does not parse the options. Used by +Ing::Commands::List+.
|
65
|
-
def describe
|
66
|
-
s=StringIO.new
|
67
|
-
with_option_parser(self.dispatch_class) do |p|
|
68
|
-
p.educate_banner s
|
69
|
-
end
|
70
|
-
s.rewind; s
|
71
|
-
end
|
72
|
-
|
73
|
-
# Returns stream (StringIO) of help text from specify_options.
|
74
|
-
# Note this does not parse the options. Used by +Ing::Commands::Help+.
|
75
|
-
def help
|
76
|
-
s=StringIO.new
|
77
|
-
with_option_parser(self.dispatch_class) do |p|
|
78
|
-
p.educate s
|
79
|
-
end
|
80
|
-
s.rewind; s
|
81
|
-
end
|
82
|
-
|
83
|
-
# Public dispatch method used by all types of dispatch (run, invoke,
|
84
|
-
# execute). Does not dispatch if invoking and already dispatched.
|
85
|
-
def dispatch(&config)
|
86
|
-
unless @invoking && dispatched?
|
87
|
-
record_dispatch
|
88
|
-
execute(&config)
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
def with_option_parser(klass) # :nodoc:
|
93
|
-
return unless klass.respond_to?(:specify_options)
|
94
|
-
klass.specify_options(p = Trollop::Parser.new)
|
95
|
-
yield p
|
96
|
-
end
|
97
|
-
|
98
|
-
private
|
99
|
-
|
100
|
-
def record_dispatch
|
101
|
-
Dispatcher.dispatched.add [dispatch_class, dispatch_meth]
|
102
|
-
end
|
103
|
-
|
104
|
-
def execute
|
105
|
-
self.options = parse_options!(args, dispatch_class) || {}
|
106
|
-
if dispatch_class.respond_to?(:new)
|
107
|
-
cmd = dispatch_class.new(options)
|
108
|
-
yield cmd if block_given?
|
109
|
-
cmd.send(dispatch_meth, *args)
|
110
|
-
else
|
111
|
-
dispatch_class.call *args, options
|
112
|
-
end
|
113
|
-
end
|
114
|
-
|
115
|
-
def parse_options!(args, klass)
|
116
|
-
with_option_parser(klass) do |p|
|
117
|
-
Trollop.with_standard_exception_handling(p) { p.parse(args) }
|
118
|
-
end
|
119
|
-
end
|
120
|
-
|
121
|
-
def extract_method!(args, klass)
|
122
|
-
return :call if args.empty?
|
123
|
-
if meth = whitelist(args.first, klass)
|
124
|
-
args.shift
|
125
|
-
else
|
126
|
-
meth = :call
|
127
|
-
end
|
128
|
-
meth
|
129
|
-
end
|
130
|
-
|
131
|
-
# Note this currently does no filtering, but basically checks for respond_to
|
132
|
-
def whitelist(meth, klass)
|
133
|
-
finder = Proc.new {|m| m == meth.to_sym}
|
134
|
-
if klass.respond_to?(:new)
|
135
|
-
klass.public_instance_methods(true).find(&finder)
|
136
|
-
else
|
137
|
-
klass.public_methods.find(&finder)
|
138
|
-
end
|
139
|
-
end
|
140
|
-
|
141
|
-
end
|
142
|
-
|
143
|
-
end
|