activefacts-compositions 1.9.6 → 1.9.8
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/.gitignore +1 -0
- data/Rakefile +33 -0
- data/activefacts-compositions.gemspec +3 -3
- data/bin/schema_compositor +142 -85
- data/lib/activefacts/compositions/binary.rb +19 -15
- data/lib/activefacts/compositions/compositor.rb +126 -125
- data/lib/activefacts/compositions/constraints.rb +74 -54
- data/lib/activefacts/compositions/datavault.rb +545 -0
- data/lib/activefacts/compositions/names.rb +58 -58
- data/lib/activefacts/compositions/relational.rb +801 -692
- data/lib/activefacts/compositions/traits/rails.rb +180 -0
- data/lib/activefacts/compositions/version.rb +1 -1
- data/lib/activefacts/generator/doc/css/ldm.css +45 -0
- data/lib/activefacts/generator/doc/cwm.rb +764 -0
- data/lib/activefacts/generator/doc/glossary.rb +473 -0
- data/lib/activefacts/generator/doc/graphviz.rb +134 -0
- data/lib/activefacts/generator/doc/ldm.rb +698 -0
- data/lib/activefacts/generator/oo.rb +130 -124
- data/lib/activefacts/generator/rails/models.rb +237 -0
- data/lib/activefacts/generator/rails/schema.rb +273 -0
- data/lib/activefacts/generator/ruby.rb +75 -67
- data/lib/activefacts/generator/sql.rb +333 -351
- data/lib/activefacts/generator/sql/server.rb +100 -39
- data/lib/activefacts/generator/summary.rb +67 -59
- data/lib/activefacts/generator/validate.rb +19 -134
- metadata +18 -15
@@ -0,0 +1,473 @@
|
|
1
|
+
#
|
2
|
+
# ActiveFacts Generators.
|
3
|
+
#
|
4
|
+
# Generate a glossary in HTML
|
5
|
+
#
|
6
|
+
# Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
|
7
|
+
#
|
8
|
+
require 'activefacts/metamodel'
|
9
|
+
require 'activefacts/registry'
|
10
|
+
require 'activefacts/compositions'
|
11
|
+
require 'activefacts/generator'
|
12
|
+
require 'byebug'
|
13
|
+
|
14
|
+
module ActiveFacts
|
15
|
+
module Generators #:nodoc:
|
16
|
+
module Doc
|
17
|
+
class Glossary #:nodoc:
|
18
|
+
# Options are comma or space separated:
|
19
|
+
# * gen_bootstrap Generate bootstrap styled glossary html
|
20
|
+
def self.options
|
21
|
+
{
|
22
|
+
gen_bootstrap: ['Boolean', "Generate bootstrap styled glossary html"],
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
# Base class for generators of object-oriented class libraries for an ActiveFacts vocabulary.
|
27
|
+
def initialize vocabulary, options = {}
|
28
|
+
@vocabulary = vocabulary
|
29
|
+
@options = options
|
30
|
+
@gen_bootstrap = options.has_key?("gen_bootstrap")
|
31
|
+
end
|
32
|
+
|
33
|
+
def puts(*a)
|
34
|
+
@out.puts *a
|
35
|
+
end
|
36
|
+
|
37
|
+
def print(*a)
|
38
|
+
@out.print *a
|
39
|
+
end
|
40
|
+
|
41
|
+
def generate
|
42
|
+
@all_object_type =
|
43
|
+
@vocabulary.
|
44
|
+
all_object_type.
|
45
|
+
sort_by{|o| o.name.gsub(/ /,'').downcase}
|
46
|
+
|
47
|
+
glossary_start +
|
48
|
+
glossary_body +
|
49
|
+
glossary_end
|
50
|
+
end
|
51
|
+
|
52
|
+
def glossary_start
|
53
|
+
if !@gen_bootstrap
|
54
|
+
# puts "<link rel='stylesheet' href='css/orm2.css' media='screen' type='text/css'/>"
|
55
|
+
css_file = "css/orm2.css"
|
56
|
+
|
57
|
+
File.open(File.dirname(__FILE__)+css_file) do |f|
|
58
|
+
"<style media='screen' type='text/css'>\n" +
|
59
|
+
f.read +
|
60
|
+
%Q{
|
61
|
+
.glossary-facttype, .glossary-constraints { display: block; }\n
|
62
|
+
.glossary-doc.hide-alternates .glossary-alternates { display: none; }\n
|
63
|
+
.glossary-doc.hide-constraints .glossary-constraints { display: none; }\n
|
64
|
+
.glossary-doc.hide-examples .glossary-example { display: none; }\n
|
65
|
+
}.gsub(/^\s+/, '') +
|
66
|
+
"</style>\n"
|
67
|
+
end +
|
68
|
+
|
69
|
+
%Q{
|
70
|
+
<style media='print' type='text/css'>\n
|
71
|
+
.keyword { color: #0000CC; font-style: italic; display: inline; }\n
|
72
|
+
.vocabulary, .object_type { color: #8A0092; font-weight: bold; }\n
|
73
|
+
.copula { color: #0E5400; }\n
|
74
|
+
.value { color: #FF990E; display: inline; }\n
|
75
|
+
.glossary-toc { display: none; }\n
|
76
|
+
.glossary-facttype, .glossary-reading { display: inline; }\n
|
77
|
+
</style>\n
|
78
|
+
}.gsub(/^\s+/, '')
|
79
|
+
else
|
80
|
+
''
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def glossary_body
|
85
|
+
if @gen_bootstrap
|
86
|
+
object_types_dump_toc()
|
87
|
+
object_types_dump_def()
|
88
|
+
else
|
89
|
+
object_types_dump_def()
|
90
|
+
object_types_dump_toc()
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def glossary_end
|
95
|
+
if !@gen_bootstrap
|
96
|
+
%Q{
|
97
|
+
<script type="text/javascript">
|
98
|
+
function toggle_class(e, c) {
|
99
|
+
if (!e) return;
|
100
|
+
var n = e.className;
|
101
|
+
var i = n.indexOf(c);
|
102
|
+
if (i == -1) {
|
103
|
+
e.className = n+' '+c;
|
104
|
+
} else {
|
105
|
+
e.className = n.slice(0, i)+n.slice(i+c.length);
|
106
|
+
}
|
107
|
+
if (document.location.toString().indexOf('#') >= 0)
|
108
|
+
document.location = document.location; // Re-scroll to the current fragment
|
109
|
+
}
|
110
|
+
function toggle_constraints() {
|
111
|
+
toggle_class(document.getElementById('glossary-doc'), 'hide-constraints');
|
112
|
+
}
|
113
|
+
function toggle_alternates() {
|
114
|
+
toggle_class(document.getElementById('glossary-doc'), 'hide-alternates');
|
115
|
+
}
|
116
|
+
function toggle_examples() {
|
117
|
+
toggle_class(document.getElementById('glossary-doc'), 'hide-examples');
|
118
|
+
}
|
119
|
+
</script>
|
120
|
+
}.gsub(/^\s+/, '')
|
121
|
+
else
|
122
|
+
''
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def object_types_dump_toc
|
127
|
+
if @gen_bootstrap
|
128
|
+
'<div class="col-md-3 glossary-sidebar">' + "\n"
|
129
|
+
else
|
130
|
+
'<div class="glossary-sidebar">' + "\n"
|
131
|
+
end +
|
132
|
+
'<h1 style="visibility: hidden">X</h1>' +"\n" +
|
133
|
+
'<ol class="glossary-toc">' + "\n"
|
134
|
+
@all_object_type.
|
135
|
+
reject do |o|
|
136
|
+
o.name == '_ImplicitBooleanValueType' or
|
137
|
+
o.kind_of?(ActiveFacts::Metamodel::ValueType) && o.all_role.size == 0 or
|
138
|
+
o.kind_of?(ActiveFacts::Metamodel::TypeInheritance)
|
139
|
+
end.
|
140
|
+
map do |o|
|
141
|
+
"<li>#{termref(o.name)}</li>"
|
142
|
+
end *"\n" + "\n" +
|
143
|
+
%Q{
|
144
|
+
</ol>
|
145
|
+
<div class="glossary-controls">
|
146
|
+
<input type="button" onclick="toggle_constraints()" value="Constraints" class="glossary-toggle-constraint">
|
147
|
+
<input type="button" onclick="toggle_alternates()" value="Alternates" class="glossary-toggle-alternates">
|
148
|
+
<input type="button" onclick="toggle_examples()" value="Examples" class="glossary-toggle-examples">
|
149
|
+
</div>
|
150
|
+
</div>
|
151
|
+
}
|
152
|
+
end
|
153
|
+
|
154
|
+
def object_types_dump_def
|
155
|
+
if @gen_bootstrap
|
156
|
+
'<div class="col-md-5 glossary-doc hide-alternates hide-constraints" id="glossary-doc">' + "\n"
|
157
|
+
else
|
158
|
+
'<div class="glossary-doc hide-alternates hide-constraints" id="glossary-doc">' + "\n"
|
159
|
+
end +
|
160
|
+
'<h1>#{@vocabulary.name}</h1>' + "\n" +
|
161
|
+
"<dl>\n" +
|
162
|
+
@all_object_type.
|
163
|
+
map do |o|
|
164
|
+
case o
|
165
|
+
when ActiveFacts::Metamodel::TypeInheritance
|
166
|
+
nil
|
167
|
+
when ActiveFacts::Metamodel::ValueType
|
168
|
+
value_type_dump(o)
|
169
|
+
else
|
170
|
+
if o.fact_type
|
171
|
+
objectified_fact_type_dump(o)
|
172
|
+
else
|
173
|
+
entity_type_dump(o)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end +
|
177
|
+
"</dl>\n" +
|
178
|
+
"</div>\n"
|
179
|
+
end
|
180
|
+
|
181
|
+
def element(text, attrs, tag = 'span')
|
182
|
+
"<#{tag}#{attrs.empty? ? '' : attrs.map{|k,v| " #{k}='#{v}'"}*''}>#{text}</#{tag}>"
|
183
|
+
end
|
184
|
+
|
185
|
+
def span(text, klass = nil)
|
186
|
+
element(text, klass ? {:class => klass} : {})
|
187
|
+
end
|
188
|
+
|
189
|
+
def div(text, klass = nil)
|
190
|
+
element(text, klass ? {:class => klass} : {}, 'div')
|
191
|
+
end
|
192
|
+
|
193
|
+
def h1(text, klass = nil)
|
194
|
+
element(text, klass ? {:class => klass} : {}, 'h1')
|
195
|
+
end
|
196
|
+
|
197
|
+
def dl(text, klass = nil)
|
198
|
+
element(text, klass ? {:class => klass} : {}, 'dl')
|
199
|
+
end
|
200
|
+
|
201
|
+
# A definition of a term
|
202
|
+
def termdef(name)
|
203
|
+
element(name, {:name => name, :class => 'object_type'}, 'a')
|
204
|
+
end
|
205
|
+
|
206
|
+
# A reference to a defined term (excluding role adjectives)
|
207
|
+
def termref(name, role_name = nil)
|
208
|
+
role_name ||= name
|
209
|
+
element(role_name, {:href=>'#'+name, :class=>:object_type}, 'a')
|
210
|
+
end
|
211
|
+
|
212
|
+
# Text that should appear as part of a term (including role adjectives)
|
213
|
+
def term(name)
|
214
|
+
element(name, :class=>:object_type)
|
215
|
+
end
|
216
|
+
|
217
|
+
def value_type_dump(o, include_alternate = true, include_facts = true, include_constraints = true)
|
218
|
+
return '' if o.all_role.size == 0 or # Skip value types that are only used as supertypes
|
219
|
+
o.name == '_ImplicitBooleanValueType'
|
220
|
+
|
221
|
+
defn_term =
|
222
|
+
' <dt>' +
|
223
|
+
"#{termdef(o.name)} " +
|
224
|
+
(if o.supertype
|
225
|
+
span('is written as ', :keyword) + termref(o.supertype.name)
|
226
|
+
else
|
227
|
+
" (a fundamental data type)"
|
228
|
+
end) +
|
229
|
+
"</dt>\n"
|
230
|
+
|
231
|
+
defn_detail =
|
232
|
+
" <dd>\n" +
|
233
|
+
value_sub_types(o) +
|
234
|
+
relevant_facts_and_constraints(o, include_alternate, include_facts, include_constraints) +
|
235
|
+
(include_facts ? values(o) : '') +
|
236
|
+
" </dd>\n"
|
237
|
+
|
238
|
+
defn_term + defn_detail
|
239
|
+
end
|
240
|
+
|
241
|
+
def value_sub_types(o)
|
242
|
+
o.
|
243
|
+
all_value_type_as_supertype. # All value types for which o is a supertype
|
244
|
+
sort_by{|sub| sub.name}.
|
245
|
+
map do |sub|
|
246
|
+
div(
|
247
|
+
"#{termref(sub.name)} #{span('is written as', 'keyword')} #{termref(o.name)}",
|
248
|
+
'glossary-facttype'
|
249
|
+
)
|
250
|
+
end * "\n" + "\n"
|
251
|
+
end
|
252
|
+
|
253
|
+
def values(o)
|
254
|
+
o.all_instance.
|
255
|
+
sort_by{|i|
|
256
|
+
[i.population.name, i.value.literal]
|
257
|
+
}.
|
258
|
+
map do |i|
|
259
|
+
v = i.value
|
260
|
+
div(
|
261
|
+
(i.population.name.empty? ? '' : i.population.name+': ') +
|
262
|
+
termref(o.name) + ' ' +
|
263
|
+
div(
|
264
|
+
# v.is_literal_string ? v.literal.inspect : v.literal,
|
265
|
+
v.literal.inspect,
|
266
|
+
'value'
|
267
|
+
),
|
268
|
+
'glossary-example'
|
269
|
+
)
|
270
|
+
end * "\n" + "\n"
|
271
|
+
end
|
272
|
+
|
273
|
+
def relevant_facts_and_constraints(o, include_alternate = true, include_facts = true, include_constraints = true)
|
274
|
+
o.
|
275
|
+
all_role.
|
276
|
+
map{|r| r.fact_type}.
|
277
|
+
uniq.
|
278
|
+
reject do |ft| ft.is_a?(ActiveFacts::Metamodel::LinkFactType) end.
|
279
|
+
map { |ft| [ft, " #{fact_type_with_constraints(ft, include_alternate, o, include_constraints)}"] }.
|
280
|
+
sort_by{|ft, text| [ ft.is_a?(ActiveFacts::Metamodel::TypeInheritance) ? 0 : 1, text]}.
|
281
|
+
map{|ft, text| text} * "\n"
|
282
|
+
end
|
283
|
+
|
284
|
+
def role_ref(rr, freq_con, l_adj, name, t_adj, role_name_def, literal)
|
285
|
+
term_parts = [l_adj, termref(name), t_adj].compact
|
286
|
+
[
|
287
|
+
freq_con ? element(freq_con, :class=>:keyword) : nil,
|
288
|
+
term_parts.size > 1 ? term([l_adj, termref(name), t_adj].compact*' ') : term_parts[0],
|
289
|
+
role_name_def,
|
290
|
+
literal
|
291
|
+
]
|
292
|
+
end
|
293
|
+
|
294
|
+
def expand_reading(reading, include_rolenames = true)
|
295
|
+
element(
|
296
|
+
reading.expand([], include_rolenames) do |rr, freq_con, l_adj, name, t_adj, role_name_def, literal|
|
297
|
+
if role_name_def
|
298
|
+
role_name_def = role_name_def.gsub(/\(as ([^)]+)\)/) {
|
299
|
+
span("(as #{ termref(rr.role.object_type.name, $1) })", 'keyword')
|
300
|
+
}
|
301
|
+
end
|
302
|
+
role_ref rr, freq_con, l_adj, name, t_adj, role_name_def, literal
|
303
|
+
end,
|
304
|
+
{:class => 'copula'}
|
305
|
+
)
|
306
|
+
end
|
307
|
+
|
308
|
+
def fact_type_block(ft, include_alternates = true, wrt = nil, include_rolenames = true)
|
309
|
+
div(fact_type(ft, include_alternates, wrt, include_rolenames), 'glossary-facttype')
|
310
|
+
end
|
311
|
+
|
312
|
+
def fact_type(ft, include_alternates = true, wrt = nil, include_rolenames = true)
|
313
|
+
role = ft.all_role.detect{|r| r.object_type == wrt}
|
314
|
+
preferred_reading = ft.reading_preferably_starting_with_role(role)
|
315
|
+
alternate_readings = ft.all_reading.reject{|r| r == preferred_reading}
|
316
|
+
|
317
|
+
div(
|
318
|
+
expand_reading(preferred_reading, include_rolenames),
|
319
|
+
'glossary-reading'
|
320
|
+
)+
|
321
|
+
(if include_alternates and alternate_readings.size > 0
|
322
|
+
div(
|
323
|
+
"(alternatively: " +
|
324
|
+
alternate_readings.map do |reading|
|
325
|
+
div(
|
326
|
+
expand_reading(reading, include_rolenames),
|
327
|
+
'glossary-reading'
|
328
|
+
)
|
329
|
+
end * ",\n" + ')',
|
330
|
+
'glossary-alternates'
|
331
|
+
)
|
332
|
+
else
|
333
|
+
''
|
334
|
+
end
|
335
|
+
)
|
336
|
+
end
|
337
|
+
|
338
|
+
def fact_type_with_constraints(ft, include_alternates = true, wrt = nil, include_constraints = true)
|
339
|
+
if ft.entity_type
|
340
|
+
div(
|
341
|
+
div(termref(ft.entity_type.name) + span(' is where ', 'keyword')) +
|
342
|
+
div(fact_type(ft, include_alternates, wrt)),
|
343
|
+
'glossary-objectification'
|
344
|
+
)
|
345
|
+
else
|
346
|
+
fact_type_block(ft, include_alternates, wrt)
|
347
|
+
end +
|
348
|
+
if include_constraints then
|
349
|
+
%Q{\n<ul class="glossary-constraints">\n} +
|
350
|
+
(unless ft.is_a?(ActiveFacts::Metamodel::TypeInheritance)
|
351
|
+
fact_type_constraints(ft)
|
352
|
+
else
|
353
|
+
''
|
354
|
+
end) +
|
355
|
+
"</ul>"
|
356
|
+
else
|
357
|
+
""
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
def fact_type_constraints(ft)
|
362
|
+
ft.internal_presence_constraints.map do |pc|
|
363
|
+
residual_role = ft.all_role.detect{|r| !pc.role_sequence.all_role_ref.detect{|rr| rr.role == r}}
|
364
|
+
next '' unless residual_role
|
365
|
+
reading = ft.all_reading.detect{|reading|
|
366
|
+
reading.role_sequence.all_role_ref_in_order[reading.role_numbers[-1]].role == residual_role
|
367
|
+
}
|
368
|
+
next '' unless reading
|
369
|
+
div(
|
370
|
+
element(
|
371
|
+
reading.expand_with_final_presence_constraint { |*a| role_ref(*a) },
|
372
|
+
{:class => 'copula'}
|
373
|
+
),
|
374
|
+
'glossary-constraint'
|
375
|
+
) + "\n"
|
376
|
+
end.compact * ''
|
377
|
+
end
|
378
|
+
|
379
|
+
def objectified_fact_type_dump(o, include_alternate = true, include_facts = true, include_constraints = true)
|
380
|
+
defn_term =
|
381
|
+
" <dt>" +
|
382
|
+
"#{termdef(o.name)}" +
|
383
|
+
# " (#{span('in which', 'keyword')} #{fact_type(o.fact_type, false, nil, nil)})" +
|
384
|
+
"</dt>\n"
|
385
|
+
# REVISIT: Handle separate identification
|
386
|
+
|
387
|
+
defn_detail =
|
388
|
+
" <dd>\n" +
|
389
|
+
fact_type_with_constraints(o.fact_type, include_alternate, nil, include_constraints) + "\n" +
|
390
|
+
|
391
|
+
o.fact_type.all_role_in_order.map do |r|
|
392
|
+
n = r.object_type.name
|
393
|
+
div("#{termref(o.name)} involves #{span('one', 'keyword')} #{termref(r.role_name || n, n)}", "glossary-facttype")
|
394
|
+
end * "\n" + "\n" +
|
395
|
+
relevant_facts_and_constraints(o, include_alternate, include_facts, include_constraints) + "\n" +
|
396
|
+
" </dd>"
|
397
|
+
|
398
|
+
defn_term + defn_detail
|
399
|
+
end
|
400
|
+
|
401
|
+
def entity_type_dump(o, include_alternate = true, include_facts = true, include_constraints = true)
|
402
|
+
pi = o.preferred_identifier
|
403
|
+
supers = o.supertypes
|
404
|
+
if (supers.size > 0) # Ignore identification by a supertype:
|
405
|
+
pi = nil if pi && pi.role_sequence.all_role_ref.detect{|rr| rr.role.fact_type.is_a?(ActiveFacts::Metamodel::TypeInheritance) }
|
406
|
+
end
|
407
|
+
|
408
|
+
defn_term =
|
409
|
+
" <dt>" +
|
410
|
+
"#{termdef(o.name)} " +
|
411
|
+
"</dt>\n"
|
412
|
+
|
413
|
+
defn_detail =
|
414
|
+
" <dd>" +
|
415
|
+
(supers.size > 0 ? "#{span('is a kind of', 'keyword')} #{supers.map{|s| termref(s.name)}*', '}\n" : '') +
|
416
|
+
if pi
|
417
|
+
"#{span('is identified by', 'keyword')} " +
|
418
|
+
pi.role_sequence.all_role_ref_in_order.map do |rr|
|
419
|
+
termref(
|
420
|
+
rr.role.object_type.name,
|
421
|
+
[ rr.leading_adjective,
|
422
|
+
rr.role.role_name || rr.role.object_type.name,
|
423
|
+
rr.trailing_adjective
|
424
|
+
].compact * '-'
|
425
|
+
)
|
426
|
+
end * ", " + "\n"
|
427
|
+
else
|
428
|
+
''
|
429
|
+
end +
|
430
|
+
relevant_facts_and_constraints(o, include_alternate, include_facts, include_constraints) +
|
431
|
+
(include_facts ? entities(o) : '') +
|
432
|
+
" </dd>\n"
|
433
|
+
|
434
|
+
defn_term + defn_detail
|
435
|
+
end
|
436
|
+
|
437
|
+
def entities(o)
|
438
|
+
return '' if o.preferred_identifier.role_sequence.all_role_ref.size > 1 # REVISIT: Composite identification
|
439
|
+
o.all_instance.map do |i|
|
440
|
+
v = i.value
|
441
|
+
ii = i # The identifying instance
|
442
|
+
|
443
|
+
until v
|
444
|
+
pi = ii.object_type.preferred_identifier # ii is an Entity Type
|
445
|
+
break if pi.role_sequence.all_role_ref.size > 1 # REVISIT: Composite identification
|
446
|
+
|
447
|
+
identifying_fact_type = pi.role_sequence.all_role_ref.single.role.fact_type
|
448
|
+
# Find the role played by this instance through which it is identified:
|
449
|
+
irv = i.all_role_value.detect{|rv| rv.fact.fact_type == identifying_fact_type }
|
450
|
+
# Get the other RoleValue in what must be a binary fact type:
|
451
|
+
orv = irv.fact.all_role_value.detect{|rv| rv != irv}
|
452
|
+
ii = orv.instance
|
453
|
+
v = ii.value # Does this instance have a value? If so, we're done.
|
454
|
+
end
|
455
|
+
|
456
|
+
next unless v
|
457
|
+
div(
|
458
|
+
(i.population.name.empty? ? '' : i.population.name+': ') +
|
459
|
+
termref(o.name) + ' ' +
|
460
|
+
div(
|
461
|
+
# v.is_literal_string ? v.literal.inspect : v.literal,
|
462
|
+
v.literal.inspect,
|
463
|
+
'value'
|
464
|
+
),
|
465
|
+
'glossary-example'
|
466
|
+
)
|
467
|
+
end * "\n" + "\n"
|
468
|
+
end
|
469
|
+
end
|
470
|
+
end
|
471
|
+
end
|
472
|
+
end
|
473
|
+
|