pytty 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|