toys-core 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bd284ba44663511c6004cf0d742c4e1b5a703cbe596fd6612350be3b316ec60f
4
- data.tar.gz: 9fdc655c88053d669727399c4c4c7321d4d2ececd579c9420bcddb1f0a40d65d
3
+ metadata.gz: 0202fa141dcee7d96aacdec2092ace8041f5498e367596d8e2a5aa893fa64d36
4
+ data.tar.gz: 20ea445f7ad7a5ab964ad23434e57f4acb6ee567e6dc38837751246d32940ca5
5
5
  SHA512:
6
- metadata.gz: ccdfadc2660d171d39776f63e708839edfaef4b63e21a2ddc39feb6456763a0d54fa27cc611cd2c9180210c291fb1f2510fa925b9f9ebea98f7e3794d42ca00e
7
- data.tar.gz: e8d68670c340ac2a2b51a83a1201334ccbbda1a69464a25b9a376badfaf891895ffcc189ad54afbd1a2901b4673ded5fcb61296065ed205e98bfd05dbf4dc810
6
+ metadata.gz: 704d70fc544435c23cf3d0e88550fa3a3951bbb9b3611eabdc89c3c9871d3ad67098cce21fa54ffb130aa4da6d0901729471b9e698ae78a394f8ff750392205f
7
+ data.tar.gz: 57d9cacc5646fd39e0399bb2729d0a936d70f5d67a1f7a7e3d4b19a5d38ab699dc6b0e5044ae455fea3cdb0d46e44406bfc898074e71e2d9b3cc7882981886d7
@@ -1,5 +1,14 @@
1
1
  # Release History
2
2
 
3
+ ### 0.6.0 / 2018-10-22
4
+
5
+ * CHANGED: Replaced Toys::Definition::DataFinder with Toys::Definition::SourceInfo.
6
+ * CHANGED: Removed Toys::Definition#find_data. Use Toys::Definition#source_info and call find_data.
7
+ * ADDED: Context directory is kept in SourceInfo and available in the DSL and the tool runtime.
8
+ * IMPROVED: Optionally omit hidden subtools (i.e. names beginning with underscore)
9
+ from subtool lists.
10
+ * IMPROVED: Optionally omit non-runnable namespaces from recursive subtool lists.
11
+
3
12
  ### 0.5.0 / 2018-10-07
4
13
 
5
14
  * FIXED: Template instantiation was failing if the hosting tool was priority-masked.
@@ -68,8 +68,8 @@ require "toys/core_version"
68
68
  require "toys/definition/acceptor"
69
69
  require "toys/definition/alias"
70
70
  require "toys/definition/arg"
71
- require "toys/definition/data_finder"
72
71
  require "toys/definition/flag"
72
+ require "toys/definition/source_info"
73
73
  require "toys/definition/tool"
74
74
  require "toys/dsl/arg"
75
75
  require "toys/dsl/flag"
@@ -173,12 +173,12 @@ module Toys
173
173
  #
174
174
  # @param [Boolean] high_priority Add the config at the head of the priority
175
175
  # list rather than the tail.
176
- # @param [String] path The "path" that will be shown in documentation for
177
- # tools defined in this block. If omitted, a default unique string will
178
- # be generated.
176
+ # @param [String] name The source name that will be shown in documentation
177
+ # for tools defined in this block. If omitted, a default unique string
178
+ # will be generated.
179
179
  #
180
- def add_config_block(high_priority: false, path: nil, &block)
181
- @loader.add_block(high_priority: high_priority, path: path, &block)
180
+ def add_config_block(high_priority: false, name: nil, &block)
181
+ @loader.add_block(high_priority: high_priority, name: name, &block)
182
182
  self
183
183
  end
184
184
 
@@ -251,7 +251,7 @@ module Toys
251
251
  @loader.lookup(args.flatten)
252
252
  end
253
253
  ContextualError.capture_path(
254
- "Error during tool execution!", tool_definition.source_path,
254
+ "Error during tool execution!", tool_definition.source_info&.source_path,
255
255
  tool_name: tool_definition.full_name, tool_args: remaining
256
256
  ) do
257
257
  Runner.new(self, tool_definition).run(remaining, verbosity: verbosity)
@@ -34,5 +34,5 @@ module Toys
34
34
  # Current version of Toys core
35
35
  # @return [String]
36
36
  #
37
- CORE_VERSION = "0.5.0"
37
+ CORE_VERSION = "0.6.0"
38
38
  end
@@ -0,0 +1,190 @@
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 Definition
34
+ ##
35
+ # Information about source toys directories and files.
36
+ #
37
+ class SourceInfo
38
+ ##
39
+ # Create a SourceInfo.
40
+ # @private
41
+ #
42
+ def initialize(parent, context_directory, source, source_type, source_name, data_dir_name)
43
+ @parent = parent
44
+ @context_directory = context_directory
45
+ @source = source
46
+ @source_type = source_type
47
+ @source_path = source if source.is_a?(::String)
48
+ @source_proc = source if source.is_a?(::Proc)
49
+ @source_name = source_name
50
+ @data_dir =
51
+ if data_dir_name && @source_path
52
+ dir = ::File.join(::File.dirname(@source_path), data_dir_name)
53
+ dir if ::File.directory?(dir) && ::File.readable?(dir)
54
+ end
55
+ end
56
+
57
+ ##
58
+ # Return the parent SourceInfo, or nil if this is the root.
59
+ # @return [Toys::Definition::SourceInfo,nil]
60
+ #
61
+ attr_reader :parent
62
+
63
+ ##
64
+ # Return the context directory path (normally the directory containing
65
+ # the toplevel toys file or directory). May return nil if there is no
66
+ # context (e.g. the tool is being defined from a block).
67
+ # @return [String,nil]
68
+ #
69
+ attr_reader :context_directory
70
+
71
+ ##
72
+ # Return the source, which may be a path or a proc.
73
+ # @return [String,Proc]
74
+ #
75
+ attr_reader :source
76
+
77
+ ##
78
+ # Return the type of source.
79
+ # @return [:file,:directory,:proc]
80
+ #
81
+ attr_reader :source_type
82
+
83
+ ##
84
+ # Return the path of the current source file or directory, or nil if this
85
+ # source is not a file system path.
86
+ # @return [String,nil]
87
+ #
88
+ attr_reader :source_path
89
+
90
+ ##
91
+ # Return the source proc, or nil if this source is not a proc.
92
+ # @return [Proc,nil]
93
+ #
94
+ attr_reader :source_proc
95
+
96
+ ##
97
+ # Return the user-visible name of this source.
98
+ # @return [String]
99
+ #
100
+ attr_reader :source_name
101
+ alias to_s source_name
102
+
103
+ ##
104
+ # Return the absolute path to the given data file or directory.
105
+ #
106
+ # @param [String] path The relative path to find
107
+ # @param [nil,:file,:directory] type Type of file system object to find,
108
+ # or nil to return any type.
109
+ # @return [String,nil] Absolute path of the result, or nil if not found.
110
+ #
111
+ def find_data(path, type: nil)
112
+ if @data_dir
113
+ full_path = ::File.join(@data_dir, path)
114
+ case type
115
+ when :file
116
+ return full_path if ::File.file?(full_path)
117
+ when :directory
118
+ return full_path if ::File.directory?(full_path)
119
+ else
120
+ return full_path if ::File.readable?(full_path)
121
+ end
122
+ end
123
+ parent&.find_data(path, type: type)
124
+ end
125
+
126
+ ##
127
+ # Create a child SourceInfo relative to the parent path.
128
+ # @private
129
+ #
130
+ def relative_child(filename, data_dir_name)
131
+ raise "Cannot create relative child of a proc" unless source_path
132
+ child_path = ::File.join(source_path, filename)
133
+ child_path, type = SourceInfo.check_path(child_path, true)
134
+ return nil unless child_path
135
+ SourceInfo.new(self, context_directory, child_path, type, child_path, data_dir_name)
136
+ end
137
+
138
+ ##
139
+ # Create a child SourceInfo with an absolute path.
140
+ # @private
141
+ #
142
+ def absolute_child(child_path)
143
+ child_path, type = SourceInfo.check_path(child_path, false)
144
+ SourceInfo.new(self, context_directory, child_path, type, child_path, nil)
145
+ end
146
+
147
+ ##
148
+ # Create a root source info for a file path.
149
+ # @private
150
+ #
151
+ def self.create_path_root(source_path)
152
+ source_path, type = check_path(source_path, false)
153
+ context_directory = ::File.dirname(source_path)
154
+ new(nil, context_directory, source_path, type, source_path, nil)
155
+ end
156
+
157
+ ##
158
+ # Create a root source info for a proc.
159
+ # @private
160
+ #
161
+ def self.create_proc_root(source_proc, source_name)
162
+ new(nil, nil, source_proc, :proc, source_name, nil)
163
+ end
164
+
165
+ ##
166
+ # Check a path and determine the canonical path and type.
167
+ # @private
168
+ #
169
+ def self.check_path(path, lenient)
170
+ path = ::File.expand_path(path)
171
+ unless ::File.readable?(path)
172
+ raise LoaderError, "Cannot read: #{path}" unless lenient
173
+ return [nil, nil]
174
+ end
175
+ if ::File.file?(path)
176
+ unless ::File.extname(path) == ".rb"
177
+ raise LoaderError, "File is not a ruby file: #{path}" unless lenient
178
+ return [nil, nil]
179
+ end
180
+ [path, :file]
181
+ elsif ::File.directory?(path)
182
+ [path, :directory]
183
+ else
184
+ raise LoaderError, "Unknown type: #{path}" unless lenient
185
+ [nil, nil]
186
+ end
187
+ end
188
+ end
189
+ end
190
+ end
@@ -91,8 +91,7 @@ module Toys
91
91
  def reset_definition(loader)
92
92
  @tool_class = DSL::Tool.new_class(@full_name, @priority, loader)
93
93
 
94
- @source_path = nil
95
- @data_finder = nil
94
+ @source_info = nil
96
95
  @definition_finished = false
97
96
 
98
97
  @desc = Utils::WrappableString.new("")
@@ -109,6 +108,7 @@ module Toys
109
108
 
110
109
  @disable_argument_parsing = false
111
110
  @includes_modules = false
111
+ @custom_context_directory = nil
112
112
  end
113
113
 
114
114
  ##
@@ -186,10 +186,18 @@ module Toys
186
186
  attr_reader :middleware_stack
187
187
 
188
188
  ##
189
- # Returns the path to the file that contains the definition of this tool.
190
- # @return [String]
189
+ # Returns info on the source of this tool, or nil if the source is not
190
+ # defined.
191
+ # @return [Toys::Definition::SourceInfo,nil]
192
+ #
193
+ attr_reader :source_info
194
+
195
+ ##
196
+ # Returns the custom context directory set for this tool, or nil if none
197
+ # is set.
198
+ # @return [String,nil]
191
199
  #
192
- attr_reader :source_path
200
+ attr_reader :custom_context_directory
193
201
 
194
202
  ##
195
203
  # Returns the local name of this tool.
@@ -366,17 +374,15 @@ module Toys
366
374
  # A tool may be defined from at most one path. If a different path is
367
375
  # already set, raises {Toys::ToolDefinitionError}
368
376
  #
369
- # @param [String] path The path to the file defining this tool
370
- # @param [Toys::Definition::DataFinder] data_finder Data finder
377
+ # @param [Toys::Definition::SourceInfo] source Source info
371
378
  #
372
- def lock_source_path(path, data_finder)
373
- if source_path && source_path != path
379
+ def lock_source(source)
380
+ if source_info && source_info.source != source.source
374
381
  raise ToolDefinitionError,
375
- "Cannot redefine tool #{display_name.inspect} in #{path}" \
376
- " (already defined in #{source_path})"
382
+ "Cannot redefine tool #{display_name.inspect} in #{source.source_name}" \
383
+ " (already defined in #{source_info.source_name})"
377
384
  end
378
- @source_path = path
379
- @data_finder = data_finder
385
+ @source_info = source
380
386
  end
381
387
 
382
388
  ##
@@ -671,15 +677,35 @@ module Toys
671
677
  end
672
678
 
673
679
  ##
674
- # Find the given data file or directory in this tool's search path.
680
+ # Set the custom context directory.
675
681
  #
676
- # @param [String] path The path to find
677
- # @param [nil,:file,:directory] type Type of file system object to find,
678
- # or nil to return any type.
679
- # @return [String,nil] Absolute path of the result, or nil if not found.
682
+ # @param [String] dir
683
+ #
684
+ def custom_context_directory=(dir)
685
+ check_definition_state
686
+ @custom_context_directory = dir
687
+ end
688
+
689
+ ##
690
+ # Return the effective context directory.
691
+ # If there is a custom context directory, uses that. Otherwise, looks for
692
+ # a custom context directory up the tool ancestor chain. If none is
693
+ # found, uses the default context directory from the source info. It is
694
+ # possible for there to be no context directory at all, in which case,
695
+ # returns nil.
696
+ #
697
+ # @return [String,nil]
698
+ #
699
+ def context_directory
700
+ lookup_custom_context_directory || source_info&.context_directory
701
+ end
702
+
703
+ ##
704
+ # Lookup the custom context directory in this tool and its ancestors.
705
+ # @private
680
706
  #
681
- def find_data(path, type: nil)
682
- @data_finder ? @data_finder.find_data(path, type: type) : nil
707
+ def lookup_custom_context_directory
708
+ custom_context_directory || @parent&.lookup_custom_context_directory
683
709
  end
684
710
 
685
711
  ##
@@ -203,7 +203,7 @@ module Toys
203
203
  end
204
204
  end
205
205
  subtool_class = subtool.tool_class
206
- DSL::Tool.prepare(subtool_class, next_remaining, @__path, @__data_finder) do
206
+ DSL::Tool.prepare(subtool_class, next_remaining, source_info) do
207
207
  subtool_class.class_eval(&block)
208
208
  end
209
209
  self
@@ -230,7 +230,7 @@ module Toys
230
230
  # @return [Toys::DSL::Tool] self, for chaining.
231
231
  #
232
232
  def load(path)
233
- @__loader.load_path(path, @__words, @__remaining_words, @__priority)
233
+ @__loader.load_path(source_info, path, @__words, @__remaining_words, @__priority)
234
234
  self
235
235
  end
236
236
 
@@ -650,6 +650,15 @@ module Toys
650
650
  super(DSL::Tool.resolve_mixin(mod, cur_tool, @__loader))
651
651
  end
652
652
 
653
+ ##
654
+ # Return the current source info object.
655
+ #
656
+ # @return [Toys::Definition::SourceInfo] Source info.
657
+ #
658
+ def source_info
659
+ @__source.last
660
+ end
661
+
653
662
  ##
654
663
  # Find the given data path (file or directory)
655
664
  #
@@ -659,7 +668,32 @@ module Toys
659
668
  # @return [String,nil] Absolute path of the result, or nil if not found.
660
669
  #
661
670
  def find_data(path, type: nil)
662
- @__data_finder ? @__data_finder.find_data(path, type: type) : nil
671
+ source_info.find_data(path, type: type)
672
+ end
673
+
674
+ ##
675
+ # Return the context directory for this tool. Generally, this defaults
676
+ # to the directory containing the toys config directory structure being
677
+ # read, but it may be changed by setting a different context directory
678
+ # for the tool.
679
+ # May return nil if there is no context.
680
+ #
681
+ # @return [String,nil] Context directory
682
+ #
683
+ def context_directory
684
+ DSL::Tool.current_tool(self, false)&.context_directory || source_info.context_directory
685
+ end
686
+
687
+ ##
688
+ # Set a custom context directory for this tool.
689
+ #
690
+ # @param [String] dir Context directory
691
+ #
692
+ def set_context_directory(dir)
693
+ cur_tool = DSL::Tool.current_tool(self, false)
694
+ return if cur_tool.nil?
695
+ cur_tool.custom_context_directory = dir
696
+ self
663
697
  end
664
698
 
665
699
  ## @private
@@ -670,8 +704,7 @@ module Toys
670
704
  tool_class.instance_variable_set(:@__priority, priority)
671
705
  tool_class.instance_variable_set(:@__loader, loader)
672
706
  tool_class.instance_variable_set(:@__remaining_words, nil)
673
- tool_class.instance_variable_set(:@__path, nil)
674
- tool_class.instance_variable_set(:@__data_finder, nil)
707
+ tool_class.instance_variable_set(:@__source, [])
675
708
  tool_class
676
709
  end
677
710
 
@@ -697,23 +730,19 @@ module Toys
697
730
  tool_class.instance_variable_set(memoize_var, cur_tool)
698
731
  end
699
732
  if cur_tool && activate
700
- path = tool_class.instance_variable_get(:@__path)
701
- data_finder = tool_class.instance_variable_get(:@__data_finder)
702
- cur_tool.lock_source_path(path, data_finder)
733
+ source = tool_class.instance_variable_get(:@__source).last
734
+ cur_tool.lock_source(source)
703
735
  end
704
736
  cur_tool
705
737
  end
706
738
 
707
739
  ## @private
708
- def self.prepare(tool_class, remaining_words, path, data_finder)
740
+ def self.prepare(tool_class, remaining_words, source)
709
741
  tool_class.instance_variable_set(:@__remaining_words, remaining_words)
710
- tool_class.instance_variable_set(:@__path, path)
711
- tool_class.instance_variable_set(:@__data_finder, data_finder)
742
+ tool_class.instance_variable_get(:@__source).push(source)
712
743
  yield
713
744
  ensure
714
- tool_class.instance_variable_set(:@__remaining_words, nil)
715
- tool_class.instance_variable_set(:@__path, nil)
716
- tool_class.instance_variable_set(:@__data_finder, nil)
745
+ tool_class.instance_variable_get(:@__source).pop
717
746
  end
718
747
 
719
748
  ## @private
@@ -40,18 +40,19 @@ module Toys::InputFile # rubocop:disable Style/ClassAndModuleChildren
40
40
  end
41
41
 
42
42
  ## @private
43
- def self.evaluate(tool_class, remaining_words, path, data_finder)
43
+ def self.evaluate(tool_class, remaining_words, source)
44
44
  namespace = ::Module.new
45
45
  namespace.module_eval do
46
46
  include ::Toys::Tool::Keys
47
47
  @tool_class = tool_class
48
48
  end
49
+ path = source.source_path
49
50
  basename = ::File.basename(path).tr(".-", "_").gsub(/\W/, "")
50
51
  name = "M#{namespace.object_id}_#{basename}"
51
52
  str = build_eval_string(name, ::IO.read(path))
52
53
  if str
53
54
  const_set(name, namespace)
54
- ::Toys::DSL::Tool.prepare(tool_class, remaining_words, path, data_finder) do
55
+ ::Toys::DSL::Tool.prepare(tool_class, remaining_words, source) do
55
56
  ::Toys::ContextualError.capture_path("Error while loading Toys config!", path) do
56
57
  # rubocop:disable Security/Eval
57
58
  eval(str, __binding, path, 0)
@@ -89,7 +89,7 @@ module Toys
89
89
  @index_file_name = index_file_name
90
90
  @preload_file_name = preload_file_name
91
91
  @preload_directory_name = preload_directory_name
92
- @empty_data_finder = Definition::DataFinder.create_empty(data_directory_name)
92
+ @data_directory_name = data_directory_name
93
93
  @middleware_stack = middleware_stack
94
94
  @worklist = []
95
95
  @tool_data = {}
@@ -101,16 +101,17 @@ module Toys
101
101
  ##
102
102
  # Add a configuration file/directory to the loader.
103
103
  #
104
- # @param [String,Array<String>] path One or more paths to add.
104
+ # @param [String,Array<String>] paths One or more paths to add.
105
105
  # @param [Boolean] high_priority If true, add this path at the top of the
106
106
  # priority list. Defaults to false, indicating the new path should be
107
107
  # at the bottom of the priority list.
108
108
  #
109
- def add_path(path, high_priority: false)
110
- paths = Array(path)
109
+ def add_path(paths, high_priority: false)
110
+ paths = Array(paths)
111
111
  priority = high_priority ? (@max_priority += 1) : (@min_priority -= 1)
112
- paths.each do |p|
113
- @worklist << [:file, check_path(p), [], @empty_data_finder, priority]
112
+ paths.each do |path|
113
+ source = Definition::SourceInfo.create_path_root(path)
114
+ @worklist << [source, [], priority]
114
115
  end
115
116
  self
116
117
  end
@@ -121,14 +122,15 @@ module Toys
121
122
  # @param [Boolean] high_priority If true, add this block at the top of the
122
123
  # priority list. Defaults to false, indicating the block should be at
123
124
  # the bottom of the priority list.
124
- # @param [String] path The "path" that will be shown in documentation for
125
- # tools defined in this block. If omitted, a default unique string will
126
- # be generated.
125
+ # @param [String] name The source name that will be shown in documentation
126
+ # for tools defined in this block. If omitted, a default unique string
127
+ # will be generated.
127
128
  #
128
- def add_block(high_priority: false, path: nil, &block)
129
- path ||= "(Block #{block.object_id})"
129
+ def add_block(high_priority: false, name: nil, &block)
130
+ name ||= "(Code block #{block.object_id})"
130
131
  priority = high_priority ? (@max_priority += 1) : (@min_priority -= 1)
131
- @worklist << [block, path, [], @empty_data_finder, priority]
132
+ source = Definition::SourceInfo.create_proc_root(block, name)
133
+ @worklist << [source, [], priority]
132
134
  self
133
135
  end
134
136
 
@@ -173,9 +175,11 @@ module Toys
173
175
  # @param [Array<String>] words The name of the parent tool
174
176
  # @param [Boolean] recursive If true, return all subtools recursively
175
177
  # rather than just the immediate children (the default)
178
+ # @param [Boolean] include_hidden If true, include hidden subtools,
179
+ # e.g. names beginning with underscores.
176
180
  # @return [Array<Toys::Definition::Tool,Toys::Definition::Alias>]
177
181
  #
178
- def list_subtools(words, recursive: false)
182
+ def list_subtools(words, recursive: false, include_hidden: false)
179
183
  load_for_prefix(words)
180
184
  found_tools = []
181
185
  len = words.length
@@ -190,6 +194,7 @@ module Toys
190
194
  found_tools << tool unless tool.nil?
191
195
  end
192
196
  sort_tools_by_name(found_tools)
197
+ include_hidden ? found_tools : filter_hidden_subtools(found_tools)
193
198
  end
194
199
 
195
200
  ##
@@ -309,30 +314,14 @@ module Toys
309
314
  end
310
315
 
311
316
  ##
312
- # Load configuration from the given path.
317
+ # Load configuration from the given path. This is called from the `load`
318
+ # directive in the DSL.
313
319
  #
314
320
  # @private
315
321
  #
316
- def load_path(path, words, remaining_words, priority)
317
- load_validated_path(check_path(path), words, remaining_words, @empty_data_finder, priority)
318
- end
319
-
320
- ##
321
- # Load configuration from the given proc.
322
- #
323
- # @private
324
- #
325
- def load_proc(proc, words, remaining_words, priority, path)
326
- if remaining_words
327
- tool_class = get_tool_definition(words, priority).tool_class
328
- ::Toys::DSL::Tool.prepare(tool_class, remaining_words, path, @empty_data_finder) do
329
- ::Toys::ContextualError.capture("Error while loading Toys config!") do
330
- tool_class.class_eval(&proc)
331
- end
332
- end
333
- else
334
- @worklist << [proc, path, words, @empty_data_finder, priority]
335
- end
322
+ def load_path(parent_source, path, words, remaining_words, priority)
323
+ source = parent_source.absolute_child(path)
324
+ load_validated_path(source, words, remaining_words, priority)
336
325
  end
337
326
 
338
327
  ##
@@ -441,59 +430,71 @@ module Toys
441
430
  def load_for_prefix(prefix)
442
431
  cur_worklist = @worklist
443
432
  @worklist = []
444
- cur_worklist.each do |source, path, words, data_finder, priority|
433
+ cur_worklist.each do |source, words, priority|
445
434
  remaining_words = calc_remaining_words(prefix, words)
446
- if source.respond_to?(:call)
447
- load_proc(source, words, remaining_words, priority, path)
448
- elsif source == :file
449
- load_validated_path(path, words, remaining_words, data_finder, priority)
435
+ if source.source_proc
436
+ load_proc(source, words, remaining_words, priority)
437
+ elsif source.source_path
438
+ load_validated_path(source, words, remaining_words, priority)
439
+ end
440
+ end
441
+ end
442
+
443
+ def load_proc(source, words, remaining_words, priority)
444
+ if remaining_words
445
+ tool_class = get_tool_definition(words, priority).tool_class
446
+ DSL::Tool.prepare(tool_class, remaining_words, source) do
447
+ ContextualError.capture("Error while loading Toys config!") do
448
+ tool_class.class_eval(&source.source_proc)
449
+ end
450
450
  end
451
+ else
452
+ @worklist << [source, words, priority]
451
453
  end
452
454
  end
453
455
 
454
- def load_validated_path(path, words, remaining_words, data_finder, priority)
456
+ def load_validated_path(source, words, remaining_words, priority)
455
457
  if remaining_words
456
- load_relevant_path(path, words, remaining_words, data_finder, priority)
458
+ load_relevant_path(source, words, remaining_words, priority)
457
459
  else
458
- @worklist << [:file, path, words, data_finder, priority]
460
+ @worklist << [source, words, priority]
459
461
  end
460
462
  end
461
463
 
462
- def load_relevant_path(path, words, remaining_words, data_finder, priority)
463
- if ::File.extname(path) == ".rb"
464
+ def load_relevant_path(source, words, remaining_words, priority)
465
+ if source.source_type == :file
464
466
  tool_class = get_tool_definition(words, priority).tool_class
465
- InputFile.evaluate(tool_class, remaining_words, path, data_finder)
467
+ InputFile.evaluate(tool_class, remaining_words, source)
466
468
  else
467
- do_preload(path)
468
- data_finder = data_finder.finder_for(path)
469
- load_index_in(path, words, remaining_words, data_finder, priority)
470
- ::Dir.entries(path).each do |child|
471
- load_child_in(path, child, words, remaining_words, data_finder, priority)
469
+ do_preload(source.source_path)
470
+ load_index_in(source, words, remaining_words, priority)
471
+ ::Dir.entries(source.source_path).each do |child|
472
+ load_child_in(source, child, words, remaining_words, priority)
472
473
  end
473
474
  end
474
475
  end
475
476
 
476
- def load_index_in(path, words, remaining_words, data_finder, priority)
477
+ def load_index_in(source, words, remaining_words, priority)
477
478
  return unless @index_file_name
478
- index_path = ::File.join(path, @index_file_name)
479
- index_path = check_path(index_path, type: :file, lenient: true)
480
- load_relevant_path(index_path, words, remaining_words, data_finder, priority) if index_path
479
+ index_source = source.relative_child(@index_file_name, @data_directory_name)
480
+ load_relevant_path(index_source, words, remaining_words, priority) if index_source
481
481
  end
482
482
 
483
- def load_child_in(path, child, words, remaining_words, data_finder, priority)
484
- return if child.start_with?(".")
485
- return if child == @index_file_name
486
- child_path = check_path(::File.join(path, child))
483
+ def load_child_in(source, child, words, remaining_words, priority)
484
+ return if child.start_with?(".") || child == @index_file_name ||
485
+ child == @preload_file_name || child == @preload_directory_name ||
486
+ child == @data_directory_name
487
+ child_source = source.relative_child(child, @data_directory_name)
487
488
  child_word = ::File.basename(child, ".rb")
488
489
  next_words = words + [child_word]
489
490
  next_remaining = Loader.next_remaining_words(remaining_words, child_word)
490
- load_validated_path(child_path, next_words, next_remaining, data_finder, priority)
491
+ load_validated_path(child_source, next_words, next_remaining, priority)
491
492
  end
492
493
 
493
494
  def do_preload(path)
494
495
  if @preload_file_name
495
496
  preload_file = ::File.join(path, @preload_file_name)
496
- if !::File.directory?(preload_file) && ::File.readable?(preload_file)
497
+ if ::File.file?(preload_file) && ::File.readable?(preload_file)
497
498
  require preload_file
498
499
  end
499
500
  end
@@ -501,37 +502,17 @@ module Toys
501
502
  preload_dir = ::File.join(path, @preload_directory_name)
502
503
  if ::File.directory?(preload_dir) && ::File.readable?(preload_dir)
503
504
  ::Dir.entries(preload_dir).each do |child|
505
+ next unless ::File.extname(child) == ".rb"
504
506
  preload_file = ::File.join(preload_dir, child)
505
- if !::File.directory?(preload_file) && ::File.readable?(preload_file)
506
- require preload_file
507
- end
507
+ next if !::File.file?(preload_file) || !::File.readable?(preload_file)
508
+ require preload_file
508
509
  end
509
510
  end
510
511
  end
511
512
  end
512
513
 
513
- def check_path(path, lenient: false, type: nil)
514
- path = ::File.expand_path(path)
515
- type ||= ::File.extname(path) == ".rb" ? :file : :dir
516
- case type
517
- when :file
518
- if ::File.directory?(path) || !::File.readable?(path)
519
- return nil if lenient
520
- raise LoaderError, "Cannot read file #{path}"
521
- end
522
- when :dir
523
- if !::File.directory?(path) || !::File.readable?(path)
524
- return nil if lenient
525
- raise LoaderError, "Cannot read directory #{path}"
526
- end
527
- else
528
- raise ::ArgumentError, "Illegal type #{type}"
529
- end
530
- path
531
- end
532
-
533
514
  def sort_tools_by_name(tools)
534
- tools.sort do |a, b|
515
+ tools.sort! do |a, b|
535
516
  a = a.full_name
536
517
  b = b.full_name
537
518
  while !a.empty? && !b.empty? && a.first == b.first
@@ -542,6 +523,19 @@ module Toys
542
523
  end
543
524
  end
544
525
 
526
+ def filter_hidden_subtools(tools)
527
+ result = []
528
+ tools.each_with_index do |tool, index|
529
+ next if tool.full_name.any? { |n| n.start_with?("_") }
530
+ unless tool.runnable?
531
+ next_tool = tools[index + 1]
532
+ next if next_tool && next_tool.full_name.slice(0..-2) == tool.full_name
533
+ end
534
+ result << tool
535
+ end
536
+ result
537
+ end
538
+
545
539
  def calc_remaining_words(words1, words2)
546
540
  index = 0
547
541
  lengths = [words1.length, words2.length]
@@ -79,6 +79,7 @@ module Toys
79
79
  def create_data(args, base_verbosity)
80
80
  data = @tool_definition.default_data.dup
81
81
  data[Tool::Keys::TOOL_DEFINITION] = @tool_definition
82
+ data[Tool::Keys::TOOL_SOURCE] = @tool_definition.source_info
82
83
  data[Tool::Keys::TOOL_NAME] = @tool_definition.full_name
83
84
  data[Tool::Keys::VERBOSITY] = base_verbosity
84
85
  data[Tool::Keys::ARGS] = args
@@ -74,6 +74,12 @@ module Toys
74
74
  #
75
75
  DEFAULT_SEARCH_FLAGS = ["-s WORD", "--search=WORD"].freeze
76
76
 
77
+ ##
78
+ # Default show-all-subtools flags
79
+ # @return [Array<String>]
80
+ #
81
+ DEFAULT_SHOW_ALL_SUBTOOLS_FLAGS = ["--all"].freeze
82
+
77
83
  ##
78
84
  # Key set when the show help flag is present
79
85
  # @return [Object]
@@ -104,6 +110,12 @@ module Toys
104
110
  #
105
111
  SEARCH_STRING_KEY = Object.new.freeze
106
112
 
113
+ ##
114
+ # Key for the show-all-subtools setting
115
+ # @return [Object]
116
+ #
117
+ SHOW_ALL_SUBTOOLS_KEY = Object.new.freeze
118
+
107
119
  ##
108
120
  # Key for the tool name
109
121
  # @return [Object]
@@ -155,8 +167,19 @@ module Toys
155
167
  # * The `false` value for no flags. (Default)
156
168
  # * A proc that takes a tool and returns any of the above.
157
169
  #
170
+ # @param [Boolean,Array<String>,Proc] show_all_subtools_flags Specify
171
+ # flags to show all subtools, including hidden tools and non-runnable
172
+ # namespaces. The value may be any of the following:
173
+ #
174
+ # * An array of flags.
175
+ # * The `true` value to use {DEFAULT_SHOW_ALL_SUBTOOLS_FLAGS}.
176
+ # * The `false` value for no flags. (Default)
177
+ # * A proc that takes a tool and returns any of the above.
178
+ #
158
179
  # @param [Boolean] default_recursive Whether to search recursively for
159
180
  # subtools by default. Default is `false`.
181
+ # @param [Boolean] default_show_all_subtools Whether to show all subtools
182
+ # by default. Default is `false`.
160
183
  # @param [Boolean] fallback_execution Cause the tool to display its own
161
184
  # help text if it is not otherwise runnable. This is mostly useful
162
185
  # for namespaces, which have children are not runnable. Default is
@@ -179,7 +202,9 @@ module Toys
179
202
  list_flags: false,
180
203
  recursive_flags: false,
181
204
  search_flags: false,
205
+ show_all_subtools_flags: false,
182
206
  default_recursive: false,
207
+ default_show_all_subtools: false,
183
208
  fallback_execution: false,
184
209
  allow_root_args: false,
185
210
  show_source_path: false,
@@ -191,7 +216,9 @@ module Toys
191
216
  @list_flags = list_flags
192
217
  @recursive_flags = recursive_flags
193
218
  @search_flags = search_flags
219
+ @show_all_subtools_flags = show_all_subtools_flags
194
220
  @default_recursive = default_recursive ? true : false
221
+ @default_show_all_subtools = default_show_all_subtools ? true : false
195
222
  @fallback_execution = fallback_execution
196
223
  @allow_root_args = allow_root_args
197
224
  @show_source_path = show_source_path
@@ -212,6 +239,7 @@ module Toys
212
239
  if (!help_flags.empty? || !list_flags.empty? || @fallback_execution) && has_subtools
213
240
  add_recursive_flags(tool_definition)
214
241
  add_search_flags(tool_definition)
242
+ add_show_all_subtools_flags(tool_definition)
215
243
  end
216
244
  if !help_flags.empty? || !usage_flags.empty? || !list_flags.empty?
217
245
  add_root_args(tool_definition)
@@ -223,16 +251,18 @@ module Toys
223
251
  ##
224
252
  # Display help text if requested.
225
253
  #
226
- def run(tool)
254
+ def run(tool) # rubocop:disable Metrics/AbcSize
227
255
  if tool[SHOW_USAGE_KEY]
228
256
  terminal.puts(get_help_text(tool).usage_string(wrap_width: terminal.width))
229
257
  elsif tool[SHOW_LIST_KEY]
230
258
  terminal.puts(get_help_text(tool).list_string(recursive: tool[RECURSIVE_SUBTOOLS_KEY],
231
259
  search: tool[SEARCH_STRING_KEY],
260
+ include_hidden: tool[SHOW_ALL_SUBTOOLS_KEY],
232
261
  wrap_width: terminal.width))
233
262
  elsif should_show_help(tool)
234
263
  output_help(get_help_text(tool).help_string(recursive: tool[RECURSIVE_SUBTOOLS_KEY],
235
264
  search: tool[SEARCH_STRING_KEY],
265
+ include_hidden: tool[SHOW_ALL_SUBTOOLS_KEY],
236
266
  show_source_path: @show_source_path,
237
267
  wrap_width: terminal.width))
238
268
  else
@@ -288,67 +318,82 @@ module Toys
288
318
  end
289
319
 
290
320
  def add_help_flags(tool_definition)
291
- help_flags = resolve_flags_spec(@help_flags, tool_definition, DEFAULT_HELP_FLAGS)
292
- unless help_flags.empty?
321
+ flags = resolve_flags_spec(@help_flags, tool_definition, DEFAULT_HELP_FLAGS)
322
+ unless flags.empty?
293
323
  tool_definition.add_flag(
294
- SHOW_HELP_KEY, help_flags,
324
+ SHOW_HELP_KEY, flags,
295
325
  report_collisions: false,
296
326
  desc: "Display help for this tool"
297
327
  )
298
328
  end
299
- help_flags
329
+ flags
300
330
  end
301
331
 
302
332
  def add_usage_flags(tool_definition)
303
- usage_flags = resolve_flags_spec(@usage_flags, tool_definition, DEFAULT_USAGE_FLAGS)
304
- unless usage_flags.empty?
333
+ flags = resolve_flags_spec(@usage_flags, tool_definition, DEFAULT_USAGE_FLAGS)
334
+ unless flags.empty?
305
335
  tool_definition.add_flag(
306
- SHOW_USAGE_KEY, usage_flags,
336
+ SHOW_USAGE_KEY, flags,
307
337
  report_collisions: false,
308
338
  desc: "Display a brief usage string for this tool"
309
339
  )
310
340
  end
311
- usage_flags
341
+ flags
312
342
  end
313
343
 
314
344
  def add_list_flags(tool_definition)
315
- list_flags = resolve_flags_spec(@list_flags, tool_definition, DEFAULT_LIST_FLAGS)
316
- unless list_flags.empty?
345
+ flags = resolve_flags_spec(@list_flags, tool_definition, DEFAULT_LIST_FLAGS)
346
+ unless flags.empty?
317
347
  tool_definition.add_flag(
318
- SHOW_LIST_KEY, list_flags,
348
+ SHOW_LIST_KEY, flags,
319
349
  report_collisions: false,
320
350
  desc: "List the subtools under this tool"
321
351
  )
322
352
  end
323
- list_flags
353
+ flags
324
354
  end
325
355
 
326
356
  def add_recursive_flags(tool_definition)
327
- recursive_flags = resolve_flags_spec(@recursive_flags, tool_definition,
328
- DEFAULT_RECURSIVE_FLAGS)
329
- if recursive_flags.empty?
357
+ flags = resolve_flags_spec(@recursive_flags, tool_definition, DEFAULT_RECURSIVE_FLAGS)
358
+ if flags.empty?
330
359
  tool_definition.default_data[RECURSIVE_SUBTOOLS_KEY] = @default_recursive
331
360
  else
332
361
  tool_definition.add_flag(
333
- RECURSIVE_SUBTOOLS_KEY, recursive_flags,
362
+ RECURSIVE_SUBTOOLS_KEY, flags,
334
363
  report_collisions: false, default: @default_recursive,
335
364
  desc: "List all subtools recursively when displaying help" \
336
365
  " (default is #{@default_recursive})"
337
366
  )
338
367
  end
339
- recursive_flags
368
+ flags
340
369
  end
341
370
 
342
371
  def add_search_flags(tool_definition)
343
- search_flags = resolve_flags_spec(@search_flags, tool_definition, DEFAULT_SEARCH_FLAGS)
344
- unless search_flags.empty?
372
+ flags = resolve_flags_spec(@search_flags, tool_definition, DEFAULT_SEARCH_FLAGS)
373
+ unless flags.empty?
345
374
  tool_definition.add_flag(
346
- SEARCH_STRING_KEY, search_flags,
375
+ SEARCH_STRING_KEY, flags,
347
376
  report_collisions: false,
348
377
  desc: "Search subtools for the given regular expression when displaying help"
349
378
  )
350
379
  end
351
- search_flags
380
+ flags
381
+ end
382
+
383
+ def add_show_all_subtools_flags(tool_definition)
384
+ flags = resolve_flags_spec(@show_all_subtools_flags, tool_definition,
385
+ DEFAULT_SHOW_ALL_SUBTOOLS_FLAGS)
386
+ if flags.empty?
387
+ tool_definition.default_data[SHOW_ALL_SUBTOOLS_KEY] = @default_show_all_subtools
388
+ else
389
+ tool_definition.add_flag(
390
+ SHOW_ALL_SUBTOOLS_KEY, flags,
391
+ report_collisions: false, default: @default_show_all_subtools,
392
+ desc: "List all subtools including hidden subtools and namespaces" \
393
+ " (default is #{@default_show_all_subtools})"
394
+ )
395
+ end
396
+ flags
352
397
  end
353
398
 
354
399
  def add_root_args(tool_definition)
@@ -78,6 +78,13 @@ module Toys
78
78
  #
79
79
  TOOL_DEFINITION = ::Object.new.freeze
80
80
 
81
+ ##
82
+ # Context key for the `Toys::Definition::SourceInfo` describing the
83
+ # source of this tool.
84
+ # @return [Object]
85
+ #
86
+ TOOL_SOURCE = ::Object.new.freeze
87
+
81
88
  ##
82
89
  # Context key for the full name of the tool being executed. Value is an
83
90
  # array of strings.
@@ -159,6 +166,14 @@ module Toys
159
166
  @__data[Keys::TOOL_DEFINITION]
160
167
  end
161
168
 
169
+ ##
170
+ # Return the source of the tool being executed.
171
+ # @return [Toys::Definition::SourceInfo]
172
+ #
173
+ def tool_source
174
+ @__data[Keys::TOOL_SOURCE]
175
+ end
176
+
162
177
  ##
163
178
  # Return the name of the tool being executed, as an array of strings.
164
179
  # @return [Array[String]]
@@ -268,7 +283,20 @@ module Toys
268
283
  # @return [String,nil] Absolute path of the result, or nil if not found.
269
284
  #
270
285
  def find_data(path, type: nil)
271
- @__data[Keys::TOOL_DEFINITION].find_data(path, type: type)
286
+ @__data[Keys::TOOL_SOURCE].find_data(path, type: type)
287
+ end
288
+
289
+ ##
290
+ # Return the context directory for this tool. Generally, this defaults
291
+ # to the directory containing the toys config directory structure being
292
+ # read, but it may be changed by setting a different context directory
293
+ # for the tool.
294
+ # May return nil if there is no context.
295
+ #
296
+ # @return [String,nil] Context directory
297
+ #
298
+ def context_directory
299
+ @__data[Keys::TOOL_DEFINITION].context_directory
272
300
  end
273
301
 
274
302
  ##
@@ -83,7 +83,9 @@ module Toys
83
83
  # Generate a short usage string.
84
84
  #
85
85
  # @param [Boolean] recursive If true, and the tool is a namespace,
86
- # display all subcommands recursively. Defaults to false.
86
+ # display all subtools recursively. Defaults to false.
87
+ # @param [Boolean] include_hidden Include hidden subtools (i.e. whose
88
+ # names begin with underscore.) Default is false.
87
89
  # @param [Integer] left_column_width Width of the first column. Default
88
90
  # is {DEFAULT_LEFT_COLUMN_WIDTH}.
89
91
  # @param [Integer] indent Indent width. Default is {DEFAULT_INDENT}.
@@ -92,10 +94,11 @@ module Toys
92
94
  #
93
95
  # @return [String] A usage string.
94
96
  #
95
- def usage_string(recursive: false, left_column_width: nil, indent: nil, wrap_width: nil)
97
+ def usage_string(recursive: false, include_hidden: false,
98
+ left_column_width: nil, indent: nil, wrap_width: nil)
96
99
  left_column_width ||= DEFAULT_LEFT_COLUMN_WIDTH
97
100
  indent ||= DEFAULT_INDENT
98
- subtools = find_subtools(recursive, nil)
101
+ subtools = find_subtools(recursive, nil, include_hidden)
99
102
  assembler = UsageStringAssembler.new(@tool, @binary_name, subtools,
100
103
  indent, left_column_width, wrap_width)
101
104
  assembler.result
@@ -105,9 +108,11 @@ module Toys
105
108
  # Generate a long help string.
106
109
  #
107
110
  # @param [Boolean] recursive If true, and the tool is a namespace,
108
- # display all subcommands recursively. Defaults to false.
111
+ # display all subtools recursively. Defaults to false.
109
112
  # @param [String,nil] search An optional string to search for when
110
- # listing subcommands. Defaults to `nil` which finds all subcommands.
113
+ # listing subtools. Defaults to `nil` which finds all subtools.
114
+ # @param [Boolean] include_hidden Include hidden subtools (i.e. whose
115
+ # names begin with underscore.) Default is false.
111
116
  # @param [Boolean] show_source_path If true, shows the source path
112
117
  # section. Defaults to false.
113
118
  # @param [Integer] indent Indent width. Default is {DEFAULT_INDENT}.
@@ -119,11 +124,12 @@ module Toys
119
124
  #
120
125
  # @return [String] A usage string.
121
126
  #
122
- def help_string(recursive: false, search: nil, show_source_path: false,
127
+ def help_string(recursive: false, search: nil, include_hidden: false,
128
+ show_source_path: false,
123
129
  indent: nil, indent2: nil, wrap_width: nil, styled: true)
124
130
  indent ||= DEFAULT_INDENT
125
131
  indent2 ||= DEFAULT_INDENT
126
- subtools = find_subtools(recursive, search)
132
+ subtools = find_subtools(recursive, search, include_hidden)
127
133
  assembler = HelpStringAssembler.new(@tool, @binary_name, subtools, search, show_source_path,
128
134
  indent, indent2, wrap_width, styled)
129
135
  assembler.result
@@ -133,9 +139,11 @@ module Toys
133
139
  # Generate a subtool list string.
134
140
  #
135
141
  # @param [Boolean] recursive If true, and the tool is a namespace,
136
- # display all subcommands recursively. Defaults to false.
142
+ # display all subtools recursively. Defaults to false.
137
143
  # @param [String,nil] search An optional string to search for when
138
- # listing subcommands. Defaults to `nil` which finds all subcommands.
144
+ # listing subtools. Defaults to `nil` which finds all subtools.
145
+ # @param [Boolean] include_hidden Include hidden subtools (i.e. whose
146
+ # names begin with underscore.) Default is false.
139
147
  # @param [Integer] indent Indent width. Default is {DEFAULT_INDENT}.
140
148
  # @param [Integer,nil] wrap_width Wrap width of the column, or `nil` to
141
149
  # disable wrap. Default is `nil`.
@@ -143,10 +151,10 @@ module Toys
143
151
  #
144
152
  # @return [String] A usage string.
145
153
  #
146
- def list_string(recursive: false, search: nil,
154
+ def list_string(recursive: false, search: nil, include_hidden: false,
147
155
  indent: nil, wrap_width: nil, styled: true)
148
156
  indent ||= DEFAULT_INDENT
149
- subtools = find_subtools(recursive, search)
157
+ subtools = find_subtools(recursive, search, include_hidden)
150
158
  assembler = ListStringAssembler.new(@tool, subtools, recursive, search,
151
159
  indent, wrap_width, styled)
152
160
  assembler.result
@@ -154,8 +162,9 @@ module Toys
154
162
 
155
163
  private
156
164
 
157
- def find_subtools(recursive, search)
158
- subtools = @loader.list_subtools(@tool.full_name, recursive: recursive)
165
+ def find_subtools(recursive, search, include_hidden)
166
+ subtools = @loader.list_subtools(@tool.full_name,
167
+ recursive: recursive, include_hidden: include_hidden)
159
168
  return subtools if search.nil? || search.empty?
160
169
  regex = ::Regexp.new(search, ::Regexp::IGNORECASE)
161
170
  subtools.find_all do |tool|
@@ -387,10 +396,10 @@ module Toys
387
396
  end
388
397
 
389
398
  def add_source_section
390
- return unless @tool.source_path && @show_source_path
399
+ return unless @show_source_path && @tool.source_info&.source_name
391
400
  @lines << ""
392
401
  @lines << bold("SOURCE")
393
- @lines << indent_str("Defined in #{@tool.source_path}")
402
+ @lines << indent_str("Defined in #{@tool.source_info.source_name}")
394
403
  end
395
404
 
396
405
  def add_description_section
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: toys-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Azuma
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-10-08 00:00:00.000000000 Z
11
+ date: 2018-10-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: highline
@@ -86,14 +86,14 @@ dependencies:
86
86
  requirements:
87
87
  - - "~>"
88
88
  - !ruby/object:Gem::Version
89
- version: 0.59.1
89
+ version: 0.59.2
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
- version: 0.59.1
96
+ version: 0.59.2
97
97
  - !ruby/object:Gem::Dependency
98
98
  name: yard
99
99
  requirement: !ruby/object:Gem::Requirement
@@ -127,8 +127,8 @@ files:
127
127
  - lib/toys/definition/acceptor.rb
128
128
  - lib/toys/definition/alias.rb
129
129
  - lib/toys/definition/arg.rb
130
- - lib/toys/definition/data_finder.rb
131
130
  - lib/toys/definition/flag.rb
131
+ - lib/toys/definition/source_info.rb
132
132
  - lib/toys/definition/tool.rb
133
133
  - lib/toys/dsl/arg.rb
134
134
  - lib/toys/dsl/flag.rb
@@ -1,108 +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 Definition
34
- ##
35
- # Finds data files.
36
- #
37
- class DataFinder
38
- ##
39
- # Create a new finder.
40
- #
41
- # @param [String,nil] data_name Name of the data directory, or nil if
42
- # data directories are disabled.
43
- # @param [Toys::Definition::DataFinder,nil] parent The parent, or nil if
44
- # this is the root.
45
- # @param [String,nil] directory The data directory, or nil for none.
46
- # @private
47
- #
48
- def initialize(data_name, parent, directory)
49
- @data_name = data_name
50
- @parent = parent
51
- @directory = directory
52
- end
53
-
54
- ##
55
- # Create a new finder for the given directory.
56
- #
57
- # @param [String] directory Toys directory path
58
- # @return [Toys::Definition::DataFinder] The finder
59
- #
60
- def finder_for(directory)
61
- return self if @data_name.nil?
62
- directory = ::File.join(directory, @data_name)
63
- return self unless ::File.directory?(directory)
64
- DataFinder.new(@data_name, self, directory)
65
- end
66
-
67
- ##
68
- # Return the absolute path to the given data file or directory.
69
- #
70
- # @param [String] path The relative path to find
71
- # @param [nil,:file,:directory] type Type of file system object to find,
72
- # or nil to return any type.
73
- # @return [String,nil] Absolute path of the result, or nil if not found.
74
- #
75
- def find_data(path, type: nil)
76
- return nil if @directory.nil?
77
- full_path = ::File.join(@directory, path)
78
- case type
79
- when :file
80
- return full_path if ::File.file?(full_path)
81
- when :directory
82
- return full_path if ::File.directory?(full_path)
83
- else
84
- return full_path if ::File.readable?(full_path)
85
- end
86
- @parent.find_data(path, type: type)
87
- end
88
-
89
- ##
90
- # Create an empty finder.
91
- #
92
- # @param [String,nil] data_name Name of the data directory, or nil if
93
- # data directories are disabled.
94
- # @return [Toys::Definition::DataFinder]
95
- #
96
- def self.create_empty(data_name)
97
- new(data_name, nil, nil)
98
- end
99
-
100
- ##
101
- # A default empty finder.
102
- #
103
- # @return [Toys::Definition::DataFinder]
104
- #
105
- EMPTY = create_empty(nil)
106
- end
107
- end
108
- end