pytty 0.4.1 → 0.5.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: 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