X-Git-Url: http://nitlanguage.org diff --git a/src/nitdoc.nit b/src/nitdoc.nit index 7de4d4f..c9691bf 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,1140 +12,1877 @@ # 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 +# Documentation generator for the nit language. +# Generate API documentation in HTML format from nit source code. +module nitdoc + +import model_utils +import modelize_property +import markdown + +# The NitdocContext contains all the knowledge used for doc generation +class NitdocContext + + private var toolcontext = new ToolContext + private var model: Model + private var mbuilder: ModelBuilder + private var mainmodule: MModule + private var class_hierarchy: POSet[MClass] + private var arguments: Array[String] + private var output_dir: nullable String + private var dot_dir: nullable String + private var share_dir: nullable String + private var source: nullable String + private var min_visibility: MVisibility + + private var github_upstream: nullable String + private var github_basesha1: nullable String + private var github_gitdir: nullable String + + private var opt_dir = new OptionString("Directory where doc is generated", "-d", "--dir") + private var opt_source = new OptionString("What link for source (%f for filename, %l for first line, %L for last line)", "--source") + private var opt_sharedir = new OptionString("Directory containing the nitdoc files", "--sharedir") + private var opt_shareurl = new OptionString("Do not copy shared files, link JS and CSS file to share url instead", "--shareurl") + private var opt_nodot = new OptionBool("Do not generate graphes with graphviz", "--no-dot") + private var opt_private: OptionBool = new OptionBool("Generate the private API", "--private") + + private var opt_custom_title: OptionString = new OptionString("Title displayed in the top of the Overview page and as suffix of all page names", "--custom-title") + private var opt_custom_menu_items: OptionString = new OptionString("Items displayed in menu before the 'Overview' item (Each item must be enclosed in 'li' tags)", "--custom-menu-items") + private var opt_custom_overview_text: OptionString = new OptionString("Text displayed as introduction of Overview page before the modules list", "--custom-overview-text") + private var opt_custom_footer_text: OptionString = new OptionString("Text displayed as footer of all pages", "--custom-footer-text") + + private var opt_github_upstream: OptionString = new OptionString("The branch where edited commits will be pulled into (ex: user:repo:branch)", "--github-upstream") + private var opt_github_base_sha1: OptionString = new OptionString("The sha1 of the base commit used to create pull request", "--github-base-sha1") + private var opt_github_gitdir: OptionString = new OptionString("The git working directory used to resolve path name (ex: /home/me/myproject/)", "--github-gitdir") + + private var opt_piwik_tracker: OptionString = new OptionString("The URL of the Piwik tracker (ex: nitlanguage.org/piwik/)", "--piwik-tracker") + private var opt_piwik_site_id: OptionString = new OptionString("The site ID in Piwik tracker", "--piwik-site-id") + + init do + toolcontext.option_context.add_option(opt_dir) + toolcontext.option_context.add_option(opt_source) + toolcontext.option_context.add_option(opt_sharedir, opt_shareurl) + toolcontext.option_context.add_option(opt_nodot) + toolcontext.option_context.add_option(opt_private) + toolcontext.option_context.add_option(opt_custom_title) + toolcontext.option_context.add_option(opt_custom_footer_text) + toolcontext.option_context.add_option(opt_custom_overview_text) + toolcontext.option_context.add_option(opt_custom_menu_items) + toolcontext.option_context.add_option(opt_github_upstream) + toolcontext.option_context.add_option(opt_github_base_sha1) + toolcontext.option_context.add_option(opt_github_gitdir) + toolcontext.option_context.add_option(opt_piwik_tracker) + toolcontext.option_context.add_option(opt_piwik_site_id) + toolcontext.process_options + self.arguments = toolcontext.option_context.rest + + if arguments.length < 1 then + print "usage: nitdoc [options] file..." + toolcontext.option_context.usage + exit(1) + end + self.process_options + + model = new Model + mbuilder = new ModelBuilder(model, toolcontext) + # Here we load and process all modules passed on the command line + var mmodules = mbuilder.parse(arguments) + if mmodules.is_empty then return + mbuilder.run_phases + + if mmodules.length == 1 then + mainmodule = mmodules.first + else + # We need a main module, so we build it by importing all modules + mainmodule = new MModule(model, null, "
", new Location(null, 0, 0, 0, 0)) + mainmodule.set_imported_mmodules(mmodules) end - assert s != null - _stage_context = s + self.class_hierarchy = mainmodule.flatten_mclass_hierarchy 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) + private fun process_options do + if opt_dir.value != null then + output_dir = opt_dir.value + else + output_dir = "doc" + end + if opt_sharedir.value != null then + share_dir = opt_sharedir.value + else + var dir = "NIT_DIR".environ + if dir.is_empty then + dir = "{sys.program_name.dirname}/../share/nitdoc" + else + dir = "{dir}/share/nitdoc" + end + share_dir = dir + if share_dir == null then + print "Error: Cannot locate nitdoc share files. Uses --sharedir or envvar NIT_DIR" + abort + end + end + if opt_private.value then + min_visibility = none_visibility + else + min_visibility = protected_visibility + end + var gh_upstream = opt_github_upstream.value + var gh_base_sha = opt_github_base_sha1.value + var gh_gitdir = opt_github_gitdir.value + if not gh_upstream == null or not gh_base_sha == null or not gh_gitdir == null then + if gh_upstream == null or gh_base_sha == null or gh_gitdir == null then + print "Error: Options {opt_github_upstream.names.first}, {opt_github_base_sha1.names.first} and {opt_github_gitdir.names.first} are required to enable the GitHub plugin" + abort + else + self.github_upstream = gh_upstream + self.github_basesha1 = gh_base_sha + self.github_gitdir = gh_gitdir + end end - f.close + source = opt_source.value 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 + fun generate_nitdoc do + # Create destination dir if it's necessary + if not output_dir.file_exists then output_dir.mkdir + if opt_shareurl.value == null then + sys.system("cp -r {share_dir.to_s}/* {output_dir.to_s}/") + else + sys.system("cp -r {share_dir.to_s}/ZeroClipboard.swf {output_dir.to_s}/") + sys.system("cp -r {share_dir.to_s}/resources/ {output_dir.to_s}/resources/") + end + self.dot_dir = null + if not opt_nodot.value then self.dot_dir = output_dir.to_s + overview + search + modules + classes + quicksearch_list + end - # Is the current module computed as a intruded one ? - readable writable var _intrude_mode: Bool = false + private fun overview do + var overviewpage = new NitdocOverview(self, dot_dir) + overviewpage.save("{output_dir.to_s}/index.html") + end - # Compued introducing entities (for the index) - var _entities: Array[MMEntity] = new Array[MMEntity] + private fun search do + var searchpage = new NitdocSearch(self) + searchpage.save("{output_dir.to_s}/search.html") + end - # Register an entity (for the index) - fun register(e: MMEntity) - do - _entities.add(e) - if e isa MMSrcModule then - _module = e + private fun modules do + for mmodule in model.mmodules do + if mmodule.name == "
" then continue + var modulepage = new NitdocModule(mmodule, self, dot_dir) + modulepage.save("{output_dir.to_s}/{mmodule.url}") end end - # Start a new file - fun clear - do - _stage_context = new StageContext(null) + private fun classes do + for mclass in mbuilder.model.mclasses do + var classpage = new NitdocClass(mclass, self, dot_dir, source) + classpage.save("{output_dir.to_s}/{mclass.url}") + end end - # Generate common files (frames, index, overview) - fun extract_other_doc - do - info("Generating other files",1) - _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) + private fun quicksearch_list do + var file = new OFStream.open("{output_dir.to_s}/quicksearch-list.js") + file.write("var nitdocQuickSearchRawList = \{ ") + for mmodule in model.mmodules do + if mmodule.name == "
" then continue + file.write("\"{mmodule.name}\": [") + file.write("\{txt: \"{mmodule.full_name}\", url:\"{mmodule.url}\" \},") + file.write("],") + end + for mclass in model.mclasses do + if mclass.visibility < min_visibility then continue + file.write("\"{mclass.name}\": [") + file.write("\{txt: \"{mclass.full_name}\", url:\"{mclass.url}\" \},") + file.write("],") + end + var name2mprops = new HashMap[String, Set[MPropDef]] + for mproperty in model.mproperties do + if mproperty.visibility < min_visibility then continue + if mproperty isa MAttribute then continue + if not name2mprops.has_key(mproperty.name) then name2mprops[mproperty.name] = new HashSet[MPropDef] + name2mprops[mproperty.name].add_all(mproperty.mpropdefs) + end + for mproperty, mpropdefs in name2mprops do + file.write("\"{mproperty}\": [") + for mpropdef in mpropdefs do + file.write("\{txt: \"{mpropdef.full_name}\", url:\"{mpropdef.url}\" \},") end + file.write("],") end + file.write(" \};") + file.close + 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") +end - 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") +# Nitdoc base page +abstract class NitdocPage - 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") + var dot_dir: nullable String + var source: nullable String + var ctx: NitdocContext + var shareurl = "." - clear - add("\n\n\n\n\n") - write_to("{dir}/index.html") + init(ctx: NitdocContext) do + self.ctx = ctx + if ctx.opt_shareurl.value != null then shareurl = ctx.opt_shareurl.value.as(not null) 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  ") + protected fun head do + append("") + append("") + append("") + append("") + append("") + append("") + var title = "" + if ctx.opt_custom_title.value != null then + title = " | {ctx.opt_custom_title.value.to_s}" + end + append("{self.title}{title}") + end + + protected fun menu do + if ctx.opt_custom_menu_items.value != null then + append(ctx.opt_custom_menu_items.value.to_s) end - add("
") end - # Sorter of entities in alphabetical order - var _sorter: AlphaSorter[MMEntity] = new AlphaSorter[MMEntity] + protected fun title: String is abstract - # Sort entities in the alphabetical order - fun sort(array: Array[MMEntity]) - do - _sorter.sort(array) + protected fun header do + append("
") + append("") + append("
") end - readable writable var _owned_modules: Array[MMModule] = new Array[MMModule] + protected fun content is abstract - # 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 + protected fun footer do + if ctx.opt_custom_footer_text.value != null then + append("
{ctx.opt_custom_footer_text.value.to_s}
") end end - readable var _opt_dir: OptionString = new OptionString("Directory where doc is generated", "-d", "--dir") + # Generate a clickable graphviz image using a dot content + protected fun generate_dot(dot: String, name: String, alt: String) do + var output_dir = dot_dir + if output_dir == null then return + var file = new OFStream.open("{output_dir}/{name}.dot") + file.write(dot) + file.close + sys.system("\{ test -f {output_dir}/{name}.png && test -f {output_dir}/{name}.s.dot && diff {output_dir}/{name}.dot {output_dir}/{name}.s.dot >/dev/null 2>&1 ; \} || \{ cp {output_dir}/{name}.dot {output_dir}/{name}.s.dot && dot -Tpng -o{output_dir}/{name}.png -Tcmapx -o{output_dir}/{name}.map {output_dir}/{name}.s.dot ; \}") + append("
") + append("{alt}") + append("
") + var fmap = new IFStream.open("{output_dir}/{name}.map") + append(fmap.read_all) + fmap.close + end - redef fun perform_work(mods) + # Add a (source) link for a given location + protected fun show_source(l: Location): String do - dir.mkdir - - for mod in modules do - assert mod isa MMSrcModule - mod.extract_module_doc(self) + if source == null then + return "({l.file.filename.simplify_path})" + else + # THIS IS JUST UGLY ! (but there is no replace yet) + var x = source.split_with("%f") + source = x.join(l.file.filename.simplify_path) + x = source.split_with("%l") + source = x.join(l.line_start.to_s) + x = source.split_with("%L") + source = x.join(l.line_end.to_s) + return " (source)" end - self.extract_other_doc end - init - do - keep_ast = true - super("nitdoc") - option_context.add_option(opt_dir) + # Render the page as a html string + protected fun render do + append("") + append("") + head + append("") + append("") + header + var footed = "" + if ctx.opt_custom_footer_text.value != null then footed = "footed" + append("
") + content + append("
") + footer + append("") + + # piwik tracking + var tracker_url = ctx.opt_piwik_tracker.value + var site_id = ctx.opt_piwik_site_id.value + if tracker_url != null and site_id != null then + append("") + append("") + append("") + end + append("") end - redef fun process_options - do - super - var d = opt_dir.value - if d != null then dir = d + # Append a string to the page + fun append(s: String) do out.write(s) + + # Save html page in the specified file + fun save(file: String) do + self.out = new OFStream.open(file) + render + self.out.close end + private var out: nullable OFStream 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 +# The overview page +class NitdocOverview + super NitdocPage + private var mbuilder: ModelBuilder + private var mmodules = new Array[MModule] + + init(ctx: NitdocContext, dot_dir: nullable String) do + super(ctx) + self.mbuilder = ctx.mbuilder + self.dot_dir = dot_dir + # get modules + var mmodules = new HashSet[MModule] + for mmodule in mbuilder.model.mmodule_importation_hierarchy do + if mmodule.name == "
" then continue + var owner = mmodule.public_owner + if owner != null then + mmodules.add(owner) + else + mmodules.add(mmodule) + end + end + # sort modules + var sorter = new MModuleNameSorter + self.mmodules.add_all(mmodules) + sorter.sort(self.mmodules) + end - # Parent stage is any - readable var _parent: nullable StageContext = null + redef fun title do return "Overview" - init(parent: nullable StageContext) do _parent = parent -end + redef fun menu do + super + append("
  • Overview
  • ") + append("
  • Search
  • ") + end + redef fun content do + append("
    ") + var title = "Overview" + if ctx.opt_custom_title.value != null then + title = ctx.opt_custom_title.value.to_s + end + append("

    {title}

    ") + var text = "" + if ctx.opt_custom_overview_text.value != null then + text = ctx.opt_custom_overview_text.value.to_s + end + append("
    {text}
    ") + append("
    ") + # module list + append("

    Modules

    ") + append("
      ") + for mmodule in mmodules do + if mbuilder.mmodule2nmodule.has_key(mmodule) then + var amodule = mbuilder.mmodule2nmodule[mmodule] + append("
    • ") + mmodule.html_link(self) + append(" {amodule.short_comment}
    • ") + end + end + append("
    ") + # module graph + process_generate_dot + append("
    ") + append("
    ") + 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 + private fun process_generate_dot do + # build poset with public owners + var poset = new POSet[MModule] + for mmodule in mmodules do + poset.add_node(mmodule) + for omodule in mmodules do + if mmodule == omodule then continue + if mmodule.in_importation < omodule then + poset.add_node(omodule) + poset.add_edge(mmodule, omodule) + end + end end - if d.has_key(b) then - sb = d[b] - else - sb = b.to_s - d[b] = sb + # build graph + 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 mmodule in poset do + op.append("\"{mmodule.name}\"[URL=\"{mmodule.url}\"];\n") + for omodule in poset[mmodule].direct_greaters do + op.append("\"{mmodule.name}\"->\"{omodule.name}\";\n") + end end - return sa <=> sb + op.append("\}\n") + generate_dot(op.to_s, "dep", "Modules hierarchy") 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 +# The search page +class NitdocSearch + super NitdocPage - # Return a one liner description - fun short_doc: String do return " " + init(ctx: NitdocContext) do + super(ctx) + self.dot_dir = null + end - # The doc node from the AST - # Return null is none - fun doc: nullable ADoc do return null + redef fun title do return "Search" - # Human redable location of the entity (module/class/property) - fun locate(dctx: DocContext): String do return "" + redef fun menu do + super + append("
  • Overview
  • ") + append("
  • Search
  • ") + end - # Part of the prototype before the name (kind, modifiers, qualifier) - fun prototype_head(dctx: DocContext): String is abstract + redef fun content do + append("
    ") + append("

    {title}

    ") + module_column + classes_column + properties_column + append("
    ") + end - # Part of the property after the name (signature, modifiers) - fun prototype_body(dctx: DocContext): String do return "" -end + # Add to content modules column + private fun module_column do + var sorted = ctx.mbuilder.model.mmodule_importation_hierarchy.to_a + var sorter = new MModuleNameSorter + sorter.sort(sorted) + append("
    ") + append("

    Modules

    ") + append("
      ") + for mmodule in sorted do + if mmodule.name == "
      " then continue + append("
    • ") + mmodule.html_link(self) + append("
    • ") + end + append("
    ") + append("
    ") + end -redef class MMModule -special MMEntity - redef fun html_link(dctx) do - if dctx.module == self then - return "{self}" - else - return "{self}" - end + # Add to content classes modules + private fun classes_column do + var sorted = ctx.mbuilder.model.mclasses + var sorter = new MClassNameSorter + sorter.sort(sorted) + append("
    ") + append("

    Classes

    ") + append("
      ") + for mclass in sorted do + if mclass.visibility < ctx.min_visibility then continue + append("
    • ") + mclass.html_link(self) + append("
    • ") + end + append("
    ") + append("
    ") 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 + # Insert the properties column of fullindex page + private fun properties_column do + var sorted = ctx.mbuilder.model.mproperties + var sorter = new MPropertyNameSorter + sorter.sort(sorted) + append("
    ") + append("

    Properties

    ") + append("
      ") + for mproperty in sorted do + if mproperty.visibility < ctx.min_visibility then continue + if mproperty isa MAttribute then continue + append("
    • ") + mproperty.intro.html_link(self) + append(" (") + mproperty.intro.mclassdef.mclass.html_link(self) + append(")
    • ") + end + append("
    ") + append("
    ") 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 + +# A module page +class NitdocModule + super NitdocPage + + private var mmodule: MModule + private var mbuilder: ModelBuilder + private var local_mclasses = new HashSet[MClass] + private var intro_mclasses = new HashSet[MClass] + private var redef_mclasses = new HashSet[MClass] + + init(mmodule: MModule, ctx: NitdocContext, dot_dir: nullable String) do + super(ctx) + self.mmodule = mmodule + self.mbuilder = ctx.mbuilder + self.dot_dir = dot_dir + # get local mclasses + for m in mmodule.in_nesting.greaters do + for mclassdef in m.mclassdefs do + if mclassdef.mclass.visibility < ctx.min_visibility then continue + if mclassdef.is_intro then + intro_mclasses.add(mclassdef.mclass) + else + if mclassdef.mclass.mpropdefs_in_module(self).is_empty then continue + redef_mclasses.add(mclassdef.mclass) + end + local_mclasses.add(mclassdef.mclass) + end 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 + redef fun title do + if mbuilder.mmodule2nmodule.has_key(mmodule) and not mbuilder.mmodule2nmodule[mmodule].short_comment.is_empty then + var nmodule = mbuilder.mmodule2nmodule[mmodule] + return "{mmodule.html_name} module | {nmodule.short_comment}" else - return max + return "{mmodule.html_name} module" end end -end + redef fun menu do + super + append("
  • Overview
  • ") + append("
  • {mmodule.html_name}
  • ") + append("
  • Search
  • ") + 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)}" + redef fun content do + append("") + append("
    ") + module_doc + append("
    ") 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}" + private fun classes_column do + var sorter = new MClassNameSorter + var sorted = new Array[MClass] + sorted.add_all(intro_mclasses) + sorted.add_all(redef_mclasses) + sorter.sort(sorted) + if not sorted.is_empty then + append("") 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)}" + private fun importation_column do + append("") 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 + private fun display_module_list(list: Array[MModule]) do + append("
      ") + var sorter = new MModuleNameSorter + sorter.sort(list) + for m in list do + append("
    • ") + m.html_link(self) + append("
    • ") + end + append("
    ") 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 + private fun module_doc do + # title + append("

    {mmodule.html_name}

    ") + append("
    ") + mmodule.html_signature(self) + append("
    ") + # comment + mmodule.html_comment(self) + process_generate_dot + # classes + var class_sorter = new MClassNameSorter + # intro + if not intro_mclasses.is_empty then + var sorted = new Array[MClass] + sorted.add_all(intro_mclasses) + class_sorter.sort(sorted) + append("
    ") + append("

    Introduced classes

    ") + for mclass in sorted do mclass.html_full_desc(self) + append("
    ") + end + # redefs + var redefs = new Array[MClass] + for mclass in redef_mclasses do if not intro_mclasses.has(mclass) then redefs.add(mclass) + class_sorter.sort(redefs) + if not redefs.is_empty then + append("
    ") + append("

    Refined classes

    ") + for mclass in redefs do mclass.html_full_desc(self) + append("
    ") 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 isa MMMethod then - if s.is_abstract then - res.append(" is abstract") - else if s.is_intern then - res.append(" is intern") + private fun process_generate_dot do + # build poset with public owners + var poset = new POSet[MModule] + for mmodule in self.mmodule.in_importation.poset do + if mmodule.name == "
    " then continue + #if mmodule.public_owner != null then continue + if not mmodule.in_importation < self.mmodule and not self.mmodule.in_importation < mmodule and mmodule != self.mmodule then continue + poset.add_node(mmodule) + for omodule in mmodule.in_importation.poset do + if mmodule == omodule then continue + if omodule.name == "
    " then continue + if not omodule.in_importation < self.mmodule and not self.mmodule.in_importation < omodule then continue + if omodule.in_importation < mmodule then + poset.add_node(omodule) + poset.add_edge(omodule, mmodule) + end + if mmodule.in_importation < omodule then + poset.add_node(omodule) + poset.add_edge(mmodule, omodule) + end + #if omodule.public_owner != null then continue + #if mmodule.in_importation < omodule then + #poset.add_node(omodule) + #poset.add_edge(mmodule, omodule) + #end end end - return res.to_s + # build graph + var op = new Buffer + var name = "dep_{mmodule.name}" + 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 mmodule in poset do + if mmodule == self.mmodule then + op.append("\"{mmodule.name}\"[shape=box,margin=0.03];\n") + else + op.append("\"{mmodule.name}\"[URL=\"{mmodule.url}\"];\n") + end + for omodule in poset[mmodule].direct_greaters do + op.append("\"{mmodule.name}\"->\"{omodule.name}\";\n") + end + end + op.append("\}\n") + generate_dot(op.to_s, name, "Dependency graph for module {mmodule.name}") end +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 +# A class page +class NitdocClass + super NitdocPage + + private var mclass: MClass + private var vtypes = new HashSet[MVirtualTypeDef] + private var consts = new HashSet[MMethodDef] + private var meths = new HashSet[MMethodDef] + private var inherited = new HashSet[MPropDef] + + init(mclass: MClass, ctx: NitdocContext, dot_dir: nullable String, source: nullable String) do + super(ctx) + self.mclass = mclass + self.dot_dir = dot_dir + self.source = source + # load properties + var locals = new HashSet[MProperty] + for mclassdef in mclass.mclassdefs do + for mpropdef in mclassdef.mpropdefs do + if mpropdef.mproperty.visibility < ctx.min_visibility then continue + if mpropdef isa MVirtualTypeDef then vtypes.add(mpropdef) + if mpropdef isa MMethodDef then + if mpropdef.mproperty.is_init then + consts.add(mpropdef) + else + meths.add(mpropdef) + end + end + locals.add(mpropdef.mproperty) + end end - if global.intro == self then - return true + # get inherited properties + for pclass in mclass.in_hierarchy(ctx.mainmodule).greaters do + if pclass == mclass then continue + for pclassdef in pclass.mclassdefs do + for mprop in pclassdef.intro_mproperties do + var mpropdef = mprop.intro + if mprop.visibility < ctx.min_visibility then continue # skip if not correct visibiility + if locals.has(mprop) then continue # skip if local + if mclass.name != "Object" and mprop.intro_mclassdef.mclass.name == "Object" and (mprop.visibility <= protected_visibility or mprop.intro_mclassdef.mmodule.public_owner == null or mprop.intro_mclassdef.mmodule.public_owner.name != "standard") then continue # skip toplevels + if mpropdef isa MVirtualTypeDef then vtypes.add(mpropdef) + if mpropdef isa MMethodDef then + if mpropdef.mproperty.is_init then + consts.add(mpropdef) + else + meths.add(mpropdef) + end + end + inherited.add(mpropdef) + end + end 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 " " + redef fun title do + var nclass = ctx.mbuilder.mclassdef2nclassdef[mclass.intro] + if nclass isa AStdClassdef then + return "{mclass.html_name} class | {nclass.short_comment}" else - return global.intro.short_doc + return "{mclass.html_name} class" 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 + + redef fun menu do + super + append("
  • Overview
  • ") + var public_owner = mclass.public_owner + if public_owner == null then + append("
  • ") + mclass.intro_mmodule.html_link(self) + append("
  • ") else - return d + append("
  • ") + public_owner.html_link(self) + append("
  • ") end + append("
  • {mclass.html_name}
  • ") + append("
  • Search
  • ") 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 - # Extract and generate html file for the module - fun extract_module_doc(dctx: DocContext) - do - dctx.info("Generating HTML for module {name}",1) - 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 + redef fun content do + append("") + append("
    ") + class_doc + append("
    ") + end - 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 + private fun properties_column do + var sorter = new MPropDefNameSorter + append("") 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}" + private fun inheritance_column do + var sorted = new Array[MClass] + var sorterp = new MClassNameSorter + append("
    \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 + append("") + end + + private fun class_doc do + # title + append("

    {mclass.html_name}{mclass.html_short_signature}

    ") + append("
    ") + if mclass.visibility < public_visibility then append("{mclass.visibility.to_s} ") + append("{mclass.kind.to_s} ") + mclass.html_namespace(self) + append("{mclass.html_short_signature}
    ") + # comment + mclass.html_comment(self) + process_generate_dot + # concerns + var concern2meths = new ArrayMap[MModule, Array[MMethodDef]] + var sorted_meths = new Array[MMethodDef] + var sorted = new Array[MModule] + sorted_meths.add_all(meths) + ctx.mainmodule.linearize_mpropdefs(sorted_meths) + for meth in meths do + if inherited.has(meth) then continue + var mmodule = meth.mclassdef.mmodule + if not concern2meths.has_key(mmodule) then + sorted.add(mmodule) + concern2meths[mmodule] = new Array[MMethodDef] + end + concern2meths[mmodule].add(meth) + end + var sections = new ArrayMap[MModule, Array[MModule]] + for mmodule in concern2meths.keys do + var owner = mmodule.public_owner + if owner == null then owner = mmodule + if not sections.has_key(owner) then sections[owner] = new Array[MModule] + if owner != mmodule then sections[owner].add(mmodule) + end + append("
    ") + append("

    Concerns

    ") + append("
      ") + for owner, mmodules in sections do + var nowner = ctx.mbuilder.mmodule2nmodule[owner] + append("
    • ") + if nowner.short_comment.is_empty then + append("{owner.html_name}") 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 + append("{owner.html_name}: {nowner.short_comment}") + end + if not mmodules.is_empty then + append("
        ") + for mmodule in mmodules do + var nmodule = ctx.mbuilder.mmodule2nmodule[mmodule] + if nmodule.short_comment.is_empty then + append("
      • {mmodule.html_name}
      • ") + else + append("
      • {mmodule.html_name}: {nmodule.short_comment}
      • ") + end + end + append("
      ") + end + append("
    • ") + end + append("
    ") + append("
    ") + # properties + var prop_sorter = new MPropDefNameSorter + var lmmodule = new List[MModule] + var nclass = ctx.mbuilder.mclassdef2nclassdef[mclass.intro] + # virtual and formal types + var local_vtypes = new Array[MVirtualTypeDef] + for vt in vtypes do if not inherited.has(vt) then local_vtypes.add(vt) + if local_vtypes.length > 0 or mclass.arity > 0 then + append("
    ") + append("

    Formal and Virtual Types

    ") + # formal types + if mclass.arity > 0 and nclass isa AStdClassdef then + for ft, bound in mclass.parameter_types do + append("
    ") + append("

    {ft}: ") + bound.html_link(self) + append("

    ") + append("
    formal generic type
    ") + append("
    ") + end + end + # virtual types + prop_sorter.sort(local_vtypes) + for prop in local_vtypes do prop.html_full_desc(self, self.mclass) + append("
    ") + end + # constructors + var local_consts = new Array[MMethodDef] + for const in consts do if not inherited.has(const) then local_consts.add(const) + prop_sorter.sort(local_consts) + if local_consts.length > 0 then + append("
    ") + append("

    Constructors

    ") + for prop in local_consts do prop.html_full_desc(self, self.mclass) + append("
    ") + end + # methods + if not concern2meths.is_empty then + append("
    ") + append("

    Methods

    ") + for owner, mmodules in sections do + append("") + if owner != mclass.intro_mmodule and owner != mclass.public_owner then + var nowner = ctx.mbuilder.mmodule2nmodule[owner] + append("

    Methods refined in ") + owner.html_link(self) + append("

    ") + append("

    ") + owner.html_link(self) + if not nowner.short_comment.is_empty then + append(": {nowner.short_comment}") + end + append("

    ") + end + if concern2meths.has_key(owner) then + var mmethods = concern2meths[owner] + prop_sorter.sort(mmethods) + for prop in mmethods do prop.html_full_desc(self, self.mclass) + end + for mmodule in mmodules do + append("") + var nmodule = ctx.mbuilder.mmodule2nmodule[mmodule] + if mmodule != mclass.intro_mmodule and mmodule != mclass.public_owner then + append("

    ") + mmodule.html_link(self) + if not nmodule.short_comment.is_empty then + append(": {nmodule.short_comment}") end + append("

    ") end + var mmethods = concern2meths[mmodule] + prop_sorter.sort(mmethods) + for prop in mmethods do prop.html_full_desc(self, self.mclass) + end + end + append("
    ") + end + # inherited properties + if inherited.length > 0 then + var sorted_inherited = new Array[MPropDef] + sorted_inherited.add_all(inherited) + ctx.mainmodule.linearize_mpropdefs(sorted_inherited) + var classes = new ArrayMap[MClass, Array[MPropDef]] + for mmethod in sorted_inherited.reversed do + var mclass = mmethod.mclassdef.mclass + if not classes.has_key(mclass) then classes[mclass] = new Array[MPropDef] + classes[mclass].add(mmethod) + end + append("
    ") + append("

    Inherited Properties

    ") + for c, mmethods in classes do + prop_sorter.sort(mmethods) + append("

    Defined in ") + c.html_link(self) + append(": ") + for i in [0..mmethods.length[ do + var mmethod = mmethods[i] + mmethod.html_link(self) + if i <= mmethods.length - 1 then append(", ") end + append("

    ") end + append("
    ") 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") + private fun process_generate_dot do + var pe = ctx.class_hierarchy[mclass] + var cla = new HashSet[MClass] + var sm = new HashSet[MClass] + var sm2 = new HashSet[MClass] + sm.add(mclass) + 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(pe.poset[x].direct_smallers) + end + var t = sm + sm = sm2 + sm2 = t + end + cla.add_all(pe.greaters) + + var op = new Buffer + var name = "dep_{mclass.name}" + 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 == mclass then + op.append("\"{c.name}\"[shape=box,margin=0.03];\n") + else + op.append("\"{c.name}\"[URL=\"{c.url}\"];\n") + end + for c2 in pe.poset[c].direct_greaters do + if not cla.has(c2) then continue + op.append("\"{c.name}\"->\"{c2.name}\";\n") + end + if not pe.poset[c].direct_smallers.is_empty then + var others = true + for c2 in pe.poset[c].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 - dctx.add("
    Class Summary of {self}
    {c.prototype_head(dctx)}{c.html_link(dctx)}{c.prototype_body(dctx)}
    {c.short_doc}

    \n") end + op.append("\}\n") + generate_dot(op.to_s, name, "Dependency graph for class {mclass.name}") + end +end - if not new_classes.is_empty then - dctx.add("\n") - dctx.add("\n") - dctx.add("
    Class Detail of {self}
    \n") +# +# Model redefs +# - for c in new_classes do - c.extract_class_doc(dctx) +redef class MModule + # Return the HTML escaped name of the module + private fun html_name: String do return name.html_escape + + # URL to nitdoc page + # module_owner_name.html + private fun url: String do + if url_cache == null then + var res = new Buffer + res.append("module_") + var mowner = public_owner + if mowner != null then + res.append("{public_owner.name}_") end + res.append("{self.name}.html") + url_cache = res.to_s end - - dctx.add("\n") + return url_cache.as(not null) end - - redef fun short_doc - do - var d = doc - if d != null then - return d.short - else - return " " + private var url_cache: nullable String + + # html anchor id for the module in a nitdoc page + # MOD_owner_name + private fun anchor: String do + if anchor_cache == null then + var res = new Buffer + res.append("MOD_") + var mowner = public_owner + if mowner != null then + res.append("{public_owner.name}_") + end + res.append(self.name) + anchor_cache = res.to_s end + return anchor_cache.as(not null) end - - redef fun doc - do - var n = node - if n.n_packagedecl == null then - return null - end - var np = n.n_packagedecl - var d = np.n_doc - if d == null then - return null - end - if d.n_comment.is_empty then - return null - else - return d + private var anchor_cache: nullable String + + # Return a link (html a tag) to the nitdoc module page + # html_name + private fun html_link(page: NitdocPage) do + if html_link_cache == null then + var res = new Buffer + if page.ctx.mbuilder.mmodule2nmodule.has_key(self) then + res.append("{html_name}") + else + res.append("{html_name}") + end + html_link_cache = res.to_s end + page.append(html_link_cache.as(not null)) 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 + private var html_link_cache: nullable String + + # Return the module signature decorated with html + # module html_full_namespace + private fun html_signature(page: NitdocPage) do + page.append("module ") + html_full_namespace(page) + page.append("") end - # Oneliner transcription of the doc - fun short: String - do - return n_comment.first.text.substring_from(1) + # Return the module full namespace decorated with html + # public_owner.html_namespace::html_link + private fun html_full_namespace(page: NitdocPage) do + page.append("") + var mowner = public_owner + if mowner != null then + public_owner.html_namespace(page) + page.append("::") + end + html_link(page) + page.append("") 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}" + # Return the module full namespace decorated with html + # public_owner.html_namespace + private fun html_namespace(page: NitdocPage) do + page.append("") + var mowner = public_owner + if mowner != null then + public_owner.html_namespace(page) else - return "{self}" + html_link(page) end + page.append("") end - redef fun short_doc do return global.intro.short_doc - - redef fun doc do return global.intro.doc + # Return the full comment of the module decorated with html + private fun html_comment(page: NitdocPage) do + page.append("
    ") + if page.ctx.mbuilder.mmodule2nmodule.has_key(self) then + var nmodule = page.ctx.mbuilder.mmodule2nmodule[self] + if page.ctx.github_gitdir != null then + var loc = nmodule.doc_location.github(page.ctx.github_gitdir.as(not null)) + page.append("") + end + if nmodule.full_comment == "" then + page.append("

    ") + page.append("no comment for ") + else + page.append("

    {nmodule.full_markdown}
    ") + page.append("

    ") + end + page.append("definition in ") + self.html_full_namespace(page) + page.append(" {page.show_source(nmodule.location)}

    ") + end + page.append("
    ") + end - 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 + private fun has_mclassdef_for(mclass: MClass): Bool do + for mmodule in self.in_nesting.greaters do + for mclassdef in mmodule.mclassdefs do + if mclassdef.mclass == mclass then return true end end return false end - redef fun locate(dctx) do return "in {module.html_link(dctx)}" + private fun has_mclassdef(mclassdef: MClassDef): Bool do + for mmodule in self.in_nesting.greaters do + for oclassdef in mmodule.mclassdefs do + if mclassdef == oclassdef then return true + end + end + return false + end +end - fun known_intro(dctx: DocContext): MMLocalClass do return dctx.known_owner_of(global.intro.module)[global] +redef class MClass + # Return the HTML escaped name of the module + private fun html_name: String do return name.html_escape - 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 + # URL to nitdoc page + # class_owner_name.html + private fun url: String do + return "class_{public_owner}_{name}.html" 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("]") + # html anchor id for the class in a nitdoc page + # MOD_owner_name + private fun anchor: String do + if anchor_cache == null then + anchor_cache = "CLASS_{public_owner.name}_{name}" end - return res.to_s + return anchor_cache.as(not null) 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 + private var anchor_cache: nullable String + + # Return a link (with signature) to the nitdoc class page + # html_name(signature) + private fun html_link(page: NitdocPage) do + if html_link_cache == null then + var res = new Buffer + res.append("Previous refinements in:
    {sup2.join(", ")}\n") + res.append(">{html_name}{html_short_signature}") + html_link_cache = res.to_s 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)) + page.append(html_link_cache.as(not null)) + end + private var html_link_cache: nullable String + + # Return a short link (without signature) to the nitdoc class page + # html_name + private fun html_short_link(page: NitdocPage) do + if html_short_link_cache == null then + var res = new Buffer + res.append("All superclasses:
    {sup2.join(", ")}\n") + res.append(">{html_name}") + html_short_link_cache = res.to_s 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)}") + page.append(html_short_link_cache.as(not null)) + end + private var html_short_link_cache: nullable String + + # Return a link (with signature) to the class anchor + # html_name + private fun html_link_anchor(page: NitdocPage) do + if html_link_anchor_cache == null then + var res = new Buffer + res.append("{html_name}{html_short_signature}") + html_link_anchor_cache = res.to_s 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)) + page.append(html_link_anchor_cache.as(not null)) + end + private var html_link_anchor_cache: nullable String + + # Return the generic signature of the class with bounds + # [E: MType, F: MType] + private fun html_signature(page: NitdocPage) do + if arity > 0 then + page.append("[") + for i in [0..intro.parameter_names.length[ do + page.append("{intro.parameter_names[i]}: ") + intro.bound_mtype.arguments[i].html_link(page) + if i < intro.parameter_names.length - 1 then page.append(", ") end + page.append("]") end - if not sup2.is_empty then - dctx.add("
    Refinements in known modules:
    {sup2.join(", ")}\n") - end - dctx.add("
    \n") + end - var doc = doc - if doc != null then - dctx.add("
    {doc.to_html}
    \n") + # Return the generic signature of the class without bounds + # [E, F] + private fun html_short_signature: String do + if arity > 0 then + return "[{intro.parameter_names.join(", ")}]" + else + return "" end + 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") + # Return the class namespace decorated with html + # intro_module::html_short_link + private fun html_namespace(page: NitdocPage) do + intro_mmodule.html_namespace(page) + page.append("::") + html_short_link(page) + page.append("") end - fun pass_name(pass: Int): String - do - var names = once ["Virtual Types", "Consructors", "Methods", "Attributes"] - return names[pass] + # Return a list item for the mclass + #
  • html_link
  • + private fun html_sidebar_item(page: NitdocModule) do + if page.mmodule.in_nesting.greaters.has(intro.mmodule) then + page.append("
  • ") + page.append("I") + html_link_anchor(page) + else if page.mmodule.has_mclassdef_for(self) then + page.append("
  • ") + page.append("R") + html_link_anchor(page) + else + page.append("
  • ") + page.append("H") + html_link(page) + end + page.append("
  • ") 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 + + private fun html_full_desc(page: NitdocModule) do + var is_redef = not page.mmodule.in_nesting.greaters.has(intro.mmodule) + var redefs = mpropdefs_in_module(page) + if not is_redef or not redefs.is_empty then + var classes = new Array[String] + classes.add(kind.to_s) + if is_redef then classes.add("redef") + classes.add(visibility.to_s) + page.append("
    ") + page.append("

    ") + page.append("") + html_short_link(page) + html_signature(page) + page.append("

    ") + html_info(page) + html_comment(page) + page.append("
    ") 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 + private fun html_info(page: NitdocModule) do + page.append("
    ") + if visibility < public_visibility then page.append("{visibility.to_s} ") + if not page.mmodule.in_nesting.greaters.has(intro.mmodule) then page.append("redef ") + page.append("{kind} ") + html_namespace(page) + page.append("{html_short_signature}
    ") + 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 + private fun html_comment(page: NitdocPage) do + page.append("
    ") + if page isa NitdocModule then + page.mmodule.linearize_mclassdefs(mclassdefs) + # comments for each mclassdef contained in current mmodule + for mclassdef in mclassdefs do + if not mclassdef.is_intro and not page.mmodule.mclassdefs.has(mclassdef) then continue + if page.ctx.mbuilder.mclassdef2nclassdef.has_key(mclassdef) then + var nclass = page.ctx.mbuilder.mclassdef2nclassdef[mclassdef] + if nclass isa AStdClassdef then + if page.ctx.github_gitdir != null then + var loc = nclass.doc_location.github(page.ctx.github_gitdir.as(not null)) + page.append("") end - else - if mmap.has_key(km) then - props = mmap[km] + if nclass.full_comment == "" then + page.append("

    ") + page.append("no comment for ") else - props = new Array[MMLocalProperty] - mmap[km] = props + page.append("

    {nclass.full_markdown}
    ") + page.append("

    ") 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]) + if mclassdef.is_intro then + page.append("introduction in ") + else + page.append("refinement in ") end + mclassdef.mmodule.html_full_namespace(page) + page.append(" {page.show_source(nclass.location)}

    ") 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) + else + # comments for intro + if page.ctx.mbuilder.mclassdef2nclassdef.has_key(intro) then + var nclass = page.ctx.mbuilder.mclassdef2nclassdef[intro] + if nclass isa AStdClassdef then + if page.ctx.github_gitdir != null then + var loc = nclass.doc_location.github(page.ctx.github_gitdir.as(not null)) + page.append("") + end + if nclass.full_comment == "" then + page.append("

    ") + page.append("no comment for ") + else + page.append("

    {nclass.full_markdown}
    ") + page.append("

    ") + end + page.append("introduction in ") + intro.mmodule.html_full_namespace(page) + page.append(" {page.show_source(nclass.location)}

    ") 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") + page.append("") + end + + private fun mpropdefs_in_module(page: NitdocModule): Array[MPropDef] do + var res = new Array[MPropDef] + page.mmodule.linearize_mclassdefs(mclassdefs) + for mclassdef in mclassdefs do + if not page.mmodule.mclassdefs.has(mclassdef) then continue + if mclassdef.is_intro then continue + for mpropdef in mclassdef.mpropdefs do + if mpropdef.mproperty.visibility < page.ctx.min_visibility then continue + if mpropdef isa MAttributeDef then continue + res.add(mpropdef) + end 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 + return res + end +end - dctx.close_stage - return new_props +redef class MProperty + # Escape name for html output + private fun html_name: String do return name.html_escape + + # Return the property namespace decorated with html + # intro_module::intro_class::html_link + private fun html_namespace(page: NitdocPage) do + intro_mclassdef.mclass.html_namespace(page) + page.append(intro_mclassdef.mclass.html_short_signature) + page.append("::") + intro.html_link(page) + page.append("") end +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 +redef class MType + # Link to the type definition in the nitdoc page + private fun html_link(page: NitdocPage) is abstract +end + +redef class MClassType + redef fun html_link(page) do mclass.html_link(page) +end + +redef class MNullableType + redef fun html_link(page) do + page.append("nullable ") + mtype.html_link(page) + end +end - dctx.open_stage - dctx.stage("
    \n") +redef class MGenericType + redef fun html_link(page) do + page.append("{mclass.html_name}[") + for i in [0..arguments.length[ do + arguments[i].html_link(page) + if i < arguments.length - 1 then page.append(", ") end - dctx.close_stage + page.append("]") + end +end - dctx.close_stage +redef class MParameterType + redef fun html_link(page) do + var name = mclass.intro.parameter_names[rank] + page.append("{name}") end +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 +redef class MVirtualType + redef fun html_link(page) do mproperty.intro.html_link(page) +end + +redef class MClassDef + # Return the classdef namespace decorated with html + private fun html_namespace(page: NitdocPage) do + mmodule.html_full_namespace(page) + page.append("::") + mclass.html_link(page) + page.append("") + end +end + +redef class MPropDef + # Return the full qualified name of the mpropdef + # module::classdef::name + private fun full_name: String do + return "{mclassdef.mclass.public_owner.name}::{mclassdef.mclass.name}::{mproperty.name}" + end + + # URL into the nitdoc page + # class_owner_name.html#anchor + private fun url: String do + if url_cache == null then + url_cache = "{mclassdef.mclass.url}#{anchor}" + end + return url_cache.as(not null) + end + private var url_cache: nullable String + + # html anchor id for the property in a nitdoc class page + # PROP_mclass_propertyname + private fun anchor: String do + if anchor_cache == null then + anchor_cache = "PROP_{mclassdef.mclass.public_owner.name}_{mproperty.name.replace(" ", "_")}" end - if not properties.is_empty then - var s: String - if heir.global == global then - s = module.html_link(dctx) + return anchor_cache.as(not null) + end + private var anchor_cache: nullable String + + # Return a link to property into the nitdoc class page + # html_name + private fun html_link(page: NitdocPage) do + if html_link_cache == null then + var res = new Buffer + if page.ctx.mbuilder.mpropdef2npropdef.has_key(self) then + var nprop = page.ctx.mbuilder.mpropdef2npropdef[self] + res.append("{mproperty.html_name}") else - s = self.html_link(dctx) + res.append("{mproperty.html_name}") end - dctx.add("in {s}{properties.join(", ")}\n") + html_link_cache = res.to_s end + page.append(html_link_cache.as(not null)) 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 " " + private var html_link_cache: nullable String + + # Return a list item for the mpropdef + #
  • html_link
  • + private fun html_sidebar_item(page: NitdocClass) do + if is_intro and mclassdef.mclass == page.mclass then + page.append("
  • ") + page.append("I") + else if is_intro and mclassdef.mclass != page.mclass then + page.append("
  • ") + page.append("H") else - var bc = global.intro - return bc.short_doc + page.append("
  • ") + page.append("R") end + html_link(page) + page.append("
  • ") end - redef fun doc - do - var n = node - if not n isa AStdClassdef then - return null + private fun html_full_desc(page: NitdocPage, ctx: MClass) is abstract + private fun html_info(page: NitdocPage, ctx: MClass) is abstract + + private fun html_comment(page: NitdocPage) do + page.append("
    ") + if not is_intro then + if page.ctx.mbuilder.mpropdef2npropdef.has_key(mproperty.intro) then + var intro_nprop = page.ctx.mbuilder.mpropdef2npropdef[mproperty.intro] + if page.ctx.github_gitdir != null then + var loc = intro_nprop.doc_location.github(page.ctx.github_gitdir.as(not null)) + page.append("") + end + if intro_nprop.full_comment.is_empty then + page.append("

    ") + page.append("no comment for ") + else + page.append("

    {intro_nprop.full_markdown}
    ") + page.append("

    ") + end + page.append("introduction in ") + mproperty.intro.mclassdef.html_namespace(page) + page.append(" {page.show_source(intro_nprop.location)}

    ") + end end - var d = n.n_doc - if d == null then - return null + if page.ctx.mbuilder.mpropdef2npropdef.has_key(self) then + var nprop = page.ctx.mbuilder.mpropdef2npropdef[self] + if page.ctx.github_gitdir != null then + var loc = nprop.doc_location.github(page.ctx.github_gitdir.as(not null)) + page.append("") + end + if nprop.full_comment == "" then + page.append("

    ") + page.append("no comment for ") + else + page.append("

    {nprop.full_markdown}
    ") + page.append("

    ") + end + if is_intro then + page.append("introduction in ") + else + page.append("redefinition in ") + end + mclassdef.html_namespace(page) + page.append(" {page.show_source(nprop.location)}

    ") end - if d.n_comment.is_empty then - return null + page.append("
    ") + end +end + +redef class MMethodDef + redef fun html_full_desc(page, ctx) do + var classes = new Array[String] + var is_redef = mproperty.intro_mclassdef.mclass != ctx + if mproperty.is_init then + classes.add("init") else - return d - end + classes.add("fun") + end + if is_redef then classes.add("redef") + classes.add(mproperty.visibility.to_s) + page.append("
    ") + if page.ctx.mbuilder.mpropdef2npropdef.has_key(self) then + page.append("

    ") + page.append("{mproperty.html_name}") + msignature.html_signature(page) + page.append("

    ") + else + page.append("

    ") + page.append("init") + msignature.html_signature(page) + page.append("

    ") + end + html_info(page, ctx) + html_comment(page) + page.append("
    ") 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 + redef fun html_info(page, ctx) do + page.append("
    ") + if mproperty.visibility < public_visibility then page.append("{mproperty.visibility.to_s} ") + if mproperty.intro_mclassdef.mclass != ctx then page.append("redef ") + if mproperty.is_init then + page.append("init ") + else + page.append("fun ") end - for p in src_local_properties do - if p.need_doc(dctx) then - return true + mproperty.html_namespace(page) + page.append("
    ") + end +end + +redef class MVirtualTypeDef + redef fun html_full_desc(page, ctx) do + var is_redef = mproperty.intro_mclassdef.mclass != ctx + var classes = new Array[String] + classes.add("type") + if is_redef then classes.add("redef") + classes.add(mproperty.visibility.to_s) + page.append("
    ") + page.append("

    {mproperty.html_name}: ") + bound.html_link(page) + page.append("

    ") + html_info(page, ctx) + html_comment(page) + page.append("
    ") + end + + redef fun html_info(page, ctx) do + page.append("
    ") + if mproperty.intro_mclassdef.mclass != ctx then page.append("redef ") + page.append("type ") + mproperty.html_namespace(page) + page.append("
    ") + end +end + +redef class MSignature + private fun html_signature(page: NitdocPage) do + if not mparameters.is_empty then + page.append("(") + for i in [0..mparameters.length[ do + mparameters[i].html_link(page) + if i < mparameters.length - 1 then page.append(", ") end + page.append(")") + end + if return_mtype != null then + page.append(": ") + return_mtype.html_link(page) end - return super end -end -redef class MMSignature - # Htlm transcription of the signature (with nested links) - fun to_html(dctx: DocContext): String - do + private fun untyped_signature(page: NitdocPage): String do var res = new Buffer - if arity > 0 then + if not mparameters.is_empty 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)) + for i in [0..mparameters.length[ do + res.append(mparameters[i].name) + if i < mparameters.length - 1 then res.append(", ") 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 +redef class MParameter + private fun html_link(page: NitdocPage) do + page.append("{name}: ") + mtype.html_link(page) + if is_vararg then page.append("...") + end end -redef class MMTypeSimpleClass - redef fun html_link(dctx) do return local_class.html_link(dctx) +# +# Nodes redefs +# + +redef class Location + fun github(gitdir: String): String do + var base_dir = getcwd.join_path(gitdir).simplify_path + var file_loc = getcwd.join_path(file.filename).simplify_path + var gith_loc = file_loc.substring(base_dir.length + 1, file_loc.length) + return "{gith_loc}:{line_start},{column_start}--{line_end},{column_end}" + end end -redef class MMTypeGeneric - redef fun html_link(dctx) - do +redef class ADoc + private fun short_comment: String do + return n_comment.first.text.substring_from(2).replace("\n", "").html_escape + end + + private fun full_comment: String 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 + for t in n_comment do + var text = t.text + text = text.substring_from(1) + if text.first == ' ' then text = text.substring_from(1) + res.append(text.html_escape) + end + var str = res.to_s + return str.substring(0, str.length - 1) + end +end + +redef class AModule + private fun short_comment: String do + if n_moduledecl != null and n_moduledecl.n_doc != null then + return n_moduledecl.n_doc.short_comment + end + return "" + end + + private fun full_comment: String do + if n_moduledecl != null and n_moduledecl.n_doc != null then + return n_moduledecl.n_doc.full_comment + end + return "" + end + + private fun full_markdown: String do + if n_moduledecl != null and n_moduledecl.n_doc != null then + return n_moduledecl.n_doc.full_markdown.html + end + return "" + end + + # The doc location or the first line of the block if doc node is null + private fun doc_location: Location do + if n_moduledecl != null and n_moduledecl.n_doc != null then + return n_moduledecl.n_doc.location + end + var l = location + return new Location(l.file, l.line_start, l.line_start, l.column_start, l.column_start) + end +end + +redef class AStdClassdef + private fun short_comment: String do + if n_doc != null then return n_doc.short_comment + return "" + end + + private fun full_comment: String do + if n_doc != null then return n_doc.full_comment + return "" + end + + private fun full_markdown: String do + if n_doc != null then return n_doc.full_markdown.html + return "" + end + + # The doc location or the first line of the block if doc node is null + private fun doc_location: Location do + if n_doc != null then return n_doc.location + var l = location + return new Location(l.file, l.line_start, l.line_start, l.column_start, l.column_start) end end -var c = new DocContext -c.exec_cmd_line +redef class APropdef + private fun short_comment: String do + if n_doc != null then return n_doc.short_comment + return "" + end + + private fun full_comment: String do + if n_doc != null then return n_doc.full_comment + return "" + end + + private fun full_markdown: String do + if n_doc != null then return n_doc.full_markdown.html + return "" + end + + private fun doc_location: Location do + if n_doc != null then return n_doc.location + var l = location + return new Location(l.file, l.line_start, l.line_start, l.column_start, l.column_start) + + end +end + + +var nitdoc = new NitdocContext +nitdoc.generate_nitdoc