ing 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +5 -0
- data/GENERATORS.md +2 -0
- data/LICENSE +18 -0
- data/OPTIONS.md +2 -0
- data/README.md +251 -0
- data/TASKS.md +21 -0
- data/bin/ing +5 -0
- data/examples/rspec_convert.rb +102 -0
- data/ing.gemspec +29 -0
- data/ing.rb +102 -0
- data/lib/ing.rb +78 -0
- data/lib/ing/actions/create_file.rb +105 -0
- data/lib/ing/actions/create_link.rb +57 -0
- data/lib/ing/actions/directory.rb +98 -0
- data/lib/ing/actions/empty_directory.rb +155 -0
- data/lib/ing/actions/file_manipulation.rb +308 -0
- data/lib/ing/actions/inject_into_file.rb +109 -0
- data/lib/ing/commands/boot.rb +76 -0
- data/lib/ing/commands/generate.rb +64 -0
- data/lib/ing/commands/help.rb +87 -0
- data/lib/ing/commands/implicit.rb +59 -0
- data/lib/ing/commands/list.rb +108 -0
- data/lib/ing/dispatcher.rb +132 -0
- data/lib/ing/files.rb +190 -0
- data/lib/ing/lib_trollop.rb +782 -0
- data/lib/ing/shell.rb +390 -0
- data/lib/ing/trollop/parser.rb +17 -0
- data/lib/ing/util.rb +61 -0
- data/lib/ing/version.rb +3 -0
- data/lib/thor/actions/file_manipulation.rb +30 -0
- data/lib/thor/shell/basic.rb +44 -0
- data/test/acceptance/ing_run_tests.rb +164 -0
- data/test/actions/create_file_spec.rb +209 -0
- data/test/actions/create_link_spec.rb +90 -0
- data/test/actions/directory_spec.rb +167 -0
- data/test/actions/empty_directory_spec.rb +146 -0
- data/test/actions/file_manipulation_spec.rb +433 -0
- data/test/actions/inject_into_file_spec.rb +147 -0
- data/test/fixtures/application.rb +2 -0
- data/test/fixtures/app{1}/README +3 -0
- data/test/fixtures/bundle/execute.rb +6 -0
- data/test/fixtures/bundle/main.thor +1 -0
- data/test/fixtures/doc/%file_name%.rb.tt +1 -0
- data/test/fixtures/doc/COMMENTER +10 -0
- data/test/fixtures/doc/README +3 -0
- data/test/fixtures/doc/block_helper.rb +3 -0
- data/test/fixtures/doc/components/.empty_directory +0 -0
- data/test/fixtures/doc/config.rb +1 -0
- data/test/fixtures/doc/config.yaml.tt +1 -0
- data/test/fixtures/group.ing.rb +76 -0
- data/test/fixtures/invok.ing.rb +50 -0
- data/test/fixtures/namespace.ing.rb +52 -0
- data/test/fixtures/require.ing.rb +7 -0
- data/test/fixtures/task.ing.rb +36 -0
- data/test/fixtures/task.thor +10 -0
- data/test/spec_helper.rb +2 -0
- data/test/test_helper.rb +41 -0
- data/todo.yml +7 -0
- metadata +147 -0
@@ -0,0 +1,64 @@
|
|
1
|
+
|
2
|
+
module Ing
|
3
|
+
module Commands
|
4
|
+
|
5
|
+
# This is the boot command invoked from `ing generate ...`
|
6
|
+
class Generate < Boot
|
7
|
+
|
8
|
+
DEFAULTS = {
|
9
|
+
namespace: 'object',
|
10
|
+
ing_file: 'ing.rb',
|
11
|
+
gen_root: ENV.fetch('ING_GENERATOR_ROOT', '~/.ing/generators')
|
12
|
+
}
|
13
|
+
|
14
|
+
def self.specify_options(parser)
|
15
|
+
parser.text "Run a generator task"
|
16
|
+
parser.opt :debug, "Display debug messages"
|
17
|
+
parser.opt :namespace, "Top-level namespace for generators",
|
18
|
+
:type => :string, :default => DEFAULTS[:namespace]
|
19
|
+
parser.opt :gen_root, "Generators root directory",
|
20
|
+
:type => :string, :short => 'r',
|
21
|
+
:default => DEFAULTS[:gen_root]
|
22
|
+
parser.opt :ing_file, "Default generator file (ruby)",
|
23
|
+
:type => :string, :short => 'f',
|
24
|
+
:default => DEFAULTS[:ing_file]
|
25
|
+
parser.stop_on_unknown
|
26
|
+
end
|
27
|
+
|
28
|
+
def generator_root
|
29
|
+
@generator_root ||= File.expand_path(options[:gen_root])
|
30
|
+
end
|
31
|
+
|
32
|
+
# Locate and require the generator ruby file identified by the first arg,
|
33
|
+
# before dispatching to it.
|
34
|
+
def before(*args)
|
35
|
+
require_generator args.first
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def require_generator(name)
|
41
|
+
path = File.expand_path(generator_name_to_path(name), generator_root)
|
42
|
+
f = if File.directory?(path)
|
43
|
+
File.join(path, options[:ing_file])
|
44
|
+
else
|
45
|
+
path
|
46
|
+
end
|
47
|
+
debug "#{__FILE__}:#{__LINE__} :: require #{f.inspect}"
|
48
|
+
require f
|
49
|
+
rescue LoadError
|
50
|
+
raise LoadError,
|
51
|
+
"No generator found named `#{name}`. Check that you have set the generator root directory correctly (looking for `#{f}`)"
|
52
|
+
end
|
53
|
+
|
54
|
+
def generator_name_to_path(name)
|
55
|
+
name.split(":").join("/")
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
# alias
|
61
|
+
G = Generate
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module Ing
|
2
|
+
module Commands
|
3
|
+
|
4
|
+
class Help
|
5
|
+
|
6
|
+
DEFAULTS = {
|
7
|
+
namespace: 'ing:commands',
|
8
|
+
ing_file: 'ing.rb'
|
9
|
+
}
|
10
|
+
|
11
|
+
def self.specify_options(parser)
|
12
|
+
parser.text "Display help on specified command"
|
13
|
+
parser.text "\nUsage:"
|
14
|
+
parser.text " ing help generate # help on built-in command generate"
|
15
|
+
parser.text " ing help --namespace test unit # help on command within namespace"
|
16
|
+
parser.text " ing help # display this message"
|
17
|
+
parser.text "\nOptions:"
|
18
|
+
parser.opt :debug, "Display debug messages"
|
19
|
+
parser.opt :namespace, "Top-level namespace",
|
20
|
+
:type => :string, :default => DEFAULTS[:namespace]
|
21
|
+
parser.opt :require, "Require file or library before running (multi)",
|
22
|
+
:multi => true, :type => :string
|
23
|
+
parser.opt :ing_file, "Default task file (ruby)",
|
24
|
+
:type => :string, :short => 'f',
|
25
|
+
:default => DEFAULTS[:ing_file]
|
26
|
+
end
|
27
|
+
|
28
|
+
attr_accessor :options
|
29
|
+
attr_writer :shell
|
30
|
+
|
31
|
+
def shell
|
32
|
+
@shell ||= ::Ing.shell_class.new
|
33
|
+
end
|
34
|
+
|
35
|
+
def initialize(options)
|
36
|
+
self.options = options
|
37
|
+
debug "#{__FILE__}:#{__LINE__} :: options #{options.inspect}"
|
38
|
+
end
|
39
|
+
|
40
|
+
# Require each passed file or library before running
|
41
|
+
# and require the ing file if it exists
|
42
|
+
def before(*args)
|
43
|
+
require_libs options[:require]
|
44
|
+
require_ing_file
|
45
|
+
end
|
46
|
+
|
47
|
+
def call(cmd="help")
|
48
|
+
before(cmd)
|
49
|
+
ns = Ing::Util.to_class_names(options[:namespace] || 'object')
|
50
|
+
cs = Ing::Util.to_class_names(cmd)
|
51
|
+
help = Dispatcher.new(ns, cs).help
|
52
|
+
shell.say help.read
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
# require relative paths relative to the Dir.pwd
|
58
|
+
# otherwise, require as given (so gems can be required, etc.)
|
59
|
+
def require_libs(libs)
|
60
|
+
libs = Array(libs)
|
61
|
+
libs.each do |lib|
|
62
|
+
f = if /\A\.{1,2}\// =~ lib
|
63
|
+
File.expand_path(lib)
|
64
|
+
else
|
65
|
+
lib
|
66
|
+
end
|
67
|
+
debug "#{__FILE__}:#{__LINE__} :: require #{f.inspect}"
|
68
|
+
require f
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def require_ing_file
|
73
|
+
f = File.expand_path(options[:ing_file])
|
74
|
+
require_libs(f) if File.exists?(f)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Internal debugging
|
78
|
+
def debug(*args)
|
79
|
+
shell.debug(*args) if options[:debug]
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
H = Help
|
85
|
+
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
|
2
|
+
module Ing
|
3
|
+
module Commands
|
4
|
+
|
5
|
+
# This is the default boot command when ARGV.first not recognized as
|
6
|
+
# a built-in Ing command. For example, `ing some:task run` .
|
7
|
+
class Implicit < Boot
|
8
|
+
|
9
|
+
DEFAULTS = {
|
10
|
+
namespace: 'object',
|
11
|
+
ing_file: 'ing.rb'
|
12
|
+
}
|
13
|
+
|
14
|
+
def self.specify_options(parser)
|
15
|
+
parser.text "(internal)"
|
16
|
+
parser.opt :debug, "Display debug messages"
|
17
|
+
parser.opt :namespace, "Top-level namespace for generators",
|
18
|
+
:type => :string, :default => DEFAULTS[:namespace]
|
19
|
+
parser.opt :require, "Require file or library before running (multi)",
|
20
|
+
:multi => true, :type => :string
|
21
|
+
parser.opt :ing_file, "Default generator file (ruby)",
|
22
|
+
:type => :string, :short => 'f',
|
23
|
+
:default => DEFAULTS[:ing_file]
|
24
|
+
parser.stop_on_unknown
|
25
|
+
end
|
26
|
+
|
27
|
+
# Require each passed file or library before running
|
28
|
+
# and require the ing file if it exists
|
29
|
+
def before(*args)
|
30
|
+
require_libs options[:require]
|
31
|
+
require_ing_file
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
# require relative paths relative to the Dir.pwd
|
37
|
+
# otherwise, require as given (so gems can be required, etc.)
|
38
|
+
def require_libs(libs)
|
39
|
+
libs = Array(libs)
|
40
|
+
libs.each do |lib|
|
41
|
+
f = if /\A\.{1,2}\// =~ lib
|
42
|
+
File.expand_path(lib)
|
43
|
+
else
|
44
|
+
lib
|
45
|
+
end
|
46
|
+
debug "#{__FILE__}:#{__LINE__} :: require #{f.inspect}"
|
47
|
+
require f
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def require_ing_file
|
52
|
+
f = File.expand_path(options[:ing_file])
|
53
|
+
require_libs(f) if File.exists?(f)
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
module Ing
|
2
|
+
module Commands
|
3
|
+
|
4
|
+
class List
|
5
|
+
|
6
|
+
DEFAULTS = {
|
7
|
+
namespace: 'ing:commands',
|
8
|
+
ing_file: 'ing.rb'
|
9
|
+
}
|
10
|
+
|
11
|
+
def self.specify_options(parser)
|
12
|
+
parser.banner "List all tasks within specified namespace"
|
13
|
+
parser.text "\nUsage:"
|
14
|
+
parser.text " ing list # list all built-in ing commands"
|
15
|
+
parser.text " ing list rspec # list all ing commands in rspec namespace, or"
|
16
|
+
parser.text " ing list --namespace rspec"
|
17
|
+
parser.text "\nOptions:"
|
18
|
+
parser.opt :debug, "Display debug messages"
|
19
|
+
parser.opt :namespace, "Top-level namespace",
|
20
|
+
:type => :string, :default => DEFAULTS[:namespace]
|
21
|
+
parser.opt :require, "Require file or library before running (multi)",
|
22
|
+
:multi => true, :type => :string
|
23
|
+
parser.opt :ing_file, "Default task file (ruby)",
|
24
|
+
:type => :string, :short => 'f',
|
25
|
+
:default => DEFAULTS[:ing_file]
|
26
|
+
end
|
27
|
+
|
28
|
+
attr_accessor :options
|
29
|
+
attr_writer :shell
|
30
|
+
|
31
|
+
def shell
|
32
|
+
@shell ||= ::Ing.shell_class.new
|
33
|
+
end
|
34
|
+
|
35
|
+
def initialize(options)
|
36
|
+
self.options = options
|
37
|
+
debug "#{__FILE__}:#{__LINE__} :: options #{options.inspect}"
|
38
|
+
end
|
39
|
+
|
40
|
+
# Require each passed file or library before running
|
41
|
+
# and require the ing file if it exists
|
42
|
+
def before(*args)
|
43
|
+
require_libs options[:require]
|
44
|
+
require_ing_file
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
def call(namespace=options[:namespace])
|
49
|
+
before(namespace)
|
50
|
+
ns = Ing::Util.to_class_names(namespace)
|
51
|
+
mod = Ing::Util.namespaced_const_get(ns)
|
52
|
+
data = mod.constants.map do |c|
|
53
|
+
desc = Dispatcher.new(ns, [c]).describe
|
54
|
+
[ "ing #{Ing::Util.encode_class_names(ns + [c])}",
|
55
|
+
(desc.gets || '(no description)').chomp
|
56
|
+
]
|
57
|
+
end.sort
|
58
|
+
shell.say desc_lines(ns, data).join("\n")
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
# require relative paths relative to the Dir.pwd
|
64
|
+
# otherwise, require as given (so gems can be required, etc.)
|
65
|
+
def require_libs(libs)
|
66
|
+
libs = Array(libs)
|
67
|
+
libs.each do |lib|
|
68
|
+
f = if /\A\.{1,2}\// =~ lib
|
69
|
+
File.expand_path(lib)
|
70
|
+
else
|
71
|
+
lib
|
72
|
+
end
|
73
|
+
debug "#{__FILE__}:#{__LINE__} :: require #{f.inspect}"
|
74
|
+
require f
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def require_ing_file
|
79
|
+
f = File.expand_path(options[:ing_file])
|
80
|
+
require_libs(f) if File.exists?(f)
|
81
|
+
end
|
82
|
+
|
83
|
+
def desc_lines(ns, data)
|
84
|
+
colwidths = data.inject([0,0]) {|max, (line, desc)|
|
85
|
+
max[0] = line.length if line.length > max[0]
|
86
|
+
max[1] = desc.length if desc.length > max[1]
|
87
|
+
max
|
88
|
+
}
|
89
|
+
["#{ns.join(' ')}: all tasks",
|
90
|
+
"-" * 80
|
91
|
+
] +
|
92
|
+
data.map {|line, desc|
|
93
|
+
[ line.ljust(colwidths[0]),
|
94
|
+
desc[0...(80 - colwidths[0] - 3)]
|
95
|
+
].join(" # ")
|
96
|
+
}
|
97
|
+
end
|
98
|
+
|
99
|
+
# Internal debugging
|
100
|
+
def debug(*args)
|
101
|
+
shell.debug(*args) if options[:debug]
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
|
106
|
+
L = List
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
require 'set'
|
3
|
+
|
4
|
+
module Ing
|
5
|
+
|
6
|
+
class Dispatcher
|
7
|
+
|
8
|
+
# Global set of dispatched commands as [dispatch_class, dispatch_meth],
|
9
|
+
# updated before dispatch
|
10
|
+
def self.dispatched
|
11
|
+
@dispatched ||= Set.new
|
12
|
+
end
|
13
|
+
|
14
|
+
# +Ing.invoke+
|
15
|
+
def self.invoke(klass, *args, &config)
|
16
|
+
allocate.tap {|d| d.initialize_preloaded(true, klass, *args) }.
|
17
|
+
dispatch(&config)
|
18
|
+
end
|
19
|
+
|
20
|
+
# +Ing.execute+
|
21
|
+
def self.execute(klass, *args, &config)
|
22
|
+
allocate.tap {|d| d.initialize_preloaded(false, klass, *args) }.
|
23
|
+
dispatch(&config)
|
24
|
+
end
|
25
|
+
|
26
|
+
attr_accessor :dispatch_class, :dispatch_meth, :args, :options
|
27
|
+
|
28
|
+
# True if current dispatch class/method has been dispatched before
|
29
|
+
def dispatched?
|
30
|
+
Dispatcher.dispatched.include?([dispatch_class,dispatch_meth])
|
31
|
+
end
|
32
|
+
|
33
|
+
# Default constructor from +Ing.run+ (command line)
|
34
|
+
def initialize(namespaces, classes, *args)
|
35
|
+
ns = Util.namespaced_const_get(namespaces)
|
36
|
+
self.dispatch_class = Util.namespaced_const_get(classes, ns)
|
37
|
+
self.dispatch_meth = extract_method!(args, dispatch_class)
|
38
|
+
self.options = parse_options!(args, dispatch_class) || {}
|
39
|
+
self.args = args
|
40
|
+
@invoking = false
|
41
|
+
end
|
42
|
+
|
43
|
+
# Alternate constructor for preloaded object and arguments
|
44
|
+
# i.e. from +invoke+ or +execute+ instead of +run+
|
45
|
+
def initialize_preloaded(invoking, klass, *args)
|
46
|
+
self.options = (Hash === args.last ? args.pop : {})
|
47
|
+
self.dispatch_class = klass
|
48
|
+
self.dispatch_meth = extract_method!(args, dispatch_class)
|
49
|
+
self.args = args
|
50
|
+
@invoking = invoking
|
51
|
+
end
|
52
|
+
|
53
|
+
# Returns stream (StringIO) of description text from specify_options.
|
54
|
+
# Note this does not parse the options. Used by +Ing::Commands::List+.
|
55
|
+
def describe
|
56
|
+
s=StringIO.new
|
57
|
+
with_option_parser(self.dispatch_class) do |p|
|
58
|
+
p.educate_banner s
|
59
|
+
end
|
60
|
+
s.rewind; s
|
61
|
+
end
|
62
|
+
|
63
|
+
# Returns stream (StringIO) of help text from specify_options.
|
64
|
+
# Note this does not parse the options. Used by +Ing::Commands::Help+.
|
65
|
+
def help
|
66
|
+
s=StringIO.new
|
67
|
+
with_option_parser(self.dispatch_class) do |p|
|
68
|
+
p.educate s
|
69
|
+
end
|
70
|
+
s.rewind; s
|
71
|
+
end
|
72
|
+
|
73
|
+
# Public dispatch method used by all types of dispatch (run, invoke,
|
74
|
+
# execute). Does not dispatch if invoking and already dispatched.
|
75
|
+
def dispatch(&config)
|
76
|
+
unless @invoking && dispatched?
|
77
|
+
record_dispatch
|
78
|
+
execute(&config)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def with_option_parser(klass) # :nodoc:
|
83
|
+
return unless klass.respond_to?(:specify_options)
|
84
|
+
klass.specify_options(p = Trollop::Parser.new)
|
85
|
+
yield p
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
def record_dispatch
|
91
|
+
Dispatcher.dispatched.add [dispatch_class, dispatch_meth]
|
92
|
+
end
|
93
|
+
|
94
|
+
def execute
|
95
|
+
if dispatch_class.respond_to?(:new)
|
96
|
+
cmd = dispatch_class.new(options)
|
97
|
+
yield cmd if block_given?
|
98
|
+
cmd.send(dispatch_meth, *args)
|
99
|
+
else
|
100
|
+
dispatch_class.call *args, options
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def parse_options!(args, klass)
|
105
|
+
with_option_parser(klass) do |p|
|
106
|
+
Trollop.with_standard_exception_handling(p) { p.parse(args) }
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def extract_method!(args, klass)
|
111
|
+
return :call if args.empty?
|
112
|
+
if meth = whitelist(args.first, klass)
|
113
|
+
args.shift
|
114
|
+
else
|
115
|
+
meth = :call
|
116
|
+
end
|
117
|
+
meth
|
118
|
+
end
|
119
|
+
|
120
|
+
# Note this currently does no filtering, but basically checks for respond_to
|
121
|
+
def whitelist(meth, klass)
|
122
|
+
finder = Proc.new {|m| m == meth.to_sym}
|
123
|
+
if klass.respond_to?(:new)
|
124
|
+
klass.public_instance_methods(true).find(&finder)
|
125
|
+
else
|
126
|
+
klass.public_methods.find(&finder)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|