toys-core 0.4.3 → 0.4.4

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a85fbb6170bb1609f304cc0b1a62d4743437802b64c796372d569856bdd1e256
4
- data.tar.gz: 8291edf5cd82f0bbbcafb83878351415a28f8d0a21e058332184534c08e29751
3
+ metadata.gz: 5f312d702768f03fa72f49fb33b81b49736aa3095a863878a2eddb70750d27d8
4
+ data.tar.gz: a3ed6d02218c3e94559178021320603b5ef6d89f0e2d263fa183a583b68caf05
5
5
  SHA512:
6
- metadata.gz: 701ff10d98b0ae6f2b8c9d758c8dcb1769622a59dc3e86b85fedfae327811403c3ae514b05060eb91b701948cd275d94c86fa20973724864fc9c2b4aae355f90
7
- data.tar.gz: b042cb80c9fb0f9d0d0132b546384179efc69873e4d060c20b7e2bc1b7c6e36721d27655e4efa4b77960cc899e726bbc47e92cbdbfd5cf2c18289dcde0b8e3cb
6
+ metadata.gz: 94ee591039f0191572b66274033dbb3245bdb210e1e93441910c6a7d1e6d5732f1afdd71ba09103a357c52dc8c59fe13b1538ddd53319e03aaea4502e266e452
7
+ data.tar.gz: 6cc7dfd5d6dc7a87c6e5cee9521d6374b1ec7b462119e7ce2700147d6a384df6d7e7d92a6498139f53dcc5f114cd47588d53e4fb2108757b2d575f46b26f3a4f
data/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Release History
2
2
 
3
+ ### 0.4.4 / 2018-07-21
4
+
5
+ * FIXED: Utils::Exec wasn't closing streams after copying.
6
+ * IMPROVED: Utils::Exec::Controller can capture or redirect the remainder of a controlled stream.
7
+ * ADDED: Terminal#ask
8
+
3
9
  ### 0.4.3 / 2018-07-13
4
10
 
5
11
  * IMPROVED: Utils::Exec methods can now spawn subprocesses in the background
@@ -34,5 +34,5 @@ module Toys
34
34
  # Current version of Toys core
35
35
  # @return [String]
36
36
  #
37
- CORE_VERSION = "0.4.3"
37
+ CORE_VERSION = "0.4.4"
38
38
  end
@@ -91,11 +91,18 @@ module Toys
91
91
  terminal.write(str, *styles)
92
92
  end
93
93
 
94
+ ##
95
+ # @see Toys::Utils::Terminal#ask
96
+ #
97
+ def ask(prompt, *styles, default: nil, trailing_text: :default)
98
+ terminal.ask(prompt, *styles, default: default, trailing_text: trailing_text)
99
+ end
100
+
94
101
  ##
95
102
  # @see Toys::Utils::Terminal#confirm
96
103
  #
97
- def confirm(prompt = "Proceed?", default: nil)
98
- terminal.confirm(prompt, default: default)
104
+ def confirm(prompt = "Proceed?", *styles, default: nil)
105
+ terminal.confirm(prompt, *styles, default: default)
99
106
  end
100
107
 
101
108
  ##
@@ -437,15 +437,139 @@ module Toys
437
437
  #
438
438
  attr_reader :pid
439
439
 
440
+ ##
441
+ # Captures the remaining data in the given stream.
442
+ # After calling this, do not read directly from the stream.
443
+ #
444
+ # @param [:out,:err] which Which stream to capture
445
+ #
446
+ def capture(which)
447
+ stream = stream_for(which)
448
+ @join_threads << ::Thread.new do
449
+ begin
450
+ @captures[which] = stream.read
451
+ ensure
452
+ stream.close
453
+ end
454
+ end
455
+ self
456
+ end
457
+
458
+ ##
459
+ # Captures the remaining data in the stdandard output stream.
460
+ # After calling this, do not read directly from the stream.
461
+ #
462
+ def capture_out
463
+ capture(:out)
464
+ end
465
+
466
+ ##
467
+ # Captures the remaining data in the stdandard error stream.
468
+ # After calling this, do not read directly from the stream.
469
+ #
470
+ def capture_err
471
+ capture(:err)
472
+ end
473
+
474
+ ##
475
+ # Redirects the remainder of the given stream.
476
+ #
477
+ # You may specify the stream as an IO or IO-like object, or as a file
478
+ # specified by its path. If specifying a file, you may optionally
479
+ # provide the mode and permissions for the call to `File#open`. You can
480
+ # also specify the value `:null` to indicate the null file.
481
+ #
482
+ # After calling this, do not interact directly with the stream.
483
+ #
484
+ # @param [:in,:out,:err] which Which stream to redirect
485
+ # @param [IO,StringIO,String,:null] io Where to redirect the stream
486
+ # @param [Object...] io_args The mode and permissions for opening the
487
+ # file, if redirecting to/from a file.
488
+ #
489
+ def redirect(which, io, *io_args)
490
+ io = ::File::NULL if io == :null
491
+ if io.is_a?(::String)
492
+ io_args = which == :in ? ["r"] : ["w"] if io_args.empty?
493
+ io = ::File.open(io, *io_args)
494
+ end
495
+ stream = stream_for(which, allow_in: true)
496
+ @join_threads << ::Thread.new do
497
+ begin
498
+ if which == :in
499
+ ::IO.copy_stream(io, stream)
500
+ else
501
+ ::IO.copy_stream(stream, io)
502
+ end
503
+ ensure
504
+ stream.close
505
+ io.close
506
+ end
507
+ end
508
+ end
509
+
510
+ ##
511
+ # Redirects the remainder of the standard input stream.
512
+ #
513
+ # You may specify the stream as an IO or IO-like object, or as a file
514
+ # specified by its path. If specifying a file, you may optionally
515
+ # provide the mode and permissions for the call to `File#open`. You can
516
+ # also specify the value `:null` to indicate the null file.
517
+ #
518
+ # After calling this, do not interact directly with the stream.
519
+ #
520
+ # @param [IO,StringIO,String,:null] io Where to redirect the stream
521
+ # @param [Object...] io_args The mode and permissions for opening the
522
+ # file, if redirecting from a file.
523
+ #
524
+ def redirect_in(io, *io_args)
525
+ redirect(:in, io, *io_args)
526
+ end
527
+
528
+ ##
529
+ # Redirects the remainder of the standard output stream.
530
+ #
531
+ # You may specify the stream as an IO or IO-like object, or as a file
532
+ # specified by its path. If specifying a file, you may optionally
533
+ # provide the mode and permissions for the call to `File#open`. You can
534
+ # also specify the value `:null` to indicate the null file.
535
+ #
536
+ # After calling this, do not interact directly with the stream.
537
+ #
538
+ # @param [IO,StringIO,String,:null] io Where to redirect the stream
539
+ # @param [Object...] io_args The mode and permissions for opening the
540
+ # file, if redirecting to a file.
541
+ #
542
+ def redirect_out(io, *io_args)
543
+ redirect(:out, io, *io_args)
544
+ end
545
+
546
+ ##
547
+ # Redirects the remainder of the standard error stream.
548
+ #
549
+ # You may specify the stream as an IO or IO-like object, or as a file
550
+ # specified by its path. If specifying a file, you may optionally
551
+ # provide the mode and permissions for the call to `File#open`.
552
+ #
553
+ # After calling this, do not interact directly with the stream.
554
+ #
555
+ # @param [IO,StringIO,String] io Where to redirect the stream
556
+ # @param [Object...] io_args The mode and permissions for opening the
557
+ # file, if redirecting to a file.
558
+ #
559
+ def redirect_err(io, *io_args)
560
+ redirect(:err, io, *io_args)
561
+ end
562
+
440
563
  ##
441
564
  # Send the given signal to the process. The signal may be specified
442
565
  # by name or number.
443
566
  #
444
- # @param [Integer,String] signal The signal to send.
567
+ # @param [Integer,String] sig The signal to send.
445
568
  #
446
- def kill(signal)
447
- ::Process.kill(signal, pid)
569
+ def kill(sig)
570
+ ::Process.kill(sig, pid)
448
571
  end
572
+ alias signal kill
449
573
 
450
574
  ##
451
575
  # Determine whether the subcommand is still executing
@@ -487,6 +611,29 @@ module Toys
487
611
  @err.close if @err && !@err.closed?
488
612
  self
489
613
  end
614
+
615
+ private
616
+
617
+ def stream_for(which, allow_in: false)
618
+ stream = nil
619
+ case which
620
+ when :out
621
+ stream = @out
622
+ @out = nil
623
+ when :err
624
+ stream = @err
625
+ @err = nil
626
+ when :in
627
+ if allow_in
628
+ stream = @in
629
+ @in = nil
630
+ end
631
+ else
632
+ raise ::ArgumentError, "Unknown stream #{which}"
633
+ end
634
+ raise ::ArgumentError, "Stream #{which} not available" unless stream
635
+ stream
636
+ end
490
637
  end
491
638
 
492
639
  ##
@@ -639,24 +786,12 @@ module Toys
639
786
 
640
787
  def setup_streams_within_fork
641
788
  @parent_streams.each(&:close)
642
- setup_in_stream_within_fork(@spawn_opts[:in])
643
- out_stream = interpret_out_stream_within_fork(@spawn_opts[:out])
644
- err_stream = interpret_out_stream_within_fork(@spawn_opts[:err])
645
- if out_stream == :close
646
- $stdout.close
647
- elsif out_stream
648
- $stdout.reopen(out_stream)
649
- $stdout.sync = true
650
- end
651
- if err_stream == :close
652
- $stderr.close
653
- elsif err_stream
654
- $stderr.reopen(err_stream)
655
- $stderr.sync = true
656
- end
789
+ setup_in_stream_within_fork(@spawn_opts[:in], $stdin)
790
+ setup_out_stream_within_fork(@spawn_opts[:out], $stdout)
791
+ setup_out_stream_within_fork(@spawn_opts[:err], $stderr)
657
792
  end
658
793
 
659
- def setup_in_stream_within_fork(stream)
794
+ def setup_in_stream_within_fork(stream, stdstream)
660
795
  in_stream =
661
796
  case stream
662
797
  when ::Integer
@@ -671,32 +806,43 @@ module Toys
671
806
  stream if stream.respond_to?(:write)
672
807
  end
673
808
  if in_stream == :close
674
- $stdin.close
809
+ stdstream.close
675
810
  elsif in_stream
676
- $stdin.reopen(in_stream)
811
+ stdstream.reopen(in_stream)
677
812
  end
678
813
  end
679
814
 
680
- def interpret_out_stream_within_fork(stream)
681
- case stream
682
- when ::Integer
683
- ::IO.open(stream)
684
- when ::Array
685
- if stream.first == :child
686
- if stream[1] == :err
687
- $stderr
688
- elsif stream[1] == :out
689
- $stdout
690
- end
815
+ def setup_out_stream_within_fork(stream, stdstream)
816
+ out_stream =
817
+ case stream
818
+ when ::Integer
819
+ ::IO.open(stream)
820
+ when ::Array
821
+ interpret_out_array_within_fork(stream)
822
+ when ::String
823
+ ::File.open(stream, "w")
824
+ when :close
825
+ :close
691
826
  else
692
- ::File.open(*stream)
827
+ stream if stream.respond_to?(:write)
828
+ end
829
+ if out_stream == :close
830
+ stdstream.close
831
+ elsif out_stream
832
+ stdstream.reopen(out_stream)
833
+ stdstream.sync = true
834
+ end
835
+ end
836
+
837
+ def interpret_out_array_within_fork(stream)
838
+ if stream.first == :child
839
+ if stream[1] == :err
840
+ $stderr
841
+ elsif stream[1] == :out
842
+ $stdout
693
843
  end
694
- when ::String
695
- ::File.open(stream, "w")
696
- when :close
697
- :close
698
844
  else
699
- stream if stream.respond_to?(:write)
845
+ ::File.open(*stream)
700
846
  end
701
847
  end
702
848
 
@@ -870,26 +1016,26 @@ module Toys
870
1016
  end
871
1017
  end
872
1018
 
873
- def copy_to_in_thread(io, close: false)
1019
+ def copy_to_in_thread(io)
874
1020
  stream = make_in_pipe
875
1021
  @join_threads << ::Thread.new do
876
1022
  begin
877
1023
  ::IO.copy_stream(io, stream)
878
1024
  ensure
879
1025
  stream.close
880
- io.close if close
1026
+ io.close
881
1027
  end
882
1028
  end
883
1029
  end
884
1030
 
885
- def copy_from_out_thread(key, io, close: false)
1031
+ def copy_from_out_thread(key, io)
886
1032
  stream = make_out_pipe(key)
887
1033
  @join_threads << ::Thread.new do
888
1034
  begin
889
1035
  ::IO.copy_stream(stream, io)
890
1036
  ensure
891
1037
  stream.close
892
- io.close if close
1038
+ io.close
893
1039
  end
894
1040
  end
895
1041
  end
@@ -35,7 +35,7 @@ require "monitor"
35
35
  begin
36
36
  require "io/console"
37
37
  rescue ::LoadError # rubocop:disable Lint/HandleExceptions
38
- # TODO: use stty to get terminal size
38
+ # TODO: alternate methods of getting terminal size
39
39
  end
40
40
 
41
41
  module Toys
@@ -210,33 +210,61 @@ module Toys
210
210
  puts
211
211
  end
212
212
 
213
+ ##
214
+ # Ask a question and get a response.
215
+ #
216
+ # @param [String] prompt Required prompt string.
217
+ # @param [Symbol,String,Array<Integer>...] styles Styles to apply to the
218
+ # prompt.
219
+ # @param [String,nil] default Default value, or `nil` for no default.
220
+ # Uses `nil` if not specified.
221
+ # @param [:default,String,nil] trailing_text Trailing text appended to
222
+ # the prompt, `nil` for none, or `:default` to show the default.
223
+ # @return [String]
224
+ #
225
+ def ask(prompt, *styles, default: nil, trailing_text: :default)
226
+ if trailing_text == :default
227
+ trailing_text = default.nil? ? nil : "[#{default}]"
228
+ end
229
+ if trailing_text
230
+ ptext, pspaces, = prompt.partition(/\s+$/)
231
+ prompt = "#{ptext} #{trailing_text}#{pspaces}"
232
+ end
233
+ write(prompt, *styles)
234
+ resp = input.gets.to_s.chomp
235
+ resp.empty? ? default.to_s : resp
236
+ end
237
+
213
238
  ##
214
239
  # Confirm with the user.
215
240
  #
216
241
  # @param [String] prompt Prompt string. Defaults to `"Proceed?"`.
242
+ # @param [Symbol,String,Array<Integer>...] styles Styles to apply to the
243
+ # prompt.
217
244
  # @param [Boolean,nil] default Default value, or `nil` for no default.
218
245
  # Uses `nil` if not specified.
219
246
  # @return [Boolean]
220
247
  #
221
- def confirm(prompt = "Proceed?", default: nil)
222
- y = default == true ? "Y" : "y"
223
- n = default == false ? "N" : "n"
224
- write("#{prompt} (#{y}/#{n}) ")
225
- resp = input.gets
226
- case resp
227
- when /^y/i
228
- true
229
- when /^n/i
230
- false
231
- when nil
232
- raise TerminalError, "Cannot confirm because the input stream is at eof." if default.nil?
233
- default
234
- else
235
- if !resp.strip.empty? || default.nil?
236
- confirm("Please answer \"y\" or \"n\"")
248
+ def confirm(prompt = "Proceed? ", *styles, default: nil)
249
+ default_val, trailing_text =
250
+ case default
251
+ when true
252
+ ["y", "(Y/n)"]
253
+ when false
254
+ ["n", "(y/N)"]
237
255
  else
238
- default
256
+ [nil, "(y/n)"]
239
257
  end
258
+ resp = ask(prompt, *styles, default: default_val, trailing_text: trailing_text)
259
+ return true if resp =~ /^y/i
260
+ return false if resp =~ /^n/i
261
+ if resp.nil? && default.nil?
262
+ raise TerminalError, "Cannot confirm because the input stream is at eof."
263
+ end
264
+ if !resp.strip.empty? || default.nil?
265
+ confirm('Please answer "y" or "n"', default: default)
266
+ else
267
+ default
240
268
  end
241
269
  end
242
270
 
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.4.3
4
+ version: 0.4.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Azuma
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-07-13 00:00:00.000000000 Z
11
+ date: 2018-07-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: highline