activefacts-compositions 1.9.22 → 1.9.23

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.
Files changed (35) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +1 -1
  3. data/activefacts-compositions.gemspec +1 -1
  4. data/bin/schema_compositor +71 -27
  5. data/lib/activefacts/compositions/binary.rb +4 -0
  6. data/lib/activefacts/compositions/datavault.rb +4 -0
  7. data/lib/activefacts/compositions/relational.rb +4 -0
  8. data/lib/activefacts/compositions/staging.rb +4 -0
  9. data/lib/activefacts/compositions/version.rb +1 -1
  10. data/lib/activefacts/generator/doc/css/glossary-print.css +72 -0
  11. data/lib/activefacts/generator/doc/css/glossary.css +194 -0
  12. data/lib/activefacts/generator/doc/css/ldm.css +12 -17
  13. data/lib/activefacts/generator/doc/css/orm2-print.css +19 -0
  14. data/lib/activefacts/generator/doc/css/orm2.css +28 -0
  15. data/lib/activefacts/generator/doc/css/reset.css +18 -0
  16. data/lib/activefacts/generator/doc/css/treetable.css +83 -0
  17. data/lib/activefacts/generator/doc/cwm.rb +60 -54
  18. data/lib/activefacts/generator/doc/glossary.rb +261 -137
  19. data/lib/activefacts/generator/doc/graphviz.rb +6 -2
  20. data/lib/activefacts/generator/doc/ldm.rb +7 -3
  21. data/lib/activefacts/generator/etl/unidex.rb +7 -2
  22. data/lib/activefacts/generator/oo.rb +2 -1
  23. data/lib/activefacts/generator/population.rb +174 -0
  24. data/lib/activefacts/generator/rails/active_admin.rb +81 -0
  25. data/lib/activefacts/generator/rails/application_record_shell.rb +78 -0
  26. data/lib/activefacts/generator/rails/models.rb +31 -72
  27. data/lib/activefacts/generator/rails/ruby_folder_generator.rb +87 -0
  28. data/lib/activefacts/generator/rails/schema.rb +12 -4
  29. data/lib/activefacts/generator/ruby.rb +7 -3
  30. data/lib/activefacts/generator/sql.rb +2 -1
  31. data/lib/activefacts/generator/summary.rb +24 -19
  32. data/lib/activefacts/generator/traits/sql.rb +4 -0
  33. data/lib/activefacts/generator/transgen.rb +7 -1
  34. data/lib/activefacts/generator/validate.rb +10 -2
  35. metadata +15 -5
@@ -13,19 +13,24 @@ module ActiveFacts
13
13
  module Generators #:nodoc:
14
14
  module Doc
15
15
  class Glossary #:nodoc:
16
+ MM = ActiveFacts::Metamodel
17
+
16
18
  # Options are comma or space separated:
17
- # * gen_bootstrap Generate bootstrap styled glossary html
18
19
  def self.options
19
20
  {
20
- gen_bootstrap: ['Boolean', "Generate bootstrap styled glossary html"],
21
21
  }
22
22
  end
23
-
23
+
24
+ def self.compatibility
25
+ [0, nil] # no composition is required
26
+ end
27
+
24
28
  # Base class for generators of object-oriented class libraries for an ActiveFacts vocabulary.
25
- def initialize vocabulary, options = {}
26
- @vocabulary = vocabulary # REVISIT: This should be a Composition here
29
+ def initialize constellation, composition, options = {}
30
+ @constellation = constellation
31
+ @compositions = Array(composition)
32
+ @vocabulary = constellation.Vocabulary.values[0]
27
33
  @options = options
28
- @gen_bootstrap = options.has_key?("gen_bootstrap")
29
34
  end
30
35
 
31
36
  def puts(*a)
@@ -41,121 +46,103 @@ module ActiveFacts
41
46
  @vocabulary.
42
47
  all_object_type.
43
48
  sort_by{|o| o.name.gsub(/ /,'').downcase}
44
-
49
+
50
+ "<html><head>" +
45
51
  glossary_start +
52
+ "</head><body>" +
46
53
  glossary_body +
47
- glossary_end
54
+ glossary_end +
55
+ "</body>"
48
56
  end
49
57
 
50
58
  def glossary_start
51
- if !@gen_bootstrap
52
- # puts "<link rel='stylesheet' href='css/orm2.css' media='screen' type='text/css'/>"
53
- css_file = "css/orm2.css"
54
-
55
- File.open(File.dirname(__FILE__)+css_file) do |f|
56
- "<style media='screen' type='text/css'>\n" +
57
- f.read +
58
- %Q{
59
- .glossary-facttype, .glossary-constraints { display: block; }\n
60
- .glossary-doc.hide-alternates .glossary-alternates { display: none; }\n
61
- .glossary-doc.hide-constraints .glossary-constraints { display: none; }\n
62
- .glossary-doc.hide-examples .glossary-example { display: none; }\n
63
- }.gsub(/^\s+/, '') +
64
- "</style>\n"
65
- end +
66
-
67
- %Q{
68
- <style media='print' type='text/css'>\n
69
- .keyword { color: #0000CC; font-style: italic; display: inline; }\n
70
- .vocabulary, .object_type { color: #8A0092; font-weight: bold; }\n
71
- .copula { color: #0E5400; }\n
72
- .value { color: #FF990E; display: inline; }\n
73
- .glossary-toc { display: none; }\n
74
- .glossary-facttype, .glossary-reading { display: inline; }\n
75
- </style>\n
76
- }.gsub(/^\s+/, '')
77
- else
78
- ''
79
- end
59
+ # Inline the following CSS files:
60
+ {
61
+ all: ["reset.css", "treetable.css"],
62
+ screen: ["orm2.css", "glossary.css"],
63
+ print: ["orm2-print.css", "glossary-print.css"]
64
+ }.
65
+ flat_map do |media, css_files|
66
+ css_files.map do |css_file|
67
+ File.open(filename = File.dirname(__FILE__)+"/css/"+css_file) do |f|
68
+ "<!-- #{css_file} -->\n"+
69
+ "<style media='#{media}' type='text/css'>\n#{f.read}</style>\n"
70
+ end
71
+ end
72
+ end*''.gsub(/^\s+/, '')
80
73
  end
81
74
 
82
75
  def glossary_body
83
- if @gen_bootstrap
84
- object_types_dump_toc()
85
- object_types_dump_def()
86
- else
87
- object_types_dump_def()
88
- object_types_dump_toc()
89
- end
76
+ div(
77
+ object_types_dump_toc +
78
+ object_types_dump_def +
79
+ dump_compositions +
80
+ controls,
81
+ 'glossary'
82
+ )
90
83
  end
91
-
84
+
92
85
  def glossary_end
93
- if !@gen_bootstrap
94
- %Q{
95
- <script type="text/javascript">
96
- function toggle_class(e, c) {
97
- if (!e) return;
98
- var n = e.className;
99
- var i = n.indexOf(c);
100
- if (i == -1) {
101
- e.className = n+' '+c;
102
- } else {
103
- e.className = n.slice(0, i)+n.slice(i+c.length);
104
- }
105
- if (document.location.toString().indexOf('#') >= 0)
106
- document.location = document.location; // Re-scroll to the current fragment
107
- }
108
- function toggle_constraints() {
109
- toggle_class(document.getElementById('glossary-doc'), 'hide-constraints');
110
- }
111
- function toggle_alternates() {
112
- toggle_class(document.getElementById('glossary-doc'), 'hide-alternates');
113
- }
114
- function toggle_examples() {
115
- toggle_class(document.getElementById('glossary-doc'), 'hide-examples');
116
- }
117
- </script>
118
- }.gsub(/^\s+/, '')
119
- else
120
- ''
121
- end
86
+ %Q{
87
+ <script type="text/javascript">
88
+ function toggle_class(e, c) {
89
+ if (!e) return;
90
+ var n = e.className;
91
+ var i = n.indexOf(c);
92
+ if (i == -1) {
93
+ e.className = n+' '+c;
94
+ } else {
95
+ e.className = n.slice(0, i)+n.slice(i+c.length);
96
+ }
97
+ if (document.location.toString().indexOf('#') >= 0)
98
+ document.location = document.location; // Re-scroll to the current fragment
99
+ }
100
+ function toggle_facts() {
101
+ toggle_class(document.getElementById('glossary-doc'), 'hide-facts');
102
+ }
103
+ function toggle_constraints() {
104
+ toggle_class(document.getElementById('glossary-doc'), 'hide-constraints');
105
+ }
106
+ function toggle_alternates() {
107
+ toggle_class(document.getElementById('glossary-doc'), 'hide-alternates');
108
+ }
109
+ function toggle_examples() {
110
+ toggle_class(document.getElementById('glossary-doc'), 'hide-examples');
111
+ }
112
+ </script>
113
+ }.gsub(/^\s+/, '')
122
114
  end
123
115
 
124
116
  def object_types_dump_toc
125
- if @gen_bootstrap
126
- '<div class="col-md-3 glossary-sidebar">' + "\n"
127
- else
128
- '<div class="glossary-sidebar">' + "\n"
129
- end +
130
- '<h1 style="visibility: hidden">X</h1>' +"\n" +
131
- '<ol class="glossary-toc">' + "\n"
117
+ %Q{\n<div class="glossary-toc#{@compositions.size > 0 ? ' glossary-is-toc' : ' glossary-toc-right'}">\n} +
118
+ # Don't show schema name here '<h1 style="visibility: hidden">X</h1>' +"\n" +
119
+ '<ol class="glossary-toc-list">' + "\n" +
132
120
  @all_object_type.
133
- reject do |o|
134
- o.name == '_ImplicitBooleanValueType' or
135
- o.kind_of?(ActiveFacts::Metamodel::ValueType) && o.all_role.size == 0 or
136
- o.kind_of?(ActiveFacts::Metamodel::TypeInheritance)
137
- end.
138
- map do |o|
139
- "<li>#{termref(o.name)}</li>"
140
- end *"\n" + "\n" +
121
+ reject do |o|
122
+ o.name == '_ImplicitBooleanValueType' or
123
+ o.kind_of?(ActiveFacts::Metamodel::ValueType) && o.all_role.size == 0 or
124
+ o.kind_of?(ActiveFacts::Metamodel::TypeInheritance)
125
+ end.
126
+ map do |o|
127
+ "<li>#{termref(o.name)}</li>"
128
+ end*"\n" + "\n</div>\n\n"
129
+ end
130
+
131
+ def controls
141
132
  %Q{
142
133
  </ol>
143
134
  <div class="glossary-controls">
135
+ <input type="button" onclick="toggle_facts()" value="Facts" class="glossary-toggle-facts">
144
136
  <input type="button" onclick="toggle_constraints()" value="Constraints" class="glossary-toggle-constraint">
145
137
  <input type="button" onclick="toggle_alternates()" value="Alternates" class="glossary-toggle-alternates">
146
138
  <input type="button" onclick="toggle_examples()" value="Examples" class="glossary-toggle-examples">
147
139
  </div>
148
- </div>
149
140
  }
150
141
  end
151
-
142
+
152
143
  def object_types_dump_def
153
- if @gen_bootstrap
154
- '<div class="col-md-5 glossary-doc hide-alternates hide-constraints" id="glossary-doc">' + "\n"
155
- else
156
- '<div class="glossary-doc hide-alternates hide-constraints" id="glossary-doc">' + "\n"
157
- end +
158
- '<h1>#{@vocabulary.name}</h1>' + "\n" +
144
+ %Q{<div class="glossary-doc #{@compositions.size > 0 ? 'glossary-is-toc' : 'glossary-toc-right'} hide-facts hide-alternates hide-constraints" id="glossary-doc">} + "\n" +
145
+ "<h1>#{@vocabulary.name}</h1>\n" +
159
146
  "<dl>\n" +
160
147
  @all_object_type.
161
148
  map do |o|
@@ -171,11 +158,146 @@ module ActiveFacts
171
158
  entity_type_dump(o)
172
159
  end
173
160
  end
174
- end +
161
+ end*"\n" +
175
162
  "</dl>\n" +
176
163
  "</div>\n"
177
164
  end
178
165
 
166
+ # Each component has
167
+ # * a span for the title,
168
+ # * a tt-type if it's a value type
169
+ # * an tt-desc if it has an associated fact type
170
+ # * child nodes
171
+ def component c, klass = ''
172
+ name = c.name
173
+ title = span(name, 'term'+(c.is_mandatory ? ' mandatory' : ''))
174
+ desc = ''
175
+ type = ''
176
+
177
+ case c
178
+ when MM::Indicator
179
+ ft = c.role.fact_type
180
+ desc = div(div(expand_reading(ft.preferred_reading, false), 'glossary-reading'), 'tt-desc')
181
+ type = div(div('boolean', 'term'), 'tt-type')
182
+
183
+ when MM::Discriminator,
184
+ MM::Injection,
185
+ MM::ComputedValue,
186
+ MM::HashValue,
187
+ MM::SurrogateKey,
188
+ # MM::Scoping,
189
+ MM::ValidFrom # This should be an Injection
190
+ p c
191
+ debugger
192
+ print ''
193
+ # REVISIT
194
+
195
+ when MM::Absorption
196
+ ft = c.parent_role.fact_type
197
+ preferred_reading = ft.reading_preferably_starting_with_role(c.parent_role)
198
+ desc = div(div(expand_reading(preferred_reading, false), 'glossary-reading'), 'tt-desc')
199
+ if MM::ValueType === c.object_type
200
+ name = c.column_name*''
201
+ title = span(name, 'term'+(c.is_mandatory ? ' mandatory' : ''))
202
+ type = div(div(c.child_role.object_type.name, 'term'), 'tt-type')
203
+ #elsif c.all_member.size == 0
204
+ # title = span(name, 'term'+(c.is_mandatory ? ' mandatory' : ''))
205
+ #else
206
+ title = span(name, 'term'+(c.is_mandatory ? ' mandatory' : ''))
207
+ end
208
+ if MM::TypeInheritance === ft
209
+ title = "as a "+title
210
+ elsif c.full_absorption
211
+ title = "fully absorbing "+title
212
+ end
213
+ if to = (c.foreign_key && c.foreign_key.composite) or
214
+ (
215
+ composite_mappings = c.object_type.all_mapping.select{|m| m.composite} and
216
+ composite_mappings.size == 1 and # In a binary mapping, there aren't any ForeignKeys
217
+ to = composite_mappings[0].composite
218
+ )
219
+ title = element(title, {href: '#'+composite_anchor(to)}, 'a')
220
+ end
221
+ klass = klass+' tt-list' unless c.parent_role.is_unique
222
+
223
+ # when MM::ValueField ... Mapping works here
224
+ when MM::Mapping # A mapping that's not an absorption; usually a Composite
225
+ if MM::EntityType === (o = c.object_type)
226
+ if o.fact_type
227
+ objectified_reading = o.fact_type.preferred_reading
228
+ desc = div(
229
+ span('is where ', :keyword) + expand_reading(objectified_reading, false),
230
+ 'tt-desc'
231
+ )
232
+ else
233
+ desc = div(
234
+ span('is identified by ', :keyword) +
235
+ o.preferred_identifier_roles.map{|r| span(r.role_name || r.name, 'term') }*', ',
236
+ 'tt-desc'
237
+ )
238
+ end
239
+ else
240
+ desc = div('', 'tt-desc')
241
+ end
242
+
243
+ when MM::Indicator
244
+ desc = div(
245
+ expand_reading(c.role.fact_type.preferred_reading, false),
246
+ 'tt-desc'
247
+ )
248
+ else
249
+ # Add other special cases here
250
+ desc = div('', 'tt-desc')
251
+ end
252
+
253
+
254
+ div(
255
+ title +
256
+ type +
257
+ desc +
258
+ c.
259
+ all_member.
260
+ sort_by{|m| m.ordinal}.
261
+ map do |member|
262
+ component(member)
263
+ end*'',
264
+ 'tt-node'+klass
265
+ )+"\n"
266
+ end
267
+
268
+ def dump_compositions
269
+ return '' if @compositions.empty?
270
+
271
+ element(
272
+ @compositions.map do |c|
273
+ "\n"+
274
+ element(
275
+ element(element(c.compositor_name + ' Composition', {href: "#{'#'}#{c.compositor_name}-composition"}, 'a'), {}, 'h2') + "\n" +
276
+ element(dump_composition(c), {}, 'div'),
277
+ {id: "#{c.compositor_name}-composition"},
278
+ 'section'
279
+ )
280
+ end*'',
281
+ {class: 'tabs glossary-compositions'},
282
+ 'article'
283
+ )
284
+ end
285
+
286
+ def composite_anchor composite
287
+ "#{composite.composition.compositor_name}_#{composite.mapping.name.words.titlecase}"
288
+ end
289
+
290
+ def dump_composition c
291
+ c.all_composite_by_name.map do |composite|
292
+ composite.mapping.re_rank
293
+ element(
294
+ component(composite.mapping, ' tt-outer'),
295
+ {name: composite_anchor(composite)},
296
+ 'a'
297
+ )
298
+ end*"&nbsp;\n"
299
+ end
300
+
179
301
  def element(text, attrs, tag = 'span')
180
302
  "<#{tag}#{attrs.empty? ? '' : attrs.map{|k,v| " #{k}='#{v}'"}*''}>#{text}</#{tag}>"
181
303
  end
@@ -198,31 +320,31 @@ module ActiveFacts
198
320
 
199
321
  # A definition of a term
200
322
  def termdef(name)
201
- element(name, {:name => name, :class => 'object_type'}, 'a')
323
+ element(name, {:name => name, :class=>:term}, 'a')
202
324
  end
203
325
 
204
326
  # A reference to a defined term (excluding role adjectives)
205
327
  def termref(name, role_name = nil)
206
328
  role_name ||= name
207
- element(role_name, {:href=>'#'+name, :class=>:object_type}, 'a')
329
+ element(role_name, {:href=>'#'+name, :class=>:term}, 'a')
208
330
  end
209
331
 
210
332
  # Text that should appear as part of a term (including role adjectives)
211
333
  def term(name)
212
- element(name, :class=>:object_type)
334
+ element(name, :class=>:term)
213
335
  end
214
336
 
215
337
  def value_type_dump(o, include_alternate = true, include_facts = true, include_constraints = true)
216
338
  return '' if o.all_role.size == 0 or # Skip value types that are only used as supertypes
217
339
  o.name == '_ImplicitBooleanValueType'
218
-
340
+
219
341
  defn_term =
220
342
  ' <dt>' +
221
343
  "#{termdef(o.name)} " +
222
344
  (if o.supertype
223
345
  span('is written as ', :keyword) + termref(o.supertype.name)
224
346
  else
225
- " (a fundamental data type)"
347
+ " (fundamental)"
226
348
  end) +
227
349
  "</dt>\n"
228
350
 
@@ -232,7 +354,7 @@ module ActiveFacts
232
354
  relevant_facts_and_constraints(o, include_alternate, include_facts, include_constraints) +
233
355
  (include_facts ? values(o) : '') +
234
356
  " </dd>\n"
235
-
357
+
236
358
  defn_term + defn_detail
237
359
  end
238
360
 
@@ -269,14 +391,14 @@ module ActiveFacts
269
391
  end
270
392
 
271
393
  def relevant_facts_and_constraints(o, include_alternate = true, include_facts = true, include_constraints = true)
272
- o.
273
- all_role.
274
- map{|r| r.fact_type}.
275
- uniq.
276
- reject do |ft| ft.is_a?(ActiveFacts::Metamodel::LinkFactType) end.
277
- map { |ft| [ft, " #{fact_type_with_constraints(ft, include_alternate, o, include_constraints)}"] }.
278
- sort_by{|ft, text| [ ft.is_a?(ActiveFacts::Metamodel::TypeInheritance) ? 0 : 1, text]}.
279
- map{|ft, text| text} * "\n"
394
+ o.
395
+ all_role.
396
+ map{|r| r.fact_type}.
397
+ uniq.
398
+ reject do |ft| ft.is_a?(ActiveFacts::Metamodel::LinkFactType) end.
399
+ map { |ft| [ft, " #{fact_type_with_constraints(ft, include_alternate, o, include_constraints)}"] }.
400
+ sort_by{|ft, text| [ ft.is_a?(ActiveFacts::Metamodel::TypeInheritance) ? 0 : 1, text]}.
401
+ map{|ft, text| text} * "\n"
280
402
  end
281
403
 
282
404
  def role_ref(rr, freq_con, l_adj, name, t_adj, role_name_def, literal)
@@ -299,7 +421,7 @@ module ActiveFacts
299
421
  end
300
422
  role_ref rr, freq_con, l_adj, name, t_adj, role_name_def, literal
301
423
  end,
302
- {:class => 'copula'}
424
+ {:class => 'reading'}
303
425
  )
304
426
  end
305
427
 
@@ -336,8 +458,9 @@ module ActiveFacts
336
458
  def fact_type_with_constraints(ft, include_alternates = true, wrt = nil, include_constraints = true)
337
459
  if ft.entity_type
338
460
  div(
339
- div(termref(ft.entity_type.name) + span(' is where ', 'keyword')) +
340
- div(fact_type(ft, include_alternates, wrt)),
461
+ (ft.entity_type == wrt ? '' : termref(ft.entity_type.name)) +
462
+ span(' is where ', 'keyword') +
463
+ fact_type(ft, include_alternates, wrt),
341
464
  'glossary-objectification'
342
465
  )
343
466
  else
@@ -367,7 +490,7 @@ module ActiveFacts
367
490
  div(
368
491
  element(
369
492
  reading.expand_with_final_presence_constraint { |*a| role_ref(*a) },
370
- {:class => 'copula'}
493
+ {:class => 'reading'}
371
494
  ),
372
495
  'glossary-constraint'
373
496
  ) + "\n"
@@ -378,20 +501,21 @@ module ActiveFacts
378
501
  defn_term =
379
502
  " <dt>" +
380
503
  "#{termdef(o.name)}" +
381
- # " (#{span('in which', 'keyword')} #{fact_type(o.fact_type, false, nil, nil)})" +
504
+ " (objectification#{o.supertypes.size > 0 ? ', subtype' : ''})" +
505
+ # Don't display OFT inline " (#{span('in which', 'keyword')} #{fact_type(o.fact_type, false, o, nil)})" +
382
506
  "</dt>\n"
383
507
  # REVISIT: Handle separate identification
384
508
 
385
509
  defn_detail =
386
- " <dd>\n" +
387
- fact_type_with_constraints(o.fact_type, include_alternate, nil, include_constraints) + "\n" +
510
+ " <dd>" +
511
+ fact_type_with_constraints(o.fact_type, include_alternate, o, include_constraints) + "\n" +
388
512
 
389
513
  o.fact_type.all_role_in_order.map do |r|
390
514
  n = r.object_type.name
391
515
  div("#{termref(o.name)} involves #{span('one', 'keyword')} #{termref(r.role_name || n, n)}", "glossary-facttype")
392
516
  end * "\n" + "\n" +
393
517
  relevant_facts_and_constraints(o, include_alternate, include_facts, include_constraints) + "\n" +
394
- " </dd>"
518
+ " </dd>\n"
395
519
 
396
520
  defn_term + defn_detail
397
521
  end
@@ -399,36 +523,35 @@ module ActiveFacts
399
523
  def entity_type_dump(o, include_alternate = true, include_facts = true, include_constraints = true)
400
524
  pi = o.preferred_identifier
401
525
  supers = o.supertypes
402
- if (supers.size > 0) # Ignore identification by a supertype:
403
- pi = nil if pi && pi.role_sequence.all_role_ref.detect{|rr| rr.role.fact_type.is_a?(ActiveFacts::Metamodel::TypeInheritance) }
404
- end
526
+ pi = nil if pi && o.identifying_supertype
405
527
 
406
528
  defn_term =
407
529
  " <dt>" +
408
530
  "#{termdef(o.name)} " +
409
- "</dt>\n"
410
-
411
- defn_detail =
412
- " <dd>" +
413
- (supers.size > 0 ? "#{span('is a kind of', 'keyword')} #{supers.map{|s| termref(s.name)}*', '}\n" : '') +
414
531
  if pi
415
532
  "#{span('is identified by', 'keyword')} " +
416
533
  pi.role_sequence.all_role_ref_in_order.map do |rr|
417
- termref(
418
- rr.role.object_type.name,
419
- [ rr.leading_adjective,
420
- rr.role.role_name || rr.role.object_type.name,
421
- rr.trailing_adjective
422
- ].compact * '-'
534
+ preferred_reading = rr.role.fact_type.preferred_reading
535
+ preferred_role_ref = preferred_reading.role_sequence.all_role_ref.detect{|rrp| rrp.role == rr.role}
536
+ term(
537
+ [ preferred_role_ref.leading_adjective,
538
+ termref(rr.role.object_type.name, preferred_role_ref.role.role_name),
539
+ preferred_role_ref.trailing_adjective
540
+ ].compact*'-'
423
541
  )
424
542
  end * ", " + "\n"
425
543
  else
426
- ''
544
+ ' (subtype)'
427
545
  end +
546
+ "</dt>\n"
547
+
548
+ defn_detail =
549
+ " <dd>\n" +
550
+ (supers.size > 0 ? "#{span('is a kind of', 'keyword')} #{supers.map{|s| termref(s.name)}*', '}\n" : '') +
428
551
  relevant_facts_and_constraints(o, include_alternate, include_facts, include_constraints) +
429
552
  (include_facts ? entities(o) : '') +
430
- " </dd>\n"
431
-
553
+ "\n </dd>"
554
+
432
555
  defn_term + defn_detail
433
556
  end
434
557
 
@@ -466,6 +589,7 @@ module ActiveFacts
466
589
  end
467
590
  end
468
591
  end
592
+ publish_generator Doc::Glossary, "Glossary generator"
469
593
  end
470
594
  end
471
595