toys-core 0.11.5 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +62 -0
  3. data/LICENSE.md +1 -1
  4. data/README.md +5 -2
  5. data/docs/guide.md +1 -1
  6. data/lib/toys/acceptor.rb +13 -4
  7. data/lib/toys/arg_parser.rb +7 -7
  8. data/lib/toys/cli.rb +170 -120
  9. data/lib/toys/compat.rb +71 -23
  10. data/lib/toys/completion.rb +18 -6
  11. data/lib/toys/context.rb +24 -15
  12. data/lib/toys/core.rb +6 -2
  13. data/lib/toys/dsl/base.rb +87 -0
  14. data/lib/toys/dsl/flag.rb +26 -20
  15. data/lib/toys/dsl/flag_group.rb +18 -14
  16. data/lib/toys/dsl/internal.rb +206 -0
  17. data/lib/toys/dsl/positional_arg.rb +26 -16
  18. data/lib/toys/dsl/tool.rb +180 -218
  19. data/lib/toys/errors.rb +64 -8
  20. data/lib/toys/flag.rb +662 -656
  21. data/lib/toys/flag_group.rb +24 -10
  22. data/lib/toys/input_file.rb +13 -7
  23. data/lib/toys/loader.rb +293 -140
  24. data/lib/toys/middleware.rb +46 -22
  25. data/lib/toys/mixin.rb +10 -8
  26. data/lib/toys/positional_arg.rb +21 -20
  27. data/lib/toys/settings.rb +914 -0
  28. data/lib/toys/source_info.rb +147 -35
  29. data/lib/toys/standard_middleware/add_verbosity_flags.rb +2 -0
  30. data/lib/toys/standard_middleware/apply_config.rb +6 -4
  31. data/lib/toys/standard_middleware/handle_usage_errors.rb +1 -0
  32. data/lib/toys/standard_middleware/set_default_descriptions.rb +19 -18
  33. data/lib/toys/standard_middleware/show_help.rb +19 -5
  34. data/lib/toys/standard_middleware/show_root_version.rb +2 -0
  35. data/lib/toys/standard_mixins/bundler.rb +24 -15
  36. data/lib/toys/standard_mixins/exec.rb +43 -34
  37. data/lib/toys/standard_mixins/fileutils.rb +3 -1
  38. data/lib/toys/standard_mixins/gems.rb +21 -17
  39. data/lib/toys/standard_mixins/git_cache.rb +46 -0
  40. data/lib/toys/standard_mixins/highline.rb +8 -8
  41. data/lib/toys/standard_mixins/terminal.rb +5 -5
  42. data/lib/toys/standard_mixins/xdg.rb +56 -0
  43. data/lib/toys/template.rb +11 -9
  44. data/lib/toys/{tool.rb → tool_definition.rb} +292 -226
  45. data/lib/toys/utils/completion_engine.rb +7 -2
  46. data/lib/toys/utils/exec.rb +162 -132
  47. data/lib/toys/utils/gems.rb +85 -60
  48. data/lib/toys/utils/git_cache.rb +813 -0
  49. data/lib/toys/utils/help_text.rb +117 -37
  50. data/lib/toys/utils/terminal.rb +11 -3
  51. data/lib/toys/utils/xdg.rb +293 -0
  52. data/lib/toys/wrappable_string.rb +9 -2
  53. data/lib/toys-core.rb +18 -6
  54. metadata +14 -7
data/lib/toys/loader.rb CHANGED
@@ -8,9 +8,6 @@ module Toys
8
8
  # appropriate tool given a set of command line arguments.
9
9
  #
10
10
  class Loader
11
- # @private
12
- BASE_PRIORITY = -999_999
13
-
14
11
  ##
15
12
  # Create a Loader
16
13
  #
@@ -44,18 +41,17 @@ module Toys
44
41
  # @param template_lookup [Toys::ModuleLookup] A lookup for
45
42
  # well-known template classes. Defaults to an empty lookup.
46
43
  #
47
- def initialize(
48
- index_file_name: nil,
49
- preload_dir_name: nil,
50
- preload_file_name: nil,
51
- data_dir_name: nil,
52
- lib_dir_name: nil,
53
- middleware_stack: [],
54
- extra_delimiters: "",
55
- mixin_lookup: nil,
56
- middleware_lookup: nil,
57
- template_lookup: nil
58
- )
44
+ def initialize(index_file_name: nil,
45
+ preload_dir_name: nil,
46
+ preload_file_name: nil,
47
+ data_dir_name: nil,
48
+ lib_dir_name: nil,
49
+ middleware_stack: [],
50
+ extra_delimiters: "",
51
+ mixin_lookup: nil,
52
+ middleware_lookup: nil,
53
+ template_lookup: nil,
54
+ git_cache: nil)
59
55
  if index_file_name && ::File.extname(index_file_name) != ".rb"
60
56
  raise ::ArgumentError, "Illegal index file name #{index_file_name.inspect}"
61
57
  end
@@ -71,30 +67,82 @@ module Toys
71
67
  @loading_started = false
72
68
  @worklist = []
73
69
  @tool_data = {}
70
+ @roots_by_priority = {}
74
71
  @max_priority = @min_priority = 0
75
- @stop_priority = BASE_PRIORITY
72
+ @stop_priority = -999_999
76
73
  @min_loaded_priority = 999_999
77
74
  @middleware_stack = Middleware.stack(middleware_stack)
78
75
  @delimiter_handler = DelimiterHandler.new(extra_delimiters)
79
- get_tool([], BASE_PRIORITY)
76
+ @git_cache = git_cache
77
+ get_tool([], -999_999)
80
78
  end
81
79
 
82
80
  ##
83
81
  # Add a configuration file/directory to the loader.
84
82
  #
85
- # @param paths [String,Array<String>] One or more paths to add.
83
+ # @param path [String] A single path to add.
86
84
  # @param high_priority [Boolean] If true, add this path at the top of the
87
85
  # priority list. Defaults to false, indicating the new path should be
88
86
  # at the bottom of the priority list.
87
+ # @param source_name [String] A custom name for the root source. Optional.
88
+ # @param context_directory [String,nil,:path,:parent] The context directory
89
+ # for tools loaded from this path. You can pass a directory path as a
90
+ # string, `:path` to denote the given path, `:parent` to denote the
91
+ # given path's parent directory, or `nil` to denote no context.
92
+ # Defaults to `:parent`.
93
+ # @return [self]
94
+ #
95
+ def add_path(path,
96
+ high_priority: false,
97
+ source_name: nil,
98
+ context_directory: :parent)
99
+ @mutex.synchronize do
100
+ raise "Cannot add a path after tool loading has started" if @loading_started
101
+ priority = high_priority ? (@max_priority += 1) : (@min_priority -= 1)
102
+ source = SourceInfo.create_path_root(path, priority,
103
+ context_directory: context_directory,
104
+ data_dir_name: @data_dir_name,
105
+ lib_dir_name: @lib_dir_name,
106
+ source_name: source_name)
107
+ @roots_by_priority[priority] = source
108
+ @worklist << [source, [], priority]
109
+ end
110
+ self
111
+ end
112
+
113
+ ##
114
+ # Add a set of configuration files/directories from a common directory to
115
+ # the loader. The set of paths will be added at the same priority level and
116
+ # will share a root.
117
+ #
118
+ # @param root_path [String] A root path to be seen as the root source. This
119
+ # should generally be a directory containing the paths to add.
120
+ # @param relative_paths [String,Array<String>] One or more paths to add, as
121
+ # relative paths from the common root.
122
+ # @param high_priority [Boolean] If true, add the paths at the top of the
123
+ # priority list. Defaults to false, indicating the new paths should be
124
+ # at the bottom of the priority list.
125
+ # @param context_directory [String,nil,:path,:parent] The context directory
126
+ # for tools loaded from this path. You can pass a directory path as a
127
+ # string, `:path` to denote the given root path, `:parent` to denote
128
+ # the given root path's parent directory, or `nil` to denote no context.
129
+ # Defaults to `:path`.
89
130
  # @return [self]
90
131
  #
91
- def add_path(paths, high_priority: false)
92
- paths = Array(paths)
132
+ def add_path_set(root_path, relative_paths,
133
+ high_priority: false,
134
+ context_directory: :path)
135
+ relative_paths = Array(relative_paths)
93
136
  @mutex.synchronize do
94
137
  raise "Cannot add a path after tool loading has started" if @loading_started
95
138
  priority = high_priority ? (@max_priority += 1) : (@min_priority -= 1)
96
- paths.each do |path|
97
- source = SourceInfo.create_path_root(path, @data_dir_name, @lib_dir_name)
139
+ root_source = SourceInfo.create_path_root(root_path, priority,
140
+ context_directory: context_directory,
141
+ data_dir_name: @data_dir_name,
142
+ lib_dir_name: @lib_dir_name)
143
+ @roots_by_priority[priority] = root_source
144
+ relative_paths.each do |path, individual_name|
145
+ source = root_source.relative_child(path, source_name: individual_name)
98
146
  @worklist << [source, [], priority]
99
147
  end
100
148
  end
@@ -107,19 +155,65 @@ module Toys
107
155
  # @param high_priority [Boolean] If true, add this block at the top of the
108
156
  # priority list. Defaults to false, indicating the block should be at
109
157
  # the bottom of the priority list.
110
- # @param name [String] The source name that will be shown in documentation
111
- # for tools defined in this block. If omitted, a default unique string
112
- # will be generated.
158
+ # @param source_name [String] The source name that will be shown in
159
+ # documentation for tools defined in this block. If omitted, a default
160
+ # unique string will be generated.
113
161
  # @param block [Proc] The block of configuration, executed in the context
114
162
  # of the tool DSL {Toys::DSL::Tool}.
163
+ # @param context_directory [String,nil] The context directory for tools
164
+ # loaded from this block. You can pass a directory path as a string, or
165
+ # `nil` to denote no context. Defaults to `nil`.
115
166
  # @return [self]
116
167
  #
117
- def add_block(high_priority: false, name: nil, &block)
118
- name ||= "(Code block #{block.object_id})"
168
+ def add_block(high_priority: false,
169
+ source_name: nil,
170
+ context_directory: nil,
171
+ &block)
119
172
  @mutex.synchronize do
120
173
  raise "Cannot add a block after tool loading has started" if @loading_started
121
174
  priority = high_priority ? (@max_priority += 1) : (@min_priority -= 1)
122
- source = SourceInfo.create_proc_root(block, name, @data_dir_name, @lib_dir_name)
175
+ source = SourceInfo.create_proc_root(block, priority,
176
+ context_directory: context_directory,
177
+ source_name: source_name,
178
+ data_dir_name: @data_dir_name,
179
+ lib_dir_name: @lib_dir_name)
180
+ @roots_by_priority[priority] = source
181
+ @worklist << [source, [], priority]
182
+ end
183
+ self
184
+ end
185
+
186
+ ##
187
+ # Add a configuration git source to the loader.
188
+ #
189
+ # @param git_remote [String] The git repo URL
190
+ # @param git_path [String] The path to the relevant file or directory in
191
+ # the repo. Specify the empty string to use the entire repo.
192
+ # @param git_commit [String] The git ref (i.e. SHA, tag, or branch name)
193
+ # @param high_priority [Boolean] If true, add this path at the top of the
194
+ # priority list. Defaults to false, indicating the new path should be
195
+ # at the bottom of the priority list.
196
+ # @param update [Boolean] If the commit is not a SHA, pulls any updates
197
+ # from the remote. Defaults to false, which uses a local cache and does
198
+ # not update if the commit has been fetched previously.
199
+ # @param context_directory [String,nil] The context directory for tools
200
+ # loaded from this source. You can pass a directory path as a string,
201
+ # or `nil` to denote no context. Defaults to `nil`.
202
+ # @return [self]
203
+ #
204
+ def add_git(git_remote, git_path, git_commit,
205
+ high_priority: false,
206
+ update: false,
207
+ context_directory: nil)
208
+ @mutex.synchronize do
209
+ raise "Cannot add a git source after tool loading has started" if @loading_started
210
+ priority = high_priority ? (@max_priority += 1) : (@min_priority -= 1)
211
+ path = git_cache.get(git_remote, path: git_path, commit: git_commit, update: update)
212
+ source = SourceInfo.create_git_root(git_remote, git_path, git_commit, path, priority,
213
+ context_directory: context_directory,
214
+ data_dir_name: @data_dir_name,
215
+ lib_dir_name: @lib_dir_name)
216
+ @roots_by_priority[priority] = source
123
217
  @worklist << [source, [], priority]
124
218
  end
125
219
  self
@@ -136,7 +230,7 @@ module Toys
136
230
  # that are not part of the tool name and should be passed as tool args.
137
231
  #
138
232
  # @param args [Array<String>] Command line arguments
139
- # @return [Array(Toys::Tool,Array<String>)]
233
+ # @return [Array(Toys::ToolDefinition,Array<String>)]
140
234
  #
141
235
  def lookup(args)
142
236
  orig_prefix, args = @delimiter_handler.find_orig_prefix(args)
@@ -157,13 +251,13 @@ module Toys
157
251
  # the given name, returns `nil`.
158
252
  #
159
253
  # @param words [Array<String>] The tool name
160
- # @return [Toys::Tool] if the tool was found
254
+ # @return [Toys::ToolDefinition] if the tool was found
161
255
  # @return [nil] if no such tool exists
162
256
  #
163
257
  def lookup_specific(words)
164
258
  words = @delimiter_handler.split_path(words.first) if words.size == 1
165
259
  load_for_prefix(words)
166
- tool = get_tool_data(words).cur_definition
260
+ tool = get_tool_data(words, false)&.cur_definition
167
261
  finish_definitions_in_tree(words) if tool
168
262
  tool
169
263
  end
@@ -177,7 +271,7 @@ module Toys
177
271
  # rather than just the immediate children (the default)
178
272
  # @param include_hidden [Boolean] If true, include hidden subtools,
179
273
  # e.g. names beginning with underscores.
180
- # @return [Array<Toys::Tool>] An array of subtools.
274
+ # @return [Array<Toys::ToolDefinition>] An array of subtools.
181
275
  #
182
276
  def list_subtools(words, recursive: false, include_hidden: false)
183
277
  load_for_prefix(words)
@@ -234,8 +328,8 @@ module Toys
234
328
  #
235
329
  # @private
236
330
  #
237
- def get_tool(words, priority)
238
- get_tool_data(words).get_tool(priority, self)
331
+ def get_tool(words, priority, tool_class = nil)
332
+ get_tool_data(words, true).get_tool(priority, self, tool_class)
239
333
  end
240
334
 
241
335
  ##
@@ -248,7 +342,7 @@ module Toys
248
342
  # @private
249
343
  #
250
344
  def activate_tool(words, priority)
251
- get_tool_data(words).activate_tool(priority, self)
345
+ get_tool_data(words, true).activate_tool(priority, self)
252
346
  end
253
347
 
254
348
  ##
@@ -267,10 +361,11 @@ module Toys
267
361
  #
268
362
  # @private
269
363
  #
270
- def build_tool(words, priority)
364
+ def build_tool(words, priority, tool_class = nil)
271
365
  parent = words.empty? ? nil : get_tool(words.slice(0..-2), priority)
272
366
  middleware_stack = parent ? parent.subtool_middleware_stack : @middleware_stack
273
- Tool.new(self, parent, words, priority, middleware_stack, @middleware_lookup)
367
+ ToolDefinition.new(parent, words, priority, @roots_by_priority[priority],
368
+ middleware_stack, @middleware_lookup, tool_class)
274
369
  end
275
370
 
276
371
  ##
@@ -335,12 +430,31 @@ module Toys
335
430
  # @private
336
431
  #
337
432
  def load_path(parent_source, path, words, remaining_words, priority)
433
+ if parent_source.git_remote
434
+ raise LoaderError,
435
+ "Git source #{parent_source.source_name} tried to load from the local file system"
436
+ end
338
437
  source = parent_source.absolute_child(path)
339
438
  @mutex.synchronize do
340
439
  load_validated_path(source, words, remaining_words, priority)
341
440
  end
342
441
  end
343
442
 
443
+ ##
444
+ # Load configuration from the given git remote. This is called from the
445
+ # `load_git` directive in the DSL.
446
+ #
447
+ # @private
448
+ #
449
+ def load_git(parent_source, git_remote, git_path, git_commit, words, remaining_words, priority,
450
+ update: false)
451
+ path = git_cache.get(git_remote, path: git_path, commit: git_commit, update: update)
452
+ source = parent_source.git_child(git_remote, git_path, git_commit, path)
453
+ @mutex.synchronize do
454
+ load_validated_path(source, words, remaining_words, priority)
455
+ end
456
+ end
457
+
344
458
  ##
345
459
  # Load a subtool block. Called from the `tool` directive in the DSL.
346
460
  #
@@ -353,6 +467,18 @@ module Toys
353
467
  end
354
468
  end
355
469
 
470
+ ##
471
+ # Get a GitCache.
472
+ #
473
+ # @private
474
+ #
475
+ def git_cache
476
+ @git_cache ||= begin
477
+ require "toys/utils/git_cache"
478
+ Utils::GitCache.new
479
+ end
480
+ end
481
+
356
482
  ##
357
483
  # Determine the next setting for remaining_words, given a word.
358
484
  #
@@ -368,6 +494,120 @@ module Toys
368
494
  end
369
495
  end
370
496
 
497
+ ##
498
+ # Tool data
499
+ #
500
+ # @private
501
+ #
502
+ class ToolData
503
+ ##
504
+ # @private
505
+ #
506
+ def initialize(words)
507
+ @words = validate_words(words)
508
+ @definitions = {}
509
+ @top_priority = @active_priority = nil
510
+ @mutex = ::Monitor.new
511
+ end
512
+
513
+ ##
514
+ # @private
515
+ #
516
+ def cur_definition
517
+ @mutex.synchronize { active_definition || top_definition }
518
+ end
519
+
520
+ ##
521
+ # @private
522
+ #
523
+ def empty?
524
+ @definitions.empty?
525
+ end
526
+
527
+ ##
528
+ # @private
529
+ #
530
+ def get_tool(priority, loader, tool_class = nil)
531
+ @mutex.synchronize do
532
+ if @top_priority.nil? || @top_priority < priority
533
+ @top_priority = priority
534
+ end
535
+ if tool_class && @definitions.include?(priority)
536
+ raise ToolDefinitionError, "Tool already defined for #{@words.inspect}"
537
+ end
538
+ @definitions[priority] ||= loader.build_tool(@words, priority, tool_class)
539
+ end
540
+ end
541
+
542
+ ##
543
+ # @private
544
+ #
545
+ def activate_tool(priority, loader)
546
+ @mutex.synchronize do
547
+ return active_definition if @active_priority == priority
548
+ return nil if @active_priority && @active_priority > priority
549
+ @active_priority = priority
550
+ get_tool(priority, loader)
551
+ end
552
+ end
553
+
554
+ private
555
+
556
+ def validate_words(words)
557
+ words.each do |word|
558
+ if /[[:cntrl:] #"$&'()*;<>\[\\\]\^`{|}]/.match(word)
559
+ raise ToolDefinitionError, "Illegal characters in name #{word.inspect}"
560
+ end
561
+ end
562
+ end
563
+
564
+ def top_definition
565
+ @top_priority ? @definitions[@top_priority] : nil
566
+ end
567
+
568
+ def active_definition
569
+ @active_priority ? @definitions[@active_priority] : nil
570
+ end
571
+ end
572
+
573
+ ##
574
+ # An object that handles name delimiting.
575
+ #
576
+ # @private
577
+ #
578
+ class DelimiterHandler
579
+ ##
580
+ # @private
581
+ #
582
+ def initialize(extra_delimiters)
583
+ unless %r{^[[:space:]./:]*$}.match?(extra_delimiters)
584
+ raise ::ArgumentError, "Illegal delimiters in #{extra_delimiters.inspect}"
585
+ end
586
+ chars = ::Regexp.escape(extra_delimiters.chars.uniq.join)
587
+ @delimiters = ::Regexp.new("[[:space:]#{chars}]")
588
+ end
589
+
590
+ ##
591
+ # @private
592
+ #
593
+ def split_path(str)
594
+ str.split(@delimiters)
595
+ end
596
+
597
+ ##
598
+ # @private
599
+ #
600
+ def find_orig_prefix(args)
601
+ first_split = (args.first || "").split(@delimiters)
602
+ if first_split.size > 1
603
+ args = first_split + args.slice(1..-1)
604
+ return [first_split, args]
605
+ end
606
+ orig_prefix = args.take_while { |arg| !arg.start_with?("-") }
607
+ [orig_prefix, args]
608
+ end
609
+ end
610
+
371
611
  private
372
612
 
373
613
  def all_cur_definitions
@@ -381,8 +621,10 @@ module Toys
381
621
  result
382
622
  end
383
623
 
384
- def get_tool_data(words)
385
- @mutex.synchronize { @tool_data[words] ||= ToolData.new(words) }
624
+ def get_tool_data(words, create)
625
+ @mutex.synchronize do
626
+ create ? (@tool_data[words] ||= ToolData.new(words)) : @tool_data[words]
627
+ end
386
628
  end
387
629
 
388
630
  ##
@@ -403,7 +645,7 @@ module Toys
403
645
  if remaining_words
404
646
  update_min_loaded_priority(priority)
405
647
  tool_class = get_tool(words, priority).tool_class
406
- DSL::Tool.prepare(tool_class, remaining_words, source) do
648
+ DSL::Internal.prepare(tool_class, words, priority, remaining_words, source, self) do
407
649
  ContextualError.capture("Error while loading Toys config!") do
408
650
  tool_class.class_eval(&source.source_proc)
409
651
  end
@@ -425,7 +667,7 @@ module Toys
425
667
  if source.source_type == :file
426
668
  update_min_loaded_priority(priority)
427
669
  tool_class = get_tool(words, priority).tool_class
428
- InputFile.evaluate(tool_class, remaining_words, source)
670
+ InputFile.evaluate(tool_class, words, priority, remaining_words, source, self)
429
671
  else
430
672
  do_preload(source.source_path)
431
673
  load_index_in(source, words, remaining_words, priority)
@@ -467,16 +709,20 @@ module Toys
467
709
  if @preload_dir_name
468
710
  preload_dir = ::File.join(path, @preload_dir_name)
469
711
  if ::File.directory?(preload_dir) && ::File.readable?(preload_dir)
470
- ::Dir.entries(preload_dir).each do |child|
471
- next unless ::File.extname(child) == ".rb"
472
- preload_file = ::File.join(preload_dir, child)
473
- next if !::File.file?(preload_file) || !::File.readable?(preload_file)
474
- require preload_file
475
- end
712
+ preload_dir_contents(preload_dir)
476
713
  end
477
714
  end
478
715
  end
479
716
 
717
+ def preload_dir_contents(preload_dir)
718
+ ::Dir.entries(preload_dir).each do |child|
719
+ next unless ::File.extname(child) == ".rb"
720
+ preload_file = ::File.join(preload_dir, child)
721
+ next if !::File.file?(preload_file) || !::File.readable?(preload_file)
722
+ require preload_file
723
+ end
724
+ end
725
+
480
726
  def sort_tools_by_name(tools)
481
727
  tools.sort! do |a, b|
482
728
  a = a.full_name
@@ -511,98 +757,5 @@ module Toys
511
757
  index += 1
512
758
  end
513
759
  end
514
-
515
- ##
516
- # Tool data
517
- #
518
- # @private
519
- #
520
- class ToolData
521
- # @private
522
- def initialize(words)
523
- @words = words
524
- @definitions = {}
525
- @top_priority = @active_priority = nil
526
- @mutex = ::Monitor.new
527
- end
528
-
529
- # @private
530
- def cur_definition
531
- @mutex.synchronize { active_definition || top_definition }
532
- end
533
-
534
- # @private
535
- def empty?
536
- @definitions.empty?
537
- end
538
-
539
- # @private
540
- def get_tool(priority, loader)
541
- @mutex.synchronize do
542
- if @top_priority.nil? || @top_priority < priority
543
- @top_priority = priority
544
- end
545
- @definitions[priority] ||= loader.build_tool(@words, priority)
546
- end
547
- end
548
-
549
- # @private
550
- def activate_tool(priority, loader)
551
- @mutex.synchronize do
552
- return active_definition if @active_priority == priority
553
- return nil if @active_priority && @active_priority > priority
554
- @active_priority = priority
555
- get_tool(priority, loader)
556
- end
557
- end
558
-
559
- private
560
-
561
- def top_definition
562
- @top_priority ? @definitions[@top_priority] : nil
563
- end
564
-
565
- def active_definition
566
- @active_priority ? @definitions[@active_priority] : nil
567
- end
568
- end
569
-
570
- ##
571
- # An object that handles name delimiting.
572
- #
573
- # @private
574
- #
575
- class DelimiterHandler
576
- ## @private
577
- ALLOWED_DELIMITERS = %r{^[\./:]*$}.freeze
578
- private_constant :ALLOWED_DELIMITERS
579
-
580
- ## @private
581
- def initialize(extra_delimiters)
582
- unless ALLOWED_DELIMITERS =~ extra_delimiters
583
- raise ::ArgumentError, "Illegal delimiters in #{extra_delimiters.inspect}"
584
- end
585
- chars = ::Regexp.escape(extra_delimiters.chars.uniq.join)
586
- @extra_delimiters = chars.empty? ? nil : ::Regexp.new("[#{chars}]")
587
- end
588
-
589
- ## @private
590
- def split_path(str)
591
- @extra_delimiters ? str.split(@extra_delimiters) : [str]
592
- end
593
-
594
- ## @private
595
- def find_orig_prefix(args)
596
- if @extra_delimiters
597
- first_split = (args.first || "").split(@extra_delimiters)
598
- if first_split.size > 1
599
- args = first_split + args.slice(1..-1)
600
- return [first_split, args]
601
- end
602
- end
603
- orig_prefix = args.take_while { |arg| !arg.start_with?("-") }
604
- [orig_prefix, args]
605
- end
606
- end
607
760
  end
608
761
  end
@@ -37,7 +37,7 @@ module Toys
37
37
  # This basic implementation does nothing and simply yields to the next
38
38
  # middleware.
39
39
  #
40
- # @param tool [Toys::Tool] The tool definition to modify.
40
+ # @param tool [Toys::ToolDefinition] The tool definition to modify.
41
41
  # @param loader [Toys::Loader] The loader that loaded this tool.
42
42
  # @return [void]
43
43
  #
@@ -140,7 +140,9 @@ module Toys
140
140
  end
141
141
  end
142
142
 
143
- ## @private
143
+ ##
144
+ # @private
145
+ #
144
146
  def spec_from_array(array)
145
147
  middleware = array.first
146
148
  if !middleware.is_a?(::String) && !middleware.is_a?(::Symbol) && !middleware.is_a?(::Class)
@@ -238,16 +240,12 @@ module Toys
238
240
  #
239
241
  attr_reader :block
240
242
 
241
- ## @private
242
- def initialize(object, name, args, kwargs, block)
243
- @object = object
244
- @name = name
245
- @args = args
246
- @kwargs = kwargs
247
- @block = block
248
- end
249
-
250
- ## @private
243
+ ##
244
+ # Equality check
245
+ #
246
+ # @param other [Object]
247
+ # @return [Boolean]
248
+ #
251
249
  def ==(other)
252
250
  other.is_a?(Spec) &&
253
251
  object.eql?(other.object) &&
@@ -258,10 +256,25 @@ module Toys
258
256
  end
259
257
  alias eql? ==
260
258
 
261
- ## @private
259
+ ##
260
+ # Return the hash code
261
+ #
262
+ # @return [Integer]
263
+ #
262
264
  def hash
263
265
  [object, name, args, kwargs, block].hash
264
266
  end
267
+
268
+ ##
269
+ # @private
270
+ #
271
+ def initialize(object, name, args, kwargs, block)
272
+ @object = object
273
+ @name = name
274
+ @args = args
275
+ @kwargs = kwargs
276
+ @block = block
277
+ end
265
278
  end
266
279
 
267
280
  ##
@@ -319,14 +332,12 @@ module Toys
319
332
  (@pre_specs + @default_specs + @post_specs).map { |spec| spec.build(middleware_lookup) }
320
333
  end
321
334
 
322
- ## @private
323
- def initialize(default_specs: nil, pre_specs: nil, post_specs: nil)
324
- @pre_specs = pre_specs || []
325
- @post_specs = post_specs || []
326
- @default_specs = default_specs || []
327
- end
328
-
329
- ## @private
335
+ ##
336
+ # Equality check
337
+ #
338
+ # @param other [Object]
339
+ # @return [Boolean]
340
+ #
330
341
  def ==(other)
331
342
  other.is_a?(Stack) &&
332
343
  pre_specs.eql?(other.pre_specs) &&
@@ -335,10 +346,23 @@ module Toys
335
346
  end
336
347
  alias eql? ==
337
348
 
338
- ## @private
349
+ ##
350
+ # Return the hash code
351
+ #
352
+ # @return [Integer]
353
+ #
339
354
  def hash
340
355
  [@pre_specs, @default_specs, @post_specs].hash
341
356
  end
357
+
358
+ ##
359
+ # @private
360
+ #
361
+ def initialize(default_specs: nil, pre_specs: nil, post_specs: nil)
362
+ @pre_specs = pre_specs || []
363
+ @post_specs = post_specs || []
364
+ @default_specs = default_specs || []
365
+ end
342
366
  end
343
367
  end
344
368
  end