toys 0.2.1 → 0.2.2
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/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
|