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,25 @@
1
+ require "rider_server/operation"
2
+ require "rider_server/response"
3
+
4
+ module RiderServer
5
+ module Ops
6
+ class Close < Operation
7
+ documentation "Close a specific session."
8
+
9
+ argument :id, :string, "The request id", required: true
10
+ argument :session, :string, "The session to close", required: true
11
+
12
+ # Handle the close operation, session will be nil
13
+ def handle(session, operation)
14
+ response = Response.new(operation)
15
+
16
+ session_id = operation["session"]
17
+
18
+ controller.delete_session(session_id)
19
+
20
+ response.status("done")
21
+ response
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,100 @@
1
+ require "rider_server/operation"
2
+ require "rider_server/response"
3
+ require "rider_server/inspect"
4
+
5
+ module RiderServer
6
+ module Ops
7
+ class Completions < Operation
8
+ documentation "Get completions for a given prefix"
9
+
10
+ argument :id, :string, "The request id", required: true
11
+ argument :prefix, :string, "The prefix to complete", required: true
12
+ argument :ns, :string, "The namespace to search for completions"
13
+
14
+ def initialize(*args)
15
+ @workspace = Workspace.new
16
+ super
17
+ end
18
+
19
+ def handle(session, operation)
20
+ response = Response.new(operation)
21
+
22
+ prefix = operation["prefix"]
23
+ log.info "prefix: #{prefix}"
24
+ ns = operation["ns"]
25
+ # options = operation["options"]
26
+
27
+ log.info "prefix: #{prefix}"
28
+ ns_object = @workspace.lookup_module(ns)
29
+ log.info "ns_object: #{ns_object}"
30
+
31
+ response.set("completions", lookup_module(prefix, ns_object))
32
+ log.info "completions: #{response["completions"]}"
33
+
34
+ response.status("done")
35
+ response
36
+ rescue ScriptError, StandardError => e
37
+ log.error "Error in completions: #{e}"
38
+ log.error e.backtrace.join("\n")
39
+ raise e
40
+ end
41
+
42
+ private
43
+
44
+ def lookup_module(module_name, klass = ::Object)
45
+ node = parse_string(module_name)
46
+ case node.type
47
+ when :const
48
+ ns = lookup_module_ast(node.children.first, klass)
49
+ prefix = node.children.last
50
+ all_constants(ns, prefix) + all_methods(ns, prefix)
51
+ when :send
52
+ ns = lookup_module_ast(node.children.first, klass)
53
+ prefix = node.children.last
54
+ all_methods(ns, prefix)
55
+ else
56
+ []
57
+ end
58
+ end
59
+
60
+ def parse_string(string)
61
+ buffer = Parser::Source::Buffer.new("(string)")
62
+ buffer.source = string
63
+ Parser::CurrentRuby.new.parse(buffer)
64
+ end
65
+
66
+ def lookup_module_ast(node, klass = ::Object)
67
+ case node.type
68
+ when :cbase
69
+ ::Object
70
+ when :const
71
+ if node.children.first.nil?
72
+ klass.const_get(node.children.last)
73
+ else
74
+ lookup_module_ast(node.children.first, klass).const_get(node.children.last)
75
+ end
76
+ else
77
+ raise ModuleLookupError, "Unknown node type #{node.type}"
78
+ end
79
+ end
80
+
81
+ def all_constants(ns_object, prefix)
82
+ ns_object.constants.grep(/^#{prefix}/).map do |constant|
83
+ {
84
+ "candidate" => constant.to_s,
85
+ "type" => "constant"
86
+ }
87
+ end
88
+ end
89
+
90
+ def all_methods(ns_object, prefix)
91
+ ns_object.methods.grep(/^#{prefix}/).map do |method|
92
+ {
93
+ "candidate" => method.to_s,
94
+ "type" => "method"
95
+ }
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,62 @@
1
+ require "rider_server/operation"
2
+ require "rider_server/response"
3
+ require "rider_server/utils"
4
+
5
+ module RiderServer
6
+ module Ops
7
+ class Eval < Operation
8
+ documentation "Evaluate a string of code"
9
+
10
+ argument :id, :string, "The request id", required: true
11
+ argument :code, :string, "The code to evaluate"
12
+ argument :ns, :string, "The namespace to evaluate the code in"
13
+ argument :file, :string, "The file the code is from"
14
+ argument :line, :integer, "The line number the code is from"
15
+
16
+ def handle(session, operation)
17
+ # TODO should do something if the session is nil
18
+
19
+ response = Response.new(operation)
20
+
21
+ # Abort if there is an evaluation with the same id
22
+ if session.running_evaluation?(operation["id"])
23
+ msg = "Evaluation already in progress for #{operation["id"]}"
24
+ log.warn(msg)
25
+ response.set("value", msg)
26
+ response.status("eval-error", "done")
27
+ send_response(response)
28
+ return
29
+ end
30
+
31
+ session.push_history(operation)
32
+ code = operation["code"]
33
+ ns = operation["ns"]
34
+ file = operation["file"] || ""
35
+ line = operation["line"] || 0
36
+
37
+ eval_thread = Thread.new do
38
+ begin
39
+ value = session.workspace.evaluate(code, ns, file, line)
40
+ response.set("value", Utils.rider_inspect(value))
41
+ response.set("ns", session.workspace.evaluate("inspect"))
42
+ response.status("done")
43
+ session.add_result(operation["id"], value)
44
+ send_response(response)
45
+ rescue EvalInterrupt, ScriptError, StandardError => e
46
+ response.set("ex", e.inspect)
47
+ response.set("ns", session.workspace.evaluate("inspect"))
48
+ response.status("eval-error", "done")
49
+ exception_id = session.add_exception(operation["id"], e)
50
+ response.set("rider/exception-id", exception_id)
51
+ send_response(response)
52
+ end
53
+
54
+ session.remove_evaluation(operation["id"])
55
+ end
56
+
57
+ session.add_evaluation(operation["id"], eval_thread)
58
+ nil
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,121 @@
1
+ require "rider_server/inspect"
2
+ require "rider_server/operation"
3
+ require "rider_server/response"
4
+ require "rider_server/utils"
5
+
6
+ module RiderServer
7
+ module Ops
8
+ class Inspect < Operation
9
+ documentation "Inspect an object"
10
+
11
+ argument :id, :string, "The request id", required: true
12
+ argument :location, :array, "The location of the object to inspect"
13
+
14
+ def handle(session, operation)
15
+ response = Response.new(operation)
16
+ location = operation["location"]
17
+ raise "Location is required" if location.nil? || location.empty?
18
+
19
+ value = traverse_location(location, nil, session)
20
+ response.set("name", Utils.rider_inspect(value))
21
+
22
+ if value.is_a?(Array)
23
+ value = value.map.with_index do |item, index|
24
+ {
25
+ "name" => item.to_s,
26
+ "value" => Utils.rider_inspect(item),
27
+ "inspect-location" => "rider_array_item:#{index}"
28
+ }
29
+ end
30
+ response.set("value-array", value)
31
+ elsif value.is_a?(Hash)
32
+ value = value.map do |key, value|
33
+ [
34
+ {
35
+ "name" => key.to_s,
36
+ "value" => Utils.rider_inspect(key),
37
+ "inspect-location" => "rider_hash_key:#{key.hash}"
38
+ },
39
+ {
40
+ "name" => value.to_s,
41
+ "value" => Utils.rider_inspect(value),
42
+ "inspect-location" => "rider_hash_value:#{key.hash}"
43
+ }
44
+ ]
45
+ end
46
+ response.set("value-hash", value)
47
+ elsif value.is_a?(String)
48
+ response.set("value", value.inspect)
49
+ elsif value.is_a?(Numeric)
50
+ response.set("value", value.to_s)
51
+ end
52
+
53
+ response.set("inspect-location", location)
54
+
55
+ response.set("class", RiderServer::Inspect.class(value))
56
+ response.set("ancestors", RiderServer::Inspect.ancestors(value))
57
+ response.set("constants", RiderServer::Inspect.constants(value))
58
+ response.set("methods", RiderServer::Inspect.methods(value))
59
+ response.set("instance-variables", RiderServer::Inspect.instance_variables(value))
60
+ response.set("instance-methods", RiderServer::Inspect.instance_methods(value))
61
+ response.set("class-variables", RiderServer::Inspect.class_variables(value))
62
+ response.status("done")
63
+ response
64
+ end
65
+
66
+ def traverse_location(location, ctx, session)
67
+ loc = location.first
68
+ locs = location.drop(1)
69
+
70
+ if locs.empty?
71
+ lookup_object(loc, ctx, session)
72
+ else
73
+ traverse_location(locs, lookup_object(loc, ctx, session), session)
74
+ end
75
+ end
76
+
77
+ def lookup_object(loc, ctx, session)
78
+ lookup, identifier = loc.split(":", 2)
79
+
80
+ case lookup
81
+ when "rider_main"
82
+ "main"
83
+ when "rider_history"
84
+ rider_history(identifier, nil, session)
85
+ when "rider_exception"
86
+ rider_exception(identifier, nil, session)
87
+ when "rider_stackframe_variable"
88
+ frame_id, local_var = identifier.split(":", 2)
89
+ ctx.__rider_bindings_stack[frame_id.to_i].local_variable_get(local_var)
90
+ when "rider_array_item"
91
+ ctx[identifier.to_i]
92
+ when "rider_hash_key"
93
+ hash = identifier.to_i
94
+ ctx.keys.find { |key| key.hash == hash }
95
+ when "rider_hash_value"
96
+ hash = identifier.to_i
97
+ key = ctx.keys.find { |key| key.hash == hash }
98
+ ctx.fetch(key)
99
+ when "toplevel_const_get"
100
+ Object.const_get(identifier)
101
+ when "instance_variable_get"
102
+ ctx.instance_variable_get(identifier)
103
+ when "const_get"
104
+ ctx.const_get(identifier)
105
+ when "ancestor_find"
106
+ ctx.class.ancestors.find { |item| item.to_s == identifier }
107
+ else
108
+ raise "Unknown inspect function: #{lookup}"
109
+ end
110
+ end
111
+
112
+ def rider_history(id, ctx, session)
113
+ session.get_result(id)
114
+ end
115
+
116
+ def rider_exception(id, ctx, session)
117
+ session.get_exception(id)["exception"]
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,47 @@
1
+ require "rider_server/operation"
2
+ require "rider_server/response"
3
+
4
+ module RiderServer
5
+ module Ops
6
+ class InspectException < Operation
7
+ documentation "Inspect an exception."
8
+
9
+ argument :id, :string, "The request id", required: true
10
+ argument :"exception-id", :string, "The exception id"
11
+
12
+ def handle(session, operation)
13
+ response = Response.new(operation)
14
+ exception_id = operation["exception-id"]
15
+
16
+ value = session.get_exception(exception_id)
17
+ response.set("inspect-location", value["id"])
18
+ response.set("exception-id", value["id"])
19
+ response.set("source-operation-id", value["operation_id"])
20
+ response.set("created-at", value["created_at"].to_s)
21
+
22
+ exception = value["exception"]
23
+ response.set("value", exception.inspect)
24
+ response.set("stacktrace", encode_stacktrace(exception, exception_id))
25
+
26
+ response.status("done")
27
+ response
28
+ end
29
+
30
+ def encode_stacktrace(exception, exception_id)
31
+ exception.backtrace.zip(exception.__rider_bindings_stack).map.with_index do |(line, frame), frame_id|
32
+ {
33
+ "line" => line,
34
+ "inspect-location" => ["rider_exception:#{exception_id}", "rider_stackframe:#{frame_id}"],
35
+ "frame" => (frame&.local_variables || []).map do |var|
36
+ {
37
+ "name" => var.to_s,
38
+ "value" => Utils.rider_inspect(frame.local_variable_get(var)),
39
+ "inspect-location" => ["rider_exception:#{exception_id}", "rider_stackframe_variable:#{frame_id}:#{var}"]
40
+ }
41
+ end
42
+ }
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,30 @@
1
+ require "rider_server/operation"
2
+ require "rider_server/response"
3
+
4
+ module RiderServer
5
+ module Ops
6
+ class Interrupt < Operation
7
+ documentation "Interrupts the evaluation of a session."
8
+
9
+ argument :id, :string, "The request id", required: true
10
+ argument :"interrupt-id", :string, "The ID of the evaluation to interrupt.", required: true
11
+
12
+ def handle(session, operation)
13
+ # TODO should do something if the session is nil
14
+
15
+ if operation["interrupt-id"].nil? || operation["interrupt-id"].empty?
16
+ if session.evaluations.empty?
17
+ raise ArgumentError, "No evaluations to interrupt"
18
+ end
19
+ session.interrupt_evaluation(session.evaluations.keys.max)
20
+ else
21
+ session.interrupt_evaluation(operation["interrupt-id"])
22
+ end
23
+
24
+ response = Response.new(operation)
25
+ response.status("interrupted", "done")
26
+ response
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,20 @@
1
+ require "rider_server/operation"
2
+ require "rider_server/response"
3
+
4
+ module RiderServer
5
+ module Ops
6
+ class LoadPath < Operation
7
+ documentation "Return the current load path."
8
+
9
+ argument :id, :string, "The request id", required: true
10
+
11
+ def handle(session, operation)
12
+ response = Response.new(operation)
13
+ load_path = session.workspace.evaluate("$LOAD_PATH", "main")
14
+ response.set("load-path", load_path)
15
+ response.status("done")
16
+ response
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,83 @@
1
+ require "rider_server/operation"
2
+ require "rider_server/response"
3
+ require "rider_server/inspect"
4
+
5
+ module RiderServer
6
+ module Ops
7
+ class Lookup < Operation
8
+ documentation "Get completions for a given string"
9
+
10
+ argument :id, :string, "The request id", required: true
11
+ argument :sym, :string, "The symbol to lookup", required: true
12
+ argument :ns, :string, "The namespace to search for completions"
13
+
14
+ def initialize(*args)
15
+ @workspace = Workspace.new
16
+ super
17
+ end
18
+
19
+ def handle(session, operation)
20
+ response = Response.new(operation)
21
+ ns = operation["ns"]
22
+ sym = operation["sym"]
23
+
24
+ ns_object = @workspace.lookup_module(ns)
25
+
26
+ method_type = nil
27
+ if sym.include?("#")
28
+ sym, method = sym.split("#")
29
+ method_type = :instance
30
+ method = method.to_sym
31
+ elsif sym.start_with?("self.class")
32
+ method = sym.split(".").last
33
+ method_type = :class
34
+ method = method.to_sym
35
+ elsif sym.include?(".")
36
+ sym, method = sym.split(".")
37
+ method_type = :class
38
+ method = method.to_sym
39
+ end
40
+
41
+ if sym.include?("::")
42
+ ns_object = lookup_ns(sym.split("::"), ns_object)
43
+ end
44
+ constant = ns_object
45
+
46
+ info = if sym.start_with?("@")
47
+ constant = ns_object.instance_variable_get(sym)
48
+ elsif method_type == :instance
49
+ RiderServer::Inspect.describe_method(constant.instance_method(method))
50
+ elsif method_type == :class
51
+ RiderServer::Inspect.describe_method(constant.method(method))
52
+ elsif constant.respond_to?(:instance_methods) && constant.instance_methods.include?(sym.to_sym)
53
+ RiderServer::Inspect.describe_method(constant.instance_method(sym))
54
+ elsif constant.respond_to?(:methods) && constant.methods.include?(sym.to_sym)
55
+ RiderServer::Inspect.describe_method(constant.method(sym))
56
+ else
57
+ {}
58
+ end
59
+
60
+ response.set("info", info)
61
+ response.status("done")
62
+ response
63
+ end
64
+
65
+ private
66
+
67
+ def lookup_ns(module_name, klass = Object)
68
+ if module_name.start_with?("::")
69
+ klass = Module
70
+ module_name = module_name.delete_prefix("::")
71
+ end
72
+
73
+ path = module_name.split("::")
74
+
75
+ path.inject(klass) do |acc, elem|
76
+ acc.const_get(elem)
77
+ end
78
+ rescue NameError
79
+ nil
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,29 @@
1
+ require "rider_server/operation"
2
+ require "rider_server/response"
3
+
4
+ module RiderServer
5
+ module Ops
6
+ class LsExceptions < Operation
7
+ documentation "List all exceptions that have occurred in the session."
8
+
9
+ argument :id, :string, "The request id", required: true
10
+
11
+ def handle(session, operation)
12
+ response = Response.new(operation)
13
+
14
+ exceptions = session.exceptions.map do |item|
15
+ {
16
+ "id" => item["id"],
17
+ "operation-id" => item["operation_id"],
18
+ "created-at" => item["created_at"].iso8601,
19
+ "exception" => item["exception"].inspect
20
+ }
21
+ end
22
+
23
+ response.set("exceptions", exceptions)
24
+ response.status("done")
25
+ response
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,19 @@
1
+ require "rider_server/operation"
2
+ require "rider_server/response"
3
+
4
+ module RiderServer
5
+ module Ops
6
+ class LsServices < Operation
7
+ documentation "List all services."
8
+
9
+ argument :id, :string, "The request id", required: true
10
+
11
+ def handle(session, operation)
12
+ response = Response.new(operation)
13
+ response.set("services", session.list_services)
14
+ response.status("done")
15
+ response
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,52 @@
1
+ require "rider_server/operation"
2
+ require "rider_server/response"
3
+
4
+ module RiderServer
5
+ module Ops
6
+ class LsSessions < Operation
7
+ documentation "List all sessions"
8
+
9
+ argument :id, :string, "The request id", required: true
10
+
11
+ def handle(session, operation)
12
+ response = Response.new(operation)
13
+ response.set("sessions", controller.sessions.map { |k, v| k })
14
+ response.set("rider/session-headings",
15
+ [
16
+ {
17
+ "id" => "id",
18
+ "name" => "ID",
19
+ "length" => 36
20
+ },
21
+ {
22
+ "id" => "namespace_name",
23
+ "name" => "Namespace",
24
+ "length" => 25
25
+ },
26
+ {
27
+ "id" => "history_items",
28
+ "name" => "History Items",
29
+ "length" => 5
30
+ },
31
+ {
32
+ "id" => "exceptions",
33
+ "name" => "Exceptions",
34
+ "length" => 5
35
+ }
36
+ ])
37
+ response.set("rider/sessions", controller.sessions.map { |k, v| session_summary(v) })
38
+ response.status("done")
39
+ response
40
+ end
41
+
42
+ def session_summary(session)
43
+ {
44
+ "id" => session.id,
45
+ "namespace_name" => session.workspace.namespace_name,
46
+ "history_items" => session.history.length.to_s,
47
+ "exceptions" => session.exceptions.length.to_s
48
+ }
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,43 @@
1
+ require "rider_server/operation"
2
+ require "rider_server/response"
3
+
4
+ module RiderServer
5
+ module Ops
6
+ class Service < Operation
7
+ documentation "Control a Ruby service integration"
8
+
9
+ argument :id, :string, "The request id", required: true
10
+ argument :service, :string, "The name of the Ruby service to control", required: true
11
+ argument :state, :string, "The desired state of the service", required: true
12
+
13
+ def handle(session, operation)
14
+ response = Response.new(operation)
15
+
16
+ service = operation["service"]
17
+ state = operation["state"]
18
+
19
+ current_state = session.service_state(service)
20
+
21
+ case state
22
+ when "start"
23
+ raise "Service already running" if current_state == "running"
24
+ session.start_service(service, response.id)
25
+ response.set("rider/stream", "true")
26
+ when "stop"
27
+ raise "Can't stop service #{service}. It's not running." if current_state == "stopped"
28
+ session.stop_service(service)
29
+ response.status("done")
30
+ else
31
+ # TODO: throwing this exception will caues a "done" response
32
+ # to be sent, which will implicitly close the stream. It
33
+ # might make sense to handle this more gracefully here.
34
+ raise "Unknown state #{state}"
35
+ end
36
+
37
+ response.set("service", service)
38
+ response.set("state", session.service_state(service).to_s)
39
+ response
40
+ end
41
+ end
42
+ end
43
+ end