toys 0.11.5 → 0.12.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 063c28a7b9b7d3bc5f7ada761beb8623ff144b92847f2a9424889ca953d27cee
4
- data.tar.gz: a1bcdb384d7592a0a43760aab7cd6da0e4afa4b6a1fd1ad59bb74ed6a6eeef73
3
+ metadata.gz: '091a51c34d19b918c203d88f0bbae74432343aae63a0ee7d62799fa30a1c8097'
4
+ data.tar.gz: 00e1a389cf5de2d5a85133a7ca148480e6885f2c80822fcfc4e9838d36d724a3
5
5
  SHA512:
6
- metadata.gz: 50596ee8741422c0e186c7c2b63b52410a0afa1de04dfbea43e06acbf52c8b56f46c54c86471a80bae451c004d238ea94b58d666e7c39742de80bf9c95f95106
7
- data.tar.gz: 1230cf4acb2414f378be58279a3017cd897a101a7c715e4db88394a8490fdb46dee7d0d9a617196698c034dddf3c6ac88b3855ba19262785dd4989dcf253d409
6
+ metadata.gz: 93b41a0886856f9665f3650596dba6fa4cb9a13c0a1c3fa040aed3b669382b86373e38d57bf3ac8bc0f90ed33a5094e2bcd4c89ea974ff98fc7dd5aebff5cb88
7
+ data.tar.gz: c83a1aa39ba8e3be2246aaa4bd8f6003b6c10d52e91beeb04ecc9082403b19e64fd5af7c0a04a8bd10c21d0f731bd63a0d8f3c0649163027fdc78429760553ef
data/CHANGELOG.md CHANGED
@@ -1,5 +1,32 @@
1
1
  # Release History
2
2
 
3
+ ### v0.12.0 / 2021-08-05
4
+
5
+ Toys 0.12.0 is a major release with significant new features and bug fixes, and a few minor breaking changes. Additionally, this release now requires Ruby 2.4 or later.
6
+
7
+ Breaking changes:
8
+
9
+ * Defining a tool with whitespace, control characters, or certain punctuation in the name, now raises ToolDefinitionError.
10
+ * The Toys::Tool class (for the object returned by the Toys::Context::Key::TOOL attribute) has been renamed to Toys::ToolDefinition so that the old name can be used for class-based tool definition.
11
+
12
+ New functionality:
13
+
14
+ * 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.
15
+ * The subtool list on help screens is now split into sections by source directory
16
+ * You can now load tools from a remote git repository using the load_git directive.
17
+ * Whitespace is now automatically considered a name delimiter when defining tools.
18
+ * There is experimental support for providing tests for 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
+ * You can now set the context directory individually for the standard build tools.
22
+ * Added a new standard mixin that provides XDG Base Directory information.
23
+ * Added a new standard mixin that provides cached access to remote git repos.
24
+
25
+ Fixes:
26
+
27
+ * Fixed some bundler integration issues that occurred when the bundle is being installed in a separate path such as a vendor directory.
28
+ * Exceptions raised from internal classes now include the full backtrace.
29
+
3
30
  ### v0.11.5 / 2021-03-28
4
31
 
5
32
  * 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
@@ -50,7 +50,7 @@ Toys does not yet specially implement tab completion for zsh or other shells.
50
50
  However, if you are using zsh, installing bash completion using `bashcompinit`
51
51
  *mostly* works.
52
52
 
53
- Toys requires Ruby 2.3 or later.
53
+ Toys requires Ruby 2.4 or later.
54
54
 
55
55
  Most parts of Toys work on JRuby. However, JRuby is not recommended because of
56
56
  JVM boot latency, lack of support for Kernel#fork, and other issues.
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ desc "Bash tab completion for Toys"
4
+
5
+ long_desc \
6
+ "Tools that manage tab completion for Toys in the bash shell.",
7
+ "",
8
+ "To install tab completion for Toys, execute the following line in a bash shell, or" \
9
+ " include it in an init file such as your .bashrc:",
10
+ [" $(toys system bash-completion install)"],
11
+ "",
12
+ "To remove tab completion, execute:",
13
+ [" $(toys system bash-completion remove)"],
14
+ "",
15
+ "It is also possible to install completions for different executable names if you have" \
16
+ " aliases for Toys. See the help for the \"install\" and \"remove\" tools for details.",
17
+ "",
18
+ "The \"eval\" tool is the actual completion command invoked by bash when it needs to" \
19
+ " complete a toys command line. You shouldn't need to invoke it directly."
20
+
21
+ tool "eval" do
22
+ desc "Tab completion command (executed by bash)"
23
+
24
+ long_desc \
25
+ "Completion command invoked by bash to compete a toys command line. Generally you do not" \
26
+ " need to invoke this directly. It reads the command line context from the COMP_LINE" \
27
+ " and COMP_POINT environment variables, and outputs completion candidates to stdout."
28
+
29
+ disable_argument_parsing
30
+
31
+ def run
32
+ require "toys/utils/completion_engine"
33
+ result = ::Toys::Utils::CompletionEngine::Bash.new(cli).run
34
+ if result > 1
35
+ logger.fatal("This tool must be invoked as a bash completion command.")
36
+ end
37
+ exit(result)
38
+ end
39
+ end
40
+
41
+ tool "install" do
42
+ desc "Install bash tab completion"
43
+
44
+ long_desc \
45
+ "Outputs a command to set up Toys tab completion in the current bash shell.",
46
+ "",
47
+ "To use, execute the following line in a bash shell, or include it in an init file" \
48
+ " such as your .bashrc:",
49
+ [" $(toys system bash-completion install)"],
50
+ "",
51
+ "This will associate the toys tab completion logic with the `toys` executable by default." \
52
+ " If you have aliases for the toys executable, pass them as arguments. e.g.",
53
+ [" $(toys system bash-completion install my-toys-alias another-alias)"]
54
+
55
+ remaining_args :executable_names,
56
+ desc: "Names of executables for which to set up tab completion" \
57
+ " (default: #{::Toys::StandardCLI::EXECUTABLE_NAME})"
58
+
59
+ def run
60
+ require "shellwords"
61
+ path = ::File.join(::File.dirname(::File.dirname(__dir__)), "share", "bash-completion.sh")
62
+ exes = executable_names.empty? ? [::Toys::StandardCLI::EXECUTABLE_NAME] : executable_names
63
+ puts Shellwords.join(["source", path] + exes)
64
+ end
65
+ end
66
+
67
+ tool "remove" do
68
+ desc "Remove bash tab completion"
69
+
70
+ long_desc \
71
+ "Outputs a command to remove Toys tab completion from the current bash shell.",
72
+ "",
73
+ "To use, execute the following line in a bash shell:",
74
+ [" $(toys system bash-completion remove)"],
75
+ "",
76
+ "If you have other names or aliases for the toys executable, pass them as arguments. e.g.",
77
+ [" $(toys system bash-completion remove my-toys-alias another-alias)"]
78
+
79
+ remaining_args :executable_names,
80
+ desc: "Names of executables for which to set up tab completion" \
81
+ " (default: #{::Toys::StandardCLI::EXECUTABLE_NAME})"
82
+
83
+ def run
84
+ require "shellwords"
85
+ path = ::File.join(::File.dirname(::File.dirname(__dir__)), "share", "bash-completion-remove.sh")
86
+ exes = executable_names.empty? ? [::Toys::StandardCLI::EXECUTABLE_NAME] : executable_names
87
+ puts Shellwords.join(["source", path] + exes)
88
+ end
89
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ desc "Run tool tests"
4
+
5
+ flag :directory, "-d", "--directory PATH",
6
+ desc: "Run tests from the given directory"
7
+ flag :seed, "-s", "--seed SEED",
8
+ desc: "Sets random seed."
9
+ flag :warnings, "-w", "--[no-]warnings",
10
+ default: true,
11
+ desc: "Turn on Ruby warnings (defaults to true)"
12
+ flag :name, "-n", "--name PATTERN",
13
+ desc: "Filter run on /regexp/ or string."
14
+ flag :exclude, "-e", "--exclude PATTERN",
15
+ desc: "Exclude /regexp/ or string from run."
16
+ flag :recursive, "--[no-]recursive", default: true,
17
+ desc: "Recursively test subtools (default is true)"
18
+ flag :tool, "-t TOOL", "--tool TOOL", default: "",
19
+ desc: "Run tests only for tools under the given path"
20
+
21
+ include :exec
22
+ include :gems
23
+ include :terminal
24
+
25
+ def run
26
+ gem "minitest", "~> 5.0"
27
+ ::Dir.chdir(tool_dir)
28
+ test_files = find_test_files
29
+ result = exec_ruby(ruby_args, in: :controller, log_cmd: "Starting minitest...") do |controller|
30
+ controller.in.puts("gem 'minitest', '~> 5.0'")
31
+ controller.in.puts("require 'minitest/autorun'")
32
+ controller.in.puts("require 'toys'")
33
+ controller.in.puts("require 'toys/testing'")
34
+ test_files.each do |file|
35
+ controller.in.puts("load '#{file}'")
36
+ end
37
+ end
38
+ if result.error?
39
+ logger.error("Minitest failed!")
40
+ exit(result.exit_code)
41
+ end
42
+ end
43
+
44
+ def find_test_files
45
+ glob = ".test/**/test_*.rb"
46
+ glob = "**/#{glob}" if recursive
47
+ test_files = ::Dir.glob(glob)
48
+ if test_files.empty?
49
+ logger.warn("No test files found")
50
+ exit
51
+ end
52
+ test_files.each do |file|
53
+ logger.info("Loading: #{file}")
54
+ end
55
+ test_files
56
+ end
57
+
58
+ def tool_dir
59
+ words = cli.loader.split_path(tool)
60
+ dir = base_dir
61
+ unless words.empty?
62
+ dir = ::File.join(dir, *words)
63
+ unless ::File.directory?(dir)
64
+ logger.warn("No such directory: #{dir}")
65
+ exit
66
+ end
67
+ end
68
+ dir
69
+ end
70
+
71
+ def base_dir
72
+ return ::File.absolute_path(directory) if directory
73
+ dir = ::Dir.getwd
74
+ loop do
75
+ candidate = ::File.join(dir, ::Toys::StandardCLI::CONFIG_DIR_NAME)
76
+ return candidate if ::File.directory?(candidate)
77
+ parent = ::File.dirname(dir)
78
+ if parent == dir
79
+ logger.error("Unable to find a Toys directory")
80
+ exit(1)
81
+ end
82
+ dir = parent
83
+ end
84
+ end
85
+
86
+ def ruby_args
87
+ args = []
88
+ args << "-w" if warnings
89
+ args << "-I#{::Toys::CORE_LIB_PATH}#{::File::PATH_SEPARATOR}#{::Toys::LIB_PATH}"
90
+ args << "-"
91
+ args << "--seed" << seed if seed
92
+ args << "--verbose" if verbosity.positive?
93
+ args << "--name" << name if name
94
+ args << "--exclude" << exclude if exclude
95
+ args
96
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ desc "Update Toys if a newer version is available"
4
+
5
+ long_desc "Checks rubygems for a newer version of Toys. If one is available, downloads" \
6
+ " and installs it."
7
+
8
+ flag :yes, "-y", "--yes", desc: "Do not ask for interactive confirmation"
9
+
10
+ include :exec
11
+ include :terminal
12
+
13
+ def run
14
+ require "rubygems"
15
+ configure_exec(exit_on_nonzero_status: true)
16
+ version_info = spinner(leading_text: "Checking rubygems for the latest release... ",
17
+ final_text: "Done.\n") do
18
+ capture(["gem", "list", "-q", "-r", "-e", "toys"])
19
+ end
20
+ if version_info =~ /toys\s\((.+)\)/
21
+ latest_version = ::Gem::Version.new(::Regexp.last_match(1))
22
+ cur_version = ::Gem::Version.new(::Toys::VERSION)
23
+ if latest_version > cur_version
24
+ prompt = "Update Toys from #{cur_version} to #{latest_version}? "
25
+ exit(1) unless yes || confirm(prompt, default: true)
26
+ result = spinner(leading_text: "Installing Toys version #{latest_version}... ",
27
+ final_text: "Done.\n") do
28
+ exec(["gem", "install", "toys", "--version", latest_version.to_s],
29
+ out: :capture, err: :capture)
30
+ end
31
+ if result.error?
32
+ puts(result.captured_out + result.captured_err)
33
+ puts("Toys failed to install version #{latest_version}", :red, :bold)
34
+ exit(1)
35
+ end
36
+ puts("Toys successfully installed version #{latest_version}", :green, :bold)
37
+ elsif latest_version < cur_version
38
+ puts("Toys is already at experimental version #{cur_version}, which is later than" \
39
+ " the latest released version #{latest_version}",
40
+ :yellow, :bold)
41
+ else
42
+ puts("Toys is already at the latest version: #{latest_version}", :green, :bold)
43
+ end
44
+ else
45
+ puts("Could not get latest Toys version", :red, :bold)
46
+ exit(1)
47
+ end
48
+ end
data/docs/guide.md CHANGED
@@ -1118,8 +1118,8 @@ run Toys with no arguments:
1118
1118
 
1119
1119
  The root tool will display the overall help screen for Toys.
1120
1120
 
1121
- Although it is a less common pattern, it is possible for a tool that has
1122
- subtools to have its own `run` method:
1121
+ Although it is a less common pattern, it is possible for a tool to have its own
1122
+ `run` method even if it has subtools:
1123
1123
 
1124
1124
  tool "test" do
1125
1125
  def run
@@ -1142,12 +1142,100 @@ subtools to have its own `run` method:
1142
1142
  Now running `toys test` will run its own implementation.
1143
1143
 
1144
1144
  (Yes, it is even possible to write a `run` method for the root tool. I don't
1145
- recommend doing so, because then you lose the root tool's useful default
1145
+ recommend doing so, because you would lose the root tool's useful default
1146
1146
  implementation that lists all your tools.)
1147
1147
 
1148
1148
  Toys allows subtools to be nested arbitrarily deep. In practice, however, more
1149
1149
  than two or three levels of hierarchy can be confusing to use.
1150
1150
 
1151
+ ### Defining subtools using classes
1152
+
1153
+ It is also possible to define subtools by subclassing `Toys::Tool`. The class
1154
+ will support the same DSL as a `tool` block would. For example, this is what
1155
+ our greet tool would look like as a class:
1156
+
1157
+ class Greet < Toys::Tool
1158
+ optional_arg :whom, default: "world"
1159
+ flag :shout, "-s", "--shout"
1160
+
1161
+ def run
1162
+ greeting = "Hello, #{whom}!"
1163
+ greeting = greeting.upcase if shout
1164
+ puts greeting
1165
+ end
1166
+ end
1167
+
1168
+ Defining tools as clases is useful if you want your Toys files to look a bit
1169
+ more like normal Ruby or you want to avoid long blocks. Additionally, they can
1170
+ be useful to scope constants, which otherwise would be visible throughout the
1171
+ entire file, as noted in the [section on using constants](#Using_constants).
1172
+
1173
+ When you define a tool as a class, Toys infers the tool name by converting the
1174
+ class name to "kebab-case". For example, the `Greet` class above would define a
1175
+ tool called `greet`. If the class were called `GreetMany`, the tool would be
1176
+ called `greet-many`. If you need to override this behavior or set a tool name
1177
+ that is not possible from legal class names, pass the desired tool name to the
1178
+ `Toys.Tool` method like this:
1179
+
1180
+ class Greet < Toys.Tool("_my_greet")
1181
+ optional_arg :whom, default: "world"
1182
+ flag :shout, "-s", "--shout"
1183
+
1184
+ def run
1185
+ greeting = "Hello, #{whom}!"
1186
+ greeting = greeting.upcase if shout
1187
+ puts greeting
1188
+ end
1189
+ end
1190
+
1191
+ You can create subtools by nesting classes:
1192
+
1193
+ class Test < Toys::Tool
1194
+ class Unit < Toys::Tool
1195
+ def run
1196
+ puts "run only unit tests here..."
1197
+ end
1198
+ end
1199
+
1200
+ class Integration < Toys::Tool
1201
+ def run
1202
+ puts "run only integration tests here..."
1203
+ end
1204
+ end
1205
+ end
1206
+
1207
+ The above defines `test unit` and `test integration` tools as expected.
1208
+
1209
+ Finally, you may not nest a class-based tool inside a block-based tool. (This
1210
+ is because Ruby's constant definition rules would put such a class in an
1211
+ unexpected namespace, leading to potential clashes that would be difficult to
1212
+ diagnose.) It is, however, permissible to nest a block-based tool inside a
1213
+ class-based tool.
1214
+
1215
+ Hence, this is not allowed, and will result in an exception:
1216
+
1217
+ tool "test" do
1218
+ # This will raise Toys::ToolDefinitionError...
1219
+ class Unit < Toys::Tool
1220
+ def run
1221
+ puts "run unit tests..."
1222
+ end
1223
+ end
1224
+ end
1225
+
1226
+ But this is legal:
1227
+
1228
+ class Test < Toys::Tool
1229
+ tool "unit" do
1230
+ def run
1231
+ puts "run unit tests..."
1232
+ end
1233
+ end
1234
+ end
1235
+
1236
+ In general, though, as a best practice, I recommend against mixing the two
1237
+ styles. Define tools either as classes or as blocks, not both.
1238
+
1151
1239
  ## Understanding Toys files
1152
1240
 
1153
1241
  Toys commands are defined in Toys files. We covered the basic syntax for these
@@ -139,7 +139,7 @@ module Toys
139
139
  add_search_path_hierarchy(start: cur_dir, terminate: global_dirs)
140
140
  global_dirs.each { |path| add_search_path(path) }
141
141
  builtins_path = ::File.join(::File.dirname(::File.dirname(__dir__)), "builtins")
142
- add_config_path(builtins_path)
142
+ add_config_path(builtins_path, source_name: "(builtin tools)", context_directory: nil)
143
143
  self
144
144
  end
145
145
 
@@ -157,8 +157,6 @@ module Toys
157
157
  end
158
158
  end
159
159
 
160
- # rubocop:disable Metrics/MethodLength
161
-
162
160
  ##
163
161
  # Returns the middleware for the standard Toys CLI.
164
162
  #
@@ -179,6 +177,7 @@ module Toys
179
177
  default_recursive: true,
180
178
  allow_root_args: true,
181
179
  show_source_path: true,
180
+ separate_sources: true,
182
181
  use_less: true,
183
182
  fallback_execution: true),
184
183
  Middleware.spec(:show_root_version,
@@ -189,8 +188,6 @@ module Toys
189
188
  ]
190
189
  end
191
190
 
192
- # rubocop:enable Metrics/MethodLength
193
-
194
191
  ##
195
192
  # Returns the default set of global config directories.
196
193
  #
@@ -23,10 +23,13 @@ module Toys
23
23
  # to clean. You can also include the symbol `:gitignore` which will
24
24
  # clean all items covered by `.gitignore` files, if contained in a
25
25
  # git working tree.
26
+ # @param context_directory [String] A custom context directory to use
27
+ # when executing this tool.
26
28
  #
27
- def initialize(name: nil, paths: [])
29
+ def initialize(name: nil, paths: [], context_directory: nil)
28
30
  @name = name
29
31
  @paths = paths
32
+ @context_directory = context_directory
30
33
  end
31
34
 
32
35
  ##
@@ -45,6 +48,17 @@ module Toys
45
48
  #
46
49
  attr_writer :paths
47
50
 
51
+ ##
52
+ # Custom context directory for this tool.
53
+ #
54
+ # @param value [String]
55
+ # @return [String]
56
+ #
57
+ attr_writer :context_directory
58
+
59
+ # @private
60
+ attr_reader :context_directory
61
+
48
62
  # @private
49
63
  def paths
50
64
  Array(@paths)
@@ -59,6 +73,8 @@ module Toys
59
73
  tool(template.name) do
60
74
  desc "Clean built files and directories."
61
75
 
76
+ set_context_directory template.context_directory if template.context_directory
77
+
62
78
  static :template_paths, template.paths
63
79
 
64
80
  include :fileutils