ing 0.1.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/.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
|