rtext 0.5.1 → 0.7.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.
data/CHANGELOG CHANGED
@@ -47,3 +47,32 @@
47
47
 
48
48
  * Fixed service connection problem when ports are in use by other processes
49
49
 
50
+ =0.5.2
51
+
52
+ * Fixed exception in default service provider when trying to follow a reference on an attribute value
53
+ * Ignore BOM in instantiator
54
+
55
+ =0.5.3
56
+
57
+ * Fixed completion of enum values which need to be quoted
58
+ * Added support for BigDecimal
59
+
60
+ =0.6.0
61
+
62
+ * Changed service provider interface to allow for more customization
63
+ * Changed Completer into DefaultCompleter to allow for customization
64
+ * Added labeled_containments language parameter
65
+ * Made DefaultLoader robust against missing files
66
+
67
+ =0.7.0
68
+
69
+ * Added DefaultResolver and support for custom resolvers for DefaultLoader and DefaultServiceProvider
70
+ * Changed instantiator on_progress proc to take a second argument with the number of progress steps
71
+ * Changed tokenizer to start new tokens immediately after error tokens
72
+ * Fixed line number in instantiator problem report for multiple childs in one-role
73
+ * Fixed unit tests for Ruby 2.0
74
+ * Fixed context builder prefixes for strings
75
+ * Fixed DefaultLoader to let fragments calculate their elements list by themselves
76
+ * Minor performance improvements, mainly for instantiator and when passing large RText messages
77
+ * Improved performance of frontend connector especially for large amounts of data
78
+
data/MIT-LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2012 Martin Thiede
1
+ Copyright (c) 2013 Martin Thiede
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/RText_Protocol CHANGED
@@ -296,94 +296,145 @@ This information can be used to show the commands in a hierarchical menu, e.g. a
296
296
  ]
297
297
  }
298
298
 
299
- === Custom Command Invocation
299
+ === Custom Command Parameters
300
300
 
301
- Custom commands are invoked just like the predefined commands. The frontend uses the command
302
- identifier returned by the "Custom Commands" request as the value of the ``command`` field in
303
- the new invocation request. If the command id was returned by a "Costum Commands" request which
304
- included context information, the frontend should send the same context information in the new
305
- invocation request and all repeated requests (see below). If this information was not present
306
- in command list request, it should not be send in the new command invocation.
301
+ Before a custom command can be invoked, the frontend must check with the backend if parameters
302
+ are required. This is done by means of the command
307
303
 
308
304
  {
309
305
  "type": "request",
310
- "command": <command id>
306
+ "command": "check_custom_command_parameters",
307
+ "command_id": <custom command id>
308
+ "context": <context lines, optional>,
309
+ "column": <cursor column, optional>
310
+ "parameters": [
311
+ {
312
+ "name": <parameter name>,
313
+ "value": <parameter value>
314
+ }
315
+ ...
316
+ ]
311
317
  }
312
318
 
313
- On first invocation, the frontend will not send any parameters with the command request.
314
- Instead, the backend's response may ask for parameters to be included into the next request.
315
- In this case, the frontend should will prompt the user to enter the required information and then
316
- reinvoke the command with the parameters included. This process of asking for more parameters
317
- may be repeated several times.
319
+ If the command id was returned by a "Costum Commands" request which
320
+ included context information, the frontend should send the same context information in the new
321
+ invocation request and all repeated requests (see below). If this information was not sent
322
+ in command list request (non-context sensitive command), it should neither be send in the new
323
+ command invocation.
318
324
 
319
- More generally, the backend will tell the frontend to display dialogs to the user.
320
- Dialogs may contain input fields, output fields and hidden fields.
321
- Fields may be marked to indicate that their value should be sent to the backend on next invocation.
325
+ If a command requires parameters, the response will have the field `success` set to false and
326
+ will include information for building a user dialog.
322
327
 
323
328
  The dialogs contain elements of certain types:
324
329
 
325
- * text: text as a string
326
- * text
327
- * element_list field
328
- * element_table field
330
+ * label: text presented to the user
331
+ * text: text input field
332
+ * table: table view/input
333
+ * hidden: hidden field used to hold state information
334
+
335
+ The frontend should keep repeating requests as long as the field ``success`` is set to ``false``.
329
336
 
330
- Each dialog element has the following attributes:
337
+ Note that the backend doesn't hold any state, so that commands can easily be canceled by just
338
+ not making any additional requests (and not actually invoking the command).
331
339
 
332
- * name: user visible title of the field [String]
333
- * desc: an optional description of the field [String]
334
- * editable: if the user can edit the field [Boolean]
335
- * visible: if the field is visible, there may be hidden fields for internal purpose [Boolean]
336
- * return: if the value should be sent back with the next request [Boolean]
337
- * error: an error description, if present the field should be marked to have an error [String]
338
- * value: the preset value of the field, type is field type specific
340
+ If the response contains step information (number of steps, current step), the the frontend
341
+ should display buttons for moving forward and backward in a wizard.
339
342
 
340
- The frontend should keep repeating requests as long as the field ``repeat`` is set to ``true``.
343
+ When the frontend receives dialog information, it should display the dialog filled with the
344
+ widgets as specified and with widget content set up properly (text fields preset with strings,
345
+ rows selected in table, etc).
346
+
347
+ When the user has finished the dialog, the frontend should send all input information back to
348
+ the backend as name/value pairs. This includes:
349
+
350
+ * hidden fields
351
+ * text fields unless disabled
352
+ * table fields unless disabled
341
353
 
342
354
  {
343
355
  "type": "response",
344
- "repeat": <if command should be repeated>,
356
+ "success": <true|false, true if command is finshed>,
357
+ "num_steps", <number of steps of this (wizard) command, optional>,
358
+ "current_step", <current wizard step, optional>,
345
359
  "dialog": {
346
360
  "title": <dialog title>,
347
- "desc": <description what to do>,
348
361
  "elements": [
349
362
  {
350
- "type": "text_input",
363
+ "type": "lable",
364
+ "text": <text to display>
365
+ },
366
+ {
367
+ "type": "hidden",
351
368
  "name": <parameter name>,
352
- "value": <preset value>,
353
- "error": <error text>
369
+ "value": <value of hidden filed [string]>
354
370
  },
355
371
  {
356
- "type": "choice_input",
357
- "num_min": <min number of elements to choose, default: 1>,
358
- "num_max": <max mumber of elements to choose, default: 1>,
359
- "choices": [
360
- "display": <display name>,
361
- "id": <identifier to be sent back>,
362
- ],
363
- "value": [ <selected choice>, <selected choice>, ...],
372
+ "type": "text",
373
+ "name": <parameter name>,
374
+ "value": <preset value>,
364
375
  "error": <error text>
376
+ "description: <discription of parameter, optional>
377
+ "disabled:" <true|false, if set to false the input widget can't be changed>
365
378
  },
366
379
  {
367
- "type": "element_input",
380
+ "type": "table",
381
+ "name": <parameter name>,
368
382
  "num_min": <min number of elements to choose, default: 1>,
369
- "num_max": <max mumber of elements to choose, default: 1>,
370
- "choices": [
383
+ "num_max": <max mumber of elements to choose, or * for unlimited, default: 1>,
384
+ "columns": [
385
+ {
386
+ "name": <column name>,
387
+ "description: <discription of parameter, optional>
388
+ }
389
+ ...
390
+ ],
391
+ "rows": [
371
392
  {
372
- "display": <display name>,
373
393
  "id": <identifier to be sent back>,
374
- "file": <fully qualfied file name, optional>,
375
- "line": <line number, optional>,
376
- "ancestors": <parent hierarchy, optional",
394
+ "values" [
395
+ {
396
+ "display": <display name>,
397
+ "file": <fully qualfied file name, optional>,
398
+ "line": <line number, optional>,
399
+ }
400
+ ...
401
+ ]
377
402
  }
378
403
  ...
379
404
  ],
380
- "value": [ <selected choice>, <selected choice>, ...],
381
- "error": <error text>
405
+ "selected": [ <row id>, <row id>, ...],
406
+ "error": <error text>,
407
+ "description: <discription of parameter, optional>
408
+ "disabled:" <true|false, if set to false, no selection can be made>
382
409
  }
383
410
  ]
384
411
  }
385
412
  }
386
413
 
414
+ === Custom Command Invocation
415
+
416
+ Custom commands are invoked using the command id returned by the "Custom Commands" request.
417
+ Before a command can be invoked, the parameters must be figured out by means of the "Check
418
+ Custom Command Parameters" command. Once figured out, the parameters will be sent with the
419
+ command invocation as a name/value list.
420
+
421
+ The context information should be sent as with the "Check Custom Command Parameters" command.
422
+
423
+ {
424
+ "type": "request",
425
+ "command": "invoke_custom_command",
426
+ "command_id": <custom command id>
427
+ "context": <context lines, optional>,
428
+ "column": <cursor column, optional>
429
+ "parameters": [
430
+ {
431
+ "name": <parameter name>,
432
+ "value": <parameter value>
433
+ }
434
+ ...
435
+ ]
436
+ }
437
+
387
438
  === Stop Service
388
439
 
389
440
  This command is normally invoked when the frontend terminates or otherwise needs to terminate
data/Rakefile CHANGED
@@ -8,7 +8,7 @@ DocFiles = [
8
8
 
9
9
  RTextGemSpec = Gem::Specification.new do |s|
10
10
  s.name = %q{rtext}
11
- s.version = "0.5.1"
11
+ s.version = "0.7.0"
12
12
  s.date = Time.now.strftime("%Y-%m-%d")
13
13
  s.summary = %q{Ruby Textual Modelling}
14
14
  s.email = %q{martin dot thiede at gmx de}
@@ -121,6 +121,7 @@ module ContextBuilder
121
121
  missing_comma = false
122
122
  unlabled_index = 0
123
123
  tokens[1..-1].each do |token|
124
+ break if token.kind == :error
124
125
  if token.kind == "["
125
126
  in_array = true
126
127
  elsif token.kind == "]"
@@ -151,7 +152,13 @@ module ContextBuilder
151
152
  end
152
153
  if [:error, :string, :integer, :float, :boolean, :identifier, :reference].
153
154
  include?(tokens.last.kind) && line !~ /\s$/
154
- prefix = tokens.last.value.to_s
155
+ last_error = tokens.rindex{|t| t.kind == :error && t.value == '"'}
156
+ last_string = tokens.rindex{|t| t.kind == :string}
157
+ if last_error && (!last_string || last_error > last_string)
158
+ prefix = line[tokens[last_error].scol-1..-1]
159
+ else
160
+ prefix = line[tokens.last.scol-1..-1]
161
+ end
155
162
  else
156
163
  prefix = ""
157
164
  end
@@ -0,0 +1,163 @@
1
+ require 'rgen/ecore/ecore_ext'
2
+
3
+ module RText
4
+
5
+ class DefaultCompleter
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
+ def complete(context)
18
+ clazz = context && context.element && context.element.class.ecore
19
+ if clazz
20
+ if context.in_block
21
+ block_options(context, clazz)
22
+ elsif !context.problem
23
+ result = []
24
+ if context.feature
25
+ add_value_options(context, result)
26
+ end
27
+ if !context.after_label
28
+ add_label_options(context, clazz, result)
29
+ end
30
+ result
31
+ else
32
+ # missing comma, after curly brace, etc.
33
+ []
34
+ end
35
+ elsif context
36
+ root_options
37
+ else
38
+ []
39
+ end
40
+ end
41
+
42
+ def block_options(context, clazz)
43
+ types = []
44
+ labled_refs = []
45
+ if context.feature
46
+ if context.feature.is_a?(RGen::ECore::EReference) && context.feature.containment
47
+ types = @lang.concrete_types(context.feature.eType)
48
+ else
49
+ # invalid, ignore
50
+ end
51
+ else
52
+ # all target types which don't need a label
53
+ # and all lables which are needed by a potential target type
54
+ @lang.containments(clazz).each do |r|
55
+ ([r.eType] + r.eType.eAllSubTypes).select{|t| !t.abstract}.each do |t|
56
+ if @lang.labeled_containment?(clazz, r) || @lang.containments_by_target_type(clazz, t).size > 1
57
+ labled_refs << r
58
+ else
59
+ types << t
60
+ end
61
+ end
62
+ end
63
+ end
64
+ types.uniq.
65
+ sort{|a,b| a.name <=> b.name}.collect do |c|
66
+ class_completion_option(c)
67
+ end +
68
+ labled_refs.uniq.collect do |r|
69
+ CompletionOption.new("#{r.name}:", "<#{r.eType.name}>")
70
+ end
71
+ end
72
+
73
+ def add_value_options(context, result)
74
+ if context.feature.is_a?(RGen::ECore::EAttribute) || !context.feature.containment
75
+ if context.feature.is_a?(RGen::ECore::EReference)
76
+ result.concat(reference_options(context))
77
+ elsif context.feature.eType.is_a?(RGen::ECore::EEnum)
78
+ result.concat(enum_options(context))
79
+ elsif context.feature.eType.instanceClass == String
80
+ result.concat(string_options(context))
81
+ elsif context.feature.eType.instanceClass == Integer
82
+ result.concat(integer_options(context))
83
+ elsif context.feature.eType.instanceClass == Float
84
+ result.concat(float_options(context))
85
+ elsif context.feature.eType.instanceClass == RGen::MetamodelBuilder::DataTypes::Boolean
86
+ result.concat(boolean_options(context))
87
+ else
88
+ # no options
89
+ end
90
+ else
91
+ # containment reference, ignore
92
+ end
93
+ end
94
+
95
+ def add_label_options(context, clazz, result)
96
+ result.concat(@lang.labled_arguments(clazz).
97
+ select{|f|
98
+ !context.element.eIsSet(f.name)}.collect do |f|
99
+ CompletionOption.new("#{f.name}:", "<#{f.eType.name}>")
100
+ end )
101
+ end
102
+
103
+ def root_options
104
+ @lang.root_classes.
105
+ sort{|a,b| a.name <=> b.name}.collect do |c|
106
+ class_completion_option(c)
107
+ end
108
+ end
109
+
110
+ def reference_options(context)
111
+ []
112
+ end
113
+
114
+ def enum_options(context)
115
+ context.feature.eType.eLiterals.collect do |l|
116
+ lname = l.name
117
+ if lname =~ /^\d|\W/ || lname == "true" || lname == "false"
118
+ lname = "\"#{lname.gsub("\\","\\\\\\\\").gsub("\"","\\\"").gsub("\n","\\n").
119
+ gsub("\r","\\r").gsub("\t","\\t").gsub("\f","\\f").gsub("\b","\\b")}\""
120
+ end
121
+ CompletionOption.new("#{lname}", "<#{context.feature.eType.name}>")
122
+ end
123
+ end
124
+
125
+ def string_options(context)
126
+ if @lang.unquoted?(context.feature)
127
+ [ CompletionOption.new("#{context.feature.name.gsub(/\W/,"")}", value_description(context)) ]
128
+ else
129
+ [ CompletionOption.new("\"\"", value_description(context)) ]
130
+ end
131
+ end
132
+
133
+ def integer_options(context)
134
+ (0..0).collect{|i| CompletionOption.new("#{i}", value_description(context)) }
135
+ end
136
+
137
+ def float_options(context)
138
+ (0..0).collect{|i| CompletionOption.new("#{i}.0", value_description(context)) }
139
+ end
140
+
141
+ def boolean_options(context)
142
+ [true, false].collect{|b| CompletionOption.new("#{b}", value_description(context)) }
143
+ end
144
+
145
+ private
146
+
147
+ def value_description(context)
148
+ if context.after_label
149
+ "<#{context.feature.eType.name}>"
150
+ else
151
+ "[#{context.feature.name}] <#{context.feature.eType.name}>"
152
+ end
153
+ end
154
+
155
+ def class_completion_option(eclass)
156
+ uargs = @lang.unlabled_arguments(eclass).collect{|a| "<#{a.name}>"}.join(", ")
157
+ CompletionOption.new(@lang.command_by_class(eclass.instanceClass), uargs)
158
+ end
159
+
160
+ end
161
+
162
+ end
163
+
@@ -2,6 +2,7 @@ require 'rgen/environment'
2
2
  require 'rgen/util/file_change_detector'
3
3
  require 'rgen/fragment/model_fragment'
4
4
  require 'rtext/instantiator'
5
+ require 'rtext/default_resolver'
5
6
 
6
7
  module RText
7
8
 
@@ -33,6 +34,10 @@ class DefaultLoader
33
34
  # if set to true, don't reload fragments which have parse errors
34
35
  # instead keep the existing fragment but attach the new problem list
35
36
  #
37
+ # :resolver
38
+ # a reference resolver responding to the methods provided by DefaultResolver
39
+ # default: DefaultResolver
40
+ #
36
41
  def initialize(language, fragmented_model, options={})
37
42
  @lang = language
38
43
  @model = fragmented_model
@@ -48,6 +53,7 @@ class DefaultLoader
48
53
  pattern = options[:pattern]
49
54
  @file_provider = options[:file_provider] || proc { Dir.glob(pattern) }
50
55
  @dont_reload_with_errors = options[:dont_reload_with_errors]
56
+ @resolver = options[:resolver] || DefaultResolver.new(language)
51
57
  end
52
58
 
53
59
  # Loads or reloads model fragments from files using the file patterns or file provider
@@ -83,9 +89,7 @@ class DefaultLoader
83
89
  @files_added.each {|f| file_added(f)}
84
90
  @files_changed.each {|f| file_changed(f)}
85
91
  @files_removed.each {|f| file_removed(f)}
86
- @lang.reference_qualifier.call(@model.unresolved_refs, @model)
87
- @model.resolve(:fragment_provider => method(:fragment_provider),
88
- :use_target_type => @lang.per_type_identifier)
92
+ @resolver.resolve_model(@model)
89
93
  end
90
94
 
91
95
  private
@@ -109,8 +113,8 @@ class DefaultLoader
109
113
  @work_last_sent = @work_done
110
114
  end
111
115
 
112
- def instantiator_progress(frag)
113
- @work_done += 1
116
+ def instantiator_progress(frag, steps)
117
+ @work_done += steps
114
118
  if @work_done > @work_last_sent + 100
115
119
  @on_progress_proc.call(frag, @work_done, @work_overall)
116
120
  @work_last_sent = @work_done
@@ -219,27 +223,31 @@ class DefaultLoader
219
223
  problems = []
220
224
  root_elements = []
221
225
  inst = RText::Instantiator.new(@lang)
222
- File.open(fragment.location, "rb") do |f|
223
- inst.instantiate(f.read,
224
- :env => env,
225
- :unresolved_refs => urefs,
226
- :problems => problems,
227
- :root_elements => root_elements,
228
- :fragment_ref => fragment.fragment_ref,
229
- :file_name => fragment.location,
230
- :on_progress => lambda do
231
- @progress_monitor.instantiator_progress(fragment)
232
- end)
226
+ begin
227
+ File.open(fragment.location, "rb") do |f|
228
+ inst.instantiate(f.read,
229
+ :env => env,
230
+ :unresolved_refs => urefs,
231
+ :problems => problems,
232
+ :root_elements => root_elements,
233
+ :fragment_ref => fragment.fragment_ref,
234
+ :file_name => fragment.location,
235
+ :on_progress => lambda do |steps|
236
+ @progress_monitor.instantiator_progress(fragment, steps)
237
+ end)
238
+ end
239
+ rescue Errno::ENOENT
240
+ # missing file, treat as empty
233
241
  end
234
242
  # data might have been created during instantiation (e.g. comment or annotation handler)
235
243
  fragment.data ||= {}
236
244
  fragment.data[:problems] = problems
245
+ # let the fragment calculate the elements, elements might have been added during
246
+ # instantiation (e.g. comment or annotation handler)
237
247
  fragment.set_root_elements(root_elements,
238
- :unresolved_refs => urefs,
239
- :elements => env.elements)
248
+ :unresolved_refs => urefs)
240
249
  fragment.build_index
241
- @lang.reference_qualifier.call(urefs, fragment)
242
- fragment.resolve_local(:use_target_type => @lang.per_type_identifier)
250
+ @resolver.resolve_fragment(fragment)
243
251
  end
244
252
 
245
253
  end