toys-core 0.4.3 → 0.4.4

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: 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