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
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 302928be71e4f0608534e86f87c555a0f3507004c93c058947b74d480cfde558
4
+ data.tar.gz: 80707ddff979bb2a92f7f696dcbc44ac68830d56dffa11cfc021aa5eec06d743
5
+ SHA512:
6
+ metadata.gz: f9fcc56b8cbceaa6df781155af166873e66c0bfe1261041b6667759dfc5eb56c3c9dfd6943b45e1e7f302cb0737e21da048ba399f837dec4a77416cb7af037bf
7
+ data.tar.gz: 74527b4e988b97b6ed527530bbdef94cc6b48776c74fef12930dbf70a59c648209f3afa615ebb835861acba0b0eba70884107268b65e7aa12da0adb7b63040f8
data/.build.yml ADDED
@@ -0,0 +1,23 @@
1
+ image: debian/stable
2
+ sources:
3
+ - https://git.sr.ht/~rsl/rider
4
+ packages:
5
+ - bundler
6
+ - curl
7
+ - git-extras
8
+ - ruby-full
9
+ - ruby-dev
10
+ - zlib1g-dev
11
+ tasks:
12
+ - lint: |
13
+ cd rider
14
+ bundle install --path="../.gems"
15
+ bundle exec rake standard
16
+ - test: |
17
+ cd rider
18
+ bundle install --path="../.gems"
19
+ bundle exec rake test
20
+ triggers:
21
+ - action: email
22
+ condition: failure
23
+ to: "Russell Sim <rsl@simopolis.xyz>"
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.1.6
data/.standard.yml ADDED
@@ -0,0 +1,3 @@
1
+ # For available configuration options, see:
2
+ # https://github.com/standardrb/standard
3
+ ruby_version: 3.0
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2024-07-27
4
+
5
+ - Initial release
data/README.md ADDED
@@ -0,0 +1,44 @@
1
+ # RIDER -- RIDER Interactive Development Environment for Ruby
2
+
3
+ RIDER Server is the server component of the RIDER development
4
+ environment.
5
+
6
+ ## Installation
7
+
8
+ TODO: Replace
9
+ `UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG`
10
+ with your gem name right after releasing it to RubyGems.org. Please do
11
+ not do it earlier due to security reasons. Alternatively, replace this
12
+ section with instructions to install your gem from git if you don't
13
+ plan to release to RubyGems.org.
14
+
15
+ Install the gem and add to the application's Gemfile by executing:
16
+
17
+ $ bundle add rider-server
18
+
19
+ If bundler is not being used to manage dependencies, install the gem by executing:
20
+
21
+ $ gem install rider-server
22
+
23
+ ## Usage
24
+
25
+ TODO: Write usage instructions here
26
+
27
+ ## Development
28
+
29
+ After checking out the repo, run `bin/setup` to install
30
+ dependencies. Then, run `rake test` to run the tests. You can also run
31
+ `bin/console` for an interactive prompt that will allow you to
32
+ experiment.
33
+
34
+ To install this gem onto your local machine, run `bundle exec rake
35
+ install`. To release a new version, update the version number in
36
+ `version.rb`, and then run `bundle exec rake release`, which will
37
+ create a git tag for the version, push git commits and the created
38
+ tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
39
+
40
+ ## Contributing
41
+
42
+ Bug reports https://todo.sr.ht/~rsl/rider
43
+
44
+ Support or discussion https://lists.sr.ht/~rsl/rider-devel
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "minitest/test_task"
5
+ require "bump/tasks"
6
+
7
+ Bump.tag_by_default = true
8
+ Minitest::TestTask.create
9
+
10
+ require "standard/rake"
11
+
12
+ task default: %i[test standard]
data/exe/rider-server ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+ # Author: Russell Sim
3
+ # Copyright (c) 2024 Russell Sim
4
+ # SPDX-License-Identifier: MIT
5
+
6
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + "/../lib")
7
+
8
+ require "rider_server/server"
9
+
10
+ server = RiderServer::Server.new
11
+ server.run
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # array.rb -- Array extensions
5
+ #
6
+ # Author: Russell Sim
7
+ # Copyright (c) 2024 Russell Sim
8
+ # SPDX-License-Identifier: MIT
9
+
10
+ class Array
11
+ def rider_inspect
12
+ max_length = 50
13
+ sample = +""
14
+
15
+ dup.slice(0, 3).each do |item|
16
+ str = item.inspect
17
+ remaining = max_length - (sample.length + str.length)
18
+
19
+ # Truncate the string, and preserve any terminators
20
+ if sample.length >= max_length
21
+ # Ignore item
22
+ elsif remaining < 0
23
+ terminator = ["]", '"', "'", ">"].member?(str[-1]) ? str[-1] : ""
24
+ sample << (str.dup[0, max_length] + "..." + terminator)
25
+ else
26
+ sample << str.dup
27
+ sample << ", "
28
+ end
29
+ end
30
+ "#<#{self.class}: [#{sample}] size: #{length}>"
31
+ end
32
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # hash.rb -- Hash extensions
5
+ #
6
+ # Author: Russell Sim
7
+ # Copyright (c) 2024 Russell Sim
8
+ # SPDX-License-Identifier: MIT
9
+
10
+ class Hash
11
+ def rider_inspect
12
+ "#<#{self.class}: #{length} items}>"
13
+ end
14
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # object.rb -- Object class extensions
5
+ #
6
+ # Author: Russell Sim
7
+ # Copyright (c) 2024 Russell Sim
8
+ # SPDX-License-Identifier: MIT
9
+
10
+ class Object
11
+ def rider_inspect
12
+ if defined?(inspect) && method(:inspect).owner != Object && method(:inspect).owner != Kernel
13
+ inspect
14
+ else
15
+ "#<#{self.class} #{object_id}>"
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # string.rb -- String extensions
5
+ #
6
+ # Author: Russell Sim
7
+ # Copyright (c) 2024 Russell Sim
8
+ # SPDX-License-Identifier: MIT
9
+
10
+ class String
11
+ def rider_inspect
12
+ if length <= 50
13
+ "#<String #{inspect}>"
14
+ else
15
+ "#<String \"#{self[0, 50]}...\" #{length - 50} more characters>"
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # symbol.rb -- Symbol class extensions
5
+ #
6
+ # Author: Russell Sim
7
+ # Copyright (c) 2024 Russell Sim
8
+ # SPDX-License-Identifier: MIT
9
+
10
+ class Symbol
11
+ def rider_inspect
12
+ ":#{self}"
13
+ end
14
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # errors.rb -- Errors
5
+ #
6
+ # Author: Russell Sim
7
+ # Copyright (c) 2024 Russell Sim
8
+ # SPDX-License-Identifier: MIT
9
+
10
+ module RiderServer
11
+ class Error < StandardError; end
12
+
13
+ class EvalInterrupt < Error; end
14
+
15
+ class ModuleLookupError < Error; end
16
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # exception_extension.rb -- Add stackframes to exceptions
5
+ #
6
+ # Author: Russell Sim
7
+ # Copyright (c) 2024 Russell Sim
8
+ # SPDX-License-Identifier: MIT
9
+
10
+ require "binding_of_caller"
11
+
12
+ module RiderServer
13
+ module ExceptionExtension
14
+ INPUT_FORMAT = %r{^((?:[a-zA-Z]:)?[^:]+):(\d+)(?::in `([^']+)')?$}
15
+
16
+ def set_backtrace(...)
17
+ if caller.none? { |loc| loc.match(INPUT_FORMAT) && Regexp.last_match(1) == __FILE__ }
18
+ @__rider_bindings_stack = ::Kernel.binding.callers.drop(1)
19
+ end
20
+
21
+ super
22
+ end
23
+
24
+ def __rider_bindings_stack
25
+ @__rider_bindings_stack || []
26
+ end
27
+ end
28
+ end
29
+
30
+ unless ::Exception.included_modules.include?(RiderServer::ExceptionExtension)
31
+ class ::Exception
32
+ prepend RiderServer::ExceptionExtension
33
+ end
34
+ end
@@ -0,0 +1,148 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # inspect.rb -- Methods to help inspect objects
5
+ #
6
+ # Author: Russell Sim
7
+ # Copyright (c) 2024 Russell Sim
8
+ # SPDX-License-Identifier: MIT
9
+
10
+ require "rider_server/utils"
11
+
12
+ module RiderServer
13
+ module Inspect
14
+
15
+ def self.safely_eval(string, file, line)
16
+ code = <<~HEREDOC
17
+ begin
18
+ #{string}
19
+ rescue StandardError => e
20
+ e
21
+ end
22
+ HEREDOC
23
+ eval(code, TOPLEVEL_BINDING, file, line) # rubocop:disable Security/Eval
24
+ end
25
+
26
+ def self.class(obj)
27
+ klass = obj.class
28
+ {
29
+ "name" => klass.to_s,
30
+ "value" => Utils.rider_inspect(safely_eval("Object.const_get('#{klass}')", __FILE__, __LINE__)),
31
+ "inspect-location" => "toplevel_const_get:#{klass.inspect}",
32
+ "source-location" => safely_eval("Object.const_source_location('#{klass}')", __FILE__, __LINE__) || []
33
+ }
34
+ end
35
+
36
+ def self.ancestors(obj)
37
+ # Look for instance ancestors on the class.
38
+ obj = obj.class if !obj.respond_to?(:ancestors) && obj.class.respond_to?(:ancestors)
39
+
40
+ if obj.class.respond_to?(:ancestors)
41
+ obj.class.ancestors.map do |item|
42
+ {
43
+ "name" => item.to_s,
44
+ "value" => Utils.rider_inspect(item),
45
+ "inspect-location" => "ancestor_find:#{item}",
46
+ "source-location" => safely_eval("Object.const_source_location('#{item}')", __FILE__, __LINE__) || []
47
+ }
48
+ end
49
+ else
50
+ []
51
+ end
52
+ end
53
+
54
+ def self.constants(obj)
55
+ if obj.respond_to?(:constants)
56
+ constants = safely_eval("#{obj.inspect}.constants", __FILE__, __LINE__)
57
+ return [] if constants.nil? # Eigen classes have no constants
58
+ constants.map do |item|
59
+ {
60
+ "name" => item.to_s,
61
+ "value" => Utils.rider_inspect(safely_eval("#{obj.inspect}.const_get('#{item}')", __FILE__, __LINE__)),
62
+ "inspect-location" => "const_get:#{obj.inspect}::#{item}",
63
+ "source-location" => safely_eval("#{obj.inspect}.const_source_location('#{item}')", __FILE__, __LINE__) || []
64
+ }
65
+ end.sort_by { |hash| hash["name"] }
66
+ else
67
+ []
68
+ end
69
+ end
70
+
71
+ def self.describe_method(method)
72
+ {
73
+ "name" => method.name.to_s,
74
+ "value" => Utils.rider_inspect(method),
75
+ "visibility" => if method.private?
76
+ ":private"
77
+ elsif method.protected?
78
+ ":protected"
79
+ elsif method.public?
80
+ ":public"
81
+ else
82
+ ":unknown"
83
+ end,
84
+ "owner" => method.owner.inspect,
85
+ "source-location" => method.source_location || [],
86
+ "parameters" => method.parameters.map { |subarray| subarray.map(&:to_s) }
87
+ }
88
+ end
89
+
90
+ def self.methods(obj)
91
+ if obj.respond_to?(:methods)
92
+ obj.methods.map do |name|
93
+ describe_method(obj.method(name))
94
+ end
95
+ else
96
+ []
97
+ end
98
+ end
99
+
100
+ def self.instance_methods(obj)
101
+ if obj.respond_to?(:instance_methods)
102
+ obj.instance_methods.map do |item|
103
+ describe_method(obj.instance_method(item))
104
+ end
105
+ else
106
+ []
107
+ end
108
+ end
109
+
110
+ def self.instance_variables(obj)
111
+ if obj.respond_to?(:instance_variables)
112
+ obj.instance_variables.map do |item|
113
+ [
114
+ {
115
+ "name" => item.to_s
116
+ },
117
+ {
118
+ "name" => obj.instance_variable_get(item).to_s,
119
+ "value" => Utils.rider_inspect(obj.instance_variable_get(item)),
120
+ "inspect-location" => "instance_variable_get:#{item}"
121
+ }
122
+ ]
123
+ end
124
+ else
125
+ []
126
+ end
127
+ end
128
+
129
+ def self.class_variables(obj)
130
+ if obj.respond_to?(:class_variables)
131
+ obj.class_variables.map do |item|
132
+ [
133
+ {
134
+ "name" => item.to_s
135
+ },
136
+ {
137
+ "name" => obj.class_variable_get(item).to_s,
138
+ "value" => Utils.rider_inspect(obj.class_variable_get(item)),
139
+ "inspect-location" => "class_variable_get:#{item}"
140
+ }
141
+ ]
142
+ end
143
+ else
144
+ []
145
+ end
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,13 @@
1
+ require "logger"
2
+
3
+ module RiderServer
4
+ module Logger
5
+ def log
6
+ @logger ||= begin
7
+ log_level = (ENV["LOG_LEVEL"] || "INFO").upcase.to_sym
8
+
9
+ ::Logger.new(STDERR, level: log_level) # rubocop:disable Style/GlobalStdStream
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # operation.rb -- Base class for operations
5
+ #
6
+ # Author: Russell Sim
7
+ # Copyright (c) 2024 Russell Sim
8
+ # SPDX-License-Identifier: MIT
9
+
10
+ require "rider_server/logger"
11
+
12
+ module RiderServer
13
+ class Operation
14
+ include RiderServer::Logger
15
+
16
+ attr_reader :documentation, :arguments, :controller
17
+ def self.operation_name
18
+ name.split("::").last.gsub(/(.)([A-Z])/, '\1_\2').downcase
19
+ end
20
+
21
+ def self.documentation(desc)
22
+ @documentation = desc
23
+ end
24
+
25
+ def self.argument(name, type, description, required: false)
26
+ @arguments ||= []
27
+
28
+ raise ArgumentError, "Invalid argument type #{type}" \
29
+ unless [:string, :integer, :array].include?(type)
30
+
31
+ @arguments << {name: name, type: type, required: required, description: description}
32
+ end
33
+
34
+ def initialize(controller)
35
+ @controller = controller
36
+ @documentation = self.class.instance_variable_get(:@documentation) || ""
37
+ @arguments = self.class.instance_variable_get(:@arguments) || []
38
+ raise ArgumentError, "Operation must have a documentation string" if @documentation.empty?
39
+ raise ArgumentError, "Operation must have at least one argument" if @arguments.empty?
40
+ end
41
+
42
+ def create_response(operation)
43
+ Utils.create_response(operation)
44
+ end
45
+
46
+ def send_response(response)
47
+ controller.send_response(response)
48
+ end
49
+
50
+ def validate_request!(request)
51
+ @arguments.each do |arg|
52
+ # Skip optional arguments
53
+ next if !arg[:required] && !request.key?(arg[:name])
54
+
55
+ name = arg[:name].to_s
56
+
57
+ if arg[:type] == :integer
58
+ request[name] = Integer(request[name])
59
+ elsif arg[:type] == :string
60
+ request[name] = String(request[name])
61
+ elsif arg[:type] == :array && request[name].is_a?(Array)
62
+ # pass, the type is correct
63
+ else
64
+ raise ArgumentError, "Invalid argument type #{arg[:type]}"
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,136 @@
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 "rider_server/logger"
11
+ require "rider_server/session"
12
+ require "rider_server/errors"
13
+ require "rider_server/response"
14
+ require "rider_server/core_ext/array"
15
+ require "rider_server/core_ext/hash"
16
+ require "rider_server/core_ext/object"
17
+ require "rider_server/core_ext/string"
18
+ require "rider_server/core_ext/symbol"
19
+ require "rider_server/ops/clone"
20
+ require "rider_server/ops/close"
21
+ require "rider_server/ops/completions"
22
+ require "rider_server/ops/eval"
23
+ require "rider_server/ops/inspect"
24
+ require "rider_server/ops/inspect_exception"
25
+ require "rider_server/ops/interrupt"
26
+ require "rider_server/ops/load_path"
27
+ require "rider_server/ops/lookup"
28
+ require "rider_server/ops/ls_exceptions"
29
+ require "rider_server/ops/ls_services"
30
+ require "rider_server/ops/ls_sessions"
31
+ require "rider_server/ops/service"
32
+ require "rider_server/ops/set_namespace"
33
+ require "rider_server/ops/set_namespace_variable"
34
+ require "rider_server/ops/toggle_catch_all_exceptions"
35
+
36
+ module RiderServer
37
+ class Operations
38
+ include RiderServer::Logger
39
+
40
+ attr_reader :executing_requests
41
+ attr_reader :sessions
42
+ attr_reader :stdin, :stdout, :stderr
43
+ attr_reader :response_queue
44
+ attr_accessor :sessions_catching_exceptions
45
+
46
+ OPERATIONS = [
47
+ Ops::Clone,
48
+ Ops::Close,
49
+ Ops::Completions,
50
+ Ops::Eval,
51
+ Ops::Inspect,
52
+ Ops::InspectException,
53
+ Ops::Interrupt,
54
+ Ops::LoadPath,
55
+ Ops::Lookup,
56
+ Ops::LsExceptions,
57
+ Ops::LsServices,
58
+ Ops::LsSessions,
59
+ Ops::Service,
60
+ Ops::SetNamespace,
61
+ Ops::SetNamespaceVariable,
62
+ Ops::ToggleCatchAllExceptions
63
+ ]
64
+
65
+ def initialize(response_queue)
66
+ @sessions = {}
67
+ @response_queue = response_queue
68
+ @executing_requests = []
69
+ @operations = OPERATIONS.each_with_object({}) do |klass, h|
70
+ h[klass.operation_name] = klass.new(self)
71
+ end
72
+ @sessions_catching_exceptions = []
73
+ end
74
+
75
+ def send_response(response)
76
+ if response
77
+ @response_queue.push(response)
78
+ if response.done?
79
+ @executing_requests.delete(response.id)
80
+ end
81
+ end
82
+ end
83
+
84
+ def handle(operation)
85
+ session = nil
86
+
87
+ session_id = operation["session"]
88
+ op = operation["op"].tr("-", "_")
89
+ log.info("Handling operation '#{operation}'")
90
+ session = get_session(session_id)
91
+ @executing_requests << operation["id"]
92
+ if @operations.key? op
93
+ @operations[op].validate_request!(operation)
94
+ send_response(@operations[op].handle(session, operation))
95
+ else
96
+ log.warn("Unknown operation '#{op}' requested")
97
+ response = Response.new(operation)
98
+ response.status "unknown-op", "done"
99
+ send_response(response)
100
+ end
101
+ rescue ScriptError, StandardError => e
102
+ response = Response.new(operation)
103
+ response.set("ex", e.inspect)
104
+ response.set("out", e.full_message)
105
+ response.status("eval-error", "done")
106
+ if session
107
+ exception_id = session.add_exception(operation["id"], e)
108
+ response.set("rider/exception-id", exception_id)
109
+ end
110
+
111
+ # Broadcast the exception to sessions that are listening
112
+ @sessions_catching_exceptions.each do |s|
113
+ next if session == s
114
+ @sessions[s]&.add_exception(operation["id"], e)
115
+ end
116
+ send_response(response)
117
+ end
118
+
119
+ def new_session
120
+ Session.new(@response_queue)
121
+ end
122
+
123
+ def get_session(id)
124
+ return nil unless id
125
+ @sessions[id]
126
+ end
127
+
128
+ def add_session(session)
129
+ @sessions[session.id] = session
130
+ end
131
+
132
+ def delete_session(session_id)
133
+ @sessions.delete(session_id)
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,32 @@
1
+ require "rider_server/operation"
2
+ require "rider_server/response"
3
+
4
+ module RiderServer
5
+ module Ops
6
+ class Clone < Operation
7
+ documentation "Clone a session"
8
+
9
+ argument :id, :string, "The request id", required: true
10
+ argument :session, :string, "The session to clone"
11
+
12
+ # Handle the clone operation, session will be nil
13
+ def handle(session, operation)
14
+ response = Response.new(operation)
15
+
16
+ # Clone a specific session if specified
17
+ storage_session = operation["session"]
18
+ new_session = if storage_session
19
+ controller.get_session(storage_session).clone
20
+ else
21
+ controller.new_session
22
+ end
23
+
24
+ controller.add_session(new_session)
25
+
26
+ response.set("new-session", new_session.id)
27
+ response.status("done")
28
+ response
29
+ end
30
+ end
31
+ end
32
+ end