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
@@ -0,0 +1,206 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Toys
4
+ module DSL
5
+ ##
6
+ # Internal utility calls used by the DSL.
7
+ #
8
+ # @private
9
+ #
10
+ module Internal
11
+ class << self
12
+ ##
13
+ # Called by the Loader and InputFile to prepare a tool class for running
14
+ # the DSL.
15
+ #
16
+ # @private
17
+ #
18
+ def prepare(tool_class, words, priority, remaining_words, source, loader)
19
+ unless tool_class.is_a?(DSL::Tool)
20
+ class << tool_class
21
+ alias_method :super_include, :include
22
+ end
23
+ tool_class.extend(DSL::Tool)
24
+ end
25
+ unless tool_class.instance_variable_defined?(:@__words)
26
+ tool_class.instance_variable_set(:@__words, words)
27
+ tool_class.instance_variable_set(:@__priority, priority)
28
+ tool_class.instance_variable_set(:@__loader, loader)
29
+ tool_class.instance_variable_set(:@__source, [])
30
+ end
31
+ tool_class.instance_variable_set(:@__remaining_words, remaining_words)
32
+ tool_class.instance_variable_get(:@__source).push(source)
33
+ old_source = ::Thread.current[:__toys_current_source]
34
+ begin
35
+ ::Thread.current[:__toys_current_source] = source
36
+ yield
37
+ ensure
38
+ tool_class.instance_variable_get(:@__source).pop
39
+ ::Thread.current[:__toys_current_source] = old_source
40
+ end
41
+ end
42
+
43
+ ##
44
+ # Called by the DSL implementation to get, and optionally activate, the
45
+ # current tool.
46
+ #
47
+ # @private
48
+ #
49
+ def current_tool(tool_class, activate)
50
+ memoize_var = activate ? :@__active_tool : :@__cur_tool
51
+ if tool_class.instance_variable_defined?(memoize_var)
52
+ tool_class.instance_variable_get(memoize_var)
53
+ else
54
+ loader = tool_class.instance_variable_get(:@__loader)
55
+ words = tool_class.instance_variable_get(:@__words)
56
+ priority = tool_class.instance_variable_get(:@__priority)
57
+ cur_tool =
58
+ if activate
59
+ loader.activate_tool(words, priority)
60
+ else
61
+ loader.get_tool(words, priority)
62
+ end
63
+ if cur_tool && activate
64
+ source = tool_class.instance_variable_get(:@__source).last
65
+ cur_tool.lock_source(source)
66
+ end
67
+ tool_class.instance_variable_set(memoize_var, cur_tool)
68
+ end
69
+ end
70
+
71
+ ##
72
+ # Called by the DSL implementation to add a getter to the tool class.
73
+ #
74
+ # @private
75
+ #
76
+ def maybe_add_getter(tool_class, key)
77
+ if key.is_a?(::Symbol) && key.to_s =~ /^[_a-zA-Z]\w*[!?]?$/ && key != :run
78
+ unless tool_class.public_method_defined?(key)
79
+ tool_class.class_eval do
80
+ define_method(key) do
81
+ self[key]
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
87
+
88
+ ##
89
+ # Called by the DSL implementation to find a named mixin.
90
+ #
91
+ # @private
92
+ #
93
+ def resolve_mixin(mixin, cur_tool, loader)
94
+ mod =
95
+ case mixin
96
+ when ::String
97
+ cur_tool.lookup_mixin(mixin)
98
+ when ::Symbol
99
+ loader.resolve_standard_mixin(mixin.to_s)
100
+ when ::Module
101
+ mixin
102
+ end
103
+ raise ToolDefinitionError, "Mixin not found: #{mixin.inspect}" unless mod
104
+ mod
105
+ end
106
+
107
+ ##
108
+ # Called by the DSL implementation to load a long description from a
109
+ # file.
110
+ #
111
+ # @private
112
+ #
113
+ def load_long_desc_file(path)
114
+ if ::File.extname(path) == ".txt"
115
+ begin
116
+ ::File.readlines(path).map do |line|
117
+ line = line.chomp
118
+ line =~ /^\s/ ? [line] : line
119
+ end
120
+ rescue ::SystemCallError => e
121
+ raise Toys::ToolDefinitionError, e.to_s
122
+ end
123
+ else
124
+ raise Toys::ToolDefinitionError, "Cannot load long desc from file type: #{path}"
125
+ end
126
+ end
127
+
128
+ ##
129
+ # Called by the Tool base class to set config values for a subclass.
130
+ #
131
+ # @private
132
+ #
133
+ def configure_class(tool_class, given_name = nil)
134
+ return if tool_class.name.nil? || tool_class.instance_variable_defined?(:@__loader)
135
+
136
+ mod_names = tool_class.name.split("::")
137
+ class_name = mod_names.pop
138
+ parent = parent_from_mod_name_segments(mod_names)
139
+ loader = parent.instance_variable_get(:@__loader)
140
+ name = given_name ? loader.split_path(given_name) : class_name_to_tool_name(class_name)
141
+
142
+ priority = parent.instance_variable_get(:@__priority)
143
+ words = parent.instance_variable_get(:@__words) + name
144
+ subtool = loader.get_tool(words, priority, tool_class)
145
+
146
+ remaining_words = parent.instance_variable_get(:@__remaining_words)
147
+ next_remaining = name.reduce(remaining_words) do |running_words, word|
148
+ Loader.next_remaining_words(running_words, word)
149
+ end
150
+
151
+ tool_class.instance_variable_set(:@__words, words)
152
+ tool_class.instance_variable_set(:@__priority, priority)
153
+ tool_class.instance_variable_set(:@__loader, loader)
154
+ tool_class.instance_variable_set(:@__source, [current_source_from_context])
155
+ tool_class.instance_variable_set(:@__remaining_words, next_remaining)
156
+ tool_class.instance_variable_set(:@__cur_tool, subtool)
157
+ end
158
+
159
+ ##
160
+ # Called by the Tool base class to add the DSL to a subclass.
161
+ #
162
+ # @private
163
+ #
164
+ def setup_class_dsl(tool_class)
165
+ return if tool_class.name.nil? || tool_class.is_a?(DSL::Tool)
166
+ class << tool_class
167
+ alias_method :super_include, :include
168
+ end
169
+ tool_class.extend(DSL::Tool)
170
+ end
171
+
172
+ private
173
+
174
+ def class_name_to_tool_name(class_name)
175
+ name = class_name.to_s.sub(/^_+/, "").sub(/_+$/, "").gsub(/_+/, "-")
176
+ while name.sub!(/([^-])([A-Z])/, "\\1-\\2") do end
177
+ [name.downcase!]
178
+ end
179
+
180
+ def parent_from_mod_name_segments(mod_names)
181
+ parent = mod_names.reduce(::Object) do |running_mod, seg|
182
+ running_mod.const_get(seg)
183
+ end
184
+ if !parent.is_a?(::Toys::Tool) && parent.instance_variable_defined?(:@__tool_class)
185
+ parent = parent.instance_variable_get(:@__tool_class)
186
+ end
187
+ unless parent.ancestors.include?(::Toys::Context)
188
+ raise ToolDefinitionError, "Toys::Tool can be subclassed only from the Toys DSL"
189
+ end
190
+ parent
191
+ end
192
+
193
+ def current_source_from_context
194
+ source = ::Thread.current[:__toys_current_source]
195
+ if source.nil?
196
+ raise ToolDefinitionError, "Toys::Tool can be subclassed only from a Toys config file"
197
+ end
198
+ unless source.source_type == :file
199
+ raise ToolDefinitionError, "Toys::Tool cannot be subclassed inside a tool block"
200
+ end
201
+ source
202
+ end
203
+ end
204
+ end
205
+ end
206
+ end
@@ -10,7 +10,7 @@ module Toys
10
10
  # {Toys::DSL::Tool#required_arg}, {Toys::DSL::Tool#optional_arg}, or
11
11
  # {Toys::DSL::Tool#remaining_args}.
12
12
  #
13
- # ## Example
13
+ # ### Example
14
14
  #
15
15
  # tool "mytool" do
16
16
  # optional_arg :value do
@@ -22,16 +22,6 @@ module Toys
22
22
  # end
23
23
  #
24
24
  class PositionalArg
25
- ## @private
26
- def initialize(acceptor, default, completion, display_name, desc, long_desc)
27
- @default = default
28
- @display_name = display_name
29
- @desc = desc
30
- @long_desc = long_desc || []
31
- accept(acceptor, **{})
32
- complete(completion, **{})
33
- end
34
-
35
25
  ##
36
26
  # Set the acceptor for this argument's values.
37
27
  # You can pass either the string name of an acceptor defined in this tool
@@ -103,7 +93,7 @@ module Toys
103
93
  # across the strings in the array. In this case, whitespace is not
104
94
  # compacted.
105
95
  #
106
- # ## Examples
96
+ # ### Examples
107
97
  #
108
98
  # If you pass in a sentence as a simple string, it may be word wrapped
109
99
  # when displayed:
@@ -134,7 +124,7 @@ module Toys
134
124
  # word-wrapped when displayed. To insert a blank line, include an empty
135
125
  # string as one of the descriptions.
136
126
  #
137
- # ## Example
127
+ # ### Example
138
128
  #
139
129
  # long_desc "This initial paragraph might get word wrapped.",
140
130
  # "This next paragraph is followed by a blank line.",
@@ -151,21 +141,41 @@ module Toys
151
141
  self
152
142
  end
153
143
 
154
- ## @private
144
+ ##
145
+ # Called only from DSL::Tool
146
+ #
147
+ # @private
148
+ #
149
+ def initialize(acceptor, default, completion, display_name, desc, long_desc)
150
+ @default = default
151
+ @display_name = display_name
152
+ @desc = desc
153
+ @long_desc = long_desc || []
154
+ accept(acceptor, **{})
155
+ complete(completion, **{})
156
+ end
157
+
158
+ ##
159
+ # @private
160
+ #
155
161
  def _add_required_to(tool, key)
156
162
  tool.add_required_arg(key,
157
163
  accept: @acceptor, complete: @completion,
158
164
  display_name: @display_name, desc: @desc, long_desc: @long_desc)
159
165
  end
160
166
 
161
- ## @private
167
+ ##
168
+ # @private
169
+ #
162
170
  def _add_optional_to(tool, key)
163
171
  tool.add_optional_arg(key,
164
172
  accept: @acceptor, default: @default, complete: @completion,
165
173
  display_name: @display_name, desc: @desc, long_desc: @long_desc)
166
174
  end
167
175
 
168
- ## @private
176
+ ##
177
+ # @private
178
+ #
169
179
  def _set_remaining_on(tool, key)
170
180
  tool.set_remaining_args(key,
171
181
  accept: @acceptor, default: @default, complete: @completion,