nitdoc: generate README file doc pages
authorAlexandre Terrasa <alexandre@moz-code.org>
Thu, 4 Jun 2015 15:36:20 +0000 (11:36 -0400)
committerAlexandre Terrasa <alexandre@moz-code.org>
Tue, 7 Jul 2015 14:49:07 +0000 (10:49 -0400)
Signed-off-by: Alexandre Terrasa <alexandre@moz-code.org>

src/doc/doc_phases/doc_html.nit
src/doc/doc_phases/doc_pages.nit
src/doc/doc_phases/doc_readme.nit [new file with mode: 0644]
src/doc/html_templates/html_templates.nit
src/nitdoc.nit

index fd4fa31..91138d0 100644 (file)
@@ -238,7 +238,13 @@ redef class SearchPage
 end
 
 redef class MEntityPage
-       redef var html_url is lazy do return mentity.nitdoc_url
+       redef var html_url is lazy do
+               if mentity isa MGroup and mentity.mdoc != null then
+                       return "api_{mentity.nitdoc_url}"
+               end
+               return mentity.nitdoc_url
+       end
+
        redef fun init_title(v, doc) do title = mentity.html_name
 end
 
@@ -246,6 +252,26 @@ end
 # doc phases. This is to preserve the compatibility with the current
 # `doc_templates` module.
 
+redef class ReadmePage
+       redef var html_url is lazy do return mentity.nitdoc_url
+
+       redef fun init_topmenu(v, doc) do
+               super
+               var mproject = mentity.mproject
+               if not mentity.is_root then
+                       topmenu.add_li new ListItem(new Link(mproject.nitdoc_url, mproject.html_name))
+               end
+               topmenu.add_li new ListItem(new Link(html_url, mproject.html_name))
+               topmenu.active_item = topmenu.items.last
+       end
+
+       redef fun init_sidebar(v, doc) do
+               super
+               var api_lnk = """<a href="api_{{{mentity.nitdoc_url}}}">Go to API</a>"""
+               sidebar.boxes.unshift new DocSideBox(api_lnk, "")
+       end
+end
+
 redef class MGroupPage
        redef fun init_topmenu(v, doc) do
                super
@@ -259,6 +285,12 @@ redef class MGroupPage
 
        redef fun init_sidebar(v, doc) do
                super
+               # README link
+               if mentity.mdoc != null then
+                       var doc_lnk = """<a href="{{{mentity.nitdoc_url}}}">Go to README</a>"""
+                       sidebar.boxes.unshift new DocSideBox(doc_lnk, "")
+               end
+               # MClasses list
                var mclasses = new HashSet[MClass]
                mclasses.add_all intros
                mclasses.add_all redefs
index 3f83001..e08a385 100644 (file)
@@ -26,6 +26,7 @@ class MakePagePhase
                doc.add_page new OverviewPage("overview", "Overview")
                doc.add_page new SearchPage("search", "Index")
                for mgroup in doc.mgroups do
+                       doc.add_page new ReadmePage(mgroup)
                        doc.add_page new MGroupPage(mgroup)
                end
                for mmodule in doc.mmodules do
@@ -65,6 +66,14 @@ class MEntityPage
        redef var title is lazy do return mentity.nitdoc_name
 end
 
+# A page that displays a `MGroup` README.
+class ReadmePage
+       super MEntityPage
+
+       redef type MENTITY: MGroup
+       redef var id is lazy do return "readme_{mentity.nitdoc_id}"
+end
+
 # A documentation page about a MGroup.
 class MGroupPage
        super MEntityPage
diff --git a/src/doc/doc_phases/doc_readme.nit b/src/doc/doc_phases/doc_readme.nit
new file mode 100644 (file)
index 0000000..0a43edb
--- /dev/null
@@ -0,0 +1,314 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# This phase parses README files.
+module doc_readme
+
+import markdown::decorators
+intrude import markdown::wikilinks
+import doc_commands
+import doc_down
+import doc_intros_redefs
+
+# Generate content of `ReadmePage`.
+#
+# This phase extracts the structure of a `ReadmePage` from the markdown content
+# of the README file.
+# It also resolves Wikilinks and commands.
+class ReadmePhase
+       super DocPhase
+
+       redef fun apply do
+               for page in doc.pages.values do page.build_content(self, doc)
+       end
+
+       # Display a warning about something wrong in the readme file.
+       fun warning(location: nullable MDLocation, page: ReadmePage, message: String) do
+               var loc = null
+               if location != null then
+                       loc = location.to_location(page.mentity.mdoc.location.file)
+               end
+               ctx.warning(loc, "readme-warning", message)
+       end
+end
+
+redef class DocPage
+       # Build content of `ReadmePage` based on the content of the readme file.
+       private fun build_content(v: ReadmePhase, doc: DocModel) do end
+end
+
+redef class ReadmePage
+       redef fun build_content(v, doc) do
+               if mentity.mdoc == null then
+                       v.warning(null, self, "Empty README for group `{mentity}`")
+                       return
+               end
+               var proc = new MarkdownProcessor
+               proc.emitter = new ReadmeMdEmitter(proc, self, v)
+               proc.emitter.decorator = new ReadmeDecorator
+               var md = mentity.mdoc.content.join("\n")
+               proc.process(md)
+       end
+end
+
+# Markdown emitter used to produce the `ReadmeArticle`.
+class ReadmeMdEmitter
+       super MarkdownEmitter
+
+       # Readme page being decorated.
+       var page: ReadmePage
+
+       # Phase used to access doc model and toolcontext.
+       var phase: ReadmePhase
+
+       init do open_article
+
+       # Push the article template on top of the buffer stack.
+       #
+       # Subsequent markdown writting will be done in the article template.
+       #
+       # See `ReadmeArticle::md`.
+       private fun push_article(article: ReadmeArticle) do
+               buffer_stack.add article.md
+       end
+
+       private var context = new Array[DocComposite]
+
+       # Creates a new ReadmeSection in `self.toc.page`.
+       #
+       # Called from `add_headline`.
+       private fun open_section(lvl: Int, title: String) do
+               var section = new ReadmeSection(title.escape_to_c, title, lvl, processor)
+               if current_section == null then
+                       page.root.add_child(section)
+               else
+                       current_section.add_child(section)
+               end
+               current_section = section
+               context.add section
+       end
+       private var current_section: nullable ReadmeSection is noinit
+
+       # Close the current section.
+       #
+       # Ensure `context.last isa ReadmeSection`.
+       private fun close_section do
+               assert context.last isa ReadmeSection
+               context.pop
+               if context.is_empty then
+                       current_section = null
+               else
+                       current_section = context.last.as(ReadmeSection)
+               end
+       end
+
+       # Add an article at current location.
+       #
+       # This closes the current article, inserts `article` then opens a new article.
+       private fun add_article(article: DocArticle) do
+               close_article
+               if current_section == null then
+                       page.root.add_child(article)
+               else
+                       current_section.add_child(article)
+               end
+               open_article
+       end
+
+       # Creates a new ReadmeArticle in `self.toc.page`.
+       #
+       # Called from `add_headline`.
+       private fun open_article do
+               var section: DocComposite = page.root
+               if current_section != null then section = current_section.as(not null)
+               var article = new ReadmeArticle("mdarticle-{section.children.length}", null, processor)
+               section.add_child(article)
+               context.add article
+               push_article article
+       end
+
+       # Close the current article.
+       #
+       # Ensure `context.last isa ReadmeArticle`.
+       fun close_article do
+               assert context.last isa ReadmeArticle
+               context.pop
+               pop_buffer
+       end
+end
+
+# MarkdownDecorator used to decorated the Readme file with links between doc entities.
+class ReadmeDecorator
+       super MdDecorator
+
+       redef type EMITTER: ReadmeMdEmitter
+
+       redef fun add_headline(v, block) do
+               var txt = block.block.first_line.value
+               var lvl = block.depth
+               if not v.context.is_empty then
+                       v.close_article
+                       while v.current_section != null do
+                               if v.current_section.depth < lvl then break
+                               v.close_section
+                       end
+               end
+               v.open_section(lvl, txt)
+               v.open_article
+       end
+
+       redef fun add_wikilink(v, token) do
+               var link = token.link.to_s
+               var cmd = new DocCommand(link)
+               if cmd isa UnknownCommand then
+                       # search MEntities by name
+                       var res = v.phase.doc.mentities_by_name(link.to_s)
+                       # no match, print warning and display wikilink as is
+                       if res.is_empty then
+                               v.phase.warning(token.location, v.page, "Link to unknown entity `{link}`")
+                               super
+                       else
+                               add_mentity_link(v, res.first, token.name, token.comment)
+                       end
+                       return
+               end
+               cmd.render(v, token)
+       end
+
+       # Renders a link to a mentity.
+       private fun add_mentity_link(v: EMITTER, mentity: MEntity, name, comment: nullable Text) do
+               # TODO real link
+               var link = mentity.full_name
+               if name == null then name = mentity.name
+               if comment == null and mentity.mdoc != null then
+                       comment = mentity.mdoc.synopsis
+               end
+               add_link(v, link, name, comment)
+       end
+end
+
+redef interface DocCommand
+
+       # Render the content of the doc command.
+       fun render(v: ReadmeMdEmitter, token: TokenWikiLink) is abstract
+
+       # Search `doc` model for mentities match `string`.
+       fun search_model(doc: DocModel, string: String): Array[MEntity] do
+               var res
+               if string.has("::") then
+                       res = doc.mentities_by_namespace(string).to_a
+               else
+                       res = doc.mentities_by_name(string).to_a
+               end
+               return res
+       end
+end
+
+redef class ArticleCommand
+       redef fun render(v, token) do
+               var string = args.first
+               var res = search_model(v.phase.doc, string)
+               res = filter_results(res)
+               if res.is_empty then
+                       v.phase.warning(
+                               token.location, v.page,
+                               "Try to include documentation of unknown entity `{args.first}`")
+                       return
+               end
+               if res.length > 1 then
+                       v.phase.warning(token.location, v.page, "conflicting article for `{args.first}` (choices : {res.join(", ")})")
+               end
+               v.add_article new DocumentationArticle("readme", "Readme", res.first)
+       end
+
+       private fun filter_results(res: Array[MEntity]): Array[MEntity] do
+               var out = new Array[MEntity]
+               for e in res do
+                       if e isa MProject then continue
+                       if e isa MGroup then continue
+                       out.add e
+               end
+               return out
+       end
+end
+
+redef class ListCommand
+       redef fun render(v, token) do
+               var string = args.first
+               var res = search_model(v.phase.doc, string)
+               if res.is_empty then
+                       v.phase.warning(token.location, v.page, "include article for unknown entity `{args.first}`")
+                       return
+               end
+               if res.length > 1 then
+                       v.phase.warning(token.location, v.page, "conflicting article for `{args.first}` (choices : {res.join(", ")})")
+               end
+               var mentity = res.first
+               if mentity isa MModule then
+                       v.add_article new MEntitiesListArticle("Classes", mentity.mclassdefs)
+               else if mentity isa MClass then
+                       var mprops = mentity.collect_intro_mproperties(public_visibility)
+                       v.add_article new MEntitiesListArticle("Methods", mprops.to_a)
+               else if mentity isa MClassDef then
+                       v.add_article new MEntitiesListArticle("Methods", mentity.mpropdefs)
+               end
+       end
+end
+
+
+# A section found in a README.
+#
+# Produced by markdown headlines like `## Section 1.1`.
+class ReadmeSection
+       super DocSection
+
+       # The depth is based on the markdown headline depth.
+       redef var depth
+
+       # Markdown processor used to process the section title.
+       var markdown_processor: MarkdownProcessor
+
+       redef var is_hidden = false
+end
+
+# An article found in a README file.
+#
+# Basically, everything found in a README that is not a headline.
+class ReadmeArticle
+       super DocArticle
+
+       # Markdown processor used to process the article content.
+       var markdown_processor: MarkdownProcessor
+
+       # Markdown content of this article extracted from the README file.
+       var md = new FlatBuffer
+
+       redef fun is_hidden do return super and md.trim.is_empty
+end
+
+# Documentation Article to introduce from the directive `doc: Something`.
+#
+# TODO merge with DefinitionArticle once the html is simplified
+class DocumentationArticle
+       super MEntityArticle
+
+       redef var is_hidden = false
+end
+
+redef class MDLocation
+       # Translate a Markdown location in Nit location.
+       private fun to_location(file: nullable SourceFile): Location do
+               return new Location(file, line_start, line_end, column_start, column_end)
+       end
+end
index e2567fa..2f893da 100644 (file)
@@ -22,6 +22,8 @@ import doc_phases::doc_hierarchies
 import doc_phases::doc_graphs
 import doc_phases::doc_intros_redefs
 import doc_phases::doc_lin
+import doc_phases::doc_readme
+intrude import doc_down
 
 # Renders the page as HTML.
 redef class DocPage
@@ -447,6 +449,9 @@ redef class IntroArticle
        redef fun render_body do
                var tabs = new DocTabs("{html_id}.tabs", "")
                var comment = mentity.html_documentation
+               if mentity isa MProject then
+                       comment = mentity.html_synopsis
+               end
                if comment != null then
                        tabs.add_panel new DocTabPanel("{html_tab_id}-comment", "Comment", comment)
                end
@@ -501,7 +506,7 @@ redef class DefinitionArticle
                var tabs = new DocTabs("{html_id}.tabs", "")
                if not is_no_body then
                        var comment
-                       if is_short_comment then
+                       if is_short_comment or mentity isa MProject then
                                comment = mentity.html_synopsis
                        else
                                comment = mentity.html_documentation
@@ -571,3 +576,49 @@ redef class GraphArticle
                addn "</div>"
        end
 end
+
+redef class ReadmeSection
+       redef var html_id is lazy do
+               return markdown_processor.emitter.decorator.strip_id(html_title.as(not null).to_s)
+       end
+
+       redef var html_title is lazy do
+               return markdown_processor.process(title.as(not null))
+       end
+end
+
+redef class ReadmeArticle
+       redef var html_id = ""
+       redef var html_title = null
+       redef var is_toc_hidden = true
+
+       redef fun render_body do
+               add markdown_processor.process(md.trim.write_to_string)
+       end
+end
+
+redef class DocumentationArticle
+       redef var html_title is lazy do
+               var synopsis = mentity.html_synopsis
+               if synopsis == null then return mentity.html_link
+               return "{mentity.html_link.write_to_string} &ndash; {synopsis.write_to_string}"
+       end
+
+       redef var html_subtitle is lazy do return null
+       redef var html_toc_title is lazy do return mentity.html_name
+       redef var is_toc_hidden is lazy do return depth > 3
+
+       redef fun render_body do
+               var tabs = new DocTabs("{html_id}.tabs", "")
+               var comment = mentity.html_comment
+               if comment != null then
+                       tabs.add_panel new DocTabPanel("{html_tab_id}-comment", "Comment", comment)
+               end
+               for child in children do
+                       if child.is_hidden then continue
+                       var title = child.html_toc_title or else child.toc_title or else ""
+                       tabs.add_panel new DocTabPanel(child.html_tab_id, title, child)
+               end
+               addn tabs
+       end
+end
index d9e17f3..bda79e7 100644 (file)
@@ -51,7 +51,8 @@ private class Nitdoc
                        new InheritanceListsPhase(toolcontext, doc),
                        new IntroRedefListPhase(toolcontext, doc),
                        new LinListPhase(toolcontext, doc),
-                       new GraphPhase(toolcontext, doc): DocPhase]
+                       new GraphPhase(toolcontext, doc),
+                       new ReadmePhase(toolcontext, doc): DocPhase]
 
                if not toolcontext.opt_test.value then
                        phases.add new RenderHTMLPhase(toolcontext, doc)