--- /dev/null
+# 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
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
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
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
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} – {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