activefacts-compositions 1.9.6 → 1.9.8

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