toys-core 0.3.6 → 0.3.7

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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +13 -0
  3. data/lib/toys-core.rb +20 -5
  4. data/lib/toys/cli.rb +39 -32
  5. data/lib/toys/core_version.rb +1 -1
  6. data/lib/toys/{tool → definition}/acceptor.rb +21 -15
  7. data/lib/toys/{utils/line_output.rb → definition/alias.rb} +47 -59
  8. data/lib/toys/{tool/arg_definition.rb → definition/arg.rb} +17 -7
  9. data/lib/toys/{tool/flag_definition.rb → definition/flag.rb} +19 -9
  10. data/lib/toys/definition/tool.rb +574 -0
  11. data/lib/toys/dsl/arg.rb +118 -0
  12. data/lib/toys/dsl/flag.rb +132 -0
  13. data/lib/toys/dsl/tool.rb +521 -0
  14. data/lib/toys/errors.rb +2 -2
  15. data/lib/toys/helpers.rb +3 -3
  16. data/lib/toys/helpers/exec.rb +31 -25
  17. data/lib/toys/helpers/fileutils.rb +8 -2
  18. data/lib/toys/helpers/highline.rb +8 -1
  19. data/lib/toys/{alias.rb → helpers/terminal.rb} +44 -53
  20. data/lib/toys/input_file.rb +61 -0
  21. data/lib/toys/loader.rb +87 -77
  22. data/lib/toys/middleware.rb +3 -3
  23. data/lib/toys/middleware/add_verbosity_flags.rb +22 -20
  24. data/lib/toys/middleware/base.rb +53 -5
  25. data/lib/toys/middleware/handle_usage_errors.rb +9 -12
  26. data/lib/toys/middleware/set_default_descriptions.rb +6 -7
  27. data/lib/toys/middleware/show_help.rb +71 -67
  28. data/lib/toys/middleware/show_root_version.rb +9 -9
  29. data/lib/toys/runner.rb +157 -0
  30. data/lib/toys/template.rb +4 -3
  31. data/lib/toys/templates.rb +2 -2
  32. data/lib/toys/templates/clean.rb +2 -2
  33. data/lib/toys/templates/gem_build.rb +5 -5
  34. data/lib/toys/templates/minitest.rb +2 -2
  35. data/lib/toys/templates/rubocop.rb +2 -2
  36. data/lib/toys/templates/yardoc.rb +2 -2
  37. data/lib/toys/tool.rb +168 -625
  38. data/lib/toys/utils/exec.rb +19 -18
  39. data/lib/toys/utils/gems.rb +140 -0
  40. data/lib/toys/utils/help_text.rb +25 -20
  41. data/lib/toys/utils/terminal.rb +412 -0
  42. data/lib/toys/utils/wrappable_string.rb +3 -1
  43. metadata +15 -24
  44. data/lib/toys/config_dsl.rb +0 -699
  45. data/lib/toys/context.rb +0 -290
  46. data/lib/toys/helpers/spinner.rb +0 -142
@@ -50,8 +50,8 @@ module Toys
50
50
  # @param [String,Symbol] name Name of the middleware class to return
51
51
  # @return [Class,nil] The class, or `nil` if not found
52
52
  #
53
- def lookup(name)
54
- Utils::ModuleLookup.lookup(:middleware, name)
53
+ def lookup!(name)
54
+ Utils::ModuleLookup.lookup!(:middleware, name)
55
55
  end
56
56
 
57
57
  ##
@@ -69,7 +69,7 @@ module Toys
69
69
  cls = input.first
70
70
  args = input[1..-1]
71
71
  if cls.is_a?(::String) || cls.is_a?(::Symbol)
72
- cls = lookup(cls)
72
+ cls = lookup!(cls)
73
73
  end
74
74
  if cls.is_a?(::Class)
75
75
  cls.new(*args)
@@ -36,7 +36,7 @@ module Toys
36
36
  #
37
37
  # This middleware adds `-v`, `--verbose`, `-q`, and `--quiet` flags, if
38
38
  # not already defined by the tool. These flags affect the setting of
39
- # {Toys::Context::VERBOSITY}, and, thus, the logger level.
39
+ # {Toys::Tool::Keys::VERBOSITY}, and, thus, the logger level.
40
40
  #
41
41
  class AddVerbosityFlags < Base
42
42
  ##
@@ -75,37 +75,39 @@ module Toys
75
75
  ##
76
76
  # Configure the tool flags.
77
77
  #
78
- def config(tool, _loader)
79
- add_verbose_flags(tool)
80
- add_quiet_flags(tool)
78
+ def config(tool_definition, _loader)
79
+ add_verbose_flags(tool_definition)
80
+ add_quiet_flags(tool_definition)
81
81
  yield
82
82
  end
83
83
 
84
84
  private
85
85
 
86
- def add_verbose_flags(tool)
87
- verbose_flags = Middleware.resolve_flags_spec(@verbose_flags, tool,
86
+ def add_verbose_flags(tool_definition)
87
+ verbose_flags = Middleware.resolve_flags_spec(@verbose_flags, tool_definition,
88
88
  DEFAULT_VERBOSE_FLAGS)
89
89
  unless verbose_flags.empty?
90
- long_desc = "Increase verbosity, causing additional logging levels to display."
91
- tool.add_flag(Context::VERBOSITY, verbose_flags,
92
- report_collisions: false,
93
- handler: ->(_val, cur) { cur + 1 },
94
- desc: "Increase verbosity",
95
- long_desc: long_desc)
90
+ tool_definition.add_flag(
91
+ Tool::Keys::VERBOSITY, verbose_flags,
92
+ report_collisions: false,
93
+ handler: ->(_val, cur) { cur + 1 },
94
+ desc: "Increase verbosity",
95
+ long_desc: "Increase verbosity, causing additional logging levels to display."
96
+ )
96
97
  end
97
98
  end
98
99
 
99
- def add_quiet_flags(tool)
100
- quiet_flags = Middleware.resolve_flags_spec(@quiet_flags, tool,
100
+ def add_quiet_flags(tool_definition)
101
+ quiet_flags = Middleware.resolve_flags_spec(@quiet_flags, tool_definition,
101
102
  DEFAULT_QUIET_FLAGS)
102
103
  unless quiet_flags.empty?
103
- long_desc = "Decrease verbosity, causing fewer logging levels to display."
104
- tool.add_flag(Context::VERBOSITY, quiet_flags,
105
- report_collisions: false,
106
- handler: ->(_val, cur) { cur - 1 },
107
- desc: "Decrease verbosity",
108
- long_desc: long_desc)
104
+ tool_definition.add_flag(
105
+ Tool::Keys::VERBOSITY, quiet_flags,
106
+ report_collisions: false,
107
+ handler: ->(_val, cur) { cur - 1 },
108
+ desc: "Decrease verbosity",
109
+ long_desc: "Decrease verbosity, causing fewer logging levels to display."
110
+ )
109
111
  end
110
112
  end
111
113
  end
@@ -30,20 +30,68 @@
30
30
  module Toys
31
31
  module Middleware
32
32
  ##
33
- # A base middleware with a no-op implementation.
33
+ # This is a base middleware with a no-op implementation.
34
+ #
35
+ # A middleware is an object that has the opportunity to alter the
36
+ # configuration and runtime behavior of each tool in a Toys CLI. A CLI
37
+ # contains an ordered list of middleware, known as the *middleware stack*,
38
+ # that together define the CLI's default behavior.
39
+ #
40
+ # Specifically, a middleware can perform two functions.
41
+ #
42
+ # First, it can modify the configuration of a tool. After tools are defined
43
+ # from configuration, the middleware stack can make modifications to each
44
+ # tool. A middleware can add flags and arguments to the tool, modify the
45
+ # description, or make any other changes to how the tool is set up.
46
+ #
47
+ # Second, a middleware can intercept and change tool execution. Like a Rack
48
+ # middleware, a Toys middleware can wrap execution with its own code,
49
+ # replace it outright, or leave it unmodified.
34
50
  #
35
51
  class Base
36
52
  ##
37
- # The base middleware does not affect tool configuration.
53
+ # This method is called after a tool has been defined, and gives this
54
+ # middleware the opportunity to modify the tool definition. It is passed
55
+ # the tool definition object and the loader, and can make any changes to
56
+ # the tool definition. In most cases, this method should also call
57
+ # `yield`, which passes control to the next middleware in the stack. A
58
+ # middleware can disable modifications done by subsequent middleware by
59
+ # omitting the `yield` call, but this is uncommon.
60
+ #
61
+ # The base middleware implementation does nothing and simply yields to
62
+ # the next middleware. Subclasses should override this if they want to
63
+ # alter the tool definition.
38
64
  #
39
- def config(_tool, _loader)
65
+ # @param [Toys::Definition::Tool] _tool_definition The tool definition
66
+ # to modify.
67
+ # @param [Toys::Loader] _loader The loader that loaded this tool.
68
+ #
69
+ def config(_tool_definition, _loader)
40
70
  yield
41
71
  end
42
72
 
43
73
  ##
44
- # The base middleware does not affect tool execution.
74
+ # This method is called when the tool is run. It gives the middleware an
75
+ # opportunity to modify the runtime behavior of the tool. It is passed
76
+ # the tool instance (i.e. the object that hosts a tool's `run` method),
77
+ # and you can use this object to access the tool's options and other
78
+ # context data. In most cases, this method should also call `yield`,
79
+ # which passes control to the next middleware in the stack. A middleware
80
+ # can "wrap" normal execution by calling `yield` somewhere in its
81
+ # implementation of this method, or it can completely replace the
82
+ # execution behavior by not calling `yield` at all.
83
+ #
84
+ # Like a tool's `run` method, this method's return value is unused. If
85
+ # you want to output from a tool, write to stdout or stderr. If you want
86
+ # to set the exit status code, call {Toys::Tool#exit} on the tool object.
87
+ #
88
+ # The base middleware implementation does nothing and simply yields to
89
+ # the next middleware. Subclasses should override this if they want to
90
+ # alter the tool execution.
91
+ #
92
+ # @param [Toys::Tool] _tool The tool execution instance.
45
93
  #
46
- def execute(_context)
94
+ def run(_tool)
47
95
  yield
48
96
  end
49
97
  end
@@ -27,11 +27,9 @@
27
27
  # POSSIBILITY OF SUCH DAMAGE.
28
28
  ;
29
29
 
30
- require "highline"
31
-
32
30
  require "toys/middleware/base"
33
31
  require "toys/utils/help_text"
34
- require "toys/utils/line_output"
32
+ require "toys/utils/terminal"
35
33
 
36
34
  module Toys
37
35
  module Middleware
@@ -54,20 +52,19 @@ module Toys
54
52
  #
55
53
  def initialize(exit_code: -1, stream: $stderr, styled_output: nil)
56
54
  @exit_code = exit_code
57
- @output = Utils::LineOutput.new(stream, styled: styled_output)
55
+ @terminal = Utils::Terminal.new(output: stream, styled: styled_output)
58
56
  end
59
57
 
60
58
  ##
61
59
  # Intercept and handle usage errors during execution.
62
60
  #
63
- def execute(context)
64
- if context[Context::USAGE_ERROR]
65
- width = ::HighLine.new.output_cols
66
- help_text = Utils::HelpText.from_context(context)
67
- @output.puts(context[Context::USAGE_ERROR], :bright_red, :bold)
68
- @output.puts("")
69
- @output.puts(help_text.usage_string(wrap_width: width))
70
- context.exit(@exit_code)
61
+ def run(tool)
62
+ if tool[Tool::Keys::USAGE_ERROR]
63
+ help_text = Utils::HelpText.from_tool(tool)
64
+ @terminal.puts(tool[Tool::Keys::USAGE_ERROR], :bright_red, :bold)
65
+ @terminal.puts("")
66
+ @terminal.puts(help_text.usage_string(wrap_width: @terminal.width))
67
+ tool.exit(@exit_code)
71
68
  else
72
69
  yield
73
70
  end
@@ -86,16 +86,15 @@ module Toys
86
86
  # Create a SetDefaultDescriptions middleware given default descriptions.
87
87
  #
88
88
  # @param [String,nil] default_tool_desc The default short description for
89
- # tools with an script, or `nil` not to set one. Defaults to
89
+ # runnable tools, or `nil` not to set one. Defaults to
90
90
  # {DEFAULT_TOOL_DESC}.
91
91
  # @param [String,nil] default_tool_long_desc The default long description
92
- # for tools with an script, or `nil` not to set one. Defaults to
93
- # `nil`.
92
+ # for runnable tools, or `nil` not to set one. Defaults to `nil`.
94
93
  # @param [String,nil] default_namespace_desc The default short
95
- # description for tools with no script, or `nil` not to set one.
94
+ # description for non-runnable tools, or `nil` not to set one.
96
95
  # Defaults to {DEFAULT_TOOL_DESC}.
97
96
  # @param [String,nil] default_namespace_long_desc The default long
98
- # description for tools with no script, or `nil` not to set one.
97
+ # description for non-runnable tools, or `nil` not to set one.
99
98
  # Defaults to `nil`.
100
99
  # @param [String,nil] default_root_desc The default short description for
101
100
  # the root tool, or `nil` not to set one. Defaults to
@@ -151,7 +150,7 @@ module Toys
151
150
  def generate_tool_desc(tool, data)
152
151
  if tool.root?
153
152
  @default_root_desc
154
- elsif !tool.includes_script? && data[:loader].has_subtools?(tool.full_name)
153
+ elsif !tool.runnable? && data[:loader].has_subtools?(tool.full_name)
155
154
  @default_namespace_desc
156
155
  else
157
156
  @default_tool_desc
@@ -174,7 +173,7 @@ module Toys
174
173
  def generate_tool_long_desc(tool, data)
175
174
  if tool.root?
176
175
  @default_root_long_desc
177
- elsif !tool.includes_script? && data[:loader].has_subtools?(tool.full_name)
176
+ elsif !tool.runnable? && data[:loader].has_subtools?(tool.full_name)
178
177
  @default_namespace_long_desc
179
178
  else
180
179
  @default_tool_long_desc
@@ -27,21 +27,19 @@
27
27
  # POSSIBILITY OF SUCH DAMAGE.
28
28
  ;
29
29
 
30
- require "highline"
31
-
32
30
  require "toys/middleware/base"
33
31
  require "toys/utils/exec"
34
32
  require "toys/utils/help_text"
35
- require "toys/utils/line_output"
33
+ require "toys/utils/terminal"
36
34
 
37
35
  module Toys
38
36
  module Middleware
39
37
  ##
40
38
  # A middleware that shows help text for the tool when a flag (typically
41
39
  # `--help`) is provided. It can also be configured to show help by
42
- # default if the tool is a namespace with no script.
40
+ # default if the tool is a namespace that is not runnable.
43
41
  #
44
- # If a tool has no script, this middleware can also add a
42
+ # If a tool is not runnable, this middleware can also add a
45
43
  # `--[no-]recursive` flag, which, when set to `true` (the default), shows
46
44
  # all subtools recursively rather than only immediate subtools. This
47
45
  # middleware can also search for keywords in its subtools.
@@ -103,9 +101,9 @@ module Toys
103
101
  # @param [Boolean] default_recursive Whether to search recursively for
104
102
  # subtools by default. Default is `false`.
105
103
  # @param [Boolean] fallback_execution Cause the tool to display its own
106
- # help text if it does not otherwise have an script. This is mostly
107
- # useful for namespaces, which have children but no script. Default
108
- # is `false`.
104
+ # help text if it is not otherwise runnable. This is mostly useful
105
+ # for namespaces, which have children are not runnable. Default is
106
+ # `false`.
109
107
  # @param [Boolean] allow_root_args If the root tool includes flags for
110
108
  # help or usage, and doesn't otherwise use positional arguments,
111
109
  # then a tool name can be passed as arguments to display help for
@@ -146,18 +144,20 @@ module Toys
146
144
  ##
147
145
  # Configure flags and default data.
148
146
  #
149
- def config(tool, loader)
150
- help_flags = add_help_flags(tool)
151
- usage_flags = add_usage_flags(tool)
147
+ def config(tool_definition, loader)
148
+ help_flags = add_help_flags(tool_definition)
149
+ usage_flags = add_usage_flags(tool_definition)
152
150
  if @allow_root_args && (!help_flags.empty? || !usage_flags.empty?)
153
- if tool.root? && tool.arg_definitions.empty?
154
- tool.set_remaining_args(:_tool_name, display_name: "TOOL_NAME",
155
- desc: "The tool for which to display help")
151
+ if tool_definition.root? && tool_definition.arg_definitions.empty?
152
+ tool_definition.set_remaining_args(:_tool_name,
153
+ display_name: "TOOL_NAME",
154
+ desc: "The tool for which to display help")
156
155
  end
157
156
  end
158
- if (!help_flags.empty? || @fallback_execution) && loader.has_subtools?(tool.full_name)
159
- add_recursive_flags(tool)
160
- add_search_flags(tool)
157
+ if (!help_flags.empty? || @fallback_execution) &&
158
+ loader.has_subtools?(tool_definition.full_name)
159
+ add_recursive_flags(tool_definition)
160
+ add_search_flags(tool_definition)
161
161
  end
162
162
  yield
163
163
  end
@@ -165,18 +165,18 @@ module Toys
165
165
  ##
166
166
  # Display help text if requested.
167
167
  #
168
- def execute(context)
169
- if context[:_show_usage]
170
- help_text = get_help_text(context)
171
- str = help_text.usage_string(wrap_width: output_cols)
172
- output.puts(str)
173
- elsif @fallback_execution && !context[Context::TOOL].includes_script? ||
174
- context[:_show_help]
175
- help_text = get_help_text(context)
176
- str = help_text.help_string(recursive: context[:_recursive_subtools],
177
- search: context[:_search_subtools],
168
+ def run(tool)
169
+ if tool[:_show_usage]
170
+ help_text = get_help_text(tool)
171
+ str = help_text.usage_string(wrap_width: terminal.width)
172
+ terminal.puts(str)
173
+ elsif @fallback_execution && !tool[Tool::Keys::TOOL_DEFINITION].runnable? ||
174
+ tool[:_show_help]
175
+ help_text = get_help_text(tool)
176
+ str = help_text.help_string(recursive: tool[:_recursive_subtools],
177
+ search: tool[:_search_subtools],
178
178
  show_source_path: @show_source_path,
179
- wrap_width: output_cols)
179
+ wrap_width: terminal.width)
180
180
  output_help(str)
181
181
  else
182
182
  yield
@@ -185,19 +185,15 @@ module Toys
185
185
 
186
186
  private
187
187
 
188
- def output_cols
189
- @output_cols ||= ::HighLine.new(nil, @stream).output_cols
190
- end
191
-
192
- def output
193
- @output ||= Utils::LineOutput.new(@stream, styled: @styled_output)
188
+ def terminal
189
+ @terminal ||= Utils::Terminal.new(output: @stream, styled: @styled_output)
194
190
  end
195
191
 
196
192
  def output_help(str)
197
193
  if less_path
198
194
  Utils::Exec.new.exec([less_path, "-R"], in_from: str)
199
195
  else
200
- output.puts(str)
196
+ terminal.puts(str)
201
197
  end
202
198
  end
203
199
 
@@ -212,62 +208,70 @@ module Toys
212
208
  @less_path
213
209
  end
214
210
 
215
- def get_help_text(context)
216
- tool_name = context[:_tool_name]
217
- return Utils::HelpText.from_context(context) if tool_name.nil? || tool_name.empty?
218
- loader = context[Context::LOADER]
219
- tool, rest = loader.lookup(tool_name)
220
- help_text = Utils::HelpText.new(tool, loader, context[Context::BINARY_NAME])
221
- report_usage_error(context, tool_name, help_text) unless rest.empty?
211
+ def get_help_text(tool)
212
+ tool_name = tool[:_tool_name]
213
+ return Utils::HelpText.from_tool(tool) if tool_name.nil? || tool_name.empty?
214
+ loader = tool[Tool::Keys::LOADER]
215
+ tool_definition, rest = loader.lookup(tool_name)
216
+ help_text = Utils::HelpText.new(tool_definition, loader, tool[Tool::Keys::BINARY_NAME])
217
+ report_usage_error(tool, tool_name, help_text) unless rest.empty?
222
218
  help_text
223
219
  end
224
220
 
225
- def report_usage_error(context, tool_name, help_text)
226
- output.puts("Tool not found: #{tool_name.join(' ')}", :bright_red, :bold)
227
- output.puts
228
- output.puts help_text.usage_string(wrap_width: output_cols)
229
- context.exit(1)
221
+ def report_usage_error(tool, tool_name, help_text)
222
+ terminal.puts("Tool not found: #{tool_name.join(' ')}", :bright_red, :bold)
223
+ terminal.puts
224
+ terminal.puts help_text.usage_string(wrap_width: terminal.width)
225
+ tool.exit(1)
230
226
  end
231
227
 
232
- def add_help_flags(tool)
233
- help_flags = Middleware.resolve_flags_spec(@help_flags, tool,
228
+ def add_help_flags(tool_definition)
229
+ help_flags = Middleware.resolve_flags_spec(@help_flags, tool_definition,
234
230
  DEFAULT_HELP_FLAGS)
235
231
  unless help_flags.empty?
236
- tool.add_flag(:_show_help, help_flags,
237
- report_collisions: false, desc: "Display help for this tool")
232
+ tool_definition.add_flag(
233
+ :_show_help, help_flags,
234
+ report_collisions: false,
235
+ desc: "Display help for this tool"
236
+ )
238
237
  end
239
238
  help_flags
240
239
  end
241
240
 
242
- def add_usage_flags(tool)
243
- usage_flags = Middleware.resolve_flags_spec(@usage_flags, tool,
241
+ def add_usage_flags(tool_definition)
242
+ usage_flags = Middleware.resolve_flags_spec(@usage_flags, tool_definition,
244
243
  DEFAULT_USAGE_FLAGS)
245
244
  unless usage_flags.empty?
246
- tool.add_flag(:_show_usage, usage_flags,
247
- report_collisions: false,
248
- desc: "Display a brief usage string for this tool")
245
+ tool_definition.add_flag(
246
+ :_show_usage, usage_flags,
247
+ report_collisions: false,
248
+ desc: "Display a brief usage string for this tool"
249
+ )
249
250
  end
250
251
  usage_flags
251
252
  end
252
253
 
253
- def add_recursive_flags(tool)
254
- recursive_flags = Middleware.resolve_flags_spec(@recursive_flags, tool,
254
+ def add_recursive_flags(tool_definition)
255
+ recursive_flags = Middleware.resolve_flags_spec(@recursive_flags, tool_definition,
255
256
  DEFAULT_RECURSIVE_FLAGS)
256
257
  unless recursive_flags.empty?
257
- tool.add_flag(:_recursive_subtools, recursive_flags,
258
- report_collisions: false,
259
- default: @default_recursive,
260
- desc: "Show all subtools recursively (default is #{@default_recursive})")
258
+ tool_definition.add_flag(
259
+ :_recursive_subtools, recursive_flags,
260
+ report_collisions: false, default: @default_recursive,
261
+ desc: "Show all subtools recursively (default is #{@default_recursive})"
262
+ )
261
263
  end
262
264
  end
263
265
 
264
- def add_search_flags(tool)
265
- search_flags = Middleware.resolve_flags_spec(@search_flags, tool,
266
+ def add_search_flags(tool_definition)
267
+ search_flags = Middleware.resolve_flags_spec(@search_flags, tool_definition,
266
268
  DEFAULT_SEARCH_FLAGS)
267
269
  unless search_flags.empty?
268
- tool.add_flag(:_search_subtools, search_flags,
269
- report_collisions: false,
270
- desc: "Search subtools for the given regular expression")
270
+ tool_definition.add_flag(
271
+ :_search_subtools, search_flags,
272
+ report_collisions: false,
273
+ desc: "Search subtools for the given regular expression"
274
+ )
271
275
  end
272
276
  end
273
277
  end