pytty 0.4.1 → 0.5.0

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: bdb07fa81c4837ef64e8bffa9abaed4abdb6b99caeff476a7d4b11b2b36be8f1
4
- data.tar.gz: 979f05736d9f1ab607edf33371c6a3f1ffadcdd60ca05c8d6d57af2a401e1808
3
+ metadata.gz: ac7cf59162fd0dccf371c30835205439e1e5886e1205046a7e8bfeba356ec66d
4
+ data.tar.gz: 91514548de9aee6be4f3f4ae8fdf1cad590ca4af1696b50dcd94075747bd5569
5
5
  SHA512:
6
- metadata.gz: 691018ead4ca0970d5ba99006e2931aa7da6b6d116adf6e2d041282c22d24f67985a0eb581659d82bb991c8f64e22b31f9bee6733b02de4fb54ca70187f0f791
7
- data.tar.gz: 5c9c8a5cbea5e16a36332b3aab35f02472322b474d075bf923dd671c6d56f49a78677c6d77370f77eb80388ae1cfa0ffb8148232178433b201cd4e3da010e625
6
+ metadata.gz: 932b0d858592fc7674e29735b6fc97e0244e0348a8c0cc944008bf10c137085aba44fa52ea9cfed2d92d161f549eef8243817363193d9d100bbc435a8bfec8eb
7
+ data.tar.gz: b467fead672a215988f55b57225f6ba2167e3ca8aae23d3849f4bb69b056159ed3d5cd505e579909d78995bf40ff2ef434c0ced8fdb0cdb1988d39b9d58add12
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- pytty (0.4.1)
4
+ pytty (0.5.0)
5
5
  async-websocket
6
6
  clamp (~> 1.3)
7
7
  falcon
@@ -10,18 +10,18 @@ PATH
10
10
  GEM
11
11
  remote: https://rubygems.org/
12
12
  specs:
13
- async (1.13.0)
13
+ async (1.14.0)
14
14
  nio4r (~> 2.3)
15
15
  timers (~> 4.1)
16
16
  async-container (0.8.1)
17
17
  async (~> 1.0)
18
18
  async-io (~> 1.4)
19
- async-http (0.37.7)
20
- async (~> 1.6)
21
- async-io (~> 1.16)
19
+ async-http (0.37.9)
20
+ async (~> 1.14)
21
+ async-io (~> 1.18)
22
22
  http-protocol (~> 0.10)
23
- async-io (1.17.2)
24
- async (~> 1.3)
23
+ async-io (1.18.1)
24
+ async (~> 1.14)
25
25
  async-websocket (0.6.1)
26
26
  async-io
27
27
  websocket-driver (~> 0.7.0)
data/README.md CHANGED
@@ -1,3 +1,3 @@
1
1
  # pytty
2
2
 
3
- Process Yield TTY
3
+ Process Yield TTY
@@ -3,4 +3,9 @@ require_relative "api/yield"
3
3
  require_relative "api/attach"
4
4
  require_relative "api/spawn"
5
5
  require_relative "api/stdout"
6
+ require_relative "api/stderr"
7
+ require_relative "api/status"
8
+
6
9
  require_relative "api/signal"
10
+ require_relative "api/rm"
11
+ require_relative "api/port"
@@ -15,10 +15,9 @@ module Pytty
15
15
  # }.to_json
16
16
  # response = internet.post("#{Pytty::Client.host_url}/v1/stdin/#{id}", headers, [stdin_body])
17
17
 
18
- if interactive
19
- $stdin.raw!
20
- $stdin.echo = false
21
- end
18
+ $stdin.raw!
19
+ $stdin.echo = false
20
+
22
21
  async_stdin = Async::IO::Stream.new(
23
22
  Async::IO::Generic.new($stdin)
24
23
  )
@@ -60,14 +59,26 @@ module Pytty
60
59
 
61
60
  response = internet.post("#{Pytty::Client.host_url}/v1/attach/#{id}", headers, [body])
62
61
  response.body.each do |c|
63
- print c
62
+ stream = if c[0] == "1"
63
+ $stdout
64
+ else
65
+ $stderr
66
+ end
67
+
68
+ case c[1..-1]
69
+ when "\n"
70
+ stream.print "#{c}\r"
71
+ else
72
+ stream.print c[1..-1]
73
+ end
64
74
  end
65
75
  rescue Async::Wrapper::Cancelled => ex
66
76
  p ["rescued", ex]
67
77
  ensure
68
78
  stdin_task.stop
69
-
70
79
  internet.close
80
+ `stty sane`
81
+ #$stdout.print "\r" #stdin.raw!
71
82
  end
72
83
  end
73
84
  end
@@ -0,0 +1,21 @@
1
+ module Pytty
2
+ module Client
3
+ module Api
4
+ class Port
5
+ def self.run(id:, from:, to:)
6
+ internet = Async::HTTP::Internet.new
7
+ headers = [['accept', 'application/json']]
8
+ body = {
9
+ from: from,
10
+ to: to
11
+ }.to_json
12
+
13
+ response = internet.post("#{Pytty::Client.host_url}/v1/port/#{id}", headers, [body])
14
+ [response, JSON.parse(response.body.read)]
15
+ ensure
16
+ internet.close
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,18 @@
1
+ module Pytty
2
+ module Client
3
+ module Api
4
+ class Rm
5
+ def self.run(id:)
6
+ internet = Async::HTTP::Internet.new
7
+ headers = [['accept', 'application/json']]
8
+ body = {}.to_json
9
+
10
+ response = internet.post("#{Pytty::Client.host_url}/v1/rm/#{id}", headers, [body])
11
+ [response, JSON.parse(response.body.read)]
12
+ ensure
13
+ internet.close
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,19 @@
1
+ module Pytty
2
+ module Client
3
+ module Api
4
+ class Status
5
+ def self.run(id:)
6
+ internet = Async::HTTP::Internet.new
7
+ headers = [['accept', 'application/json']]
8
+ body = {
9
+ }.to_json
10
+
11
+ response = internet.post("#{Pytty::Client.host_url}/v1/status/#{id}", headers, [body])
12
+ [response, response.read]
13
+ ensure
14
+ internet.close
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ module Pytty
2
+ module Client
3
+ module Api
4
+ class Stderr
5
+ def self.run(id:)
6
+ internet = Async::HTTP::Internet.new
7
+ headers = [['accept', 'application/json']]
8
+ body = {
9
+ }.to_json
10
+
11
+ response = internet.post("#{Pytty::Client.host_url}/v1/stderr/#{id}", headers, [body])
12
+ [response, response.body]
13
+ ensure
14
+ internet.close
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -9,7 +9,7 @@ module Pytty
9
9
  }.to_json
10
10
 
11
11
  response = internet.post("#{Pytty::Client.host_url}/v1/stdout/#{id}", headers, [body])
12
- puts response.read
12
+ [response, response.body]
13
13
  ensure
14
14
  internet.close
15
15
  end
@@ -11,6 +11,9 @@ require_relative "cli/rm_command"
11
11
  require_relative "cli/signal_command"
12
12
  require_relative "cli/attach_command"
13
13
  require_relative "cli/stdout_command"
14
+ require_relative "cli/stderr_command"
15
+ require_relative "cli/status_command"
16
+ require_relative "cli/port_command"
14
17
 
15
18
  require_relative "cli/run_command"
16
19
 
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+ require 'async'
3
+ require 'async/http'
4
+ require 'async/http/internet'
5
+ require 'json'
6
+
7
+ module Pytty
8
+ module Client
9
+ module Cli
10
+ class PortCommand < Clamp::Command
11
+ parameter "ID", "id"
12
+ parameter "PORTS", "ports"
13
+
14
+ def execute
15
+ Async.run do
16
+ from,to = ports.split(":")
17
+ Pytty::Client::Api::Port.run id: id, from: from, to: to
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+
@@ -24,12 +24,9 @@ module Pytty
24
24
  end
25
25
 
26
26
  Async.run do
27
- internet = Async::HTTP::Internet.new
28
- headers = [['accept', 'application/json']]
29
- body = {}.to_json
30
-
31
27
  for id in ids do
32
- response = internet.post("#{Pytty::Client.host_url}/v1/rm/#{id}", headers, [body])
28
+ response, body = Pytty::Client::Api::Rm.run id: id
29
+
33
30
  if response.status == 200
34
31
  puts id
35
32
  else
@@ -37,8 +34,6 @@ module Pytty
37
34
  exit 1
38
35
  end
39
36
  end
40
- ensure
41
- internet.close
42
37
  end
43
38
  end
44
39
  end
@@ -20,6 +20,9 @@ module Pytty
20
20
  subcommand ["signal"], "signal", SignalCommand
21
21
  subcommand ["attach"], "attach", AttachCommand
22
22
  subcommand ["stdout"], "stdout", StdoutCommand
23
+ subcommand ["stderr"], "stderr", StderrCommand
24
+ subcommand ["status"], "status", StatusCommand
25
+ subcommand ["port"], "port", PortCommand
23
26
 
24
27
  def self.run
25
28
  super
@@ -31,4 +34,3 @@ module Pytty
31
34
  end
32
35
  end
33
36
  end
34
-
@@ -11,8 +11,10 @@ module Pytty
11
11
  parameter "CMD ...", "command"
12
12
  option ["-i","--interactive"], :flag, "interactive"
13
13
  option ["-t","--tty"], :flag, "tty"
14
+
14
15
  option ["-d","--detach"], :flag, "detach"
15
16
  option ["--name"], "name", "name"
17
+ option ["--rm"], :flag, "rm"
16
18
 
17
19
  def execute
18
20
  Async.run do |task|
@@ -22,16 +24,23 @@ module Pytty
22
24
  exit 1
23
25
  end
24
26
  process_yield = Pytty::Client::ProcessYield.from_json body
25
- unless detach?
27
+
28
+ attach_task = unless detach?
26
29
  task.async do
27
30
  process_yield.attach interactive: interactive?
31
+ process_yield.rm
28
32
  end
29
33
  end
30
34
 
31
- process_yield.spawn tty: tty?, interactive: interactive?
32
-
33
- if detach?
34
- puts process_yield.id
35
+ response, body = process_yield.spawn tty: tty?, interactive: interactive?
36
+ if response.status == 200
37
+ if detach?
38
+ puts process_yield.id
39
+ end
40
+ else
41
+ puts body
42
+ attach_task.stop
43
+ print "\r" unless detach?
35
44
  end
36
45
  end
37
46
  end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pytty
4
+ module Client
5
+ module Cli
6
+ class StatusCommand < Clamp::Command
7
+ parameter "ID ...", "id"
8
+
9
+ def execute
10
+ Async.run do
11
+ for id in id_list do
12
+ response, body = Pytty::Client::Api::Status.run id: id
13
+ if response.status == 200
14
+ puts body
15
+ else
16
+ puts body
17
+ exit 1
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+ require 'async'
3
+ require 'async/http'
4
+ require 'async/http/internet'
5
+ require 'json'
6
+
7
+ module Pytty
8
+ module Client
9
+ module Cli
10
+ class StderrCommand < Clamp::Command
11
+ parameter "ID", "id"
12
+
13
+ def execute
14
+ Async.run do
15
+ response, body = Pytty::Client::Api::Stderr.run id: id
16
+ if response.status == 200
17
+ puts body.read
18
+ else
19
+ puts body.read
20
+ exit 1
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+
@@ -12,7 +12,13 @@ module Pytty
12
12
 
13
13
  def execute
14
14
  Async.run do
15
- Pytty::Client::Api::Stdout.run id: id
15
+ response, body = Pytty::Client::Api::Stdout.run id: id
16
+ if response.status == 200
17
+ puts body.read
18
+ else
19
+ puts body.read
20
+ exit 1
21
+ end
16
22
  end
17
23
  end
18
24
  end
@@ -20,9 +20,11 @@ module Pytty
20
20
  status: json.fetch("status")
21
21
  })
22
22
  end
23
+
23
24
  def running?
24
25
  !@pid.nil?
25
26
  end
27
+
26
28
  def to_s
27
29
  fields = []
28
30
  fields << @id
@@ -32,6 +34,10 @@ module Pytty
32
34
  fields.join("\t")
33
35
  end
34
36
 
37
+ def rm
38
+ Pytty::Client::Api::Rm.run id: @id
39
+ end
40
+
35
41
  def spawn(tty:, interactive:)
36
42
  Pytty::Client::Api::Spawn.run id: @id, tty: tty, interactive: interactive
37
43
  end
@@ -15,20 +15,29 @@ module Pytty
15
15
  end
16
16
 
17
17
  resp = case params["component"]
18
- when "stdout"
18
+ when "stdout","stderr"
19
19
  process_yield = Pytty::Daemon.yields[params["id"]]
20
20
  return [404, {'content-type' => 'text/html; charset=utf-8'}, ["does not exist"]] unless process_yield
21
21
 
22
- body = Async::HTTP::Body::Writable.new
22
+ stream_path = if params["component"] == "stdout"
23
+ process_yield.stdout_path
24
+ else
25
+ process_yield.stderr_path
26
+ end
27
+
28
+ stream = Async::IO::Stream.new(
29
+ File.open stream_path, "r"
30
+ )
23
31
 
32
+ body = Async::HTTP::Body::Writable.new
24
33
  begin
25
- our_stdout = process_yield.stdout.dup
26
- while c = our_stdout.read
34
+ while c = stream.read
27
35
  body.write c
28
36
  end
29
37
  rescue Exception => ex
30
- p ex
38
+ p "read", ex
31
39
  ensure
40
+ stream.close if stream
32
41
  body.close
33
42
  end
34
43
 
@@ -37,17 +46,25 @@ module Pytty
37
46
  process_yield = Pytty::Daemon.yields[params["id"]]
38
47
  return [404, {'content-type' => 'text/html; charset=utf-8'}, ["does not exist"]] unless process_yield
39
48
 
40
- puts "got attach: #{req.object_id}"
41
49
  body = Async::HTTP::Body::Writable.new
42
50
 
43
- Async::Task.current.async do |task|
51
+ stderr_done = Async::Task.current.async do |task|
52
+ notification = process_yield.add_stderr body
53
+ notification.wait
54
+ end
55
+
56
+ stdout_done = Async::Task.current.async do |task|
44
57
  notification = process_yield.add_stdout body
45
58
  notification.wait
46
- rescue Exception => ex
47
- puts "----"
48
- p ["attach", ex]
59
+ end
60
+
61
+ Async::Task.current.async do |task|
62
+ stderr_done.wait
63
+ p ["stderr done"]
64
+ stdout_done.wait
65
+ p ["stderr done"]
49
66
  ensure
50
- puts "closing attach: #{req.object_id}"
67
+ p ["closing attach:", req.object_id]
51
68
  body.close
52
69
  end
53
70
 
@@ -55,7 +72,7 @@ module Pytty
55
72
  when "ps","yield"
56
73
  status, output = Pytty::Daemon::Components::Handler.handle component: params["component"], params: body
57
74
  [status, {"Content-Type" => "application/json"}, [output.to_json]]
58
- when "spawn","rm","signal","stdin"
75
+ when "spawn","rm","signal","stdin","status","port"
59
76
  status, output = Pytty::Daemon::Components::YieldHandler.handle component: params["component"], id: params["id"], params: body
60
77
  [status, {"Content-Type" => "application/json"}, [output.to_json]]
61
78
  when "ws"
@@ -74,4 +91,4 @@ module Pytty
74
91
  end
75
92
  end
76
93
  end
77
- end
94
+ end
@@ -9,10 +9,48 @@ module Pytty
9
9
  return [404, "not found"] unless process_yield
10
10
 
11
11
  return case component
12
+ when "port"
13
+ endpoint = Async::IO::Endpoint.tcp('0.0.0.0', params["from"].to_i)
14
+ endpoint.accept do |client|
15
+ process_yield.spawn unless process_yield.running?
16
+ p ["port accept", client.object_id]
17
+ upstream = Async::IO::Endpoint.tcp('0.0.0.0', params["to"].to_i)
18
+ peer = nil
19
+ upstream.connect do |peer|
20
+ Async::Task.current.async do |task|
21
+ while rata = peer.read(1)
22
+ client.write rata
23
+ end
24
+ client.close
25
+ end
26
+
27
+ while data = client.read(2)
28
+ p data
29
+ peer.write(data)
30
+ end
31
+ end
32
+ rescue Errno::ECONNREFUSED
33
+ sleep 0.1
34
+ p ["port upstream retry"]
35
+ retry
36
+ # rescue Async::Wrapper::Cancelled
37
+ # # ???
38
+ rescue Exception => ex
39
+ p ["accex", ex]
40
+ ensure
41
+ peer.close
42
+ client.close
43
+ end
44
+
45
+ [200, "ok"]
12
46
  when "stdin"
13
47
  process_yield.stdin.enqueue params["c"]
14
48
 
15
49
  [200, "ok"]
50
+ when "status"
51
+ return [500, "still running"] if process_yield.running?
52
+
53
+ [200, process_yield.status]
16
54
  when "signal"
17
55
  return [500, "not running"] unless process_yield.running?
18
56
 
@@ -21,8 +59,7 @@ module Pytty
21
59
  [200, "ok"]
22
60
  when "spawn"
23
61
  return [500, "already running"] if process_yield.running?
24
-
25
- if process_yield.spawn
62
+ if process_yield.spawn tty: params["tty"], interactive: params["interactive"]
26
63
  Pytty::Daemon.dump
27
64
  else
28
65
  return [500, "could not spawn"]
@@ -31,6 +68,8 @@ module Pytty
31
68
  [200, "ok"]
32
69
  when "rm"
33
70
  process_yield.signal("KILL") if process_yield.running?
71
+ process_yield.cleanup
72
+
34
73
  Pytty::Daemon.yields.delete process_yield.id
35
74
  Pytty::Daemon.dump
36
75
 
@@ -43,4 +82,3 @@ module Pytty
43
82
  end
44
83
  end
45
84
  end
46
-
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  require "securerandom"
3
3
  require "pty"
4
+ require "open3"
4
5
 
5
6
  module Pytty
6
7
  module Daemon
@@ -13,12 +14,17 @@ module Pytty
13
14
  @status = nil
14
15
  @id = id || SecureRandom.uuid
15
16
 
16
- @stdout = nil
17
17
  @stdouts = {}
18
+ @stderrs = {}
19
+
20
+ @stdout_path = File.join Pytty::Daemon.pytty_path, "#{@id}.stdout"
21
+ @stderr_path = File.join Pytty::Daemon.pytty_path, "#{@id}.stderr"
22
+
18
23
  @stdin = Async::Queue.new
19
24
  end
20
25
 
21
- attr_reader :id, :cmd, :pid, :status, :stdout
26
+ attr_reader :id, :cmd, :pid, :status
27
+ attr_reader :stdout_path, :stderr_path
22
28
  attr_accessor :stdin
23
29
 
24
30
  def add_stdout(stdout)
@@ -26,8 +32,14 @@ module Pytty
26
32
  @stdouts[notification] = stdout
27
33
  notification
28
34
  end
35
+ def add_stderr(stderr)
36
+ notification = Async::Notification.new
37
+ @stderrs[notification] = stderr
38
+ notification
39
+ end
29
40
 
30
41
  def running?
42
+ # test by killing?
31
43
  !@pid.nil?
32
44
  end
33
45
 
@@ -42,86 +54,172 @@ module Pytty
42
54
  }.to_json
43
55
  end
44
56
 
45
- def spawn
46
- return false if running?
57
+ def cleanup
47
58
  @status = nil
48
59
 
49
- executable, args = @cmd
50
- # @env.merge!({
51
- # "TERM" => "xterm"
52
- # })
60
+ File.unlink @stdout_path if File.exist? @stdout_path
61
+ File.unlink @stderr_path if File.exist? @stderr_path
62
+ end
63
+
64
+ def spawn(tty: false, interactive: false)
65
+ return false if running?
66
+ cleanup
53
67
 
54
- stdout_path = File.join(Pytty::Daemon.pytty_path, @id)
55
- File.unlink stdout_path if File.exist? stdout_path
56
68
  stdout_appender = Async::IO::Stream.new(
57
- File.open stdout_path, "a"
69
+ File.open @stdout_path, "a"
58
70
  )
59
- @stdout = Async::IO::Stream.new(
60
- File.open stdout_path, "r"
71
+ stderr_appender = Async::IO::Stream.new(
72
+ File.open @stderr_path, "a"
61
73
  )
74
+
75
+ executable, *args = @cmd
76
+ # @env.merge!({
77
+ # "TERM" => "xterm"
78
+ # })
79
+
62
80
  Async::Task.current.async do |task|
63
- real_stdout, real_stdin, pid = PTY.spawn @env, executable, *args
64
- @pid = pid
81
+ real_stdout, real_stdin, real_stderr, @pid, wait_thr = begin
82
+ if tty
83
+ stderr_reader, stderr_writer = IO.pipe
84
+ p ["spawn", "PTY"]
85
+ #TODO: if bash is started, no prompt is written in stderr or stdout
86
+ p_stdout, p_stdin, pid = PTY.spawn @env, executable, *args, err: stderr_writer
87
+
88
+ [p_stdout, p_stdin, stderr_reader, pid]
89
+ else
90
+ p_stdin, p_stdout, p_stderr, p_wait_thr = Open3.popen3 @env, executable, *args, {}
91
+ [p_stdout, p_stdin, p_stderr, p_wait_thr.pid, p_wait_thr]
92
+ end
93
+ rescue Errno::ENOENT => ex
94
+ raise unless ex.message == "No such file or directory - #{executable}"
95
+ end
96
+
97
+ next unless @pid #command failed to spawn
98
+
65
99
  async_stdout = Async::IO::Generic.new real_stdout
66
100
  async_stdin = Async::IO::Generic.new real_stdin
101
+ async_stderr = Async::IO::Generic.new real_stderr
67
102
 
68
- task_stdin_writer = task.async do |subtask|
69
- while c = @stdin.dequeue do
70
- async_stdin.write c
103
+ task_stdin_writer = if interactive
104
+ task.async do |subtask|
105
+ p ["task_stdin_writer", "started"]
106
+ while c = @stdin.dequeue do
107
+ async_stdin.write c
108
+ end
109
+ rescue Async::Stop => ex
110
+ puts "async_stdin#write Async::Stop"
111
+ rescue Exception => ex
112
+ puts "async_stdin#write: #{ex.inspect}"
113
+ ensure
114
+ async_stdin.close
115
+ p ["async_stdin", "closed"]
71
116
  end
72
- rescue Async::Stop => ex
117
+ else
118
+ nil
119
+ end
120
+
121
+ task_stderr_writer = task.async do
122
+ p ["task_stderr_writer", "started"]
123
+ while c = async_stderr.read(1) do
124
+ stderr_appender.write c
125
+ stderr_appender.flush
126
+
127
+ @stderrs.each do |notification, stderr|
128
+ begin
129
+ stderr.write "2#{c}"
130
+ rescue Async::HTTP::Body::Writable::Closed
131
+ puts "signaling error"
132
+ notification.signal
133
+ @stderrs.delete notification
134
+ rescue => ex
135
+ raise ex
136
+ end
137
+ end
138
+ end
139
+ p ["task_stderr_writer", "async_stderr has no more read"]
73
140
  rescue Exception => ex
74
- puts "async_stdin.write: #{ex.inspect}"
141
+ p ["async_stderr ex:", ex]
142
+ ensure
143
+ stderr_appender.flush
144
+ stderr_appender.close
145
+ puts "stderr_appender closed"
146
+ async_stderr.close
147
+ puts "async_stderr closed"
75
148
  end
76
149
 
77
- task_stdout_writer = task.async do |subtask|
150
+ task_stdout_writer = task.async do
151
+ p ["task_stdout_writer", "started"]
152
+
78
153
  while c = async_stdout.read(1)
79
154
  stdout_appender.write c
80
155
  stdout_appender.flush
81
156
 
82
157
  @stdouts.each do |notification, stdout|
83
158
  begin
84
- stdout.write c
159
+ stdout.write "1#{c}"
85
160
  rescue Errno::EPIPE, Errno::EPROTOTYPE => ex
86
161
  notification.signal
87
162
  @stdouts.delete notification
88
163
  end
89
164
  end
90
165
  end
166
+ p ["task_stdout_writer", "stopped"]
91
167
  rescue Async::Stop
92
168
  signal "kill"
93
169
  rescue Exception => ex
94
- p ["async_stdout.read", ex]
170
+ p ["async_stdout", ex]
95
171
  ensure
96
- task_stdin_writer.stop
97
- Process.wait(@pid)
98
- @status = if $?.exitstatus
99
- $?.exitstatus
172
+ process_status = if wait_thr
173
+ wait_thr.value
100
174
  else
101
- Signal.signame $?.termsig
175
+ begin
176
+ Process.wait(@pid)
177
+ $?
178
+ rescue Errno::ECHILD => ex
179
+ raise ex unless ex.message == "No child processes"
180
+ puts "NO CHILD PROCESS"
181
+ nil
182
+ end
183
+ end
184
+
185
+ @status = if process_status && process_status.exitstatus
186
+ process_status.exitstatus
187
+ else
188
+ Signal.signame process_status.termsig
102
189
  end
103
- puts "exited #{@id} with status: #{@status}"
104
190
 
191
+ puts "exited #{@id} with status: #{@status}"
105
192
  @pid = nil
106
- stdout_appender.close
193
+ task_stdin_writer.stop if task_stdin_writer
107
194
 
108
195
  @stdouts.each do |notification, stdout|
109
196
  notification.signal
110
197
  @stdouts.delete notification
111
198
  end
199
+ @stderrs.each do |notification, stderr|
200
+ notification.signal
201
+ @stdouts.delete notification
202
+ end
112
203
  Pytty::Daemon.dump
113
204
  end
114
- end.wait
115
-
116
- puts "spawned #{id}"
117
- return true
205
+ end
206
+
207
+ if @pid
208
+ p ["spawned", id]
209
+ return true
210
+ else
211
+ p ["failed to spawn"]
212
+ @status = 127
213
+ return false
214
+ end
118
215
  end
119
216
 
120
217
  def signal(sig)
121
218
  return unless @pid
122
219
  Process.kill(sig.upcase, @pid)
220
+ rescue Errno::ESRCH => ex
221
+ raise ex unless ex.message == "No such process"
123
222
  end
124
223
  end
125
224
  end
126
225
  end
127
-
data/lib/pytty/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Pytty
2
- VERSION = "0.4.1"
2
+ VERSION = "0.5.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pytty
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matti Paksula
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-01-14 00:00:00.000000000 Z
11
+ date: 2019-01-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: clamp
@@ -190,19 +190,26 @@ files:
190
190
  - lib/pytty/client.rb
191
191
  - lib/pytty/client/api.rb
192
192
  - lib/pytty/client/api/attach.rb
193
+ - lib/pytty/client/api/port.rb
193
194
  - lib/pytty/client/api/ps.rb
195
+ - lib/pytty/client/api/rm.rb
194
196
  - lib/pytty/client/api/signal.rb
195
197
  - lib/pytty/client/api/spawn.rb
198
+ - lib/pytty/client/api/status.rb
199
+ - lib/pytty/client/api/stderr.rb
196
200
  - lib/pytty/client/api/stdout.rb
197
201
  - lib/pytty/client/api/yield.rb
198
202
  - lib/pytty/client/cli.rb
199
203
  - lib/pytty/client/cli/attach_command.rb
204
+ - lib/pytty/client/cli/port_command.rb
200
205
  - lib/pytty/client/cli/ps_command.rb
201
206
  - lib/pytty/client/cli/rm_command.rb
202
207
  - lib/pytty/client/cli/root_command.rb
203
208
  - lib/pytty/client/cli/run_command.rb
204
209
  - lib/pytty/client/cli/signal_command.rb
205
210
  - lib/pytty/client/cli/spawn_command.rb
211
+ - lib/pytty/client/cli/status_command.rb
212
+ - lib/pytty/client/cli/stderr_command.rb
206
213
  - lib/pytty/client/cli/stdout_command.rb
207
214
  - lib/pytty/client/cli/stream_command.rb
208
215
  - lib/pytty/client/cli/yield_command.rb