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.
- 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
|
+
|