rtext 0.9.0 → 0.9.3

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 (36) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +113 -98
  3. data/Project.yaml +14 -0
  4. data/RText_Protocol +1 -1
  5. data/lib/rtext/default_completer.rb +212 -208
  6. data/lib/rtext/frontend/connector.rb +122 -55
  7. data/lib/rtext/instantiator.rb +11 -3
  8. data/lib/rtext/service.rb +264 -260
  9. metadata +18 -44
  10. data/Rakefile +0 -46
  11. data/test/completer_test.rb +0 -607
  12. data/test/context_builder_test.rb +0 -949
  13. data/test/frontend/context_test.rb +0 -302
  14. data/test/instantiator_test.rb +0 -1733
  15. data/test/integration/backend.out +0 -16
  16. data/test/integration/crash_on_request_editor.rb +0 -12
  17. data/test/integration/ecore_editor.rb +0 -50
  18. data/test/integration/frontend.log +0 -40058
  19. data/test/integration/model/invalid_encoding.invenc +0 -2
  20. data/test/integration/model/test.crash_on_request +0 -18
  21. data/test/integration/model/test.crashing_backend +0 -18
  22. data/test/integration/model/test.dont_open_socket +0 -0
  23. data/test/integration/model/test.invalid_cmd_line +0 -0
  24. data/test/integration/model/test.not_in_rtext +0 -0
  25. data/test/integration/model/test_large_with_errors.ect3 +0 -43523
  26. data/test/integration/model/test_metamodel.ect +0 -24
  27. data/test/integration/model/test_metamodel2.ect +0 -5
  28. data/test/integration/model/test_metamodel3.ect4 +0 -7
  29. data/test/integration/model/test_metamodel_error.ect2 +0 -3
  30. data/test/integration/model/test_metamodel_ok.ect2 +0 -18
  31. data/test/integration/test.rb +0 -968
  32. data/test/link_detector_test.rb +0 -288
  33. data/test/message_helper_test.rb +0 -117
  34. data/test/rtext_test.rb +0 -11
  35. data/test/serializer_test.rb +0 -1024
  36. data/test/tokenizer_test.rb +0 -174
data/lib/rtext/service.rb CHANGED
@@ -1,260 +1,264 @@
1
- require 'socket'
2
- require 'rtext/context_builder'
3
- require 'rtext/message_helper'
4
- require 'rtext/link_detector'
5
-
6
- # optimization: garbage collect while service is idle
7
-
8
- module RText
9
-
10
- class Service
11
- include RText::MessageHelper
12
-
13
- PortRangeStart = 9001
14
- PortRangeEnd = 9100
15
-
16
- FlushInterval = 1
17
- ProtocolVersion = 1
18
-
19
- # Creates an RText backend service. Options:
20
- #
21
- # :timeout
22
- # idle time in seconds after which the service will terminate itself
23
- #
24
- # :logger
25
- # a logger object on which the service will write its logging output
26
- #
27
- # :on_startup:
28
- # a Proc which is called right after the service has started up
29
- # can be used to output version information
30
- #
31
- def initialize(service_provider, options={})
32
- @service_provider = service_provider
33
- @timeout = options[:timeout] || 60
34
- @logger = options[:logger]
35
- @on_startup = options[:on_startup]
36
- end
37
-
38
- def run
39
- server = create_server
40
- puts "RText service, listening on port #{server.addr[1]}"
41
- @on_startup.call if @on_startup
42
- $stdout.flush
43
-
44
- last_access_time = Time.now
45
- last_flush_time = Time.now
46
- @stop_requested = false
47
- sockets = []
48
- request_data = {}
49
- while !@stop_requested
50
- begin
51
- sock = server.accept_nonblock
52
- sock.sync = true
53
- sockets << sock
54
- @logger.info "accepted connection" if @logger
55
- rescue Errno::EAGAIN, Errno::ECONNABORTED, Errno::EPROTO, Errno::EINTR, Errno::EWOULDBLOCK
56
- rescue Exception => e
57
- @logger.warn "unexpected exception during socket accept: #{e.class}"
58
- end
59
- sockets.dup.each do |sock|
60
- data = nil
61
- begin
62
- data = sock.read_nonblock(100000)
63
- rescue Errno::EWOULDBLOCK
64
- rescue IOError, EOFError, Errno::ECONNRESET, Errno::ECONNABORTED
65
- sock.close
66
- request_data[sock] = nil
67
- sockets.delete(sock)
68
- rescue Exception => e
69
- # catch Exception to make sure we don't crash due to unexpected exceptions
70
- @logger.warn "unexpected exception during socket read: #{e.class}"
71
- sock.close
72
- request_data[sock] = nil
73
- sockets.delete(sock)
74
- end
75
- if data
76
- last_access_time = Time.now
77
- request_data[sock] ||= ""
78
- request_data[sock].concat(data)
79
- while obj = extract_message(request_data[sock])
80
- message_received(sock, obj)
81
- end
82
- end
83
- end
84
- IO.select([server] + sockets, [], [], 1)
85
- if Time.now > last_access_time + @timeout
86
- @logger.info("RText service, stopping now (timeout)") if @logger
87
- break
88
- end
89
- if Time.now > last_flush_time + FlushInterval
90
- $stdout.flush
91
- last_flush_time = Time.now
92
- end
93
- end
94
- end
95
-
96
- def message_received(sock, obj)
97
- if check_request(obj)
98
- request_start = Time.now
99
- @logger.debug("request: "+obj.inspect) if @logger
100
- response = { "type" => "response", "invocation_id" => obj["invocation_id"] }
101
- case obj["command"]
102
- when "version"
103
- version(sock, obj, response)
104
- when "load_model"
105
- load_model(sock, obj, response)
106
- when "content_complete"
107
- content_complete(sock, obj, response)
108
- when "link_targets"
109
- link_targets(sock, obj, response)
110
- when "find_elements"
111
- find_elements(sock, obj, response)
112
- when "stop"
113
- @logger.info("RText service, stopping now (stop requested)") if @logger
114
- @stop_requested = true
115
- else
116
- @logger.warn("unknown command #{obj["command"]}") if @logger
117
- response["type"] = "unknown_command_error"
118
- response["command"] = obj["command"]
119
- end
120
- @logger.debug("response: "+truncate_response_for_debug_output(response).inspect) \
121
- if response && @logger
122
- send_response(sock, response)
123
- @logger.info("request complete (#{Time.now-request_start}s)")
124
- end
125
- end
126
-
127
- private
128
-
129
- def check_request(obj)
130
- if obj["type"] != "request"
131
- @logger.warn("received message is not a request") if @logger
132
- false
133
- elsif !obj["invocation_id"].is_a?(Integer)
134
- @logger.warn("invalid invocation id #{obj["invocation_id"]}") if @logger
135
- false
136
- else
137
- true
138
- end
139
- end
140
-
141
- def version(sock, request, response)
142
- response["version"] = ProtocolVersion
143
- end
144
-
145
- def load_model(sock, request, response)
146
- problems = @service_provider.get_problems(
147
- :on_progress => lambda do |frag, work_done, work_overall|
148
- work_overall = 1 if work_overall < 1
149
- work_done = work_overall if work_done > work_overall
150
- work_done = 0 if work_done < 0
151
- send_response(sock, {
152
- "type" => "progress",
153
- "invocation_id" => request["invocation_id"],
154
- "percentage" => work_done*100/work_overall
155
- })
156
- end)
157
- total = 0
158
- response["problems"] = problems.collect do |fp|
159
- { "file" => fp.file,
160
- "problems" => fp.problems.collect do |p|
161
- total += 1
162
- { "severity" => "error", "line" => p.line, "message" => p.message }
163
- end }
164
- end
165
- response["total_problems"] = total
166
- end
167
-
168
- InsertString = "insert"
169
- DisplayString = "display"
170
- DescriptionString = "desc"
171
-
172
- def content_complete(sock, request, response)
173
- # column numbers start at 1
174
- linepos = request["column"]-1
175
- lines = request["context"]
176
- version = request["version"].to_i
177
- lang = @service_provider.language
178
- response["options"] = []
179
- return unless lang
180
- context = ContextBuilder.build_context(lang, lines, linepos)
181
- @logger.debug("context element: #{lang.identifier_provider.call(context.element, nil, nil, nil)}") \
182
- if context && context.element && @logger
183
- options = @service_provider.get_completion_options(context, version)
184
- response["options"] = options.collect do |o|
185
- { InsertString => o.insert, DisplayString => o.display, DescriptionString => o.description }
186
- end
187
- end
188
-
189
- def link_targets(sock, request, response)
190
- # column numbers start at 1
191
- linepos = request["column"]
192
- lines = request["context"]
193
- lang = @service_provider.language
194
- response["targets"] = []
195
- return unless lang
196
- link_descriptor = RText::LinkDetector.new(lang).detect(lines, linepos)
197
- if link_descriptor
198
- response["begin_column"] = link_descriptor.scol
199
- response["end_column"] = link_descriptor.ecol
200
- targets = []
201
- @service_provider.get_link_targets(link_descriptor).each do |t|
202
- targets << { "file" => t.file, "line" => t.line, "display" => t.display_name }
203
- end
204
- response["targets"] = targets
205
- end
206
- end
207
-
208
- def find_elements(sock, request, response)
209
- pattern = request["search_pattern"]
210
- total = 0
211
- response["elements"] = @service_provider.get_open_element_choices(pattern).collect do |c|
212
- total += 1
213
- { "display" => c.display_name, "file" => c.file, "line" => c.line }
214
- end
215
- response["total_elements"] = total
216
- end
217
-
218
- def send_response(sock, response)
219
- if response
220
- begin
221
- sock.write(serialize_message(response))
222
- sock.flush
223
- # if there is an exception, the next read should shutdown the connection properly
224
- rescue IOError, EOFError, Errno::ECONNRESET, Errno::ECONNABORTED
225
- rescue Exception => e
226
- # catch Exception to make sure we don't crash due to unexpected exceptions
227
- @logger.warn "unexpected exception during socket write: #{e.class}"
228
- end
229
- end
230
- end
231
-
232
- def truncate_response_for_debug_output(response_hash)
233
- result = {}
234
- response_hash.each_pair do |k,v|
235
- if v.is_a?(Array) && v.size > 100
236
- result[k] = v[0..99] + ["<truncated>"]
237
- else
238
- result[k] = v
239
- end
240
- end
241
- result
242
- end
243
-
244
- def create_server
245
- port = PortRangeStart
246
- serv = nil
247
- begin
248
- serv = TCPServer.new("127.0.0.1", port)
249
- rescue Errno::EADDRINUSE, Errno::EAFNOSUPPORT, Errno::EACCES
250
- port += 1
251
- retry if port <= PortRangeEnd
252
- raise
253
- end
254
- serv
255
- end
256
-
257
- end
258
-
259
- end
260
-
1
+ require 'socket'
2
+ require 'rtext/context_builder'
3
+ require 'rtext/message_helper'
4
+ require 'rtext/link_detector'
5
+
6
+ # optimization: garbage collect while service is idle
7
+
8
+ module RText
9
+
10
+ class Service
11
+ include RText::MessageHelper
12
+
13
+ PortRangeStart = 9001
14
+ PortRangeEnd = 9100
15
+
16
+ FlushInterval = 1
17
+ ProtocolVersion = 1
18
+
19
+ # Creates an RText backend service. Options:
20
+ #
21
+ # :timeout
22
+ # idle time in seconds after which the service will terminate itself
23
+ #
24
+ # :logger
25
+ # a logger object on which the service will write its logging output
26
+ #
27
+ # :on_startup:
28
+ # a Proc which is called right after the service has started up
29
+ # can be used to output version information
30
+ #
31
+ def initialize(service_provider, options={})
32
+ @service_provider = service_provider
33
+ @timeout = options[:timeout] || 60
34
+ @logger = options[:logger]
35
+ @on_startup = options[:on_startup]
36
+ end
37
+
38
+ def run
39
+ server = create_server
40
+ puts "RText service, listening on port #{server.addr[1]}"
41
+ @on_startup.call if @on_startup
42
+ $stdout.flush
43
+
44
+ last_access_time = Time.now
45
+ last_flush_time = Time.now
46
+ @stop_requested = false
47
+ sockets = []
48
+ request_data = {}
49
+ while !@stop_requested
50
+ begin
51
+ sock = server.accept_nonblock
52
+ sock.sync = true
53
+ sockets << sock
54
+ @logger.info "accepted connection" if @logger
55
+ rescue Errno::EAGAIN, Errno::ECONNABORTED, Errno::EPROTO, Errno::EINTR, Errno::EWOULDBLOCK
56
+ rescue Exception => e
57
+ @logger.warn "unexpected exception during socket accept: #{e.class}"
58
+ end
59
+ sockets.dup.each do |sock|
60
+ data = nil
61
+ begin
62
+ data = sock.read_nonblock(100000)
63
+ rescue Errno::EWOULDBLOCK
64
+ rescue IOError, EOFError, Errno::ECONNRESET, Errno::ECONNABORTED
65
+ sock.close
66
+ request_data[sock] = nil
67
+ sockets.delete(sock)
68
+ rescue Exception => e
69
+ # catch Exception to make sure we don't crash due to unexpected exceptions
70
+ @logger.warn "unexpected exception during socket read: #{e.class}"
71
+ sock.close
72
+ request_data[sock] = nil
73
+ sockets.delete(sock)
74
+ end
75
+ if data
76
+ last_access_time = Time.now
77
+ request_data[sock] ||= ""
78
+ request_data[sock].concat(data)
79
+ while obj = extract_message(request_data[sock])
80
+ message_received(sock, obj)
81
+ end
82
+ end
83
+ end
84
+ IO.select([server] + sockets, [], [], 1)
85
+ if Time.now > last_access_time + @timeout
86
+ @logger.info("RText service, stopping now (timeout)") if @logger
87
+ break
88
+ end
89
+ if Time.now > last_flush_time + FlushInterval
90
+ $stdout.flush
91
+ last_flush_time = Time.now
92
+ end
93
+ end
94
+ end
95
+
96
+ def message_received(sock, obj)
97
+ if check_request(obj)
98
+ request_start = Time.now
99
+ @logger.debug("request: "+obj.inspect) if @logger
100
+ response = { "type" => "response", "invocation_id" => obj["invocation_id"] }
101
+ case obj["command"]
102
+ when "version"
103
+ version(sock, obj, response)
104
+ when "load_model"
105
+ load_model(sock, obj, response)
106
+ when "content_complete"
107
+ content_complete(sock, obj, response)
108
+ when "link_targets"
109
+ link_targets(sock, obj, response)
110
+ when "find_elements"
111
+ find_elements(sock, obj, response)
112
+ when "stop"
113
+ @logger.info("RText service, stopping now (stop requested)") if @logger
114
+ @stop_requested = true
115
+ else
116
+ @logger.warn("unknown command #{obj["command"]}") if @logger
117
+ response["type"] = "unknown_command_error"
118
+ response["command"] = obj["command"]
119
+ end
120
+ @logger.debug("response: "+truncate_response_for_debug_output(response).inspect) \
121
+ if response && @logger
122
+ send_response(sock, response)
123
+ @logger.info("request complete (#{Time.now-request_start}s)")
124
+ end
125
+ end
126
+
127
+ private
128
+
129
+ def check_request(obj)
130
+ if obj["type"] != "request"
131
+ @logger.warn("received message is not a request") if @logger
132
+ false
133
+ elsif !obj["invocation_id"].is_a?(Integer)
134
+ @logger.warn("invalid invocation id #{obj["invocation_id"]}") if @logger
135
+ false
136
+ else
137
+ true
138
+ end
139
+ end
140
+
141
+ def version(sock, request, response)
142
+ response["version"] = ProtocolVersion
143
+ end
144
+
145
+ def load_model(sock, request, response)
146
+ problems = @service_provider.get_problems(
147
+ :on_progress => lambda do |frag, work_done, work_overall|
148
+ work_overall = 1 if work_overall < 1
149
+ work_done = work_overall if work_done > work_overall
150
+ work_done = 0 if work_done < 0
151
+ send_response(sock, {
152
+ "type" => "progress",
153
+ "invocation_id" => request["invocation_id"],
154
+ "percentage" => work_done*100/work_overall
155
+ })
156
+ end)
157
+ total = 0
158
+ response["problems"] = problems.collect do |fp|
159
+ { "file" => fp.file,
160
+ "problems" => fp.problems.collect do |p|
161
+ total += 1
162
+ { "severity" => "error", "line" => p.line, "message" => p.message }
163
+ end }
164
+ end
165
+ response["total_problems"] = total
166
+ end
167
+
168
+ InsertString = "insert"
169
+ DisplayString = "display"
170
+ DescriptionString = "desc"
171
+
172
+ def content_complete(sock, request, response)
173
+ # column numbers start at 1
174
+ linepos = request["column"]-1
175
+ lines = request["context"]
176
+ version = request["version"].to_i
177
+ lang = @service_provider.language
178
+ response["options"] = []
179
+ return unless lang
180
+ context = ContextBuilder.build_context(lang, lines, linepos)
181
+ @logger.debug("context element: #{lang.identifier_provider.call(context.element, nil, nil, nil)}") \
182
+ if context && context.element && @logger
183
+ if @service_provider.method(:get_completion_options).arity == 1
184
+ options = @service_provider.get_completion_options(context)
185
+ else
186
+ options = @service_provider.get_completion_options(context, version)
187
+ end
188
+ response["options"] = options.collect do |o|
189
+ { InsertString => o.insert, DisplayString => o.display, DescriptionString => o.description }
190
+ end
191
+ end
192
+
193
+ def link_targets(sock, request, response)
194
+ # column numbers start at 1
195
+ linepos = request["column"]
196
+ lines = request["context"]
197
+ lang = @service_provider.language
198
+ response["targets"] = []
199
+ return unless lang
200
+ link_descriptor = RText::LinkDetector.new(lang).detect(lines, linepos)
201
+ if link_descriptor
202
+ response["begin_column"] = link_descriptor.scol
203
+ response["end_column"] = link_descriptor.ecol
204
+ targets = []
205
+ @service_provider.get_link_targets(link_descriptor).each do |t|
206
+ targets << { "file" => t.file, "line" => t.line, "display" => t.display_name }
207
+ end
208
+ response["targets"] = targets
209
+ end
210
+ end
211
+
212
+ def find_elements(sock, request, response)
213
+ pattern = request["search_pattern"]
214
+ total = 0
215
+ response["elements"] = @service_provider.get_open_element_choices(pattern).collect do |c|
216
+ total += 1
217
+ { "display" => c.display_name, "file" => c.file, "line" => c.line }
218
+ end
219
+ response["total_elements"] = total
220
+ end
221
+
222
+ def send_response(sock, response)
223
+ if response
224
+ begin
225
+ sock.write(serialize_message(response))
226
+ sock.flush
227
+ # if there is an exception, the next read should shutdown the connection properly
228
+ rescue IOError, EOFError, Errno::ECONNRESET, Errno::ECONNABORTED
229
+ rescue Exception => e
230
+ # catch Exception to make sure we don't crash due to unexpected exceptions
231
+ @logger.warn "unexpected exception during socket write: #{e.class}"
232
+ end
233
+ end
234
+ end
235
+
236
+ def truncate_response_for_debug_output(response_hash)
237
+ result = {}
238
+ response_hash.each_pair do |k,v|
239
+ if v.is_a?(Array) && v.size > 100
240
+ result[k] = v[0..99] + ["<truncated>"]
241
+ else
242
+ result[k] = v
243
+ end
244
+ end
245
+ result
246
+ end
247
+
248
+ def create_server
249
+ port = PortRangeStart
250
+ serv = nil
251
+ begin
252
+ serv = TCPServer.new("127.0.0.1", port)
253
+ rescue Errno::EADDRINUSE, Errno::EAFNOSUPPORT, Errno::EACCES
254
+ port += 1
255
+ retry if port <= PortRangeEnd
256
+ raise
257
+ end
258
+ serv
259
+ end
260
+
261
+ end
262
+
263
+ end
264
+
metadata CHANGED
@@ -1,32 +1,32 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rtext
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.0
4
+ version: 0.9.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Martin Thiede
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-09-26 00:00:00.000000000 Z
11
+ date: 2017-11-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rgen
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - '>='
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 0.8.2
19
+ version: 0.8.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - '>='
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 0.8.2
26
+ version: 0.8.0
27
27
  description: RText can be used to derive textual languages from an RGen metamodel
28
28
  with very little effort.
29
- email: martin dot thiede at gmx de
29
+ email:
30
30
  executables: []
31
31
  extensions: []
32
32
  extra_rdoc_files:
@@ -36,6 +36,12 @@ extra_rdoc_files:
36
36
  - RText_Users_Guide
37
37
  - RText_Protocol
38
38
  files:
39
+ - CHANGELOG
40
+ - MIT-LICENSE
41
+ - Project.yaml
42
+ - README.rdoc
43
+ - RText_Protocol
44
+ - RText_Users_Guide
39
45
  - lib/rtext/context_builder.rb
40
46
  - lib/rtext/default_completer.rb
41
47
  - lib/rtext/default_loader.rb
@@ -55,62 +61,30 @@ files:
55
61
  - lib/rtext/serializer.rb
56
62
  - lib/rtext/service.rb
57
63
  - lib/rtext/tokenizer.rb
58
- - test/completer_test.rb
59
- - test/context_builder_test.rb
60
- - test/frontend/context_test.rb
61
- - test/instantiator_test.rb
62
- - test/integration/backend.out
63
- - test/integration/crash_on_request_editor.rb
64
- - test/integration/ecore_editor.rb
65
- - test/integration/frontend.log
66
- - test/integration/model/invalid_encoding.invenc
67
- - test/integration/model/test.crash_on_request
68
- - test/integration/model/test.crashing_backend
69
- - test/integration/model/test.dont_open_socket
70
- - test/integration/model/test.invalid_cmd_line
71
- - test/integration/model/test.not_in_rtext
72
- - test/integration/model/test_large_with_errors.ect3
73
- - test/integration/model/test_metamodel.ect
74
- - test/integration/model/test_metamodel2.ect
75
- - test/integration/model/test_metamodel3.ect4
76
- - test/integration/model/test_metamodel_error.ect2
77
- - test/integration/model/test_metamodel_ok.ect2
78
- - test/integration/test.rb
79
- - test/link_detector_test.rb
80
- - test/message_helper_test.rb
81
- - test/rtext_test.rb
82
- - test/serializer_test.rb
83
- - test/tokenizer_test.rb
84
- - README.rdoc
85
- - CHANGELOG
86
- - MIT-LICENSE
87
- - RText_Users_Guide
88
- - RText_Protocol
89
- - Rakefile
90
64
  homepage: http://ruby-gen.org
91
65
  licenses: []
92
66
  metadata: {}
93
67
  post_install_message:
94
68
  rdoc_options:
95
- - --main
69
+ - "--main"
96
70
  - README.rdoc
97
- - -x
71
+ - "-x"
98
72
  - test
99
73
  require_paths:
100
74
  - lib
101
75
  required_ruby_version: !ruby/object:Gem::Requirement
102
76
  requirements:
103
- - - '>='
77
+ - - ">="
104
78
  - !ruby/object:Gem::Version
105
79
  version: '0'
106
80
  required_rubygems_version: !ruby/object:Gem::Requirement
107
81
  requirements:
108
- - - '>='
82
+ - - ">="
109
83
  - !ruby/object:Gem::Version
110
84
  version: '0'
111
85
  requirements: []
112
86
  rubyforge_project:
113
- rubygems_version: 2.0.3
87
+ rubygems_version: 2.5.2
114
88
  signing_key:
115
89
  specification_version: 4
116
90
  summary: Ruby Textual Modelling