toys-core 0.3.6 → 0.3.7

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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +13 -0
  3. data/lib/toys-core.rb +20 -5
  4. data/lib/toys/cli.rb +39 -32
  5. data/lib/toys/core_version.rb +1 -1
  6. data/lib/toys/{tool → definition}/acceptor.rb +21 -15
  7. data/lib/toys/{utils/line_output.rb → definition/alias.rb} +47 -59
  8. data/lib/toys/{tool/arg_definition.rb → definition/arg.rb} +17 -7
  9. data/lib/toys/{tool/flag_definition.rb → definition/flag.rb} +19 -9
  10. data/lib/toys/definition/tool.rb +574 -0
  11. data/lib/toys/dsl/arg.rb +118 -0
  12. data/lib/toys/dsl/flag.rb +132 -0
  13. data/lib/toys/dsl/tool.rb +521 -0
  14. data/lib/toys/errors.rb +2 -2
  15. data/lib/toys/helpers.rb +3 -3
  16. data/lib/toys/helpers/exec.rb +31 -25
  17. data/lib/toys/helpers/fileutils.rb +8 -2
  18. data/lib/toys/helpers/highline.rb +8 -1
  19. data/lib/toys/{alias.rb → helpers/terminal.rb} +44 -53
  20. data/lib/toys/input_file.rb +61 -0
  21. data/lib/toys/loader.rb +87 -77
  22. data/lib/toys/middleware.rb +3 -3
  23. data/lib/toys/middleware/add_verbosity_flags.rb +22 -20
  24. data/lib/toys/middleware/base.rb +53 -5
  25. data/lib/toys/middleware/handle_usage_errors.rb +9 -12
  26. data/lib/toys/middleware/set_default_descriptions.rb +6 -7
  27. data/lib/toys/middleware/show_help.rb +71 -67
  28. data/lib/toys/middleware/show_root_version.rb +9 -9
  29. data/lib/toys/runner.rb +157 -0
  30. data/lib/toys/template.rb +4 -3
  31. data/lib/toys/templates.rb +2 -2
  32. data/lib/toys/templates/clean.rb +2 -2
  33. data/lib/toys/templates/gem_build.rb +5 -5
  34. data/lib/toys/templates/minitest.rb +2 -2
  35. data/lib/toys/templates/rubocop.rb +2 -2
  36. data/lib/toys/templates/yardoc.rb +2 -2
  37. data/lib/toys/tool.rb +168 -625
  38. data/lib/toys/utils/exec.rb +19 -18
  39. data/lib/toys/utils/gems.rb +140 -0
  40. data/lib/toys/utils/help_text.rb +25 -20
  41. data/lib/toys/utils/terminal.rb +412 -0
  42. data/lib/toys/utils/wrappable_string.rb +3 -1
  43. metadata +15 -24
  44. data/lib/toys/config_dsl.rb +0 -699
  45. data/lib/toys/context.rb +0 -290
  46. data/lib/toys/helpers/spinner.rb +0 -142
data/lib/toys/errors.rb CHANGED
@@ -79,7 +79,7 @@ module Toys
79
79
  e = ContextualError.new(e, banner, opts)
80
80
  end
81
81
  raise e
82
- rescue ::StandardError => e
82
+ rescue ::ScriptError, ::StandardError => e
83
83
  e = ContextualError.new(e, banner)
84
84
  add_fields_if_missing(e, opts)
85
85
  add_config_path_if_missing(e, path)
@@ -92,7 +92,7 @@ module Toys
92
92
  rescue ContextualError => e
93
93
  add_fields_if_missing(e, opts)
94
94
  raise e
95
- rescue ::StandardError => e
95
+ rescue ::ScriptError, ::StandardError => e
96
96
  raise ContextualError.new(e, banner, opts)
97
97
  end
98
98
 
data/lib/toys/helpers.rb CHANGED
@@ -45,10 +45,10 @@ module Toys
45
45
  # * `:spinner` : Displays a spinner on the terminal.
46
46
  #
47
47
  # @param [String,Symbol] name Name of the helper module to return
48
- # @return [Module,nil] The module, or `nil` if not found
48
+ # @return [Module,nil] The module.
49
49
  #
50
- def self.lookup(name)
51
- Utils::ModuleLookup.lookup(:helpers, name)
50
+ def self.lookup!(name)
51
+ Utils::ModuleLookup.lookup!(:helpers, name)
52
52
  end
53
53
  end
54
54
  end
@@ -37,24 +37,15 @@ module Toys
37
37
  # in a string. Also provides an interface for controlling a spawned
38
38
  # process's streams.
39
39
  #
40
+ # You may make these methods available to your tool by including the
41
+ # following directive in your tool configuration:
42
+ #
43
+ # include :exec
44
+ #
40
45
  # This is a frontend for {Toys::Utils::Exec}. More information is
41
46
  # available in that class's documentation.
42
47
  #
43
48
  module Exec
44
- ## @private
45
- def self.extended(context)
46
- context[Exec] = Utils::Exec.new do |k|
47
- case k
48
- when :logger
49
- context[Context::LOGGER]
50
- when :nonzero_status_handler
51
- if context[Context::EXIT_ON_NONZERO_STATUS]
52
- proc { |s| context.exit(s.exitstatus) }
53
- end
54
- end
55
- end
56
- end
57
-
58
49
  ##
59
50
  # Set default configuration keys.
60
51
  #
@@ -62,7 +53,7 @@ module Toys
62
53
  # configuration options in the {Toys::Utils::Exec} docs.
63
54
  #
64
55
  def configure_exec(opts = {})
65
- self[Exec].configure_defaults(opts)
56
+ Exec._exec(self).configure_defaults(Exec._setup_exec_opts(opts, self))
66
57
  end
67
58
 
68
59
  ##
@@ -82,7 +73,7 @@ module Toys
82
73
  # code and any captured output.
83
74
  #
84
75
  def exec(cmd, opts = {}, &block)
85
- self[Exec].exec(cmd, Exec._setup_exec_opts(opts, self), &block)
76
+ Exec._exec(self).exec(cmd, Exec._setup_exec_opts(opts, self), &block)
86
77
  end
87
78
 
88
79
  ##
@@ -101,7 +92,7 @@ module Toys
101
92
  # code and any captured output.
102
93
  #
103
94
  def ruby(args, opts = {}, &block)
104
- self[Exec].ruby(args, Exec._setup_exec_opts(opts, self), &block)
95
+ Exec._exec(self).ruby(args, Exec._setup_exec_opts(opts, self), &block)
105
96
  end
106
97
 
107
98
  ##
@@ -110,13 +101,11 @@ module Toys
110
101
  # @param [String] cmd The shell command to execute.
111
102
  # @param [Hash] opts The command options. See the section on
112
103
  # configuration options in the {Toys::Utils::Exec} module docs.
113
- # @yieldparam controller [Toys::Utils::Exec::Controller] A controller for
114
- # the subprocess streams.
115
104
  #
116
105
  # @return [Integer] The exit code
117
106
  #
118
107
  def sh(cmd, opts = {})
119
- self[Exec].sh(cmd, Exec._setup_exec_opts(opts, self))
108
+ Exec._exec(self).sh(cmd, Exec._setup_exec_opts(opts, self))
120
109
  end
121
110
 
122
111
  ##
@@ -128,21 +117,38 @@ module Toys
128
117
  # @param [String,Array<String>] cmd The command to execute.
129
118
  # @param [Hash] opts The command options. See the section on
130
119
  # configuration options in the {Toys::Utils::Exec} module docs.
131
- # @yieldparam controller [Toys::Utils::Exec::Controller] A controller for
132
- # the subprocess streams.
133
120
  #
134
121
  # @return [String] What was written to standard out.
135
122
  #
136
123
  def capture(cmd, opts = {})
137
- self[Exec].capture(cmd, Exec._setup_exec_opts(opts, self))
124
+ Exec._exec(self).capture(cmd, Exec._setup_exec_opts(opts, self))
125
+ end
126
+
127
+ ##
128
+ # Exit if the given status code is nonzero. Otherwise, returns 0.
129
+ #
130
+ # @param [Integer,Process::Status,Toys::Utils::Exec::Result] status
131
+ #
132
+ def exit_on_nonzero_status(status)
133
+ status = status.exit_code if status.respond_to?(:exit_code)
134
+ status = status.exitstatus if status.respond_to?(:exitstatus)
135
+ exit(status) unless status.zero?
136
+ 0
137
+ end
138
+
139
+ ## @private
140
+ def self._exec(tool)
141
+ tool[Exec] ||= Utils::Exec.new do |k|
142
+ k == :logger ? tool[Tool::Keys::LOGGER] : nil
143
+ end
138
144
  end
139
145
 
140
146
  ## @private
141
- def self._setup_exec_opts(opts, context)
147
+ def self._setup_exec_opts(opts, tool)
142
148
  return opts unless opts.key?(:exit_on_nonzero_status)
143
149
  nonzero_status_handler =
144
150
  if opts[:exit_on_nonzero_status]
145
- proc { |s| context.exit(s.exitstatus) }
151
+ proc { |s| tool.exit(s.exitstatus) }
146
152
  end
147
153
  opts.merge(nonzero_status_handler: nonzero_status_handler)
148
154
  end
@@ -34,10 +34,16 @@ module Toys
34
34
  ##
35
35
  # A module that provides all methods in the "fileutils" standard library.
36
36
  #
37
+ # You may make the methods in the `FileUtils` standard library module
38
+ # available to your tool by including the following directive in your tool
39
+ # configuration:
40
+ #
41
+ # include :fileutils
42
+ #
37
43
  module Fileutils
38
44
  ## @private
39
- def self.extend_object(obj)
40
- obj.extend(::FileUtils)
45
+ def self.included(mod)
46
+ mod.include(::FileUtils)
41
47
  end
42
48
  end
43
49
  end
@@ -27,12 +27,19 @@
27
27
  # POSSIBILITY OF SUCH DAMAGE.
28
28
  ;
29
29
 
30
+ require "toys/utils/gems"
31
+ Toys::Utils::Gems.activate("highline", "~> 1.7")
30
32
  require "highline"
31
33
 
32
34
  module Toys
33
35
  module Helpers
34
36
  ##
35
- # A module that provides access to highline.
37
+ # A module that provides access to the capabilities of the highline gem.
38
+ #
39
+ # You may make these methods available to your tool by including the
40
+ # following directive in your tool configuration:
41
+ #
42
+ # include :highline
36
43
  #
37
44
  module Highline
38
45
  ##
@@ -27,68 +27,59 @@
27
27
  # POSSIBILITY OF SUCH DAMAGE.
28
28
  ;
29
29
 
30
+ require "toys/utils/terminal"
31
+
30
32
  module Toys
31
- ##
32
- # An alias is a name that refers to another name.
33
- #
34
- class Alias
33
+ module Helpers
35
34
  ##
36
- # Create a new alias.
35
+ # A helper that provides a simple terminal. It includes a set of methods
36
+ # that produce styled output, get user input, and otherwise interact with
37
+ # the user's terminal.
37
38
  #
38
- # @param [Array<String>] full_name The name of the alias.
39
- # @param [String,Array<String>] target The name of the target. May either
40
- # be a local reference (a single string) or a global reference (an
41
- # array of strings)
39
+ # You may make these methods available to your tool by including the
40
+ # following directive in your tool configuration:
42
41
  #
43
- def initialize(full_name, target)
44
- @target_name =
45
- if target.is_a?(::String)
46
- full_name[0..-2] + [target]
47
- else
48
- target.dup
49
- end
50
- @target_name.freeze
51
- @full_name = full_name.dup.freeze
52
- end
53
-
54
- ##
55
- # Return the name of the tool as an array of strings.
56
- # This array may not be modified.
57
- # @return [Array<String>]
42
+ # include :terminal
58
43
  #
59
- attr_reader :full_name
44
+ module Terminal
45
+ ##
46
+ # Returns a global terminal instance
47
+ # @return [Toys::Utils::Terminal]
48
+ #
49
+ def terminal
50
+ self[Terminal] ||= Utils::Terminal.new
51
+ end
60
52
 
61
- ##
62
- # Return the name of the target as an array of strings.
63
- # This array may not be modified.
64
- # @return [Array<String>]
65
- #
66
- attr_reader :target_name
53
+ ##
54
+ # @see Toys::Utils::Terminal#puts
55
+ #
56
+ def puts(str = "", *styles)
57
+ terminal.puts(str, *styles)
58
+ end
67
59
 
68
- ##
69
- # Returns the local name of this tool.
70
- # @return [String]
71
- #
72
- def simple_name
73
- full_name.last
74
- end
60
+ ##
61
+ # @see Toys::Utils::Terminal#write
62
+ #
63
+ def write(str = "", *styles)
64
+ terminal.write(str, *styles)
65
+ end
75
66
 
76
- ##
77
- # Returns a displayable name of this tool, generally the full name
78
- # delimited by spaces.
79
- # @return [String]
80
- #
81
- def display_name
82
- full_name.join(" ")
83
- end
67
+ ##
68
+ # @see Toys::Utils::Terminal#confirm
69
+ #
70
+ def confirm(prompt = "Proceed?")
71
+ terminal.confirm(prompt)
72
+ end
84
73
 
85
- ##
86
- # Returns a displayable name of the target, generally the full name
87
- # delimited by spaces.
88
- # @return [String]
89
- #
90
- def display_target
91
- target_name.join(" ")
74
+ ##
75
+ # @see Toys::Utils::Terminal#spinner
76
+ #
77
+ def spinner(leading_text: "", final_text: "",
78
+ frame_length: nil, frames: nil, style: nil, &block)
79
+ terminal.spinner(leading_text: leading_text, final_text: final_text,
80
+ frame_length: frame_length, frames: frames, style: style,
81
+ &block)
82
+ end
92
83
  end
93
84
  end
94
85
  end
@@ -0,0 +1,61 @@
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
+ ##
31
+ # Internal modules providing constant namespaces for config files.
32
+ #
33
+ module Toys::InputFile # rubocop:disable Style/ClassAndModuleChildren
34
+ ## @private
35
+ def self.__binding
36
+ binding
37
+ end
38
+
39
+ ## @private
40
+ def self.evaluate(tool_class, remaining_words, path)
41
+ namespace = ::Module.new
42
+ namespace.module_eval do
43
+ include ::Toys::Tool::Keys
44
+ @tool_class = tool_class
45
+ end
46
+ name = "M#{namespace.object_id}"
47
+ const_set(name, namespace)
48
+ str = <<-STR
49
+ module #{name}; @tool_class.class_eval do
50
+ #{::IO.read(path)}
51
+ end end
52
+ STR
53
+ ::Toys::DSL::Tool.prepare(tool_class, remaining_words, path) do
54
+ ::Toys::ContextualError.capture_path("Error while loading Toys config!", path) do
55
+ # rubocop:disable Security/Eval
56
+ eval(str, __binding, path, 0)
57
+ # rubocop:enable Security/Eval
58
+ end
59
+ end
60
+ end
61
+ end
data/lib/toys/loader.rb CHANGED
@@ -33,6 +33,17 @@ module Toys
33
33
  # appropriate tool given a set of command line arguments.
34
34
  #
35
35
  class Loader
36
+ ## @private
37
+ ToolData = ::Struct.new(:definitions, :top_priority, :active_priority) do
38
+ def top_definition
39
+ top_priority ? definitions[top_priority] : nil
40
+ end
41
+
42
+ def active_definition
43
+ active_priority ? definitions[active_priority] : nil
44
+ end
45
+ end
46
+
36
47
  ##
37
48
  # Create a Loader
38
49
  #
@@ -60,7 +71,7 @@ module Toys
60
71
  @preload_file_name = preload_file_name
61
72
  @middleware_stack = middleware_stack
62
73
  @load_worklist = []
63
- @tools = {}
74
+ @tool_data = {}
64
75
  @max_priority = @min_priority = 0
65
76
  end
66
77
 
@@ -93,27 +104,26 @@ module Toys
93
104
  # that are not part of the tool name and should be passed as tool args.
94
105
  #
95
106
  # @param [String] args Command line arguments
96
- # @return [Array(Toys::Tool,Array<String>)]
107
+ # @return [Array(Toys::Definition::Tool,Array<String>)]
97
108
  #
98
109
  def lookup(args)
99
110
  orig_prefix = args.take_while { |arg| !arg.start_with?("-") }
100
- cur_prefix = orig_prefix.dup
111
+ cur_prefix = orig_prefix
101
112
  loop do
102
113
  load_for_prefix(cur_prefix)
103
- p = orig_prefix.dup
104
- while p.length >= cur_prefix.length
105
- tool = get_tool(p, [])
106
- if tool
107
- finish_definitions_in_tree(tool.full_name)
108
- return [tool, args.slice(p.length..-1)]
114
+ p = orig_prefix
115
+ loop do
116
+ tool_definition = get_active_tool(p, [])
117
+ if tool_definition
118
+ finish_definitions_in_tree(tool_definition.full_name)
119
+ return [tool_definition, args.slice(p.length..-1)]
109
120
  end
110
- p.pop
121
+ break if p.empty? || p.length <= cur_prefix.length
122
+ p = p.slice(0..-2)
111
123
  end
112
- break unless cur_prefix.pop
124
+ return nil if cur_prefix.empty?
125
+ cur_prefix = cur_prefix.slice(0..-2)
113
126
  end
114
- tool = get_or_create_tool([])
115
- finish_definitions_in_tree([])
116
- [tool, args]
117
127
  end
118
128
 
119
129
  ##
@@ -123,20 +133,21 @@ module Toys
123
133
  # @param [Array<String>] words The name of the parent tool
124
134
  # @param [Boolean] recursive If true, return all subtools recursively
125
135
  # rather than just the immediate children (the default)
126
- # @return [Array<Toys::Tool,Tool::Alias>]
136
+ # @return [Array<Toys::Definition::Tool,Tool::Definition::Alias>]
127
137
  #
128
138
  def list_subtools(words, recursive: false)
129
139
  load_for_prefix(words)
130
140
  found_tools = []
131
141
  len = words.length
132
- @tools.each do |n, tp|
142
+ @tool_data.each do |n, td|
133
143
  next if n.empty?
134
144
  if recursive
135
145
  next if n.length <= len || n.slice(0, len) != words
136
146
  else
137
147
  next unless n.slice(0..-2) == words
138
148
  end
139
- found_tools << tp.first
149
+ tool = td.active_definition || td.top_definition
150
+ found_tools << tool unless tool.nil?
140
151
  end
141
152
  sort_tools_by_name(found_tools)
142
153
  end
@@ -151,7 +162,7 @@ module Toys
151
162
  def has_subtools?(words)
152
163
  load_for_prefix(words)
153
164
  len = words.length
154
- @tools.each do |n, _tp|
165
+ @tool_data.each_key do |n|
155
166
  return true if !n.empty? && n.length > len && n.slice(0, len) == words
156
167
  end
157
168
  false
@@ -167,9 +178,10 @@ module Toys
167
178
  def finish_definitions_in_tree(words)
168
179
  load_for_prefix(words)
169
180
  len = words.length
170
- @tools.each do |n, tp|
181
+ @tool_data.each do |n, td|
171
182
  next if n.length < len || n.slice(0, len) != words
172
- tp.first.finish_definition(self) unless tp.first.is_a?(Alias)
183
+ tool = td.active_definition || td.top_definition
184
+ tool.finish_definition(self) if tool.is_a?(Definition::Tool)
173
185
  end
174
186
  end
175
187
 
@@ -178,28 +190,18 @@ module Toys
178
190
  # Does not do any loading. If the tool is not present, creates it.
179
191
  #
180
192
  # @param [Array<String>] words The name of the tool.
181
- # @param [Integer,nil] priority The priority of the request.
182
- # @return [Toys::Tool,Toys::Alias,nil] The tool or alias, or `nil` if the
183
- # given priority is insufficient for modification
193
+ # @param [Integer] priority The priority of the request.
194
+ # @return [Toys::Definition::Tool,Toys::Definition::Alias,nil] The tool or
195
+ # alias, or `nil` if the given priority is insufficient
184
196
  #
185
197
  # @private
186
198
  #
187
- def get_or_create_tool(words, priority: nil)
188
- if @tools.key?(words)
189
- tool, tool_priority = @tools[words]
190
- if !priority || !tool_priority || tool_priority == priority
191
- if priority && tool.is_a?(Alias)
192
- raise LoaderError, "Cannot modify #{@words.inspect} because it is already an alias"
193
- end
194
- return tool
195
- end
196
- return nil if tool_priority > priority
197
- end
198
- get_or_create_tool(words[0..-2]) unless words.empty?
199
- tool = Tool.new(words)
200
- tool.middleware_stack.concat(Middleware.resolve_stack(@middleware_stack))
201
- @tools[words] = [tool, priority]
202
- tool
199
+ def activate_tool_definition(words, priority)
200
+ tool_data = get_tool_data(words)
201
+ return tool_data.active_definition if tool_data.active_priority == priority
202
+ return nil if tool_data.active_priority && tool_data.active_priority > priority
203
+ tool_data.active_priority = priority
204
+ get_tool_definition(words, priority)
203
205
  end
204
206
 
205
207
  ##
@@ -209,39 +211,20 @@ module Toys
209
211
  # @param [Array<String>] target The alias target name
210
212
  # @param [Integer] priority The priority of the request
211
213
  #
212
- # @return [Toys::Alias] The alias created
214
+ # @return [Toys::Definition::Alias] The alias created
213
215
  #
214
216
  # @private
215
217
  #
216
218
  def make_alias(words, target, priority)
217
- if @tools.key?(words)
218
- tool_priority = @tools[words].last
219
- if tool_priority
220
- if tool_priority == priority
221
- raise LoaderError, "Cannot make #{words.inspect} an alias because it is already defined"
222
- elsif tool_priority > priority
223
- return nil
224
- end
225
- end
219
+ tool_data = get_tool_data(words)
220
+ if tool_data.definitions.key?(priority)
221
+ raise ToolDefinitionError,
222
+ "Cannot make #{words.inspect} an alias because it is already defined"
226
223
  end
227
- a = Alias.new(words, target)
228
- @tools[words] = [a, priority]
229
- a
230
- end
231
-
232
- ##
233
- # Adds a tool directly to the loader.
234
- # This should be used only for testing, as it overrides normal priority
235
- # checking.
236
- #
237
- # @param [Toys::Tool] tool Tool to add.
238
- # @param [Integer,nil] priority Priority for the tool.
239
- #
240
- # @private
241
- #
242
- def put_tool!(tool, priority = nil)
243
- @tools[tool.full_name] = [tool, priority]
244
- self
224
+ alias_def = Definition::Alias.new(self, words, target, priority)
225
+ tool_data.definitions[priority] = alias_def
226
+ activate_tool_definition(words, priority)
227
+ alias_def
245
228
  end
246
229
 
247
230
  ##
@@ -254,7 +237,7 @@ module Toys
254
237
  # @private
255
238
  #
256
239
  def tool_defined?(words)
257
- @tools.key?(words)
240
+ @tool_data.key?(words)
258
241
  end
259
242
 
260
243
  ##
@@ -264,25 +247,47 @@ module Toys
264
247
  # @param [Array<Array<String>>] looked_up List of names that have already
265
248
  # been traversed during alias resolution. Used to detect circular
266
249
  # alias references.
267
- # @return [Toys::Tool,nil] The tool, or `nil` if not found
250
+ # @return [Toys::Definition::Tool,nil] The tool, or `nil` if not found
268
251
  #
269
252
  # @private
270
253
  #
271
- def get_tool(words, looked_up = [])
272
- return nil unless @tools.key?(words)
273
- result = @tools[words].first
274
- if result.is_a?(Alias)
254
+ def get_active_tool(words, looked_up = [])
255
+ tool_data = get_tool_data(words)
256
+ result = tool_data.active_definition
257
+ case result
258
+ when Definition::Alias
275
259
  words = result.target_name
276
260
  if looked_up.include?(words)
277
- raise LoaderError, "Circular alias references: #{looked_up.inspect}"
261
+ raise ToolDefinitionError, "Circular alias references: #{looked_up.inspect}"
278
262
  end
279
263
  looked_up << words
280
- get_tool(words, looked_up)
281
- else
264
+ get_active_tool(words, looked_up)
265
+ when Definition::Tool
282
266
  result
267
+ else
268
+ tool_data.top_definition
283
269
  end
284
270
  end
285
271
 
272
+ ##
273
+ # Get the tool definition for the given name and priority.
274
+ #
275
+ # @private
276
+ #
277
+ def get_tool_definition(words, priority)
278
+ parent = words.empty? ? nil : get_tool_definition(words.slice(0..-2), priority)
279
+ if parent.is_a?(Definition::Alias)
280
+ raise ToolDefinitionError,
281
+ "Cannot create children of #{parent.display_name.inspect} because it is an alias"
282
+ end
283
+ tool_data = get_tool_data(words)
284
+ if tool_data.top_priority.nil? || tool_data.top_priority < priority
285
+ tool_data.top_priority = priority
286
+ end
287
+ tool_data.definitions[priority] ||=
288
+ Definition::Tool.new(self, parent, words, priority, @middleware_stack)
289
+ end
290
+
286
291
  ##
287
292
  # Load configuration from the given path.
288
293
  #
@@ -309,6 +314,10 @@ module Toys
309
314
 
310
315
  private
311
316
 
317
+ def get_tool_data(words)
318
+ @tool_data[words] ||= ToolData.new({}, nil, nil)
319
+ end
320
+
312
321
  def load_for_prefix(prefix)
313
322
  cur_worklist = @load_worklist
314
323
  @load_worklist = []
@@ -327,7 +336,8 @@ module Toys
327
336
 
328
337
  def load_path(path, words, remaining_words, priority)
329
338
  if ::File.extname(path) == ".rb"
330
- ConfigDSL.evaluate(words, remaining_words, priority, self, path, ::IO.read(path))
339
+ tool_class = get_tool_definition(words, priority).tool_class
340
+ Toys::InputFile.evaluate(tool_class, remaining_words, path)
331
341
  else
332
342
  require_preload_in(path)
333
343
  load_index_in(path, words, remaining_words, priority)