ruby-lsp-ree 0.1.2 → 0.1.4

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.
@@ -0,0 +1,42 @@
1
+ require_relative "../handlers/hover_handler"
2
+
3
+ module RubyLsp
4
+ module Ree
5
+ class HoverListener
6
+ include RubyLsp::Ree::ReeLspUtils
7
+
8
+ def initialize(response_builder, node_context, index, dispatcher)
9
+ @response_builder = response_builder
10
+ @handler = RubyLsp::Ree::HoverHandler.new(index, node_context)
11
+
12
+ dispatcher.register(self, :on_call_node_enter)
13
+ dispatcher.register(self, :on_constant_read_node_enter)
14
+ end
15
+
16
+ def on_constant_read_node_enter(node)
17
+ # ree_object = @finder.search_classes(node.name.to_s).first
18
+
19
+ # $stderr.puts("ree_object #{ree_object.inspect}")
20
+
21
+ # return unless ree_object
22
+
23
+ # $stderr.puts("ree_object comm #{ree_object.comments.to_s}")
24
+
25
+ # @response_builder.push(ree_object.comments.to_s, category: :documentation)
26
+ end
27
+
28
+ def on_call_node_enter(node)
29
+ hover_items = @handler.get_ree_object_hover_items(node)
30
+ put_items_into_response(hover_items)
31
+ rescue => e
32
+ $stderr.puts("error in hover listener(on_call_node_enter): #{e.message} : #{e.backtrace.first}")
33
+ end
34
+
35
+ def put_items_into_response(items)
36
+ items.each do |item|
37
+ @response_builder.push(item, category: :documentation)
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -1,28 +1,30 @@
1
1
  require_relative 'parsed_link_node'
2
+ require 'ostruct'
2
3
 
3
4
  class RubyLsp::Ree::ParsedDocument
4
5
  include RubyLsp::Ree::ReeLspUtils
5
6
 
6
7
  LINK_DSL_MODULE = 'Ree::LinkDSL'
7
8
 
8
- attr_reader :ast, :package_name, :class_node, :fn_node, :fn_block_node, :class_includes,
9
- :link_nodes, :values, :action_node, :action_block_node, :dao_node, :dao_block_node, :filters,
10
- :bean_node, :bean_block_node, :bean_methods
9
+ attr_reader :ast, :package_name, :class_node, :fn_node, :class_includes,
10
+ :link_nodes, :values, :action_node, :dao_node, :filters,
11
+ :bean_node, :bean_methods, :mapper_node, :links_container_block_node, :aggregate_node
11
12
 
12
13
  def initialize(ast)
13
14
  @ast = ast
14
15
  end
15
16
 
16
17
  def links_container_node
17
- @fn_node || @action_node || @dao_node || @bean_node
18
+ # TODO don't use separate node, use one field for all and additional type field: links_container_node_type
19
+ @fn_node || @action_node || @dao_node || @bean_node || @mapper_node || @aggregate_node
18
20
  end
19
21
 
20
- def links_container_block_node
21
- @fn_block_node || @action_block_node || @dao_block_node || @bean_block_node
22
+ def allows_root_links?
23
+ false
22
24
  end
23
25
 
24
26
  def includes_link_dsl?
25
- @class_includes.any?{ _1.name == LINK_DSL_MODULE }
27
+ @class_includes.any?{ node_name(_1) == LINK_DSL_MODULE }
26
28
  end
27
29
 
28
30
  def includes_linked_constant?(const_name)
@@ -30,11 +32,27 @@ class RubyLsp::Ree::ParsedDocument
30
32
  end
31
33
 
32
34
  def includes_linked_object?(obj_name)
33
- @link_nodes.map(&:name).include?(obj_name)
35
+ @link_nodes.map{ node_name(_1) }.include?(obj_name)
36
+ end
37
+
38
+ def find_link_node(name)
39
+ @link_nodes.detect{ node_name(_1) == name }
40
+ end
41
+
42
+ def find_link_with_imported_object(name)
43
+ @link_nodes.detect do |link_node|
44
+ link_node.imports.include?(name)
45
+ end
46
+ end
47
+
48
+ def find_import_for_package(name, package_name)
49
+ @link_nodes.detect do |link_node|
50
+ link_node.imports.include?(name) && link_node.link_package_name == package_name
51
+ end
34
52
  end
35
53
 
36
54
  def has_blank_links_container?
37
- links_container_node && !links_container_block_node
55
+ links_container_node && !@links_container_block_node
38
56
  end
39
57
 
40
58
  def set_package_name(package_name)
@@ -48,35 +66,49 @@ class RubyLsp::Ree::ParsedDocument
48
66
  def parse_fn_node
49
67
  return unless class_node
50
68
 
51
- @fn_node ||= class_node.body.body.detect{ |node| node.name == :fn }
52
- @fn_block_node = @fn_node&.block
69
+ @fn_node ||= class_node.body.body.detect{ |node| node_name(node) == :fn }
70
+ @links_container_block_node ||= @fn_node&.block
53
71
  end
54
72
 
55
73
  def parse_action_node
56
74
  return unless class_node
57
75
 
58
- @action_node ||= class_node.body.body.detect{ |node| node.name == :action }
59
- @action_block_node = @action_node&.block
76
+ @action_node ||= class_node.body.body.detect{ |node| node_name(node) == :action }
77
+ @links_container_block_node ||= @action_node&.block
60
78
  end
61
79
 
62
80
  def parse_dao_node
63
81
  return unless class_node
64
82
 
65
- @dao_node ||= class_node.body.body.detect{ |node| node.name == :dao }
66
- @dao_block_node = @dao_node&.block
83
+ @dao_node ||= class_node.body.body.detect{ |node| node_name(node) == :dao }
84
+ @links_container_block_node ||= @dao_node&.block
67
85
  end
68
86
 
69
87
  def parse_bean_node
70
88
  return unless class_node
71
89
 
72
- @bean_node ||= class_node.body.body.detect{ |node| node.name == :bean }
73
- @bean_block_node = @bean_node&.block
90
+ @bean_node ||= class_node.body.body.detect{ |node| node_name(node) == :bean }
91
+ @links_container_block_node ||= @bean_node&.block
92
+ end
93
+
94
+ def parse_mapper_node
95
+ return unless class_node
96
+
97
+ @mapper_node ||= class_node.body.body.detect{ |node| node_name(node) == :mapper }
98
+ @links_container_block_node ||= @mapper_node&.block
99
+ end
100
+
101
+ def parse_aggregate_node
102
+ return unless class_node
103
+
104
+ @aggregate_node ||= class_node.body.body.detect{ |node| node_name(node) == :aggregate }
105
+ @links_container_block_node ||= @aggregate_node&.block
74
106
  end
75
107
 
76
108
  def parse_class_includes
77
109
  return unless class_node
78
110
 
79
- @class_includes ||= class_node.body.body.select{ _1.name == :include }.map do |class_include|
111
+ @class_includes ||= class_node.body.body.select{ node_name(_1) == :include }.map do |class_include|
80
112
  parent_name = class_include.arguments.arguments.first.parent.name.to_s
81
113
  module_name = class_include.arguments.arguments.first.name
82
114
 
@@ -89,10 +121,10 @@ class RubyLsp::Ree::ParsedDocument
89
121
  def parse_links
90
122
  return unless class_node
91
123
 
92
- nodes = if links_container_node && links_container_block_node.body
93
- links_container_block_node.body.body.select{ |node| node.name == :link }
94
- elsif class_includes.any?{ _1.name == LINK_DSL_MODULE }
95
- class_node.body.body.select{ |node| node.name == :link }
124
+ nodes = if links_container_node && @links_container_block_node && @links_container_block_node.body
125
+ @links_container_block_node.body.body.select{ |node| node_name(node) == :link }
126
+ elsif class_includes.any?{ node_name(_1) == LINK_DSL_MODULE }
127
+ class_node.body.body.select{ |node| node_name(node) == :link }
96
128
  else
97
129
  []
98
130
  end
@@ -108,7 +140,7 @@ class RubyLsp::Ree::ParsedDocument
108
140
  return unless class_node
109
141
 
110
142
  @values ||= class_node.body.body
111
- .select{ _1.name == :val }
143
+ .select{ node_name(_1) == :val }
112
144
  .map{ OpenStruct.new(name: _1.arguments.arguments.first.unescaped) }
113
145
  end
114
146
 
@@ -116,7 +148,7 @@ class RubyLsp::Ree::ParsedDocument
116
148
  return unless class_node
117
149
 
118
150
  @filters ||= class_node.body.body
119
- .select{ _1.name == :filter }
151
+ .select{ node_name(_1) == :filter }
120
152
  .map{ OpenStruct.new(name: _1.arguments.arguments.first.unescaped, signatures: parse_filter_signature(_1)) }
121
153
 
122
154
  end
@@ -126,7 +158,7 @@ class RubyLsp::Ree::ParsedDocument
126
158
 
127
159
  @bean_methods ||= class_node.body.body
128
160
  .select{ _1.is_a?(Prism::DefNode) }
129
- .map{ OpenStruct.new(name: _1.name.to_s, signatures: parse_signatures_from_params(_1.parameters)) }
161
+ .map{ OpenStruct.new(name: node_name(_1).to_s, signatures: parse_signatures_from_params(_1.parameters)) }
130
162
  end
131
163
 
132
164
  def parse_filter_signature(filter_node)
@@ -147,4 +179,10 @@ class RubyLsp::Ree::ParsedDocument
147
179
  name_parts = [class_node.constant_path&.parent&.name, class_node.constant_path.name]
148
180
  name_parts.compact.map(&:to_s).join('::')
149
181
  end
182
+
183
+ def node_name(node)
184
+ return nil unless node.respond_to?(:name)
185
+
186
+ node.name
187
+ end
150
188
  end
@@ -1,5 +1,6 @@
1
1
  require 'prism'
2
2
  require_relative 'parsed_document'
3
+ require_relative 'parsed_rspec_document'
3
4
 
4
5
  class RubyLsp::Ree::ParsedDocumentBuilder
5
6
  extend RubyLsp::Ree::ReeLspUtils
@@ -7,6 +8,7 @@ class RubyLsp::Ree::ParsedDocumentBuilder
7
8
  def self.build_from_uri(uri, type = nil)
8
9
  ast = Prism.parse_file(uri.path).value
9
10
  document = build_document(ast, type)
11
+ return unless document
10
12
 
11
13
  document.set_package_name(package_name_from_uri(uri))
12
14
 
@@ -15,6 +17,7 @@ class RubyLsp::Ree::ParsedDocumentBuilder
15
17
 
16
18
  def self.build_from_ast(ast, uri, type = nil)
17
19
  document = build_document(ast, type)
20
+ return unless document
18
21
 
19
22
  document.set_package_name(package_name_from_uri(uri))
20
23
 
@@ -35,10 +38,37 @@ class RubyLsp::Ree::ParsedDocumentBuilder
35
38
  when :bean
36
39
  build_bean_document(ast)
37
40
  else
41
+ build_detected_doument_type(ast)
42
+ end
43
+ end
44
+
45
+ def self.build_detected_doument_type(ast)
46
+ if has_root_class?(ast)
38
47
  build_regular_document(ast)
48
+ elsif has_root_rspec_call?(ast)
49
+ build_rspec_document(ast)
50
+ else
51
+ nil
39
52
  end
40
53
  end
41
54
 
55
+ def self.has_root_class?(ast)
56
+ ast.statements.body.detect{ |node| node.is_a?(Prism::ClassNode) }
57
+ end
58
+
59
+ def self.has_root_rspec_call?(ast)
60
+ ast.statements.body.detect{ |node| node.is_a?(Prism::CallNode) && node.name == :describe }
61
+ end
62
+
63
+ def self.build_rspec_document(ast)
64
+ document = RubyLsp::Ree::ParsedRspecDocument.new(ast)
65
+
66
+ document.parse_describe_node
67
+ document.parse_links
68
+
69
+ document
70
+ end
71
+
42
72
  def self.build_regular_document(ast)
43
73
  document = RubyLsp::Ree::ParsedDocument.new(ast)
44
74
 
@@ -47,6 +77,8 @@ class RubyLsp::Ree::ParsedDocumentBuilder
47
77
  document.parse_action_node
48
78
  document.parse_bean_node
49
79
  document.parse_dao_node
80
+ document.parse_mapper_node
81
+ document.parse_aggregate_node
50
82
  document.parse_class_includes
51
83
  document.parse_links
52
84
 
@@ -13,7 +13,12 @@ class RubyLsp::Ree::ParsedLinkNode
13
13
  end
14
14
 
15
15
  def link_package_name
16
- from_arg_value || document_package
16
+ case link_type
17
+ when :object_name
18
+ from_arg_value || document_package
19
+ when :file_path
20
+ @name.split('/').first
21
+ end
17
22
  end
18
23
 
19
24
  def location
@@ -0,0 +1,57 @@
1
+ require_relative 'parsed_link_node'
2
+ require 'ostruct'
3
+
4
+ class RubyLsp::Ree::ParsedRspecDocument
5
+ include RubyLsp::Ree::ReeLspUtils
6
+
7
+ attr_reader :ast, :package_name, :describe_node
8
+
9
+ def initialize(ast)
10
+ @ast = ast
11
+ end
12
+
13
+ def set_package_name(package_name)
14
+ @package_name = package_name
15
+ end
16
+
17
+ def allows_root_links?
18
+ true
19
+ end
20
+
21
+ def includes_linked_object?(obj_name)
22
+ @link_nodes.map(&:name).include?(obj_name)
23
+ end
24
+
25
+ def links_container_node
26
+ nil
27
+ end
28
+
29
+ def root_node_line_location
30
+ OpenStruct.new(
31
+ start_line: @describe_node.location.start_line,
32
+ end_column: @describe_node.block.opening_loc.end_column
33
+ )
34
+ end
35
+
36
+ def has_blank_links_container?
37
+ false
38
+ end
39
+
40
+ def includes_link_dsl?
41
+ false
42
+ end
43
+
44
+ def parse_describe_node
45
+ @describe_node ||= @ast.statements.body.detect{ |node| node.is_a?(Prism::CallNode) && node.name == :describe }
46
+ end
47
+
48
+ def parse_links
49
+ nodes = @describe_node.block.body.body.select{ |node| node.name == :link }
50
+
51
+ @link_nodes = nodes.map do |link_node|
52
+ link_node = RubyLsp::Ree::ParsedLinkNode.new(link_node, package_name)
53
+ link_node.parse_imports
54
+ link_node
55
+ end
56
+ end
57
+ end
@@ -10,6 +10,8 @@ module RubyLsp
10
10
  def run_formatting(uri, document)
11
11
  source = document.source
12
12
  sort_links(source)
13
+ rescue => e
14
+ $stderr.puts("error in ree_formatter: #{e.message} : #{e.backtrace.first}")
13
15
  end
14
16
 
15
17
  private
@@ -1,12 +1,12 @@
1
1
  require 'prism'
2
- require_relative "ree_lsp_utils"
2
+ require_relative "utils/ree_lsp_utils"
3
3
 
4
4
  module RubyLsp
5
5
  module Ree
6
6
  class ReeIndexingEnhancement < RubyIndexer::Enhancement
7
7
  include RubyLsp::Ree::ReeLspUtils
8
8
 
9
- REE_INDEXED_OBJECTS = [:fn, :enum, :action, :dao, :bean]
9
+ REE_INDEXED_OBJECTS = [:fn, :enum, :action, :dao, :bean, :mapper, :aggregate]
10
10
 
11
11
  def on_call_node_enter(node)
12
12
  return unless @listener.current_owner
@@ -33,8 +33,8 @@ module RubyLsp
33
33
  private
34
34
 
35
35
  def parse_signatures(fn_name)
36
- uri = @listener.instance_variable_get(:@uri)
37
- ast = Prism.parse_file(uri.path).value
36
+ source = @listener.instance_variable_get(:@source_lines).join
37
+ ast = Prism.parse(source).value
38
38
 
39
39
  class_node = ast.statements.body.detect{ |node| node.is_a?(Prism::ClassNode) }
40
40
  return [] unless class_node
@@ -7,9 +7,15 @@ module RubyLsp
7
7
  ENUM_TYPE_STRING = 'type: :enum'
8
8
  DAO_TYPE_STRING = 'type: :dao'
9
9
  BEAN_TYPE_STRING = 'type: :bean'
10
+ MAPPER_TYPE_STRING = 'type: :mapper'
11
+ AGGREGATE_TYPE_STRING = 'type: :aggregate'
10
12
 
11
- def self.search_objects(index, name, limit)
12
- index.prefix_search(name)
13
+ def initialize(index)
14
+ @index = index
15
+ end
16
+
17
+ def search_objects(name, limit)
18
+ @index.prefix_search(name)
13
19
  .take(MAX_LIMIT)
14
20
  .flatten
15
21
  .select{ _1.comments }
@@ -18,26 +24,69 @@ module RubyLsp
18
24
  .take(limit)
19
25
  end
20
26
 
21
- def self.find_enum(index, name)
22
- objects_by_name = index[name]
27
+ def search_class_objects(name)
28
+ @index
29
+ .names
30
+ .select{ _1.split('::').last[0...name.size] == name}
31
+ end
32
+
33
+ def search_classes(name)
34
+ keys = search_class_objects(name)
35
+ @index.instance_variable_get(:@entries).values_at(*keys)
36
+ end
37
+
38
+ def find_object(name)
39
+ objects_by_name = @index[name]
40
+ return unless objects_by_name
41
+
42
+ objects_by_name.detect{ _1.comments.to_s.lines.first&.chomp == REE_OBJECT_STRING }
43
+ end
44
+
45
+ def find_objects_by_types(name, types)
46
+ objects_by_name = @index[name]
47
+ return [] unless objects_by_name
48
+
49
+ objects_by_name.select{ types.include?(object_type(_1)) }
50
+ end
51
+
52
+ def find_enum(name)
53
+ objects_by_name = @index[name]
23
54
  return unless objects_by_name
24
55
 
25
56
  objects_by_name.detect{ _1.comments.lines[1]&.chomp == ENUM_TYPE_STRING }
26
57
  end
27
58
 
28
- def self.find_dao(index, name)
29
- objects_by_name = index[name]
59
+ def find_dao(name)
60
+ objects_by_name = @index[name]
30
61
  return unless objects_by_name
31
62
 
32
63
  objects_by_name.detect{ _1.comments.lines[1]&.chomp == DAO_TYPE_STRING }
33
64
  end
34
65
 
35
- def self.find_bean(index, name)
36
- objects_by_name = index[name]
66
+ def find_bean(index, name)
67
+ objects_by_name = @index[name]
37
68
  return unless objects_by_name
38
69
 
39
70
  objects_by_name.detect{ _1.comments.lines[1]&.chomp == BEAN_TYPE_STRING }
40
71
  end
72
+
73
+ def object_type(ree_object)
74
+ # TODO rewrite to use string split
75
+ case ree_object.comments.lines[1]&.chomp
76
+ when DAO_TYPE_STRING
77
+ :dao
78
+ when BEAN_TYPE_STRING
79
+ :bean
80
+ when ENUM_TYPE_STRING
81
+ :enum
82
+ when MAPPER_TYPE_STRING
83
+ :mapper
84
+ when AGGREGATE_TYPE_STRING
85
+ :aggregate
86
+ else
87
+ nil
88
+ end
89
+ end
41
90
  end
42
91
  end
43
92
  end
@@ -18,7 +18,7 @@ module RubyLsp
18
18
  end
19
19
 
20
20
  def path_from_package_folder(uri)
21
- uri_parts = uri.chomp(File.extname(uri)).split('/')
21
+ uri_parts = uri.to_s.chomp(File.extname(uri)).split('/')
22
22
 
23
23
  package_folder_index = uri_parts.index('package')
24
24
  return unless package_folder_index
@@ -49,6 +49,13 @@ module RubyLsp
49
49
  elsif parsed_doc.includes_link_dsl?
50
50
  fn_line = parsed_doc.link_nodes.first.location.start_line - 1
51
51
  position = parsed_doc.link_nodes.first.location.start_column
52
+ elsif parsed_doc.allows_root_links?
53
+ root_node_location = parsed_doc.root_node_line_location
54
+
55
+ fn_line = root_node_location.start_line
56
+ position = root_node_location.end_column + 1
57
+ else
58
+ return nil
52
59
  end
53
60
 
54
61
  Interface::Range.new(
@@ -1,9 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Ruby
4
- module Lsp
5
- module Ree
6
- VERSION = "0.1.2"
7
- end
3
+ module RubyLsp
4
+ module Ree
5
+ VERSION = "0.1.4"
8
6
  end
9
7
  end
data/ruby-lsp-ree.gemspec CHANGED
@@ -4,7 +4,7 @@ require_relative "lib/ruby_lsp_ree/version"
4
4
 
5
5
  Gem::Specification.new do |spec|
6
6
  spec.name = "ruby-lsp-ree"
7
- spec.version = Ruby::Lsp::Ree::VERSION
7
+ spec.version = RubyLsp::Ree::VERSION
8
8
  spec.authors = ["Ruslan Gatiyatov"]
9
9
  spec.email = ["ruslan.gatiyatov@gmail.com"]
10
10
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-lsp-ree
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ruslan Gatiyatov
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-02-14 00:00:00.000000000 Z
11
+ date: 2025-02-25 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: A Ruby LSP addon that adds extra editor functionality for Ree applications
14
14
  email:
@@ -17,6 +17,7 @@ executables: []
17
17
  extensions: []
18
18
  extra_rdoc_files: []
19
19
  files:
20
+ - ".ruby-version"
20
21
  - CHANGELOG.md
21
22
  - CODE_OF_CONDUCT.md
22
23
  - Gemfile
@@ -25,16 +26,20 @@ files:
25
26
  - README.md
26
27
  - Rakefile
27
28
  - lib/ruby_lsp/ruby_lsp_ree/addon.rb
28
- - lib/ruby_lsp/ruby_lsp_ree/completion.rb
29
- - lib/ruby_lsp/ruby_lsp_ree/completion_utils.rb
30
- - lib/ruby_lsp/ruby_lsp_ree/definition.rb
29
+ - lib/ruby_lsp/ruby_lsp_ree/handlers/completion_handler.rb
30
+ - lib/ruby_lsp/ruby_lsp_ree/handlers/definition_handler.rb
31
+ - lib/ruby_lsp/ruby_lsp_ree/handlers/hover_handler.rb
32
+ - lib/ruby_lsp/ruby_lsp_ree/listeners/completion_listener.rb
33
+ - lib/ruby_lsp/ruby_lsp_ree/listeners/definition_listener.rb
34
+ - lib/ruby_lsp/ruby_lsp_ree/listeners/hover_listener.rb
31
35
  - lib/ruby_lsp/ruby_lsp_ree/parsing/parsed_document.rb
32
36
  - lib/ruby_lsp/ruby_lsp_ree/parsing/parsed_document_builder.rb
33
37
  - lib/ruby_lsp/ruby_lsp_ree/parsing/parsed_link_node.rb
38
+ - lib/ruby_lsp/ruby_lsp_ree/parsing/parsed_rspec_document.rb
34
39
  - lib/ruby_lsp/ruby_lsp_ree/ree_formatter.rb
35
40
  - lib/ruby_lsp/ruby_lsp_ree/ree_indexing_enhancement.rb
36
- - lib/ruby_lsp/ruby_lsp_ree/ree_lsp_utils.rb
37
41
  - lib/ruby_lsp/ruby_lsp_ree/ree_object_finder.rb
42
+ - lib/ruby_lsp/ruby_lsp_ree/utils/ree_lsp_utils.rb
38
43
  - lib/ruby_lsp_ree.rb
39
44
  - lib/ruby_lsp_ree/version.rb
40
45
  - ruby-lsp-ree.gemspec
@@ -61,7 +66,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
61
66
  - !ruby/object:Gem::Version
62
67
  version: '0'
63
68
  requirements: []
64
- rubygems_version: 3.4.1
69
+ rubygems_version: 3.5.3
65
70
  signing_key:
66
71
  specification_version: 4
67
72
  summary: Ruby LSP addon for Ree framework.