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.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/.build.yml +23 -0
  3. data/.ruby-version +1 -0
  4. data/.standard.yml +3 -0
  5. data/CHANGELOG.md +5 -0
  6. data/README.md +44 -0
  7. data/Rakefile +12 -0
  8. data/exe/rider-server +11 -0
  9. data/lib/rider_server/core_ext/array.rb +32 -0
  10. data/lib/rider_server/core_ext/hash.rb +14 -0
  11. data/lib/rider_server/core_ext/object.rb +18 -0
  12. data/lib/rider_server/core_ext/string.rb +18 -0
  13. data/lib/rider_server/core_ext/symbol.rb +14 -0
  14. data/lib/rider_server/errors.rb +16 -0
  15. data/lib/rider_server/exception_extension.rb +34 -0
  16. data/lib/rider_server/inspect.rb +148 -0
  17. data/lib/rider_server/logger.rb +13 -0
  18. data/lib/rider_server/operation.rb +69 -0
  19. data/lib/rider_server/operations.rb +136 -0
  20. data/lib/rider_server/ops/clone.rb +32 -0
  21. data/lib/rider_server/ops/close.rb +25 -0
  22. data/lib/rider_server/ops/completions.rb +100 -0
  23. data/lib/rider_server/ops/eval.rb +62 -0
  24. data/lib/rider_server/ops/inspect.rb +121 -0
  25. data/lib/rider_server/ops/inspect_exception.rb +47 -0
  26. data/lib/rider_server/ops/interrupt.rb +30 -0
  27. data/lib/rider_server/ops/load_path.rb +20 -0
  28. data/lib/rider_server/ops/lookup.rb +83 -0
  29. data/lib/rider_server/ops/ls_exceptions.rb +29 -0
  30. data/lib/rider_server/ops/ls_services.rb +19 -0
  31. data/lib/rider_server/ops/ls_sessions.rb +52 -0
  32. data/lib/rider_server/ops/service.rb +43 -0
  33. data/lib/rider_server/ops/set_namespace.rb +79 -0
  34. data/lib/rider_server/ops/set_namespace_variable.rb +80 -0
  35. data/lib/rider_server/ops/stdin.rb +20 -0
  36. data/lib/rider_server/ops/toggle_catch_all_exceptions.rb +27 -0
  37. data/lib/rider_server/response.rb +69 -0
  38. data/lib/rider_server/server.rb +104 -0
  39. data/lib/rider_server/service.rb +20 -0
  40. data/lib/rider_server/services/capture_exceptions.rb +62 -0
  41. data/lib/rider_server/services/capture_io.rb +302 -0
  42. data/lib/rider_server/services/rails.rb +129 -0
  43. data/lib/rider_server/session.rb +190 -0
  44. data/lib/rider_server/transports/bencode.rb +0 -0
  45. data/lib/rider_server/utils.rb +63 -0
  46. data/lib/rider_server/version.rb +12 -0
  47. data/lib/rider_server/workspace.rb +111 -0
  48. data/lib/rider_server.rb +5 -0
  49. 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