toys-core 0.11.5 → 0.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +31 -0
  3. data/README.md +1 -1
  4. data/lib/toys-core.rb +4 -1
  5. data/lib/toys/acceptor.rb +3 -3
  6. data/lib/toys/arg_parser.rb +6 -7
  7. data/lib/toys/cli.rb +44 -14
  8. data/lib/toys/compat.rb +19 -22
  9. data/lib/toys/completion.rb +3 -1
  10. data/lib/toys/context.rb +2 -2
  11. data/lib/toys/core.rb +1 -1
  12. data/lib/toys/dsl/base.rb +85 -0
  13. data/lib/toys/dsl/flag.rb +3 -3
  14. data/lib/toys/dsl/flag_group.rb +7 -7
  15. data/lib/toys/dsl/internal.rb +206 -0
  16. data/lib/toys/dsl/positional_arg.rb +3 -3
  17. data/lib/toys/dsl/tool.rb +174 -216
  18. data/lib/toys/errors.rb +1 -0
  19. data/lib/toys/flag.rb +15 -18
  20. data/lib/toys/flag_group.rb +5 -4
  21. data/lib/toys/input_file.rb +4 -4
  22. data/lib/toys/loader.rb +189 -50
  23. data/lib/toys/middleware.rb +1 -1
  24. data/lib/toys/mixin.rb +2 -2
  25. data/lib/toys/positional_arg.rb +3 -3
  26. data/lib/toys/settings.rb +900 -0
  27. data/lib/toys/source_info.rb +121 -18
  28. data/lib/toys/standard_middleware/apply_config.rb +5 -4
  29. data/lib/toys/standard_middleware/set_default_descriptions.rb +18 -18
  30. data/lib/toys/standard_middleware/show_help.rb +17 -5
  31. data/lib/toys/standard_mixins/exec.rb +12 -14
  32. data/lib/toys/standard_mixins/git_cache.rb +48 -0
  33. data/lib/toys/standard_mixins/xdg.rb +56 -0
  34. data/lib/toys/template.rb +2 -2
  35. data/lib/toys/{tool.rb → tool_definition.rb} +100 -41
  36. data/lib/toys/utils/exec.rb +4 -5
  37. data/lib/toys/utils/gems.rb +8 -7
  38. data/lib/toys/utils/git_cache.rb +184 -0
  39. data/lib/toys/utils/help_text.rb +90 -34
  40. data/lib/toys/utils/terminal.rb +1 -1
  41. data/lib/toys/utils/xdg.rb +293 -0
  42. metadata +14 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ae396604e3a0005b460ad94f95187b603f36e8d10d1147da2a78b069232f7963
4
- data.tar.gz: 267209d6e7ab4575053219eee5fb1c6be47b9bd6ac6468b6da5c9abb9ed227c2
3
+ metadata.gz: 2e6f3ce99b43220466c4b43842c1fa78d3cfca815ecc559be749518d26156be9
4
+ data.tar.gz: 8a3dbb068001b3a52d5b070ae0f8652aeea5f1763ae19a938aa84da693023c2c
5
5
  SHA512:
6
- metadata.gz: 527f51e0e679f93786ecd671ff14386615e2014e0dbd4995fd4bb6e58040cb00b27d83d3721c412eb8cc83fdc82fe7dff63c97e0e3d8bca8f8b8d7b04a5d9f41
7
- data.tar.gz: 07c617895710b6f5dd69c6fdb16613c2ed7474361a1cbd8617ae1d271eca647ffa457e77bec7e1b30915b1e01ad25173c74251f1728f261a5d5565ca29b80d68
6
+ metadata.gz: de8dc024af7e706749b65a8e2b4fe9430e380b58add4bc9b643bc357dce7d5a5109057a2aaccdbfb657410def1372494b020cab59b1ebd03de83d2359a0746ab
7
+ data.tar.gz: 96b6ca463c27eff9fe403d365afe15a6d4c75bed057b8dc162b40c696b0f31606738197c5bdb68ed759d9c0a0f29af0e4ddd4aed929e41649cb6a46b67909ea8
data/CHANGELOG.md CHANGED
@@ -1,5 +1,36 @@
1
1
  # Release History
2
2
 
3
+ ### v0.12.0 / 2021-08-05
4
+
5
+ Toys-Core 0.12.0 is a major release with significant new features and bug fixes, and a few breaking interface changes. Additionally, this release now requires Ruby 2.4 or later.
6
+
7
+ Breaking interface changes:
8
+
9
+ * The Toys::Tool class has been renamed Toys::ToolDefinition so that the old name can be used for class-based tool definition.
10
+ * Tool definition now raises ToolDefinitionError if whitespace, control characters, or certain punctuation are used in a tool name.
11
+ * Toys::Loader#add_path no longer supports multiple paths. Use add_path_set instead.
12
+ * The "name" argument was renamed to "source_name" in Toys::Loader#add_block and Toys::CLI#add_config_block
13
+
14
+ New functionality:
15
+
16
+ * The DSL now supports a class-based tool definition syntax (in addition to the existing block-based syntax). Some users may prefer this new class-based style as more Ruby-like.
17
+ * You can now load tools from a remote git repository using the load_git directive.
18
+ * Whitespace is now automatically considered a name delimiter when defining tools.
19
+ * There is now an extensible settings mechanism to activate less-common tool behavior. Currently there is one setting, which causes subtools to inherit their parent's methods by default.
20
+ * The load directive can load into a new tool.
21
+ * Added a new utility class and mixin that provides XDG Base Directory information.
22
+ * Added a new utility class and mixin that provides cached access to remote git repos.
23
+ * The help text generator now supports splitting the subtool list by source.
24
+ * Loader and CLI methods that add tool configs now uniformly provide optional source_name and context_directory arguments.
25
+ * Toys::SourceInfo now supports getting the root ancestor and priority of a source.
26
+ * Toys::ToolDefinition now has a direct accessor for the source root. This is always set for a tool, even if it isn't marked as finished.
27
+
28
+ Fixes:
29
+
30
+ * Fixed some bundler integration issues that occurred when the bundle is being installed in a separate path such as a vendor directory.
31
+ * Toys::ContextualError now includes the full backtrace of the cause.
32
+ * Cleaned up some unused memory objects during tool loading and lookup.
33
+
3
34
  ### v0.11.5 / 2021-03-28
4
35
 
5
36
  * BREAKING CHANGE: The exit_on_nonzero_status option to exec now exits on signals and failures to spawn, in addition to error codes.
data/README.md CHANGED
@@ -34,7 +34,7 @@ Install the **toys-core** gem using:
34
34
  You may also install the **toys** gem, which brings in **toys-core** as a
35
35
  dependency.
36
36
 
37
- Toys-Core requires Ruby 2.3 or later.
37
+ Toys-Core requires Ruby 2.4 or later.
38
38
 
39
39
  Most parts of Toys-Core work on JRuby. However, JRuby is not recommended
40
40
  because of JVM boot latency, lack of support for Kernel#fork, and other issues.
data/lib/toys-core.rb CHANGED
@@ -70,8 +70,10 @@ require "toys/compat"
70
70
  require "toys/completion"
71
71
  require "toys/context"
72
72
  require "toys/core"
73
+ require "toys/dsl/base"
73
74
  require "toys/dsl/flag"
74
75
  require "toys/dsl/flag_group"
76
+ require "toys/dsl/internal"
75
77
  require "toys/dsl/positional_arg"
76
78
  require "toys/dsl/tool"
77
79
  require "toys/errors"
@@ -83,7 +85,8 @@ require "toys/middleware"
83
85
  require "toys/mixin"
84
86
  require "toys/module_lookup"
85
87
  require "toys/positional_arg"
88
+ require "toys/settings"
86
89
  require "toys/source_info"
87
90
  require "toys/template"
88
- require "toys/tool"
91
+ require "toys/tool_definition"
89
92
  require "toys/wrappable_string"
data/lib/toys/acceptor.rb CHANGED
@@ -602,11 +602,11 @@ module Toys
602
602
  Simple.new(type_desc: "boolean", well_known_spec: spec) do |s|
603
603
  if s.nil?
604
604
  default
605
+ elsif s.empty?
606
+ REJECT
605
607
  else
606
608
  s = s.downcase
607
- if s.empty?
608
- REJECT
609
- elsif TRUE_STRINGS.any? { |t| t.start_with?(s) }
609
+ if TRUE_STRINGS.any? { |t| t.start_with?(s) }
610
610
  true
611
611
  elsif FALSE_STRINGS.any? { |f| f.start_with?(s) }
612
612
  false
@@ -261,7 +261,7 @@ module Toys
261
261
  # @param message [String] The message. Required.
262
262
  #
263
263
  def initialize(message)
264
- super(message)
264
+ super(message, name: nil)
265
265
  end
266
266
  end
267
267
 
@@ -269,7 +269,7 @@ module Toys
269
269
  # Create an argument parser for a particular tool.
270
270
  #
271
271
  # @param cli [Toys::CLI] The CLI in effect.
272
- # @param tool [Toys::Tool] The tool defining the argument format.
272
+ # @param tool [Toys::ToolDefinition] The tool defining the argument format.
273
273
  # @param default_data [Hash] Additional initial data (such as verbosity).
274
274
  # @param require_exact_flag_match [Boolean] Whether to require flag matches
275
275
  # be exact (not partial). Default is false.
@@ -295,7 +295,7 @@ module Toys
295
295
 
296
296
  ##
297
297
  # The tool definition governing this parser.
298
- # @return [Toys::Tool]
298
+ # @return [Toys::ToolDefinition]
299
299
  #
300
300
  attr_reader :tool
301
301
 
@@ -429,7 +429,7 @@ module Toys
429
429
  Context::Key::TOOL_NAME => tool.full_name,
430
430
  Context::Key::USAGE_ERRORS => [],
431
431
  }
432
- Compat.merge_clones(data, tool.default_data)
432
+ tool.default_data.each { |k, v| data[k] = v.clone }
433
433
  default_data.each { |k, v| data[k] ||= v }
434
434
  data
435
435
  end
@@ -450,7 +450,7 @@ module Toys
450
450
  case arg
451
451
  when "--"
452
452
  @flags_allowed = false
453
- when /\A(--\w[\?\w-]*)=(.*)\z/
453
+ when /\A(--\w[?\w-]*)=(.*)\z/
454
454
  handle_valued_flag(::Regexp.last_match(1), ::Regexp.last_match(2))
455
455
  when /\A--.+\z/
456
456
  handle_plain_flag(arg)
@@ -474,8 +474,7 @@ module Toys
474
474
  return "" unless flag_def
475
475
  @seen_flag_keys << flag_def.key
476
476
  if flag_def.flag_type == :boolean
477
- add_data(flag_def.key, flag_def.handler, nil, !flag_result.unique_flag_negative?,
478
- :flag, name)
477
+ add_data(flag_def.key, flag_def.handler, nil, !flag_result.unique_flag_negative?, :flag, name)
479
478
  elsif following.empty?
480
479
  if flag_def.value_type == :required || flag_result.unique_flag_syntax.value_delim == " "
481
480
  @active_flag_def = flag_def
data/lib/toys/cli.rb CHANGED
@@ -78,8 +78,8 @@ module Toys
78
78
  # with different verbosity settings (since the logger cannot have
79
79
  # multiple level settings simultaneously). In that case, do not set a
80
80
  # global logger, but use the `logger_factory` parameter instead.
81
- # @param logger_factory [Proc] A proc that takes a {Toys::Tool} as an
82
- # argument, and returns a `Logger` to use when running that tool.
81
+ # @param logger_factory [Proc] A proc that takes a {Toys::ToolDefinition}
82
+ # as an argument, and returns a `Logger` to use when running that tool.
83
83
  # Optional. If not provided (and no global logger is set), CLI will use
84
84
  # a default factory that writes generates loggers writing formatted
85
85
  # output to `STDERR`, as defined by {Toys::CLI.default_logger_factory}.
@@ -321,10 +321,22 @@ module Toys
321
321
  # a Toys directory.
322
322
  # @param high_priority [Boolean] Add the config at the head of the priority
323
323
  # list rather than the tail.
324
+ # @param source_name [String] A custom name for the root source. Optional.
325
+ # @param context_directory [String,nil,:path,:parent] The context directory
326
+ # for tools loaded from this path. You can pass a directory path as a
327
+ # string, `:path` to denote the given path, `:parent` to denote the
328
+ # given path's parent directory, or `nil` to denote no context.
329
+ # Defaults to `:parent`.
324
330
  # @return [self]
325
331
  #
326
- def add_config_path(path, high_priority: false)
327
- @loader.add_path(path, high_priority: high_priority)
332
+ def add_config_path(path,
333
+ high_priority: false,
334
+ source_name: nil,
335
+ context_directory: :parent)
336
+ @loader.add_path(path,
337
+ high_priority: high_priority,
338
+ source_name: source_name,
339
+ context_directory: context_directory)
328
340
  self
329
341
  end
330
342
 
@@ -336,15 +348,24 @@ module Toys
336
348
  #
337
349
  # @param high_priority [Boolean] Add the config at the head of the priority
338
350
  # list rather than the tail.
339
- # @param name [String] The source name that will be shown in documentation
340
- # for tools defined in this block. If omitted, a default unique string
341
- # will be generated.
351
+ # @param source_name [String] The source name that will be shown in
352
+ # documentation for tools defined in this block. If omitted, a default
353
+ # unique string will be generated.
342
354
  # @param block [Proc] The block of configuration, executed in the context
343
355
  # of the tool DSL {Toys::DSL::Tool}.
356
+ # @param context_directory [String,nil] The context directory for tools
357
+ # loaded from this block. You can pass a directory path as a string, or
358
+ # `nil` to denote no context. Defaults to `nil`.
344
359
  # @return [self]
345
360
  #
346
- def add_config_block(high_priority: false, name: nil, &block)
347
- @loader.add_block(high_priority: high_priority, name: name, &block)
361
+ def add_config_block(high_priority: false,
362
+ source_name: nil,
363
+ context_directory: nil,
364
+ &block)
365
+ @loader.add_block(high_priority: high_priority,
366
+ source_name: source_name,
367
+ context_directory: context_directory,
368
+ &block)
348
369
  self
349
370
  end
350
371
 
@@ -358,19 +379,28 @@ module Toys
358
379
  # @param search_path [String] A path to search for configs.
359
380
  # @param high_priority [Boolean] Add the configs at the head of the
360
381
  # priority list rather than the tail.
382
+ # @param context_directory [String,nil,:path,:parent] The context directory
383
+ # for tools loaded from this path. You can pass a directory path as a
384
+ # string, `:path` to denote the given path, `:parent` to denote the
385
+ # given path's parent directory, or `nil` to denote no context.
386
+ # Defaults to `:path`.
361
387
  # @return [self]
362
388
  #
363
- def add_search_path(search_path, high_priority: false)
389
+ def add_search_path(search_path,
390
+ high_priority: false,
391
+ context_directory: :path)
364
392
  paths = []
365
393
  if @config_file_name
366
394
  file_path = ::File.join(search_path, @config_file_name)
367
- paths << file_path if !::File.directory?(file_path) && ::File.readable?(file_path)
395
+ paths << @config_file_name if !::File.directory?(file_path) && ::File.readable?(file_path)
368
396
  end
369
397
  if @config_dir_name
370
398
  dir_path = ::File.join(search_path, @config_dir_name)
371
- paths << dir_path if ::File.directory?(dir_path) && ::File.readable?(dir_path)
399
+ paths << @config_dir_name if ::File.directory?(dir_path) && ::File.readable?(dir_path)
372
400
  end
373
- @loader.add_path(paths, high_priority: high_priority)
401
+ @loader.add_path_set(search_path, paths,
402
+ high_priority: high_priority,
403
+ context_directory: context_directory)
374
404
  self
375
405
  end
376
406
 
@@ -443,7 +473,7 @@ module Toys
443
473
  # Run the given tool with the given arguments.
444
474
  # Does not handle exceptions.
445
475
  #
446
- # @param tool [Toys::Tool] The tool to run.
476
+ # @param tool [Toys::ToolDefinition] The tool to run.
447
477
  # @param args [Array<String>] Command line arguments passed to the tool.
448
478
  # @param default_data [Hash] Initial tool context data.
449
479
  # @return [Integer] The resulting status code
data/lib/toys/compat.rb CHANGED
@@ -53,28 +53,6 @@ module Toys
53
53
  end
54
54
  end
55
55
 
56
- # In Ruby < 2.4, some objects such as nil cannot be cloned.
57
- if ruby_version >= 20400
58
- # @private
59
- def self.merge_clones(hash, orig)
60
- orig.each { |k, v| hash[k] = v.clone }
61
- hash
62
- end
63
- else
64
- # @private
65
- def self.merge_clones(hash, orig)
66
- orig.each do |k, v|
67
- hash[k] =
68
- begin
69
- v.clone
70
- rescue ::TypeError
71
- v
72
- end
73
- end
74
- hash
75
- end
76
- end
77
-
78
56
  # The :base argument to Dir.glob requires Ruby 2.5 or later.
79
57
  if ruby_version >= 20500
80
58
  # @private
@@ -106,5 +84,24 @@ module Toys
106
84
  end
107
85
  end
108
86
  end
87
+
88
+ # File.absolute_path? requires Ruby 2.7 or later. For earlier Rubies, use
89
+ # an ad-hoc mechanism.
90
+ if ruby_version >= 20700
91
+ # @private
92
+ def self.absolute_path?(path)
93
+ ::File.absolute_path?(path)
94
+ end
95
+ elsif ::Dir.getwd =~ /^[a-zA-Z]:/
96
+ # @private
97
+ def self.absolute_path?(path)
98
+ /^[a-zA-Z]:/.match?(path)
99
+ end
100
+ else
101
+ # @private
102
+ def self.absolute_path?(path)
103
+ path.start_with?("/")
104
+ end
105
+ end
109
106
  end
110
107
  end
@@ -88,7 +88,7 @@ module Toys
88
88
 
89
89
  ##
90
90
  # The tool being invoked, which should control the completion.
91
- # @return [Toys::Tool]
91
+ # @return [Toys::ToolDefinition]
92
92
  #
93
93
  def tool
94
94
  lookup_tool
@@ -236,6 +236,7 @@ module Toys
236
236
  # prefix. Defaults to requiring the prefix be empty.
237
237
  #
238
238
  def initialize(cwd: nil, omit_files: false, omit_directories: false, prefix_constraint: "")
239
+ super()
239
240
  @cwd = cwd || ::Dir.pwd
240
241
  @include_files = !omit_files
241
242
  @include_directories = !omit_directories
@@ -328,6 +329,7 @@ module Toys
328
329
  # prefix. Defaults to requiring the prefix be empty.
329
330
  #
330
331
  def initialize(values, prefix_constraint: "")
332
+ super()
331
333
  @values = values.flatten.map { |v| Candidate.new(v) }.sort
332
334
  @prefix_constraint = prefix_constraint
333
335
  end
data/lib/toys/context.rb CHANGED
@@ -33,7 +33,7 @@ module Toys
33
33
  # This module is mixed into the runtime context. This means you can
34
34
  # reference any of these constants directly from your run method.
35
35
  #
36
- # ## Example
36
+ # ### Example
37
37
  #
38
38
  # tool "my-name" do
39
39
  # def run
@@ -78,7 +78,7 @@ module Toys
78
78
  LOGGER = ::Object.new.freeze
79
79
 
80
80
  ##
81
- # Context key for the {Toys::Tool} object being executed.
81
+ # Context key for the {Toys::ToolDefinition} object being executed.
82
82
  # @return [Object]
83
83
  #
84
84
  TOOL = ::Object.new.freeze
data/lib/toys/core.rb CHANGED
@@ -9,7 +9,7 @@ module Toys
9
9
  # Current version of Toys core.
10
10
  # @return [String]
11
11
  #
12
- VERSION = "0.11.5"
12
+ VERSION = "0.12.0"
13
13
  end
14
14
 
15
15
  ## @private deprecated
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##
4
+ # Create a base class for defining a tool with a given name.
5
+ #
6
+ # This method returns a base class for defining a tool with a given name.
7
+ # This is useful if the naming behavior of {Toys::Tool} is not adequate for
8
+ # your tool.
9
+ #
10
+ # ### Example
11
+ #
12
+ # class FooBar < Toys.Tool("Foo_Bar")
13
+ # desc "This is a tool called Foo_Bar"
14
+ #
15
+ # def run
16
+ # puts "Foo_Bar called"
17
+ # end
18
+ # end
19
+ #
20
+ # @param name [String] Name of the tool. Defaults to a name inferred from the
21
+ # class name. (See {Toys::Tool}.)
22
+ # @param base [Class] Use this tool class as the base class, and inherit helper
23
+ # methods from it.
24
+ # @param args [String,Class] Any string-valued positional argument is
25
+ # interpreted as the name. Any class-valued positional argument is
26
+ # interpreted as the base class.
27
+ #
28
+ def Toys.Tool(*args, name: nil, base: nil) # rubocop:disable Naming/MethodName
29
+ args.each do |arg|
30
+ case arg
31
+ when ::Class
32
+ raise ::ArgumentError, "Both base keyword argument and class-valud argument received" if base
33
+ base = arg
34
+ when ::String, ::Symbol
35
+ raise ::ArgumentError, "Both name keyword argument and string-valud argument received" if name
36
+ name = arg
37
+ else
38
+ raise ::ArgumentError, "Unrecognized argument: #{arg}"
39
+ end
40
+ end
41
+ if base && !base.ancestors.include?(::Toys::Context)
42
+ raise ::ArgumentError, "Base class must itself be a tool"
43
+ end
44
+ return base || ::Toys::Tool if name.nil?
45
+ ::Class.new(base || ::Toys::Context) do
46
+ base_class = self
47
+ define_singleton_method(:inherited) do |tool_class|
48
+ ::Toys::DSL::Internal.configure_class(tool_class, base_class == self ? name.to_s : nil)
49
+ super(tool_class)
50
+ ::Toys::DSL::Internal.setup_class_dsl(tool_class)
51
+ end
52
+ end
53
+ end
54
+
55
+ module Toys
56
+ ##
57
+ # Base class for defining tools
58
+ #
59
+ # This base class provides an alternative to the {Toys::DSL::Tool#tool}
60
+ # directive for defining tools in the Toys DSL. Creating a subclass of
61
+ # `Toys::Tool` will create a tool whose name is the "kebab-case" of the class
62
+ # name. Subclasses can be created only in the context of a tool configuration
63
+ # DSL. Furthermore, a class-defined tool can be created only at the top level
64
+ # of a configuration file, or within another class-defined tool. It cannot
65
+ # be a subtool of a tool block.
66
+ #
67
+ # ### Example
68
+ #
69
+ # class FooBar < Toys::Tool
70
+ # desc "This is a tool called foo-bar"
71
+ #
72
+ # def run
73
+ # puts "foo-bar called"
74
+ # end
75
+ # end
76
+ #
77
+ class Tool < Context
78
+ # @private
79
+ def self.inherited(tool_class)
80
+ DSL::Internal.configure_class(tool_class)
81
+ super
82
+ DSL::Internal.setup_class_dsl(tool_class)
83
+ end
84
+ end
85
+ end