1 # This file is part of NIT ( http://www.nitlanguage.org ).
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
15 # This phase parses README files.
18 import markdown
::decorators
19 intrude import markdown
::wikilinks
22 import doc_intros_redefs
24 # Generate content of `ReadmePage`.
26 # This phase extracts the structure of a `ReadmePage` from the markdown content
28 # It also resolves Wikilinks and commands.
33 for page
in doc
.pages
.values
do page
.build_content
(self, doc
)
36 # Display a warning about something wrong in the readme file.
37 fun warning
(location
: nullable MDLocation, page
: ReadmePage, message
: String) do
39 if location
!= null then
40 loc
= location
.to_location
(page
.mentity
.mdoc
.location
.file
)
42 ctx
.warning
(loc
, "readme-warning", message
)
47 # Build content of `ReadmePage` based on the content of the readme file.
48 private fun build_content
(v
: ReadmePhase, doc
: DocModel) do end
51 redef class ReadmePage
52 redef fun build_content
(v
, doc
) do
53 if mentity
.mdoc
== null then
54 v
.warning
(null, self, "Empty README for group `{mentity}`")
57 var proc
= new MarkdownProcessor
58 proc
.emitter
= new ReadmeMdEmitter(proc
, self, v
)
59 proc
.emitter
.decorator
= new ReadmeDecorator
60 var md
= mentity
.mdoc
.content
.join
("\n")
65 # Markdown emitter used to produce the `ReadmeArticle`.
69 # Readme page being decorated.
72 # Phase used to access doc model and toolcontext.
73 var phase
: ReadmePhase
77 # Push the article template on top of the buffer stack.
79 # Subsequent markdown writting will be done in the article template.
81 # See `ReadmeArticle::md`.
82 private fun push_article
(article
: ReadmeArticle) do
83 buffer_stack
.add article
.md
86 private var context
= new Array[DocComposite]
88 # Creates a new ReadmeSection in `self.toc.page`.
90 # Called from `add_headline`.
91 private fun open_section
(lvl
: Int, title
: String) do
92 var section
= new ReadmeSection(title
.escape_to_c
, title
, lvl
, processor
)
93 if current_section
== null then
94 page
.root
.add_child
(section
)
96 current_section
.add_child
(section
)
98 current_section
= section
101 private var current_section
: nullable ReadmeSection is noinit
103 # Close the current section.
105 # Ensure `context.last isa ReadmeSection`.
106 private fun close_section
do
107 assert context
.last
isa ReadmeSection
109 if context
.is_empty
then
110 current_section
= null
112 current_section
= context
.last
.as(ReadmeSection)
116 # Add an article at current location.
118 # This closes the current article, inserts `article` then opens a new article.
119 private fun add_article
(article
: DocArticle) do
121 if current_section
== null then
122 page
.root
.add_child
(article
)
124 current_section
.add_child
(article
)
129 # Creates a new ReadmeArticle in `self.toc.page`.
131 # Called from `add_headline`.
132 private fun open_article
do
133 var section
: DocComposite = page
.root
134 if current_section
!= null then section
= current_section
.as(not null)
135 var article
= new ReadmeArticle("mdarticle-{section.children.length}", null, processor
)
136 section
.add_child
(article
)
141 # Close the current article.
143 # Ensure `context.last isa ReadmeArticle`.
145 assert context
.last
isa ReadmeArticle
151 # MarkdownDecorator used to decorated the Readme file with links between doc entities.
152 class ReadmeDecorator
155 redef type EMITTER: ReadmeMdEmitter
157 redef fun add_headline
(v
, block
) do
158 var txt
= block
.block
.first_line
.value
159 var lvl
= block
.depth
160 if not v
.context
.is_empty
then
162 while v
.current_section
!= null do
163 if v
.current_section
.depth
< lvl
then break
167 v
.open_section
(lvl
, txt
)
171 redef fun add_wikilink
(v
, token
) do
172 var link
= token
.link
.to_s
173 var cmd
= new DocCommand(link
)
174 if cmd
isa UnknownCommand then
175 # search MEntities by name
176 var res
= v
.phase
.doc
.mentities_by_name
(link
.to_s
)
177 # no match, print warning and display wikilink as is
179 v
.phase
.warning
(token
.location
, v
.page
, "Link to unknown entity `{link}`")
182 add_mentity_link
(v
, res
.first
, token
.name
, token
.comment
)
189 # Renders a link to a mentity.
190 private fun add_mentity_link
(v
: EMITTER, mentity
: MEntity, name
, comment
: nullable Text) do
192 var link
= mentity
.full_name
193 if name
== null then name
= mentity
.name
194 if comment
== null and mentity
.mdoc
!= null then
195 comment
= mentity
.mdoc
.synopsis
197 add_link
(v
, link
, name
, comment
)
201 redef interface DocCommand
203 # Render the content of the doc command.
204 fun render
(v
: ReadmeMdEmitter, token
: TokenWikiLink) is abstract
206 # Search `doc` model for mentities match `string`.
207 fun search_model
(doc
: DocModel, string
: String): Array[MEntity] do
209 if string
.has
("::") then
210 res
= doc
.mentities_by_namespace
(string
).to_a
212 res
= doc
.mentities_by_name
(string
).to_a
218 redef class ArticleCommand
219 redef fun render
(v
, token
) do
220 var string
= args
.first
221 var res
= search_model
(v
.phase
.doc
, string
)
222 res
= filter_results
(res
)
225 token
.location
, v
.page
,
226 "Try to include documentation of unknown entity `{args.first}`")
229 if res
.length
> 1 then
230 v
.phase
.warning
(token
.location
, v
.page
, "conflicting article for `{args.first}` (choices : {res.join(", ")})")
232 v
.add_article
new DocumentationArticle("readme", "Readme", res
.first
)
235 private fun filter_results
(res
: Array[MEntity]): Array[MEntity] do
236 var out
= new Array[MEntity]
238 if e
isa MPackage then continue
239 if e
isa MGroup then continue
246 redef class ListCommand
247 redef fun render
(v
, token
) do
248 var string
= args
.first
249 var res
= search_model
(v
.phase
.doc
, string
)
251 v
.phase
.warning
(token
.location
, v
.page
, "include article for unknown entity `{args.first}`")
254 if res
.length
> 1 then
255 v
.phase
.warning
(token
.location
, v
.page
, "conflicting article for `{args.first}` (choices : {res.join(", ")})")
257 var mentity
= res
.first
258 if mentity
isa MModule then
259 v
.add_article
new MEntitiesListArticle("Classes", null, mentity
.mclassdefs
)
260 else if mentity
isa MClass then
261 var mprops
= mentity
.collect_intro_mproperties
(public_visibility
)
262 v
.add_article
new MEntitiesListArticle("Methods", null, mprops
.to_a
)
263 else if mentity
isa MClassDef then
264 v
.add_article
new MEntitiesListArticle("Methods", null, mentity
.mpropdefs
)
270 # A section found in a README.
272 # Produced by markdown headlines like `## Section 1.1`.
276 # The depth is based on the markdown headline depth.
279 # Markdown processor used to process the section title.
280 var markdown_processor
: MarkdownProcessor
282 redef var is_hidden
= false
285 # An article found in a README file.
287 # Basically, everything found in a README that is not a headline.
291 # Markdown processor used to process the article content.
292 var markdown_processor
: MarkdownProcessor
294 # Markdown content of this article extracted from the README file.
295 var md
= new FlatBuffer
297 redef fun is_hidden
do return super and md
.trim
.is_empty
300 # Documentation Article to introduce from the directive `doc: Something`.
302 # TODO merge with DefinitionArticle once the html is simplified
303 class DocumentationArticle
306 redef var is_hidden
= false
309 redef class MDLocation
310 # Translate a Markdown location in Nit location.
311 private fun to_location
(file
: nullable SourceFile): Location do
312 return new Location(file
, line_start
, line_end
, column_start
, column_end
)