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