pytty 0.2.0 → 0.3.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 +4 -4
- data/Gemfile.lock +9 -1
- data/bin/build +15 -0
- data/exe/pytty +1 -0
- data/exe/pyttyd +1 -0
- data/lib/pytty/client.rb +9 -0
- data/lib/pytty/client/api.rb +4 -0
- data/lib/pytty/client/api/attach.rb +61 -0
- data/lib/pytty/client/api/ps.rb +18 -0
- data/lib/pytty/client/api/spawn.rb +21 -0
- data/lib/pytty/client/api/yield.rb +25 -0
- data/lib/pytty/client/cli.rb +7 -0
- data/lib/pytty/client/cli/attach_command.rb +22 -0
- data/lib/pytty/client/cli/kill_command.rb +31 -0
- data/lib/pytty/client/cli/ps_command.rb +34 -0
- data/lib/pytty/client/cli/rm_command.rb +43 -0
- data/lib/pytty/client/cli/root_command.rb +7 -0
- data/lib/pytty/client/cli/run_command.rb +12 -9
- data/lib/pytty/client/cli/signal_command.rb +33 -0
- data/lib/pytty/client/cli/spawn_command.rb +24 -0
- data/lib/pytty/client/cli/stream_command.rb +31 -0
- data/lib/pytty/client/cli/yield_command.rb +30 -0
- data/lib/pytty/client/process_yield.rb +35 -0
- data/lib/pytty/daemon.rb +38 -0
- data/lib/pytty/daemon/api/router.rb +35 -11
- data/lib/pytty/daemon/api/server.rb +5 -18
- data/lib/pytty/daemon/cli/serve_command.rb +22 -2
- data/lib/pytty/daemon/components.rb +3 -2
- data/lib/pytty/daemon/components/handler.rb +71 -0
- data/lib/pytty/daemon/components/{web_handler.rb → http_handler.rb} +21 -1
- data/lib/pytty/daemon/components/stream.rb +8 -1
- data/lib/pytty/daemon/process_yield.rb +93 -0
- data/lib/pytty/version.rb +1 -1
- data/pytty.gemspec +11 -3
- metadata +36 -11
- data/.gitignore +0 -11
- data/.rspec +0 -3
- data/.ruby-gemset +0 -1
- data/.ruby-version +0 -1
- data/.travis.yml +0 -7
- data/Guardfile +0 -25
- data/lib/pytty/daemon/api/chunk.rb +0 -28
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fd905d25dba15816b22209e67dfd888c3379e3f0ef01d18611b86c21e49a6666
|
|
4
|
+
data.tar.gz: 7b215e1046ea4a67b3bc619ccb6603ba8f540a8473a6060b6f47d9921187296e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 19c80222d3401ca8e1b84878cba3a2fd9f858de56382388104533a03a9ec88ac8cb83594984218273467b8c83e9f94c640b28985462b1f54e76a1fda562dded9
|
|
7
|
+
data.tar.gz: 1056958148e94f81ba23f50970e4fc399300fc93fe2854518f3a551f46b1128c61da8a9166fab9349bb6ebaa67bfa66b1eb46e64c10ee9c71e5aca0320a58e76
|
data/Gemfile.lock
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
pytty (0.
|
|
4
|
+
pytty (0.3.0)
|
|
5
5
|
async-websocket
|
|
6
6
|
clamp (~> 1.3)
|
|
7
7
|
falcon
|
|
8
|
+
mustermann
|
|
8
9
|
|
|
9
10
|
GEM
|
|
10
11
|
remote: https://rubygems.org/
|
|
@@ -51,6 +52,9 @@ GEM
|
|
|
51
52
|
guard (~> 2.2)
|
|
52
53
|
guard-compat (~> 1.1)
|
|
53
54
|
guard-compat (1.2.1)
|
|
55
|
+
guard-process (1.2.1)
|
|
56
|
+
guard-compat (~> 1.2, >= 1.2.1)
|
|
57
|
+
spoon (~> 0.0.1)
|
|
54
58
|
guard-rspec (4.7.3)
|
|
55
59
|
guard (~> 2.1)
|
|
56
60
|
guard-compat (~> 1.1)
|
|
@@ -67,6 +71,7 @@ GEM
|
|
|
67
71
|
lumberjack (1.0.13)
|
|
68
72
|
mapping (1.1.1)
|
|
69
73
|
method_source (0.9.2)
|
|
74
|
+
mustermann (1.0.3)
|
|
70
75
|
nenv (0.3.0)
|
|
71
76
|
nio4r (2.3.1)
|
|
72
77
|
notiffany (0.1.1)
|
|
@@ -99,6 +104,8 @@ GEM
|
|
|
99
104
|
mapping (~> 1.0)
|
|
100
105
|
rainbow (>= 2.0, < 4.0)
|
|
101
106
|
shellany (0.0.1)
|
|
107
|
+
spoon (0.0.6)
|
|
108
|
+
ffi
|
|
102
109
|
thor (0.20.3)
|
|
103
110
|
timers (4.2.0)
|
|
104
111
|
websocket-driver (0.7.0)
|
|
@@ -112,6 +119,7 @@ DEPENDENCIES
|
|
|
112
119
|
bundler (~> 1.16)
|
|
113
120
|
guard
|
|
114
121
|
guard-bundler
|
|
122
|
+
guard-process
|
|
115
123
|
guard-rspec
|
|
116
124
|
kommando
|
|
117
125
|
pytty!
|
data/bin/build
ADDED
data/exe/pytty
CHANGED
data/exe/pyttyd
CHANGED
data/lib/pytty/client.rb
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
module Pytty
|
|
2
|
+
module Client
|
|
3
|
+
module Api
|
|
4
|
+
class Attach
|
|
5
|
+
def self.run(id:)
|
|
6
|
+
Async::Task.current.async do |stdin_task|
|
|
7
|
+
internet = Async::HTTP::Internet.new
|
|
8
|
+
headers = [['accept', 'application/json']]
|
|
9
|
+
body = {}.to_json
|
|
10
|
+
|
|
11
|
+
$stdin.raw!
|
|
12
|
+
$stdin.echo = false
|
|
13
|
+
async_stdin = Async::IO::Stream.new(
|
|
14
|
+
Async::IO::Generic.new($stdin)
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
stdin_body = {
|
|
18
|
+
c: "\f"
|
|
19
|
+
}.to_json
|
|
20
|
+
response = internet.post("http://localhost:1234/v1/stdin/#{id}", headers, [stdin_body])
|
|
21
|
+
|
|
22
|
+
detach_sequence_started = false
|
|
23
|
+
while c = async_stdin.read(1) do
|
|
24
|
+
case c
|
|
25
|
+
when "\x10"
|
|
26
|
+
detach_sequence_started = true
|
|
27
|
+
next
|
|
28
|
+
when "\x11"
|
|
29
|
+
if detach_sequence_started
|
|
30
|
+
detach_sequence_started = false
|
|
31
|
+
exit 0
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
detach_sequence_started = false
|
|
36
|
+
|
|
37
|
+
stdin_body = {
|
|
38
|
+
c: c
|
|
39
|
+
}.to_json
|
|
40
|
+
response = internet.post("http://localhost:1234/v1/stdin/#{id}", headers, [stdin_body])
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
begin
|
|
45
|
+
internet = Async::HTTP::Internet.new
|
|
46
|
+
headers = [['accept', 'application/json']]
|
|
47
|
+
body = {}.to_json
|
|
48
|
+
|
|
49
|
+
response = internet.post("http://localhost:1234/v1/attach/#{id}", headers, [body])
|
|
50
|
+
response.body.each do |c|
|
|
51
|
+
print c
|
|
52
|
+
end
|
|
53
|
+
rescue Async::Wrapper::Cancelled => ex
|
|
54
|
+
ensure
|
|
55
|
+
internet.close
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module Pytty
|
|
2
|
+
module Client
|
|
3
|
+
module Api
|
|
4
|
+
class Ps
|
|
5
|
+
def self.run
|
|
6
|
+
internet = Async::HTTP::Internet.new
|
|
7
|
+
headers = [['accept', 'application/json']]
|
|
8
|
+
body = {}.to_json
|
|
9
|
+
|
|
10
|
+
response = internet.post("http://localhost:1234/v1/ps", headers, [body])
|
|
11
|
+
JSON.parse(response.body.read)
|
|
12
|
+
ensure
|
|
13
|
+
internet.close
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module Pytty
|
|
2
|
+
module Client
|
|
3
|
+
module Api
|
|
4
|
+
class Spawn
|
|
5
|
+
def self.run(id:, tty:, interactive:)
|
|
6
|
+
internet = Async::HTTP::Internet.new
|
|
7
|
+
headers = [['accept', 'application/json']]
|
|
8
|
+
body = {
|
|
9
|
+
tty: tty,
|
|
10
|
+
interactive: interactive
|
|
11
|
+
}.to_json
|
|
12
|
+
|
|
13
|
+
response = internet.post("http://localhost:1234/v1/spawn/#{id}", headers, [body])
|
|
14
|
+
JSON.parse(response.read)
|
|
15
|
+
ensure
|
|
16
|
+
internet.close
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Pytty
|
|
2
|
+
module Client
|
|
3
|
+
module Api
|
|
4
|
+
class Yield
|
|
5
|
+
def self.run(cmd:, env:)
|
|
6
|
+
internet = Async::HTTP::Internet.new
|
|
7
|
+
headers = [['accept', 'application/json']]
|
|
8
|
+
|
|
9
|
+
term_env = {
|
|
10
|
+
"LINES" => IO.console.winsize.first.to_s,
|
|
11
|
+
"COLUMNS" => IO.console.winsize.last.to_s
|
|
12
|
+
}.merge env
|
|
13
|
+
|
|
14
|
+
body = {
|
|
15
|
+
cmd: cmd,
|
|
16
|
+
env: term_env
|
|
17
|
+
}.to_json
|
|
18
|
+
|
|
19
|
+
response = internet.post("http://localhost:1234/v1/yield", headers, [body])
|
|
20
|
+
JSON.parse(response.body.read)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
data/lib/pytty/client/cli.rb
CHANGED
|
@@ -3,5 +3,12 @@ require "clamp"
|
|
|
3
3
|
require_relative "../common/cli/version_command"
|
|
4
4
|
require_relative "cli/run_command"
|
|
5
5
|
require_relative "cli/stream_command"
|
|
6
|
+
require_relative "cli/yield_command"
|
|
7
|
+
require_relative "cli/ps_command"
|
|
8
|
+
require_relative "cli/kill_command"
|
|
9
|
+
require_relative "cli/rm_command"
|
|
10
|
+
require_relative "cli/spawn_command"
|
|
11
|
+
require_relative "cli/signal_command"
|
|
12
|
+
require_relative "cli/attach_command"
|
|
6
13
|
|
|
7
14
|
require_relative "cli/root_command"
|
|
@@ -0,0 +1,22 @@
|
|
|
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 AttachCommand < Clamp::Command
|
|
11
|
+
parameter "ID", "id"
|
|
12
|
+
|
|
13
|
+
def execute
|
|
14
|
+
Async.run do
|
|
15
|
+
Pytty::Client::Api::Attach.run id: id
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
@@ -0,0 +1,31 @@
|
|
|
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 KillCommand < Clamp::Command
|
|
11
|
+
parameter "ID ...", "id"
|
|
12
|
+
|
|
13
|
+
def execute
|
|
14
|
+
Async.run do
|
|
15
|
+
internet = Async::HTTP::Internet.new
|
|
16
|
+
headers = [['accept', 'application/json']]
|
|
17
|
+
body = {}.to_json
|
|
18
|
+
|
|
19
|
+
for id in id_list do
|
|
20
|
+
response = internet.post("http://localhost:1234/v1/kill/#{id}", headers, [body])
|
|
21
|
+
puts response.read
|
|
22
|
+
end
|
|
23
|
+
ensure
|
|
24
|
+
internet.close
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
@@ -0,0 +1,34 @@
|
|
|
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 PsCommand < Clamp::Command
|
|
11
|
+
option ["-q","--quiet"], :flag, "quiet"
|
|
12
|
+
|
|
13
|
+
def execute
|
|
14
|
+
process_yields = Async.run do
|
|
15
|
+
Pytty::Client::Api::Ps.run
|
|
16
|
+
end.wait
|
|
17
|
+
|
|
18
|
+
unless quiet?
|
|
19
|
+
puts "id cmd"
|
|
20
|
+
puts "-"*40
|
|
21
|
+
end
|
|
22
|
+
for process_yield in process_yields do
|
|
23
|
+
if quiet?
|
|
24
|
+
puts process_yield.fetch "id"
|
|
25
|
+
else
|
|
26
|
+
puts process_yield
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
@@ -0,0 +1,43 @@
|
|
|
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 RmCommand < Clamp::Command
|
|
11
|
+
parameter "[ID] ...", "id"
|
|
12
|
+
option ["--all"], :flag, "all"
|
|
13
|
+
|
|
14
|
+
def execute
|
|
15
|
+
ids = if all?
|
|
16
|
+
process_yield_jsons = Async.run do
|
|
17
|
+
Pytty::Client::Api::Ps.run
|
|
18
|
+
end.wait
|
|
19
|
+
process_yield_jsons.map do |json|
|
|
20
|
+
json.fetch("id")
|
|
21
|
+
end
|
|
22
|
+
else
|
|
23
|
+
id_list
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
Async.run do
|
|
27
|
+
internet = Async::HTTP::Internet.new
|
|
28
|
+
headers = [['accept', 'application/json']]
|
|
29
|
+
body = {}.to_json
|
|
30
|
+
|
|
31
|
+
for id in ids do
|
|
32
|
+
response = internet.post("http://localhost:1234/v1/rm/#{id}", headers, [body])
|
|
33
|
+
puts id
|
|
34
|
+
end
|
|
35
|
+
ensure
|
|
36
|
+
internet.close
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
@@ -14,6 +14,13 @@ module Pytty
|
|
|
14
14
|
subcommand ["version"], "Show version information", Pytty::Common::Cli::VersionCommand
|
|
15
15
|
subcommand ["run"], "run", RunCommand
|
|
16
16
|
subcommand ["stream"], "stream", StreamCommand
|
|
17
|
+
subcommand ["yield"], "yield", YieldCommand
|
|
18
|
+
subcommand ["ps"], "ps", PsCommand
|
|
19
|
+
subcommand ["kill"], "kill", KillCommand
|
|
20
|
+
subcommand ["rm"], "rm", RmCommand
|
|
21
|
+
subcommand ["spawn"], "spawn", SpawnCommand
|
|
22
|
+
subcommand ["signal"], "signal", SignalCommand
|
|
23
|
+
subcommand ["attach"], "attach", AttachCommand
|
|
17
24
|
|
|
18
25
|
def self.run
|
|
19
26
|
super
|
|
@@ -9,18 +9,21 @@ module Pytty
|
|
|
9
9
|
module Cli
|
|
10
10
|
class RunCommand < Clamp::Command
|
|
11
11
|
parameter "CMD ...", "command"
|
|
12
|
+
option ["-i","--interactive"], :flag, "interactive"
|
|
13
|
+
option ["-t","--tty"], :flag, "tty"
|
|
14
|
+
option ["-d","--detach"], :flag, "detach"
|
|
15
|
+
|
|
12
16
|
def execute
|
|
13
17
|
Async.run do
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
cmd: cmd_list
|
|
18
|
-
}.to_json
|
|
18
|
+
json = Pytty::Client::Api::Yield.run cmd: cmd_list, env: {}
|
|
19
|
+
process_yield = Pytty::Client::ProcessYield.from_json json
|
|
20
|
+
process_yield.spawn tty: tty?, interactive: interactive?
|
|
19
21
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
if detach?
|
|
23
|
+
puts process_yield.id
|
|
24
|
+
else
|
|
25
|
+
process_yield.attach
|
|
26
|
+
end
|
|
24
27
|
end
|
|
25
28
|
end
|
|
26
29
|
end
|
|
@@ -0,0 +1,33 @@
|
|
|
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 SignalCommand < Clamp::Command
|
|
11
|
+
parameter "SIGNAL", "signal"
|
|
12
|
+
parameter "ID ...", "id"
|
|
13
|
+
|
|
14
|
+
def execute
|
|
15
|
+
Async.run do
|
|
16
|
+
internet = Async::HTTP::Internet.new
|
|
17
|
+
headers = [['accept', 'application/json']]
|
|
18
|
+
body = {}.to_json
|
|
19
|
+
|
|
20
|
+
for id in id_list do
|
|
21
|
+
#TODO /v1/process/:id/signal ?
|
|
22
|
+
response = internet.post("http://localhost:1234/v1/signal/#{signal}/#{id}", headers, [body])
|
|
23
|
+
p response.read
|
|
24
|
+
end
|
|
25
|
+
ensure
|
|
26
|
+
internet.close
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
@@ -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 SpawnCommand < Clamp::Command
|
|
11
|
+
parameter "ID ...", "id"
|
|
12
|
+
|
|
13
|
+
def execute
|
|
14
|
+
Async.run do
|
|
15
|
+
for id in id_list
|
|
16
|
+
Pytty::Client::Api::Spawn.run id: id
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
@@ -11,6 +11,37 @@ module Pytty
|
|
|
11
11
|
class StreamCommand < Clamp::Command
|
|
12
12
|
parameter "CMD ...", "command"
|
|
13
13
|
def execute
|
|
14
|
+
|
|
15
|
+
$stdin.raw!
|
|
16
|
+
$stdin.echo = false
|
|
17
|
+
Async::Reactor.run do |task|
|
|
18
|
+
async_stdin = Async::IO::Stream.new(
|
|
19
|
+
Async::IO::Generic.new($stdin)
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
while c = async_stdin.read(1) do
|
|
23
|
+
case c
|
|
24
|
+
when "\x01"
|
|
25
|
+
print "\r"
|
|
26
|
+
when "\x03"
|
|
27
|
+
puts "\r\n\nctrl+c\n\r"
|
|
28
|
+
break
|
|
29
|
+
when "\r"
|
|
30
|
+
print "\n\r"
|
|
31
|
+
when "\e"
|
|
32
|
+
print c
|
|
33
|
+
print async_stdin.read(2)
|
|
34
|
+
else
|
|
35
|
+
print c.inspect
|
|
36
|
+
# print c
|
|
37
|
+
# p c
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
exit 0
|
|
43
|
+
|
|
44
|
+
#---------------
|
|
14
45
|
env = {
|
|
15
46
|
"LINES" => IO.console.winsize.first.to_s,
|
|
16
47
|
"COLUMNS" => IO.console.winsize.last.to_s
|
|
@@ -0,0 +1,30 @@
|
|
|
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 YieldCommand < Clamp::Command
|
|
11
|
+
parameter "CMD ...", "command"
|
|
12
|
+
option ["-q", "--quiet"], :flag, "quiet"
|
|
13
|
+
|
|
14
|
+
def execute
|
|
15
|
+
process_yield = Async.run do
|
|
16
|
+
json = Pytty::Client::Api::Yield.run cmd: cmd_list, env: {}
|
|
17
|
+
::Pytty::Client::ProcessYield.from_json json
|
|
18
|
+
end.wait
|
|
19
|
+
|
|
20
|
+
if quiet?
|
|
21
|
+
puts process_yield.id
|
|
22
|
+
else
|
|
23
|
+
puts process_yield
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
module Pytty
|
|
2
|
+
module Client
|
|
3
|
+
class ProcessYield
|
|
4
|
+
def initialize(id:, cmd:, env:, pid:)
|
|
5
|
+
@cmd = cmd
|
|
6
|
+
@env = env
|
|
7
|
+
@pid = pid
|
|
8
|
+
@id = id
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
attr_reader :id
|
|
12
|
+
|
|
13
|
+
def self.from_json(json)
|
|
14
|
+
self.new({
|
|
15
|
+
id: json.fetch("id"),
|
|
16
|
+
cmd: json.fetch("cmd"),
|
|
17
|
+
env: json.fetch("env"),
|
|
18
|
+
pid: json.fetch("pid")
|
|
19
|
+
})
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def to_s
|
|
23
|
+
"#{@id} #{@cmd}"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def spawn(tty:, interactive:)
|
|
27
|
+
Pytty::Client::Api::Spawn.run id: @id, tty: tty, interactive: interactive
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def attach
|
|
31
|
+
Pytty::Client::Api::Attach.run id: @id
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
data/lib/pytty/daemon.rb
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pytty
|
|
4
|
+
module Daemon
|
|
5
|
+
@@yields = {}
|
|
6
|
+
|
|
7
|
+
def self.yields
|
|
8
|
+
@@yields
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.dump
|
|
12
|
+
FileUtils.mkdir_p File.dirname(yields_json)
|
|
13
|
+
File.write yields_json, @@yields.to_json
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.load
|
|
17
|
+
return unless File.exist? yields_json
|
|
18
|
+
puts "restoring from #{yields_json}"
|
|
19
|
+
|
|
20
|
+
objs = JSON.parse(File.read(yields_json))
|
|
21
|
+
objs.each do |k,obj|
|
|
22
|
+
process_yield = ProcessYield.new obj["cmd"], id: obj["id"], env: obj["env"]
|
|
23
|
+
@@yields[obj["id"]] = process_yield
|
|
24
|
+
print "spawning #{process_yield.cmd} ... "
|
|
25
|
+
process_yield.spawn if obj["running"]
|
|
26
|
+
puts "done"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.yields_json
|
|
32
|
+
File.join(Dir.home,".pytty","yields.json")
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
require_relative "daemon/process_yield"
|
|
38
|
+
require_relative "daemon/components"
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
require_relative "../components"
|
|
1
|
+
require "mustermann"
|
|
2
|
+
require "json"
|
|
5
3
|
|
|
6
4
|
module Pytty
|
|
7
5
|
module Daemon
|
|
@@ -9,20 +7,46 @@ module Pytty
|
|
|
9
7
|
class Router
|
|
10
8
|
def call(env)
|
|
11
9
|
req = Rack::Request.new(env)
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
|
|
11
|
+
params = Mustermann.new('/:component(/?:id)?').params(req.path_info)
|
|
12
|
+
body = begin
|
|
13
|
+
JSON.parse(req.body.read)
|
|
14
|
+
rescue
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
resp = case params["component"]
|
|
18
|
+
when "stdin"
|
|
19
|
+
c = body["c"]
|
|
20
|
+
Pytty::Daemon.yields[params["id"]].stdin.enqueue c
|
|
21
|
+
[200, {"Content-Type" => "text/html"}, ["ok"]]
|
|
22
|
+
when "stream"
|
|
14
23
|
if env["HTTP_UPGRADE"] == "websocket"
|
|
15
24
|
handler = Pytty::Daemon::Components::WebSocketHandler.new(env)
|
|
16
25
|
handler.handle
|
|
17
26
|
end
|
|
18
27
|
|
|
19
28
|
[404, {"Content-Type" => "text/html"}, ["websocket only"]]
|
|
20
|
-
when "
|
|
21
|
-
|
|
22
|
-
|
|
29
|
+
when "attach"
|
|
30
|
+
task = Async::Task.current
|
|
31
|
+
body = Async::HTTP::Body::Writable.new
|
|
32
|
+
|
|
33
|
+
task.async do |task|
|
|
34
|
+
Pytty::Daemon.yields[params["id"]].stdouts << body
|
|
35
|
+
loop do
|
|
36
|
+
task.sleep 0.1
|
|
37
|
+
end
|
|
38
|
+
rescue Exception => ex
|
|
39
|
+
p ex
|
|
40
|
+
ensure
|
|
41
|
+
puts "closing body"
|
|
42
|
+
body.close
|
|
43
|
+
end
|
|
23
44
|
|
|
24
|
-
[200, {
|
|
25
|
-
when "
|
|
45
|
+
[200, {'content-type' => 'text/html; charset=utf-8'}, body]
|
|
46
|
+
when "run","yield","ps","rm","kill","spawn","signal"
|
|
47
|
+
status, output = Pytty::Daemon::Components::Handler.handle component: params["component"], id: params["id"], params: body
|
|
48
|
+
[status, {"Content-Type" => "application/json"}, [output.to_json]]
|
|
49
|
+
when "ws"
|
|
26
50
|
if env["HTTP_UPGRADE"] == "websocket"
|
|
27
51
|
ws = WebSockets.new env
|
|
28
52
|
ws.handle
|
|
@@ -1,21 +1,12 @@
|
|
|
1
1
|
require "falcon"
|
|
2
2
|
require_relative "router"
|
|
3
|
-
require_relative "chunk"
|
|
4
3
|
|
|
5
4
|
module Pytty
|
|
6
5
|
module Daemon
|
|
7
6
|
module Api
|
|
8
7
|
class Server
|
|
9
|
-
def
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def run
|
|
8
|
+
def self.run(url:)
|
|
13
9
|
rack_app = Rack::Builder.new do
|
|
14
|
-
#use Rack::CommonLogger
|
|
15
|
-
|
|
16
|
-
map "/chunk" do
|
|
17
|
-
run Chunk.new
|
|
18
|
-
end
|
|
19
10
|
map "/v1" do
|
|
20
11
|
run Router.new
|
|
21
12
|
end
|
|
@@ -23,16 +14,12 @@ module Pytty
|
|
|
23
14
|
|
|
24
15
|
app = Falcon::Server.middleware rack_app, verbose: true
|
|
25
16
|
|
|
26
|
-
endpoint = Async::HTTP::URLEndpoint.parse
|
|
27
|
-
bound_endpoint = Async::
|
|
28
|
-
Async::IO::SharedEndpoint.bound(endpoint)
|
|
29
|
-
end.result
|
|
17
|
+
endpoint = Async::HTTP::URLEndpoint.parse url
|
|
18
|
+
bound_endpoint = Async::IO::SharedEndpoint.bound(endpoint)
|
|
30
19
|
|
|
31
20
|
server = Falcon::Server.new(app, bound_endpoint, endpoint.protocol, endpoint.scheme)
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
puts "serving..."
|
|
35
|
-
end
|
|
21
|
+
puts "serving at #{url}"
|
|
22
|
+
server.run
|
|
36
23
|
end
|
|
37
24
|
end
|
|
38
25
|
end
|
|
@@ -5,9 +5,29 @@ module Pytty
|
|
|
5
5
|
module Daemon
|
|
6
6
|
module Cli
|
|
7
7
|
class ServeCommand < Clamp::Command
|
|
8
|
+
option ["--url"], "URL", "url"
|
|
9
|
+
|
|
8
10
|
def execute
|
|
9
|
-
|
|
10
|
-
|
|
11
|
+
puts "🚽 pyttyd #{Pytty::VERSION}"
|
|
12
|
+
|
|
13
|
+
url_parts = ["http://"]
|
|
14
|
+
url_parts << if bind = ENV.get("PYTTY_BIND")
|
|
15
|
+
bind
|
|
16
|
+
else
|
|
17
|
+
"127.0.0.1"
|
|
18
|
+
end
|
|
19
|
+
url_parts << if port = ENV.get("PYTTY_PORT")
|
|
20
|
+
if port == "PORT"
|
|
21
|
+
ENV.fetch "PORT"
|
|
22
|
+
else
|
|
23
|
+
"1234"
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
Async::Reactor.run do
|
|
28
|
+
Pytty::Daemon.load
|
|
29
|
+
Pytty::Daemon::Api::Server.run url: url_parts.join("")
|
|
30
|
+
end
|
|
11
31
|
end
|
|
12
32
|
end
|
|
13
33
|
end
|
|
@@ -2,5 +2,6 @@
|
|
|
2
2
|
require_relative "components/run"
|
|
3
3
|
require_relative "components/stream"
|
|
4
4
|
|
|
5
|
-
require_relative "components/
|
|
6
|
-
require_relative "components/web_socket_handler"
|
|
5
|
+
require_relative "components/http_handler"
|
|
6
|
+
require_relative "components/web_socket_handler"
|
|
7
|
+
require_relative "components/handler"
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require "rack/request"
|
|
3
|
+
require "mustermann"
|
|
4
|
+
|
|
5
|
+
module Pytty
|
|
6
|
+
module Daemon
|
|
7
|
+
module Components
|
|
8
|
+
module Handler
|
|
9
|
+
def self.handle(component:, id:, params:)
|
|
10
|
+
return case component
|
|
11
|
+
when "signal"
|
|
12
|
+
process_yield = Pytty::Daemon.yields[id]
|
|
13
|
+
return [404, "not found"] unless process_yield
|
|
14
|
+
return [500, "not running"] unless process_yield.running?
|
|
15
|
+
p params
|
|
16
|
+
process_yield.signal params["signal"]
|
|
17
|
+
[200, "ok"]
|
|
18
|
+
when "spawn"
|
|
19
|
+
process_yield = Pytty::Daemon.yields[id]
|
|
20
|
+
return [404, "not found"] unless process_yield
|
|
21
|
+
|
|
22
|
+
process_yield.spawn
|
|
23
|
+
Pytty::Daemon.dump
|
|
24
|
+
|
|
25
|
+
[200, "ok"]
|
|
26
|
+
when "ps"
|
|
27
|
+
output = []
|
|
28
|
+
Pytty::Daemon.yields.each do |id, process_yield|
|
|
29
|
+
output << process_yield
|
|
30
|
+
end
|
|
31
|
+
[200, output]
|
|
32
|
+
when "yield"
|
|
33
|
+
cmd = params.dig "cmd"
|
|
34
|
+
env = params.dig "env"
|
|
35
|
+
process_yield = Pytty::Daemon::ProcessYield.new cmd, env: env
|
|
36
|
+
Pytty::Daemon.yields[process_yield.id] = process_yield
|
|
37
|
+
Pytty::Daemon.dump
|
|
38
|
+
|
|
39
|
+
[200, process_yield]
|
|
40
|
+
when "rm"
|
|
41
|
+
process_yield = Pytty::Daemon.yields[id]
|
|
42
|
+
p Pytty::Daemon.yields
|
|
43
|
+
p id
|
|
44
|
+
return [404, "not found"] unless process_yield
|
|
45
|
+
if process_yield.running?
|
|
46
|
+
process_yield.kill
|
|
47
|
+
end
|
|
48
|
+
Pytty::Daemon.yields.delete process_yield.id
|
|
49
|
+
Pytty::Daemon.dump
|
|
50
|
+
|
|
51
|
+
[200, nil]
|
|
52
|
+
when "spawn"
|
|
53
|
+
process_yield = Pytty::Daemon.yields[id]
|
|
54
|
+
return [404, "not found"] unless process_yield
|
|
55
|
+
|
|
56
|
+
pipe = IO.pipe
|
|
57
|
+
stderr_reader = Async::IO::Generic.new(pipe.first)
|
|
58
|
+
stderr_writer = Async::IO::Generic.new(pipe.last)
|
|
59
|
+
|
|
60
|
+
process_yield.spawn stdout: $stdout, stderr: stderr_writer, stdin: $stdin
|
|
61
|
+
stderr_reader.close
|
|
62
|
+
[200, process_yield]
|
|
63
|
+
else
|
|
64
|
+
raise "unknown: #{component}"
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
require "rack/request"
|
|
3
|
+
require "mustermann"
|
|
3
4
|
|
|
4
5
|
module Pytty
|
|
5
6
|
module Daemon
|
|
6
7
|
module Components
|
|
7
|
-
class
|
|
8
|
+
class HttpHandler
|
|
8
9
|
def initialize(env)
|
|
9
10
|
@env = env
|
|
10
11
|
end
|
|
@@ -16,6 +17,25 @@ module Pytty
|
|
|
16
17
|
rescue
|
|
17
18
|
end
|
|
18
19
|
|
|
20
|
+
params = Mustermann.new('/:component(/?:id)?').params(req.path_info)
|
|
21
|
+
case params.fetch("component")
|
|
22
|
+
when "rm"
|
|
23
|
+
id = params["id"]
|
|
24
|
+
|
|
25
|
+
process_yield = Pytty::Daemon.yields[id]
|
|
26
|
+
unless process_yield
|
|
27
|
+
return [404, nil]
|
|
28
|
+
else
|
|
29
|
+
return [200, process_yield]
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
case req.path_info
|
|
34
|
+
when "/kill"
|
|
35
|
+
p req
|
|
36
|
+
return [200, {}]
|
|
37
|
+
end
|
|
38
|
+
|
|
19
39
|
obj = case req.path_info
|
|
20
40
|
when "/run"
|
|
21
41
|
Run.new cmd: body.dig("cmd")
|
|
@@ -22,13 +22,20 @@ module Pytty
|
|
|
22
22
|
stderr_writer.close
|
|
23
23
|
|
|
24
24
|
stdout = Async::IO::Generic.new process_stdout
|
|
25
|
-
stdin = Async::IO::Generic.new process_stdin
|
|
25
|
+
# stdin = Async::IO::Generic.new process_stdin
|
|
26
26
|
Async::Task.current.async do |task|
|
|
27
27
|
while c = stdout.read(1)
|
|
28
28
|
stream.write c
|
|
29
29
|
end
|
|
30
30
|
stream.close
|
|
31
31
|
end
|
|
32
|
+
# Async::Task.current.async do |task|
|
|
33
|
+
# loop do
|
|
34
|
+
# p "o"
|
|
35
|
+
# task.sleep 1
|
|
36
|
+
# end
|
|
37
|
+
# end
|
|
38
|
+
|
|
32
39
|
end
|
|
33
40
|
end
|
|
34
41
|
end
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require "securerandom"
|
|
3
|
+
|
|
4
|
+
module Pytty
|
|
5
|
+
module Daemon
|
|
6
|
+
class ProcessYield
|
|
7
|
+
def initialize(cmd, id:nil, env:{})
|
|
8
|
+
@cmd = cmd
|
|
9
|
+
@env = env
|
|
10
|
+
|
|
11
|
+
@pid = nil
|
|
12
|
+
@id = id || SecureRandom.uuid
|
|
13
|
+
|
|
14
|
+
@stdouts = []
|
|
15
|
+
@stdin = Async::Queue.new
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
attr_reader :id, :cmd, :pid
|
|
19
|
+
attr_accessor :stdouts, :stdin
|
|
20
|
+
|
|
21
|
+
def running?
|
|
22
|
+
!@pid.nil?
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def to_json(json_generator_state=nil)
|
|
26
|
+
{
|
|
27
|
+
id: @id,
|
|
28
|
+
pid: @pid,
|
|
29
|
+
cmd: @cmd,
|
|
30
|
+
env: @env,
|
|
31
|
+
running: running?
|
|
32
|
+
}.to_json
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def spawn
|
|
36
|
+
executable, args = @cmd
|
|
37
|
+
@env.merge!({
|
|
38
|
+
"TERM" => "vt100"
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
Async::Task.current.async do |task|
|
|
42
|
+
p ["spawn", executable, args, @env]
|
|
43
|
+
|
|
44
|
+
real_stdout, real_stdin, pid = PTY.spawn @env, executable, *args
|
|
45
|
+
@pid = pid
|
|
46
|
+
async_stdout = Async::IO::Generic.new real_stdout
|
|
47
|
+
async_stdin = Async::IO::Generic.new real_stdin
|
|
48
|
+
|
|
49
|
+
task.async do |subtask|
|
|
50
|
+
while c = @stdin.dequeue do
|
|
51
|
+
p c
|
|
52
|
+
async_stdin.write c
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
while c = async_stdout.read(1)
|
|
57
|
+
@stdouts.each do |s|
|
|
58
|
+
begin
|
|
59
|
+
s.write c
|
|
60
|
+
rescue Errno::EPIPE => ex
|
|
61
|
+
puts "cannnot write, popping"
|
|
62
|
+
@stdouts.pop
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
puts "CLOSED"
|
|
67
|
+
#stdout.close
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
end
|
|
71
|
+
def signal(sig)
|
|
72
|
+
Process.kill(sig, @pid)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def tstp
|
|
76
|
+
Process.kill("TSTP", @pid)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def cont
|
|
80
|
+
Process.kill("CONT", @pid)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def kill
|
|
84
|
+
Process.kill("KILL", @pid)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def term
|
|
88
|
+
Process.kill("TERM", @pid)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
data/lib/pytty/version.rb
CHANGED
data/pytty.gemspec
CHANGED
|
@@ -18,6 +18,15 @@ Gem::Specification.new do |spec|
|
|
|
18
18
|
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
|
19
19
|
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
|
20
20
|
end
|
|
21
|
+
ignored_files = ["Dockerfile", "docker-compose.yml",
|
|
22
|
+
".dockerignore",".gitignore",
|
|
23
|
+
".ruby-gemset",".ruby-version",
|
|
24
|
+
".rspec",
|
|
25
|
+
".travis.yml",
|
|
26
|
+
"Guardfile"
|
|
27
|
+
]
|
|
28
|
+
spec.files = spec.files - ignored_files
|
|
29
|
+
|
|
21
30
|
spec.bindir = "exe"
|
|
22
31
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
23
32
|
spec.require_paths = ["lib"]
|
|
@@ -25,6 +34,7 @@ Gem::Specification.new do |spec|
|
|
|
25
34
|
spec.add_runtime_dependency "clamp", "~> 1.3"
|
|
26
35
|
spec.add_runtime_dependency "falcon"
|
|
27
36
|
spec.add_runtime_dependency "async-websocket"
|
|
37
|
+
spec.add_runtime_dependency "mustermann"
|
|
28
38
|
|
|
29
39
|
spec.add_development_dependency "bundler", "~> 1.16"
|
|
30
40
|
spec.add_development_dependency "rake", "~> 10.0"
|
|
@@ -32,7 +42,5 @@ Gem::Specification.new do |spec|
|
|
|
32
42
|
spec.add_development_dependency "guard"
|
|
33
43
|
spec.add_development_dependency "guard-rspec"
|
|
34
44
|
spec.add_development_dependency "guard-bundler"
|
|
35
|
-
|
|
36
|
-
spec.add_development_dependency "kommando"
|
|
37
|
-
|
|
45
|
+
spec.add_development_dependency "guard-process"
|
|
38
46
|
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
|
+
version: 0.3.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-
|
|
11
|
+
date: 2019-01-13 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: clamp
|
|
@@ -52,6 +52,20 @@ dependencies:
|
|
|
52
52
|
- - ">="
|
|
53
53
|
- !ruby/object:Gem::Version
|
|
54
54
|
version: '0'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: mustermann
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - ">="
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '0'
|
|
62
|
+
type: :runtime
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - ">="
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '0'
|
|
55
69
|
- !ruby/object:Gem::Dependency
|
|
56
70
|
name: bundler
|
|
57
71
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -137,7 +151,7 @@ dependencies:
|
|
|
137
151
|
- !ruby/object:Gem::Version
|
|
138
152
|
version: '0'
|
|
139
153
|
- !ruby/object:Gem::Dependency
|
|
140
|
-
name:
|
|
154
|
+
name: guard-process
|
|
141
155
|
requirement: !ruby/object:Gem::Requirement
|
|
142
156
|
requirements:
|
|
143
157
|
- - ">="
|
|
@@ -159,28 +173,37 @@ executables:
|
|
|
159
173
|
extensions: []
|
|
160
174
|
extra_rdoc_files: []
|
|
161
175
|
files:
|
|
162
|
-
- ".gitignore"
|
|
163
|
-
- ".rspec"
|
|
164
|
-
- ".ruby-gemset"
|
|
165
|
-
- ".ruby-version"
|
|
166
|
-
- ".travis.yml"
|
|
167
176
|
- Gemfile
|
|
168
177
|
- Gemfile.lock
|
|
169
|
-
- Guardfile
|
|
170
178
|
- LICENSE.txt
|
|
171
179
|
- README.md
|
|
172
180
|
- Rakefile
|
|
181
|
+
- bin/build
|
|
173
182
|
- bin/console
|
|
174
183
|
- bin/setup
|
|
175
184
|
- exe/pytty
|
|
176
185
|
- exe/pyttyd
|
|
177
186
|
- lib/pytty.rb
|
|
187
|
+
- lib/pytty/client.rb
|
|
188
|
+
- lib/pytty/client/api.rb
|
|
189
|
+
- lib/pytty/client/api/attach.rb
|
|
190
|
+
- lib/pytty/client/api/ps.rb
|
|
191
|
+
- lib/pytty/client/api/spawn.rb
|
|
192
|
+
- lib/pytty/client/api/yield.rb
|
|
178
193
|
- lib/pytty/client/cli.rb
|
|
194
|
+
- lib/pytty/client/cli/attach_command.rb
|
|
195
|
+
- lib/pytty/client/cli/kill_command.rb
|
|
196
|
+
- lib/pytty/client/cli/ps_command.rb
|
|
197
|
+
- lib/pytty/client/cli/rm_command.rb
|
|
179
198
|
- lib/pytty/client/cli/root_command.rb
|
|
180
199
|
- lib/pytty/client/cli/run_command.rb
|
|
200
|
+
- lib/pytty/client/cli/signal_command.rb
|
|
201
|
+
- lib/pytty/client/cli/spawn_command.rb
|
|
181
202
|
- lib/pytty/client/cli/stream_command.rb
|
|
203
|
+
- lib/pytty/client/cli/yield_command.rb
|
|
204
|
+
- lib/pytty/client/process_yield.rb
|
|
182
205
|
- lib/pytty/common/cli/version_command.rb
|
|
183
|
-
- lib/pytty/daemon
|
|
206
|
+
- lib/pytty/daemon.rb
|
|
184
207
|
- lib/pytty/daemon/api/router.rb
|
|
185
208
|
- lib/pytty/daemon/api/server.rb
|
|
186
209
|
- lib/pytty/daemon/api/web_sockets.rb
|
|
@@ -188,10 +211,12 @@ files:
|
|
|
188
211
|
- lib/pytty/daemon/cli/root_command.rb
|
|
189
212
|
- lib/pytty/daemon/cli/serve_command.rb
|
|
190
213
|
- lib/pytty/daemon/components.rb
|
|
214
|
+
- lib/pytty/daemon/components/handler.rb
|
|
215
|
+
- lib/pytty/daemon/components/http_handler.rb
|
|
191
216
|
- lib/pytty/daemon/components/run.rb
|
|
192
217
|
- lib/pytty/daemon/components/stream.rb
|
|
193
|
-
- lib/pytty/daemon/components/web_handler.rb
|
|
194
218
|
- lib/pytty/daemon/components/web_socket_handler.rb
|
|
219
|
+
- lib/pytty/daemon/process_yield.rb
|
|
195
220
|
- lib/pytty/version.rb
|
|
196
221
|
- pytty.gemspec
|
|
197
222
|
homepage: https://github.com/pyttyhq/pytty-ruby
|
data/.gitignore
DELETED
data/.rspec
DELETED
data/.ruby-gemset
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
pytty
|
data/.ruby-version
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
2.6.0
|
data/.travis.yml
DELETED
data/Guardfile
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
guard :rspec, cmd: "bundle exec rspec" do
|
|
2
|
-
require "guard/rspec/dsl"
|
|
3
|
-
dsl = Guard::RSpec::Dsl.new(self)
|
|
4
|
-
|
|
5
|
-
watch %r{^lib\/pytty\/(?<component>client|daemon)\/cli\/(?<command>.+_command)\.rb$} do |m|
|
|
6
|
-
"spec/cli/#{m[:component]}/#{m[:command]}_spec.rb"
|
|
7
|
-
end
|
|
8
|
-
|
|
9
|
-
watch %r{^spec\/cli\/(?<component>client|daemon)\/(?<command>.+_command_spec)\.rb$} do |m|
|
|
10
|
-
m.instance_variable_get(:@original_value)
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
guard :bundler do
|
|
16
|
-
require 'guard/bundler'
|
|
17
|
-
require 'guard/bundler/verify'
|
|
18
|
-
helper = Guard::Bundler::Verify.new
|
|
19
|
-
|
|
20
|
-
files = ['Gemfile']
|
|
21
|
-
files += Dir['*.gemspec'] if files.any? { |f| helper.uses_gemspec?(f) }
|
|
22
|
-
|
|
23
|
-
# Assume files are symlinked from somewhere
|
|
24
|
-
files.each { |file| watch(helper.real_path(file)) }
|
|
25
|
-
end
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
require "falcon"
|
|
2
|
-
require_relative "router"
|
|
3
|
-
|
|
4
|
-
module Pytty
|
|
5
|
-
module Daemon
|
|
6
|
-
module Api
|
|
7
|
-
class Chunk
|
|
8
|
-
|
|
9
|
-
def call(env)
|
|
10
|
-
task = Async::Task.current
|
|
11
|
-
body = Async::HTTP::Body::Writable.new
|
|
12
|
-
task.async do |task|
|
|
13
|
-
10.times do
|
|
14
|
-
body.write "hello"
|
|
15
|
-
task.sleep 0.5
|
|
16
|
-
end
|
|
17
|
-
rescue Exception => ex
|
|
18
|
-
p ex
|
|
19
|
-
ensure
|
|
20
|
-
body.close
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
[200, {'content-type' => 'text/html; charset=utf-8'}, body]
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
end
|