metaruby 1.0.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +6 -3
- data/Gemfile +6 -0
- data/Rakefile +12 -2
- data/lib/metaruby.rb +2 -0
- data/lib/metaruby/attributes.rb +52 -60
- data/lib/metaruby/backward/singleton_class_p.rb +14 -0
- data/lib/metaruby/dsls.rb +31 -1
- data/lib/metaruby/dsls/doc.rb +33 -25
- data/lib/metaruby/dsls/find_through_method_missing.rb +78 -16
- data/lib/metaruby/gui.rb +1 -0
- data/lib/metaruby/gui/html/exception_view.css +4 -4
- data/lib/metaruby/gui/html/page_body.rhtml +0 -3
- data/lib/metaruby/gui/model_browser.rb +35 -20
- data/lib/metaruby/gui/model_hierarchy.rb +267 -0
- data/lib/metaruby/gui/model_selector.rb +63 -40
- data/lib/metaruby/model_as_class.rb +9 -13
- data/lib/metaruby/model_as_module.rb +2 -5
- data/lib/metaruby/registration.rb +25 -19
- data/lib/metaruby/test.rb +3 -0
- data/lib/metaruby/version.rb +1 -1
- metadata +5 -4
@@ -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,
|
49
|
-
|
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
|
60
|
-
|
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
|
-
|
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
|
data/lib/metaruby/gui.rb
CHANGED
@@ -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:
|
2
|
-
.backtrace_links { font-size:
|
3
|
-
.backtrace { font-size:
|
4
|
-
.backtrace_summary { font-size:
|
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
|
|
@@ -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
|
65
|
-
|
66
|
-
|
67
|
-
|
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(
|
155
|
-
model_selector.register_type(
|
156
|
-
|
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
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
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#
|
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#
|
250
|
-
def
|
251
|
-
if model_selector.
|
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
|
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
|
+
|