metaruby 1.0.0 → 2.0.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.
@@ -1,5 +1,65 @@
1
+ require 'metaruby/attributes'
2
+
1
3
  module MetaRuby
2
4
  module DSLs
5
+ # Common definition of #respond_to_missing? and #method_missing to be
6
+ # used in conjunction with {DSLs.find_through_method_missing} and
7
+ # {DSLs.has_through_method_missing?}
8
+ #
9
+ # @example resolve 'event' objects using method_missing
10
+ # class Task
11
+ # # Tests if this task has an event by this name
12
+ # #
13
+ # # @param [String] name
14
+ # # @return [Boolean]
15
+ # def has_event?(name)
16
+ # end
17
+ #
18
+ # # Finds an event by name
19
+ # #
20
+ # # @param [String] name
21
+ # # @return [Object,nil] the found event, or nil if there is no
22
+ # # event by this name
23
+ # def find_event(name)
24
+ # end
25
+ #
26
+ # include MetaRuby::DSLs::FindThroughMethodMissing
27
+ #
28
+ # # Check if the given method matches a find object
29
+ # def has_through_method_missing?(m)
30
+ # MetaRuby::DSLs.has_through_method_missing?(
31
+ # self, m, '_event' => :has_event?) || super
32
+ # end
33
+ #
34
+ # # Check if the given method matches a find object
35
+ # def find_through_method_missing(m, args)
36
+ # MetaRuby::DSLs.find_through_method_missing(
37
+ # self, m, args, '_event' => :find_event) || super
38
+ # end
39
+ # end
40
+ #
41
+ module FindThroughMethodMissing
42
+ # Empty implementation of has_through_method_missing? to allow for
43
+ # classes to call 'super'
44
+ def has_through_method_missing?(m)
45
+ end
46
+
47
+ # Empty implementation of find_through_method_missing to allow for
48
+ # classes to call 'super'
49
+ def find_through_method_missing(m, args)
50
+ end
51
+
52
+ # Resolves the given method using {#has_through_method_missing?}
53
+ def respond_to_missing?(m, include_private)
54
+ has_through_method_missing?(m) || super
55
+ end
56
+
57
+ # Resolves the given method using {#find_through_method_missing}
58
+ def method_missing(m, *args)
59
+ find_through_method_missing(m, args) || super
60
+ end
61
+ end
62
+
3
63
  # Generic implementation to create suffixed accessors for child objects
4
64
  # on a class
5
65
  #
@@ -45,32 +105,34 @@ module MetaRuby
45
105
  # object.add_state 'my'
46
106
  # object.my_state # will resolve the 'my' state
47
107
  #
48
- def self.find_through_method_missing(object, m, args, *suffixes)
49
- suffix_match = Hash.new
50
- if suffixes.last.kind_of?(Hash)
51
- suffix_match.merge!(suffixes.pop)
52
- end
53
- suffixes.each do |name|
54
- suffix_match[name] = "find_#{name}"
55
- end
108
+ def self.find_through_method_missing(object, m, args, suffix_match)
109
+ return false if m == :to_ary
56
110
 
57
111
  m = m.to_s
58
112
  suffix_match.each do |s, find_method_name|
59
- if m == find_method_name
60
- raise NoMethodError.new("#{object} has no method called #{find_method_name}", m)
61
- elsif m =~ /(.*)_#{s}$/
62
- name = $1
113
+ if m.end_with?(s)
114
+ name = m[0, m.size - s.size]
63
115
  if !args.empty?
64
116
  raise ArgumentError, "expected zero arguments to #{m}, got #{args.size}", caller(4)
65
- elsif found = object.send(find_method_name, name)
66
- return found
67
117
  else
68
- msg = "#{object} has no #{s} named #{name}"
69
- raise NoMethodError.new(msg, m), msg, caller(4)
118
+ return object.send(find_method_name, name)
70
119
  end
71
120
  end
72
121
  end
73
122
  nil
74
123
  end
124
+
125
+ def self.has_through_method_missing?(object, m, suffix_match)
126
+ return false if m == :to_ary
127
+
128
+ m = m.to_s
129
+ suffix_match.each do |s, has_method_name|
130
+ if m.end_with?(s)
131
+ name = m[0, m.size - s.size]
132
+ return !!object.send(has_method_name, name)
133
+ end
134
+ end
135
+ false
136
+ end
75
137
  end
76
138
  end
@@ -4,6 +4,7 @@ require 'kramdown'
4
4
  require 'pp'
5
5
  require 'metaruby/gui/html'
6
6
  require 'metaruby/gui/ruby_constants_item_model'
7
+ require 'metaruby/gui/model_hierarchy'
7
8
  require 'metaruby/gui/rendering_manager'
8
9
  require 'metaruby/gui/model_browser'
9
10
  require 'metaruby/gui/model_selector'
@@ -1,6 +1,6 @@
1
- .message { font-size: 80%; font-weight: normal; background-color: rgb(176,206,234); text-align: left; }
2
- .backtrace_links { font-size: 80%; margin-left: 2em;}
3
- .backtrace { font-size: 80%; background-color: rgb(219,230,241); padding-left: 1em; }
4
- .backtrace_summary { font-size: 80%; background-color: rgb(219,230,241); padding-left: 1em; }
1
+ .message { font-size: 100%; font-weight: normal; background-color: rgb(176,206,234); text-align: left; }
2
+ .backtrace_links { font-size: 100%; margin-left: 2em;}
3
+ .backtrace { font-size: 100%; background-color: rgb(219,230,241); padding-left: 1em; }
4
+ .backtrace_summary { font-size: 100%; background-color: rgb(219,230,241); padding-left: 1em; }
5
5
  .user_file { font-weight: bold; }
6
6
 
@@ -3,8 +3,5 @@
3
3
  <% end %>
4
4
  <% fragments.each do |fragment| %>
5
5
  <%= html_fragment(fragment, ressource_dir: ressource_dir) %>
6
- <% if fragment.id %>
7
- </div>
8
- <% end %>
9
6
  <% end %>
10
7
 
@@ -57,14 +57,19 @@ module MetaRuby
57
57
  # A Page object tunes to create URIs for objects that are suitable
58
58
  # for {#model_selector}
59
59
  class Page < HTML::Page
60
+ def initialize(model_selector, display_page)
61
+ super(display_page)
62
+ @model_selector = model_selector
63
+ end
64
+
60
65
  # Overloaded from {HTML::Page} to resolve object paths (in the
61
66
  # constant hierarchy, e.g. A::B::C) into the corresponding
62
67
  # path expected by {#model_selector} (e.g. /A/B/C)
63
68
  def uri_for(object)
64
- if object.respond_to?(:name) && (obj_name = object.name) && (obj_name =~ /^[\w:]+$/)
65
- path = obj_name.split("::")
66
- "/" + path.join("/")
67
- else super
69
+ if resolver = @model_selector.find_resolver_from_model(object)
70
+ "/" + resolver.split_name(object).join("/")
71
+ else
72
+ super
68
73
  end
69
74
  end
70
75
  end
@@ -151,9 +156,11 @@ module MetaRuby
151
156
  # models might be submodels of various types at the same time (as
152
157
  # e.g. when both a model and its supermodel are registered here).
153
158
  # The one with the highest priority will be used.
154
- def register_type(type, rendering_class, name, priority = 0)
155
- model_selector.register_type(type, name, priority)
156
- manager.register_type(type, rendering_class)
159
+ def register_type(root_model, rendering_class, name, priority = 0, categories: [], resolver: ModelHierarchy::Resolver.new)
160
+ model_selector.register_type(
161
+ root_model, name, priority,
162
+ categories: categories, resolver: resolver)
163
+ manager.register_type(root_model, rendering_class)
157
164
  end
158
165
 
159
166
  # Sets up the widgets that form the central part of the browser
@@ -181,7 +188,7 @@ module MetaRuby
181
188
  end
182
189
  splitter.add_widget(display)
183
190
  splitter.set_stretch_factor(1, 2)
184
- self.page = Page.new(display.page)
191
+ self.page = Page.new(@model_selector, display.page)
185
192
 
186
193
  model_selector.connect(SIGNAL('model_selected(QVariant)')) do |mod|
187
194
  mod = mod.to_ruby
@@ -194,20 +201,28 @@ module MetaRuby
194
201
  #
195
202
  # @param [Page] page the new page object
196
203
  def page=(page)
197
- manager.page = page
198
- page.connect(SIGNAL('linkClicked(const QUrl&)')) do |url|
199
- if url.scheme == "link"
200
- path = url.path
201
- path = path.split('/')[1..-1]
202
- select_by_path(*path)
203
- end
204
+ if @page
205
+ disconnect(@page, SIGNAL('linkClicked(const QUrl&)'), self, SLOT('linkClicked(const QUrl&)'))
206
+ disconnect(@page, SIGNAL('updated()'), self, SLOT('update_exceptions()'))
207
+ disconnect(@page, SIGNAL('fileOpenClicked(const QUrl&)'), self, SLOT('fileOpenClicked(const QUrl&)'))
204
208
  end
209
+ manager.page = page
210
+ connect(page, SIGNAL('linkClicked(const QUrl&)'), self, SLOT('linkClicked(const QUrl&)'))
205
211
  connect(page, SIGNAL('updated()'), self, SLOT('update_exceptions()'))
206
212
  connect(page, SIGNAL('fileOpenClicked(const QUrl&)'), self, SLOT('fileOpenClicked(const QUrl&)'))
207
213
  connect(manager, SIGNAL('updated()'), self, SLOT('update_exceptions()'))
208
214
  @page = page
209
215
  end
210
216
 
217
+ def linkClicked(url)
218
+ if url.scheme == "link"
219
+ path = url.path
220
+ path = path.split('/')[1..-1]
221
+ select_by_path(*path)
222
+ end
223
+ end
224
+ slots 'linkClicked(const QUrl&)'
225
+
211
226
  signals 'fileOpenClicked(const QUrl&)'
212
227
 
213
228
  # Call to render the given model
@@ -239,16 +254,16 @@ module MetaRuby
239
254
  end
240
255
  slots 'update_exceptions()'
241
256
 
242
- # (see ModelSelector#select_by_module)
257
+ # (see ModelSelector#select_by_model)
243
258
  def select_by_path(*path)
244
259
  if model_selector.select_by_path(*path)
245
260
  push_to_history(path)
246
261
  end
247
262
  end
248
263
 
249
- # (see ModelSelector#select_by_module)
250
- def select_by_module(model)
251
- if model_selector.select_by_module(model)
264
+ # (see ModelSelector#select_by_model)
265
+ def select_by_model(model)
266
+ if model_selector.select_by_model(model)
252
267
  push_to_history(model)
253
268
  end
254
269
  end
@@ -289,7 +304,7 @@ module MetaRuby
289
304
  def select_by_history_element(h)
290
305
  if h.respond_to?(:to_ary)
291
306
  select_by_path(*h)
292
- else select_by_module(h)
307
+ else select_by_model(h)
293
308
  end
294
309
  end
295
310
 
@@ -0,0 +1,267 @@
1
+ module MetaRuby
2
+ module GUI
3
+ class ModelHierarchy < Qt::StandardItemModel
4
+ Metadata = Struct.new :name, :search_key, :categories do
5
+ def merge(other)
6
+ a, b = search_key, other.search_key.dup
7
+ if a.size < b.size
8
+ a, b = b, a
9
+ end
10
+ b.size.times do |i|
11
+ a[i] |= b[i]
12
+ end
13
+ self.search_key = a
14
+ self.categories = categories | other.categories
15
+ end
16
+
17
+ def to_user_role
18
+ categories = self.categories.to_a.join(",")
19
+ search_key = self.search_key.map do |level_keys|
20
+ level_keys.join(",")
21
+ end.join(",;,")
22
+ ",#{categories},;,#{search_key},"
23
+ end
24
+ end
25
+
26
+ class Resolver
27
+ def initialize(root_model)
28
+ @root_model = root_model
29
+ end
30
+
31
+ def split_name(model)
32
+ name = model.name
33
+ split = model.name.split('::')
34
+ if name.start_with?('::')
35
+ split[1..-1]
36
+ else split
37
+ end
38
+ end
39
+
40
+ def each_submodel(model)
41
+ if model == @root_model
42
+ model.each_submodel do |m|
43
+ yield(m, !m.name)
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ def initialize
50
+ super()
51
+ @root_models = Array.new
52
+ end
53
+
54
+ # Find the resolver object that has been responsible for a given
55
+ # object's discovery
56
+ #
57
+ # @return [Object,nil]
58
+ def find_resolver_from_model(model)
59
+ @resolver_from_model[model]
60
+ end
61
+
62
+ # Returns the path to the given model or nil if it is not registered
63
+ def find_path_from_model(model)
64
+ if resolver = find_resolver_from_model(model)
65
+ resolver.split_name(model)
66
+ end
67
+ end
68
+
69
+ RootModel = Struct.new :model, :priority, :categories, :resolver
70
+
71
+ def add_root(root_model, priority, categories: [], resolver: Resolver.new(root_model))
72
+ @root_models << RootModel.new(root_model, priority, categories, resolver)
73
+ end
74
+
75
+ # Refresh the model to match the current hierarchy that starts with
76
+ # this object's root model
77
+ def reload
78
+ begin_reset_model
79
+ clear
80
+
81
+ @items_to_models = Hash.new
82
+ @models_to_items = Hash.new
83
+ @names_to_item = Hash.new
84
+ @items_metadata = Hash[self => Metadata.new([], [], Set.new)]
85
+ @resolver_from_model = Hash.new
86
+
87
+ seen = Set.new
88
+ sorted_roots = @root_models.
89
+ sort_by(&:priority).reverse
90
+
91
+ sorted_roots.each do |root_model|
92
+ models = discover_model_hierarchy(root_model.model, root_model.categories, root_model.resolver, seen)
93
+ models.each do |m|
94
+ @resolver_from_model[m] = root_model.resolver
95
+ end
96
+ end
97
+
98
+ rowCount.times do |row|
99
+ compute_and_store_metadata(item(row))
100
+ end
101
+ self.horizontal_header_labels = [""]
102
+ ensure
103
+ end_reset_model
104
+ end
105
+
106
+ # Returns the model that matches the QStandardItem
107
+ #
108
+ # @return [Object,nil]
109
+ def find_model_from_item(item)
110
+ @items_to_models[item]
111
+ end
112
+
113
+ # Returns the model that matches the QModelIndex
114
+ #
115
+ # @return [Object,nil]
116
+ def find_model_from_index(index)
117
+ @items_to_models[
118
+ item_from_index(index)]
119
+ end
120
+
121
+ # @api private
122
+ #
123
+ # Compute each item's metadata and stores it in UserRole. The
124
+ # metadata is stored as
125
+ # "Category0|Category1;name/with/slashes/between/parts". This is
126
+ # meant to be used in a seach/filter function.
127
+ def compute_and_store_metadata(item)
128
+ current = @items_metadata[item]
129
+
130
+ item.rowCount.times do |row|
131
+ current.merge compute_and_store_metadata(item.child(row))
132
+ end
133
+
134
+ item.set_data(Qt::Variant.new(current.to_user_role), Qt::UserRole)
135
+ current
136
+ end
137
+
138
+ # @api private
139
+ #
140
+ # Register a model in the hierarchy
141
+ def register_model(model, model_categories, resolver)
142
+ name = resolver.split_name(model)
143
+ if !name || name.empty?
144
+ #raise ArgumentError, "cannot resolve #{model.name}"
145
+ puts "Cannot resolve #{model}"
146
+ return
147
+ end
148
+
149
+ context = name[0..-2].inject(self) do |item, name|
150
+ resolve_namespace(item, name)
151
+ end
152
+ if !(item = find_item_child_by_name(context, name.last))
153
+ item = Qt::StandardItem.new(name.last)
154
+ context.append_row(item)
155
+ (@names_to_item[context] ||= Hash.new)[name.last] = item
156
+ end
157
+
158
+ item.flags = Qt::ItemIsEnabled
159
+
160
+ @items_metadata[item] = Metadata.new(name, name.map { |n| [n] }, model_categories)
161
+ @items_to_models[item] = model
162
+ @models_to_items[model] = item
163
+ end
164
+
165
+ # @api private
166
+ def find_item_child_by_name(item, name)
167
+ if context = @names_to_item[item]
168
+ context[name]
169
+ end
170
+ end
171
+
172
+ # Returns the QStandardItem object that sits at the given path
173
+ #
174
+ # @return [Qt::StandardItem,nil]
175
+ def find_item_by_path(*path)
176
+ path.inject(self) do |parent_item, name|
177
+ return if !name
178
+ if names = @names_to_item[parent_item]
179
+ names[name]
180
+ end
181
+ end
182
+ end
183
+
184
+ # Returns the ModelIndex object that sits at the given path
185
+ #
186
+ # @return [Qt::ModelIndex,nil]
187
+ def find_index_by_path(*path)
188
+ if item = find_item_by_path(*path)
189
+ item.index
190
+ end
191
+ end
192
+
193
+ # Returns the QStandardItem object that represents the given model
194
+ def find_item_by_model(model)
195
+ @models_to_items[model]
196
+ end
197
+
198
+ # Returns the QModelIndex object that represents the given model
199
+ def find_index_by_model(model)
200
+ if item = find_item_by_model(model)
201
+ item.index
202
+ end
203
+ end
204
+
205
+ # @api private
206
+ #
207
+ # Register a model and its whole submodels hierarchy
208
+ def discover_model_hierarchy(root_model, categories, resolver, seen)
209
+ discovered = Array.new
210
+ queue = [root_model]
211
+ categories = categories.to_set
212
+
213
+ while !queue.empty?
214
+ m = queue.shift
215
+ next if seen.include?(m)
216
+ seen << m
217
+ discovered << m
218
+
219
+ register_model(m, categories, resolver)
220
+ resolver.each_submodel(m) do |model, excluded|
221
+ raise if model.kind_of?(String)
222
+ if excluded
223
+ seen << model
224
+ else
225
+ queue << model
226
+ end
227
+ end
228
+ end
229
+ discovered
230
+ end
231
+
232
+ # @api private
233
+ #
234
+ # Find or create the StandardItem that represents a root in the
235
+ # hierarchy
236
+ #
237
+ # @param [String] name the name of the namespace
238
+ # @return [Qt::StandardItem]
239
+ def resolve_root_namespace(name)
240
+ resolve_namespace(self, name)
241
+ end
242
+
243
+ # @api private
244
+ #
245
+ # Find or create the StandardItem that represents a namespace in the
246
+ # hierarchy
247
+ #
248
+ # @param [Qt::StandardItem] parent_item the parent item
249
+ # @param [String] name the name of the namespace
250
+ # @return [Qt::StandardItem]
251
+ def resolve_namespace(parent_item, name)
252
+ if item = find_item_child_by_name(parent_item, name)
253
+ item
254
+ else
255
+ item = Qt::StandardItem.new(name)
256
+ item.flags = 0
257
+
258
+ parent_name = @items_metadata[parent_item].name
259
+ @items_metadata[item] = Metadata.new(parent_name + [name], [], Set.new)
260
+ parent_item.append_row item
261
+ (@names_to_item[parent_item] ||= Hash.new)[name] = item
262
+ end
263
+ end
264
+ end
265
+ end
266
+ end
267
+