X-Git-Url: http://nitlanguage.org diff --git a/src/nitdoc.nit b/src/nitdoc.nit index 23d4684..3c3539f 100644 --- a/src/nitdoc.nit +++ b/src/nitdoc.nit @@ -1,12 +1,10 @@ # 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 +# 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, @@ -14,1534 +12,255 @@ # 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 - super AbstractCompiler - # Destination directory - readable writable var _dir: String = "doc" - - # 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 - var f = new OFStream.open(filename) - for s in _stage_context.content do - f.write(s) - end - f.close - end - - # Start a new file - fun clear - do - _stage_context = new StageContext(null) - 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 var _opt_dir: OptionString = new OptionString("Directory where doc is generated", "-d", "--dir") - readable var _opt_source: OptionString = new OptionString("What link for source (%f for filename, %l for first line, %L for last line)", "--source") - readable var _opt_public: OptionBool = new OptionBool("Generate only the public API", "--public") - readable var _opt_private: OptionBool = new OptionBool("Generate the private API", "--private") - readable var _opt_nodot: OptionBool = new OptionBool("Do not generate graphes with graphviz", "--no-dot") - readable var _opt_sharedir: OptionString = new OptionString("Directory containing the nitdoc files", "--sharedir") - var sharedir: nullable String - - fun public_only: Bool - do - if self._opt_public.value == true then return true - return false - end - - fun with_private: Bool - do - if self._opt_private.value == true then return true - return false - end - - # The current processed filename - var filename: String - - # The main virtual module - var mainmod: nullable MMVirtualModule - - redef fun perform_work(mods) - do - mainmod = new MMVirtualModule(self, mods) - - dir.mkdir +# Generator of static API documentation for the Nit language +# +# Generate API documentation in HTML format from Nit source code. +module nitdoc - sys.system("cp -r '{sharedir.to_s}'/* {dir}/") +import doc::static - # Compute the set of direct owned nested modules - var owns = new HashMap[MMModule, Array[MMModule]] - for mod in modules do - owns[mod] = new Array[MMModule]# [mod] - end - for mod in modules do - if mod == mainmod then continue - var d = mod.directory - loop - var o = d.owner - if o != null and o != mod then - owns[o].add(mod) - end - var dp = d.parent - if dp == null or dp == d then break - d = dp - end - end +redef class ToolContext - # Builds the various module hierarchies - var mnh = new PartialOrder[MMModule] # nested module hierarchy - var tmh = new PartialOrder[MMModule] # top module import hierrchy - var ms = mainmod.mhe.linear_extension.reversed - for m in ms do - if ms == mainmod then continue - m.mnhe_ = mnh.add(m, owns[m]) - var pub = new Array[MMModule] - for m2 in m.mhe.greaters do - if m2.toplevel_owner != m2 and m2.toplevel_owner != m.toplevel_owner then continue - if m.mnhe <= m2 then continue - if m.visibility_for(m2) <= 0 then - # nothing - else if m.visibility_for(m2) == 1 then - else - pub.add(m2) - end - end - m.tmhe_ = tmh.add(m, pub) - end + # Nitdoc generation phase + var docphase: Phase = new Nitdoc(self, null) - var head = "" + - "\n" + - "\n" + - "" + # Directory where the Nitdoc is rendered + var opt_dir = new OptionString("Output directory", "-d", "--dir") - var action_bar = "
\n" + # Do not generate documentation for attributes + var opt_no_attributes = new OptionBool("Ignore the attributes", "--no-attributes") - # generate the index - self.filename = "index.html" - clear - add("") - add("{head}Index\n") - add(action_bar) - add("
") - add("
") - add("

Modules

\n
    ") - var modss = mainmod.mhe.greaters_and_self.to_a - sort(modss) - for mod in modss do - if not mod.is_toplevel then continue - if not mod.require_doc(self) then continue - assert mod isa MMSrcModule - add("
  • {mod.html_link(self)} {mod.short_doc}
  • ") + # Do not generate documentation for private properties + var opt_private = new OptionBool("Also generate private API", "--private") - end - add("
") + # Use a shareurl instead of copy shared files + # + # This is usefull if you don't want to store the Nitdoc templates with your + # documentation. + var opt_shareurl = new OptionString("Use shareurl instead of copy shared files", "--shareurl") - var op = new Buffer - op.append("digraph dep \{ rankdir=BT; node[shape=none,margin=0,width=0,height=0,fontsize=10]; edge[dir=none,color=gray]; ranksep=0.2; nodesep=0.1;\n") - for mod in modss do - if not mod.is_toplevel then continue - if not mod.require_doc(self) then continue - op.append("\"{mod.name}\"[URL=\"{mod.html_name}.html\"];\n") - for mod2 in mod.tmhe.direct_greaters do - if not modss.has(mod2) then continue - op.append("\"{mod.name}\"->\"{mod2.name}\";\n") - end - end - op.append("\}\n") - self.gen_dot(op.to_s, "dep", "Modules hierarchy") - add("
") - add("
") - add("
") - add("\n") - write_to("{dir}/index.html") + # Use a custom title for the homepage + var opt_custom_title = new OptionString("Custom title for homepage", "--custom-title") - # Generate page for modules - for mod in modules do - if mod == mainmod then continue - assert mod isa MMSrcModule - if not mod.require_doc(self) then continue - self.filename = mod.html_name - action_bar = "
\n" - clear - add("") - add("{head}Module {mod.name}\n") - add(action_bar) - add("
") - mod.file_page_doc(self) - add("
") - add("\n") - write_to("{dir}/{mod.html_name}.html") - end + # Display a custom brand or logo in the documentation top menu + var opt_custom_brand = new OptionString("Custom link to external site", "--custom-brand") - # Generate pages for global classes - for c in mainmod.local_classes do - if not c.require_doc(self) then continue - self.filename = c.html_name - action_bar = "
\n" - clear - add("") - add("{head}Class {c.name}\n") - add(action_bar) - add("
") - c.file_page_doc(self) - add("
") - add("\n") - write_to("{dir}/{c.html_name}.html") - end + # Display a custom introduction text before the packages overview + var opt_custom_intro = new OptionString("Custom intro text for homepage", "--custom-overview-text") - self.filename = "fullindex" - action_bar = "
\n" - clear - add("") - add("{head}Full Index\n") - add(action_bar) - add("
") - add("
") - mainmod.file_index_page_doc(self) - add("
") - add("
") - add("\n") - write_to("{dir}/full-index.html") - end + # Display a custom footer on each documentation page + # + # Generally used to display the documentation or product version. + var opt_custom_footer = new OptionString("Custom footer text", "--custom-footer-text") + # Piwik tracker URL + # + # If you want to monitor your visitors. + var opt_piwik_tracker = new OptionString("Piwik tracker URL (ex: `nitlanguage.org/piwik/`)", "--piwik-tracker") - # Add a (source) link fo a given location - fun show_source(l: Location) - do - var s = opt_source.value - if s == null then - add("in #{l.file.filename.simplify_path}") - else - # THIS IS JUST UGLY ! (but there is no replace yet) - var x = s.split_with("%f") - s = x.join(l.file.filename.simplify_path) - x = s.split_with("%l") - s = x.join(l.line_start.to_s) - x = s.split_with("%L") - s = x.join(l.line_end.to_s) - add(" (show code)") - end - end + # Piwik tracker site id + var opt_piwik_site_id = new OptionString("Piwik site ID", "--piwik-site-id") - # Generate a clicable graphiz image using a dot content. - # `name' refer to the filename (without extension) and the id name of the map. - # `name' must also match the name of the graph in the dot content (eg. digraph NAME {...) - fun gen_dot(dot: String, name: String, alt: String) - do - if opt_nodot.value then return - var f = new OFStream.open("{self.dir}/{name}.dot") - f.write(dot) - f.close - sys.system("\{ test -f {self.dir}/{name}.png && test -f {self.dir}/{name}.s.dot && diff {self.dir}/{name}.dot {self.dir}/{name}.s.dot >/dev/null 2>&1 ; \} || \{ cp {self.dir}/{name}.dot {self.dir}/{name}.s.dot && dot -Tpng -o{self.dir}/{name}.png -Tcmapx -o{self.dir}/{name}.map {self.dir}/{name}.s.dot ; \}") - self.add("
\"{alt}\"/
") - var fmap = new IFStream.open("{self.dir}/{name}.map") - self.add(fmap.read_all) - fmap.close - end + # Do not generate dot/graphviz diagrams + var opt_nodot = new OptionBool("Do not generate graphs with graphviz", "--no-dot") - init - do - keep_ast = true - super("nitdoc") - filename = "-unset-" - option_context.add_option(opt_public) - option_context.add_option(opt_private) - option_context.add_option(opt_dir) - option_context.add_option(opt_source) - option_context.add_option(opt_nodot) - option_context.add_option(opt_sharedir) - end + # Do not include highlighted code + var opt_nocode = new OptionBool("Do not generate code with nitlight", "--no-code") - redef fun process_options - do - super - var d = opt_dir.value - if d != null then dir = d + # File pattern used to link documentation to source code. + var opt_source = new OptionString("Format to link source code (%f for filename, " + + "%l for first line, %L for last line) only works with option --no-code", "--source") - if not opt_nodot.value then - # Test if dot is runable - var res = sys.system("sh -c dot /dev/null 2>&1") - if res != 0 then - stderr.write "--no-dot implied since `dot' is not available. Try to install graphviz.\n" - opt_nodot.value = true - end - end - - sharedir = opt_sharedir.value - if sharedir == null then - var dir = once ("NIT_DIR".to_symbol).environ - if dir.is_empty then - dir = "{sys.program_name.dirname}/../share/nitdoc" - if dir.file_exists then sharedir = dir - else - dir = "{dir}/share/nitdoc" - if dir.file_exists then sharedir = dir - end - if sharedir == null then - fatal_error(null, "Error: Cannot locate nitdoc shared files. Uses --sharedir or envvar NIT_DIR.") - end - dir = "{sharedir.to_s}/scripts/js-facilities.js" - if sharedir == null then - fatal_error(null, "Error: Invalid nitdoc shared files. Check --sharedir or envvar NIT_DIR.") - end + # Disable HTML rendering + var opt_norender = new OptionBool("DO not render any HTML", "--no-render") - end - end + # Test mode + # + # Display test data and remove the progress bar + var opt_test = new OptionBool("Output test data", "--test") - redef fun handle_property_conflict(lc, impls) - do - # THIS IS SO UGLY! See MMVirtualModule - if lc.mmmodule == self.mainmod then - return # We just accept, so one in impls is arbitrary inherited - end + redef init do super + option_context.add_option( + opt_dir, opt_no_attributes, opt_private, + opt_share_dir, opt_shareurl, opt_custom_title, + opt_custom_footer, opt_custom_intro, opt_custom_brand, + opt_piwik_tracker, opt_piwik_site_id, + opt_nodot, opt_nocode, opt_source, opt_norender, opt_test) end end -redef class String - # Replace all occurence of pattern ith string - fun replace(p: Pattern, string: String): String - do - return self.split_with(p).join(string) - end - - # Escape the following characters < > & and " with their html counterpart - fun html_escape: String - do - var ret = self - if ret.has('&') then ret = ret.replace('&', "&") - if ret.has('<') then ret = ret.replace('<', "<") - if ret.has('>') then ret = ret.replace('>', ">") - if ret.has('"') then ret = ret.replace('"', """) - return ret - end - - # Remove "/./", "//" and "bla/../" - fun simplify_path: String - do - var a = self.split_with("/") - var a2 = new Array[String] - for x in a do - if x == "." then continue - if x == "" and not a2.is_empty then continue - if x == ".." and not a2.is_empty then - a2.pop - continue - end - a2.push(x) - end - return a2.join("/") - end -end - -# A virtual module is used to work as an implicit main module that combine unrelated modules -# Since conflict may arrise in a virtual module (the main method for instance) conflicts are disabled -class MMVirtualModule - super MMModule - init(ctx: MMContext, mods: Array[MMModule]) - do - # We need to compute the whole metamodel since there is no mmbuilder to do it - super(" main".to_symbol, mods.first.directory, ctx, new Location(null,0,0,0,0)) - ctx.add_module(self, mods) - for m in mods do - self.add_super_module(m, 1) - end - self.import_global_classes - self.import_local_classes - for c in self.local_classes do - c.compute_super_classes - end - for c in self.local_classes do - c.compute_ancestors - end - - end - redef fun require_doc(dctx) do return false -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] - super 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 - - # 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 -end - -redef class MMModule - super MMEntity - redef fun html_link(dctx) do - return "{self}" - end - - fun require_doc(dctx: DocContext): Bool - do - if dctx.public_only and not is_toplevel then return false - return true - end - - # Return true if the module is a top-level owner or a top-level module - fun is_toplevel: Bool - do - var pd = directory.parent - return pd == null or (pd.owner == null and directory.owner == self) - end - - # Element in the module nesting tree - fun mnhe: PartialOrderElement[MMModule] do return mnhe_.as(not null) - var mnhe_: nullable PartialOrderElement[MMModule] = null - - # Element in the top level module importation hierarchy - fun tmhe: PartialOrderElement[MMModule] do return tmhe_.as(not null) - var tmhe_: nullable PartialOrderElement[MMModule] = null - - fun toplevel_owner: MMModule - do - var m = self - loop - var ds = m.mnhe.direct_smallers - if ds.length == 0 then return m - if ds.length == 1 then m = ds.first else abort - end - end - - fun html_name: String - do - return "{name}" - end - - fun direct_owner: nullable MMModule - do - var d = directory - while d.owner == self do d = d.parent.as(not null) - return d.owner - end - - # Fill the body for the page associated to the module - fun file_page_doc(dctx: DocContext) - do - dctx.add("
\n") - - var mods = new Array[MMModule] - mods = self.mhe.greaters.to_a - dctx.sort(mods) - - dctx.open_stage - dctx.stage("\n") - dctx.close_stage - - if not dctx.public_only then - mods = self.mnhe.direct_greaters.to_a - dctx.sort(mods) - dctx.open_stage - dctx.stage("\n") - dctx.close_stage - end - - dctx.add("
") # metadata - - dctx.add("
\n") - dctx.add("

{name}

\n") - dctx.add("
module ") - for m in mnhe.smallers do - dctx.add("{m.html_link(dctx)}::") - end - dctx.add("{self.name}
\n") - - dctx.add("
\n") - - var doc = doc - if doc != null then - dctx.add("
\n") - dctx.add("
{doc.to_html}
\n") - dctx.add("
\n") - end - - var op = new Buffer - op.append("digraph {name} \{ rankdir=BT; node[shape=none,margin=0,width=0,height=0,fontsize=10]; edge[dir=none,color=gray]; ranksep=0.2; nodesep=0.1;\n") - var ms = new Array[nullable MMModule] - do - var m0: nullable MMModule = self - while m0 != null do - m0 = m0.direct_owner - ms.add(m0) - end - end - var cla = new HashSet[MMModule] - cla.add(self) - for m0 in self.mhe.greaters do - if not m0.require_doc(dctx) then continue - if self.visibility_for(m0) <= 1 then continue # private or hidden - if self.mnhe <= m0 then continue # do not want nested stuff - if m0.direct_owner != null and not m0.direct_owner.mnhe <= self then continue # not in the right nesting - cla.add(m0) - end - for m0 in self.mhe.smallers do - if not m0.require_doc(dctx) then continue - if m0.visibility_for(self) <= 1 then continue # private or hidden - if m0.direct_owner != null and not m0.direct_owner.mnhe <= self then continue # not in the right nesting - cla.add(m0) - end - for m0 in self.mnhe.smallers do - cla.add(m0) - end - ms = ms.reversed - for m0 in ms do - if m0 != null then - op.append("subgraph \"cluster_{m0.name}\"\{\n") - end - for c in cla do - if c.direct_owner != m0 then continue - if c == self then - op.append("\"{c.name}\"[shape=box,margin=0.03];\n") - else - op.append("\"{c.name}\"[URL=\"{c.html_name}.html\"];\n") - end - end - if m0 != null then - op.append("\"{m0.name}\"[URL=\"{m0.html_name}.html\"];\n") - for c in m0.mhe.direct_greaters do - if not cla.has(c) then continue - op.append("\"{m0.name}\"->\"{c.name}\";\n") - end - end - end - for m0 in ms do - # Close the nesting subgraph - if m0 != null then - op.append("\}\n") - end - end - for c in cla do - for c2 in c.tmhe.direct_greaters do - if not cla.has(c2) then continue - op.append("\"{c.name}\"->\"{c2.name}\";\n") - end - end - op.append("\}\n") - dctx.gen_dot(op.to_s, name.to_s, "Dependency graph for module {name}") - dctx.add("
") - - var clas = new Array[MMLocalClass] - var props = new HashMap[MMGlobalProperty, Array[MMLocalProperty]] - var gprops = new Array[MMLocalProperty] - do - var m = self - for g in m.global_classes do - var lc = m[g] - if not lc.require_doc(dctx) then continue - var im = g.intro.mmmodule - if self.visibility_for(im) <= 1 then continue # private import or invisible import - var keep = false - for lc2 in lc.crhe.greaters_and_self do - if not lc2 isa MMSrcLocalClass then continue - if not self.mnhe <= lc2.mmmodule then continue # not introduced/redefined here/stolen - keep = true - end - if not keep then continue - clas.add(self[g]) - lc.compute_super_classes - for gp in lc.global_properties do - if self.visibility_for(gp.intro.local_class.mmmodule) <= 1 then continue # private import or invisible import - var lp = lc[gp] - var mp = lp.local_class.mmmodule - if not self.mnhe <= mp then continue # not introduced/redefined here/stolen - lp = self[g][gp] - if not lp.require_doc(dctx) then continue - if props.has_key(lp.global) then - if not props[lp.global].has(lp) then - props[lp.global].add(lp) - end - else - props[lp.global] = [lp] - gprops.add(lp.global.intro) - end - end - end - end - dctx.add("
\n") - dctx.open_stage - dctx.stage("
\n") - dctx.stage("

Classes

\n") - dctx.sort(clas) - dctx.stage("
    \n") - for lc in clas do - if self.mnhe <= lc.global.intro.mmmodule then - dctx.add("
  • I ") - else - dctx.add("
  • R ") - end - dctx.add("{lc.html_link(dctx)}
  • \n") - end - dctx.stage("
\n") - dctx.close_stage - - dctx.open_stage - dctx.stage("
\n") - dctx.stage("

Properties

\n") - dctx.sort(gprops) - dctx.stage("
    \n") - for lgp in gprops do - var gp = lgp.global - var lps = props[gp] - - if gp.intro isa MMAttribute then continue - - var lpi = self[gp.intro.local_class.global][gp] - - if lps.has(lpi) then - dctx.add("
  • I {lpi.html_open_link(dctx)}{lpi.html_name} ({lpi.local_class})
  • \n") - lps.remove(lpi) - else - dctx.add("
  • I {lpi.html_name}") - dctx.add(" ({lpi.local_class})
  • \n") - end - if lps.length >= 1 then - dctx.sort(lps) - for lp in lps do - dctx.add("
  • R {lp.html_open_link(dctx)}{lp.html_name} ({lp.local_class})
  • ") - end - end - end - dctx.stage("
\n") - dctx.close_stage - dctx.add("
\n") - dctx.add("
\n") - end - - # Fill the body for the page associated to the full index - fun file_index_page_doc(dctx: DocContext) - do - - dctx.add("

Full Index

\n") - - var clas = new Array[MMLocalClass] - var props = new HashMap[MMGlobalProperty, Array[MMLocalProperty]] - var gprops = new Array[MMLocalProperty] - var mods = new Array[MMModule] - for m in mhe.greaters_and_self do - if not m.require_doc(dctx) then continue - mods.add(m) - end - for g in global_classes do - var lc = self[g] - if not lc.require_doc(dctx) then continue - clas.add(lc) - for gp in lc.global_properties do - var lp = lc[gp] - if not lp.require_doc(dctx) then continue - if props.has_key(lp.global) then - if not props[lp.global].has(lp) then - props[lp.global].add(lp) - end - else - props[lp.global] = [lp] - gprops.add(lp.global.intro) - end - end - end - dctx.open_stage - dctx.stage("
\n") - dctx.stage("

Modules

\n") - dctx.sort(mods) - dctx.stage("
\n") - dctx.close_stage - - dctx.open_stage - dctx.stage("
\n") - dctx.stage("

Classes

\n") - dctx.sort(clas) - dctx.stage("
\n") - dctx.close_stage - - dctx.open_stage - dctx.stage("
\n") - dctx.stage("

Properties

\n") - dctx.sort(gprops) - dctx.stage("
\n") - dctx.close_stage - end -end - -redef class MMLocalProperty - super MMEntity - # Anchor of the property description in the module html file - fun html_anchor: String - do - return "PROP_{local_class}_{cmangle(name)}" - end - - fun html_open_link(dctx: DocContext): String - do - if not require_doc(dctx) then print "not required {self}" - var title = "{html_name}{signature.to_s}" - if short_doc != " " then - title += " #{short_doc}" - end - return "" - end - - fun html_name: String - do - return self.name.to_s.html_escape - end - - redef fun html_link(dctx) - do - if not require_doc(dctx) then print "not required {self}" - var title = "{html_name}{signature.to_s}" - if short_doc != " " then - title += " #{short_doc}" - end - return "{html_name}" - end - - fun html_link_special(dctx: DocContext, lc: MMLocalClass): String - do - if not require_doc(dctx) then print "not required {self}" - var title = "{html_name}{signature_for(lc.get_type)}" - if short_doc != " " then - title += " #{short_doc}" - end - return "{html_name}" - end - - # Kind of property (fun, attr, etc.) - fun kind: String is abstract - - 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 n isa APropdef then - return null - end - var d = n.n_doc - if d == null then - return null - end - if d.n_comment.is_empty then - return null - else - return d - end - end - - # The most specific module in the nesting hierarchy that exports the intro of self - fun intro_module: MMModule - do - var m = global.intro.mmmodule - var mo = m.direct_owner - while mo != null and mo.visibility_for(m) >= 2 do - m = mo - mo = m.direct_owner - end - return m - end - - # Is the intro of self exported by the top-level module ? - fun is_toplevel: Bool - do - var m = intro_module - return m == m.toplevel_owner - end - - # Return true if the global property must be documented according to the visibility configured - fun require_doc(dctx: DocContext): Bool - do - if global.visibility_level == 3 and not dctx.with_private then return false # Private - if dctx.public_only then - var m = intro_module - if m != m.toplevel_owner then return false # Unexported - end - return true - end - - # Document the global property in the global class lc - fun full_documentation(dctx: DocContext, lc: MMLocalClass) - do - var visibility: String - if global.visibility_level == 1 then - visibility = "public" - else if global.visibility_level == 2 then - visibility = "protected" - else if global.visibility_level == 3 then - visibility = "private" - else - abort - end - - var intro_class = global.intro.local_class - var is_redef = local_class.global != intro_class.global or local_class.mmmodule.toplevel_owner != intro_class.mmmodule.toplevel_owner - - dctx.add("
\n") - dctx.add("

{html_name}{signature.to_html(dctx, true)}

\n") - dctx.add("
\n") - #dctx.add("

LP: {self.mmmodule.html_link(dctx)}::{self.local_class.html_link(dctx)}::{self.html_link(dctx)}

") - - if is_redef then - dctx.add("redef ") - end - if not is_toplevel then - dctx.add("(unexported) ") - end - if global.visibility_level == 2 then - dctx.add("protected ") - else if global.visibility_level == 3 then - dctx.add("private ") - end - dctx.add(kind) - dctx.add(" {intro_class.mmmodule.toplevel_owner.name}") - if intro_class.global == lc.global then - dctx.add("::{lc.name}") - else - dctx.add("::{mmmodule[intro_class.global].html_link(dctx)}") - end - if is_redef then - dctx.add("::{mmmodule[intro_class.global][global].html_link(dctx)}") - else - dctx.add("::{html_name}") - end - dctx.add("
") - - dctx.add("
") - - # Collect all refinement of the global property in the same global property - var lps = new Array[MMLocalProperty] - for l in prhe.greaters_and_self do - lps.add(l) - end - - var introdoc = false - if global.intro.doc != null then - for lp in lps do - if lp.doc == null then introdoc = true - end - end - if introdoc then - dctx.add("
{global.intro.doc.to_html}
") - end - - var tlmods = new Array[MMModule] - for lp in lps do - var bm = lp.mmmodule.toplevel_owner - var lcm = lc.global.intro.mmmodule - if lcm.mhe < lp.mmmodule then bm = lcm.toplevel_owner - if not tlmods.has(bm) then tlmods.add(bm) - end - - for tm in tlmods do - # Document the top level property for the current top level module - var tlp - if tm.global_classes.has(lc.global) then - tlp = tm[lc.global][self.global] - assert lps.has(tlp) - else if tm.global_classes.has(self.local_class.global) then - # Self is the inherited property. Process it - tlp = tm[self.local_class.global][self.global] - assert lps.has(tlp) - else - # We skip this module since the props defined by the module is - continue - end - - var tlcm = lc.global.intro.mmmodule.toplevel_owner - if not tlcm.mhe <= tm then - dctx.add("

In module {tm.html_link(dctx)} :

") - end - - #dctx.add("

TLP: {tm} x {lc} : {tlp.full_name}

") - - var doc = tlp.doc - if doc != null and (not introdoc or global.intro.doc != doc) then - dctx.add("
{doc.to_html}
") - end - dctx.add("

") - if tlp.local_class.global != lc.global then - dctx.add("inherited from {tlp.local_class.html_link(dctx)} ") - end - if tm != tlp.mmmodule then - dctx.add("defined by the module {tlp.mmmodule.html_link(dctx)} ") - end - var n = tlp.node - if n != null then - var l = n.location - dctx.show_source(l) - end - - dctx.open_stage - dctx.stage(". previously defined by:") - for lp in lps do - var tl = lp.mmmodule.toplevel_owner - if tl != tm then continue - if lp == tlp then continue - dctx.add(" {lp.mmmodule.html_link(dctx)}") - if lp.local_class.global != lc.global then - dctx.add(" for {lp.local_class.html_link(dctx)}") - end - - n = lp.node - if n != null then - var l = n.location - dctx.show_source(l) - end - - #var doc = lp.doc - #if doc != null and (not introdoc or global.intro.doc != doc) then - # dctx.add("

{doc.to_html}
") - #end - end - dctx.close_stage - dctx.add("

") - end - dctx.add("
") - dctx.add("
") - end -end -redef class MMMethod - redef fun kind do return if global.is_init then "init" else "fun" -end -redef class MMAttribute - redef fun kind do return "var" -end -redef class MMTypeProperty - redef fun kind do return "type" -end - -redef class MMSrcModule - redef fun short_doc - do - var d = doc - if d != null then - return d.short - else - return " " - end - end +redef class DocModel - redef fun doc - do - var n = node - if n.n_moduledecl == null then - return null - end - var np = n.n_moduledecl - var d = np.n_doc - if d == null then - return null - end - if d.n_comment.is_empty then - return null - else - return d - end + # Generate a documentation page + fun gen_page(page: DocPage, output_dir: String) do + page.apply_structure(self) + page.render(self).write_to_file("{output_dir}/{page.html_url}") 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)) +# Nitdoc phase explores the model and generate pages for each mentity found +private class Nitdoc + super Phase + + redef fun process_mainmodule(mainmodule, mmodules) + do + var modelbuilder = toolcontext.modelbuilder + var model = modelbuilder.model + + var min_visibility = private_visibility + if not toolcontext.opt_private.value then min_visibility = protected_visibility + var accept_attribute = true + if toolcontext.opt_no_attributes.value then accept_attribute = false + + var catalog = new Catalog(toolcontext.modelbuilder) + catalog.build_catalog(mainmodule.model.mpackages) + + var filter = new ModelFilter( + min_visibility, + accept_attribute = accept_attribute, + accept_fictive = true, + accept_generated = true, + accept_test = false, + accept_redef = true, + accept_extern = true, + accept_empty_doc = true, + accept_example = true, + accept_broken = false) + + var doc = new DocModel(model, mainmodule, modelbuilder, catalog, filter) + + model.nitdoc_md_processor = doc.md_processor + doc.no_dot = toolcontext.opt_nodot.value + doc.no_code = toolcontext.opt_nocode.value + doc.code_url = toolcontext.opt_source.value + doc.share_url = toolcontext.opt_shareurl.value + doc.custom_brand = toolcontext.opt_custom_brand.value + doc.custom_title = toolcontext.opt_custom_title.value + doc.custom_footer = toolcontext.opt_custom_footer.value + doc.custom_intro = toolcontext.opt_custom_intro.value + doc.tracker_url = toolcontext.opt_piwik_tracker.value + doc.piwik_site_id = toolcontext.opt_piwik_site_id.value + + # Prepare output dir + var test_mode = toolcontext.opt_test.value + var no_render = toolcontext.opt_norender.value + var output_dir = toolcontext.opt_dir.value or else "doc" + + if not no_render then + output_dir.mkdir + + # Copy assets + var share_dir = toolcontext.opt_share_dir.value or else "{toolcontext.share_dir}/nitdoc" + sys.system("cp -r -- {share_dir.escape_to_sh}/* {output_dir.escape_to_sh}/") + end + + # Collect model to document + var mpackages = model.collect_mpackages(filter) + var mgroups = model.collect_mgroups(filter) + var nmodules = model.collect_mmodules(filter) + var mclasses = model.collect_mclasses(filter) + var mprops = model.collect_mproperties(filter) + + var mentities = new Array[MEntity] + mentities.add_all mpackages + mentities.add_all mgroups + mentities.add_all nmodules + mentities.add_all mclasses + mentities.add_all mprops + + var persons = doc.catalog.persons + var tags = doc.catalog.tag2proj.keys + + # Prepare progress bar + var count = 0 + var pages = 1 # count homepage + pages += mentities.length + pages += persons.length + pages += tags.length + + print "Generating documentation pages..." + var progress = new TermProgress(pages, 0) + if not test_mode then progress.display + + # Make pages + count += 1 + if not test_mode then progress.update(count, "homepage") + if not no_render then doc.gen_page(new PageHome("Overview"), output_dir) + + for mentity in mentities do + count += 1 + if not test_mode then progress.update(count, "page {count}/{pages}") + if not no_render then doc.gen_page(new PageMEntity(mentity), output_dir) + end + for name, person in persons do + count += 1 + if not test_mode then progress.update(count, "page {count}/{pages}") + if not no_render then doc.gen_page(new PagePerson(person), output_dir) + end + for tag in tags do + count += 1 + if not test_mode then progress.update(count, "page {count}/{pages}") + if not no_render then doc.gen_page(new PageTag(tag), output_dir) + end + + if not test_mode then print "" # finalise progress + if not no_render then + doc.create_index_file("{output_dir}/quicksearch-list.js") + print "Documentation produced in `{output_dir}`" + end + + if test_mode then + print "Generated {count}/{pages} pages" + print " PageHome: 1" + print " PageMPackage: {mpackages.length}" + print " PageMGroup: {mgroups.length}" + print " PageMModule: {nmodules.length}" + print " PageMClass: {mclasses.length}" + print " PageMProperty: {mprops.length}" + print " PagePerson: {persons.length}" + print " PageTag: {tags.length}" end - return res.to_s.html_escape - end - - # Oneliner transcription of the doc - fun short: String - do - return n_comment.first.text.substring_from(1).html_escape end end -redef class MMLocalClass - super MMEntity - - # Anchor of the class description in the module html file - fun html_anchor: String do return "CLASS_{self}" - - fun html_name: String do return "{self}" - - redef fun html_link(dctx) - do - if not require_doc(dctx) then print "{dctx.filename}: not required {self}" - return "{self}" - end - - redef fun short_doc do return global.intro.short_doc - - redef fun doc do return global.intro.doc +redef class Catalog - fun kind: String - do - if global.is_interface then - return "interface" - else if global.is_abstract then - return "abstract class" - else if global.is_enum then - return "enum" - else - return "class" + # Build the catalog from `mpackages` + fun build_catalog(mpackages: Array[MPackage]) do + # Compute the poset + for p in mpackages do + var g = p.root + assert g != null + modelbuilder.scan_group(g) end - end - - # The most specific module in the nesting hierarchy that exports the intro of self - fun intro_module: MMModule - do - var m = global.intro.mmmodule - var mo = m.direct_owner - while mo != null and mo.visibility_for(m) >= 2 do - m = mo - mo = m.direct_owner - end - return m - end - - fun menu_link(dctx: DocContext, p: MMLocalProperty) - do - if p.local_class.global != self.global then - if p.global.intro.local_class.name == "Object".to_symbol then return - if p.global.is_init or p isa MMTypeProperty then - dctx.add("
  • H {p.html_link_special(dctx, self)}
  • \n") - else - dctx.add("
  • H {p.html_link(dctx)}
  • \n") - end - else if p.global.intro.local_class.global == self.global then - dctx.add("
  • I {p.html_link_special(dctx, self)}
  • \n") - else - dctx.add("
  • R {p.html_link_special(dctx, self)}
  • \n") + # Build the catalog + for mpackage in mpackages do + package_page(mpackage) + git_info(mpackage) + mpackage_stats(mpackage) end end - - # Return true if the global class must be documented according to the visibility configured - fun require_doc(dctx: DocContext): Bool - do - if global.visibility_level == 3 and not dctx.with_private then return false # Private - if dctx.public_only then - var m = intro_module - if m != m.toplevel_owner then return false # Unexported - end - return true - end - - # Fill the body for the page associated to the global class - fun file_page_doc(dctx: DocContext) - do - dctx.add("
    \n") - - var props = new Array[MMLocalProperty] - var inh = new HashMap[MMLocalClass, Array[MMLocalProperty]] - var inhs = new Array[MMLocalClass] - for g in global_properties do - var p = self[g] - if not p.require_doc(dctx) then continue - if p.local_class.global == global or g.is_init_for(self) or p isa MMTypeProperty then - props.add(p) - else - var lc = mmmodule[p.local_class.global] - if inh.has_key(lc) then - inh[lc].add(p) - else - inh[lc] = [p] - inhs.add(lc) - end - props.add(p) - end - end - dctx.sort(props) - - dctx.add("\n") - - dctx.add("\n") - - dctx.add("
    \n") - - - dctx.add("
    \n") - dctx.add("

    {name}

    \n") - dctx.add("
    ") - if global.visibility_level == 2 then - abort - else if global.visibility_level == 3 then - dctx.add("private ") - else if self.global.intro.mmmodule.toplevel_owner.visibility_for(self.global.intro.mmmodule) <= 1 then - dctx.add("(unexported) ") - end - dctx.add("{kind} {global.intro.mmmodule.toplevel_owner.html_link(dctx)}::{name}
    ") - - dctx.add("
    \n") - var doc = doc - if doc != null then - dctx.add("
    {doc.to_html}
    \n") - end - - var cla = new HashSet[MMLocalClass] - var sm = new HashSet[MMLocalClass] - var sm2 = new HashSet[MMLocalClass] - sm.add(self) - while cla.length + sm.length < 10 and sm.length > 0 do - cla.add_all(sm) - sm2.clear - for x in sm do - sm2.add_all(x.cshe.direct_smallers) - end - var t = sm - sm = sm2 - sm2 = t - end - cla.add_all(cshe.greaters_and_self) - - var op = new Buffer - op.append("digraph {name} \{ rankdir=BT; node[shape=none,margin=0,width=0,height=0,fontsize=10]; edge[dir=none,color=gray]; ranksep=0.2; nodesep=0.1;\n") - for c in cla do - if c == self then - op.append("\"{c.name}\"[shape=box,margin=0.03];\n") - else - op.append("\"{c.name}\"[URL=\"{c.html_name}.html\"];\n") - end - for c2 in c.cshe.direct_greaters do - if not cla.has(c2) then continue - op.append("\"{c.name}\"->\"{c2.name}\";\n") - end - if not c.cshe.direct_smallers.is_empty then - var others = true - for c2 in c.cshe.direct_smallers do - if cla.has(c2) then others = false - end - if others then - op.append("\"{c.name}...\"[label=\"\"];\n") - op.append("\"{c.name}...\"->\"{c.name}\"[style=dotted];\n") - end - end - end - op.append("\}\n") - dctx.gen_dot(op.to_s, name.to_s, "Inheritance graph for class {name}") - - - var mods = new Array[MMModule] - mods.add(global.intro.mmmodule.toplevel_owner) - for lc in crhe.greaters do - if not lc isa MMSrcLocalClass then continue - var m = lc.mmmodule.toplevel_owner - if not mods.has(m) then mods.add(m) - end - dctx.sort(mods) - for m in mods do - if m == global.intro.mmmodule.toplevel_owner then - dctx.add("

    Introduced by {m.html_link(dctx)}") - else - dctx.add("

    Refined by {m.html_link(dctx)}") - end - dctx.open_stage - dctx.stage(". Definition in:") - for lc in crhe.greaters do - if lc.mmmodule.toplevel_owner != m then continue - dctx.add(" {lc.mmmodule.html_link(dctx)}") - assert lc isa MMSrcLocalClass - var n = lc.node - if n != null then - dctx.show_source(n.location) - end - end - dctx.close_stage - dctx.add("

    \n") - end - dctx.add("
    \n") - - dctx.open_stage - dctx.stage("
    \n") - dctx.stage("

    Formal and Virtual Types

    \n") - for i in [0..arity[ do - var f = get_formal(i) - f.full_documentation(dctx, self) - end - for p in props do - if not p isa MMTypeProperty then continue - p.full_documentation(dctx, self) - end - dctx.stage("
    \n") - dctx.close_stage - - dctx.open_stage - dctx.stage("
    \n") - dctx.stage("

    Constructors

    \n") - for p in props do - if not p.global.is_init_for(self) then continue - p.full_documentation(dctx, self) - end - dctx.stage("
    \n") - dctx.close_stage - - dctx.open_stage - dctx.stage("
    \n") - dctx.stage("

    Methods

    \n") - for p in props do - if p.global.is_init then continue - if p.local_class.global != self.global then continue - if not p isa MMMethod then continue - p.full_documentation(dctx, self) - end - if not inhs.is_empty then - dctx.open_stage - dctx.stage("

    Inherited Methods

    \n") - for lc in inhs do - dctx.open_stage - dctx.stage("

    Defined in {lc.html_link(dctx)}:") - for p in inh[lc] do - if p.global.is_init then continue - if not p isa MMMethod then continue - dctx.add(" {p.html_link(dctx)}") - end - dctx.stage("

    ") - dctx.close_stage - end - dctx.close_stage - end - dctx.add("
    \n") - dctx.close_stage - dctx.add("
    \n") - 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 = node - if not n isa AStdClassdef then - return null - end - var d = n.n_doc - if d == null then - return null - end - if d.n_comment.is_empty then - return null - else - return d - end - end -end - -redef class MMSignature - # Htlm transcription of the signature (with nested links) - fun to_html(dctx: DocContext, with_closure: Bool): String - do - var res = new Buffer - if arity > 0 then - res.append("(") - res.append(self.params[0].name.to_s) - res.append(": ") - res.append(self[0].html_link(dctx)) - for i in [1..arity[ do - res.append(", ") - res.append(self.params[i].name.to_s) - 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 - if with_closure then - for c in closures do - res.append(" ") - if c.is_optional then res.append("[") - if c.is_break then res.append("break ") - res.append("!{c.name}") - res.append(c.signature.to_html(dctx, false)) - if c.is_optional then res.append("]") - end - 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 - -redef class MMTypeFormalParameter - fun html_anchor: String - do - return "FT_{local_class}_{cmangle(name)}" - end - redef fun html_link(dctx) - do - return "{name}" - end - fun full_documentation(dctx: DocContext, lc: MMLocalClass) - do - dctx.add("
    \n") - dctx.add("

    {name}: {bound.html_link(dctx)}

    \n") - dctx.add("
    ") - dctx.add("formal generic type") - dctx.add("
    ") - dctx.add("
    ") - end -end - -redef class MMNullableType - redef fun html_link(dctx) do return "nullable " + as_notnull.html_link(dctx) -end - -redef class MMVirtualType - redef fun html_link(dctx) do return property.html_link(dctx) end -var c = new DocContext -c.exec_cmd_line +# build toolcontext +var toolcontext = new ToolContext +var tpl = new Template +tpl.add "Usage: nitdoc [OPTION]... ...\n" +tpl.add "Generates HTML pages of API documentation from Nit source files." +toolcontext.tooldescription = tpl.write_to_string + +# process options +toolcontext.process_options(args) +var arguments = toolcontext.option_context.rest + +# build model +var model = new Model +var mbuilder = new ModelBuilder(model, toolcontext) +var mmodules = mbuilder.parse_full(arguments) + +# process +if mmodules.is_empty then return +print "Parsing code..." +mbuilder.run_phases +toolcontext.run_global_phases(mmodules)