-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, "<main>", new Location(null, 0, 0, 0, 0))
- mainmodule.set_imported_mmodules(mmodules)
- end
- self.class_hierarchy = mainmodule.flatten_mclass_hierarchy
- end
-
- 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
- source = opt_source.value
- end
-
- 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
-
- private fun overview do
- var overviewpage = new NitdocOverview(self, dot_dir)
- overviewpage.save("{output_dir.to_s}/index.html")
- end
-
- private fun search do
- var searchpage = new NitdocSearch(self)
- searchpage.save("{output_dir.to_s}/search.html")
- end
-
- private fun modules do
- for mmodule in model.mmodules do
- if mmodule.name == "<main>" then continue
- var modulepage = new NitdocModule(mmodule, self, dot_dir)
- modulepage.save("{output_dir.to_s}/{mmodule.url}")
- end
- end
-
- 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
-
- 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 == "<main>" 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
-
-end
-
-# Nitdoc base page
-abstract class NitdocPage
-
- var dot_dir: nullable String
- var source: nullable String
- var ctx: NitdocContext
- var shareurl = "."
-
- init(ctx: NitdocContext) do
- self.ctx = ctx
- if ctx.opt_shareurl.value != null then shareurl = ctx.opt_shareurl.value.as(not null)
- end
-
- protected fun head do
- append("<meta charset='utf-8'/>")
- append("<link rel='stylesheet' href='{shareurl}/styles/main.css' type='text/css' media='screen'/>")
- append("<link rel='stylesheet' href='{shareurl}/styles/Nitdoc.UI.css' type='text/css' media='screen'/>")
- append("<link rel='stylesheet' href='{shareurl}/styles/Nitdoc.QuickSearch.css' type='text/css' media='screen'/>")
- append("<link rel='stylesheet' href='{shareurl}/styles/Nitdoc.GitHub.css' type='text/css' media='screen'/>")
- var title = ""
- if ctx.opt_custom_title.value != null then
- title = " | {ctx.opt_custom_title.value.to_s}"
- end
- append("<title>{self.title}{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
- end
-
- protected fun title: String is abstract
-
- protected fun header do
- append("<header>")
- append("<nav class='main'>")
- append("<ul>")
- menu
- append("</ul>")
- append("</nav>")
- append("</header>")
- end
-
- protected fun content is abstract
-
- protected fun footer do
- if ctx.opt_custom_footer_text.value != null then
- append("<footer>{ctx.opt_custom_footer_text.value.to_s}</footer>")
- end
- end
-
- # 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("<article class='graph'>")
- append("<img src='{name}.png' usemap='#{name}' style='margin:auto' alt='{alt}'/>")
- append("</article>")
- var fmap = new IFStream.open("{output_dir}/{name}.map")
- append(fmap.read_all)
- fmap.close
- end
-
- # Add a (source) link for a given location
- protected fun show_source(l: Location): String
- do
- 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 " (<a target='_blank' title='Show source' href=\"{source.to_s}\">source</a>)"
- end
- end
-
- # Render the page as a html string
- protected fun render do
- append("<!DOCTYPE html>")
- append("<head>")
- head
- append("</head>")
- append("<body")
- append(" data-bootstrap-share='{shareurl}'")
- if ctx.opt_github_upstream.value != null and ctx.opt_github_base_sha1.value != null then
- append(" data-github-upstream='{ctx.opt_github_upstream.value.as(not null)}'")
- append(" data-github-base-sha1='{ctx.opt_github_base_sha1.value.as(not null)}'")
- end
- append(">")
- header
- var footed = ""
- if ctx.opt_custom_footer_text.value != null then footed = "footed"
- append("<div class='page {footed}'>")
- content
- append("</div>")
- footer
- append("<script data-main=\"{shareurl}/js/nitdoc\" src=\"{shareurl}/js/lib/require.js\"></script>")
-
- # 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("<!-- Piwik -->")
- append("<script type=\"text/javascript\">")
- append(" var _paq = _paq || [];")
- append(" _paq.push([\"trackPageView\"]);")
- append(" _paq.push([\"enableLinkTracking\"]);")
- append(" (function() \{")
- append(" var u=((\"https:\" == document.location.protocol) ? \"https\" : \"http\") + \"://{tracker_url}\";")
- append(" _paq.push([\"setTrackerUrl\", u+\"piwik.php\"]);")
- append(" _paq.push([\"setSiteId\", \"{site_id}\"]);")
- append(" var d=document, g=d.createElement(\"script\"), s=d.getElementsByTagName(\"script\")[0]; g.type=\"text/javascript\";")
- append(" g.defer=true; g.async=true; g.src=u+\"piwik.js\"; s.parentNode.insertBefore(g,s);")
- append(" \})();")
- append(" </script>")
- append("<!-- End Piwik Code -->")
- end
- append("</body>")
- end
-
- # 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
-
-# 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 == "<main>" 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
-
- redef fun title do return "Overview"
-
- redef fun menu do
- super
- append("<li class='current'>Overview</li>")
- append("<li><a href='search.html'>Search</a></li>")
- end
-
- redef fun content do
- append("<div class='content fullpage'>")
- var title = "Overview"
- if ctx.opt_custom_title.value != null then
- title = ctx.opt_custom_title.value.to_s
- end
- append("<h1>{title}</h1>")
- var text = ""
- if ctx.opt_custom_overview_text.value != null then
- text = ctx.opt_custom_overview_text.value.to_s
- end
- append("<article class='overview'>{text}</article>")
- append("<article class='overview'>")
- # module list
- append("<h2>Modules</h2>")
- append("<ul>")
- for mmodule in mmodules do
- if mbuilder.mmodule2nmodule.has_key(mmodule) then
- var amodule = mbuilder.mmodule2nmodule[mmodule]
- append("<li>")
- mmodule.html_link(self)
- append(" {amodule.short_comment}</li>")
- end
- end
- append("</ul>")
- # module graph
- process_generate_dot
- append("</article>")
- append("</div>")
- end
-
- 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
- # 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
- op.append("\}\n")
- generate_dot(op.to_s, "dep", "Modules hierarchy")
- end
-end
-
-# The search page
-class NitdocSearch
- super NitdocPage