rtext 0.2.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.
@@ -0,0 +1,4 @@
1
+ =0.2.0
2
+
3
+ * First public release
4
+
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Martin Thiede
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,29 @@
1
+ == RText - Ruby Textual Modelling
2
+
3
+ RText can be used to derive textual languages from an RGen metamodel with very little effort.
4
+
5
+ RText features include:
6
+ * Generic textual syntax
7
+ * Generic instantiator (parser) and serializer
8
+ * Consistency checks derived from the metamodel
9
+ * Designed to work with lightweight editor plugins providing
10
+ - Syntax Highlighting
11
+ - Auto Completion on language commands and model references
12
+ - Error Annotations
13
+ - Model Navigation
14
+
15
+
16
+ = Installation
17
+
18
+ > gem install rtext
19
+
20
+
21
+ = Documentation
22
+
23
+
24
+ = Examples
25
+
26
+
27
+ = License
28
+
29
+ RGen is released under the MIT license.
@@ -0,0 +1,247 @@
1
+ =RText Plugin Implementation Guide
2
+
3
+ RText editors consist of a frontend and a backend. Most of the logic is implemented
4
+ within the backend while the frontend is kept as thin as possible. This should simplify
5
+ the task of writing a frontend and promote development of RText frontends for different
6
+ editing environments.
7
+
8
+ This guide explains how to build a RText frontend which will typically be a plugin for an
9
+ existing editor.
10
+
11
+
12
+ ==Plugin Responsibilies
13
+
14
+ Here is a rough overview of what a RText plugin has to do:
15
+
16
+ * find the .rtext config file for a given RText model file
17
+ * start the corresponding backend process if not started already
18
+ * provide a way to reload the model and issue the refresh command to the backend
19
+ * implement syntax highlighting for the generic RText syntax
20
+ * forward auto completion requests to the backend, let the user choose from the returned options
21
+ and insert the option chosen
22
+ * forward element search requests to the backend, let the user choose from the matched elements
23
+ and jump to the element chosen
24
+ * forward reference target requests to the backend, let the user choose from the targets found
25
+ and jump to the target chosen
26
+ * query the backend for model problems and display them to the user
27
+ * stop the backend process when it's no longer needed
28
+
29
+
30
+ ==Plugin Implementation Hints
31
+
32
+ The plugin implementation could include the following entities.
33
+
34
+ ===Command
35
+
36
+ A command issued by the plugin and sent to the backend service. See the section about the
37
+ Frontend/Backend Communication Protocol below for details about available commands.
38
+
39
+
40
+ ===Connector
41
+
42
+ A connector represents the connection to a running backend process.
43
+ Each connector/process is associated with a specific .rtext file and a specific service specification
44
+ within this file. See the RText Users Guide for details about the .rtext config file.
45
+
46
+ When the backend service is started, it outputs the UDP port it is listening on. The connector
47
+ should use this port to send commands to the backend.
48
+
49
+ The connector should send commands to the backend. It should assign invocation ids and keep the
50
+ invocation until a response is received or a timeout has occured.
51
+
52
+ When the backend responds, the connector should associate the response packages with the previous
53
+ request using the invocation id. It should also join fragmented response packages and send an
54
+ internal notification when a response has been fully received.
55
+
56
+
57
+ ===Connector Manager
58
+
59
+ The connector manager should be able to return a connector for a given RText model file.
60
+
61
+ It needs to identify the backend process to which to connect by means of the .rtext config file and
62
+ a contained service specification (one .rtext files may contain service specifications for starting
63
+ different backend services depending on the model file name).
64
+
65
+ For this, it needs to find the first .rtext config file containing a service specification matching
66
+ the given RText model file name. See the RText Users guide for details about the .rtext config file
67
+ and the way how they are found in the filesystem.
68
+
69
+ Once the .rtext file/service specification is found it can be used as a key for a lookup for
70
+ already established connections. The plugin should avoid starting the same process twice. All
71
+ RText model files with the same file extension and belonging to the same .rtext file should
72
+ use the same process.
73
+
74
+ The process should be kept running as long as editing is in progress. This will greatly
75
+ reduce response times as the model can be kept in memory and only the changed parts need
76
+ to be reloaded.
77
+
78
+ When the plugin lifecycle ends, the connector manager should send the "stop" command to the
79
+ backend processes.
80
+
81
+
82
+ ==Frontend/Backend Communication Protocol
83
+
84
+ Communication takes place via plain text transmitted via UDP. There are requests by
85
+ the frontend and responses by the backend. In both cases the text is interpreted as
86
+ a set of lines (separated by \n or \r\n).
87
+
88
+
89
+ ===Request
90
+
91
+ The request consists of a command id, an invocation id and the command parameters.
92
+ The invocation id is repeated within the response to allow proper association with the
93
+ request. Command parameters are command specific (see below).
94
+
95
+ Requests can not span more than one UDP package and thus are limited to the UDP payload size.
96
+
97
+ line 1: <command id>
98
+ line 2: <invocation id>
99
+ line 3..n: <command parameters>
100
+
101
+
102
+ ===Response
103
+
104
+ Each response contains the invocation id of the request in the first line.
105
+ Since it could be splitted over several UDP packages it contains an indication
106
+ in the second line if more packages are following ("more") or this is the last package
107
+ ("last"). The rest of the response is a fragment of the command result data. All fragments
108
+ of a set of response packages with the same invocation id need to be joined.
109
+
110
+ Note that there are no mechanisms to protect agains package loss or reordering since
111
+ this protocol is intended to be used on local socket connections only.
112
+
113
+ line 1: <invocation id>
114
+ line 2: "more" | "last"
115
+ line 3..n: <command result data>
116
+
117
+
118
+ ===Command: Refresh
119
+
120
+ The refresh command tells the backend to reload the model from the file system. Frontends
121
+ could issue this command after a model file has been changed in the file system or on
122
+ an explicit user request.
123
+
124
+ Request Format:
125
+ command id: "refresh"
126
+ no parameters
127
+
128
+ Response Format:
129
+ no result data
130
+
131
+
132
+ ===Command: Complete
133
+
134
+ This command is a request by the frontend to show auto completion options at a given
135
+ location within a file. The location is expressed using a set of context lines and the cursor
136
+ column position in the current line.
137
+
138
+ Context lines are lines from an RText file which contain a (context) command and all
139
+ the parent commands wrapped around it. Any sibling commands can be omitted as well as
140
+ any lines containing closing braces and brackets. The order of lines is the same as in the RText file.
141
+
142
+ Here is an example. Consider the following RText file with the cursor in the line of "Command3" at
143
+ the time when the auto completion command is issued.
144
+
145
+ Command1 {
146
+ Command2 {
147
+ role1: [
148
+ Command3 <== cursor in this line
149
+ Command4
150
+ ]
151
+ }
152
+ Command5
153
+ }
154
+
155
+ The context lines in this case would be the following.
156
+
157
+ Command1 {
158
+ Command2 {
159
+ role1: [
160
+ Command3
161
+
162
+ The current line is always the last of the context lines.
163
+
164
+ Note that all siblings of the command and parent commands have been stripped off, as well as
165
+ any closing braces or brackets.
166
+
167
+ The purpose of this special context line format is to keep the task of extracting the
168
+ context in the frontend simple and the amount of data transmitted to the backend low.
169
+ It's also a way to keep the parsing time of the context low in the backend and thus to minimize
170
+ the user noticable delay.
171
+
172
+ Request Format:
173
+ command id: "complete"
174
+ param line 1: cursor column position in the current line
175
+ param line 2..n: context lines
176
+
177
+ Response Format:
178
+ result line 1..n: <completion string>;<extra info string>
179
+
180
+
181
+ ===Command: Show Problems
182
+
183
+ This command is a request by the frontend to determine and return all problems present in the current model.
184
+ The command implicitly reloads the model from the filesystem before checking for problems.
185
+
186
+ Request Format:
187
+ command id: "show_problems"
188
+ no parameters
189
+
190
+ Response Format:
191
+ result line n: <file name>
192
+ result line n+1..n+m <line number>;<message>
193
+
194
+ Note that the file name is repeated only once for all problems within a file to reduce the amout of
195
+ result data which needs to be transmitted.
196
+
197
+
198
+ ===Command: Reference Targets
199
+
200
+ This command is used to retrieve the targets of a given reference. The reference location is expressed
201
+ by means of a set of context lines and the cursor column position in the current line. The cursor
202
+ column position must be within the string representing the reference. The format of the context lines
203
+ is the same as the one described with the "Complete" command (see above).
204
+
205
+ Note that the service provider is free to define how a reference is represented. In particular it
206
+ can interpret the command identifier as a reference and use this mechanism to show incoming references
207
+ (reverse references).
208
+
209
+ As references can be ambiguous, the result is a list of possible targets. Each target consists of the
210
+ filename and line number of the target element as well as the text to be displayed to the user in case
211
+ there are more than one targets to choose from.
212
+
213
+ Request Format:
214
+ command id: "get_reference_targets"
215
+ param line 1: cursor column position in the current line
216
+ param line 2..n: context lines
217
+
218
+ Response Format:
219
+ result line 1..n: <file name>;<line number>;<display name>
220
+
221
+
222
+ ===Command: Find Elements
223
+
224
+ This command returns a set of model elements matching a search pattern. The format of the
225
+ pattern depends on the service provider. In a simple case, for example, the pattern could be the
226
+ beginning of the element names.
227
+
228
+ Request Format:
229
+ command id: "get_elements"
230
+ param line 1: <seach pattern>
231
+
232
+ Response Format:
233
+ result line 1..n: <display name>;<file name>;<line number>
234
+
235
+
236
+ ===Command: Stop
237
+
238
+ This command is normally invoked when the frontend terminates or otherwise needs to terminate
239
+ the backend service. When receiving this command, the backend will terminate.
240
+
241
+ Request Format:
242
+ command id: "stop"
243
+ no parameters
244
+
245
+ Response Format:
246
+ no result data
247
+
@@ -0,0 +1,31 @@
1
+ =RText Users Guide
2
+
3
+ == The .rtext config file
4
+
5
+ A .rtext config file is used to specify which backend service is to be started for a given
6
+ RText model file.
7
+
8
+ In order to be found by an RText plugin, the .rtext file must be located in the same directory
9
+ as the RText model file being edited or in any of its parent directories.
10
+
11
+ Here is the grammar for the config file syntax:
12
+
13
+ rtext_config ::= <service_spec>+
14
+ service_spec ::= <file_pattern_list>:\n<command line string>\n
15
+ file_pattern_list ::= <file_pattern> | <file_pattern>, <file_pattern_list>
16
+ file_pattern ::= <filename without path> | *.<extension>
17
+
18
+ Here is an example:
19
+
20
+ *.ext1:
21
+ ruby service1.rb
22
+ *.ext2, *.ext3:
23
+ ruby service2.rb *.ext2 *.ext3
24
+ noext:
25
+ service3.sh
26
+
27
+ This example contains specifications for three different backend services. service1.rb is started
28
+ when a model file with the extension "ext1" is being edited. service2.rb is started when a
29
+ model file with extension "ext2" or "ext3" is edited. service2.rb also receives additional command
30
+ line parameters. service3.sh is started whenever a file named "noext" is edited.
31
+
@@ -0,0 +1,46 @@
1
+ require 'rake/gempackagetask'
2
+ require 'rake/rdoctask'
3
+
4
+ DocFiles = [
5
+ "README", "CHANGELOG", "MIT-LICENSE",
6
+ "RText_Users_Guide",
7
+ "RText_Plugin_Implementation_Guide"]
8
+
9
+ RTextGemSpec = Gem::Specification.new do |s|
10
+ s.name = %q{rtext}
11
+ s.version = "0.2.0"
12
+ s.date = Time.now.strftime("%Y-%m-%d")
13
+ s.summary = %q{Ruby Textual Modelling}
14
+ s.email = %q{martin dot thiede at gmx de}
15
+ s.homepage = %q{http://ruby-gen.org}
16
+ s.description = %q{RText can be used to derive textual languages from an RGen metamodel with very little effort.}
17
+ s.authors = ["Martin Thiede"]
18
+ s.add_dependency('rgen', '>= 0.6.0')
19
+ gemfiles = Rake::FileList.new
20
+ gemfiles.include("{lib,test}/**/*")
21
+ gemfiles.include(DocFiles)
22
+ gemfiles.include("Rakefile")
23
+ gemfiles.exclude(/\b\.bak\b/)
24
+ s.files = gemfiles
25
+ s.rdoc_options = ["--main", "README", "-x", "test"]
26
+ s.extra_rdoc_files = DocFiles
27
+ end
28
+
29
+ Rake::RDocTask.new do |rd|
30
+ rd.main = "README"
31
+ rd.rdoc_files.include(DocFiles)
32
+ rd.rdoc_files.include("lib/**/*.rb")
33
+ rd.rdoc_dir = "doc"
34
+ end
35
+
36
+ RTextPackageTask = Rake::GemPackageTask.new(RTextGemSpec) do |p|
37
+ p.need_zip = false
38
+ end
39
+
40
+ task :prepare_package_rdoc => :rdoc do
41
+ RTextPackageTask.package_files.include("doc/**/*")
42
+ end
43
+
44
+ task :release => [:prepare_package_rdoc, :package]
45
+
46
+ task :clobber => [:clobber_rdoc, :clobber_package]
@@ -0,0 +1,152 @@
1
+ require 'rgen/ecore/ecore_ext'
2
+
3
+ module RText
4
+
5
+ class Completer
6
+
7
+ CompletionOption = Struct.new(:text, :extra)
8
+
9
+ # Creates a completer for RText::Language +language+.
10
+ #
11
+ def initialize(language)
12
+ @lang = language
13
+ end
14
+
15
+ # Provides completion options
16
+ #
17
+ # :linestart
18
+ # the content of the current line before the cursor
19
+ #
20
+ # :prev_line_provider
21
+ # is a proc which must return lines above the current line
22
+ # it receives an index parameter in the range 1..n
23
+ # 1 is the line just above the current one, 2 is the second line above, etc.
24
+ # the proc must return the line as a string or nil if there is no more line
25
+ #
26
+ # :ref_completion_option_provider
27
+ # a proc which receives a EReference and should return
28
+ # the possible completion options as CompletionOption objects
29
+ # note, that the context element may be nil if this information is unavailable
30
+ #
31
+ def complete(line, linepos, prev_line_provider, ref_completion_option_provider=nil)
32
+ linestart = line[0..linepos-1]
33
+ # command
34
+ if linestart =~ /^\s*(\w*)$/
35
+ prefix = $1
36
+ classes = completion_classes(prev_line_provider)
37
+ classes = classes.select{|c| c.name.index(prefix) == 0} if prefix
38
+ classes.sort{|a,b| a.name <=> b.name}.collect do |c|
39
+ uargs = @lang.unlabled_arguments(c).collect{|a| "<#{a.name}>"}.join(", ")
40
+ CompletionOption.new(c.name, uargs)
41
+ end
42
+ # attribute
43
+ elsif linestart =~ /^\s*(\w+)\s+(?:[^,]+,)*\s*(\w*)$/
44
+ command, prefix = $1, $2
45
+ clazz = @lang.class_by_command(command)
46
+ if clazz
47
+ features = @lang.labled_arguments(clazz.ecore)
48
+ features = features.select{|f| f.name.index(prefix) == 0} if prefix
49
+ features.sort{|a,b| a.name <=> b.name}.collect do |f|
50
+ CompletionOption.new("#{f.name}:", "<#{f.eType.name}>")
51
+ end
52
+ else
53
+ []
54
+ end
55
+ # value
56
+ elsif linestart =~ /\s*(\w+)\s+(?:[^,]+,)*\s*(\w+):\s*(\S*)$/
57
+ command, fn, prefix = $1, $2, $3
58
+ clazz = @lang.class_by_command(command)
59
+ feature = clazz && @lang.non_containments(clazz.ecore).find{|f| f.name == fn}
60
+ if feature
61
+ if feature.is_a?(RGen::ECore::EReference)
62
+ if ref_completion_option_provider
63
+ ref_completion_option_provider.call(feature)
64
+ else
65
+ []
66
+ end
67
+ elsif feature.eType.is_a?(RGen::ECore::EEnum)
68
+ feature.eType.eLiterals.collect do |l|
69
+ CompletionOption.new("#{l.name}")
70
+ end
71
+ elsif feature.eType.instanceClass == String
72
+ [ CompletionOption.new("\"\"") ]
73
+ elsif feature.eType.instanceClass == Integer
74
+ (0..4).collect{|i| CompletionOption.new("#{i}") }
75
+ elsif feature.eType.instanceClass == Float
76
+ (0..4).collect{|i| CompletionOption.new("#{i}.0") }
77
+ elsif feature.eType.instanceClass == RGen::MetamodelBuilder::DataTypes::Boolean
78
+ [true, false].collect{|b| CompletionOption.new("#{b}") }
79
+ else
80
+ []
81
+ end
82
+ else
83
+ []
84
+ end
85
+ else
86
+ []
87
+ end
88
+ end
89
+
90
+ private
91
+
92
+ def completion_classes(prev_line_provider)
93
+ clazz, feature = context(prev_line_provider)
94
+ if clazz
95
+ if feature
96
+ @lang.concrete_types(feature.eType)
97
+ else
98
+ refs_by_class = {}
99
+ clazz.eAllReferences.select{|r| r.containment}.each do |r|
100
+ @lang.concrete_types(r.eType).each { |c| (refs_by_class[c] ||= []) << r }
101
+ end
102
+ refs_by_class.keys.select{|c| refs_by_class[c].size == 1}
103
+ end
104
+ else
105
+ @lang.root_epackage.eAllClasses.select{|c| !c.abstract &&
106
+ !c.eAllReferences.any?{|r| r.eOpposite && r.eOpposite.containment}}
107
+ end
108
+ end
109
+
110
+ def context(prev_line_provider)
111
+ command, role = parse_context(prev_line_provider)
112
+ clazz = command && @lang.root_epackage.eAllClasses.find{|c| c.name == command}
113
+ feature = role && clazz && clazz.eAllReferences.find{|r| r.containment && r.name == role}
114
+ [clazz, feature]
115
+ end
116
+
117
+ def parse_context(prev_line_provider)
118
+ block_nesting = 0
119
+ array_nesting = 0
120
+ non_empty_lines = 0
121
+ role = nil
122
+ i = 0
123
+ while line = prev_line_provider.call(i+=1)
124
+ # empty or comment
125
+ next if line =~ /^\s*$/ || line =~ /^\s*#/
126
+ # role
127
+ if line =~ /^\s*(\w+):\s*$/
128
+ role = $1 if non_empty_lines == 0
129
+ # block open
130
+ elsif line =~ /^\s*(\S+).*\{\s*$/
131
+ block_nesting -= 1
132
+ return [$1, role] if block_nesting < 0
133
+ # block close
134
+ elsif line =~ /^\s*\}\s*$/
135
+ block_nesting += 1
136
+ # array open
137
+ elsif line =~ /^\s*(\w+):\s*\[\s*$/
138
+ array_nesting -= 1
139
+ role = $1 if array_nesting < 0
140
+ # array close
141
+ elsif line =~ /^\s*\]\s*$/
142
+ array_nesting += 1
143
+ end
144
+ non_empty_lines += 1
145
+ end
146
+ [nil, nil]
147
+ end
148
+
149
+ end
150
+
151
+ end
152
+