toys-core 0.11.5 → 0.13.0

Sign up to get free protection for your applications and to get access to all the features.
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,