rtext 0.5.1 → 0.7.0

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