toys-core 0.13.1 → 0.14.1

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.
@@ -54,54 +54,88 @@ module Toys
54
54
  # options.
55
55
  #
56
56
  # Three general strategies are available for custom stream handling. First,
57
- # you may redirect to other streams such as files, IO objects, or Ruby
57
+ # you can redirect to other streams such as files, IO objects, or Ruby
58
58
  # strings. Some of these options map directly to options provided by the
59
- # `Process#spawn` method. Second, you may use a controller to manipulate
60
- # the streams programmatically. Third, you may capture output stream data
59
+ # `Process#spawn` method. Second, you can use a controller to manipulate
60
+ # the streams programmatically. Third, you can capture output stream data
61
61
  # and make it available in the result.
62
62
  #
63
63
  # Following is a full list of the stream handling options, along with how
64
64
  # to specify them using the `:in`, `:out`, and `:err` options.
65
65
  #
66
- # * **Inherit parent stream:** You may inherit the corresponding stream
66
+ # * **Inherit parent stream:** You can inherit the corresponding stream
67
67
  # in the parent process by passing `:inherit` as the option value. This
68
68
  # is the default if the subprocess is *not* run in the background.
69
- # * **Redirect to null:** You may redirect to a null stream by passing
69
+ #
70
+ # * **Redirect to null:** You can redirect to a null stream by passing
70
71
  # `:null` as the option value. This connects to a stream that is not
71
72
  # closed but contains no data, i.e. `/dev/null` on unix systems. This
72
73
  # is the default if the subprocess is run in the background.
73
- # * **Close the stream:** You may close the stream by passing `:close` as
74
+ #
75
+ # * **Close the stream:** You can close the stream by passing `:close` as
74
76
  # the option value. This is the same as passing `:close` to
75
77
  # `Process#spawn`.
76
- # * **Redirect to a file:** You may redirect to a file. This reads from
78
+ #
79
+ # * **Redirect to a file:** You can redirect to a file. This reads from
77
80
  # an existing file when connected to `:in`, and creates or appends to a
78
81
  # file when connected to `:out` or `:err`. To specify a file, use the
79
- # setting `[:file, "/path/to/file"]`. You may also, when writing a
82
+ # setting `[:file, "/path/to/file"]`. You can also, when writing a
80
83
  # file, append an optional mode and permission code to the array. For
81
84
  # example, `[:file, "/path/to/file", "a", 0644]`.
82
- # * **Redirect to an IO object:** You may redirect to an IO object in the
83
- # parent process, by passing the IO object as the option value. You may
85
+ #
86
+ # * **Redirect to an IO object:** You can redirect to an IO object in the
87
+ # parent process, by passing the IO object as the option value. You can
84
88
  # use any IO object. For example, you could connect the child's output
85
89
  # to the parent's error using `out: $stderr`, or you could connect to
86
90
  # an existing File stream. Unlike `Process#spawn`, this works for IO
87
91
  # objects that do not have a corresponding file descriptor (such as
88
92
  # StringIO objects). In such a case, a thread will be spawned to pipe
89
93
  # the IO data through to the child process.
90
- # * **Combine with another child stream:** You may redirect one child
94
+ #
95
+ # * **Redirect to a pipe:** You can redirect to a pipe created using
96
+ # `IO.pipe` (i.e. a two-element array of read and write IO objects) by
97
+ # passing the array as the option value. This will connect the
98
+ # appropriate IO (either read or write), and close it in the parent.
99
+ # Thus, you can connect only one process to each end. If you want more
100
+ # direct control over IO closing behavior, pass the IO object (i.e. the
101
+ # element of the pipe array) directly.
102
+ #
103
+ # * **Combine with another child stream:** You can redirect one child
91
104
  # output stream to another, to combine them. To merge the child's error
92
105
  # stream into its output stream, use `err: [:child, :out]`.
93
- # * **Read from a string:** You may pass a string to the input stream by
106
+ #
107
+ # * **Read from a string:** You can pass a string to the input stream by
94
108
  # setting `[:string, "the string"]`. This works only for `:in`.
95
- # * **Capture output stream:** You may capture a stream and make it
109
+ #
110
+ # * **Capture output stream:** You can capture a stream and make it
96
111
  # available on the {Toys::Utils::Exec::Result} object, using the
97
112
  # setting `:capture`. This works only for the `:out` and `:err`
98
113
  # streams.
99
- # * **Use the controller:** You may hook a stream to the controller using
114
+ #
115
+ # * **Use the controller:** You can hook a stream to the controller using
100
116
  # the setting `:controller`. You can then manipulate the stream via the
101
117
  # controller. If you pass a block to {Toys::Utils::Exec#exec}, it
102
118
  # yields the {Toys::Utils::Exec::Controller}, giving you access to
103
119
  # streams.
104
120
  #
121
+ # * **Make copies of an output stream:** You can "tee," or duplicate the
122
+ # `:out` or `:err` stream and redirect those copies to various
123
+ # destinations. To specify a tee, use the setting `[:tee, ...]` where
124
+ # the additional array elements include two or more of the following.
125
+ # See the corresponding documentation above for more detail.
126
+ # * `:inherit` to direct to the parent process's stream.
127
+ # * `:capture` to capture the stream and store it in the result.
128
+ # * `:controller` to direct the stream to the controller.
129
+ # * `[:file, "/path/to/file"]` to write to a file.
130
+ # * An `IO` or `StringIO` object.
131
+ # * An array of two `IO` objects representing a pipe
132
+ #
133
+ # Additionally, the last element of the array can be a hash of options.
134
+ # Supported options include:
135
+ # * `:buffer_size` The size of the memory buffer for each element of
136
+ # the tee. Larger buffers may allow higher throughput. The default
137
+ # is 65536.
138
+ #
105
139
  # ### Result handling
106
140
  #
107
141
  # A subprocess result is represented by a {Toys::Utils::Exec::Result}
@@ -171,7 +205,7 @@ module Toys
171
205
  # not present, the command is not logged.
172
206
  #
173
207
  # * `:log_level` (Integer,false) Level for logging the actual command.
174
- # Defaults to Logger::INFO if not present. You may also pass `false` to
208
+ # Defaults to Logger::INFO if not present. You can also pass `false` to
175
209
  # disable logging of the command.
176
210
  #
177
211
  # * `:log_cmd` (String) The string logged for the actual command.
@@ -227,7 +261,7 @@ module Toys
227
261
  end
228
262
 
229
263
  ##
230
- # Execute a command. The command may be given as a single string to pass
264
+ # Execute a command. The command can be given as a single string to pass
231
265
  # to a shell, or an array of strings indicating a posix command.
232
266
  #
233
267
  # If the process is not set to run in the background, and a block is
@@ -310,7 +344,7 @@ module Toys
310
344
  end
311
345
 
312
346
  ##
313
- # Execute a command. The command may be given as a single string to pass
347
+ # Execute a command. The command can be given as a single string to pass
314
348
  # to a shell, or an array of strings indicating a posix command.
315
349
  #
316
350
  # Captures standard out and returns it as a string.
@@ -400,7 +434,7 @@ module Toys
400
434
  # An object that controls a subprocess. This object is returned from an
401
435
  # execution running in the background, or is yielded to a control block
402
436
  # for an execution running in the foreground.
403
- # You may use this object to interact with the subcommand's streams,
437
+ # You can use this object to interact with the subcommand's streams,
404
438
  # send signals to the process, and get its result.
405
439
  #
406
440
  class Controller
@@ -502,8 +536,8 @@ module Toys
502
536
  ##
503
537
  # Redirects the remainder of the given stream.
504
538
  #
505
- # You may specify the stream as an IO or IO-like object, or as a file
506
- # specified by its path. If specifying a file, you may optionally
539
+ # You can specify the stream as an IO or IO-like object, or as a file
540
+ # specified by its path. If specifying a file, you can optionally
507
541
  # provide the mode and permissions for the call to `File#open`. You can
508
542
  # also specify the value `:null` to indicate the null file.
509
543
  #
@@ -540,8 +574,8 @@ module Toys
540
574
  ##
541
575
  # Redirects the remainder of the standard input stream.
542
576
  #
543
- # You may specify the stream as an IO or IO-like object, or as a file
544
- # specified by its path. If specifying a file, you may optionally
577
+ # You can specify the stream as an IO or IO-like object, or as a file
578
+ # specified by its path. If specifying a file, you can optionally
545
579
  # provide the mode and permissions for the call to `File#open`. You can
546
580
  # also specify the value `:null` to indicate the null file.
547
581
  #
@@ -559,8 +593,8 @@ module Toys
559
593
  ##
560
594
  # Redirects the remainder of the standard output stream.
561
595
  #
562
- # You may specify the stream as an IO or IO-like object, or as a file
563
- # specified by its path. If specifying a file, you may optionally
596
+ # You can specify the stream as an IO or IO-like object, or as a file
597
+ # specified by its path. If specifying a file, you can optionally
564
598
  # provide the mode and permissions for the call to `File#open`. You can
565
599
  # also specify the value `:null` to indicate the null file.
566
600
  #
@@ -578,8 +612,8 @@ module Toys
578
612
  ##
579
613
  # Redirects the remainder of the standard error stream.
580
614
  #
581
- # You may specify the stream as an IO or IO-like object, or as a file
582
- # specified by its path. If specifying a file, you may optionally
615
+ # You can specify the stream as an IO or IO-like object, or as a file
616
+ # specified by its path. If specifying a file, you can optionally
583
617
  # provide the mode and permissions for the call to `File#open`.
584
618
  #
585
619
  # After calling this, do not interact directly with the stream.
@@ -594,7 +628,7 @@ module Toys
594
628
  end
595
629
 
596
630
  ##
597
- # Send the given signal to the process. The signal may be specified
631
+ # Send the given signal to the process. The signal can be specified
598
632
  # by name or number.
599
633
  #
600
634
  # @param sig [Integer,String] The signal to send.
@@ -1091,7 +1125,7 @@ module Toys
1091
1125
  when :close
1092
1126
  :close
1093
1127
  else
1094
- stream if stream.respond_to?(:write)
1128
+ stream if stream.respond_to?(:read)
1095
1129
  end
1096
1130
  if in_stream == :close
1097
1131
  stdstream.close
@@ -1163,16 +1197,23 @@ module Toys
1163
1197
  end
1164
1198
 
1165
1199
  def interpret_in_array(setting)
1166
- case setting.first
1167
- when ::Symbol
1200
+ if setting.first.is_a?(::Symbol)
1168
1201
  setup_in_stream_of_type(setting.first, setting[1..-1])
1169
- when ::String
1202
+ elsif setting.first.is_a?(::String)
1170
1203
  setup_in_stream_of_type(:file, setting)
1204
+ elsif setting.size == 2 && setting.first.is_a?(::IO) && setting.last.is_a?(::IO)
1205
+ interpret_in_pipe(*setting)
1171
1206
  else
1172
1207
  raise "Unknown value for in: #{setting.inspect}"
1173
1208
  end
1174
1209
  end
1175
1210
 
1211
+ def interpret_in_pipe(reader, writer)
1212
+ @spawn_opts[:in] = reader
1213
+ @child_streams << reader
1214
+ @parent_streams << writer
1215
+ end
1216
+
1176
1217
  def setup_in_stream_of_type(type, args)
1177
1218
  case type
1178
1219
  when :controller
@@ -1199,7 +1240,7 @@ module Toys
1199
1240
  end
1200
1241
 
1201
1242
  def interpret_in_file(args)
1202
- raise "Expected only file name" unless args.size == 1 && args.first.is_a?(::String)
1243
+ raise "Expected only file name for in" unless args.size == 1 && args.first.is_a?(::String)
1203
1244
  @spawn_opts[:in] = args + [::File::RDONLY]
1204
1245
  end
1205
1246
 
@@ -1230,16 +1271,23 @@ module Toys
1230
1271
  end
1231
1272
 
1232
1273
  def interpret_out_array(key, setting)
1233
- case setting.first
1234
- when ::Symbol
1274
+ if setting.first.is_a?(::Symbol)
1235
1275
  setup_out_stream_of_type(key, setting.first, setting[1..-1])
1236
- when ::String
1276
+ elsif setting.first.is_a?(::String)
1237
1277
  setup_out_stream_of_type(key, :file, setting)
1278
+ elsif setting.size == 2 && setting.first.is_a?(::IO) && setting.last.is_a?(::IO)
1279
+ interpret_out_pipe(key, *setting)
1238
1280
  else
1239
1281
  raise "Unknown value for #{key}: #{setting.inspect}"
1240
1282
  end
1241
1283
  end
1242
1284
 
1285
+ def interpret_out_pipe(key, reader, writer)
1286
+ @spawn_opts[key] = writer
1287
+ @child_streams << writer
1288
+ @parent_streams << reader
1289
+ end
1290
+
1243
1291
  def setup_out_stream_of_type(key, type, args)
1244
1292
  case type
1245
1293
  when :controller
@@ -1260,17 +1308,150 @@ module Toys
1260
1308
  copy_from_out_thread(key, args.first)
1261
1309
  when :file
1262
1310
  interpret_out_file(key, args)
1311
+ when :tee
1312
+ interpret_out_tee(key, args)
1263
1313
  else
1264
1314
  raise "Unknown type for #{key}: #{type.inspect}"
1265
1315
  end
1266
1316
  end
1267
1317
 
1268
1318
  def interpret_out_file(key, args)
1269
- raise "Expected file name" if args.empty? || !args.first.is_a?(::String)
1270
- raise "Too many file arguments" if args.size > 3
1319
+ raise "Expected file name for #{key}" if args.empty? || !args.first.is_a?(::String)
1320
+ raise "Too many file arguments for #{key}" if args.size > 3
1271
1321
  @spawn_opts[key] = args.size == 1 ? args.first : args
1272
1322
  end
1273
1323
 
1324
+ def interpret_out_tee(key, args)
1325
+ opts = args.last.is_a?(::Hash) ? args.pop : {}
1326
+ reader = make_out_pipe(key)
1327
+ sinks = interpret_out_tee_arguments(key, args)
1328
+ tee_runner(key, reader, sinks, opts[:buffer_size] || 65_536)
1329
+ end
1330
+
1331
+ def interpret_out_tee_arguments(key, args)
1332
+ args.map do |arg|
1333
+ case arg
1334
+ when :inherit
1335
+ [key == :err ? $stderr : $stdout, nil]
1336
+ when :capture
1337
+ [::StringIO.new, :capture]
1338
+ when :controller
1339
+ tee_sink_for_controller(key)
1340
+ when ::IO, ::StringIO
1341
+ [arg, nil]
1342
+ when ::String
1343
+ [::File.open(arg, "w"), :close]
1344
+ when ::Array
1345
+ tee_sink_for_array(key, arg)
1346
+ else
1347
+ raise "Unknown value for #{key} tee argument: #{arg.inspect}"
1348
+ end
1349
+ end
1350
+ end
1351
+
1352
+ def tee_sink_for_controller(key)
1353
+ @controller_streams[key], writer = ::IO.pipe
1354
+ writer.sync = true
1355
+ [writer, :close]
1356
+ end
1357
+
1358
+ def tee_sink_for_array(key, arg)
1359
+ if arg.size == 2 &&
1360
+ arg.last.is_a?(::IO) &&
1361
+ (arg.first == :autoclose || arg.first.is_a?(::IO))
1362
+ [arg.last, :close]
1363
+ else
1364
+ arg = arg[1..-1] if arg.first == :file
1365
+ if arg.empty? || !arg.first.is_a?(::String)
1366
+ raise "Expected file name for #{key} tee argument"
1367
+ end
1368
+ raise "Too many file arguments for #{key} tee argument" if arg.size > 3
1369
+ arg += ["w"] if arg.size == 1
1370
+ [::File.open(*arg), :close]
1371
+ end
1372
+ end
1373
+
1374
+ def tee_runner(key, reader, sinks, buffer_size)
1375
+ @join_threads << ::Thread.new do
1376
+ sinks.map! { |io, on_done| [io, ::String.new, :write_nonblock, on_done] }
1377
+ until sinks.empty?
1378
+ tee_wait_for_streams(reader, sinks)
1379
+ reader = tee_read_stream(reader, sinks, buffer_size)
1380
+ tee_write_streams(sinks, key, reader.nil?)
1381
+ end
1382
+ end
1383
+ end
1384
+
1385
+ def tee_wait_for_streams(reader, sinks)
1386
+ read_select = reader && [reader]
1387
+ write_select = []
1388
+ sinks.each do |io, buffer, _write_method, _on_done|
1389
+ write_select << io unless buffer.empty?
1390
+ end
1391
+ ::IO.select(read_select, write_select)
1392
+ end
1393
+
1394
+ def tee_read_stream(reader, sinks, buffer_size)
1395
+ return nil if reader.nil?
1396
+ max = tee_amount_to_read(sinks, buffer_size)
1397
+ return reader unless max.positive?
1398
+ begin
1399
+ data = reader.read_nonblock(max)
1400
+ unless data.empty?
1401
+ sinks.each { |_io, buffer, _write_method, _on_done| buffer << data }
1402
+ end
1403
+ reader
1404
+ rescue ::IO::WaitReadable
1405
+ reader
1406
+ rescue ::StandardError
1407
+ reader.close rescue nil # rubocop:disable Style/RescueModifier
1408
+ nil
1409
+ end
1410
+ end
1411
+
1412
+ def tee_write_streams(sinks, key, read_complete)
1413
+ sinks.delete_if do |sink|
1414
+ io, buffer, write_method, on_done = sink
1415
+ done, write_method = tee_write_one_stream(io, buffer, write_method, read_complete)
1416
+ sink[2] = write_method
1417
+ if done
1418
+ case on_done
1419
+ when :close
1420
+ io.close rescue nil # rubocop:disable Style/RescueModifier
1421
+ when :capture
1422
+ @mutex.synchronize do
1423
+ @captures[key] = io.string
1424
+ end
1425
+ end
1426
+ end
1427
+ done
1428
+ end
1429
+ end
1430
+
1431
+ def tee_write_one_stream(io, buffer, write_method, read_complete)
1432
+ return [read_complete, write_method] if buffer.empty?
1433
+ begin
1434
+ bytes = io.send(write_method, buffer)
1435
+ buffer.slice!(0, bytes)
1436
+ [false, write_method]
1437
+ rescue ::IO::WaitWritable, ::Errno::EINTR
1438
+ [false, write_method]
1439
+ rescue ::Errno::EBADF, ::NoMethodError
1440
+ raise if write_method == :write
1441
+ [false, :write]
1442
+ rescue ::StandardError
1443
+ [true, write_method]
1444
+ end
1445
+ end
1446
+
1447
+ def tee_amount_to_read(sink_info, buffer_size)
1448
+ maxbuff = 0
1449
+ sink_info.each do |_sink, buffer, _meth|
1450
+ maxbuff = buffer.size if buffer.size > maxbuff
1451
+ end
1452
+ buffer_size - maxbuff
1453
+ end
1454
+
1274
1455
  def make_null_stream(key, mode)
1275
1456
  f = ::File.open(::File::NULL, mode)
1276
1457
  @spawn_opts[key] = f
@@ -171,7 +171,10 @@ module Toys
171
171
  ([@tool] + @delegates).each do |tool|
172
172
  name_len = tool.full_name.length
173
173
  subtools = @loader.list_subtools(tool.full_name,
174
- recursive: recursive, include_hidden: include_hidden)
174
+ recursive: recursive,
175
+ include_hidden: include_hidden,
176
+ include_namespaces: include_hidden,
177
+ include_non_runnable: include_hidden)
175
178
  subtools.each do |subtool|
176
179
  local_name = subtool.full_name.slice(name_len..-1).join(" ")
177
180
  subtools_by_name[local_name] = subtool
@@ -0,0 +1,138 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "toys/utils/exec"
4
+
5
+ module Toys
6
+ module Utils
7
+ ##
8
+ # A class that invokes an external pager.
9
+ #
10
+ # @example Using a pager for regular output
11
+ #
12
+ # Toys::Utils::Pager.start do |io|
13
+ # io.puts "A long string\n"
14
+ # end
15
+ #
16
+ # @example Piping output from a command
17
+ #
18
+ # exec_service = Toys::Utils::Exec.new
19
+ # Toys::Utils::Pager.start(exec_service: exec_service) do |io|
20
+ # exec_service.exec(["/bin/ls", "-alF"], out: io)
21
+ # end
22
+ #
23
+ class Pager
24
+ ##
25
+ # Creates a new pager.
26
+ #
27
+ # @param command [String,Array<String>,boolean] The command to use to
28
+ # invoke the pager. May be specified as a string to be passed to the
29
+ # shell, an array of strings representing a posix command, the value
30
+ # `true` to use the default (normally `less -FIRX`), or the value
31
+ # `false` to disable the pager and write directly to the output
32
+ # stream. Default is `true`.
33
+ # @param exec_service [Toys::Utils::Exec] The service to use for
34
+ # executing commands, or `nil` (the default) to use a default.
35
+ # @param fallback_io [IO] An IO-like object to write to if the pager is
36
+ # disabled. Defaults to `$stdout`.
37
+ #
38
+ def initialize(command: true, exec_service: nil, fallback_io: nil)
39
+ @command = command == true ? Pager.default_command : command
40
+ @command ||= nil
41
+ @exec_service = exec_service || Pager.default_exec_service
42
+ @fallback_io = fallback_io || $stdout
43
+ end
44
+
45
+ ##
46
+ # Runs the pager. Takes a block and yields an IO-like object that passes
47
+ # text to the pager. Can be called multiple times on the same pager.
48
+ #
49
+ # @yieldparam io [IO] An object that can be written to, to pass data to
50
+ # the pager.
51
+ # @return [Integer] The exit code of the pager process.
52
+ #
53
+ # @example
54
+ #
55
+ # pager = Toys::Utils::Pager.new
56
+ # pager.start do |io|
57
+ # io.puts "A long string\n"
58
+ # end
59
+ #
60
+ def start
61
+ if @command
62
+ result = @exec_service.exec(@command, in: :controller) do |controller|
63
+ yield controller.in if controller.pid
64
+ end
65
+ return result.exit_code unless result.failed?
66
+ end
67
+ yield @fallback_io
68
+ 0
69
+ end
70
+
71
+ ##
72
+ # The command for running the pager process. May be specified as a string
73
+ # to be passed to the shell, an array of strings representing a posix
74
+ # command, or `nil` to disable the pager and write directly to an output
75
+ # stream.
76
+ #
77
+ # @return [String,Array<String>,nil]
78
+ #
79
+ attr_accessor :command
80
+
81
+ ##
82
+ # The IO stream used if the pager is disabled or could not be executed.
83
+ #
84
+ # @return [IO]
85
+ #
86
+ attr_accessor :fallback_io
87
+
88
+ class << self
89
+ ##
90
+ # A convenience method that creates a pager and runs it once by calling
91
+ # {Pager#start}.
92
+ #
93
+ # @param command [String,Array<String>,boolean] The command to use to
94
+ # invoke the pager. May be specified as a string to be passed to the
95
+ # shell, an array of strings representing a posix command, the value
96
+ # `true` to use the default (normally `less -FIRX`), or the value
97
+ # `false` to disable the pager and write directly to the output
98
+ # stream. Default is `true`.
99
+ # @param exec_service [Toys::Utils::Exec] The service to use for
100
+ # executing commands, or `nil` (the default) to use a default.
101
+ # @param fallback_io [IO] An IO-like object to write to if the pager is
102
+ # disabled. Defaults to `$stdout`.
103
+ # @return [Integer] The exit code of the pager process.
104
+ #
105
+ # @example
106
+ #
107
+ # Toys::Utils::Pager.start do |io|
108
+ # io.puts "A long string\n"
109
+ # end
110
+ def start(command: true, exec_service: nil, fallback_io: nil, &block)
111
+ pager = new(command: command, exec_service: exec_service, fallback_io: fallback_io)
112
+ pager.start(&block)
113
+ end
114
+
115
+ ##
116
+ # @private
117
+ #
118
+ def default_command
119
+ unless defined? @default_command
120
+ @default_command = ::ENV["PAGER"]
121
+ unless @default_command
122
+ path = `which less`.strip
123
+ @default_command = [path, "-FIRX"] unless path.empty?
124
+ end
125
+ end
126
+ @default_command
127
+ end
128
+
129
+ ##
130
+ # @private
131
+ #
132
+ def default_exec_service
133
+ @default_exec_service ||= Exec.new
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
@@ -4,9 +4,19 @@ module Toys
4
4
  ##
5
5
  # A string intended for word-wrapped display.
6
6
  #
7
+ # A WrappableString is an array of string "fragments" representing the atomic
8
+ # units that should not be split when word-wrapping. It should be possible to
9
+ # reconstruct the original string by joining these fragments with whitespace.
10
+ #
7
11
  class WrappableString
8
12
  ##
9
13
  # Create a wrapped string.
14
+ #
15
+ # You can pass either:
16
+ #
17
+ # * A single String, which will be split into fragments by whitespace.
18
+ # * An array of Strings representing the fragments explicitly.
19
+ #
10
20
  # @param string [String,Array<String>] The string or array of string
11
21
  # fragments
12
22
  #
@@ -35,6 +45,7 @@ module Toys
35
45
 
36
46
  ##
37
47
  # Returns true if the string is empty (i.e. has no fragments)
48
+ #
38
49
  # @return [Boolean]
39
50
  #
40
51
  def empty?
@@ -43,6 +54,7 @@ module Toys
43
54
 
44
55
  ##
45
56
  # Returns the string without any wrapping
57
+ #
46
58
  # @return [String]
47
59
  #
48
60
  def string
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: toys-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.13.1
4
+ version: 0.14.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Azuma
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-03-01 00:00:00.000000000 Z
11
+ date: 2022-10-03 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Toys-Core is the command line tool framework underlying Toys. It can
14
14
  be used to create command line executables using the Toys DSL and classes.
@@ -60,6 +60,7 @@ files:
60
60
  - lib/toys/standard_mixins/gems.rb
61
61
  - lib/toys/standard_mixins/git_cache.rb
62
62
  - lib/toys/standard_mixins/highline.rb
63
+ - lib/toys/standard_mixins/pager.rb
63
64
  - lib/toys/standard_mixins/terminal.rb
64
65
  - lib/toys/standard_mixins/xdg.rb
65
66
  - lib/toys/template.rb
@@ -69,6 +70,7 @@ files:
69
70
  - lib/toys/utils/gems.rb
70
71
  - lib/toys/utils/git_cache.rb
71
72
  - lib/toys/utils/help_text.rb
73
+ - lib/toys/utils/pager.rb
72
74
  - lib/toys/utils/terminal.rb
73
75
  - lib/toys/utils/xdg.rb
74
76
  - lib/toys/wrappable_string.rb
@@ -76,10 +78,10 @@ homepage: https://github.com/dazuma/toys
76
78
  licenses:
77
79
  - MIT
78
80
  metadata:
79
- changelog_uri: https://dazuma.github.io/toys/gems/toys-core/v0.13.1/file.CHANGELOG.html
81
+ changelog_uri: https://dazuma.github.io/toys/gems/toys-core/v0.14.1/file.CHANGELOG.html
80
82
  source_code_uri: https://github.com/dazuma/toys/tree/main/toys-core
81
83
  bug_tracker_uri: https://github.com/dazuma/toys/issues
82
- documentation_uri: https://dazuma.github.io/toys/gems/toys-core/v0.13.1
84
+ documentation_uri: https://dazuma.github.io/toys/gems/toys-core/v0.14.1
83
85
  post_install_message:
84
86
  rdoc_options: []
85
87
  require_paths: