ing 0.1.5 → 0.2.1
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/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
|