toys 0.2.2 → 0.3.0
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.
- checksums.yaml +5 -5
- data/CHANGELOG.md +5 -0
- data/README.md +8 -0
- data/lib/toys.rb +6 -8
- data/lib/toys/builder.rb +95 -30
- data/lib/toys/builtins/do.rb +44 -0
- data/lib/toys/builtins/system.rb +5 -5
- data/lib/toys/cli.rb +46 -18
- data/lib/toys/context.rb +109 -21
- data/lib/toys/helpers.rb +41 -0
- data/lib/toys/helpers/exec.rb +211 -201
- data/lib/toys/helpers/file_utils.rb +7 -5
- data/lib/toys/{lookup.rb → loader.rb} +29 -18
- data/lib/toys/middleware.rb +41 -0
- data/lib/toys/middleware/base.rb +45 -0
- data/lib/toys/middleware/group_default.rb +57 -0
- data/lib/toys/middleware/set_verbosity.rb +51 -0
- data/lib/toys/middleware/show_tool_help.rb +57 -0
- data/lib/toys/middleware/show_usage_errors.rb +51 -0
- data/lib/toys/templates.rb +41 -0
- data/lib/toys/templates/clean.rb +28 -26
- data/lib/toys/templates/gem_build.rb +51 -49
- data/lib/toys/templates/minitest.rb +48 -42
- data/lib/toys/templates/rubocop.rb +28 -26
- data/lib/toys/templates/yardoc.rb +39 -37
- data/lib/toys/tool.rb +200 -294
- data/lib/toys/utils/module_lookup.rb +101 -0
- data/lib/toys/utils/usage.rb +119 -0
- data/lib/toys/version.rb +1 -1
- metadata +20 -8
data/lib/toys/templates/clean.rb
CHANGED
@@ -27,38 +27,40 @@
|
|
27
27
|
# POSSIBILITY OF SUCH DAMAGE.
|
28
28
|
;
|
29
29
|
|
30
|
-
module Toys
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
30
|
+
module Toys
|
31
|
+
module Templates
|
32
|
+
##
|
33
|
+
# A template for tools that clean build artifacts
|
34
|
+
#
|
35
|
+
class Clean
|
36
|
+
include Template
|
36
37
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
38
|
+
def initialize(opts = {})
|
39
|
+
@name = opts[:name] || "clean"
|
40
|
+
@paths = opts[:paths] || []
|
41
|
+
end
|
41
42
|
|
42
|
-
|
43
|
-
|
43
|
+
attr_accessor :name
|
44
|
+
attr_accessor :paths
|
44
45
|
|
45
|
-
|
46
|
-
|
47
|
-
|
46
|
+
to_expand do |template|
|
47
|
+
name(template.name) do
|
48
|
+
desc "Clean built files and directories."
|
48
49
|
|
49
|
-
|
50
|
+
use :file_utils
|
50
51
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
52
|
+
execute do
|
53
|
+
files = []
|
54
|
+
patterns = Array(template.paths)
|
55
|
+
patterns = ["lib/**/*.rb"] if patterns.empty?
|
56
|
+
patterns.each do |pattern|
|
57
|
+
files.concat(::Dir.glob(pattern))
|
58
|
+
end
|
59
|
+
files.uniq!
|
59
60
|
|
60
|
-
|
61
|
-
|
61
|
+
files.each do |file|
|
62
|
+
rm_rf file
|
63
|
+
end
|
62
64
|
end
|
63
65
|
end
|
64
66
|
end
|
@@ -29,62 +29,64 @@
|
|
29
29
|
|
30
30
|
require "rubygems/package"
|
31
31
|
|
32
|
-
module Toys
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
32
|
+
module Toys
|
33
|
+
module Templates
|
34
|
+
##
|
35
|
+
# A template for tools that build and release gems
|
36
|
+
#
|
37
|
+
class GemBuild
|
38
|
+
include Template
|
38
39
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
40
|
+
def initialize(opts = {})
|
41
|
+
@name = opts[:name] || "build"
|
42
|
+
@gem_name = opts[:gem_name]
|
43
|
+
@push_gem = opts[:push_gem]
|
44
|
+
@tag = opts[:tag]
|
45
|
+
@push_tag = opts[:push_tag]
|
46
|
+
end
|
46
47
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
48
|
+
attr_accessor :name
|
49
|
+
attr_accessor :gem_name
|
50
|
+
attr_accessor :push_gem
|
51
|
+
attr_accessor :tag
|
52
|
+
attr_accessor :push_tag
|
52
53
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
54
|
+
to_expand do |template|
|
55
|
+
unless template.gem_name
|
56
|
+
candidates = ::Dir.glob("*.gemspec")
|
57
|
+
if candidates.empty?
|
58
|
+
raise ToolDefinitionError, "Could not find a gemspec"
|
59
|
+
end
|
60
|
+
template.gem_name = candidates.first.sub(/\.gemspec$/, "")
|
58
61
|
end
|
59
|
-
|
60
|
-
end
|
61
|
-
task_type = template.push_gem ? "Release" : "Build"
|
62
|
+
task_type = template.push_gem ? "Release" : "Build"
|
62
63
|
|
63
|
-
|
64
|
-
|
64
|
+
name(template.name) do
|
65
|
+
desc "#{task_type} the gem: #{template.gem_name}"
|
65
66
|
|
66
|
-
|
67
|
-
|
67
|
+
use :file_utils
|
68
|
+
use :exec
|
68
69
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
70
|
+
execute do
|
71
|
+
configure_exec(exit_on_nonzero_status: true)
|
72
|
+
gemspec = ::Gem::Specification.load "#{template.gem_name}.gemspec"
|
73
|
+
version = gemspec.version
|
74
|
+
gemfile = "#{template.gem_name}-#{version}.gem"
|
75
|
+
::Gem::Package.build gemspec
|
76
|
+
mkdir_p "pkg"
|
77
|
+
mv gemfile, "pkg"
|
78
|
+
if template.push_gem
|
79
|
+
if ::File.directory?(".git") && capture("git status -s").strip != ""
|
80
|
+
logger.error "Cannot push the gem when there are uncommited changes"
|
81
|
+
exit(1)
|
82
|
+
end
|
83
|
+
sh "gem push pkg/#{gemfile}"
|
84
|
+
if template.tag
|
85
|
+
sh "git tag v#{version}"
|
86
|
+
if template.push_tag
|
87
|
+
template.push_tag = "origin" if template.push_tag == true
|
88
|
+
sh "git push #{template.push_tag} v#{version}"
|
89
|
+
end
|
88
90
|
end
|
89
91
|
end
|
90
92
|
end
|
@@ -27,57 +27,63 @@
|
|
27
27
|
# POSSIBILITY OF SUCH DAMAGE.
|
28
28
|
;
|
29
29
|
|
30
|
-
module Toys
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
30
|
+
module Toys
|
31
|
+
module Templates
|
32
|
+
##
|
33
|
+
# A template for tools that run minitest
|
34
|
+
#
|
35
|
+
class Minitest
|
36
|
+
include Template
|
36
37
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
38
|
+
def initialize(opts = {})
|
39
|
+
@name = opts[:name] || "test"
|
40
|
+
@libs = opts[:libs] || ["lib"]
|
41
|
+
@files = opts[:files] || ["test/test*.rb"]
|
42
|
+
@warnings = opts.include?(:warnings) ? opts[:warnings] : true
|
43
|
+
end
|
43
44
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
45
|
+
attr_accessor :name
|
46
|
+
attr_accessor :libs
|
47
|
+
attr_accessor :files
|
48
|
+
attr_accessor :warnings
|
48
49
|
|
49
|
-
|
50
|
-
|
51
|
-
|
50
|
+
to_expand do |template|
|
51
|
+
name(template.name) do
|
52
|
+
desc "Run minitest on the current project."
|
52
53
|
|
53
|
-
|
54
|
+
use :exec
|
54
55
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
56
|
+
switch(
|
57
|
+
:warnings, "-w", "--[no-]warnings",
|
58
|
+
default: template.warnings,
|
59
|
+
doc: "Turn on Ruby warnings (defaults to #{template.warnings})"
|
60
|
+
)
|
61
|
+
remaining_args(:tests, doc: "Paths to the tests to run (defaults to all tests)")
|
61
62
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
63
|
+
execute do
|
64
|
+
ruby_args = []
|
65
|
+
unless template.libs.empty?
|
66
|
+
lib_path = template.libs.join(::File::PATH_SEPARATOR)
|
67
|
+
ruby_args << "-I#{lib_path}"
|
68
|
+
end
|
69
|
+
ruby_args << "-w" if self[:warnings]
|
69
70
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
71
|
+
tests = self[:tests]
|
72
|
+
if tests.empty?
|
73
|
+
Array(template.files).each do |pattern|
|
74
|
+
tests.concat(::Dir.glob(pattern))
|
75
|
+
end
|
76
|
+
tests.uniq!
|
74
77
|
end
|
75
|
-
tests.uniq!
|
76
|
-
end
|
77
78
|
|
78
|
-
|
79
|
-
|
80
|
-
|
79
|
+
result = ruby(ruby_args, in_from: :controller) do |controller|
|
80
|
+
tests.each do |file|
|
81
|
+
controller.in.puts("load '#{file}'")
|
82
|
+
end
|
83
|
+
end
|
84
|
+
if result.error?
|
85
|
+
logger.error("Minitest failed!")
|
86
|
+
exit(result.exit_code)
|
81
87
|
end
|
82
88
|
end
|
83
89
|
end
|
@@ -27,37 +27,39 @@
|
|
27
27
|
# POSSIBILITY OF SUCH DAMAGE.
|
28
28
|
;
|
29
29
|
|
30
|
-
module Toys
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
30
|
+
module Toys
|
31
|
+
module Templates
|
32
|
+
##
|
33
|
+
# A template for tools that run rubocop
|
34
|
+
#
|
35
|
+
class Rubocop
|
36
|
+
include Template
|
36
37
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
38
|
+
def initialize(opts = {})
|
39
|
+
@name = opts[:name] || "rubocop"
|
40
|
+
@fail_on_error = opts.include?(:fail_on_error) ? opts[:fail_on_error] : true
|
41
|
+
@options = opts[:options] || []
|
42
|
+
end
|
42
43
|
|
43
|
-
|
44
|
-
|
45
|
-
|
44
|
+
attr_accessor :name
|
45
|
+
attr_accessor :fail_on_error
|
46
|
+
attr_accessor :options
|
46
47
|
|
47
|
-
|
48
|
-
|
49
|
-
|
48
|
+
to_expand do |template|
|
49
|
+
name(template.name) do
|
50
|
+
desc "Run rubocop on the current project."
|
50
51
|
|
51
|
-
|
52
|
+
use :exec
|
52
53
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
54
|
+
execute do
|
55
|
+
require "rubocop"
|
56
|
+
cli = ::RuboCop::CLI.new
|
57
|
+
logger.info "Running RuboCop..."
|
58
|
+
result = cli.run(template.options)
|
59
|
+
if result.nonzero?
|
60
|
+
logger.error "RuboCop failed!"
|
61
|
+
exit(1) if template.fail_on_error
|
62
|
+
end
|
61
63
|
end
|
62
64
|
end
|
63
65
|
end
|
@@ -27,50 +27,52 @@
|
|
27
27
|
# POSSIBILITY OF SUCH DAMAGE.
|
28
28
|
;
|
29
29
|
|
30
|
-
module Toys
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
30
|
+
module Toys
|
31
|
+
module Templates
|
32
|
+
##
|
33
|
+
# A template for tools that run yardoc
|
34
|
+
#
|
35
|
+
class Yardoc
|
36
|
+
include Template
|
36
37
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
38
|
+
def initialize(opts = {})
|
39
|
+
@name = opts[:name] || "yardoc"
|
40
|
+
@files = opts[:files] || []
|
41
|
+
@options = opts[:options] || []
|
42
|
+
@stats_options = opts[:stats_options] || []
|
43
|
+
end
|
43
44
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
45
|
+
attr_accessor :name
|
46
|
+
attr_accessor :files
|
47
|
+
attr_accessor :options
|
48
|
+
attr_accessor :stats_options
|
48
49
|
|
49
|
-
|
50
|
-
|
51
|
-
|
50
|
+
to_expand do |template|
|
51
|
+
name(template.name) do
|
52
|
+
desc "Run yardoc on the current project."
|
52
53
|
|
53
|
-
|
54
|
+
use :exec
|
54
55
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
56
|
+
execute do
|
57
|
+
require "yard"
|
58
|
+
files = []
|
59
|
+
patterns = Array(template.files)
|
60
|
+
patterns = ["lib/**/*.rb"] if patterns.empty?
|
61
|
+
patterns.each do |pattern|
|
62
|
+
files.concat(::Dir.glob(pattern))
|
63
|
+
end
|
64
|
+
files.uniq!
|
64
65
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
66
|
+
unless template.stats_options.empty?
|
67
|
+
template.options << "--no-stats"
|
68
|
+
template.stats_options << "--use-cache"
|
69
|
+
end
|
69
70
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
71
|
+
yardoc = ::YARD::CLI::Yardoc.new
|
72
|
+
yardoc.run(*(template.options + files))
|
73
|
+
unless template.stats_options.empty?
|
74
|
+
::YARD::CLI::Stats.run(*template.stats_options)
|
75
|
+
end
|
74
76
|
end
|
75
77
|
end
|
76
78
|
end
|
data/lib/toys/tool.rb
CHANGED
@@ -34,17 +34,17 @@ module Toys
|
|
34
34
|
# A tool definition
|
35
35
|
#
|
36
36
|
class Tool
|
37
|
-
def initialize(
|
38
|
-
@lookup = lookup
|
37
|
+
def initialize(full_name, middleware_stack)
|
39
38
|
@full_name = full_name
|
39
|
+
@middleware_stack = middleware_stack.dup
|
40
40
|
|
41
41
|
@definition_path = nil
|
42
42
|
@cur_path = nil
|
43
|
-
|
44
43
|
@alias_target = nil
|
44
|
+
@definition_finished = false
|
45
45
|
|
46
|
+
@desc = nil
|
46
47
|
@long_desc = nil
|
47
|
-
@short_desc = nil
|
48
48
|
|
49
49
|
@default_data = {}
|
50
50
|
@switches = []
|
@@ -57,7 +57,6 @@ module Toys
|
|
57
57
|
@executor = nil
|
58
58
|
end
|
59
59
|
|
60
|
-
attr_reader :lookup
|
61
60
|
attr_reader :full_name
|
62
61
|
attr_reader :switches
|
63
62
|
attr_reader :required_args
|
@@ -68,6 +67,8 @@ module Toys
|
|
68
67
|
attr_reader :helpers
|
69
68
|
attr_reader :executor
|
70
69
|
attr_reader :alias_target
|
70
|
+
attr_reader :middleware_stack
|
71
|
+
attr_reader :definition_path
|
71
72
|
|
72
73
|
def simple_name
|
73
74
|
full_name.last
|
@@ -81,40 +82,41 @@ module Toys
|
|
81
82
|
full_name.empty?
|
82
83
|
end
|
83
84
|
|
84
|
-
def
|
85
|
-
|
85
|
+
def includes_executor?
|
86
|
+
executor.is_a?(::Proc)
|
86
87
|
end
|
87
88
|
|
88
89
|
def alias?
|
89
90
|
!alias_target.nil?
|
90
91
|
end
|
91
92
|
|
92
|
-
def
|
93
|
-
@
|
93
|
+
def effective_desc
|
94
|
+
@desc || default_desc
|
94
95
|
end
|
95
96
|
|
96
|
-
def
|
97
|
-
|
98
|
-
@lookup.exact_tool(full_name.slice(0..-2))
|
97
|
+
def effective_long_desc
|
98
|
+
@long_desc || @desc || default_desc
|
99
99
|
end
|
100
100
|
|
101
|
-
def
|
102
|
-
|
101
|
+
def includes_description?
|
102
|
+
!@long_desc.nil? || !@desc.nil?
|
103
103
|
end
|
104
104
|
|
105
|
-
def
|
106
|
-
|
105
|
+
def includes_arguments?
|
106
|
+
!default_data.empty? || !switches.empty? ||
|
107
|
+
!required_args.empty? || !optional_args.empty? || !remaining_args.nil?
|
107
108
|
end
|
108
109
|
|
109
|
-
def
|
110
|
-
|
110
|
+
def includes_helpers?
|
111
|
+
!helpers.empty? || !modules.empty?
|
111
112
|
end
|
112
113
|
|
113
114
|
def includes_definition?
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
115
|
+
alias? || includes_arguments? || includes_executor? || includes_helpers?
|
116
|
+
end
|
117
|
+
|
118
|
+
def used_switches
|
119
|
+
@switches.reduce([]) { |used, switch| used + switch.switches }.uniq
|
118
120
|
end
|
119
121
|
|
120
122
|
def defining_from(path)
|
@@ -142,22 +144,17 @@ module Toys
|
|
142
144
|
if root?
|
143
145
|
raise ToolDefinitionError, "Cannot make the root tool an alias"
|
144
146
|
end
|
145
|
-
if only_collection?
|
146
|
-
raise ToolDefinitionError, "Tool #{display_name.inspect} is already" \
|
147
|
-
" a collection and cannot be made an alias"
|
148
|
-
end
|
149
147
|
if includes_description? || includes_definition?
|
150
148
|
raise ToolDefinitionError, "Tool #{display_name.inspect} already has" \
|
151
149
|
" a definition and cannot be made an alias"
|
152
150
|
end
|
153
|
-
parent.ensure_collection_only(full_name) unless root?
|
154
151
|
@alias_target = target_word
|
155
152
|
self
|
156
153
|
end
|
157
154
|
|
158
|
-
def
|
155
|
+
def desc=(str)
|
159
156
|
check_definition_state
|
160
|
-
@
|
157
|
+
@desc = str
|
161
158
|
end
|
162
159
|
|
163
160
|
def long_desc=(str)
|
@@ -166,95 +163,109 @@ module Toys
|
|
166
163
|
end
|
167
164
|
|
168
165
|
def add_helper(name, &block)
|
169
|
-
check_definition_state
|
166
|
+
check_definition_state
|
170
167
|
name_str = name.to_s
|
171
168
|
unless name_str =~ /^[a-z]\w+$/
|
172
169
|
raise ToolDefinitionError, "Illegal helper name: #{name_str.inspect}"
|
173
170
|
end
|
174
171
|
@helpers[name.to_sym] = block
|
172
|
+
self
|
175
173
|
end
|
176
174
|
|
177
|
-
def use_module(
|
178
|
-
check_definition_state
|
179
|
-
case
|
175
|
+
def use_module(name)
|
176
|
+
check_definition_state
|
177
|
+
case name
|
180
178
|
when ::Module
|
181
|
-
@modules <<
|
179
|
+
@modules << name
|
182
180
|
when ::Symbol
|
183
|
-
mod =
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
require "toys/helpers/#{file_name}"
|
189
|
-
const_name = mod.gsub(/(^|_)([a-zA-Z0-9])/) { |_m| $2.upcase }
|
190
|
-
@modules << Helpers.const_get(const_name)
|
181
|
+
mod = Helpers.lookup(name.to_s)
|
182
|
+
if mod.nil?
|
183
|
+
raise ToolDefinitionError, "Module not found: #{name.inspect}"
|
184
|
+
end
|
185
|
+
@modules << mod
|
191
186
|
else
|
192
|
-
raise ToolDefinitionError, "Illegal helper module name: #{
|
187
|
+
raise ToolDefinitionError, "Illegal helper module name: #{name.inspect}"
|
193
188
|
end
|
189
|
+
self
|
194
190
|
end
|
195
191
|
|
196
|
-
def add_switch(key, *switches,
|
197
|
-
|
198
|
-
|
192
|
+
def add_switch(key, *switches,
|
193
|
+
accept: nil, default: nil, doc: nil, only_unique: false, handler: nil)
|
194
|
+
check_definition_state
|
199
195
|
switches << "--#{Tool.canonical_switch(key)}=VALUE" if switches.empty?
|
200
196
|
switches << accept unless accept.nil?
|
201
197
|
switches += Array(doc)
|
202
|
-
|
198
|
+
switch_info = SwitchInfo.new(key, switches, handler)
|
199
|
+
if only_unique
|
200
|
+
switch_info.remove_switches(used_switches)
|
201
|
+
end
|
202
|
+
if switch_info.active?
|
203
|
+
@default_data[key] = default
|
204
|
+
@switches << switch_info
|
205
|
+
end
|
206
|
+
self
|
203
207
|
end
|
204
208
|
|
205
209
|
def add_required_arg(key, accept: nil, doc: nil)
|
206
|
-
check_definition_state
|
210
|
+
check_definition_state
|
207
211
|
@default_data[key] = nil
|
208
212
|
@required_args << ArgInfo.new(key, accept, Array(doc))
|
213
|
+
self
|
209
214
|
end
|
210
215
|
|
211
216
|
def add_optional_arg(key, accept: nil, default: nil, doc: nil)
|
212
|
-
check_definition_state
|
217
|
+
check_definition_state
|
213
218
|
@default_data[key] = default
|
214
219
|
@optional_args << ArgInfo.new(key, accept, Array(doc))
|
220
|
+
self
|
215
221
|
end
|
216
222
|
|
217
223
|
def set_remaining_args(key, accept: nil, default: [], doc: nil)
|
218
|
-
check_definition_state
|
224
|
+
check_definition_state
|
219
225
|
@default_data[key] = default
|
220
226
|
@remaining_args = ArgInfo.new(key, accept, Array(doc))
|
227
|
+
self
|
221
228
|
end
|
222
229
|
|
223
230
|
def executor=(executor)
|
224
|
-
check_definition_state
|
231
|
+
check_definition_state
|
225
232
|
@executor = executor
|
226
233
|
end
|
227
234
|
|
228
|
-
def
|
229
|
-
|
235
|
+
def finish_definition
|
236
|
+
if !alias? && !@definition_finished
|
237
|
+
config_proc = proc {}
|
238
|
+
middleware_stack.reverse.each do |middleware|
|
239
|
+
config_proc = make_config_proc(middleware, config_proc)
|
240
|
+
end
|
241
|
+
config_proc.call
|
242
|
+
end
|
243
|
+
@definition_finished = true
|
244
|
+
self
|
230
245
|
end
|
231
246
|
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
if includes_definition?
|
236
|
-
raise ToolDefinitionError, "Cannot create tool #{source_name.inspect}" \
|
237
|
-
" because #{display_name.inspect} is already a tool."
|
238
|
-
end
|
239
|
-
unless @executor == false
|
240
|
-
@executor = false
|
241
|
-
parent.ensure_collection_only(source_name) unless root?
|
242
|
-
end
|
247
|
+
def execute(context_base, args, verbosity: 0)
|
248
|
+
finish_definition unless @definition_finished
|
249
|
+
Execution.new(self).execute(context_base, args, verbosity: verbosity)
|
243
250
|
end
|
244
251
|
|
245
252
|
private
|
246
253
|
|
254
|
+
def make_config_proc(middleware, next_config)
|
255
|
+
proc { middleware.config(self, &next_config) }
|
256
|
+
end
|
257
|
+
|
247
258
|
def default_desc
|
248
259
|
if alias?
|
249
260
|
"(Alias of #{@alias_target.inspect})"
|
250
|
-
elsif
|
261
|
+
elsif includes_executor?
|
251
262
|
"(No description available)"
|
252
263
|
else
|
253
|
-
"(A
|
264
|
+
"(A group of commands)"
|
254
265
|
end
|
255
266
|
end
|
256
267
|
|
257
|
-
def check_definition_state
|
268
|
+
def check_definition_state
|
258
269
|
if alias?
|
259
270
|
raise ToolDefinitionError, "Tool #{display_name.inspect} is an alias"
|
260
271
|
end
|
@@ -264,13 +275,9 @@ module Toys
|
|
264
275
|
"Cannot redefine tool #{display_name.inspect} #{in_clause}" \
|
265
276
|
"(already defined in #{@definition_path})"
|
266
277
|
end
|
267
|
-
if
|
268
|
-
|
269
|
-
|
270
|
-
"Cannot make tool #{display_name.inspect} executable because" \
|
271
|
-
" a descendant is already executable"
|
272
|
-
end
|
273
|
-
parent.ensure_collection_only(full_name) unless root?
|
278
|
+
if @definition_finished
|
279
|
+
raise ToolDefinitionError,
|
280
|
+
"Defintion of tool #{display_name.inspect} is already finished"
|
274
281
|
end
|
275
282
|
end
|
276
283
|
|
@@ -284,13 +291,44 @@ module Toys
|
|
284
291
|
# Representation of a formal switch
|
285
292
|
#
|
286
293
|
class SwitchInfo
|
287
|
-
def initialize(key, optparse_info)
|
294
|
+
def initialize(key, optparse_info, handler = nil)
|
288
295
|
@key = key
|
289
296
|
@optparse_info = optparse_info
|
297
|
+
@handler = handler || ->(val, _cur) { val }
|
298
|
+
@switches = nil
|
290
299
|
end
|
291
300
|
|
292
301
|
attr_reader :key
|
293
302
|
attr_reader :optparse_info
|
303
|
+
attr_reader :handler
|
304
|
+
|
305
|
+
def switches
|
306
|
+
@switches ||= optparse_info.map { |s| extract_switch(s) }.flatten
|
307
|
+
end
|
308
|
+
|
309
|
+
def active?
|
310
|
+
!switches.empty?
|
311
|
+
end
|
312
|
+
|
313
|
+
def remove_switches(switches)
|
314
|
+
@optparse_info.select! do |s|
|
315
|
+
extract_switch(s).all? { |ss| !switches.include?(ss) }
|
316
|
+
end
|
317
|
+
@switches = nil
|
318
|
+
self
|
319
|
+
end
|
320
|
+
|
321
|
+
def extract_switch(str)
|
322
|
+
if str =~ /^(-[\?\w])/
|
323
|
+
[$1]
|
324
|
+
elsif str =~ /^--\[no-\](\w[\w-]*)/
|
325
|
+
["--#{$1}", "--no-#{$1}"]
|
326
|
+
elsif str =~ /^(--\w[\w-]*)/
|
327
|
+
[$1]
|
328
|
+
else
|
329
|
+
[]
|
330
|
+
end
|
331
|
+
end
|
294
332
|
end
|
295
333
|
|
296
334
|
##
|
@@ -309,7 +347,7 @@ module Toys
|
|
309
347
|
|
310
348
|
def process_value(val)
|
311
349
|
return val unless accept
|
312
|
-
n =
|
350
|
+
n = canonical_name
|
313
351
|
result = val
|
314
352
|
optparse = ::OptionParser.new
|
315
353
|
optparse.on("--#{n}=VALUE", accept) { |v| result = v }
|
@@ -329,235 +367,59 @@ module Toys
|
|
329
367
|
class Execution
|
330
368
|
def initialize(tool)
|
331
369
|
@tool = tool
|
370
|
+
@data = @tool.default_data.dup
|
371
|
+
@data[Context::TOOL] = tool
|
372
|
+
@data[Context::TOOL_NAME] = tool.full_name
|
332
373
|
end
|
333
374
|
|
334
|
-
def execute(context_base, args)
|
375
|
+
def execute(context_base, args, verbosity: 0)
|
335
376
|
return execute_alias(context_base, args) if @tool.alias?
|
336
377
|
|
337
|
-
|
338
|
-
context = create_child_context(context_base
|
378
|
+
parse_args(args, verbosity)
|
379
|
+
context = create_child_context(context_base)
|
339
380
|
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
show_usage(parsed_args.optparse, recursive: parsed_args.recursive)
|
347
|
-
0
|
348
|
-
else
|
349
|
-
catch(:result) do
|
350
|
-
context.instance_eval(&@tool.executor)
|
351
|
-
0
|
352
|
-
end
|
381
|
+
original_level = context.logger.level
|
382
|
+
context.logger.level = context_base.base_level - @data[Context::VERBOSITY]
|
383
|
+
begin
|
384
|
+
perform_execution(context)
|
385
|
+
ensure
|
386
|
+
context.logger.level = original_level
|
353
387
|
end
|
354
388
|
end
|
355
389
|
|
356
390
|
private
|
357
391
|
|
358
|
-
def
|
359
|
-
|
360
|
-
|
361
|
-
@
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
context
|
368
|
-
end
|
369
|
-
|
370
|
-
def show_usage(optparse, recursive: false)
|
371
|
-
puts(optparse.to_s)
|
372
|
-
if @tool.leaf?
|
373
|
-
required_args = @tool.required_args
|
374
|
-
optional_args = @tool.optional_args
|
375
|
-
remaining_args = @tool.remaining_args
|
376
|
-
if !required_args.empty? || !optional_args.empty? || remaining_args
|
377
|
-
show_positional_arguments(required_args, optional_args, remaining_args)
|
378
|
-
end
|
379
|
-
else
|
380
|
-
show_command_list(recursive)
|
381
|
-
end
|
382
|
-
end
|
383
|
-
|
384
|
-
def show_positional_arguments(required_args, optional_args, remaining_args)
|
385
|
-
puts("")
|
386
|
-
puts("Positional arguments:")
|
387
|
-
args_to_display = required_args + optional_args
|
388
|
-
args_to_display << remaining_args if remaining_args
|
389
|
-
args_to_display.each do |arg_info|
|
390
|
-
puts(" #{arg_info.canonical_name.ljust(31)} #{arg_info.doc.first}")
|
391
|
-
next if arg_info.doc.empty?
|
392
|
-
arg_info.doc[1..-1].each do |d|
|
393
|
-
puts(" #{d}")
|
394
|
-
end
|
395
|
-
end
|
396
|
-
end
|
397
|
-
|
398
|
-
def show_command_list(recursive)
|
399
|
-
puts("")
|
400
|
-
puts("Commands:")
|
401
|
-
name_len = @tool.full_name.length
|
402
|
-
@tool.lookup.list_subtools(@tool.full_name, recursive).each do |subtool|
|
403
|
-
desc = subtool.effective_short_desc
|
404
|
-
tool_name = subtool.full_name.slice(name_len..-1).join(" ").ljust(31)
|
405
|
-
puts(" #{tool_name} #{desc}")
|
406
|
-
end
|
407
|
-
end
|
408
|
-
|
409
|
-
def execute_alias(context_base, args)
|
410
|
-
target_name = @tool.full_name.slice(0..-2) + [@tool.alias_target]
|
411
|
-
target_tool = @tool.lookup.lookup(target_name)
|
412
|
-
if target_tool.full_name == target_name
|
413
|
-
target_tool.execute(context_base, args)
|
414
|
-
else
|
415
|
-
logger.fatal("Alias target #{@tool.alias_target.inspect} not found")
|
416
|
-
-1
|
417
|
-
end
|
418
|
-
end
|
419
|
-
end
|
420
|
-
|
421
|
-
##
|
422
|
-
# An internal class that manages parsing of tool arguments
|
423
|
-
# @private
|
424
|
-
#
|
425
|
-
class ParsedArgs
|
426
|
-
def initialize(tool, binary_name, args)
|
427
|
-
binary_name ||= ::File.basename($PROGRAM_NAME)
|
428
|
-
@show_help = !tool.leaf?
|
429
|
-
@usage_error = nil
|
430
|
-
@delta_severity = 0
|
431
|
-
@recursive = false
|
432
|
-
@data = tool.default_data.dup
|
433
|
-
@optparse = create_option_parser(tool, binary_name)
|
434
|
-
parse_args(args, tool)
|
435
|
-
end
|
436
|
-
|
437
|
-
attr_reader :show_help
|
438
|
-
attr_reader :usage_error
|
439
|
-
attr_reader :delta_severity
|
440
|
-
attr_reader :recursive
|
441
|
-
attr_reader :data
|
442
|
-
attr_reader :optparse
|
443
|
-
|
444
|
-
private
|
445
|
-
|
446
|
-
##
|
447
|
-
# Well-known flags
|
448
|
-
# @private
|
449
|
-
#
|
450
|
-
SPECIAL_FLAGS = %w[
|
451
|
-
-q
|
452
|
-
--quiet
|
453
|
-
-v
|
454
|
-
--verbose
|
455
|
-
-?
|
456
|
-
-h
|
457
|
-
--help
|
458
|
-
].freeze
|
459
|
-
|
460
|
-
def parse_args(args, tool)
|
461
|
-
remaining = @optparse.parse(args)
|
462
|
-
remaining = parse_required_args(remaining, tool, args)
|
463
|
-
remaining = parse_optional_args(remaining, tool)
|
464
|
-
parse_remaining_args(remaining, tool, args)
|
392
|
+
def parse_args(args, base_verbosity)
|
393
|
+
optparse = create_option_parser
|
394
|
+
@data[Context::VERBOSITY] = base_verbosity
|
395
|
+
@data[Context::ARGS] = args
|
396
|
+
@data[Context::USAGE_ERROR] = nil
|
397
|
+
remaining = optparse.parse(args)
|
398
|
+
remaining = parse_required_args(remaining, args)
|
399
|
+
remaining = parse_optional_args(remaining)
|
400
|
+
parse_remaining_args(remaining, args)
|
465
401
|
rescue ::OptionParser::ParseError => e
|
466
|
-
@
|
402
|
+
@data[Context::USAGE_ERROR] = e.message
|
467
403
|
end
|
468
404
|
|
469
|
-
def create_option_parser
|
405
|
+
def create_option_parser
|
470
406
|
optparse = ::OptionParser.new
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
unless tool.effective_long_desc.empty?
|
478
|
-
optparse.separator("")
|
479
|
-
optparse.separator(tool.effective_long_desc)
|
480
|
-
end
|
481
|
-
optparse.separator("")
|
482
|
-
optparse.separator("Options:")
|
483
|
-
if tool.leaf?
|
484
|
-
leaf_switches(tool, optparse)
|
485
|
-
else
|
486
|
-
collection_switches(optparse)
|
487
|
-
end
|
488
|
-
optparse
|
489
|
-
end
|
490
|
-
|
491
|
-
def leaf_banner(tool, binary_name)
|
492
|
-
banner = ["Usage:", binary_name] + tool.full_name
|
493
|
-
banner << "[<options...>]" unless tool.switches.empty?
|
494
|
-
tool.required_args.each do |arg_info|
|
495
|
-
banner << "<#{arg_info.canonical_name}>"
|
496
|
-
end
|
497
|
-
tool.optional_args.each do |arg_info|
|
498
|
-
banner << "[<#{arg_info.canonical_name}>]"
|
499
|
-
end
|
500
|
-
if tool.remaining_args
|
501
|
-
banner << "[<#{tool.remaining_args.canonical_name}...>]"
|
502
|
-
end
|
503
|
-
banner.join(" ")
|
504
|
-
end
|
505
|
-
|
506
|
-
def collection_banner(tool, binary_name)
|
507
|
-
(["Usage:", binary_name] + tool.full_name + ["<command>", "[<options...>]"]).join(" ")
|
508
|
-
end
|
509
|
-
|
510
|
-
def leaf_switches(tool, optparse)
|
511
|
-
found_special_flags = []
|
512
|
-
leaf_normal_switches(tool.switches, optparse, found_special_flags)
|
513
|
-
leaf_verbose_switch(optparse, found_special_flags)
|
514
|
-
leaf_quiet_switch(optparse, found_special_flags)
|
515
|
-
leaf_help_switch(optparse, found_special_flags)
|
516
|
-
end
|
517
|
-
|
518
|
-
def collection_switches(optparse)
|
519
|
-
optparse.on("-?", "--help", "Show help message")
|
520
|
-
optparse.on("-r", "--[no-]recursive", "Show all subcommands recursively") do |val|
|
521
|
-
@recursive = val
|
522
|
-
end
|
523
|
-
end
|
524
|
-
|
525
|
-
def leaf_normal_switches(switches, optparse, found_special_flags)
|
526
|
-
switches.each do |switch|
|
527
|
-
found_special_flags |= (switch.optparse_info & SPECIAL_FLAGS)
|
407
|
+
# The following clears out the Officious (hidden default switches).
|
408
|
+
optparse.remove
|
409
|
+
optparse.remove
|
410
|
+
optparse.new
|
411
|
+
optparse.new
|
412
|
+
@tool.switches.each do |switch|
|
528
413
|
optparse.on(*switch.optparse_info) do |val|
|
529
|
-
@data[switch.key] = val
|
414
|
+
@data[switch.key] = switch.handler.call(val, @data[switch.key])
|
530
415
|
end
|
531
416
|
end
|
417
|
+
optparse
|
532
418
|
end
|
533
419
|
|
534
|
-
def
|
535
|
-
|
536
|
-
|
537
|
-
optparse.on(*(flags + ["Increase verbosity"])) do
|
538
|
-
@delta_severity -= 1
|
539
|
-
end
|
540
|
-
end
|
541
|
-
|
542
|
-
def leaf_quiet_switch(optparse, found_special_flags)
|
543
|
-
flags = ["-q", "--quiet"] - found_special_flags
|
544
|
-
return if flags.empty?
|
545
|
-
optparse.on(*(flags + ["Decrease verbosity"])) do
|
546
|
-
@delta_severity += 1
|
547
|
-
end
|
548
|
-
end
|
549
|
-
|
550
|
-
def leaf_help_switch(optparse, found_special_flags)
|
551
|
-
flags = ["-?", "-h", "--help"] - found_special_flags
|
552
|
-
return if flags.empty?
|
553
|
-
optparse.on(*(flags + ["Show help message"])) do
|
554
|
-
@show_help = true
|
555
|
-
end
|
556
|
-
end
|
557
|
-
|
558
|
-
def parse_required_args(remaining, tool, args)
|
559
|
-
tool.required_args.each do |arg_info|
|
560
|
-
if !@show_help && remaining.empty?
|
420
|
+
def parse_required_args(remaining, args)
|
421
|
+
@tool.required_args.each do |arg_info|
|
422
|
+
if remaining.empty?
|
561
423
|
reason = "No value given for required argument named <#{arg_info.canonical_name}>"
|
562
424
|
raise create_parse_error(args, reason)
|
563
425
|
end
|
@@ -566,25 +428,25 @@ module Toys
|
|
566
428
|
remaining
|
567
429
|
end
|
568
430
|
|
569
|
-
def parse_optional_args(remaining
|
570
|
-
tool.optional_args.each do |arg_info|
|
431
|
+
def parse_optional_args(remaining)
|
432
|
+
@tool.optional_args.each do |arg_info|
|
571
433
|
break if remaining.empty?
|
572
434
|
@data[arg_info.key] = arg_info.process_value(remaining.shift)
|
573
435
|
end
|
574
436
|
remaining
|
575
437
|
end
|
576
438
|
|
577
|
-
def parse_remaining_args(remaining,
|
439
|
+
def parse_remaining_args(remaining, args)
|
578
440
|
return if remaining.empty?
|
579
|
-
unless tool.remaining_args
|
580
|
-
if tool.
|
441
|
+
unless @tool.remaining_args
|
442
|
+
if @tool.includes_executor?
|
581
443
|
raise create_parse_error(remaining, "Extra arguments provided")
|
582
444
|
else
|
583
|
-
raise create_parse_error(tool.full_name + args, "Tool not found")
|
445
|
+
raise create_parse_error(@tool.full_name + args, "Tool not found")
|
584
446
|
end
|
585
447
|
end
|
586
|
-
@data[tool.remaining_args.key] =
|
587
|
-
remaining.map { |arg| tool.remaining_args.process_value(arg) }
|
448
|
+
@data[@tool.remaining_args.key] =
|
449
|
+
remaining.map { |arg| @tool.remaining_args.process_value(arg) }
|
588
450
|
end
|
589
451
|
|
590
452
|
def create_parse_error(path, reason)
|
@@ -592,6 +454,50 @@ module Toys
|
|
592
454
|
e.reason = reason
|
593
455
|
end
|
594
456
|
end
|
457
|
+
|
458
|
+
def create_child_context(context_base)
|
459
|
+
context = context_base.create_context(@data)
|
460
|
+
@tool.modules.each do |mod|
|
461
|
+
context.extend(mod)
|
462
|
+
end
|
463
|
+
@tool.helpers.each do |name, block|
|
464
|
+
context.define_singleton_method(name, &block)
|
465
|
+
end
|
466
|
+
context
|
467
|
+
end
|
468
|
+
|
469
|
+
def perform_execution(context)
|
470
|
+
executor = proc do
|
471
|
+
if @tool.includes_executor?
|
472
|
+
context.instance_eval(&@tool.executor)
|
473
|
+
else
|
474
|
+
context.logger.fatal("No implementation for #{@tool.display_name.inspect}")
|
475
|
+
context.exit(-1)
|
476
|
+
end
|
477
|
+
end
|
478
|
+
@tool.middleware_stack.reverse.each do |middleware|
|
479
|
+
executor = make_executor(middleware, context, executor)
|
480
|
+
end
|
481
|
+
catch(:result) do
|
482
|
+
executor.call
|
483
|
+
0
|
484
|
+
end
|
485
|
+
end
|
486
|
+
|
487
|
+
def make_executor(middleware, context, next_executor)
|
488
|
+
proc { middleware.execute(context, &next_executor) }
|
489
|
+
end
|
490
|
+
|
491
|
+
def execute_alias(context_base, args)
|
492
|
+
target_name = @tool.full_name.slice(0..-2) + [@tool.alias_target]
|
493
|
+
target_tool = context_base.loader.lookup(target_name)
|
494
|
+
if target_tool.full_name == target_name
|
495
|
+
target_tool.execute(context_base, args)
|
496
|
+
else
|
497
|
+
context_base.logger.fatal("Alias target #{@tool.alias_target.inspect} not found")
|
498
|
+
-1
|
499
|
+
end
|
500
|
+
end
|
595
501
|
end
|
596
502
|
end
|
597
503
|
end
|