toys 0.9.4 → 0.10.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: 61f79d9f7f044f396ffee09be4d6c3b9e4c758528b2fbc217c951d73e15f8750
4
- data.tar.gz: 454e1c492241769432ac58782d52cc5c87b106ee18a16fc083c6ebd09f97a0e5
3
+ metadata.gz: 7a4ebb08f9d26fc9c6ac9ef461d8e4e25800809719b0ee80f1560ce55824b35a
4
+ data.tar.gz: bcbfda3dd04eec615e1b587bfa8736a5f99fd9620d53228462fb5142076ec206
5
5
  SHA512:
6
- metadata.gz: 9dadef613e97d409603869f8c812c7a690e467c7719dcd9893d7d0efff627954f07ff237cae99742efbef5e1988e55c288a1fc26d265fa15ac1b0b2b485a12c9
7
- data.tar.gz: 5194bb826ca09d62f4a48eb78122dcf8d3653bfa97107e8042748357226a4e37d5c8fd06970d714c29a053a61a312ef3461dc9c7ea5ba63140fe730a884d6faf
6
+ metadata.gz: 030426d1d840fa13e987d1beaecd04a9d8ffa39d36316410ca69edb508ce889236cab069bb758e070235b2b05c75bc26ba7f9d3612bc2a1c693d974ce4e44015
7
+ data.tar.gz: a05d11cd67d9965517fe238d2f1d54ec8fcdd139eea39b18e78c9c1f20d4beeeacf21db31504877f6fe036c7d82025a43556989fa45ca6dd859f218754a9696f
data/.yardopts CHANGED
@@ -3,7 +3,8 @@
3
3
  --markup=markdown
4
4
  --markup-provider redcarpet
5
5
  --main=README.md
6
- ./lib/**/*.rb
6
+ ./lib/toys/**/*.rb
7
+ ./lib/toys.rb
7
8
  -
8
9
  README.md
9
10
  LICENSE.md
@@ -1,5 +1,21 @@
1
1
  # Release History
2
2
 
3
+ ### 0.10.0 / 2020-02-24
4
+
5
+ * ADDED: `:bundler` mixin that installs and sets up a bundle for the tool
6
+ * ADDED: `bundler` options in the standard templates, to run those tools in a bundle
7
+ * ADDED: `subtool_apply` directive which applies a block to all subtools.
8
+ * ADDED: Add `.lib` directories to the Ruby load path when executing a tool.
9
+ * ADDED: `toys_version?` and `toys_version!` directives that check against version requirements.
10
+ * ADDED: `exec_separate_tool` and `capture_separate_tool` methods in the `:exec` mixin, to support executing tools in a separate process without forking
11
+ * IMPROVED: `long_desc` directive can now read the description from a text file.
12
+ * IMPROVED: The `tool` directive can take delimited strings as tool names.
13
+ * IMPROVED: Subtool blocks aren't actually executed unless the tool is needed.
14
+ * CHANGED: Added `on_missing` and `on_conflict` arguments to `Toys::Utils::Gems` constructor (which also affects the `:gems` mixin), and deprecated `suppress_confirm` and `default_confirm`.
15
+ * CHANGED: Tightened `rdoc` template's default gem version to `~> 6.1.0`.
16
+ * FIXED: `rdoc` template crashed if any nonstandard options were given.
17
+ * FIXED: `rubocop` template would abort prematurely if standard streams were redirected.
18
+
3
19
  ### 0.9.4 / 2020-01-26
4
20
 
5
21
  * FIXED: Crash in the loader when a non-ruby file appears in a toys directory
data/LICENSE.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # License
2
2
 
3
- Copyright 2019 Daniel Azuma
3
+ Copyright 2019-2020 Daniel Azuma and the Toys contributors
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -91,7 +91,7 @@ automatically generates a full help screen, which you can view using the
91
91
  Toys searches up the directory hierarchy for Toys files. So it will find this
92
92
  `.toys.rb` if you are located in this directory or any subdirectory. It will
93
93
  also read multiple files if it finds them, so you can "scope" your tools more
94
- specifically or generally by locating them in your directory hierarchy.
94
+ specifically or generally by locating them in your directory hierarchy.
95
95
 
96
96
  If you want to define "global" tools that apply anywhere, write a Toys file
97
97
  either in your home directory, or in the system configuration directory
@@ -204,7 +204,7 @@ You'll notice some diagnostic log output. Toys provides a standard Ruby Logger
204
204
  for each tool, and you can use it to emit diagnostic logs directly as
205
205
  demonstrated in the example. Some other Toys features might also emit log
206
206
  entries: the `:exec` mixin, for example, by default logs every external command
207
- it runs (although this can be customized).
207
+ it runs (although this can be customized).
208
208
 
209
209
  By default, only warnings and higher severity logs are displayed, but you can
210
210
  change that by applying the `--verbose` or `--quiet` flags as we have done
@@ -318,7 +318,7 @@ Toys scripts instead of Rakefiles.
318
318
 
319
319
  ## License
320
320
 
321
- Copyright 2019 Daniel Azuma
321
+ Copyright 2019-2020 Daniel Azuma and the Toys contributors
322
322
 
323
323
  Permission is hereby granted, free of charge, to any person obtaining a copy
324
324
  of this software and associated documentation files (the "Software"), to deal
data/bin/toys CHANGED
@@ -1,27 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- # Copyright 2019 Daniel Azuma
5
- #
6
- # Permission is hereby granted, free of charge, to any person obtaining a copy
7
- # of this software and associated documentation files (the "Software"), to deal
8
- # in the Software without restriction, including without limitation the rights
9
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
- # copies of the Software, and to permit persons to whom the Software is
11
- # furnished to do so, subject to the following conditions:
12
- #
13
- # The above copyright notice and this permission notice shall be included in
14
- # all copies or substantial portions of the Software.
15
- #
16
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
- # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
22
- # IN THE SOFTWARE.
23
- ;
24
-
25
4
  ::ENV["TOYS_BIN_PATH"] ||= ::File.absolute_path(__FILE__)
26
5
 
27
6
  $LOAD_PATH.unshift(::File.absolute_path(::File.join(::File.dirname(__dir__), "lib")))
@@ -1,25 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright 2019 Daniel Azuma
4
- #
5
- # Permission is hereby granted, free of charge, to any person obtaining a copy
6
- # of this software and associated documentation files (the "Software"), to deal
7
- # in the Software without restriction, including without limitation the rights
8
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- # copies of the Software, and to permit persons to whom the Software is
10
- # furnished to do so, subject to the following conditions:
11
- #
12
- # The above copyright notice and this permission notice shall be included in
13
- # all copies or substantial portions of the Software.
14
- #
15
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20
- # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21
- # IN THE SOFTWARE.
22
-
23
3
  desc "Run multiple tools in order"
24
4
 
25
5
  long_desc \
@@ -1,25 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright 2019 Daniel Azuma
4
- #
5
- # Permission is hereby granted, free of charge, to any person obtaining a copy
6
- # of this software and associated documentation files (the "Software"), to deal
7
- # in the Software without restriction, including without limitation the rights
8
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- # copies of the Software, and to permit persons to whom the Software is
10
- # furnished to do so, subject to the following conditions:
11
- #
12
- # The above copyright notice and this permission notice shall be included in
13
- # all copies or substantial portions of the Software.
14
- #
15
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20
- # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21
- # IN THE SOFTWARE.
22
-
23
3
  desc "A set of system commands for Toys"
24
4
 
25
5
  long_desc "Contains tools that inspect, configure, and update Toys itself."
@@ -44,6 +24,7 @@ tool "update" do
44
24
  include :terminal
45
25
 
46
26
  def run
27
+ require "rubygems"
47
28
  configure_exec(exit_on_nonzero_status: true)
48
29
  version_info = spinner(leading_text: "Checking rubygems for the latest release... ",
49
30
  final_text: "Done.\n") do
@@ -1698,7 +1698,7 @@ the module.
1698
1698
 
1699
1699
  ### Templates
1700
1700
 
1701
- One final way to share code is to expand a **template**.
1701
+ Another way to share code is to expand a **template**.
1702
1702
 
1703
1703
  A template is a class that inserts a bunch of lines into a Toys file. It is
1704
1704
  often used to "instantiate" prefabricated tools. For instance, Toys comes with
@@ -1841,15 +1841,102 @@ following in their Toys file:
1841
1841
  require "my_analysis"
1842
1842
  expand MyAnalysis::ToysTemplate
1843
1843
 
1844
- ### Preloading Ruby files
1844
+ ### Loading from a lib directory
1845
1845
 
1846
1846
  For more complicated tools, you might want to write normal Ruby modules and
1847
1847
  classes as helpers. Toys provides a way to write Ruby code outside of its DSL
1848
- and incorporate it into your tool definitions, using "preloaded" files.
1848
+ and `require` the code from your tool, using `.lib` directories.
1849
+
1850
+ To use `.lib` directories, you must define your tools inside a
1851
+ [Toys directory](#Toys_directories). When a tool is executed, it looks for
1852
+ directories called `.lib` in the Toys directory, and adds them to the Ruby load
1853
+ path. Your tool can thus call `require` to load helpers from any Ruby files in
1854
+ a `.lib` directory.
1855
+
1856
+ For example, take the following directory structure:
1857
+
1858
+ (current directory)
1859
+ |
1860
+ +- .toys/
1861
+ |
1862
+ +- .lib/ <-- available when a tool is executed
1863
+ | |
1864
+ | +- greeting_helper.rb
1865
+ |
1866
+ +- greet.rb
1867
+
1868
+ The `greeting_helper.rb` file can contain any Ruby code.
1869
+
1870
+ # .toys/.lib/greeting_helper.rb
1871
+
1872
+ module GreetingHelper
1873
+ def self.make_greeting(whom)
1874
+ "Hello, #{whom}!"
1875
+ end
1876
+ end
1877
+
1878
+ Now you can `require "greeting_helper"` in your `greet` tool.
1879
+
1880
+ # .toys/greet.rb
1881
+
1882
+ tool "greet" do
1883
+ optional_arg :whom, default: "world", desc: "Whom to greet."
1884
+ def run
1885
+ require "greeting_helper"
1886
+ puts GreetingHelper.make_greeting(whom)
1887
+ end
1888
+ end
1889
+
1890
+ Note that `.lib` directories are available only when your tool is being *run*,
1891
+ not when it is being defined. So any `require` statements should be located
1892
+ *inside* your `run` method.
1893
+
1894
+ tool "greet" do
1895
+ # Do not try to require the file here. Toys will not find it because
1896
+ # the tool is not yet being run.
1897
+ # require "greeting_helper" # ERRORS!
1898
+
1899
+ optional_arg :whom, default: "world", desc: "Whom to greet."
1900
+ def run
1901
+ # Require a helper file here, so it is loaded during tool execution.
1902
+ require "greeting_helper"
1903
+ # Now you can use classes defined in the helper
1904
+ puts GreetingHelper.make_greeting(whom)
1905
+ end
1906
+ end
1907
+
1908
+ If your Toys directory has subdirectories, lib directories will be prioritized
1909
+ by how close they are to the tool being executed. For example:
1910
+
1911
+ (current directory)
1912
+ |
1913
+ +- .toys/
1914
+ |
1915
+ +- .lib/ <-- available when any tool defined in this directory
1916
+ | | is executed
1917
+ | |
1918
+ | +- helper.rb <-- visible to "greet" but not "test unit"
1919
+ | |
1920
+ | +- helper2.rb <-- visible to both "greet" and "test unit"
1921
+ |
1922
+ +- greet.rb
1923
+ |
1924
+ +- test/
1925
+ |
1926
+ +- .lib/ <-- available only when tools under "test" are executed
1927
+ | |
1928
+ | +- helper.rb <-- overrides the other helper.rb when
1929
+ | "test unit" is executed
1930
+ |
1931
+ +- unit.rb
1932
+
1933
+ ### Preloading Ruby files
1849
1934
 
1850
- A preloaded file is loaded using the standard Ruby `require` mechanism, before
1851
- tools are defined. You can use such files to define Ruby classes, modules, and
1852
- other code that may be used and shared by your tools.
1935
+ You may also provide Ruby files that are "preloaded" before tools are defined.
1936
+ This is useful if those Ruby files are required by the tool definitions
1937
+ themselves. Like files in the `.lib` directory, preloaded files can define Ruby
1938
+ classes, modules, and other code. But preloaded files *automatically* loaded
1939
+ (i.e. you do not `require` them explicitly) *before* your tools are defined.
1853
1940
 
1854
1941
  To use preloaded files, you must define your tools inside a
1855
1942
  [Toys directory](#Toys_directories). Before any tools inside a directory are
@@ -1899,58 +1986,174 @@ loaded first before the `.preload` directory contents.
1899
1986
 
1900
1987
  ## Using third-party gems
1901
1988
 
1902
- The Ruby community has developed many resources for building command line
1903
- tools, including a variety of gems that provide alternate command line parsing,
1904
- control of the ANSI terminal, formatted output such as trees and tables, and
1905
- effects such as hidden input, progress bars, various subprocess tools, and so
1906
- forth.
1989
+ The toys executable itself uses only two gems: **toys** and **toys-core**. It
1990
+ has no other gem dependencies. However, the Ruby community has developed many
1991
+ resources for building command line tools, including a variety of gems that
1992
+ provide alternate command line parsing, control of the ANSI terminal, formatted
1993
+ output such as trees and tables, and effects such as hidden input, progress
1994
+ bars, various ways to spawn and control subprocesses, and so forth. You may
1995
+ find some of these gems useful when writing your tools. Additionally, if you
1996
+ are using Toys for your project's build scripts, it might be necessary to
1997
+ install your bundle when running some tools.
1907
1998
 
1908
- This section describes how to use a third-party gem in your tool.
1999
+ This section describes how to manage and use external gems with Toys. Note that
2000
+ running Toys with `bundle exec` is generally *not* recommended. We'll discuss
2001
+ the reasons for this, and what you can do instead.
1909
2002
 
1910
- ### Activating gems
2003
+ ### Why not "bundle exec toys"
1911
2004
 
1912
- The toys executable itself uses only two gems: **toys** and **toys-core**. It
1913
- has no other gem dependencies. However, if you want to use a third-party gem in
1914
- your tool, Toys provides a convenient mechanism to ensure the gem is installed.
1915
-
1916
- (Note that you generally do not use bundler when running Toys; i.e. you do not
1917
- normally run `bundle exec toys`. This is because Toys is intended as a
1918
- general-purpose tool that can be run anywhere. It would be inconvenient to have
1919
- to include Gemfiles in every directory where you might want to run it.)
1920
-
1921
- To access the gem services, include the `:gems` mixin. This mixin adds a `gem`
1922
- directive to ensure a gem is installed and activated when you're defining a
1923
- tool, and a `gem` method to ensure a gem is available when you're running a
1924
- tool.
1925
-
1926
- Both the `gem` directive and the `gem` method take the name of the gem, and an
1927
- optional set of version requirements. If a gem matching the given version
1928
- requirements is installed, it is activated. If not, the gem is installed (which
1929
- the user can confirm or abort). Or, if Toys is being run in a bundle, a message
1930
- is printed informing the user that they need to add the gem to their Gemfile.
1931
-
1932
- For example, here's a way to configure a tool with flags for each of the
1933
- HighLine styles. Because highline is needed to decide what flags to define, we
1934
- use the `gem` directive to ensure highline is installed while the tool is being
1935
- defined.
2005
+ [Bundler](https://bundler.io) is often used when a command-line program depends
2006
+ on external gems. You specify the gem dependencies in a `Gemfile`, use bundler
2007
+ to resolve and install those dependencies, and then run the program prefixed by
2008
+ `bundle exec` to ensure those dependencies are in the Ruby load path. When
2009
+ running a Rake task, for example, it is almost automatic for many Ruby
2010
+ developers to run `bundle exec rake my-task`.
2011
+
2012
+ So why not simply run `bundle exec toys my-tool`?
2013
+
2014
+ In simple cases, this will work just fine. However, Toys is a much more
2015
+ flexible tool than Rake, and it covers two cases that are not well served by
2016
+ `bundle exec`.
2017
+
2018
+ First, Toys lets you define *global tools* that are defined in your home
2019
+ directory or system config directory. (See the previous section on
2020
+ [the Toys search path](#The_Toys_search_path).) These tools are global, and can
2021
+ be called from anywhere. But if they have gem dependencies, it might not be
2022
+ feasible for their Gemfiles to be present in every directory from which you
2023
+ might want to run them.
2024
+
2025
+ Second, it's possible for a variety of tools to be available together,
2026
+ including both locally and globally defined, with potentially different sets of
2027
+ dependencies. With `bundle exec`, you must choose beforehand which bundle to
2028
+ use.
2029
+
2030
+ Although traditional `bundle exec` doesn't always work, Toys provides ways for
2031
+ individual tools to manage their own gem dependencies.
2032
+
2033
+ ### Using bundler with a tool
2034
+
2035
+ The recommended way for a Toys tool to depend on third-party gems is for the
2036
+ tool to set up Bundler when it runs. The tool can load a bundle from an
2037
+ appropriate `Gemfile` at runtime, by including the `:bundler` mixin.
2038
+
2039
+ Here's an example. Suppose you are writing a tool in a Rails app. It might, for
2040
+ example, load the Rails environment and populate some data into the database.
2041
+ Hence, it needs to run with your app's bundle, represented by your app's
2042
+ `Gemfile`.
2043
+
2044
+ Simply `include :bundler` in your tool definition:
2045
+
2046
+ tool "populate-data" do
2047
+ include :bundler
1936
2048
 
1937
- tool "highline-styles-demo" do
1938
- include :gems
1939
- gem "highline", "~> 2.0"
1940
- require "highline"
1941
- HighLine::BuiltinStyles::STYLES.each do |style|
1942
- style = style.downcase
1943
- flag style.to_sym, "--#{style}", "Apply #{style} to the text"
1944
- end
1945
2049
  def run
1946
- # ...
2050
+ # The bundle will be set up before the tool is run,
2051
+ # so you can now run code that depends on rails:
2052
+ require "./config/environment.rb"
2053
+ # ... etc.
1947
2054
  end
1948
2055
  end
1949
2056
 
2057
+ When the `:bundler` mixin is included in a tool, it installs a
2058
+ [mixin initializer](#Mixin_initializers) that calls `Bundler.setup` when the
2059
+ tool is *executed*. This assumes the bundle is already installed, and brings
2060
+ the appropriate gems into the Ruby load path. That is, it's basically the same
2061
+ as `bundle exec`, but it applies only to the running tool.
2062
+
2063
+ In many cases, you might find that bundler is needed for many or most of the
2064
+ tools you write for a particular project. In this case, you might find it
2065
+ convenient to use
2066
+ [Toys::DSL::Tool#subtool_apply](https://dazuma.github.io/toys/gems/toys-core/latest/Toys/DSL/Tool#subtool_apply-instance_method)
2067
+ to include the bundle in all your tools. For example:
2068
+
2069
+ # Include bundler in every tool under this one
2070
+ subtool_apply do
2071
+ include :bundler
2072
+ end
2073
+
2074
+ tool "one-tool" do
2075
+ # This tool will run with the bundle
2076
+ # ...
2077
+ end
2078
+
2079
+ tool "another-tool" do
2080
+ # So will this tool
2081
+ # ...
2082
+ end
2083
+
2084
+ See the section on
2085
+ [applying directives to multiple tools](#Applying_directives_to_multiple_tools)
2086
+ for more information on `subtool_apply`.
2087
+
2088
+ #### Bundler options
2089
+
2090
+ By default, the `:bundler` mixin will look for a `Gemfile` within the `.toys`
2091
+ directory (if your tool is defined in one), and if one is not found there,
2092
+ within the [context directory](#The_context_directory) (the directory
2093
+ containing your `.toys` directory or `.toys.rb`file), and if one still is not
2094
+ found, in the current working directory. You can change this behavior by
2095
+ passing an option to the `:bundler` mixin. For example, you can search only the
2096
+ current working directory by passing `search_dirs: :current` as such:
2097
+
2098
+ tool "populate-data" do
2099
+ include :bundler, search_dirs: :current
2100
+ # etc...
2101
+ end
2102
+
2103
+ You can also pass a specific directory path to this option.
2104
+
2105
+ If the bundle is not installed, or is out of date, Toys will ask you whether
2106
+ you want it to install the bundle first before running the tool. A tool can
2107
+ also choose to install the bundle without prompting, or simply to raise an
2108
+ error, by passing another option to the `:bundler` mixin. For example, to
2109
+ simply install the bundle without asking for confirmation:
2110
+
2111
+ tool "populate-data" do
2112
+ include :bundler, on_missing: :install
2113
+ # etc...
2114
+ end
2115
+
2116
+ See the documentation for
2117
+ [Toys::StandardMixins::Bundler](https://dazuma.github.io/toys/gems/toys-core/latest/Toys/StandardMixins/Bundler)
2118
+ for more information about bundler options.
2119
+
2120
+ #### Solving bundle conflicts
2121
+
2122
+ It is important to understand that the `:bundler` mixin installs the bundle
2123
+ when the tool *executes*, rather than when the tool is defined. Gems in the
2124
+ bundle will not be available during tool definition, so for example you
2125
+ *cannot* reference bundled gems when you are setting up the tool's flags,
2126
+ description, or other directives. This is so that Toys can define tools with
2127
+ competing bundles. Your Rails app's tools can use that app's bundle, while your
2128
+ global tools can use a different bundle. They will not conflict because Toys
2129
+ will not actually load a bundle until one or the other tool is executed. (This
2130
+ is of course different from using `bundle exec`, which chooses and loads a
2131
+ bundle before even starting Toys.)
2132
+
2133
+ If a *different* bundle (i.e. a different `Gemfile`) is already in effect when
2134
+ a tool is run, then the `:bundler` mixin will raise an error. Ruby will not let
2135
+ you set up two different bundles at the same time. This might happen, for
2136
+ example, if you use `bundle exec` to run Toys, but the tool you are running
2137
+ asks for a different bundle---one more reason not to use `bundle exec` with
2138
+ Toys.
2139
+
2140
+ It might also happen if one tool that uses one bundle, *calls* a tool that uses
2141
+ a different bundle. If you need to do this, use the
2142
+ [Toys::StandardMixins::Exec#exec_separate_tool](https://dazuma.github.io/toys/gems/toys-core/latest/Toys/StandardMixins/Exec#exec_separate_tool-instance_method)
2143
+ method from the `:exec` mixin, to call the tool. This method spawns a separate
2144
+ process with a clean Bundler setup for running the tool.
2145
+
2146
+ ### Activating gems directly
2147
+
2148
+ Although we recommend the `:bundler` mixin for most cases, it is also possible
2149
+ for a tool to install individual gems, using the `:gems` mixin. This mixin
2150
+ provides a way for a tool to install individual gems without using Bundler.
2151
+
1950
2152
  Here's an example tool that just runs `rake`. Because it requires rake to be
1951
- installed in order to *run* the tool, we call the
2153
+ installed in order to run the tool, we call the
1952
2154
  [Toys::StandardMixins::Gems#gem](https://dazuma.github.io/toys/gems/toys-core/latest/Toys/StandardMixins/Gems#gem-instance_method)
1953
- method provided by the `:gems` mixin when running.
2155
+ method (which is provided by the `:gems` mixin) at the beginning of the `run`
2156
+ method:
1954
2157
 
1955
2158
  tool "rake" do
1956
2159
  include :gems
@@ -1961,10 +2164,39 @@ method provided by the `:gems` mixin when running.
1961
2164
  end
1962
2165
  end
1963
2166
 
2167
+ The `gem` method takes the name of the gem, and an optional set of version
2168
+ requirements. If a gem matching the given version requirements is installed, it
2169
+ is activated. If not, the gem is installed (which the user can confirm or
2170
+ abort). Or, if Toys is being run in a bundle, a message is printed informing
2171
+ the user that they need to add the gem to their Gemfile.
2172
+
1964
2173
  If a gem satisfying the given version constraints is already activated, it
1965
2174
  remains active. If a gem with a conflicting version is already activated, an
1966
2175
  exception is raised.
1967
2176
 
2177
+ The `:gems` mixin also provides a `gem` *directive* that ensures a gem is
2178
+ installed while the tool is being defined. In general, we recommend avoiding
2179
+ doing this, because it could make your tool incompatible with another tool that
2180
+ might need a competing gem during its definition. Toys would not be able to
2181
+ define both tools together. However, occasionally it might be useful.
2182
+
2183
+ Here's an example tool with flags for each of the HighLine styles. Because
2184
+ highline is needed to decide what flags to define, we use the `gem` directive
2185
+ to ensure highline is installed while the tool is being defined.
2186
+
2187
+ tool "highline-styles-demo" do
2188
+ include :gems
2189
+ gem "highline", "~> 2.0"
2190
+ require "highline"
2191
+ HighLine::BuiltinStyles::STYLES.each do |style|
2192
+ style = style.downcase
2193
+ flag style.to_sym, "--#{style}", "Apply #{style} to the text"
2194
+ end
2195
+ def run
2196
+ # ...
2197
+ end
2198
+ end
2199
+
1968
2200
  If you are not in the Toys DSL context—for example from a class-based
1969
2201
  mixin—you should use
1970
2202
  [Toys::Utils::Gems.activate](https://dazuma.github.io/toys/gems/toys-core/latest/Toys/Utils/Gems#activate-class_method)
@@ -1984,8 +2216,9 @@ you, whereas Rubygems will just throw an exception.
1984
2216
 
1985
2217
  ### Useful gems
1986
2218
 
1987
- Now that you know how to ensure a gem is installed, let's look at some third-
1988
- party gems that you might find useful when writing tools.
2219
+ Now that you know how to ensure a gem is installed, either individually or as
2220
+ part of a bundle, let's look at some third-party gems that you might find
2221
+ useful when writing tools.
1989
2222
 
1990
2223
  We already saw how to use the **highline** gem. Highline generally provides two
1991
2224
  features: terminal styling, and prompts. For these capabilities and many more,
@@ -1998,7 +2231,6 @@ To produce styled output, consider
1998
2231
 
1999
2232
  tool "fancy-output" do
2000
2233
  def run
2001
- gem "pastel", "~> 0.7"
2002
2234
  require "pastel"
2003
2235
  pastel = Pastel.new
2004
2236
  puts pastel.red("Rubies!")
@@ -2010,7 +2242,6 @@ To create rich user prompts, consider
2010
2242
 
2011
2243
  tool "favorite-language" do
2012
2244
  def run
2013
- gem "tty-prompt", "~> 0.16"
2014
2245
  require "tty-prompt"
2015
2246
  prompt = TTY::Prompt.new
2016
2247
  lang = prompt.select("What is your favorite language?",
@@ -2024,7 +2255,6 @@ To create tabular output, consider
2024
2255
 
2025
2256
  tool "matrix" do
2026
2257
  def run
2027
- gem "tty-table", "~> 0.10"
2028
2258
  require "tty-table"
2029
2259
  table = TTY::Table.new(["Language", "Creator"],
2030
2260
  [["Ruby", "Matz"],
@@ -2042,7 +2272,6 @@ non-deterministic.
2042
2272
 
2043
2273
  tool "waiting" do
2044
2274
  def run
2045
- gem "tty-progressbar", "~> 0.15"
2046
2275
  require "tty-progressbar"
2047
2276
  bar = TTY::ProgressBar.new("Waiting [:bar]", total: 30)
2048
2277
  30.times do
@@ -2418,6 +2647,52 @@ a namespace to delegate to one of its subtools:
2418
2647
 
2419
2648
  Now `toys test` delegates to, and thus has the same effect as `toys test unit`.
2420
2649
 
2650
+ ### Applying directives to multiple tools
2651
+
2652
+ Sometimes a group of tools are set up similarly or share a set of flags,
2653
+ mixins, or other directives. You can apply a set of directives to all subtools
2654
+ (recursively) of the current tool, using the
2655
+ [Toys::DSL::Tool#subtool_apply](https://dazuma.github.io/toys/gems/toys-core/latest/Toys/DSL/Tool#subtool_apply-instance_method)
2656
+ directive.
2657
+
2658
+ For example, it is common for tools to use the `:exec` built-in mixin to invoke
2659
+ external programs. You can use `subtool_apply` to ensure that the mixin is
2660
+ included in all subtools, so that you do not need to repeat the `include`
2661
+ directive in every tool.
2662
+
2663
+ subtool_apply do
2664
+ # Include the mixin only if the tool hasn't already done so
2665
+ unless include?(:exec)
2666
+ include :exec, exit_on_nonzero_status: true
2667
+ end
2668
+ end
2669
+
2670
+ tool "my-tool" do
2671
+ def run
2672
+ # This tool has access to methods defined by the :exec mixin
2673
+ # because the above block is applied to the tool
2674
+ sh "echo hello"
2675
+ end
2676
+ end
2677
+
2678
+ Importantly, `subtool_apply` blocks are "applied" at the *end* of a tool's
2679
+ definition. Therefore, when using `subtool_apply`, you have the ability to look
2680
+ at the current definition of the tool to decide whether to apply further
2681
+ changes. The `subtool_apply` block in the above example uses this technique; it
2682
+ checks whether the `:exec` mixin has already been included before attempting to
2683
+ include it. Thus, it is possible for a tool to "override" the inclusion, say,
2684
+ to use a different configuration:
2685
+
2686
+ tool "another-tool" do
2687
+ # Use a different configuration for the :exec mixin.
2688
+ # This "overrides" the subtool_apply block above.
2689
+ include :exec, exit_on_nonzero_status: false
2690
+ def run
2691
+ # This is run with exit_on_nonzero_status: false
2692
+ sh "echo hello"
2693
+ end
2694
+ end
2695
+
2421
2696
  ### Custom acceptors
2422
2697
 
2423
2698
  We saw earlier that flags and positional arguments can have acceptors, which
@@ -2827,8 +3102,8 @@ subdirectory:
2827
3102
 
2828
3103
  Rake handles this by actually changing the current working directory to the
2829
3104
  directory containing the active Rakefile. Toys, however, does not change the
2830
- working directory unless you tell it. You can make the `list-tests` tool work
2831
- correctly by changing the directory to the context directory (which is the
3105
+ working directory unless you tell it to. You can make the `list-tests` tool
3106
+ work correctly by changing the directory to the context directory (which is the
2832
3107
  directory containing the `.toys.rb` file, i.e. the `my-project` directory.)
2833
3108
 
2834
3109
  tool "list-tests" do
@@ -2856,7 +3131,7 @@ the entire toys directory structure. So if your tool definition is inside a
2856
3131
  |
2857
3132
  etc...
2858
3133
 
2859
- This behavior is particularly useful for build tools. Indeed, all the build
3134
+ This technique is particularly useful for build tools. Indeed, all the build
2860
3135
  tools described in the section on
2861
3136
  [Toys as a Rake Replacement](#Toys_as_a_Rake_replacement) automatically move
2862
3137
  into the context directory when they execute.