toys-core 0.4.5 → 0.5.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: 7e238ba243a2e72d0c04df73414040baf052dae6179f6f81030e5bf96b1bc06a
4
- data.tar.gz: 4b711336fca9d960390e523741ddf800674e7657da1677a884679e28fbe9c6f1
3
+ metadata.gz: bd284ba44663511c6004cf0d742c4e1b5a703cbe596fd6612350be3b316ec60f
4
+ data.tar.gz: 9fdc655c88053d669727399c4c4c7321d4d2ececd579c9420bcddb1f0a40d65d
5
5
  SHA512:
6
- metadata.gz: 470b8f9c78917c60db54df07c54f43a14b7626cf31f22ab7810756103af41482f34a66a92b09d82587e5ac09f3774f7446275a0f2f131fab5f4a7a0eec6903dc
7
- data.tar.gz: 0eb481163a3476ad7fd94146f09ee0b8acc4a02b1a0aadd95d559100756e6793e913091588e35523073e41152b464a5dbb48aba37c86613a34a483afbb50cbe3
6
+ metadata.gz: ccdfadc2660d171d39776f63e708839edfaef4b63e21a2ddc39feb6456763a0d54fa27cc611cd2c9180210c291fb1f2510fa925b9f9ebea98f7e3794d42ca00e
7
+ data.tar.gz: e8d68670c340ac2a2b51a83a1201334ccbbda1a69464a25b9a376badfaf891895ffcc189ad54afbd1a2901b4673ded5fcb61296065ed205e98bfd05dbf4dc810
data/.yardopts CHANGED
@@ -1,6 +1,7 @@
1
1
  --no-private
2
2
  --title=Toys Core
3
3
  --markup=markdown
4
+ --markup-provider redcarpet
4
5
  --main=README.md
5
6
  ./lib/**/*.rb
6
7
  -
@@ -1,5 +1,15 @@
1
1
  # Release History
2
2
 
3
+ ### 0.5.0 / 2018-10-07
4
+
5
+ * FIXED: Template instantiation was failing if the hosting tool was priority-masked.
6
+ * ADDED: Several additional characters can optionally be used as tool path delimiters.
7
+ * ADDED: Support for preloaded files and directories
8
+ * ADDED: Support for data directories
9
+ * ADDED: Ability to display just the list of subtools of a tool
10
+ * IMPROVED: The tool directive can now take an array as the tool name.
11
+ * IMPROVED: The tool directive can now take an `if_defined` argument.
12
+
3
13
  ### 0.4.5 / 2018-08-05
4
14
 
5
15
  * CHANGED: Dropped preload file feature
@@ -68,6 +68,7 @@ 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"
71
72
  require "toys/definition/flag"
72
73
  require "toys/definition/tool"
73
74
  require "toys/dsl/arg"
@@ -58,9 +58,25 @@ module Toys
58
58
  # loaded first as a standalone configuration file. If not provided,
59
59
  # standalone configuration files are disabled.
60
60
  # The default toys CLI sets this to `".toys.rb"`.
61
+ # @param [String,nil] preload_file_name A file with this name that appears
62
+ # in any configuration directory is preloaded before any tools in that
63
+ # configuration directory are defined.
64
+ # The default toys CLI sets this to `".preload.rb"`.
65
+ # @param [String,nil] preload_directory_name A directory with this name
66
+ # that appears in any configuration directory is searched for Ruby
67
+ # files, which are preloaded before any tools in that configuration
68
+ # directory are defined.
69
+ # The default toys CLI sets this to `".preload"`.
70
+ # @param [String,nil] data_directory_name A directory with this name that
71
+ # appears in any configuration directory is added to the data directory
72
+ # search path for any tool file in that directory.
73
+ # The default toys CLI sets this to `".data"`.
61
74
  # @param [Array] middleware_stack An array of middleware that will be used
62
75
  # by default for all tools loaded by this CLI. If not provided, uses
63
76
  # {Toys::CLI.default_middleware_stack}.
77
+ # @param [String] extra_delimiters A string containing characters that can
78
+ # function as delimiters in a tool name. Defaults to empty. Allowed
79
+ # characters are period, colon, and slash.
64
80
  # @param [Toys::Utils::ModuleLookup] mixin_lookup A lookup for well-known
65
81
  # mixin modules. If not provided, uses
66
82
  # {Toys::CLI.default_mixin_lookup}.
@@ -81,23 +97,30 @@ module Toys
81
97
  # Default is a {Toys::CLI::DefaultErrorHandler} writing to the logger.
82
98
  #
83
99
  def initialize(
84
- binary_name: nil, middleware_stack: nil,
100
+ binary_name: nil, middleware_stack: nil, extra_delimiters: "",
85
101
  config_dir_name: nil, config_file_name: nil, index_file_name: nil,
102
+ preload_file_name: nil, preload_directory_name: nil, data_directory_name: nil,
86
103
  mixin_lookup: nil, middleware_lookup: nil, template_lookup: nil,
87
104
  logger: nil, base_level: nil, error_handler: nil
88
105
  )
89
106
  @logger = logger || self.class.default_logger
90
107
  @base_level = base_level || @logger.level
91
108
  @middleware_stack = middleware_stack || self.class.default_middleware_stack
109
+ @extra_delimiters = extra_delimiters
92
110
  @binary_name = binary_name || ::File.basename($PROGRAM_NAME)
93
111
  @config_dir_name = config_dir_name
94
112
  @config_file_name = config_file_name
95
113
  @index_file_name = index_file_name
114
+ @preload_file_name = preload_file_name
115
+ @preload_directory_name = preload_directory_name
116
+ @data_directory_name = data_directory_name
96
117
  @mixin_lookup = mixin_lookup || self.class.default_mixin_lookup
97
118
  @middleware_lookup = middleware_lookup || self.class.default_middleware_lookup
98
119
  @template_lookup = template_lookup || self.class.default_template_lookup
99
120
  @loader = Loader.new(
100
- index_file_name: index_file_name,
121
+ index_file_name: index_file_name, extra_delimiters: @extra_delimiters,
122
+ preload_directory_name: @preload_directory_name, preload_file_name: @preload_file_name,
123
+ data_directory_name: @data_directory_name,
101
124
  mixin_lookup: @mixin_lookup, template_lookup: @template_lookup,
102
125
  middleware_lookup: @middleware_lookup, middleware_stack: @middleware_stack
103
126
  )
@@ -251,7 +274,11 @@ module Toys
251
274
  config_dir_name: @config_dir_name,
252
275
  config_file_name: @config_file_name,
253
276
  index_file_name: @index_file_name,
277
+ preload_directory_name: @preload_directory_name,
278
+ preload_file_name: @preload_file_name,
279
+ data_directory_name: @data_directory_name,
254
280
  middleware_stack: @middleware_stack,
281
+ extra_delimiters: @extra_delimiters,
255
282
  mixin_lookup: @mixin_lookup,
256
283
  middleware_lookup: @middleware_lookup,
257
284
  template_lookup: @template_lookup,
@@ -34,5 +34,5 @@ module Toys
34
34
  # Current version of Toys core
35
35
  # @return [String]
36
36
  #
37
- CORE_VERSION = "0.4.5"
37
+ CORE_VERSION = "0.5.0"
38
38
  end
@@ -0,0 +1,108 @@
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
@@ -72,11 +72,27 @@ module Toys
72
72
  def initialize(loader, parent, full_name, priority, middleware_stack)
73
73
  @parent = parent
74
74
  @full_name = full_name.dup.freeze
75
- @tool_class = DSL::Tool.new_class(@full_name, priority, loader)
76
75
  @priority = priority
77
76
  @middleware_stack = middleware_stack
78
77
 
78
+ @acceptors = {}
79
+ @mixins = {}
80
+ @templates = {}
81
+
82
+ reset_definition(loader)
83
+ end
84
+
85
+ ##
86
+ # Reset the definition of this tool, deleting all definition data but
87
+ # leaving named acceptors, mixins, and templates intact.
88
+ # Should be called only from the DSL.
89
+ # @private
90
+ #
91
+ def reset_definition(loader)
92
+ @tool_class = DSL::Tool.new_class(@full_name, @priority, loader)
93
+
79
94
  @source_path = nil
95
+ @data_finder = nil
80
96
  @definition_finished = false
81
97
 
82
98
  @desc = Utils::WrappableString.new("")
@@ -86,17 +102,13 @@ module Toys
86
102
  @used_flags = []
87
103
  @initializers = []
88
104
 
89
- @acceptors = {}
90
- @mixins = {}
91
- @templates = {}
92
-
93
105
  @flag_definitions = []
94
106
  @required_arg_definitions = []
95
107
  @optional_arg_definitions = []
96
108
  @remaining_args_definition = nil
97
109
 
98
110
  @disable_argument_parsing = false
99
- @runnable = false
111
+ @includes_modules = false
100
112
  end
101
113
 
102
114
  ##
@@ -209,7 +221,15 @@ module Toys
209
221
  # @return [Boolean]
210
222
  #
211
223
  def runnable?
212
- @runnable
224
+ tool_class.public_instance_methods(false).include?(:run)
225
+ end
226
+
227
+ ##
228
+ # Returns true if this tool has at least one included module.
229
+ # @return [Boolean]
230
+ #
231
+ def includes_modules?
232
+ @includes_modules
213
233
  end
214
234
 
215
235
  ##
@@ -236,7 +256,8 @@ module Toys
236
256
  # @return [Boolean]
237
257
  #
238
258
  def includes_definition?
239
- includes_arguments? || runnable?
259
+ includes_arguments? || runnable? || argument_parsing_disabled? ||
260
+ includes_modules? || includes_description?
240
261
  end
241
262
 
242
263
  ##
@@ -346,14 +367,16 @@ module Toys
346
367
  # already set, raises {Toys::ToolDefinitionError}
347
368
  #
348
369
  # @param [String] path The path to the file defining this tool
370
+ # @param [Toys::Definition::DataFinder] data_finder Data finder
349
371
  #
350
- def lock_source_path(path)
372
+ def lock_source_path(path, data_finder)
351
373
  if source_path && source_path != path
352
374
  raise ToolDefinitionError,
353
375
  "Cannot redefine tool #{display_name.inspect} in #{path}" \
354
376
  " (already defined in #{source_path})"
355
377
  end
356
378
  @source_path = path
379
+ @data_finder = data_finder
357
380
  end
358
381
 
359
382
  ##
@@ -648,12 +671,24 @@ module Toys
648
671
  end
649
672
 
650
673
  ##
651
- # Mark this tool as runnable. Should be called from the DSL only.
674
+ # Find the given data file or directory in this tool's search path.
675
+ #
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.
680
+ #
681
+ def find_data(path, type: nil)
682
+ @data_finder ? @data_finder.find_data(path, type: type) : nil
683
+ end
684
+
685
+ ##
686
+ # Mark this tool as having at least one module included
652
687
  # @private
653
688
  #
654
- def mark_runnable
689
+ def mark_includes_modules
655
690
  check_definition_state
656
- @runnable = true
691
+ @includes_modules = true
657
692
  self
658
693
  end
659
694
 
@@ -687,12 +722,11 @@ module Toys
687
722
  end
688
723
  end
689
724
 
690
- private
691
-
692
- def make_config_proc(middleware, loader, next_config)
693
- proc { middleware.config(self, loader, &next_config) }
694
- end
695
-
725
+ ##
726
+ # Check that the tool can still be defined. Should be called internally
727
+ # or from the DSL only.
728
+ # @private
729
+ #
696
730
  def check_definition_state(is_arg: false)
697
731
  if @definition_finished
698
732
  raise ToolDefinitionError,
@@ -704,6 +738,12 @@ module Toys
704
738
  end
705
739
  self
706
740
  end
741
+
742
+ private
743
+
744
+ def make_config_proc(middleware, loader, next_config)
745
+ proc { middleware.config(self, loader, &next_config) }
746
+ end
707
747
  end
708
748
  end
709
749
  end
@@ -64,9 +64,8 @@ module Toys
64
64
  #
65
65
  module Tool
66
66
  ## @private
67
- def method_added(meth)
68
- cur_tool = DSL::Tool.current_tool(self, true)
69
- cur_tool.mark_runnable if cur_tool && meth == :run
67
+ def method_added(_meth)
68
+ DSL::Tool.current_tool(self, true)&.check_definition_state
70
69
  end
71
70
 
72
71
  ##
@@ -177,18 +176,34 @@ module Toys
177
176
  ##
178
177
  # Create a subtool. You must provide a block defining the subtool.
179
178
  #
180
- # If the subtool is already defined (either as a tool or a namespace), the
181
- # old definition is discarded and replaced with the new definition.
182
- #
183
- # @param [String] word The name of the subtool
179
+ # @param [String,Array<String>] words The name of the subtool
180
+ # @param [:combine,:reset,:ignore] if_defined What to do if a definition
181
+ # already exists for this tool. Possible values are `:combine` (the
182
+ # default) indicating the definition should be combined with the
183
+ # existing definition, `:reset` indicating the earlier definition
184
+ # should be reset and the new definition applied instead, or
185
+ # `:ignore` indicating the new definition should be ignored.
184
186
  # @return [Toys::DSL::Tool] self, for chaining.
185
187
  #
186
- def tool(word, &block)
187
- word = word.to_s
188
- subtool_words = @__words + [word]
189
- next_remaining = Loader.next_remaining_words(@__remaining_words, word)
190
- subtool_class = @__loader.get_tool_definition(subtool_words, @__priority).tool_class
191
- DSL::Tool.prepare(subtool_class, next_remaining, @__path) do
188
+ def tool(words, if_defined: :combine, &block)
189
+ subtool_words = @__words
190
+ next_remaining = @__remaining_words
191
+ Array(words).each do |word|
192
+ word = word.to_s
193
+ subtool_words += [word]
194
+ next_remaining = Loader.next_remaining_words(next_remaining, word)
195
+ end
196
+ subtool = @__loader.get_tool_definition(subtool_words, @__priority)
197
+ if subtool.includes_definition?
198
+ case if_defined
199
+ when :ignore
200
+ return self
201
+ when :reset
202
+ subtool.reset_definition(@__loader)
203
+ end
204
+ end
205
+ subtool_class = subtool.tool_class
206
+ DSL::Tool.prepare(subtool_class, next_remaining, @__path, @__data_finder) do
192
207
  subtool_class.class_eval(&block)
193
208
  end
194
209
  self
@@ -231,8 +246,7 @@ module Toys
231
246
  # @return [Toys::DSL::Tool] self, for chaining.
232
247
  #
233
248
  def expand(template_class, *args)
234
- cur_tool = DSL::Tool.current_tool(self, true)
235
- return self if cur_tool.nil?
249
+ cur_tool = DSL::Tool.current_tool(self, false)
236
250
  name = template_class.to_s
237
251
  if template_class.is_a?(::String)
238
252
  template_class = cur_tool.resolve_template(template_class)
@@ -607,11 +621,14 @@ module Toys
607
621
  if included_modules.include?(mod)
608
622
  raise ToolDefinitionError, "Mixin already included: #{mod.name}"
609
623
  end
610
- if mod.respond_to?(:initialization_callback) && mod.initialization_callback
611
- cur_tool.add_initializer(mod.initialization_callback, *args)
624
+ cur_tool.mark_includes_modules
625
+ if mod.respond_to?(:initialization_callback)
626
+ callback = mod.initialization_callback
627
+ cur_tool.add_initializer(callback, *args) if callback
612
628
  end
613
- if mod.respond_to?(:inclusion_callback) && mod.inclusion_callback
614
- class_exec(*args, &mod.inclusion_callback)
629
+ if mod.respond_to?(:inclusion_callback)
630
+ callback = mod.inclusion_callback
631
+ class_exec(*args, &callback) if callback
615
632
  end
616
633
  super(mod)
617
634
  end
@@ -633,6 +650,18 @@ module Toys
633
650
  super(DSL::Tool.resolve_mixin(mod, cur_tool, @__loader))
634
651
  end
635
652
 
653
+ ##
654
+ # Find the given data path (file or directory)
655
+ #
656
+ # @param [String] path The path to find
657
+ # @param [nil,:file,:directory] type Type of file system object to find,
658
+ # or nil to return any type.
659
+ # @return [String,nil] Absolute path of the result, or nil if not found.
660
+ #
661
+ def find_data(path, type: nil)
662
+ @__data_finder ? @__data_finder.find_data(path, type: type) : nil
663
+ end
664
+
636
665
  ## @private
637
666
  def self.new_class(words, priority, loader)
638
667
  tool_class = ::Class.new(::Toys::Tool)
@@ -642,13 +671,13 @@ module Toys
642
671
  tool_class.instance_variable_set(:@__loader, loader)
643
672
  tool_class.instance_variable_set(:@__remaining_words, nil)
644
673
  tool_class.instance_variable_set(:@__path, nil)
674
+ tool_class.instance_variable_set(:@__data_finder, nil)
645
675
  tool_class
646
676
  end
647
677
 
648
678
  ## @private
649
679
  def self.current_tool(tool_class, activate)
650
680
  memoize_var = activate ? :@__active_tool : :@__cur_tool
651
- path = tool_class.instance_variable_get(:@__path)
652
681
  if tool_class.instance_variable_defined?(memoize_var)
653
682
  cur_tool = tool_class.instance_variable_get(memoize_var)
654
683
  else
@@ -667,18 +696,24 @@ module Toys
667
696
  end
668
697
  tool_class.instance_variable_set(memoize_var, cur_tool)
669
698
  end
670
- cur_tool.lock_source_path(path) if cur_tool && activate
699
+ 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)
703
+ end
671
704
  cur_tool
672
705
  end
673
706
 
674
707
  ## @private
675
- def self.prepare(tool_class, remaining_words, path)
708
+ def self.prepare(tool_class, remaining_words, path, data_finder)
676
709
  tool_class.instance_variable_set(:@__remaining_words, remaining_words)
677
710
  tool_class.instance_variable_set(:@__path, path)
711
+ tool_class.instance_variable_set(:@__data_finder, data_finder)
678
712
  yield
679
713
  ensure
680
714
  tool_class.instance_variable_set(:@__remaining_words, nil)
681
715
  tool_class.instance_variable_set(:@__path, nil)
716
+ tool_class.instance_variable_set(:@__data_finder, nil)
682
717
  end
683
718
 
684
719
  ## @private
@@ -40,7 +40,7 @@ module Toys::InputFile # rubocop:disable Style/ClassAndModuleChildren
40
40
  end
41
41
 
42
42
  ## @private
43
- def self.evaluate(tool_class, remaining_words, path)
43
+ def self.evaluate(tool_class, remaining_words, path, data_finder)
44
44
  namespace = ::Module.new
45
45
  namespace.module_eval do
46
46
  include ::Toys::Tool::Keys
@@ -51,7 +51,7 @@ module Toys::InputFile # rubocop:disable Style/ClassAndModuleChildren
51
51
  str = build_eval_string(name, ::IO.read(path))
52
52
  if str
53
53
  const_set(name, namespace)
54
- ::Toys::DSL::Tool.prepare(tool_class, remaining_words, path) do
54
+ ::Toys::DSL::Tool.prepare(tool_class, remaining_words, path, data_finder) do
55
55
  ::Toys::ContextualError.capture_path("Error while loading Toys config!", path) do
56
56
  # rubocop:disable Security/Eval
57
57
  eval(str, __binding, path, 0)
@@ -34,6 +34,8 @@ module Toys
34
34
  # The Loader service loads tools from configuration files, and finds the
35
35
  # appropriate tool given a set of command line arguments.
36
36
  #
37
+ # This class is not thread-safe.
38
+ #
37
39
  class Loader
38
40
  ## @private
39
41
  ToolData = ::Struct.new(:definitions, :top_priority, :active_priority) do
@@ -53,8 +55,21 @@ module Toys
53
55
  # in any configuration directory (not just a toplevel directory) is
54
56
  # loaded first as a standalone configuration file. If not provided,
55
57
  # standalone configuration files are disabled.
58
+ # @param [String,nil] preload_file_name A file with this name that appears
59
+ # in any configuration directory is preloaded before any tools in that
60
+ # configuration directory are defined.
61
+ # @param [String,nil] preload_directory_name A directory with this name
62
+ # that appears in any configuration directory is searched for Ruby
63
+ # files, which are preloaded before any tools in that configuration
64
+ # directory are defined.
65
+ # @param [String,nil] data_directory_name A directory with this name that
66
+ # appears in any configuration directory is added to the data directory
67
+ # search path for any tool file in that directory.
56
68
  # @param [Array] middleware_stack An array of middleware that will be used
57
69
  # by default for all tools loaded by this loader.
70
+ # @param [String] extra_delimiters A string containing characters that can
71
+ # function as delimiters in a tool name. Defaults to empty. Allowed
72
+ # characters are period, colon, and slash.
58
73
  # @param [Toys::Utils::ModuleLookup] mixin_lookup A lookup for well-known
59
74
  # mixin modules. Defaults to an empty lookup.
60
75
  # @param [Toys::Utils::ModuleLookup] middleware_lookup A lookup for
@@ -62,7 +77,8 @@ module Toys
62
77
  # @param [Toys::Utils::ModuleLookup] template_lookup A lookup for
63
78
  # well-known template classes. Defaults to an empty lookup.
64
79
  #
65
- def initialize(index_file_name: nil, middleware_stack: [],
80
+ def initialize(index_file_name: nil, preload_directory_name: nil, preload_file_name: nil,
81
+ data_directory_name: nil, middleware_stack: [], extra_delimiters: "",
66
82
  mixin_lookup: nil, middleware_lookup: nil, template_lookup: nil)
67
83
  if index_file_name && ::File.extname(index_file_name) != ".rb"
68
84
  raise ::ArgumentError, "Illegal index file name #{index_file_name.inspect}"
@@ -71,10 +87,14 @@ module Toys
71
87
  @middleware_lookup = middleware_lookup || Utils::ModuleLookup.new
72
88
  @template_lookup = template_lookup || Utils::ModuleLookup.new
73
89
  @index_file_name = index_file_name
90
+ @preload_file_name = preload_file_name
91
+ @preload_directory_name = preload_directory_name
92
+ @empty_data_finder = Definition::DataFinder.create_empty(data_directory_name)
74
93
  @middleware_stack = middleware_stack
75
94
  @worklist = []
76
95
  @tool_data = {}
77
96
  @max_priority = @min_priority = 0
97
+ @extra_delimiters = process_extra_delimiters(extra_delimiters)
78
98
  get_tool_definition([], -999_999)
79
99
  end
80
100
 
@@ -90,7 +110,7 @@ module Toys
90
110
  paths = Array(path)
91
111
  priority = high_priority ? (@max_priority += 1) : (@min_priority -= 1)
92
112
  paths.each do |p|
93
- @worklist << [:file, check_path(p), [], priority]
113
+ @worklist << [:file, check_path(p), [], @empty_data_finder, priority]
94
114
  end
95
115
  self
96
116
  end
@@ -108,7 +128,7 @@ module Toys
108
128
  def add_block(high_priority: false, path: nil, &block)
109
129
  path ||= "(Block #{block.object_id})"
110
130
  priority = high_priority ? (@max_priority += 1) : (@min_priority -= 1)
111
- @worklist << [block, path, [], priority]
131
+ @worklist << [block, path, [], @empty_data_finder, priority]
112
132
  self
113
133
  end
114
134
 
@@ -123,23 +143,23 @@ module Toys
123
143
  # Returns a tuple of the found tool, and the array of remaining arguments
124
144
  # that are not part of the tool name and should be passed as tool args.
125
145
  #
126
- # @param [String] args Command line arguments
146
+ # @param [Array<String>] args Command line arguments
127
147
  # @return [Array(Toys::Definition::Tool,Array<String>)]
128
148
  #
129
149
  def lookup(args)
130
- orig_prefix = args.take_while { |arg| !arg.start_with?("-") }
150
+ orig_prefix, args = find_orig_prefix(args)
131
151
  cur_prefix = orig_prefix
132
152
  loop do
133
153
  load_for_prefix(cur_prefix)
134
- p = orig_prefix
154
+ prefix = orig_prefix
135
155
  loop do
136
- tool_definition = get_active_tool(p, [])
156
+ tool_definition = get_active_tool(prefix, [])
137
157
  if tool_definition
138
158
  finish_definitions_in_tree(tool_definition.full_name)
139
- return [tool_definition, args.slice(p.length..-1)]
159
+ return [tool_definition, args.slice(prefix.length..-1)]
140
160
  end
141
- break if p.empty? || p.length <= cur_prefix.length
142
- p = p.slice(0..-2)
161
+ break if prefix.empty? || prefix.length <= cur_prefix.length
162
+ prefix = prefix.slice(0..-2)
143
163
  end
144
164
  raise "Unexpected error" if cur_prefix.empty?
145
165
  cur_prefix = cur_prefix.slice(0..-2)
@@ -189,13 +209,16 @@ module Toys
189
209
  end
190
210
 
191
211
  ##
192
- # Returns a tool specified by the given words, with the given priority.
193
- # Does not do any loading. If the tool is not present, creates it.
212
+ # Returns the active tool specified by the given words, with the given
213
+ # priority, without doing any loading. If the given priority matches the
214
+ # currently active tool, returns it. If the given priority is lower than
215
+ # the active priority, returns `nil`. If the given priority is higher than
216
+ # the active priority, returns and activates a new tool.
194
217
  #
195
218
  # @param [Array<String>] words The name of the tool.
196
219
  # @param [Integer] priority The priority of the request.
197
220
  # @return [Toys::Definition::Tool,Toys::Definition::Alias,nil] The tool or
198
- # alias, or `nil` if the given priority is insufficient
221
+ # alias, or `nil` if the given priority is insufficient.
199
222
  #
200
223
  # @private
201
224
  #
@@ -259,9 +282,10 @@ module Toys
259
282
  if tool_data.top_priority.nil? || tool_data.top_priority < priority
260
283
  tool_data.top_priority = priority
261
284
  end
262
- middlewares = @middleware_stack.map { |m| resolve_middleware(m) }
263
- tool_data.definitions[priority] ||=
285
+ tool_data.definitions[priority] ||= begin
286
+ middlewares = @middleware_stack.map { |m| resolve_middleware(m) }
264
287
  Definition::Tool.new(self, parent, words, priority, middlewares)
288
+ end
265
289
  end
266
290
 
267
291
  ##
@@ -290,7 +314,7 @@ module Toys
290
314
  # @private
291
315
  #
292
316
  def load_path(path, words, remaining_words, priority)
293
- load_validated_path(check_path(path), words, remaining_words, priority)
317
+ load_validated_path(check_path(path), words, remaining_words, @empty_data_finder, priority)
294
318
  end
295
319
 
296
320
  ##
@@ -301,13 +325,13 @@ module Toys
301
325
  def load_proc(proc, words, remaining_words, priority, path)
302
326
  if remaining_words
303
327
  tool_class = get_tool_definition(words, priority).tool_class
304
- ::Toys::DSL::Tool.prepare(tool_class, remaining_words, path) do
328
+ ::Toys::DSL::Tool.prepare(tool_class, remaining_words, path, @empty_data_finder) do
305
329
  ::Toys::ContextualError.capture("Error while loading Toys config!") do
306
330
  tool_class.class_eval(&proc)
307
331
  end
308
332
  end
309
333
  else
310
- @worklist << [proc, path, words, priority]
334
+ @worklist << [proc, path, words, @empty_data_finder, priority]
311
335
  end
312
336
  end
313
337
 
@@ -328,6 +352,28 @@ module Toys
328
352
 
329
353
  private
330
354
 
355
+ ALLOWED_DELIMITERS = %r{^[\./:]*$}
356
+
357
+ def process_extra_delimiters(input)
358
+ unless ALLOWED_DELIMITERS =~ input
359
+ raise ::ArgumentError, "Illegal delimiters in #{input.inspect}"
360
+ end
361
+ chars = ::Regexp.escape(input.chars.uniq.join)
362
+ chars.empty? ? nil : ::Regexp.new("[#{chars}]")
363
+ end
364
+
365
+ def find_orig_prefix(args)
366
+ if @extra_delimiters
367
+ first_split = (args.first || "").split(@extra_delimiters)
368
+ if first_split.size > 1
369
+ args = first_split + args.slice(1..-1)
370
+ return [first_split, args]
371
+ end
372
+ end
373
+ orig_prefix = args.take_while { |arg| !arg.start_with?("-") }
374
+ [orig_prefix, args]
375
+ end
376
+
331
377
  def get_tool_data(words)
332
378
  @tool_data[words] ||= ToolData.new({}, nil, nil)
333
379
  end
@@ -395,51 +441,73 @@ module Toys
395
441
  def load_for_prefix(prefix)
396
442
  cur_worklist = @worklist
397
443
  @worklist = []
398
- cur_worklist.each do |source, path, words, priority|
444
+ cur_worklist.each do |source, path, words, data_finder, priority|
399
445
  remaining_words = calc_remaining_words(prefix, words)
400
446
  if source.respond_to?(:call)
401
447
  load_proc(source, words, remaining_words, priority, path)
402
448
  elsif source == :file
403
- load_validated_path(path, words, remaining_words, priority)
449
+ load_validated_path(path, words, remaining_words, data_finder, priority)
404
450
  end
405
451
  end
406
452
  end
407
453
 
408
- def load_validated_path(path, words, remaining_words, priority)
454
+ def load_validated_path(path, words, remaining_words, data_finder, priority)
409
455
  if remaining_words
410
- load_relevant_path(path, words, remaining_words, priority)
456
+ load_relevant_path(path, words, remaining_words, data_finder, priority)
411
457
  else
412
- @worklist << [:file, path, words, priority]
458
+ @worklist << [:file, path, words, data_finder, priority]
413
459
  end
414
460
  end
415
461
 
416
- def load_relevant_path(path, words, remaining_words, priority)
462
+ def load_relevant_path(path, words, remaining_words, data_finder, priority)
417
463
  if ::File.extname(path) == ".rb"
418
464
  tool_class = get_tool_definition(words, priority).tool_class
419
- Toys::InputFile.evaluate(tool_class, remaining_words, path)
465
+ InputFile.evaluate(tool_class, remaining_words, path, data_finder)
420
466
  else
421
- load_index_in(path, words, remaining_words, priority)
467
+ do_preload(path)
468
+ data_finder = data_finder.finder_for(path)
469
+ load_index_in(path, words, remaining_words, data_finder, priority)
422
470
  ::Dir.entries(path).each do |child|
423
- load_child_in(path, child, words, remaining_words, priority)
471
+ load_child_in(path, child, words, remaining_words, data_finder, priority)
424
472
  end
425
473
  end
426
474
  end
427
475
 
428
- def load_index_in(path, words, remaining_words, priority)
476
+ def load_index_in(path, words, remaining_words, data_finder, priority)
429
477
  return unless @index_file_name
430
478
  index_path = ::File.join(path, @index_file_name)
431
479
  index_path = check_path(index_path, type: :file, lenient: true)
432
- load_relevant_path(index_path, words, remaining_words, priority) if index_path
480
+ load_relevant_path(index_path, words, remaining_words, data_finder, priority) if index_path
433
481
  end
434
482
 
435
- def load_child_in(path, child, words, remaining_words, priority)
483
+ def load_child_in(path, child, words, remaining_words, data_finder, priority)
436
484
  return if child.start_with?(".")
437
485
  return if child == @index_file_name
438
486
  child_path = check_path(::File.join(path, child))
439
487
  child_word = ::File.basename(child, ".rb")
440
488
  next_words = words + [child_word]
441
489
  next_remaining = Loader.next_remaining_words(remaining_words, child_word)
442
- load_validated_path(child_path, next_words, next_remaining, priority)
490
+ load_validated_path(child_path, next_words, next_remaining, data_finder, priority)
491
+ end
492
+
493
+ def do_preload(path)
494
+ if @preload_file_name
495
+ preload_file = ::File.join(path, @preload_file_name)
496
+ if !::File.directory?(preload_file) && ::File.readable?(preload_file)
497
+ require preload_file
498
+ end
499
+ end
500
+ if @preload_directory_name
501
+ preload_dir = ::File.join(path, @preload_directory_name)
502
+ if ::File.directory?(preload_dir) && ::File.readable?(preload_dir)
503
+ ::Dir.entries(preload_dir).each do |child|
504
+ preload_file = ::File.join(preload_dir, child)
505
+ if !::File.directory?(preload_file) && ::File.readable?(preload_file)
506
+ require preload_file
507
+ end
508
+ end
509
+ end
510
+ end
443
511
  end
444
512
 
445
513
  def check_path(path, lenient: false, type: nil)
@@ -56,6 +56,12 @@ module Toys
56
56
  #
57
57
  DEFAULT_USAGE_FLAGS = ["--usage"].freeze
58
58
 
59
+ ##
60
+ # Default list subtools flags
61
+ # @return [Array<String>]
62
+ #
63
+ DEFAULT_LIST_FLAGS = ["--tools"].freeze
64
+
59
65
  ##
60
66
  # Default recursive flags
61
67
  # @return [Array<String>]
@@ -80,6 +86,12 @@ module Toys
80
86
  #
81
87
  SHOW_USAGE_KEY = Object.new.freeze
82
88
 
89
+ ##
90
+ # Key set when the show subtool list flag is present
91
+ # @return [Object]
92
+ #
93
+ SHOW_LIST_KEY = Object.new.freeze
94
+
83
95
  ##
84
96
  # Key for the recursive setting
85
97
  # @return [Object]
@@ -117,6 +129,14 @@ module Toys
117
129
  # * The `false` value for no flags. (Default)
118
130
  # * A proc that takes a tool and returns any of the above.
119
131
  #
132
+ # @param [Boolean,Array<String>,Proc] list_flags Specify flags to
133
+ # display subtool list. The value may be any of the following:
134
+ #
135
+ # * An array of flags.
136
+ # * The `true` value to use {DEFAULT_LIST_FLAGS}.
137
+ # * The `false` value for no flags. (Default)
138
+ # * A proc that takes a tool and returns any of the above.
139
+ #
120
140
  # @param [Boolean,Array<String>,Proc] recursive_flags Specify flags
121
141
  # to control recursive subtool search. The value may be any of the
122
142
  # following:
@@ -156,6 +176,7 @@ module Toys
156
176
  #
157
177
  def initialize(help_flags: false,
158
178
  usage_flags: false,
179
+ list_flags: false,
159
180
  recursive_flags: false,
160
181
  search_flags: false,
161
182
  default_recursive: false,
@@ -167,6 +188,7 @@ module Toys
167
188
  styled_output: nil)
168
189
  @help_flags = help_flags
169
190
  @usage_flags = usage_flags
191
+ @list_flags = list_flags
170
192
  @recursive_flags = recursive_flags
171
193
  @search_flags = search_flags
172
194
  @default_recursive = default_recursive ? true : false
@@ -183,20 +205,17 @@ module Toys
183
205
  #
184
206
  def config(tool_definition, loader)
185
207
  unless tool_definition.argument_parsing_disabled?
208
+ has_subtools = loader.has_subtools?(tool_definition.full_name)
186
209
  help_flags = add_help_flags(tool_definition)
187
210
  usage_flags = add_usage_flags(tool_definition)
188
- if @allow_root_args && (!help_flags.empty? || !usage_flags.empty?)
189
- if tool_definition.root? && tool_definition.arg_definitions.empty?
190
- tool_definition.set_remaining_args(TOOL_NAME_KEY,
191
- display_name: "TOOL_NAME",
192
- desc: "The tool for which to display help")
193
- end
194
- end
195
- if (!help_flags.empty? || @fallback_execution) &&
196
- loader.has_subtools?(tool_definition.full_name)
211
+ list_flags = has_subtools ? add_list_flags(tool_definition) : []
212
+ if (!help_flags.empty? || !list_flags.empty? || @fallback_execution) && has_subtools
197
213
  add_recursive_flags(tool_definition)
198
214
  add_search_flags(tool_definition)
199
215
  end
216
+ if !help_flags.empty? || !usage_flags.empty? || !list_flags.empty?
217
+ add_root_args(tool_definition)
218
+ end
200
219
  end
201
220
  yield
202
221
  end
@@ -206,17 +225,16 @@ module Toys
206
225
  #
207
226
  def run(tool)
208
227
  if tool[SHOW_USAGE_KEY]
209
- help_text = get_help_text(tool)
210
- str = help_text.usage_string(wrap_width: terminal.width)
211
- terminal.puts(str)
212
- elsif @fallback_execution && !tool[Tool::Keys::TOOL_DEFINITION].runnable? ||
213
- tool[SHOW_HELP_KEY]
214
- help_text = get_help_text(tool)
215
- str = help_text.help_string(recursive: tool[RECURSIVE_SUBTOOLS_KEY],
216
- search: tool[SEARCH_STRING_KEY],
217
- show_source_path: @show_source_path,
218
- wrap_width: terminal.width)
219
- output_help(str)
228
+ terminal.puts(get_help_text(tool).usage_string(wrap_width: terminal.width))
229
+ elsif tool[SHOW_LIST_KEY]
230
+ terminal.puts(get_help_text(tool).list_string(recursive: tool[RECURSIVE_SUBTOOLS_KEY],
231
+ search: tool[SEARCH_STRING_KEY],
232
+ wrap_width: terminal.width))
233
+ elsif should_show_help(tool)
234
+ output_help(get_help_text(tool).help_string(recursive: tool[RECURSIVE_SUBTOOLS_KEY],
235
+ search: tool[SEARCH_STRING_KEY],
236
+ show_source_path: @show_source_path,
237
+ wrap_width: terminal.width))
220
238
  else
221
239
  yield
222
240
  end
@@ -228,6 +246,11 @@ module Toys
228
246
  @terminal ||= Utils::Terminal.new(output: @stream, styled: @styled_output)
229
247
  end
230
248
 
249
+ def should_show_help(tool)
250
+ @fallback_execution && !tool[Tool::Keys::TOOL_DEFINITION].runnable? ||
251
+ tool[SHOW_HELP_KEY]
252
+ end
253
+
231
254
  def output_help(str)
232
255
  if less_path
233
256
  Utils::Exec.new.exec([less_path, "-R"], in: [:string, str])
@@ -288,6 +311,18 @@ module Toys
288
311
  usage_flags
289
312
  end
290
313
 
314
+ def add_list_flags(tool_definition)
315
+ list_flags = resolve_flags_spec(@list_flags, tool_definition, DEFAULT_LIST_FLAGS)
316
+ unless list_flags.empty?
317
+ tool_definition.add_flag(
318
+ SHOW_LIST_KEY, list_flags,
319
+ report_collisions: false,
320
+ desc: "List the subtools under this tool"
321
+ )
322
+ end
323
+ list_flags
324
+ end
325
+
291
326
  def add_recursive_flags(tool_definition)
292
327
  recursive_flags = resolve_flags_spec(@recursive_flags, tool_definition,
293
328
  DEFAULT_RECURSIVE_FLAGS)
@@ -297,7 +332,8 @@ module Toys
297
332
  tool_definition.add_flag(
298
333
  RECURSIVE_SUBTOOLS_KEY, recursive_flags,
299
334
  report_collisions: false, default: @default_recursive,
300
- desc: "Show all subtools recursively (default is #{@default_recursive})"
335
+ desc: "List all subtools recursively when displaying help" \
336
+ " (default is #{@default_recursive})"
301
337
  )
302
338
  end
303
339
  recursive_flags
@@ -309,12 +345,20 @@ module Toys
309
345
  tool_definition.add_flag(
310
346
  SEARCH_STRING_KEY, search_flags,
311
347
  report_collisions: false,
312
- desc: "Search subtools for the given regular expression"
348
+ desc: "Search subtools for the given regular expression when displaying help"
313
349
  )
314
350
  end
315
351
  search_flags
316
352
  end
317
353
 
354
+ def add_root_args(tool_definition)
355
+ if @allow_root_args && tool_definition.root? && tool_definition.arg_definitions.empty?
356
+ tool_definition.set_remaining_args(TOOL_NAME_KEY,
357
+ display_name: "TOOL_NAME",
358
+ desc: "The tool for which to display help")
359
+ end
360
+ end
361
+
318
362
  def resolve_flags_spec(flags, tool_definition, defaults)
319
363
  flags = flags.call(tool_definition) if flags.respond_to?(:call)
320
364
  case flags
@@ -259,6 +259,18 @@ module Toys
259
259
  end
260
260
  end
261
261
 
262
+ ##
263
+ # Find the given data file or directory in this tool's search path.
264
+ #
265
+ # @param [String] path The path to find
266
+ # @param [nil,:file,:directory] type Type of file system object to find,
267
+ # or nil to return any type.
268
+ # @return [String,nil] Absolute path of the result, or nil if not found.
269
+ #
270
+ def find_data(path, type: nil)
271
+ @__data[Keys::TOOL_DEFINITION].find_data(path, type: type)
272
+ end
273
+
262
274
  ##
263
275
  # Exit immediately with the given status code
264
276
  #
@@ -129,6 +129,29 @@ module Toys
129
129
  assembler.result
130
130
  end
131
131
 
132
+ ##
133
+ # Generate a subtool list string.
134
+ #
135
+ # @param [Boolean] recursive If true, and the tool is a namespace,
136
+ # display all subcommands recursively. Defaults to false.
137
+ # @param [String,nil] search An optional string to search for when
138
+ # listing subcommands. Defaults to `nil` which finds all subcommands.
139
+ # @param [Integer] indent Indent width. Default is {DEFAULT_INDENT}.
140
+ # @param [Integer,nil] wrap_width Wrap width of the column, or `nil` to
141
+ # disable wrap. Default is `nil`.
142
+ # @param [Boolean] styled Output ansi styles. Default is `true`.
143
+ #
144
+ # @return [String] A usage string.
145
+ #
146
+ def list_string(recursive: false, search: nil,
147
+ indent: nil, wrap_width: nil, styled: true)
148
+ indent ||= DEFAULT_INDENT
149
+ subtools = find_subtools(recursive, search)
150
+ assembler = ListStringAssembler.new(@tool, subtools, recursive, search,
151
+ indent, wrap_width, styled)
152
+ assembler.result
153
+ end
154
+
132
155
  private
133
156
 
134
157
  def find_subtools(recursive, search)
@@ -493,6 +516,86 @@ module Toys
493
516
  "#{' ' * (@indent + @indent2)}#{str}"
494
517
  end
495
518
  end
519
+
520
+ ## @private
521
+ class ListStringAssembler
522
+ def initialize(tool, subtools, recursive, search_term, indent, wrap_width, styled)
523
+ @tool = tool
524
+ @subtools = subtools
525
+ @recursive = recursive
526
+ @search_term = search_term
527
+ @indent = indent
528
+ @wrap_width = wrap_width
529
+ assemble(styled)
530
+ end
531
+
532
+ attr_reader :result
533
+
534
+ private
535
+
536
+ def assemble(styled)
537
+ @lines = Utils::Terminal.new(output: ::StringIO.new, styled: styled)
538
+ add_header
539
+ add_list
540
+ @result = @lines.output.string
541
+ end
542
+
543
+ def add_header
544
+ top_line = @recursive ? "Recursive list of tools" : "List of tools"
545
+ @lines <<
546
+ if @tool.root?
547
+ "#{top_line}:"
548
+ else
549
+ "#{top_line} under #{bold(@tool.display_name)}:"
550
+ end
551
+ @lines << ""
552
+ if @search_term
553
+ @lines << "Showing search results for \"#{@search_term}\""
554
+ @lines << ""
555
+ end
556
+ end
557
+
558
+ def add_list
559
+ name_len = @tool.full_name.length
560
+ @subtools.each do |subtool|
561
+ tool_name = subtool.full_name.slice(name_len..-1).join(" ")
562
+ desc =
563
+ if subtool.is_a?(Definition::Alias)
564
+ "(Alias of #{subtool.display_target})"
565
+ else
566
+ subtool.desc
567
+ end
568
+ add_prefix_with_desc(bold(tool_name), desc)
569
+ end
570
+ end
571
+
572
+ def add_prefix_with_desc(prefix, desc)
573
+ if desc.empty?
574
+ @lines << prefix
575
+ elsif !desc.is_a?(Utils::WrappableString)
576
+ @lines << "#{prefix} - #{desc}"
577
+ else
578
+ desc = wrap_indent(Utils::WrappableString.new(["#{prefix} -"] + desc.fragments))
579
+ @lines << desc[0]
580
+ desc[1..-1].each do |line|
581
+ @lines << indent_str(line)
582
+ end
583
+ end
584
+ end
585
+
586
+ def wrap_indent(input)
587
+ return Utils::WrappableString.wrap_lines(input, nil) unless @wrap_width
588
+ Utils::WrappableString.wrap_lines(input, @wrap_width, @wrap_width - @indent)
589
+ end
590
+
591
+ def bold(str)
592
+ @lines.apply_styles(str, :bold)
593
+ end
594
+
595
+ def indent_str(str)
596
+ "#{' ' * @indent}#{str}"
597
+ end
598
+ end
496
599
  end
497
600
  end
498
601
  end
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.4.5
4
+ version: 0.5.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-08-06 00:00:00.000000000 Z
11
+ date: 2018-10-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: highline
@@ -66,34 +66,48 @@ dependencies:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
68
  version: '5.2'
69
+ - !ruby/object:Gem::Dependency
70
+ name: redcarpet
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.4'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.4'
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: rubocop
71
85
  requirement: !ruby/object:Gem::Requirement
72
86
  requirements:
73
87
  - - "~>"
74
88
  - !ruby/object:Gem::Version
75
- version: 0.58.2
89
+ version: 0.59.1
76
90
  type: :development
77
91
  prerelease: false
78
92
  version_requirements: !ruby/object:Gem::Requirement
79
93
  requirements:
80
94
  - - "~>"
81
95
  - !ruby/object:Gem::Version
82
- version: 0.58.2
96
+ version: 0.59.1
83
97
  - !ruby/object:Gem::Dependency
84
98
  name: yard
85
99
  requirement: !ruby/object:Gem::Requirement
86
100
  requirements:
87
101
  - - "~>"
88
102
  - !ruby/object:Gem::Version
89
- version: 0.9.14
103
+ version: 0.9.16
90
104
  type: :development
91
105
  prerelease: false
92
106
  version_requirements: !ruby/object:Gem::Requirement
93
107
  requirements:
94
108
  - - "~>"
95
109
  - !ruby/object:Gem::Version
96
- version: 0.9.14
110
+ version: 0.9.16
97
111
  description: Toys-Core is the command line tool framework underlying Toys. It can
98
112
  be used to create command line binaries using the internal Toys APIs.
99
113
  email:
@@ -113,6 +127,7 @@ files:
113
127
  - lib/toys/definition/acceptor.rb
114
128
  - lib/toys/definition/alias.rb
115
129
  - lib/toys/definition/arg.rb
130
+ - lib/toys/definition/data_finder.rb
116
131
  - lib/toys/definition/flag.rb
117
132
  - lib/toys/definition/tool.rb
118
133
  - lib/toys/dsl/arg.rb
@@ -145,7 +160,11 @@ files:
145
160
  homepage: https://github.com/dazuma/toys
146
161
  licenses:
147
162
  - BSD-3-Clause
148
- metadata: {}
163
+ metadata:
164
+ changelog_uri: https://github.com/dazuma/toys/blob/master/toys-core/CHANGELOG.md
165
+ source_code_uri: https://github.com/dazuma/toys
166
+ bug_tracker_uri: https://github.com/dazuma/toys/issues
167
+ documentation_uri: https://www.rubydoc.info/gems/toys-core
149
168
  post_install_message:
150
169
  rdoc_options: []
151
170
  require_paths: