rider-server 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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