toys 0.2.1 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/LICENSE.md +29 -0
- data/README.md +36 -0
- data/bin/toys +31 -2
- data/lib/toys.rb +45 -1
- data/lib/toys/builder.rb +162 -0
- data/lib/toys/builtins/system.rb +32 -10
- data/lib/toys/cli.rb +83 -27
- data/lib/toys/context.rb +65 -13
- data/lib/toys/errors.rb +37 -2
- data/lib/toys/helpers/exec.rb +262 -31
- data/lib/toys/helpers/file_utils.rb +34 -4
- data/lib/toys/lookup.rb +152 -81
- data/lib/toys/template.rb +43 -14
- data/lib/toys/templates/clean.rb +67 -0
- data/lib/toys/templates/gem_build.rb +68 -36
- data/lib/toys/templates/minitest.rb +75 -29
- data/lib/toys/templates/rubocop.rb +66 -0
- data/lib/toys/templates/yardoc.rb +79 -0
- data/lib/toys/tool.rb +400 -215
- data/lib/toys/version.rb +34 -1
- metadata +13 -36
- data/lib/toys/parser.rb +0 -138
@@ -1,58 +1,90 @@
|
|
1
|
+
# Copyright 2018 Daniel Azuma
|
2
|
+
#
|
3
|
+
# All rights reserved.
|
4
|
+
#
|
5
|
+
# Redistribution and use in source and binary forms, with or without
|
6
|
+
# modification, are permitted provided that the following conditions are met:
|
7
|
+
#
|
8
|
+
# * Redistributions of source code must retain the above copyright notice,
|
9
|
+
# this list of conditions and the following disclaimer.
|
10
|
+
# * Redistributions in binary form must reproduce the above copyright notice,
|
11
|
+
# this list of conditions and the following disclaimer in the documentation
|
12
|
+
# and/or other materials provided with the distribution.
|
13
|
+
# * Neither the name of the copyright holder, nor the names of any other
|
14
|
+
# contributors to this software, may be used to endorse or promote products
|
15
|
+
# derived from this software without specific prior written permission.
|
16
|
+
#
|
17
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
18
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
19
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
20
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
21
|
+
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
22
|
+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
23
|
+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
24
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
25
|
+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
26
|
+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
27
|
+
# POSSIBILITY OF SUCH DAMAGE.
|
28
|
+
;
|
29
|
+
|
1
30
|
require "rubygems/package"
|
2
31
|
|
3
|
-
module Toys
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
32
|
+
module Toys::Templates
|
33
|
+
##
|
34
|
+
# A template for tools that build and release gems
|
35
|
+
#
|
36
|
+
class GemBuild
|
37
|
+
include ::Toys::Template
|
38
|
+
|
39
|
+
def initialize(opts = {})
|
40
|
+
@name = opts[:name] || "build"
|
41
|
+
@gem_name = opts[:gem_name]
|
42
|
+
@push_gem = opts[:push_gem]
|
43
|
+
@tag = opts[:tag]
|
44
|
+
@push_tag = opts[:push_tag]
|
15
45
|
end
|
16
46
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
47
|
+
attr_accessor :name
|
48
|
+
attr_accessor :gem_name
|
49
|
+
attr_accessor :push_gem
|
50
|
+
attr_accessor :tag
|
51
|
+
attr_accessor :push_tag
|
52
|
+
|
53
|
+
to_expand do |template|
|
54
|
+
unless template.gem_name
|
21
55
|
candidates = ::Dir.glob("*.gemspec")
|
22
|
-
if candidates.
|
23
|
-
|
24
|
-
else
|
25
|
-
raise Toys::ToysDefinitionError, "Could not find a gemspec"
|
56
|
+
if candidates.empty?
|
57
|
+
raise ::Toys::ToolDefinitionError, "Could not find a gemspec"
|
26
58
|
end
|
59
|
+
template.gem_name = candidates.first.sub(/\.gemspec$/, "")
|
27
60
|
end
|
28
|
-
|
29
|
-
tag = opts[:tag]
|
30
|
-
push_tag = opts[:push_tag]
|
61
|
+
task_type = template.push_gem ? "Release" : "Build"
|
31
62
|
|
32
|
-
name
|
33
|
-
short_desc "#{
|
63
|
+
name(template.name) do
|
64
|
+
short_desc "#{task_type} the gem: #{template.gem_name}"
|
34
65
|
|
35
66
|
use :file_utils
|
36
67
|
use :exec
|
37
68
|
|
38
69
|
execute do
|
39
|
-
|
70
|
+
configure_exec(exit_on_nonzero_status: true)
|
71
|
+
gemspec = ::Gem::Specification.load "#{template.gem_name}.gemspec"
|
40
72
|
version = gemspec.version
|
41
|
-
gemfile = "#{gem_name}-#{version}.gem"
|
42
|
-
Gem::Package.build gemspec
|
73
|
+
gemfile = "#{template.gem_name}-#{version}.gem"
|
74
|
+
::Gem::Package.build gemspec
|
43
75
|
mkdir_p "pkg"
|
44
76
|
mv gemfile, "pkg"
|
45
|
-
if push_gem
|
46
|
-
if File.directory?(".git") && capture("git status -s").strip != ""
|
77
|
+
if template.push_gem
|
78
|
+
if ::File.directory?(".git") && capture("git status -s").strip != ""
|
47
79
|
logger.error "Cannot push the gem when there are uncommited changes"
|
48
80
|
exit(1)
|
49
81
|
end
|
50
|
-
sh "gem push pkg/#{gemfile}"
|
51
|
-
if tag
|
52
|
-
sh "git tag v#{version}"
|
53
|
-
if push_tag
|
54
|
-
push_tag = "origin" if push_tag == true
|
55
|
-
sh "git push #{push_tag} v#{version}"
|
82
|
+
sh "gem push pkg/#{gemfile}"
|
83
|
+
if template.tag
|
84
|
+
sh "git tag v#{version}"
|
85
|
+
if template.push_tag
|
86
|
+
template.push_tag = "origin" if template.push_tag == true
|
87
|
+
sh "git push #{template.push_tag} v#{version}"
|
56
88
|
end
|
57
89
|
end
|
58
90
|
end
|
@@ -1,39 +1,85 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
1
|
+
# Copyright 2018 Daniel Azuma
|
2
|
+
#
|
3
|
+
# All rights reserved.
|
4
|
+
#
|
5
|
+
# Redistribution and use in source and binary forms, with or without
|
6
|
+
# modification, are permitted provided that the following conditions are met:
|
7
|
+
#
|
8
|
+
# * Redistributions of source code must retain the above copyright notice,
|
9
|
+
# this list of conditions and the following disclaimer.
|
10
|
+
# * Redistributions in binary form must reproduce the above copyright notice,
|
11
|
+
# this list of conditions and the following disclaimer in the documentation
|
12
|
+
# and/or other materials provided with the distribution.
|
13
|
+
# * Neither the name of the copyright holder, nor the names of any other
|
14
|
+
# contributors to this software, may be used to endorse or promote products
|
15
|
+
# derived from this software without specific prior written permission.
|
16
|
+
#
|
17
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
18
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
19
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
20
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
21
|
+
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
22
|
+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
23
|
+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
24
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
25
|
+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
26
|
+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
27
|
+
# POSSIBILITY OF SUCH DAMAGE.
|
28
|
+
;
|
29
|
+
|
30
|
+
module Toys::Templates
|
31
|
+
##
|
32
|
+
# A template for tools that run minitest
|
33
|
+
#
|
34
|
+
class Minitest
|
35
|
+
include ::Toys::Template
|
36
|
+
|
37
|
+
def initialize(opts = {})
|
38
|
+
@name = opts[:name] || "test"
|
39
|
+
@libs = opts[:libs] || ["lib"]
|
40
|
+
@files = opts[:files] || ["test/test*.rb"]
|
41
|
+
@warnings = opts.include?(:warnings) ? opts[:warnings] : true
|
14
42
|
end
|
15
43
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
cmd << File.join(RbConfig::CONFIG["bindir"], RbConfig::CONFIG["ruby_install_name"])
|
24
|
-
cmd << "-I#{lib_path}" unless libs.empty?
|
25
|
-
cmd << "-w" if warning
|
26
|
-
cmd << "-e" << "ARGV.each{|f| load f}"
|
27
|
-
cmd << "--"
|
28
|
-
cmd = Shellwords.join(cmd + test_files)
|
29
|
-
|
30
|
-
name toy_name do
|
44
|
+
attr_accessor :name
|
45
|
+
attr_accessor :libs
|
46
|
+
attr_accessor :files
|
47
|
+
attr_accessor :warnings
|
48
|
+
|
49
|
+
to_expand do |template|
|
50
|
+
name(template.name) do
|
31
51
|
short_desc "Run minitest"
|
32
52
|
|
33
53
|
use :exec
|
34
54
|
|
55
|
+
switch(
|
56
|
+
:warnings, "-w", "--[no-]warnings",
|
57
|
+
default: template.warnings,
|
58
|
+
doc: "Turn on Ruby warnings (defaults to #{template.warnings})"
|
59
|
+
)
|
60
|
+
remaining_args(:tests, doc: "Paths to the tests to run (defaults to all tests)")
|
61
|
+
|
35
62
|
execute do
|
36
|
-
|
63
|
+
ruby_args = []
|
64
|
+
unless template.libs.empty?
|
65
|
+
lib_path = template.libs.join(::File::PATH_SEPARATOR)
|
66
|
+
ruby_args << "-I#{lib_path}"
|
67
|
+
end
|
68
|
+
ruby_args << "-w" if self[:warnings]
|
69
|
+
|
70
|
+
tests = self[:tests]
|
71
|
+
if tests.empty?
|
72
|
+
Array(template.files).each do |pattern|
|
73
|
+
tests.concat(::Dir.glob(pattern))
|
74
|
+
end
|
75
|
+
tests.uniq!
|
76
|
+
end
|
77
|
+
|
78
|
+
ruby(ruby_args, in_from: :controller, exit_on_nonzero_status: true) do |controller|
|
79
|
+
tests.each do |file|
|
80
|
+
controller.in.puts("load '#{file}'")
|
81
|
+
end
|
82
|
+
end
|
37
83
|
end
|
38
84
|
end
|
39
85
|
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# Copyright 2018 Daniel Azuma
|
2
|
+
#
|
3
|
+
# All rights reserved.
|
4
|
+
#
|
5
|
+
# Redistribution and use in source and binary forms, with or without
|
6
|
+
# modification, are permitted provided that the following conditions are met:
|
7
|
+
#
|
8
|
+
# * Redistributions of source code must retain the above copyright notice,
|
9
|
+
# this list of conditions and the following disclaimer.
|
10
|
+
# * Redistributions in binary form must reproduce the above copyright notice,
|
11
|
+
# this list of conditions and the following disclaimer in the documentation
|
12
|
+
# and/or other materials provided with the distribution.
|
13
|
+
# * Neither the name of the copyright holder, nor the names of any other
|
14
|
+
# contributors to this software, may be used to endorse or promote products
|
15
|
+
# derived from this software without specific prior written permission.
|
16
|
+
#
|
17
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
18
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
19
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
20
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
21
|
+
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
22
|
+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
23
|
+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
24
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
25
|
+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
26
|
+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
27
|
+
# POSSIBILITY OF SUCH DAMAGE.
|
28
|
+
;
|
29
|
+
|
30
|
+
module Toys::Templates
|
31
|
+
##
|
32
|
+
# A template for tools that run rubocop
|
33
|
+
#
|
34
|
+
class Rubocop
|
35
|
+
include ::Toys::Template
|
36
|
+
|
37
|
+
def initialize(opts = {})
|
38
|
+
@name = opts[:name] || "rubocop"
|
39
|
+
@fail_on_error = opts.include?(:fail_on_error) ? opts[:fail_on_error] : true
|
40
|
+
@options = opts[:options] || []
|
41
|
+
end
|
42
|
+
|
43
|
+
attr_accessor :name
|
44
|
+
attr_accessor :fail_on_error
|
45
|
+
attr_accessor :options
|
46
|
+
|
47
|
+
to_expand do |template|
|
48
|
+
name(template.name) do
|
49
|
+
short_desc "Run RuboCop"
|
50
|
+
|
51
|
+
use :exec
|
52
|
+
|
53
|
+
execute do
|
54
|
+
require "rubocop"
|
55
|
+
cli = ::RuboCop::CLI.new
|
56
|
+
logger.info "Running RuboCop..."
|
57
|
+
result = cli.run(template.options)
|
58
|
+
if result.nonzero?
|
59
|
+
logger.error "RuboCop failed!"
|
60
|
+
exit(1) if template.fail_on_error
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# Copyright 2018 Daniel Azuma
|
2
|
+
#
|
3
|
+
# All rights reserved.
|
4
|
+
#
|
5
|
+
# Redistribution and use in source and binary forms, with or without
|
6
|
+
# modification, are permitted provided that the following conditions are met:
|
7
|
+
#
|
8
|
+
# * Redistributions of source code must retain the above copyright notice,
|
9
|
+
# this list of conditions and the following disclaimer.
|
10
|
+
# * Redistributions in binary form must reproduce the above copyright notice,
|
11
|
+
# this list of conditions and the following disclaimer in the documentation
|
12
|
+
# and/or other materials provided with the distribution.
|
13
|
+
# * Neither the name of the copyright holder, nor the names of any other
|
14
|
+
# contributors to this software, may be used to endorse or promote products
|
15
|
+
# derived from this software without specific prior written permission.
|
16
|
+
#
|
17
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
18
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
19
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
20
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
21
|
+
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
22
|
+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
23
|
+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
24
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
25
|
+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
26
|
+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
27
|
+
# POSSIBILITY OF SUCH DAMAGE.
|
28
|
+
;
|
29
|
+
|
30
|
+
module Toys::Templates
|
31
|
+
##
|
32
|
+
# A template for tools that run yardoc
|
33
|
+
#
|
34
|
+
class Yardoc
|
35
|
+
include ::Toys::Template
|
36
|
+
|
37
|
+
def initialize(opts = {})
|
38
|
+
@name = opts[:name] || "yardoc"
|
39
|
+
@files = opts[:files] || []
|
40
|
+
@options = opts[:options] || []
|
41
|
+
@stats_options = opts[:stats_options] || []
|
42
|
+
end
|
43
|
+
|
44
|
+
attr_accessor :name
|
45
|
+
attr_accessor :files
|
46
|
+
attr_accessor :options
|
47
|
+
attr_accessor :stats_options
|
48
|
+
|
49
|
+
to_expand do |template|
|
50
|
+
name(template.name) do
|
51
|
+
short_desc "Run yardoc"
|
52
|
+
|
53
|
+
use :exec
|
54
|
+
|
55
|
+
execute do
|
56
|
+
require "yard"
|
57
|
+
files = []
|
58
|
+
patterns = Array(template.files)
|
59
|
+
patterns = ["lib/**/*.rb"] if patterns.empty?
|
60
|
+
patterns.each do |pattern|
|
61
|
+
files.concat(::Dir.glob(pattern))
|
62
|
+
end
|
63
|
+
files.uniq!
|
64
|
+
|
65
|
+
unless template.stats_options.empty?
|
66
|
+
template.options << "--no-stats"
|
67
|
+
template.stats_options << "--use-cache"
|
68
|
+
end
|
69
|
+
|
70
|
+
yardoc = ::YARD::CLI::Yardoc.new
|
71
|
+
yardoc.run(*(template.options + files))
|
72
|
+
unless template.stats_options.empty?
|
73
|
+
::YARD::CLI::Stats.run(*template.stats_options)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
data/lib/toys/tool.rb
CHANGED
@@ -1,14 +1,42 @@
|
|
1
|
-
|
1
|
+
# Copyright 2018 Daniel Azuma
|
2
|
+
#
|
3
|
+
# All rights reserved.
|
4
|
+
#
|
5
|
+
# Redistribution and use in source and binary forms, with or without
|
6
|
+
# modification, are permitted provided that the following conditions are met:
|
7
|
+
#
|
8
|
+
# * Redistributions of source code must retain the above copyright notice,
|
9
|
+
# this list of conditions and the following disclaimer.
|
10
|
+
# * Redistributions in binary form must reproduce the above copyright notice,
|
11
|
+
# this list of conditions and the following disclaimer in the documentation
|
12
|
+
# and/or other materials provided with the distribution.
|
13
|
+
# * Neither the name of the copyright holder, nor the names of any other
|
14
|
+
# contributors to this software, may be used to endorse or promote products
|
15
|
+
# derived from this software without specific prior written permission.
|
16
|
+
#
|
17
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
18
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
19
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
20
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
21
|
+
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
22
|
+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
23
|
+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
24
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
25
|
+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
26
|
+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
27
|
+
# POSSIBILITY OF SUCH DAMAGE.
|
28
|
+
;
|
29
|
+
|
2
30
|
require "optparse"
|
3
31
|
|
4
32
|
module Toys
|
33
|
+
##
|
34
|
+
# A tool definition
|
35
|
+
#
|
5
36
|
class Tool
|
6
|
-
|
7
|
-
|
8
|
-
@
|
9
|
-
@simple_name = name
|
10
|
-
@full_name = name ? [name] : []
|
11
|
-
@full_name = parent.full_name + @full_name if parent
|
37
|
+
def initialize(lookup, full_name)
|
38
|
+
@lookup = lookup
|
39
|
+
@full_name = full_name
|
12
40
|
|
13
41
|
@definition_path = nil
|
14
42
|
@cur_path = nil
|
@@ -23,22 +51,53 @@ module Toys
|
|
23
51
|
@required_args = []
|
24
52
|
@optional_args = []
|
25
53
|
@remaining_args = nil
|
54
|
+
|
26
55
|
@helpers = {}
|
27
56
|
@modules = []
|
28
57
|
@executor = nil
|
29
58
|
end
|
30
59
|
|
31
|
-
attr_reader :
|
60
|
+
attr_reader :lookup
|
32
61
|
attr_reader :full_name
|
33
|
-
|
34
|
-
|
35
|
-
|
62
|
+
attr_reader :switches
|
63
|
+
attr_reader :required_args
|
64
|
+
attr_reader :optional_args
|
65
|
+
attr_reader :remaining_args
|
66
|
+
attr_reader :default_data
|
67
|
+
attr_reader :modules
|
68
|
+
attr_reader :helpers
|
69
|
+
attr_reader :executor
|
70
|
+
attr_reader :alias_target
|
71
|
+
|
72
|
+
def simple_name
|
73
|
+
full_name.last
|
36
74
|
end
|
37
75
|
|
38
76
|
def display_name
|
39
77
|
full_name.join(" ")
|
40
78
|
end
|
41
79
|
|
80
|
+
def root?
|
81
|
+
full_name.empty?
|
82
|
+
end
|
83
|
+
|
84
|
+
def leaf?
|
85
|
+
@executor.is_a?(::Proc)
|
86
|
+
end
|
87
|
+
|
88
|
+
def alias?
|
89
|
+
!alias_target.nil?
|
90
|
+
end
|
91
|
+
|
92
|
+
def only_collection?
|
93
|
+
@executor == false
|
94
|
+
end
|
95
|
+
|
96
|
+
def parent
|
97
|
+
return nil if root?
|
98
|
+
@lookup.exact_tool(full_name.slice(0..-2))
|
99
|
+
end
|
100
|
+
|
42
101
|
def effective_short_desc
|
43
102
|
@short_desc || default_desc
|
44
103
|
end
|
@@ -47,28 +106,24 @@ module Toys
|
|
47
106
|
@long_desc || @short_desc || default_desc
|
48
107
|
end
|
49
108
|
|
50
|
-
def
|
109
|
+
def includes_description?
|
51
110
|
!@long_desc.nil? || !@short_desc.nil?
|
52
111
|
end
|
53
112
|
|
54
|
-
def
|
113
|
+
def includes_definition?
|
55
114
|
!@default_data.empty? || !@switches.empty? ||
|
56
115
|
!@required_args.empty? || !@optional_args.empty? ||
|
57
|
-
!@remaining_args.nil? ||
|
116
|
+
!@remaining_args.nil? || leaf? ||
|
58
117
|
!@helpers.empty? || !@modules.empty?
|
59
118
|
end
|
60
119
|
|
61
|
-
def only_collection?
|
62
|
-
@executor == false
|
63
|
-
end
|
64
|
-
|
65
120
|
def defining_from(path)
|
66
121
|
raise ToolDefinitionError, "Already being defined" if @cur_path
|
67
122
|
@cur_path = path
|
68
123
|
begin
|
69
124
|
yield
|
70
125
|
ensure
|
71
|
-
@definition_path = @cur_path if
|
126
|
+
@definition_path = @cur_path if includes_description? || includes_definition?
|
72
127
|
@cur_path = nil
|
73
128
|
end
|
74
129
|
end
|
@@ -83,24 +138,21 @@ module Toys
|
|
83
138
|
end
|
84
139
|
end
|
85
140
|
|
86
|
-
def
|
87
|
-
|
88
|
-
raise
|
141
|
+
def make_alias_of(target_word)
|
142
|
+
if root?
|
143
|
+
raise ToolDefinitionError, "Cannot make the root tool an alias"
|
89
144
|
end
|
90
145
|
if only_collection?
|
91
146
|
raise ToolDefinitionError, "Tool #{display_name.inspect} is already" \
|
92
147
|
" a collection and cannot be made an alias"
|
93
148
|
end
|
94
|
-
if
|
149
|
+
if includes_description? || includes_definition?
|
95
150
|
raise ToolDefinitionError, "Tool #{display_name.inspect} already has" \
|
96
151
|
" a definition and cannot be made an alias"
|
97
152
|
end
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
end
|
102
|
-
@parent.ensure_collection_only(full_name) if @parent
|
103
|
-
@alias_target = target_tool
|
153
|
+
parent.ensure_collection_only(full_name) unless root?
|
154
|
+
@alias_target = target_word
|
155
|
+
self
|
104
156
|
end
|
105
157
|
|
106
158
|
def short_desc=(str)
|
@@ -122,17 +174,20 @@ module Toys
|
|
122
174
|
@helpers[name.to_sym] = block
|
123
175
|
end
|
124
176
|
|
125
|
-
def
|
177
|
+
def use_module(mod)
|
126
178
|
check_definition_state(true)
|
127
179
|
case mod
|
128
|
-
when Module
|
180
|
+
when ::Module
|
129
181
|
@modules << mod
|
130
|
-
when Symbol
|
182
|
+
when ::Symbol
|
131
183
|
mod = mod.to_s
|
132
|
-
file_name =
|
184
|
+
file_name =
|
185
|
+
mod
|
186
|
+
.gsub(/([a-zA-Z])([A-Z])/) { |_m| "#{$1}_#{$2.downcase}" }
|
187
|
+
.downcase
|
133
188
|
require "toys/helpers/#{file_name}"
|
134
|
-
const_name = mod.gsub(/(^|_)([a-zA-Z0-9])/){ |
|
135
|
-
@modules <<
|
189
|
+
const_name = mod.gsub(/(^|_)([a-zA-Z0-9])/) { |_m| $2.upcase }
|
190
|
+
@modules << Helpers.const_get(const_name)
|
136
191
|
else
|
137
192
|
raise ToolDefinitionError, "Illegal helper module name: #{mod.inspect}"
|
138
193
|
end
|
@@ -141,28 +196,28 @@ module Toys
|
|
141
196
|
def add_switch(key, *switches, accept: nil, default: nil, doc: nil)
|
142
197
|
check_definition_state(true)
|
143
198
|
@default_data[key] = default
|
144
|
-
switches << "--#{canonical_switch(key)}=VALUE" if switches.empty?
|
199
|
+
switches << "--#{Tool.canonical_switch(key)}=VALUE" if switches.empty?
|
145
200
|
switches << accept unless accept.nil?
|
146
201
|
switches += Array(doc)
|
147
|
-
@switches <<
|
202
|
+
@switches << SwitchInfo.new(key, switches)
|
148
203
|
end
|
149
204
|
|
150
205
|
def add_required_arg(key, accept: nil, doc: nil)
|
151
206
|
check_definition_state(true)
|
152
207
|
@default_data[key] = nil
|
153
|
-
@required_args <<
|
208
|
+
@required_args << ArgInfo.new(key, accept, Array(doc))
|
154
209
|
end
|
155
210
|
|
156
211
|
def add_optional_arg(key, accept: nil, default: nil, doc: nil)
|
157
212
|
check_definition_state(true)
|
158
213
|
@default_data[key] = default
|
159
|
-
@optional_args <<
|
214
|
+
@optional_args << ArgInfo.new(key, accept, Array(doc))
|
160
215
|
end
|
161
216
|
|
162
217
|
def set_remaining_args(key, accept: nil, default: [], doc: nil)
|
163
218
|
check_definition_state(true)
|
164
219
|
@default_data[key] = default
|
165
|
-
@remaining_args =
|
220
|
+
@remaining_args = ArgInfo.new(key, accept, Array(doc))
|
166
221
|
end
|
167
222
|
|
168
223
|
def executor=(executor)
|
@@ -170,243 +225,373 @@ module Toys
|
|
170
225
|
@executor = executor
|
171
226
|
end
|
172
227
|
|
173
|
-
def execute(
|
174
|
-
|
175
|
-
execution_data = parse_args(args, context.binary_name)
|
176
|
-
context = create_child_context(context, args, execution_data)
|
177
|
-
if execution_data[:usage_error]
|
178
|
-
puts(execution_data[:usage_error])
|
179
|
-
puts("")
|
180
|
-
show_usage(context, execution_data[:optparse])
|
181
|
-
-1
|
182
|
-
elsif execution_data[:show_help]
|
183
|
-
show_usage(context, execution_data[:optparse],
|
184
|
-
recursive: execution_data[:recursive])
|
185
|
-
0
|
186
|
-
else
|
187
|
-
catch(:result) do
|
188
|
-
context.instance_eval(&@executor)
|
189
|
-
0
|
190
|
-
end
|
191
|
-
end
|
228
|
+
def execute(context_base, args)
|
229
|
+
Execution.new(self).execute(context_base, args)
|
192
230
|
end
|
193
231
|
|
194
232
|
protected
|
195
233
|
|
196
234
|
def ensure_collection_only(source_name)
|
197
|
-
if
|
235
|
+
if includes_definition?
|
198
236
|
raise ToolDefinitionError, "Cannot create tool #{source_name.inspect}" \
|
199
237
|
" because #{display_name.inspect} is already a tool."
|
200
238
|
end
|
201
|
-
|
239
|
+
unless @executor == false
|
202
240
|
@executor = false
|
203
|
-
|
241
|
+
parent.ensure_collection_only(source_name) unless root?
|
204
242
|
end
|
205
243
|
end
|
206
244
|
|
207
245
|
private
|
208
246
|
|
209
|
-
SPECIAL_FLAGS = ["-q", "--quiet", "-v", "--verbose", "-?", "-h", "--help"]
|
210
|
-
|
211
247
|
def default_desc
|
212
|
-
if
|
213
|
-
"(Alias of #{@alias_target.
|
214
|
-
elsif
|
248
|
+
if alias?
|
249
|
+
"(Alias of #{@alias_target.inspect})"
|
250
|
+
elsif leaf?
|
215
251
|
"(No description available)"
|
216
252
|
else
|
217
253
|
"(A collection of commands)"
|
218
254
|
end
|
219
255
|
end
|
220
256
|
|
221
|
-
def check_definition_state(execution_field=false)
|
222
|
-
if
|
257
|
+
def check_definition_state(execution_field = false)
|
258
|
+
if alias?
|
223
259
|
raise ToolDefinitionError, "Tool #{display_name.inspect} is an alias"
|
224
260
|
end
|
225
261
|
if @definition_path
|
226
262
|
in_clause = @cur_path ? "in #{@cur_path} " : ""
|
227
263
|
raise ToolDefinitionError,
|
228
|
-
|
229
|
-
|
264
|
+
"Cannot redefine tool #{display_name.inspect} #{in_clause}" \
|
265
|
+
"(already defined in #{@definition_path})"
|
230
266
|
end
|
231
267
|
if execution_field
|
232
|
-
if
|
268
|
+
if only_collection?
|
233
269
|
raise ToolDefinitionError,
|
234
|
-
|
235
|
-
|
270
|
+
"Cannot make tool #{display_name.inspect} executable because" \
|
271
|
+
" a descendant is already executable"
|
236
272
|
end
|
237
|
-
|
273
|
+
parent.ensure_collection_only(full_name) unless root?
|
238
274
|
end
|
239
275
|
end
|
240
276
|
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
banner << "[<options...>]" unless @switches.empty?
|
245
|
-
@required_args.each do |key, opts|
|
246
|
-
banner << "<#{canonical_switch(key)}>"
|
277
|
+
class << self
|
278
|
+
def canonical_switch(name)
|
279
|
+
name.to_s.downcase.tr("_", "-").gsub(/[^a-z0-9-]/, "")
|
247
280
|
end
|
248
|
-
|
249
|
-
|
281
|
+
end
|
282
|
+
|
283
|
+
##
|
284
|
+
# Representation of a formal switch
|
285
|
+
#
|
286
|
+
class SwitchInfo
|
287
|
+
def initialize(key, optparse_info)
|
288
|
+
@key = key
|
289
|
+
@optparse_info = optparse_info
|
290
|
+
end
|
291
|
+
|
292
|
+
attr_reader :key
|
293
|
+
attr_reader :optparse_info
|
294
|
+
end
|
295
|
+
|
296
|
+
##
|
297
|
+
# Representation of a formal argument
|
298
|
+
#
|
299
|
+
class ArgInfo
|
300
|
+
def initialize(key, accept, doc)
|
301
|
+
@key = key
|
302
|
+
@accept = accept
|
303
|
+
@doc = doc
|
250
304
|
end
|
251
|
-
|
252
|
-
|
305
|
+
|
306
|
+
attr_reader :key
|
307
|
+
attr_reader :accept
|
308
|
+
attr_reader :doc
|
309
|
+
|
310
|
+
def process_value(val)
|
311
|
+
return val unless accept
|
312
|
+
n = canonical_switch(key)
|
313
|
+
result = val
|
314
|
+
optparse = ::OptionParser.new
|
315
|
+
optparse.on("--#{n}=VALUE", accept) { |v| result = v }
|
316
|
+
optparse.parse(["--#{n}", val])
|
317
|
+
result
|
253
318
|
end
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
319
|
+
|
320
|
+
def canonical_name
|
321
|
+
Tool.canonical_switch(key)
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
##
|
326
|
+
# An internal class that manages execution of a tool
|
327
|
+
# @private
|
328
|
+
#
|
329
|
+
class Execution
|
330
|
+
def initialize(tool)
|
331
|
+
@tool = tool
|
332
|
+
end
|
333
|
+
|
334
|
+
def execute(context_base, args)
|
335
|
+
return execute_alias(context_base, args) if @tool.alias?
|
336
|
+
|
337
|
+
parsed_args = ParsedArgs.new(@tool, context_base.binary_name, args)
|
338
|
+
context = create_child_context(context_base, parsed_args, args)
|
339
|
+
|
340
|
+
if parsed_args.usage_error
|
341
|
+
puts(parsed_args.usage_error)
|
342
|
+
puts("")
|
343
|
+
show_usage(parsed_args.optparse)
|
344
|
+
-1
|
345
|
+
elsif parsed_args.show_help
|
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
|
267
353
|
end
|
268
354
|
end
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
355
|
+
|
356
|
+
private
|
357
|
+
|
358
|
+
def create_child_context(context_base, parsed_args, args)
|
359
|
+
context = context_base.create_context(@tool.full_name, args, parsed_args.data)
|
360
|
+
context.logger.level += parsed_args.delta_severity
|
361
|
+
@tool.modules.each do |mod|
|
362
|
+
context.extend(mod)
|
273
363
|
end
|
364
|
+
@tool.helpers.each do |name, block|
|
365
|
+
context.define_singleton_method(name, &block)
|
366
|
+
end
|
367
|
+
context
|
274
368
|
end
|
275
|
-
|
276
|
-
|
277
|
-
optparse.
|
278
|
-
|
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)
|
279
381
|
end
|
280
382
|
end
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
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
|
285
417
|
end
|
286
418
|
end
|
287
|
-
optparse
|
288
419
|
end
|
289
420
|
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
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)
|
465
|
+
rescue ::OptionParser::ParseError => e
|
466
|
+
@usage_error = e
|
467
|
+
end
|
468
|
+
|
469
|
+
def create_option_parser(tool, binary_name)
|
470
|
+
optparse = ::OptionParser.new
|
471
|
+
optparse.banner =
|
472
|
+
if tool.leaf?
|
473
|
+
leaf_banner(tool, binary_name)
|
474
|
+
else
|
475
|
+
collection_banner(tool, binary_name)
|
329
476
|
end
|
330
|
-
|
477
|
+
unless tool.effective_long_desc.empty?
|
478
|
+
optparse.separator("")
|
479
|
+
optparse.separator(tool.effective_long_desc)
|
331
480
|
end
|
332
|
-
|
333
|
-
|
334
|
-
|
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
|
335
522
|
end
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
error = OptionParser::ParseError.new(*(full_name + args))
|
344
|
-
error.reason = "Tool not found"
|
345
|
-
raise error
|
346
|
-
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)
|
528
|
+
optparse.on(*switch.optparse_info) do |val|
|
529
|
+
@data[switch.key] = val
|
347
530
|
end
|
348
|
-
key = @remaining_args[0]
|
349
|
-
accept = @remaining_args[1]
|
350
|
-
optdata[key] = remaining.map{ |arg| process_value(key, arg, accept) }
|
351
531
|
end
|
352
|
-
rescue OptionParser::ParseError => e
|
353
|
-
execution_data[:usage_error] = e
|
354
532
|
end
|
355
|
-
execution_data
|
356
|
-
end
|
357
533
|
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
534
|
+
def leaf_verbose_switch(optparse, found_special_flags)
|
535
|
+
flags = ["-v", "--verbose"] - found_special_flags
|
536
|
+
return if flags.empty?
|
537
|
+
optparse.on(*(flags + ["Increase verbosity"])) do
|
538
|
+
@delta_severity -= 1
|
539
|
+
end
|
364
540
|
end
|
365
|
-
|
366
|
-
|
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
|
367
548
|
end
|
368
|
-
context
|
369
|
-
end
|
370
549
|
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
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?
|
561
|
+
reason = "No value given for required argument named <#{arg_info.canonical_name}>"
|
562
|
+
raise create_parse_error(args, reason)
|
384
563
|
end
|
564
|
+
@data[arg_info.key] = arg_info.process_value(remaining.shift)
|
385
565
|
end
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
puts(" #{tool_name} #{desc}")
|
566
|
+
remaining
|
567
|
+
end
|
568
|
+
|
569
|
+
def parse_optional_args(remaining, tool)
|
570
|
+
tool.optional_args.each do |arg_info|
|
571
|
+
break if remaining.empty?
|
572
|
+
@data[arg_info.key] = arg_info.process_value(remaining.shift)
|
394
573
|
end
|
574
|
+
remaining
|
395
575
|
end
|
396
|
-
end
|
397
576
|
|
398
|
-
|
399
|
-
|
400
|
-
|
577
|
+
def parse_remaining_args(remaining, tool, args)
|
578
|
+
return if remaining.empty?
|
579
|
+
unless tool.remaining_args
|
580
|
+
if tool.leaf?
|
581
|
+
raise create_parse_error(remaining, "Extra arguments provided")
|
582
|
+
else
|
583
|
+
raise create_parse_error(tool.full_name + args, "Tool not found")
|
584
|
+
end
|
585
|
+
end
|
586
|
+
@data[tool.remaining_args.key] =
|
587
|
+
remaining.map { |arg| tool.remaining_args.process_value(arg) }
|
588
|
+
end
|
401
589
|
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
optparse.on("--#{n}=VALUE", accept){ |v| result = v }
|
408
|
-
optparse.parse(["--#{n}", val])
|
409
|
-
result
|
590
|
+
def create_parse_error(path, reason)
|
591
|
+
OptionParser::ParseError.new(*path).tap do |e|
|
592
|
+
e.reason = reason
|
593
|
+
end
|
594
|
+
end
|
410
595
|
end
|
411
596
|
end
|
412
597
|
end
|