nitdoc: add --no-dot to skip graph generation
[nit.git] / src / nitdoc.nit
index 7777177..23cf6b8 100644 (file)
@@ -83,6 +83,7 @@ class DocContext
        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_nodot: OptionBool = new OptionBool("Do not generate graphes with graphviz", "--no-dot")
 
        fun public_only: Bool
        do
@@ -142,16 +143,18 @@ class DocContext
                        m.tmhe_ = tmh.add(m, pub)
                end
 
-               var head = "<script type=\"text/javascript\" src=\"http://moz-concept.com/nitdoc/scripts/jquery-1.7.1.min.js\"></script>\n" + 
+               var head = "<meta charset=\"utf-8\">" +
+                       "<script type=\"text/javascript\" src=\"http://moz-concept.com/nitdoc/scripts/jquery-1.7.1.min.js\"></script>\n" + 
                        "<script type=\"text/javascript\" src=\"http://moz-concept.com/nitdoc/scripts/js-facilities.js\"></script>\n" +
-                       "<link rel=\"stylesheet\" href=\"http://moz-concept.com/nitdoc/styles/main.css\" type=\"text/css\"  media=\"screen\">"
+                       "<link rel=\"stylesheet\" href=\"http://moz-concept.com/nitdoc/styles/main.css\" type=\"text/css\"  media=\"screen\" />"
 
-               var action_bar = "<header><nav class='main'><ul><li><a href='.'>Overview</a></li><li><a href='full-index.html'>Full Index</a></li></ul></nav></header>\n"
+               var action_bar = "<header><nav class='main'><ul><li class=\"current\">Overview</li><li><a href='full-index.html'>Full Index</a></li></ul></nav></header>\n"
 
                # generate the index
                self.filename = "index.html"
                clear
-               add("<html><head>{head}</head><body>\n")
+               add("<!DOCTYPE html>")
+               add("<html><head>{head}<title>Index</title></head><body>\n")
                add(action_bar)
                add("<div class=\"page\">")
                add("<div class=\"content fullpage\">")
@@ -179,7 +182,7 @@ class DocContext
                        end
                end
                op.append("\}\n")
-               self.gen_dot(op.to_s, "dep")
+               self.gen_dot(op.to_s, "dep", "Modules hierarchy")
                add("</article></div>")
                add("<div class='clear'></div>")
                add("</div>")
@@ -192,7 +195,9 @@ class DocContext
                        assert mod isa MMSrcModule
                        if not mod.require_doc(self) then continue
                        self.filename = mod.html_name
+                       action_bar = "<header><nav class='main'><ul><li><a href='./'>Overview</a></li><li class=\"current\">{mod.name}</li><li><a href='full-index.html'>Full Index</a></li></ul></nav></header>\n"
                        clear
+                       add("<!DOCTYPE html>")
                        add("<html><head>{head}<title>Module {mod.name}</title></head><body>\n")
                        add(action_bar)
                        add("<div class=\"page\">")
@@ -206,7 +211,9 @@ class DocContext
                for c in mainmod.local_classes do
                        if not c.require_doc(self) then continue
                        self.filename = c.html_name
+                       action_bar = "<header><nav class='main'><ul><li><a href='./'>Overview</a></li><li>{c.global.intro.mmmodule.toplevel_owner.html_link(self)}</li><li class=\"current\">{c.name}</li><li><a href='full-index.html'>Full Index</a></li></ul></nav></header>\n"
                        clear
+                       add("<!DOCTYPE html>")
                        add("<html><head>{head}<title>Class {c.name}</title></head><body>\n")
                        add(action_bar)
                        add("<div class=\"page\">")
@@ -217,8 +224,10 @@ class DocContext
                end
 
                self.filename = "fullindex"
+               action_bar = "<header><nav class='main'><ul><li><a href='./'>Overview</a></li><li class=\"current\">Full Index</li></ul></nav></header>\n"
                clear
-               add("<html><head>{head}</head><body>\n")
+               add("<!DOCTYPE html>")
+               add("<html><head>{head}<title>Full Index</title></head><body>\n")
                add(action_bar)
                add("<div class=\"page\">")
                add("<div class=\"content fullpage\">")
@@ -235,11 +244,11 @@ class DocContext
        do
                var s = opt_source.value
                if s == null then
-                       add("in #{l.file.filename}")
+                       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)
+                       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")
@@ -251,13 +260,14 @@ class DocContext
        # 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)
+       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("<div><img src=\"{name}.png\" usemap=\"#{name}\" style=\"margin:auto\"/></div>")
+               self.add("<article class=\"graph\"><img src=\"{name}.png\" usemap=\"#{name}\" style=\"margin:auto\" alt=\"{alt}\"/></article>")
                var fmap = new IFStream.open("{self.dir}/{name}.map")
                self.add(fmap.read_all)
                fmap.close
@@ -271,6 +281,7 @@ class DocContext
                option_context.add_option(opt_public)
                option_context.add_option(opt_dir)
                option_context.add_option(opt_source)
+               option_context.add_option(opt_nodot)
        end
 
        redef fun process_options
@@ -278,6 +289,15 @@ class DocContext
                super
                var d = opt_dir.value
                if d != null then dir = d
+
+               if not opt_nodot.value then
+                       # Test if dot is runable
+                       var res = sys.system("sh -c dot </dev/null >/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
        end
 
        redef fun handle_property_conflict(lc, impls)
@@ -290,6 +310,42 @@ class DocContext
        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('&', "&amp;")
+               if ret.has('<') then ret = ret.replace('<', "&lt;")
+               if ret.has('>') then ret = ret.replace('>', "&gt;")
+               if ret.has('"') then ret = ret.replace('"', "&quot;")
+               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
@@ -375,7 +431,7 @@ end
 redef class MMModule
        super MMEntity
        redef fun html_link(dctx) do 
-               return "<a href=\"{html_name}.html\">{self}</a>"
+               return "<a href=\"{html_name}.html\" title=\"{short_doc}\">{self}</a>"
        end
 
        fun require_doc(dctx: DocContext): Bool
@@ -552,7 +608,7 @@ redef class MMModule
                        end
                end
                op.append("\}\n")
-               dctx.gen_dot(op.to_s, name.to_s)
+               dctx.gen_dot(op.to_s, name.to_s, "Dependency graph for module {name}")
                dctx.add("</section>")
 
                var clas = new Array[MMLocalClass]
@@ -622,24 +678,22 @@ redef class MMModule
                        var lpi = self[gp.intro.local_class.global][gp]
                
                        if lps.has(lpi) then
-                               dctx.add("<li class='intro'><span title='introduction in an other module'>I</span>&nbsp;<a href=\"{lpi.local_class.html_name}.html#{lpi.html_anchor}\">{lpi}&nbsp;({lpi.local_class})</a></li>\n")
+                               dctx.add("<li class='intro'><span title='introduction in an other module'>I</span>&nbsp;{lpi.html_open_link(dctx)}{lpi.html_name}&nbsp;({lpi.local_class})</a></li>\n")
                                lps.remove(lpi)
                        else
-                               dctx.add("<li class='intro'><span title='introduction in this module'>I</span>&nbsp;{lpi}")
+                               dctx.add("<li class='intro'><span title='introduction in this module'>I</span>&nbsp;{lpi.html_name}")
                                dctx.add("&nbsp;({lpi.local_class})</li>\n")
                        end
                        if lps.length >= 1 then
                                dctx.sort(lps)
                                for lp in lps do
-                                       dctx.add("<li class='redef'><span title='redefinition'>R</span>&nbsp;<a href=\"{lp.local_class.html_name}.html#{lp.html_anchor}\">{lp}&nbsp;({lp.local_class})</a></li>")
+                                       dctx.add("<li class='redef'><span title='redefinition'>R</span>&nbsp;{lp.html_open_link(dctx)}{lp.html_name}&nbsp;({lp.local_class})</a></li>")
                                end
                        end
                end
                dctx.stage("</ul></article>\n")
                dctx.close_stage
-
-
-               dctx.add("</div>\n")
+               dctx.add("</section>\n")
                dctx.add("</div>\n")
        end
 
@@ -710,11 +764,11 @@ redef class MMModule
                        var lpi = self[gp.intro.local_class.global][gp]
                        
                        lps.remove(lpi)
-                               dctx.add("<li class='intro'><span title='introduction'>I</span>&nbsp;<a href=\"{lpi.local_class.html_name}.html#{lpi.html_anchor}\">{lpi}&nbsp;({lpi.local_class})</a></li>\n")
+                               dctx.add("<li class='intro'><span title='introduction'>I</span>&nbsp;{lpi.html_open_link(dctx)}{lpi.html_name}&nbsp;({lpi.local_class})</a></li>\n")
                        if lps.length >= 1 then
                                dctx.sort(lps)
                                for lp in lps do
-                                       dctx.add("<li class='redef'><span title='redefinition'>R</span>&nbsp;<a href=\"{lp.local_class.html_name}.html#{lp.html_anchor}\">{lp}&nbsp;({lp.local_class})</a></li>\n")
+                                       dctx.add("<li class='redef'><span title='redefinition'>R</span>&nbsp;{lp.html_open_link(dctx)}{lp.html_name}&nbsp;({lp.local_class})</a></li>\n")
                                end
                        end
                end
@@ -731,16 +785,39 @@ redef class MMLocalProperty
                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 != "&nbsp;" then
+                       title += " #{short_doc}"
+               end
+               return "<a href=\"{local_class.html_name}.html#{html_anchor}\" title=\"{title}\">"
+       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}"
-               return "<a href=\"{local_class.html_name}.html#{html_anchor}\">{self}</a>"
+               var title = "{html_name}{signature.to_s}"
+               if short_doc != "&nbsp;" then
+                       title += " #{short_doc}"
+               end
+               return "<a href=\"{local_class.html_name}.html#{html_anchor}\" title=\"{title}\">{html_name}</a>"
        end
 
        fun html_link_special(dctx: DocContext, lc: MMLocalClass): String
        do
                if not require_doc(dctx) then print "not required {self}"
-               return "<a href=\"{lc.html_name}.html#{html_anchor}\">{self}</a>"
+               var title = "{html_name}{signature_for(lc.get_type)}"
+               if short_doc != "&nbsp;" then
+                       title += " #{short_doc}"
+               end
+               return "<a href=\"{lc.html_name}.html#{html_anchor}\" title=\"{title}\">{html_name}</a>"
        end
 
        # Kind of property (fun, attr, etc.)
@@ -823,7 +900,7 @@ redef class MMLocalProperty
                var is_redef = local_class.global != intro_class.global or local_class.mmmodule.toplevel_owner != intro_class.mmmodule.toplevel_owner
 
                dctx.add("<article id=\"{html_anchor}\" class=\"{kind} {visibility} {if is_redef then "redef" else ""}\">\n")
-               dctx.add("<h3 class=\"signature\">{name}{signature.to_html(dctx, true)}</h3>\n")
+               dctx.add("<h3 class=\"signature\">{html_name}{signature.to_html(dctx, true)}</h3>\n")
                dctx.add("<div class=\"info\">\n")
                #dctx.add("<p>LP: {self.mmmodule.html_link(dctx)}::{self.local_class.html_link(dctx)}::{self.html_link(dctx)}</p>")
 
@@ -848,7 +925,7 @@ redef class MMLocalProperty
                if is_redef then
                        dctx.add("::{mmmodule[intro_class.global][global].html_link(dctx)}")
                else
-                       dctx.add("::{name}")
+                       dctx.add("::{html_name}")
                end
                dctx.add("</div>")
 
@@ -994,13 +1071,13 @@ redef class ADoc
                for c in n_comment do
                        res.append(c.text.substring_from(1))
                end
-               return res.to_s
+               return res.to_s.html_escape
        end
 
        # Oneliner transcription of the doc
        fun short: String
        do
-               return n_comment.first.text.substring_from(1)
+               return n_comment.first.text.substring_from(1).html_escape
        end
 end
 
@@ -1015,7 +1092,7 @@ redef class MMLocalClass
        redef fun html_link(dctx)
        do
                if not require_doc(dctx) then print "{dctx.filename}: not required {self}"
-               return "<a href=\"{html_name}.html\">{self}</a>"
+               return "<a href=\"{html_name}.html\" title=\"{short_doc}\">{self}</a>"
        end
 
        redef fun short_doc do return global.intro.short_doc
@@ -1133,7 +1210,7 @@ redef class MMLocalClass
 
                dctx.add("<nav class=\"inheritance filterable\">\n")
                dctx.add("<h3>Inheritance</h3>\n")
-               dctx.add("<h4>Superclasses</h3>\n<ul>\n")
+               dctx.add("<h4>Superclasses</h4>\n<ul>\n")
                for lc in cshe.linear_extension do
                        if lc == self then continue
                        if not lc.require_doc(dctx) then continue
@@ -1223,7 +1300,7 @@ redef class MMLocalClass
                        end
                end
                op.append("\}\n")
-               dctx.gen_dot(op.to_s, name.to_s)
+               dctx.gen_dot(op.to_s, name.to_s, "Inheritance graph for class {name}")
 
 
                var mods = new Array[MMModule]
@@ -1254,8 +1331,6 @@ redef class MMLocalClass
                        dctx.close_stage
                        dctx.add("</p>\n")
                end
-               dctx.add("</ul>\n")
-               
                dctx.add("</section>\n")
 
                dctx.open_stage
@@ -1293,7 +1368,7 @@ redef class MMLocalClass
                end
                if not inhs.is_empty then
                        dctx.open_stage
-                       dctx.stage("<h3>Inherited Methods</h4>\n")
+                       dctx.stage("<h3>Inherited Methods</h3>\n")
                        for lc in inhs do
                                dctx.open_stage
                                dctx.stage("<p>Defined in {lc.html_link(dctx)}:")