#
# ActiveFacts Generators.
#
# Generate a glossary in HTML
#
# Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
#
require 'activefacts/metamodel'
require 'activefacts/compositions'
require 'activefacts/generator'
module ActiveFacts
module Generators #:nodoc:
module Doc
class Glossary #:nodoc:
MM = ActiveFacts::Metamodel
# Options are comma or space separated:
def self.options
{
}
end
def self.compatibility
[0, nil] # no composition is required
end
# Base class for generators of object-oriented class libraries for an ActiveFacts vocabulary.
def initialize constellation, composition, options = {}
@constellation = constellation
@compositions = Array(composition)
@vocabulary = constellation.Vocabulary.values[0]
@options = options
end
def puts(*a)
@out.puts *a
end
def print(*a)
@out.print *a
end
def generate
@all_object_type =
@vocabulary.
all_object_type.
sort_by{|o| o.name.gsub(/ /,'').downcase}
"
" +
glossary_start +
"" +
glossary_body +
glossary_end +
""
end
def glossary_start
# Inline the following CSS files:
{
all: ["reset.css", "treetable.css"],
screen: ["orm2.css", "glossary.css"],
print: ["orm2-print.css", "glossary-print.css"]
}.
flat_map do |media, css_files|
css_files.map do |css_file|
File.open(filename = File.dirname(__FILE__)+"/css/"+css_file) do |f|
"\n"+
"\n"
end
end
end*''.gsub(/^\s+/, '')
end
def glossary_body
div(
object_types_dump_toc +
object_types_dump_def +
dump_compositions +
controls,
'glossary'
)
end
def glossary_end
%Q{
}.gsub(/^\s+/, '')
end
def object_types_dump_toc
%Q{\n
\n} +
# Don't show schema name here '
X
' +"\n" +
'' + "\n" +
@all_object_type.
reject do |o|
o.name == '_ImplicitBooleanValueType' or
o.kind_of?(ActiveFacts::Metamodel::ValueType) && o.all_role.size == 0 or
o.kind_of?(ActiveFacts::Metamodel::TypeInheritance)
end.
map do |o|
"
#{termref(o.name)}
"
end*"\n" + "\n
\n\n"
end
def controls
%Q{
}
end
def object_types_dump_def
%Q{
} + "\n" +
"
#{@vocabulary.name}
\n" +
"
\n" +
@all_object_type.
map do |o|
case o
when ActiveFacts::Metamodel::TypeInheritance
nil
when ActiveFacts::Metamodel::ValueType
value_type_dump(o)
else
if o.fact_type
objectified_fact_type_dump(o)
else
entity_type_dump(o)
end
end
end*"\n" +
"
\n" +
"
\n"
end
# Each component has
# * a span for the title,
# * a tt-type if it's a value type
# * an tt-desc if it has an associated fact type
# * child nodes
def component c, klass = ''
name = c.name
title = span(name, 'term'+(c.is_mandatory ? ' mandatory' : ''))
desc = ''
type = ''
case c
when MM::Indicator
ft = c.role.fact_type
desc = div(div(expand_reading(ft.preferred_reading, false), 'glossary-reading'), 'tt-desc')
type = div(div('boolean', 'term'), 'tt-type')
when MM::Discriminator,
MM::Injection,
MM::ComputedValue,
MM::HashValue,
MM::SurrogateKey,
# MM::Scoping,
MM::ValidFrom # This should be an Injection
p c
debugger
print ''
# REVISIT
when MM::Absorption
ft = c.parent_role.fact_type
preferred_reading = ft.reading_preferably_starting_with_role(c.parent_role)
desc = div(div(expand_reading(preferred_reading, false), 'glossary-reading'), 'tt-desc')
if MM::ValueType === c.object_type
name = c.column_name*''
title = span(name, 'term'+(c.is_mandatory ? ' mandatory' : ''))
type = div(div(c.child_role.object_type.name, 'term'), 'tt-type')
#elsif c.all_member.size == 0
# title = span(name, 'term'+(c.is_mandatory ? ' mandatory' : ''))
#else
title = span(name, 'term'+(c.is_mandatory ? ' mandatory' : ''))
end
if MM::TypeInheritance === ft
title = "as a "+title
elsif c.full_absorption
title = "fully absorbing "+title
end
if to = (c.foreign_key && c.foreign_key.composite) or
(
composite_mappings = c.object_type.all_mapping.select{|m| m.composite} and
composite_mappings.size == 1 and # In a binary mapping, there aren't any ForeignKeys
to = composite_mappings[0].composite
)
title = element(title, {href: '#'+composite_anchor(to)}, 'a')
end
klass = klass+' tt-list' unless c.parent_role.is_unique
# when MM::ValueField ... Mapping works here
when MM::Mapping # A mapping that's not an absorption; usually a Composite
if MM::EntityType === (o = c.object_type)
if o.fact_type
objectified_reading = o.fact_type.preferred_reading
desc = div(
span('is where ', :keyword) + expand_reading(objectified_reading, false),
'tt-desc'
)
else
desc = div(
span('is identified by ', :keyword) +
o.preferred_identifier_roles.map{|r| span(r.role_name || r.name, 'term') }*', ',
'tt-desc'
)
end
else
desc = div('', 'tt-desc')
end
when MM::Indicator
desc = div(
expand_reading(c.role.fact_type.preferred_reading, false),
'tt-desc'
)
else
# Add other special cases here
desc = div('', 'tt-desc')
end
div(
title +
type +
desc +
c.
all_member.
sort_by{|m| m.ordinal}.
map do |member|
component(member)
end*'',
'tt-node'+klass
)+"\n"
end
def dump_compositions
return '' if @compositions.empty?
element(
@compositions.map do |c|
"\n"+
element(
element(element(c.compositor_name + ' Composition', {href: "#{'#'}#{c.compositor_name}-composition"}, 'a'), {}, 'h2') + "\n" +
element(dump_composition(c), {}, 'div'),
{id: "#{c.compositor_name}-composition"},
'section'
)
end*'',
{class: 'tabs glossary-compositions'},
'article'
)
end
def composite_anchor composite
"#{composite.composition.compositor_name}_#{composite.mapping.name.words.titlecase}"
end
def dump_composition c
c.all_composite_by_name.map do |composite|
composite.mapping.re_rank
element(
component(composite.mapping, ' tt-outer'),
{name: composite_anchor(composite)},
'a'
)
end*" \n"
end
def element(text, attrs, tag = 'span')
"<#{tag}#{attrs.empty? ? '' : attrs.map{|k,v| " #{k}='#{v}'"}*''}>#{text}#{tag}>"
end
def span(text, klass = nil)
element(text, klass ? {:class => klass} : {})
end
def div(text, klass = nil)
element(text, klass ? {:class => klass} : {}, 'div')
end
def h1(text, klass = nil)
element(text, klass ? {:class => klass} : {}, 'h1')
end
def dl(text, klass = nil)
element(text, klass ? {:class => klass} : {}, 'dl')
end
# A definition of a term
def termdef(name)
element(name, {:name => name, :class=>:term}, 'a')
end
# A reference to a defined term (excluding role adjectives)
def termref(name, role_name = nil)
role_name ||= name
element(role_name, {:href=>'#'+name, :class=>:term}, 'a')
end
# Text that should appear as part of a term (including role adjectives)
def term(name)
element(name, :class=>:term)
end
def value_type_dump(o, include_alternate = true, include_facts = true, include_constraints = true)
return '' if o.all_role.size == 0 or # Skip value types that are only used as supertypes
o.name == '_ImplicitBooleanValueType'
defn_term =
'
' +
"#{termdef(o.name)} " +
(if o.supertype
span('is written as ', :keyword) + termref(o.supertype.name)
else
" (fundamental)"
end) +
"
"
defn_term + defn_detail
end
def entities(o)
return '' if o.preferred_identifier.role_sequence.all_role_ref.size > 1 # REVISIT: Composite identification
o.all_instance.map do |i|
v = i.value
ii = i # The identifying instance
until v
pi = ii.object_type.preferred_identifier # ii is an Entity Type
break if pi.role_sequence.all_role_ref.size > 1 # REVISIT: Composite identification
identifying_fact_type = pi.role_sequence.all_role_ref.single.role.fact_type
# Find the role played by this instance through which it is identified:
irv = i.all_role_value.detect{|rv| rv.fact.fact_type == identifying_fact_type }
# Get the other RoleValue in what must be a binary fact type:
orv = irv.fact.all_role_value.detect{|rv| rv != irv}
ii = orv.instance
v = ii.value # Does this instance have a value? If so, we're done.
end
next unless v
div(
(i.population.name.empty? ? '' : i.population.name+': ') +
termref(o.name) + ' ' +
div(
# v.is_literal_string ? v.literal.inspect : v.literal,
v.literal.inspect,
'value'
),
'glossary-example'
)
end * "\n" + "\n"
end
end
end
publish_generator Doc::Glossary, "Glossary generator"
end
end