rider-server 0.1.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 +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
|