rider-server 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.build.yml +23 -0
- data/.ruby-version +1 -0
- data/.standard.yml +3 -0
- data/CHANGELOG.md +5 -0
- data/README.md +44 -0
- data/Rakefile +12 -0
- data/exe/rider-server +11 -0
- data/lib/rider_server/core_ext/array.rb +32 -0
- data/lib/rider_server/core_ext/hash.rb +14 -0
- data/lib/rider_server/core_ext/object.rb +18 -0
- data/lib/rider_server/core_ext/string.rb +18 -0
- data/lib/rider_server/core_ext/symbol.rb +14 -0
- data/lib/rider_server/errors.rb +16 -0
- data/lib/rider_server/exception_extension.rb +34 -0
- data/lib/rider_server/inspect.rb +148 -0
- data/lib/rider_server/logger.rb +13 -0
- data/lib/rider_server/operation.rb +69 -0
- data/lib/rider_server/operations.rb +136 -0
- data/lib/rider_server/ops/clone.rb +32 -0
- data/lib/rider_server/ops/close.rb +25 -0
- data/lib/rider_server/ops/completions.rb +100 -0
- data/lib/rider_server/ops/eval.rb +62 -0
- data/lib/rider_server/ops/inspect.rb +121 -0
- data/lib/rider_server/ops/inspect_exception.rb +47 -0
- data/lib/rider_server/ops/interrupt.rb +30 -0
- data/lib/rider_server/ops/load_path.rb +20 -0
- data/lib/rider_server/ops/lookup.rb +83 -0
- data/lib/rider_server/ops/ls_exceptions.rb +29 -0
- data/lib/rider_server/ops/ls_services.rb +19 -0
- data/lib/rider_server/ops/ls_sessions.rb +52 -0
- data/lib/rider_server/ops/service.rb +43 -0
- data/lib/rider_server/ops/set_namespace.rb +79 -0
- data/lib/rider_server/ops/set_namespace_variable.rb +80 -0
- data/lib/rider_server/ops/stdin.rb +20 -0
- data/lib/rider_server/ops/toggle_catch_all_exceptions.rb +27 -0
- data/lib/rider_server/response.rb +69 -0
- data/lib/rider_server/server.rb +104 -0
- data/lib/rider_server/service.rb +20 -0
- data/lib/rider_server/services/capture_exceptions.rb +62 -0
- data/lib/rider_server/services/capture_io.rb +302 -0
- data/lib/rider_server/services/rails.rb +129 -0
- data/lib/rider_server/session.rb +190 -0
- data/lib/rider_server/transports/bencode.rb +0 -0
- data/lib/rider_server/utils.rb +63 -0
- data/lib/rider_server/version.rb +12 -0
- data/lib/rider_server/workspace.rb +111 -0
- data/lib/rider_server.rb +5 -0
- metadata +122 -0
@@ -0,0 +1,79 @@
|
|
1
|
+
require "rider_server/operation"
|
2
|
+
require "rider_server/response"
|
3
|
+
|
4
|
+
module RiderServer
|
5
|
+
module Ops
|
6
|
+
class SetNamespace < Operation
|
7
|
+
documentation "Set the namespace to the given location."
|
8
|
+
|
9
|
+
argument :id, :string, "The request id", required: true
|
10
|
+
argument :location, :array, "The location to set the namespace to", required: true
|
11
|
+
|
12
|
+
def handle(session, operation)
|
13
|
+
response = Response.new(operation)
|
14
|
+
location = operation["location"]
|
15
|
+
|
16
|
+
value = traverse_location(location, nil, session)
|
17
|
+
session.workspace.set_namespace(value)
|
18
|
+
response.set("ns", session.workspace.namespace_name)
|
19
|
+
response.set("location", location)
|
20
|
+
response.status("done")
|
21
|
+
response
|
22
|
+
end
|
23
|
+
|
24
|
+
def traverse_location(location, ctx, session)
|
25
|
+
loc = location.first
|
26
|
+
locs = location.drop(1)
|
27
|
+
|
28
|
+
if locs.empty?
|
29
|
+
lookup_object(loc, ctx, session)
|
30
|
+
else
|
31
|
+
traverse_location(locs, lookup_object(loc, ctx, session), session)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def lookup_object(loc, ctx, session)
|
36
|
+
lookup, identifier = loc.split(":", 2)
|
37
|
+
|
38
|
+
case lookup
|
39
|
+
when "rider_main"
|
40
|
+
"main"
|
41
|
+
when "rider_history"
|
42
|
+
rider_history(identifier, nil, session)
|
43
|
+
when "rider_exception"
|
44
|
+
rider_exception(identifier, nil, session)
|
45
|
+
when "rider_stackframe"
|
46
|
+
ctx.__rider_bindings_stack[identifier.to_i]
|
47
|
+
when "rider_stackframe_variable"
|
48
|
+
frame_id, local_var = identifier.split(":", 2)
|
49
|
+
ctx.__rider_bindings_stack[frame_id.to_i].local_variable_get(local_var)
|
50
|
+
when "rider_hash_key"
|
51
|
+
hash = identifier.to_i
|
52
|
+
ctx.keys.find { |key| key.hash == hash }
|
53
|
+
when "rider_hash_value"
|
54
|
+
hash = identifier.to_i
|
55
|
+
key = ctx.keys.find { |key| key.hash == hash }
|
56
|
+
ctx.fetch(key)
|
57
|
+
when "toplevel_const_get"
|
58
|
+
Object.const_get(identifier)
|
59
|
+
when "instance_variable_get"
|
60
|
+
ctx.instance_variable_get(identifier)
|
61
|
+
when "const_get"
|
62
|
+
ctx.const_get(identifier)
|
63
|
+
when "ancestor_find"
|
64
|
+
ctx.class.ancestors.find { |item| item.to_s == identifier }
|
65
|
+
else
|
66
|
+
raise "Unknown inspect function: #{lookup}"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def rider_history(value, ctx, session)
|
71
|
+
session.get_result(value)
|
72
|
+
end
|
73
|
+
|
74
|
+
def rider_exception(id, ctx, session)
|
75
|
+
session.get_exception(id)["exception"]
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require "rider_server/operation"
|
2
|
+
require "rider_server/response"
|
3
|
+
|
4
|
+
module RiderServer
|
5
|
+
module Ops
|
6
|
+
class SetNamespaceVariable < Operation
|
7
|
+
documentation "Set a variable in the current REPL binding."
|
8
|
+
|
9
|
+
argument :id, :string, "The request id", required: true
|
10
|
+
argument :name, :string, "The name of the local variable to set", required: true
|
11
|
+
argument :location, :array, "The location to set the namespace to", required: true
|
12
|
+
|
13
|
+
def handle(session, operation)
|
14
|
+
response = Response.new(operation)
|
15
|
+
name = operation["name"]
|
16
|
+
raise "Name must be a valid Ruby identifier" unless name =~ /\A[a-zA-Z_]\w*\z/
|
17
|
+
location = operation["location"]
|
18
|
+
|
19
|
+
value = traverse_location(location, nil, session)
|
20
|
+
session.workspace.binding_local_variable_set(name, value)
|
21
|
+
response.status("done")
|
22
|
+
response
|
23
|
+
end
|
24
|
+
|
25
|
+
def traverse_location(location, ctx, session)
|
26
|
+
loc = location.first
|
27
|
+
locs = location.drop(1)
|
28
|
+
|
29
|
+
if locs.empty?
|
30
|
+
lookup_object(loc, ctx, session)
|
31
|
+
else
|
32
|
+
traverse_location(locs, lookup_object(loc, ctx, session), session)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def lookup_object(loc, ctx, session)
|
37
|
+
lookup, identifier = loc.split(":", 2)
|
38
|
+
|
39
|
+
case lookup
|
40
|
+
when "rider_main"
|
41
|
+
"main"
|
42
|
+
when "rider_history"
|
43
|
+
rider_history(identifier, nil, session)
|
44
|
+
when "rider_exception"
|
45
|
+
rider_exception(identifier, nil, session)
|
46
|
+
when "rider_stackframe"
|
47
|
+
ctx.__rider_bindings_stack[identifier.to_i]
|
48
|
+
when "rider_stackframe_variable"
|
49
|
+
frame_id, local_var = identifier.split(":", 2)
|
50
|
+
ctx.__rider_bindings_stack[frame_id.to_i].local_variable_get(local_var)
|
51
|
+
when "rider_hash_key"
|
52
|
+
hash = identifier.to_i
|
53
|
+
ctx.keys.find { |key| key.hash == hash }
|
54
|
+
when "rider_hash_value"
|
55
|
+
hash = identifier.to_i
|
56
|
+
key = ctx.keys.find { |key| key.hash == hash }
|
57
|
+
ctx.fetch(key)
|
58
|
+
when "toplevel_const_get"
|
59
|
+
Object.const_get(identifier)
|
60
|
+
when "instance_variable_get"
|
61
|
+
ctx.instance_variable_get(identifier)
|
62
|
+
when "const_get"
|
63
|
+
ctx.const_get(identifier)
|
64
|
+
when "ancestor_find"
|
65
|
+
ctx.class.ancestors.find { |item| item.to_s == identifier }
|
66
|
+
else
|
67
|
+
raise "Unknown inspect function: #{lookup}"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def rider_history(value, ctx, session)
|
72
|
+
session.get_result(value)
|
73
|
+
end
|
74
|
+
|
75
|
+
def rider_exception(id, ctx, session)
|
76
|
+
session.get_exception(id)["exception"]
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require "rider_server/operation"
|
2
|
+
require "rider_server/response"
|
3
|
+
|
4
|
+
module RiderServer
|
5
|
+
module Ops
|
6
|
+
class Stdin < Operation
|
7
|
+
documentation "List all exceptions that have occurred in the session."
|
8
|
+
|
9
|
+
argument :id, :string, "The request id", required: true
|
10
|
+
argument :stdin, :string, "The input to write to the stdin of the process", required: true
|
11
|
+
|
12
|
+
def handle(session, operation)
|
13
|
+
controller.stdin.write(operation["stdin"])
|
14
|
+
response = Response.new(operation)
|
15
|
+
response.status("done")
|
16
|
+
response
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require "rider_server/operation"
|
2
|
+
require "rider_server/response"
|
3
|
+
|
4
|
+
module RiderServer
|
5
|
+
module Ops
|
6
|
+
class ToggleCatchAllExceptions < Operation
|
7
|
+
documentation "Enable catching all exceptions. Not just the ones with a session."
|
8
|
+
|
9
|
+
argument :id, :string, "The request id", required: true
|
10
|
+
|
11
|
+
def handle(session, operation)
|
12
|
+
if @controller.sessions_catching_exceptions.member?(session.id)
|
13
|
+
@controller.sessions_catching_exceptions.delete(session.id)
|
14
|
+
value = "disabled"
|
15
|
+
else
|
16
|
+
@controller.sessions_catching_exceptions.push(session.id)
|
17
|
+
value = "enabled"
|
18
|
+
end
|
19
|
+
|
20
|
+
response = Response.new(operation)
|
21
|
+
response.status("done")
|
22
|
+
response.set("value", value)
|
23
|
+
response
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# operations.rb -- Handle all the nRepl op codes
|
5
|
+
#
|
6
|
+
# Author: Russell Sim
|
7
|
+
# Copyright (c) 2024 Russell Sim
|
8
|
+
# SPDX-License-Identifier: MIT
|
9
|
+
|
10
|
+
require "bencode"
|
11
|
+
|
12
|
+
module RiderServer
|
13
|
+
class Response
|
14
|
+
def initialize(operation)
|
15
|
+
raise "Operation ID cannot be nil" if operation["id"].nil?
|
16
|
+
@op = operation["op"]
|
17
|
+
|
18
|
+
@data = {
|
19
|
+
"id" => operation["id"],
|
20
|
+
"timestamp" => Time.now.strftime("%Y-%m-%d %H:%M:%S"),
|
21
|
+
"status" => []
|
22
|
+
}
|
23
|
+
|
24
|
+
if operation["session"]
|
25
|
+
@data["session"] = operation["session"]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def id
|
30
|
+
@data["id"]
|
31
|
+
end
|
32
|
+
|
33
|
+
def session
|
34
|
+
@data["session"]
|
35
|
+
end
|
36
|
+
|
37
|
+
def set(key, value)
|
38
|
+
@data[key] = value
|
39
|
+
end
|
40
|
+
|
41
|
+
def fetch(*args)
|
42
|
+
@data.fetch(*args)
|
43
|
+
end
|
44
|
+
|
45
|
+
def [](item)
|
46
|
+
@data[item]
|
47
|
+
end
|
48
|
+
|
49
|
+
def status(*value)
|
50
|
+
@data["status"].concat(value)
|
51
|
+
end
|
52
|
+
|
53
|
+
def done?
|
54
|
+
@data["status"].include?("done")
|
55
|
+
end
|
56
|
+
|
57
|
+
def bencode
|
58
|
+
@data.bencode
|
59
|
+
end
|
60
|
+
|
61
|
+
def inspect
|
62
|
+
"#<RiderServer::Response:#{object_id} id=#{id} session=#{session} op=#{@op} status=#{@data["status"]}>"
|
63
|
+
end
|
64
|
+
|
65
|
+
def to_h
|
66
|
+
@data
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# server.rb -- Server Class
|
5
|
+
#
|
6
|
+
# Author: Russell Sim
|
7
|
+
# Copyright (c) 2024 Russell Sim
|
8
|
+
# SPDX-License-Identifier: MIT
|
9
|
+
|
10
|
+
require "rider_server/exception_extension"
|
11
|
+
require "rider_server/operations"
|
12
|
+
require "rider_server/utils"
|
13
|
+
require "rider_server/logger"
|
14
|
+
require "bencode"
|
15
|
+
|
16
|
+
module RiderServer
|
17
|
+
class Server
|
18
|
+
include RiderServer::Logger
|
19
|
+
|
20
|
+
attr_reader :status
|
21
|
+
attr_reader :listeners
|
22
|
+
|
23
|
+
def initialize
|
24
|
+
@status = :stopped
|
25
|
+
@shutdown_pipe = nil
|
26
|
+
end
|
27
|
+
|
28
|
+
def listen(address, port)
|
29
|
+
Utils.create_listeners(address, port)
|
30
|
+
end
|
31
|
+
|
32
|
+
def setup_shutdown_pipe
|
33
|
+
@shutdown_pipe ||= IO.pipe
|
34
|
+
end
|
35
|
+
|
36
|
+
def run
|
37
|
+
socket = listen("127.0.0.1", 7888)
|
38
|
+
request_count = 0
|
39
|
+
|
40
|
+
responses = Thread::Queue.new
|
41
|
+
operations = Operations.new(responses)
|
42
|
+
|
43
|
+
loop do
|
44
|
+
client = socket[0].accept
|
45
|
+
|
46
|
+
request_count += 1
|
47
|
+
|
48
|
+
threads = []
|
49
|
+
threads << Thread.new {
|
50
|
+
log.debug "Starting response thread"
|
51
|
+
loop do
|
52
|
+
response = responses.pop
|
53
|
+
|
54
|
+
if response == :shutdown
|
55
|
+
break
|
56
|
+
end
|
57
|
+
|
58
|
+
begin
|
59
|
+
output = response.bencode
|
60
|
+
rescue StandardError => e
|
61
|
+
log.error "Error encoding response: #{e}"
|
62
|
+
response = Response.new(response.to_h)
|
63
|
+
response.set("ex", e.inspect)
|
64
|
+
response.set("out", e.full_message)
|
65
|
+
response.status("eval-error")
|
66
|
+
output = response.bencode
|
67
|
+
end
|
68
|
+
|
69
|
+
if log.debug?
|
70
|
+
log.debug "sending response #{response.to_h}"
|
71
|
+
else
|
72
|
+
log.info "sending response #{response.inspect}"
|
73
|
+
end
|
74
|
+
client.write(output)
|
75
|
+
# Try to allow time for the client to read the response
|
76
|
+
sleep 0.01
|
77
|
+
end
|
78
|
+
}
|
79
|
+
|
80
|
+
threads << Thread.new {
|
81
|
+
parser = BEncode::Parser.new(client)
|
82
|
+
loop do
|
83
|
+
# Peek at the next character, and exit if it is nil
|
84
|
+
c = client.getc
|
85
|
+
if c.nil?
|
86
|
+
responses.push :shutdown
|
87
|
+
log.debug("Connection closed, closing")
|
88
|
+
break
|
89
|
+
end
|
90
|
+
client.ungetc(c)
|
91
|
+
|
92
|
+
operation = parser.parse!
|
93
|
+
|
94
|
+
log.debug(operation.inspect)
|
95
|
+
operations.handle(operation)
|
96
|
+
end
|
97
|
+
}
|
98
|
+
threads.each { |thr| thr.join }
|
99
|
+
|
100
|
+
client.close
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# service.rb -- Base class for services
|
5
|
+
#
|
6
|
+
# Author: Russell Sim
|
7
|
+
# Copyright (c) 2024 Russell Sim
|
8
|
+
# SPDX-License-Identifier: MIT
|
9
|
+
|
10
|
+
module RiderServer
|
11
|
+
class Service
|
12
|
+
def self.service_name
|
13
|
+
name.split("::").last.gsub(/(.)([A-Z])/, '\1_\2').downcase
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(session)
|
17
|
+
@session = session
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# capture_exception.rb -- Capture exceptions and send them to the session
|
5
|
+
#
|
6
|
+
# Author: Russell Sim
|
7
|
+
# Copyright (c) 2024 Russell Sim
|
8
|
+
# SPDX-License-Identifier: MIT
|
9
|
+
|
10
|
+
require "rider_server/service"
|
11
|
+
|
12
|
+
module RiderServer
|
13
|
+
module Services
|
14
|
+
class CaptureExceptions < Service
|
15
|
+
@sessions = []
|
16
|
+
|
17
|
+
def self.sessions
|
18
|
+
@sessions
|
19
|
+
end
|
20
|
+
|
21
|
+
# Take an exception object created by the session and create a
|
22
|
+
# response to send back to the client
|
23
|
+
def self.create_response(id, exception)
|
24
|
+
{
|
25
|
+
"id" => id,
|
26
|
+
"rider/exception-id" => exception.id,
|
27
|
+
"time-stamp" => Time.now.strftime("%Y-%m-%d %H:%M:%S")
|
28
|
+
}
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.handle_exception(exception, metadata = {})
|
32
|
+
@sessions.each do |session, stream_id|
|
33
|
+
ex = session.add_exception(stream_id, exception, metadata)
|
34
|
+
session.response_queue.push(create_response(stream_id, ex))
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def initialize(session)
|
39
|
+
@session = session
|
40
|
+
end
|
41
|
+
|
42
|
+
def start(stream_id)
|
43
|
+
@stream_id = stream_id
|
44
|
+
self.class.sessions.push([@session, @stream_id])
|
45
|
+
:running
|
46
|
+
end
|
47
|
+
|
48
|
+
def stop
|
49
|
+
self.class.sessions.delete_if { |s| s[0] == @session }
|
50
|
+
:stopped
|
51
|
+
end
|
52
|
+
|
53
|
+
def status
|
54
|
+
if self.class.sessions.find { |s| s[0] == @session }
|
55
|
+
:running
|
56
|
+
else
|
57
|
+
:stopped
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|