# This file is part of NIT ( http://www.nitlanguage.org ). # # Copyright 2008 Jean Privat # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # The main module of the nitdoc program package nitdoc import syntax private import utils import abstracttool # Store knowledge and facilities to generate files class DocContext special AbstractCompiler # Destination directory readable writable var _dir: String = "." # Content of a generated file var _stage_context: StageContext = new StageContext(null) # Add a string in the content fun add(s: String) do _stage_context.content.add(s) _stage_context.validate = true end # Add a string in the content iff some other string are added fun stage(s: String) do _stage_context.content.add(s) # Create a new stage in the content fun open_stage do _stage_context = new StageContext(_stage_context) # Close the current stage in the content fun close_stage do var s = _stage_context.parent if _stage_context.validate then s.content.add_all(_stage_context.content) s.validate = true end assert s != null _stage_context = s end # Write the content to a new file fun write_to(filename: String) do print "Generate {filename}" var f = new OFStream.open(filename) for s in _stage_context.content do f.write(s) end f.close end # Currently computed module readable var _module: nullable MMSrcModule # Is the current directory module computed as a simple modude ? readable writable var _inside_mode: Bool = false # Is the current module computed as a intruded one ? readable writable var _intrude_mode: Bool = false # Compued introducing entities (for the index) var _entities: Array[MMEntity] = new Array[MMEntity] # Register an entity (for the index) fun register(e: MMEntity) do _entities.add(e) if e isa MMSrcModule then _module = e end end # Start a new file fun clear do _stage_context = new StageContext(null) end # Generate common files (frames, index, overview) fun extract_other_doc do _module = null inside_mode = false intrude_mode = false clear add("\n") add("Overview
\n") add("Index
\n") var modules = modules.to_a sort(modules) var rootdirs = new Array[MMDirectory] for m in modules do var md = m.directory if md.parent == null and not rootdirs.has(md) then rootdirs.add(md) end end var done = new Array[MMModule] for root in rootdirs do var dirstack = [root] var curdir = root add("{root.name}
\n") var indent = "  " while not dirstack.is_empty do var redo = false for m in modules do if done.has(m) then continue var md = m.directory if md.owner == m and md.parent == curdir then # It's a directory module add("{indent}{m}
\n") curdir = md dirstack.push(curdir) indent = "  " * dirstack.length redo = true break # restart to preserve alphabetic order else if md == curdir then if md.owner == m then add("{indent}{m}
\n") else add("{indent}{m}
\n") end done.add(m) redo = true end end if not redo then dirstack.pop if not dirstack.is_empty then curdir = dirstack[dirstack.length-1] indent = "  " * dirstack.length end end end end add("\n") write_to("{dir}/menu-frame.html") clear add_header("Index") add("
\n") sort(_entities) for e in _entities do add("
{e.html_link(self)} - {e.prototype_head(self)} {e}{e.prototype_body(self)} {e.locate(self)}
{e.short_doc}\n") end add("
\n") write_to("{dir}/index-1.html") clear add_header("Overview") add("\n") add("\n") for m in modules do add("\n") end add("
Overview of all Modules
{m.html_link(self)}{m.short_doc}
\n") write_to("{dir}/overview.html") clear add("\n\n\n\n\n") write_to("{dir}/index.html") end fun add_header(title: String) do add("{title}\n\n") add("
\n") add("Overview  Index  With Frames\n") add("
") add("Visibility: ") if (not inside_mode and not intrude_mode) or module == null then add("Public  ") else add("Public  ") end if inside_mode or module == null then add("Inside  ") else if module.directory.owner != module then add("Inside  ") else add("Inside  ") end if intrude_mode or module == null then add("Intrude  ") else add("Intrude  ") end add("
") end # Sorter of entities in alphabetical order var _sorter: AlphaSorter[MMEntity] = new AlphaSorter[MMEntity] # Sort entities in the alphabetical order fun sort(array: Array[MMEntity]) do _sorter.sort(array) end readable writable var _owned_modules: Array[MMModule] = new Array[MMModule] # Return the known_owner for current module # if inside_mode is set, it could be a different result fun known_owner_of(m: MMModule): MMModule do var module = module if module == null then return m var res = module.known_owner_of(m) if not inside_mode and not intrude_mode and res.directory.owner == module then return module else return res end end readable var _opt_dir: OptionString = new OptionString("Directory where doc is generated", "-d", "--dir") redef fun perform_work(mods) do dir.mkdir for mod in modules do assert mod isa MMSrcModule mod.extract_module_doc(self) end self.extract_other_doc end init do super("nitdoc") option_context.add_option(opt_dir) end redef fun process_options do super var d = opt_dir.value if d != null then dir = d end end # Conditionnal part of the text content of a DocContext class StageContext # Content of the current stage readable var _content: Array[String] = new Array[String] # Is a normal string already added? readable writable var _validate: Bool = false # Parent stage is any readable var _parent: nullable StageContext = null init(parent: nullable StageContext) do _parent = parent end # Efficiently sort object with their to_s method class AlphaSorter[E: Object] special AbstractSorter[E] redef fun compare(a, b) do var sa: String var sb: String var d = _dico if d.has_key(a) then sa = d[a] else sa = a.to_s d[a] = sa end if d.has_key(b) then sb = d[b] else sb = b.to_s d[b] = sb end return sa <=> sb end # Keep track of to_s values var _dico: HashMap[Object, String] = new HashMap[Object, String] init do end end # Generalization of metamodel entities class MMEntity # Return a link to fun html_link(dctx: DocContext): String is abstract # Is the entity should appear in the generaed doc fun need_doc(dctx: DocContext): Bool is abstract # Return a one liner description fun short_doc: String do return " " # The doc node from the AST # Return null is none fun doc: nullable ADoc do return null # Human redable location of the entity (module/class/property) fun locate(dctx: DocContext): String do return "" # Part of the prototype before the name (kind, modifiers, qualifier) fun prototype_head(dctx: DocContext): String is abstract # Part of the property after the name (signature, modifiers) fun prototype_body(dctx: DocContext): String do return "" end redef class MMModule special MMEntity redef fun html_link(dctx) do if dctx.module == self then return "{self}" else return "{self}" end end redef fun need_doc(dctx) do return true redef fun prototype_head(dctx) do return "module " var _known_owner_of_cache: Map[MMModule, MMModule] = new HashMap[MMModule, MMModule] fun known_owner_of(module: MMModule): MMModule do if _known_owner_of_cache.has_key(module) then return _known_owner_of_cache[module] var res = module if mhe < module and visibility_for(module) != 0 then res = known_owner_of_intern(module, self, false) else res = module.owner(self) end _known_owner_of_cache[module] = res return res end # Return the most general module that own self fun owner(from: MMModule): MMModule do var res = self var d: nullable MMDirectory = directory while d != null and d != from.directory do var o = d.owner if o != null and o.mhe <= res then res = o d = d.parent end return res end private fun known_owner_of_intern(module: MMModule, from: MMModule, as_owner: Bool): MMModule do if module == self then return self var candidates = new Array[MMModule] for m in explicit_imported_modules do if from.visibility_for(m) == 0 then continue if not m.mhe <= module then continue candidates.add(m.known_owner_of_intern(module, from, true)) end assert not candidates.is_empty var max = candidates.first for m in candidates do if max.mhe < m then max = m end if as_owner and max.directory.owner == self then return self else return max end end end redef class MMLocalProperty special MMEntity # Anchor of the property description in the module html file fun html_anchor: String do return "PROP_{local_class}_{cmangle(name)}" end redef fun html_link(dctx) do var m = module if not need_doc(dctx) then m = global.intro.module var m = dctx.known_owner_of(m) if m == dctx.module then return "{self}" else return "{self}" end end # Kind of property (fun, attr, etc.) fun kind: String is abstract redef fun locate(dctx) do return "in {module.html_link(dctx)}::{local_class.html_link(dctx)}" end fun known_intro_class(dctx: DocContext): MMLocalClass do var mod = dctx.known_owner_of(global.intro.local_class.module) var cla = mod[global.intro.local_class.global] return cla end redef fun prototype_head(dctx) do var res = new Buffer var intro_class = known_intro_class(dctx) var is_redef = local_class != intro_class if is_redef then res.append("redef ") if global.visibility_level == 2 then res.append("protected ") else if global.visibility_level == 3 then res.append("private ") end res.append(kind) if is_redef then var gp = global.intro if intro_class.global != local_class.global then res.append(" {module[intro_class.global].html_link(dctx)}::") else if intro_class.module != module then res.append(" {intro_class.module.html_link(dctx)}::") end end return res.to_s end redef fun prototype_body(dctx) do var res = new Buffer res.append(signature.to_html(dctx)) var s = self if s.node != null then if s.node isa ADeferredMethPropdef then res.append(" is abstract") else if s.node isa AInternMethPropdef then res.append(" is intern") end end return res.to_s end redef fun need_doc(dctx) do if global.visibility_level >= 3 or self isa MMAttribute then if not dctx.intrude_mode then return false if dctx.module.visibility_for(module) == 0 then return false end if global.intro == self then return true end return doc != null end redef fun short_doc do var d = doc if d != null then return d.short else if global.intro == self then return " " else return global.intro.short_doc end end redef fun doc do var n = node if n == null or not node isa PPropdef then return null end assert n isa PPropdef var d = n.n_doc if d == null then return null end assert d isa ADoc if d.n_comment.is_empty then return null else return d end end end redef class MMMethod redef fun kind do return if global.is_init then "init" else "meth" end redef class MMAttribute redef fun kind do return "attr" end redef class MMTypeProperty redef fun kind do return "type" end redef class MMSrcModule # Extract and generate html file for the module fun extract_module_doc(dctx: DocContext) do dctx.register(self) dctx.clear extract_module_doc_inside(dctx) dctx.write_to("{dctx.dir}/{name}.html") dctx.intrude_mode = true dctx.clear extract_module_doc_inside(dctx) dctx.write_to("{dctx.dir}/{name}__.html") dctx.intrude_mode = false if directory.owner == self then dctx.inside_mode = true dctx.clear extract_module_doc_inside(dctx) dctx.write_to("{dctx.dir}/{name}_.html") dctx.inside_mode = false end end fun extract_module_doc_inside(dctx: DocContext) do dctx.add_header("Module {self}") dctx.add("

Module {self}

\n
") var s = "" var d: nullable MMDirectory = directory while d == null do if d.owner != null and (d.owner != self or dctx.inside_mode or dctx.intrude_mode) then s = "{d.owner.html_link(dctx)}::{s}" end d = d.parent end dctx.add("{s}
{prototype_head(dctx)}{self}{prototype_body(dctx)}
\n") var strs = new Array[String] var intrude_modules = new Array[MMModule] var public_modules = new Array[MMModule] var private_modules = new Array[MMModule] var owned_modules = dctx.owned_modules owned_modules.clear for m in mhe.greaters do var v = visibility_for(m) if not dctx.inside_mode and not dctx.intrude_mode and m.directory.owner == self then if v >= 2 then owned_modules.add(m) continue end if v == 3 then intrude_modules.add(m) else if v == 2 then public_modules.add(m) else if v == 1 then private_modules.add(m) end end if not intrude_modules.is_empty then var mods = mhe.order.select_smallests(intrude_modules) for i in mods do strs.add(i.html_link(dctx)) dctx.add("
Intruded modules:
{strs.join(", ")}\n") end if not public_modules.is_empty then strs.clear var mods = mhe.order.select_smallests(public_modules) for i in mods do strs.add(i.html_link(dctx)) dctx.add("
Imported modules:
{strs.join(", ")}\n") end if not private_modules.is_empty then strs.clear var mods = mhe.order.select_smallests(private_modules) for i in mods do strs.add(i.html_link(dctx)) dctx.add("
Privatly imported modules:
{strs.join(", ")}\n") end dctx.add("
\n") var doc = doc if doc != null then dctx.add("
{doc.to_html}
\n") var new_classes = new Array[MMLocalClass] for c in local_classes do if c.need_doc(dctx) then new_classes.add(c) if c.global.intro == c then dctx.register(c) end else for m in owned_modules do if m.global_classes.has(c.global) then var mc = m[c.global] if mc.need_doc(dctx) then new_classes.add(c) break end end end end end if not new_classes.is_empty then dctx.sort(new_classes) dctx.add("\n") dctx.add("\n") for c in new_classes do dctx.add("\n") end dctx.add("
Class Summary of {self}
{c.prototype_head(dctx)}{c.html_link(dctx)}{c.prototype_body(dctx)}
{c.short_doc}

\n") end if not new_classes.is_empty then dctx.add("\n") dctx.add("\n") dctx.add("
Class Detail of {self}
\n") for c in new_classes do c.extract_class_doc(dctx) end end dctx.add("\n") end redef fun short_doc do var d = doc if d != null then return d.short else return " " end end redef fun doc do var n = node if n.n_packagedecl == null then return null end var np = n.n_packagedecl assert np isa APackagedecl var d = np.n_doc if d == null then return null end assert d isa ADoc if d.n_comment.is_empty then return null else return d end end end redef class ADoc # Html transcription of the doc fun to_html: String do var res = new Buffer for c in n_comment do res.append(c.text.substring_from(1)) end return res.to_s end # Oneliner transcription of the doc fun short: String do return n_comment.first.text.substring_from(1) end end redef class MMLocalClass special MMEntity # Anchor of the class description in the module html file fun html_anchor: String do return "CLASS_{self}" redef fun html_link(dctx) do var m = module if not need_doc(dctx) then m = global.module var m = dctx.known_owner_of(m) if m == dctx.module then return "{self}" else return "{self}" end end redef fun short_doc do return global.intro.short_doc redef fun doc do return global.intro.doc redef fun need_doc(dctx) do if module == dctx.module then for m in dctx.owned_modules do if m.global_classes.has(global) then var c = m[global] if c.need_doc(dctx) then return true end end end return false end redef fun locate(dctx) do return "in {module.html_link(dctx)}" fun known_intro(dctx: DocContext): MMLocalClass do return dctx.known_owner_of(global.intro.module)[global] redef fun prototype_head(dctx) do var res = new Buffer var ki = known_intro(dctx) var is_redef = ki != self if is_redef then res.append("redef ") if global.visibility_level == 3 then res.append("private ") res.append("class ") if is_redef then res.append("{ki.module.html_link(dctx)}::") return res.to_s end redef fun prototype_body(dctx) do var res = new Buffer if arity > 0 then res.append("[") for i in [0..arity[ do var t = get_formal(i) res.append(t.name.to_s) res.append(": ") res.append(t.bound.html_link(dctx)) end res.append("]") end return res.to_s end # Extract the doc of a class fun extract_class_doc(dctx: DocContext) do dctx.add("

{self}

{module.html_link(dctx)}::
{prototype_head(dctx)}{self}{prototype_body(dctx)}\n") dctx.add("
\n") dctx.add("
\n") var sup2 = new Array[String] var intro_module = dctx.known_owner_of(global.module) if intro_module != module then dctx.add("
Refine {self} from:
{intro_module.html_link(dctx)}\n") sup2.clear var mods = new Array[MMModule] for c in crhe.greaters do if c.need_doc(dctx) then var km = dctx.known_owner_of(c.module) if km != module and km != intro_module and not mods.has(km) then mods.add(km) end end end for c in crhe.linear_extension do if mods.has(c.module) then sup2.add(c.module.html_link(dctx)) end if not sup2.is_empty then dctx.add("
Previous refinements in:
{sup2.join(", ")}\n") end if not cshe.greaters.is_empty then sup2.clear var clas = new Array[MMLocalClass] for c in cshe.direct_greaters do sup2.add(c.html_link(dctx)) end dctx.add("
Direct superclasses:
{sup2.join(", ")}\n") sup2.clear for c in cshe.linear_extension do if c != self then sup2.add(c.html_link(dctx)) end dctx.add("
All superclasses:
{sup2.join(", ")}\n") end if not cshe.direct_smallers.is_empty then sup2.clear for c in cshe.direct_smallers do sup2.add(c.html_link(dctx)) end dctx.add("
Direct subclasses:
{sup2.join(", ")}\n") end sup2.clear for c in crhe.smallers do c.compute_super_classes for c2 in c.module.local_classes do if not c2 isa MMConcreteClass then continue c2.compute_super_classes c2.compute_ancestors c2.inherit_global_properties end for c2 in c.cshe.direct_smallers do if c2.global.intro == c2 then sup2.add("{c2.html_link(dctx)}") end end end if not sup2.is_empty then dctx.add("
Other direct subclasses in known modules:
{sup2.join(", ")}\n") end sup2.clear for c in crhe.order do if not module.mhe <= c.module and c.need_doc(dctx) then sup2.add(c.module.html_link(dctx)) end end if not sup2.is_empty then dctx.add("
Refinements in known modules:
{sup2.join(", ")}\n") end dctx.add("
\n") var doc = doc if doc != null then dctx.add("
{doc.to_html}
\n") end var details = new Array[Array[MMLocalProperty]] for i in [0..4[ do details.add(property_summary(dctx, i)) for i in [0..4[ do property_detail(dctx, i, details[i]) dctx.add("

\n") end fun pass_name(pass: Int): String do var names = once ["Virtual Types", "Consructors", "Methods", "Attributes"] return names[pass] end fun accept_prop(p: MMLocalProperty, pass: Int): Bool do if pass == 0 then return p isa MMTypeProperty else if pass == 1 then return p.global.is_init else if pass == 2 then return p isa MMMethod and not p.global.is_init else if pass == 3 then return p isa MMAttribute end abort end fun property_summary(dctx: DocContext, pass: Int): Array[MMLocalProperty] do var passname = pass_name(pass) dctx.open_stage dctx.stage("\n") dctx.stage("\n") var new_props = new Array[MMLocalProperty] for g in global_properties do if not accept_prop(g.intro, pass) then continue if module.visibility_for(g.intro.module) < g.visibility_level then continue var p = self[g] if p.local_class != self or not p.need_doc(dctx) then var cla = new Array[MMLocalClass] for m in dctx.owned_modules do if not m.global_classes.has(global) then continue var c = m[global] if not c isa MMConcreteClass then continue if not c.has_global_property(g) then continue var p2 = c[g] if p2.local_class != c or not p2.need_doc(dctx) then continue cla.add(c) end if cla.is_empty then continue cla = crhe.order.select_smallests(cla) end new_props.add(p) if p.global.intro == p then dctx.register(p) end end dctx.sort(new_props) for p in new_props do dctx.add("\n") end dctx.stage("
{passname} Summary of {self}
{p.prototype_head(dctx)}{p.html_link(dctx)}{p.prototype_body(dctx)}
    {p.short_doc}

\n") dctx.open_stage dctx.stage("\n") if pass != 1 then # skip pass 1 because constructors are not inherited var cmap = new HashMap[MMLocalClass, Array[MMLocalProperty]] var mmap = new HashMap[MMModule, Array[MMLocalProperty]] var props = new Array[MMLocalClass] for c in che.greaters do if c isa MMSrcLocalClass then var km = dctx.known_owner_of(c.module) var kc = km[c.global] if kc == self then continue var props: Array[MMLocalProperty] if km == module then if cmap.has_key(kc) then props = cmap[kc] else props = new Array[MMLocalProperty] cmap[kc] = props end else if mmap.has_key(km) then props = mmap[km] else props = new Array[MMLocalProperty] mmap[km] = props end end for g in c.global_properties do var p = c[g] if p.local_class == c and p.need_doc(dctx) and accept_prop(p, pass) then props.add(kc[g]) end end end end dctx.open_stage dctx.stage("\n") for c in cshe.linear_extension do if not cmap.has_key(c) then continue var props = cmap[c] if props.is_empty then continue dctx.sort(props) var properties = new Array[String] for p in props do properties.add(p.html_link(dctx)) dctx.add("\n") end dctx.close_stage dctx.open_stage dctx.stage("\n") for m in module.mhe.linear_extension do if not mmap.has_key(m) then continue var props = mmap[m] if props.is_empty then continue dctx.sort(props) var properties = new Array[String] for p in props do properties.add(p.html_link(dctx)) dctx.add("\n") end dctx.close_stage end var mmap = new HashMap[MMModule, Array[MMLocalProperty]] var props = new Array[MMLocalClass] for c in crhe.order do if module.mhe <= c.module or dctx.owned_modules.has(c.module) or not c isa MMSrcLocalClass then continue var km = dctx.known_owner_of(c.module) if module.mhe <= km then continue var kc = km[c.global] var props: Array[MMLocalProperty] if mmap.has_key(km) then props = mmap[km] else props = new Array[MMLocalProperty] mmap[km] = props end for g in c.global_properties do var p = c[g] if p.local_class == c and p.need_doc(dctx) and accept_prop(p, pass) then var kp = kc[g] if not props.has(kp) then props.add(kp) end end # c.properties_inherited_from(dctx, self, pass) end dctx.open_stage dctx.stage("\n") for c in crhe.order do var m = c.module if not mmap.has_key(m) then continue var props = mmap[m] if props.is_empty then continue dctx.sort(props) var properties = new Array[String] for p in props do properties.add(p.html_link(dctx)) dctx.add("\n") end dctx.close_stage dctx.stage("
Inherited {passname}
from {c.html_link(dctx)}{properties.join(", ")}
Imported {passname}
from {m.html_link(dctx)}{properties.join(", ")}
Added {passname} in known modules
in {m.html_link(dctx)}{properties.join(", ")}


\n") dctx.close_stage dctx.close_stage return new_props end fun property_detail(dctx: DocContext, pass: Int, new_props: Array[MMLocalProperty]) do var passname = pass_name(pass) dctx.open_stage dctx.stage("\n") dctx.stage("\n") dctx.stage("
{passname} Detail of {self}
\n") dctx.open_stage for p in new_props do dctx.add("

{p}

{p.module.html_link(dctx)}::{p.local_class.html_link(dctx)}::
{p.prototype_head(dctx)} {p.name}{p.prototype_body(dctx)}

\n") dctx.add("
") var doc = p.doc if doc != null then dctx.add("
{doc.to_html}
\n") end dctx.stage("
\n") dctx.close_stage dctx.open_stage dctx.stage("
\n") end dctx.close_stage dctx.close_stage end # Add rows for properties inheriterd to some heirs fun properties_inherited_from(dctx: DocContext, heir: MMLocalClass, pass: Int) do var properties = new Array[String] for g in global_properties do var p = self[g] if p.local_class == self and p.need_doc(dctx) and accept_prop(p, pass) then properties.add(p.html_link(dctx)) end end if not properties.is_empty then var s: String if heir.global == global then s = module.html_link(dctx) else s = self.html_link(dctx) end dctx.add("in {s}{properties.join(", ")}\n") end end end redef class MMSrcLocalClass redef fun short_doc do var d = doc if d != null then return d.short else if global.intro == self then return " " else var bc = global.intro return bc.short_doc end end redef fun doc do var n = nodes.first if not n isa AClassdef then return null end assert n isa AClassdef var d = n.n_doc if d == null then return null end assert d isa ADoc if d.n_comment.is_empty then return null else return d end end redef fun need_doc(dctx) do if global.visibility_level >= 3 then if not dctx.intrude_mode then return false if dctx.module.visibility_for(module) == 0 then return false end if global.intro == self then return true end for p in src_local_properties do if p.need_doc(dctx) then return true end end return super end end redef class MMSignature # Htlm transcription of the signature (with nested links) fun to_html(dctx: DocContext): String do var res = new Buffer if arity > 0 then res.append("(") res.append(self[0].html_link(dctx)) for i in [1..arity[ do res.append(", ") res.append(self[i].html_link(dctx)) end res.append(")") end if return_type != null then res.append(": ") res.append(return_type.html_link(dctx)) end return res.to_s end end redef class MMType # Htlm transcription of the type (with nested links) fun html_link(dctx: DocContext): String do return to_s end redef class MMTypeSimpleClass redef fun html_link(dctx) do return local_class.html_link(dctx) end redef class MMTypeGeneric redef fun html_link(dctx) do var res = new Buffer res.append(local_class.html_link(dctx)) res.append("[") res.append(params[0].html_link(dctx)) for i in [1..params.length[ do res.append(", ") res.append(params[i].html_link(dctx)) end res.append("]") return res.to_s end end var c = new DocContext c.exec_cmd_line