toys-core 0.7.0 → 0.8.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 +4 -4
- data/CHANGELOG.md +98 -0
- data/LICENSE.md +16 -24
- data/README.md +307 -59
- data/docs/guide.md +44 -4
- data/lib/toys-core.rb +58 -49
- data/lib/toys/acceptor.rb +672 -0
- data/lib/toys/alias.rb +106 -0
- data/lib/toys/arg_parser.rb +624 -0
- data/lib/toys/cli.rb +422 -181
- data/lib/toys/compat.rb +83 -0
- data/lib/toys/completion.rb +442 -0
- data/lib/toys/context.rb +354 -0
- data/lib/toys/core_version.rb +18 -26
- data/lib/toys/dsl/flag.rb +213 -56
- data/lib/toys/dsl/flag_group.rb +237 -51
- data/lib/toys/dsl/positional_arg.rb +210 -0
- data/lib/toys/dsl/tool.rb +968 -317
- data/lib/toys/errors.rb +46 -28
- data/lib/toys/flag.rb +821 -0
- data/lib/toys/flag_group.rb +282 -0
- data/lib/toys/input_file.rb +18 -26
- data/lib/toys/loader.rb +110 -100
- data/lib/toys/middleware.rb +24 -31
- data/lib/toys/mixin.rb +90 -59
- data/lib/toys/module_lookup.rb +125 -0
- data/lib/toys/positional_arg.rb +184 -0
- data/lib/toys/source_info.rb +192 -0
- data/lib/toys/standard_middleware/add_verbosity_flags.rb +38 -43
- data/lib/toys/standard_middleware/handle_usage_errors.rb +39 -40
- data/lib/toys/standard_middleware/set_default_descriptions.rb +111 -89
- data/lib/toys/standard_middleware/show_help.rb +130 -113
- data/lib/toys/standard_middleware/show_root_version.rb +29 -35
- data/lib/toys/standard_mixins/exec.rb +116 -78
- data/lib/toys/standard_mixins/fileutils.rb +16 -24
- data/lib/toys/standard_mixins/gems.rb +29 -30
- data/lib/toys/standard_mixins/highline.rb +34 -41
- data/lib/toys/standard_mixins/terminal.rb +72 -26
- data/lib/toys/template.rb +51 -35
- data/lib/toys/tool.rb +1161 -206
- data/lib/toys/utils/completion_engine.rb +171 -0
- data/lib/toys/utils/exec.rb +279 -182
- data/lib/toys/utils/gems.rb +58 -49
- data/lib/toys/utils/help_text.rb +117 -111
- data/lib/toys/utils/terminal.rb +69 -62
- data/lib/toys/wrappable_string.rb +162 -0
- metadata +24 -22
- data/lib/toys/definition/acceptor.rb +0 -191
- data/lib/toys/definition/alias.rb +0 -112
- data/lib/toys/definition/arg.rb +0 -140
- data/lib/toys/definition/flag.rb +0 -370
- data/lib/toys/definition/flag_group.rb +0 -205
- data/lib/toys/definition/source_info.rb +0 -190
- data/lib/toys/definition/tool.rb +0 -842
- data/lib/toys/dsl/arg.rb +0 -132
- data/lib/toys/runner.rb +0 -188
- data/lib/toys/standard_middleware.rb +0 -47
- data/lib/toys/utils/module_lookup.rb +0 -135
- data/lib/toys/utils/wrappable_string.rb +0 -165
data/lib/toys/dsl/arg.rb
DELETED
@@ -1,132 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# Copyright 2018 Daniel Azuma
|
4
|
-
#
|
5
|
-
# All rights reserved.
|
6
|
-
#
|
7
|
-
# Redistribution and use in source and binary forms, with or without
|
8
|
-
# modification, are permitted provided that the following conditions are met:
|
9
|
-
#
|
10
|
-
# * Redistributions of source code must retain the above copyright notice,
|
11
|
-
# this list of conditions and the following disclaimer.
|
12
|
-
# * Redistributions in binary form must reproduce the above copyright notice,
|
13
|
-
# this list of conditions and the following disclaimer in the documentation
|
14
|
-
# and/or other materials provided with the distribution.
|
15
|
-
# * Neither the name of the copyright holder, nor the names of any other
|
16
|
-
# contributors to this software, may be used to endorse or promote products
|
17
|
-
# derived from this software without specific prior written permission.
|
18
|
-
#
|
19
|
-
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
20
|
-
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
21
|
-
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
22
|
-
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
23
|
-
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
24
|
-
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
25
|
-
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
26
|
-
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
27
|
-
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
28
|
-
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
29
|
-
# POSSIBILITY OF SUCH DAMAGE.
|
30
|
-
;
|
31
|
-
|
32
|
-
module Toys
|
33
|
-
module DSL
|
34
|
-
##
|
35
|
-
# DSL for an arg definition block. Lets you set arg attributes in a block
|
36
|
-
# instead of a long series of keyword arguments.
|
37
|
-
#
|
38
|
-
# These directives are available inside a block passed to
|
39
|
-
# {Toys::DSL::Tool#required_arg}, {Toys::DSL::Tool#optional_arg}, or
|
40
|
-
# {Toys::DSL::Tool#remaining_args}.
|
41
|
-
#
|
42
|
-
class Arg
|
43
|
-
## @private
|
44
|
-
def initialize(accept, default, display_name, desc, long_desc)
|
45
|
-
@accept = accept
|
46
|
-
@default = default
|
47
|
-
@display_name = display_name
|
48
|
-
@desc = desc
|
49
|
-
@long_desc = long_desc || []
|
50
|
-
end
|
51
|
-
|
52
|
-
##
|
53
|
-
# Set the OptionParser acceptor.
|
54
|
-
#
|
55
|
-
# @param [Object] accept
|
56
|
-
# @return [Toys::DSL::Tool] self, for chaining.
|
57
|
-
#
|
58
|
-
def accept(accept)
|
59
|
-
@accept = accept
|
60
|
-
self
|
61
|
-
end
|
62
|
-
|
63
|
-
##
|
64
|
-
# Set the default value.
|
65
|
-
#
|
66
|
-
# @param [Object] default
|
67
|
-
# @return [Toys::DSL::Tool] self, for chaining.
|
68
|
-
#
|
69
|
-
def default(default)
|
70
|
-
@default = default
|
71
|
-
self
|
72
|
-
end
|
73
|
-
|
74
|
-
##
|
75
|
-
# Set the name of this arg as it appears in help screens.
|
76
|
-
#
|
77
|
-
# @param [String] display_name
|
78
|
-
# @return [Toys::DSL::Tool] self, for chaining.
|
79
|
-
#
|
80
|
-
def display_name(display_name)
|
81
|
-
@display_name = display_name
|
82
|
-
self
|
83
|
-
end
|
84
|
-
|
85
|
-
##
|
86
|
-
# Set the short description. See {Toys::DSL::Tool#desc} for the allowed
|
87
|
-
# formats.
|
88
|
-
#
|
89
|
-
# @param [String,Array<String>,Toys::Utils::WrappableString] desc
|
90
|
-
# @return [Toys::DSL::Tool] self, for chaining.
|
91
|
-
#
|
92
|
-
def desc(desc)
|
93
|
-
@desc = desc
|
94
|
-
self
|
95
|
-
end
|
96
|
-
|
97
|
-
##
|
98
|
-
# Adds to the long description. This may be called multiple times, and
|
99
|
-
# the results are cumulative. See {Toys::DSL::Tool#long_desc} for the
|
100
|
-
# allowed formats.
|
101
|
-
#
|
102
|
-
# @param [String,Array<String>,Toys::Utils::WrappableString...] long_desc
|
103
|
-
# @return [Toys::DSL::Tool] self, for chaining.
|
104
|
-
#
|
105
|
-
def long_desc(*long_desc)
|
106
|
-
@long_desc += long_desc
|
107
|
-
self
|
108
|
-
end
|
109
|
-
|
110
|
-
## @private
|
111
|
-
def _add_required_to(tool, key)
|
112
|
-
tool.add_required_arg(key,
|
113
|
-
accept: @accept, display_name: @display_name,
|
114
|
-
desc: @desc, long_desc: @long_desc)
|
115
|
-
end
|
116
|
-
|
117
|
-
## @private
|
118
|
-
def _add_optional_to(tool, key)
|
119
|
-
tool.add_optional_arg(key,
|
120
|
-
accept: @accept, default: @default, display_name: @display_name,
|
121
|
-
desc: @desc, long_desc: @long_desc)
|
122
|
-
end
|
123
|
-
|
124
|
-
## @private
|
125
|
-
def _set_remaining_on(tool, key)
|
126
|
-
tool.set_remaining_args(key,
|
127
|
-
accept: @accept, default: @default, display_name: @display_name,
|
128
|
-
desc: @desc, long_desc: @long_desc)
|
129
|
-
end
|
130
|
-
end
|
131
|
-
end
|
132
|
-
end
|
data/lib/toys/runner.rb
DELETED
@@ -1,188 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# Copyright 2018 Daniel Azuma
|
4
|
-
#
|
5
|
-
# All rights reserved.
|
6
|
-
#
|
7
|
-
# Redistribution and use in source and binary forms, with or without
|
8
|
-
# modification, are permitted provided that the following conditions are met:
|
9
|
-
#
|
10
|
-
# * Redistributions of source code must retain the above copyright notice,
|
11
|
-
# this list of conditions and the following disclaimer.
|
12
|
-
# * Redistributions in binary form must reproduce the above copyright notice,
|
13
|
-
# this list of conditions and the following disclaimer in the documentation
|
14
|
-
# and/or other materials provided with the distribution.
|
15
|
-
# * Neither the name of the copyright holder, nor the names of any other
|
16
|
-
# contributors to this software, may be used to endorse or promote products
|
17
|
-
# derived from this software without specific prior written permission.
|
18
|
-
#
|
19
|
-
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
20
|
-
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
21
|
-
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
22
|
-
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
23
|
-
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
24
|
-
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
25
|
-
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
26
|
-
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
27
|
-
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
28
|
-
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
29
|
-
# POSSIBILITY OF SUCH DAMAGE.
|
30
|
-
;
|
31
|
-
|
32
|
-
require "optparse"
|
33
|
-
|
34
|
-
module Toys
|
35
|
-
##
|
36
|
-
# An internal class that orchestrates execution of a tool.
|
37
|
-
#
|
38
|
-
# Generaly, you should not need to use this class directly. Instead, run a
|
39
|
-
# tool using {Toys::CLI#run}.
|
40
|
-
#
|
41
|
-
class Runner
|
42
|
-
##
|
43
|
-
# Create a runner for a particular tool in a particular CLI.
|
44
|
-
#
|
45
|
-
# @param [Toys::CLI] cli The CLI that is running the tool. This will
|
46
|
-
# provide needed context information.
|
47
|
-
# @param [Toys::Definition::Tool] tool_definition The tool to run.
|
48
|
-
#
|
49
|
-
def initialize(cli, tool_definition)
|
50
|
-
@cli = cli
|
51
|
-
@tool_definition = tool_definition
|
52
|
-
end
|
53
|
-
|
54
|
-
##
|
55
|
-
# Run the tool, provided given arguments.
|
56
|
-
#
|
57
|
-
# @param [Array<String>] args Command line arguments passed to the tool.
|
58
|
-
# @param [Integer] verbosity Initial verbosity. Default is 0.
|
59
|
-
#
|
60
|
-
# @return [Integer] The resulting status code
|
61
|
-
#
|
62
|
-
def run(args, verbosity: 0)
|
63
|
-
data = create_data(args, verbosity)
|
64
|
-
parse_args(args, data) unless @tool_definition.argument_parsing_disabled?
|
65
|
-
tool = @tool_definition.tool_class.new(@cli, data)
|
66
|
-
@tool_definition.run_initializers(tool)
|
67
|
-
|
68
|
-
original_level = @cli.logger.level
|
69
|
-
@cli.logger.level = @cli.base_level - data[Tool::Keys::VERBOSITY]
|
70
|
-
begin
|
71
|
-
perform_execution(tool)
|
72
|
-
ensure
|
73
|
-
@cli.logger.level = original_level
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
private
|
78
|
-
|
79
|
-
def create_data(args, base_verbosity)
|
80
|
-
data = @tool_definition.default_data.dup
|
81
|
-
data[Tool::Keys::TOOL_DEFINITION] = @tool_definition
|
82
|
-
data[Tool::Keys::TOOL_SOURCE] = @tool_definition.source_info
|
83
|
-
data[Tool::Keys::TOOL_NAME] = @tool_definition.full_name
|
84
|
-
data[Tool::Keys::VERBOSITY] = base_verbosity
|
85
|
-
data[Tool::Keys::ARGS] = args
|
86
|
-
data[Tool::Keys::USAGE_ERROR] = nil
|
87
|
-
data
|
88
|
-
end
|
89
|
-
|
90
|
-
def parse_args(args, data)
|
91
|
-
optparse, seen = create_option_parser(data)
|
92
|
-
remaining = optparse.parse(args)
|
93
|
-
validate_flags(args, seen)
|
94
|
-
remaining = parse_required_args(remaining, args, data)
|
95
|
-
remaining = parse_optional_args(remaining, data)
|
96
|
-
parse_remaining_args(remaining, args, data)
|
97
|
-
rescue ::OptionParser::ParseError => e
|
98
|
-
data[Tool::Keys::USAGE_ERROR] = e.message
|
99
|
-
end
|
100
|
-
|
101
|
-
def create_option_parser(data)
|
102
|
-
seen = []
|
103
|
-
optparse = ::OptionParser.new
|
104
|
-
# The following clears out the Officious (hidden default flags).
|
105
|
-
optparse.remove
|
106
|
-
optparse.remove
|
107
|
-
optparse.new
|
108
|
-
optparse.new
|
109
|
-
@tool_definition.flag_definitions.each do |flag|
|
110
|
-
optparse.on(*flag.optparser_info) do |val|
|
111
|
-
seen << flag.key
|
112
|
-
data[flag.key] = flag.handler.call(val, data[flag.key])
|
113
|
-
end
|
114
|
-
end
|
115
|
-
@tool_definition.custom_acceptors do |accept|
|
116
|
-
optparse.accept(accept)
|
117
|
-
end
|
118
|
-
[optparse, seen]
|
119
|
-
end
|
120
|
-
|
121
|
-
def validate_flags(args, seen)
|
122
|
-
@tool_definition.flag_groups.each do |group|
|
123
|
-
error = group.validation_error(seen)
|
124
|
-
raise create_parse_error(args, error) if error
|
125
|
-
end
|
126
|
-
end
|
127
|
-
|
128
|
-
def parse_required_args(remaining, args, data)
|
129
|
-
@tool_definition.required_arg_definitions.each do |arg_info|
|
130
|
-
if remaining.empty?
|
131
|
-
reason = "No value given for required argument #{arg_info.display_name}"
|
132
|
-
raise create_parse_error(args, reason)
|
133
|
-
end
|
134
|
-
data[arg_info.key] = arg_info.process_value(remaining.shift)
|
135
|
-
end
|
136
|
-
remaining
|
137
|
-
end
|
138
|
-
|
139
|
-
def parse_optional_args(remaining, data)
|
140
|
-
@tool_definition.optional_arg_definitions.each do |arg_info|
|
141
|
-
break if remaining.empty?
|
142
|
-
data[arg_info.key] = arg_info.process_value(remaining.shift)
|
143
|
-
end
|
144
|
-
remaining
|
145
|
-
end
|
146
|
-
|
147
|
-
def parse_remaining_args(remaining, args, data)
|
148
|
-
return if remaining.empty?
|
149
|
-
unless @tool_definition.remaining_args_definition
|
150
|
-
if @tool_definition.runnable?
|
151
|
-
raise create_parse_error(remaining, "Extra arguments provided")
|
152
|
-
else
|
153
|
-
raise create_parse_error(@tool_definition.full_name + args, "Tool not found")
|
154
|
-
end
|
155
|
-
end
|
156
|
-
data[@tool_definition.remaining_args_definition.key] =
|
157
|
-
remaining.map { |arg| @tool_definition.remaining_args_definition.process_value(arg) }
|
158
|
-
end
|
159
|
-
|
160
|
-
def create_parse_error(path, reason)
|
161
|
-
::OptionParser::ParseError.new(*path).tap do |e|
|
162
|
-
e.reason = reason
|
163
|
-
end
|
164
|
-
end
|
165
|
-
|
166
|
-
def perform_execution(tool)
|
167
|
-
executor = proc do
|
168
|
-
if @tool_definition.runnable?
|
169
|
-
tool.run
|
170
|
-
else
|
171
|
-
@cli.logger.fatal("No implementation for tool #{@tool_definition.display_name.inspect}")
|
172
|
-
tool.exit(-1)
|
173
|
-
end
|
174
|
-
end
|
175
|
-
@tool_definition.middleware_stack.reverse_each do |middleware|
|
176
|
-
executor = make_executor(middleware, tool, executor)
|
177
|
-
end
|
178
|
-
catch(:result) do
|
179
|
-
executor.call
|
180
|
-
0
|
181
|
-
end
|
182
|
-
end
|
183
|
-
|
184
|
-
def make_executor(middleware, tool, next_executor)
|
185
|
-
proc { middleware.run(tool, &next_executor) }
|
186
|
-
end
|
187
|
-
end
|
188
|
-
end
|
@@ -1,47 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# Copyright 2018 Daniel Azuma
|
4
|
-
#
|
5
|
-
# All rights reserved.
|
6
|
-
#
|
7
|
-
# Redistribution and use in source and binary forms, with or without
|
8
|
-
# modification, are permitted provided that the following conditions are met:
|
9
|
-
#
|
10
|
-
# * Redistributions of source code must retain the above copyright notice,
|
11
|
-
# this list of conditions and the following disclaimer.
|
12
|
-
# * Redistributions in binary form must reproduce the above copyright notice,
|
13
|
-
# this list of conditions and the following disclaimer in the documentation
|
14
|
-
# and/or other materials provided with the distribution.
|
15
|
-
# * Neither the name of the copyright holder, nor the names of any other
|
16
|
-
# contributors to this software, may be used to endorse or promote products
|
17
|
-
# derived from this software without specific prior written permission.
|
18
|
-
#
|
19
|
-
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
20
|
-
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
21
|
-
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
22
|
-
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
23
|
-
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
24
|
-
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
25
|
-
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
26
|
-
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
27
|
-
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
28
|
-
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
29
|
-
# POSSIBILITY OF SUCH DAMAGE.
|
30
|
-
;
|
31
|
-
|
32
|
-
module Toys
|
33
|
-
##
|
34
|
-
# Namespace for standard middleware classes.
|
35
|
-
#
|
36
|
-
module StandardMiddleware
|
37
|
-
## @private
|
38
|
-
COMMON_FLAG_GROUP = :__common
|
39
|
-
|
40
|
-
## @private
|
41
|
-
def self.append_common_flag_group(tool)
|
42
|
-
tool.add_flag_group(type: :optional, name: COMMON_FLAG_GROUP,
|
43
|
-
desc: "Common Flags", report_collisions: false)
|
44
|
-
COMMON_FLAG_GROUP
|
45
|
-
end
|
46
|
-
end
|
47
|
-
end
|
@@ -1,135 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# Copyright 2018 Daniel Azuma
|
4
|
-
#
|
5
|
-
# All rights reserved.
|
6
|
-
#
|
7
|
-
# Redistribution and use in source and binary forms, with or without
|
8
|
-
# modification, are permitted provided that the following conditions are met:
|
9
|
-
#
|
10
|
-
# * Redistributions of source code must retain the above copyright notice,
|
11
|
-
# this list of conditions and the following disclaimer.
|
12
|
-
# * Redistributions in binary form must reproduce the above copyright notice,
|
13
|
-
# this list of conditions and the following disclaimer in the documentation
|
14
|
-
# and/or other materials provided with the distribution.
|
15
|
-
# * Neither the name of the copyright holder, nor the names of any other
|
16
|
-
# contributors to this software, may be used to endorse or promote products
|
17
|
-
# derived from this software without specific prior written permission.
|
18
|
-
#
|
19
|
-
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
20
|
-
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
21
|
-
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
22
|
-
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
23
|
-
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
24
|
-
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
25
|
-
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
26
|
-
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
27
|
-
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
28
|
-
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
29
|
-
# POSSIBILITY OF SUCH DAMAGE.
|
30
|
-
;
|
31
|
-
|
32
|
-
module Toys
|
33
|
-
module Utils
|
34
|
-
##
|
35
|
-
# A helper module that provides methods to do module lookups. This is
|
36
|
-
# used to obtain named helpers, middleware, and templates from the
|
37
|
-
# respective modules.
|
38
|
-
#
|
39
|
-
class ModuleLookup
|
40
|
-
class << self
|
41
|
-
##
|
42
|
-
# Convert the given string to a path element. Specifically, converts
|
43
|
-
# to `lower_snake_case`.
|
44
|
-
#
|
45
|
-
# @param [String,Symbol] str String to convert.
|
46
|
-
# @return [String] Converted string
|
47
|
-
#
|
48
|
-
def to_path_name(str)
|
49
|
-
str = str.to_s.sub(/^_/, "").sub(/_$/, "").gsub(/_+/, "_")
|
50
|
-
while str.sub!(/([^_])([A-Z])/, "\\1_\\2") do end
|
51
|
-
str.downcase
|
52
|
-
end
|
53
|
-
|
54
|
-
##
|
55
|
-
# Convert the given string to a module name. Specifically, converts
|
56
|
-
# to `UpperCamelCase`, and then to a symbol.
|
57
|
-
#
|
58
|
-
# @param [String,Symbol] str String to convert.
|
59
|
-
# @return [Symbol] Converted name
|
60
|
-
#
|
61
|
-
def to_module_name(str)
|
62
|
-
str = str.to_s.sub(/^_/, "").sub(/_$/, "").gsub(/_+/, "_")
|
63
|
-
str.to_s.gsub(/(^|_)([a-zA-Z])/) { |_m| $2.upcase }.to_sym
|
64
|
-
end
|
65
|
-
|
66
|
-
##
|
67
|
-
# Given a require path, return the module expected to be defined.
|
68
|
-
#
|
69
|
-
# @param [String] path File path, delimited by forward slash
|
70
|
-
# @return [Module] The module loaded from that path
|
71
|
-
#
|
72
|
-
def path_to_module(path)
|
73
|
-
path.split("/").reduce(::Object) do |running_mod, seg|
|
74
|
-
mod_name = to_module_name(seg)
|
75
|
-
unless running_mod.constants.include?(mod_name)
|
76
|
-
raise ::NameError, "Module #{running_mod.name}::#{mod_name} not found"
|
77
|
-
end
|
78
|
-
running_mod.const_get(mod_name)
|
79
|
-
end
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
##
|
84
|
-
# Create an empty ModuleLookup
|
85
|
-
#
|
86
|
-
def initialize
|
87
|
-
@paths = []
|
88
|
-
end
|
89
|
-
|
90
|
-
##
|
91
|
-
# Add a lookup path for modules.
|
92
|
-
#
|
93
|
-
# @param [String] path_base The base require path
|
94
|
-
# @param [Module] module_base The base module, or `nil` (the default) to
|
95
|
-
# infer a default from the path base.
|
96
|
-
# @param [Boolean] high_priority If true, add to the head of the lookup
|
97
|
-
# path, otherwise add to the end.
|
98
|
-
#
|
99
|
-
def add_path(path_base, module_base: nil, high_priority: false)
|
100
|
-
module_base ||= ModuleLookup.path_to_module(path_base)
|
101
|
-
if high_priority
|
102
|
-
@paths.unshift([path_base, module_base])
|
103
|
-
else
|
104
|
-
@paths << [path_base, module_base]
|
105
|
-
end
|
106
|
-
self
|
107
|
-
end
|
108
|
-
|
109
|
-
##
|
110
|
-
# Obtain a named module. Returns `nil` if the name is not present.
|
111
|
-
#
|
112
|
-
# @param [String,Symbol] name The name of the module to return.
|
113
|
-
#
|
114
|
-
# @return [Module] The specified module
|
115
|
-
#
|
116
|
-
def lookup(name)
|
117
|
-
@paths.each do |path_base, module_base|
|
118
|
-
path = "#{path_base}/#{ModuleLookup.to_path_name(name)}"
|
119
|
-
begin
|
120
|
-
require path
|
121
|
-
rescue ::LoadError
|
122
|
-
next
|
123
|
-
end
|
124
|
-
mod_name = ModuleLookup.to_module_name(name)
|
125
|
-
unless module_base.constants.include?(mod_name)
|
126
|
-
raise ::NameError,
|
127
|
-
"File #{path.inspect} did not define #{module_base.name}::#{mod_name}"
|
128
|
-
end
|
129
|
-
return module_base.const_get(mod_name)
|
130
|
-
end
|
131
|
-
nil
|
132
|
-
end
|
133
|
-
end
|
134
|
-
end
|
135
|
-
end
|