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
23 import model
::model_index
25 # Generate content of `ReadmePage`.
27 # This phase extracts the structure of a `ReadmePage` from the markdown content
29 # It also resolves Wikilinks and commands.
34 for page
in doc
.pages
.values
do page
.build_content
(self, doc
)
37 # Display a warning about something wrong in the readme file.
38 fun warning
(location
: nullable MDLocation, page
: ReadmePage, message
: String) do
40 if location
!= null then
41 var mdoc
= page
.mentity
.mdoc
42 if mdoc
!= null then loc
= location
.to_location
(mdoc
.location
.file
)
44 ctx
.warning
(loc
, "readme-warning", message
)
49 # Build content of `ReadmePage` based on the content of the readme file.
50 private fun build_content
(v
: ReadmePhase, doc
: DocModel) do end
53 redef class ReadmePage
54 redef fun build_content
(v
, doc
) do
55 var mdoc
= mentity
.mdoc
57 v
.warning
(null, self, "Empty README for group `{mentity}`")
60 var proc
= new MarkdownProcessor
61 proc
.emitter
= new ReadmeMdEmitter(proc
, self, v
)
62 proc
.emitter
.decorator
= new ReadmeDecorator
63 var md
= mdoc
.content
.join
("\n")
68 # Markdown emitter used to produce the `ReadmeArticle`.
72 # Readme page being decorated.
75 # Phase used to access doc model and toolcontext.
76 var phase
: ReadmePhase
80 # Push the article template on top of the buffer stack.
82 # Subsequent markdown writting will be done in the article template.
84 # See `ReadmeArticle::md`.
85 private fun push_article
(article
: ReadmeArticle) do
86 buffer_stack
.add article
.md
89 private var context
= new Array[DocComposite]
91 # Creates a new ReadmeSection in `self.toc.page`.
93 # Called from `add_headline`.
94 private fun open_section
(lvl
: Int, title
: String) do
95 var section
= new ReadmeSection(title
.escape_to_c
, title
, lvl
, processor
)
96 var current_section
= self.current_section
97 if current_section
== null then
98 page
.root
.add_child
(section
)
100 current_section
.add_child
(section
)
102 current_section
= section
105 private var current_section
: nullable ReadmeSection is noinit
107 # Close the current section.
109 # Ensure `context.last isa ReadmeSection`.
110 private fun close_section
do
111 assert context
.last
isa ReadmeSection
113 if context
.is_empty
then
114 current_section
= null
116 current_section
= context
.last
.as(ReadmeSection)
120 # Add an article at current location.
122 # This closes the current article, inserts `article` then opens a new article.
123 private fun add_article
(article
: DocArticle) do
125 var current_section
= self.current_section
126 if current_section
== null then
127 page
.root
.add_child
(article
)
129 current_section
.add_child
(article
)
134 # Creates a new ReadmeArticle in `self.toc.page`.
136 # Called from `add_headline`.
137 private fun open_article
do
138 var section
: DocComposite = page
.root
139 if current_section
!= null then section
= current_section
.as(not null)
140 var article
= new ReadmeArticle("mdarticle-{section.children.length}", null, processor
)
141 section
.add_child
(article
)
146 # Close the current article.
148 # Ensure `context.last isa ReadmeArticle`.
150 assert context
.last
isa ReadmeArticle
155 # Find mentities matching `query`.
156 fun find_mentities
(query
: String): Array[MEntity] do
157 # search MEntities by full_name
158 var mentity
= phase
.doc
.mentity_by_full_name
(query
)
159 if mentity
!= null then return [mentity
]
160 # search MEntities by name
161 return phase
.doc
.mentities_by_name
(query
)
164 # Suggest mentities based on `query`.
165 fun suggest_mentities
(query
: String): Array[MEntity] do
166 return phase
.doc
.find
(query
, 3)
169 # Display a warning message with suggestions.
170 fun warn
(token
: TokenWikiLink, message
: String, suggest
: nullable Array[MEntity]) do
173 if suggest
!= null and suggest
.not_empty
then
174 msg
.append
" (suggestions: "
177 msg
.append
"`{s.full_name}`"
178 if i
< suggest
.length
- 1 then msg
.append
", "
183 phase
.warning
(token
.location
, page
, msg
.write_to_string
)
187 # MarkdownDecorator used to decorated the Readme file with links between doc entities.
188 class ReadmeDecorator
191 # Parser used to process doc commands
192 var parser
= new DocCommandParser
194 redef type EMITTER: ReadmeMdEmitter
196 redef fun add_headline
(v
, block
) do
197 var txt
= block
.block
.first_line
.as(not null).value
198 var lvl
= block
.depth
199 if not v
.context
.is_empty
then
201 while v
.current_section
!= null do
202 if v
.current_section
.as(not null).depth
< lvl
then break
206 v
.open_section
(lvl
, txt
)
210 redef fun add_wikilink
(v
, token
) do
211 var link
= token
.link
.as(not null).to_s
212 var cmd
= parser
.parse
(link
)
214 # search MEntities by name
215 var res
= v
.find_mentities
(link
.to_s
)
216 # no match, print warning and display wikilink as is
218 v
.warn
(token
, "Link to unknown entity `{link}`", v
.suggest_mentities
(link
.to_s
))
221 add_mentity_link
(v
, res
.first
, token
.name
, token
.comment
)
228 # Renders a link to a mentity.
229 private fun add_mentity_link
(v
: EMITTER, mentity
: MEntity, name
, comment
: nullable Text) do
231 var link
= mentity
.full_name
232 if name
== null then name
= mentity
.name
233 if comment
== null then
234 var mdoc
= mentity
.mdoc
235 if mdoc
!= null then comment
= mdoc
.synopsis
237 add_link
(v
, link
, name
, comment
)
241 redef class DocCommand
243 # Render the content of the doc command.
244 fun render
(v
: ReadmeMdEmitter, token
: TokenWikiLink) is abstract
247 redef class CommentCommand
248 redef fun render
(v
, token
) do
249 var string
= args
.first
250 var res
= v
.find_mentities
(string
)
253 "Try to include documentation of unknown entity `{string}`",
254 v
.suggest_mentities
(string
))
257 v
.add_article
new DocumentationArticle("readme", "Readme", res
.first
)
261 redef class ListCommand
262 redef fun render
(v
, token
) do
263 var string
= args
.first
264 var res
= v
.find_mentities
(string
)
267 "Try to include article of unknown entity `{string}`",
268 v
.suggest_mentities
(string
))
271 if res
.length
> 1 then
272 v
.warn
(token
, "Conflicting article for `{args.first}`", res
)
274 var mentity
= res
.first
275 if mentity
isa MModule then
276 v
.add_article
new MEntitiesListArticle("Classes", null, mentity
.mclassdefs
)
277 else if mentity
isa MClass then
278 var mprops
= mentity
.collect_intro_mproperties
(mentity
.public_view
)
279 v
.add_article
new MEntitiesListArticle("Methods", null, mprops
.to_a
)
280 else if mentity
isa MClassDef then
281 v
.add_article
new MEntitiesListArticle("Methods", null, mentity
.mpropdefs
)
287 # A section found in a README.
289 # Produced by markdown headlines like `## Section 1.1`.
293 # The depth is based on the markdown headline depth.
296 # Markdown processor used to process the section title.
297 var markdown_processor
: MarkdownProcessor
299 redef var is_hidden
= false
302 # An article found in a README file.
304 # Basically, everything found in a README that is not a headline.
308 # Markdown processor used to process the article content.
309 var markdown_processor
: MarkdownProcessor
311 # Markdown content of this article extracted from the README file.
312 var md
= new FlatBuffer
314 redef fun is_hidden
do return super and md
.trim
.is_empty
317 # Documentation Article to introduce from the directive `doc: Something`.
319 # TODO merge with DefinitionArticle once the html is simplified
320 class DocumentationArticle
323 redef var is_hidden
= false
326 redef class MDLocation
327 # Translate a Markdown location in Nit location.
328 private fun to_location
(file
: nullable SourceFile): Location do
329 return new Location(file
, line_start
, line_end
, column_start
, column_end
)