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 redef type EMITTER: ReadmeMdEmitter
193 redef fun add_headline
(v
, block
) do
194 var txt
= block
.block
.first_line
.as(not null).value
195 var lvl
= block
.depth
196 if not v
.context
.is_empty
then
198 while v
.current_section
!= null do
199 if v
.current_section
.as(not null).depth
< lvl
then break
203 v
.open_section
(lvl
, txt
)
207 redef fun add_wikilink
(v
, token
) do
208 var link
= token
.link
.as(not null).to_s
209 var cmd
= new DocCommand(link
)
210 if cmd
isa UnknownCommand then
211 # search MEntities by name
212 var res
= v
.find_mentities
(link
.to_s
)
213 # no match, print warning and display wikilink as is
215 v
.warn
(token
, "Link to unknown entity `{link}`", v
.suggest_mentities
(link
.to_s
))
218 add_mentity_link
(v
, res
.first
, token
.name
, token
.comment
)
225 # Renders a link to a mentity.
226 private fun add_mentity_link
(v
: EMITTER, mentity
: MEntity, name
, comment
: nullable Text) do
228 var link
= mentity
.full_name
229 if name
== null then name
= mentity
.name
230 if comment
== null then
231 var mdoc
= mentity
.mdoc
232 if mdoc
!= null then comment
= mdoc
.synopsis
234 add_link
(v
, link
, name
, comment
)
238 redef interface DocCommand
240 # Render the content of the doc command.
241 fun render
(v
: ReadmeMdEmitter, token
: TokenWikiLink) is abstract
244 redef class ArticleCommand
245 redef fun render
(v
, token
) do
246 var string
= args
.first
247 var res
= v
.find_mentities
(string
)
250 "Try to include documentation of unknown entity `{string}`",
251 v
.suggest_mentities
(string
))
254 v
.add_article
new DocumentationArticle("readme", "Readme", res
.first
)
258 redef class ListCommand
259 redef fun render
(v
, token
) do
260 var string
= args
.first
261 var res
= v
.find_mentities
(string
)
264 "Try to include article of unknown entity `{string}`",
265 v
.suggest_mentities
(string
))
268 if res
.length
> 1 then
269 v
.warn
(token
, "Conflicting article for `{args.first}`", res
)
271 var mentity
= res
.first
272 if mentity
isa MModule then
273 v
.add_article
new MEntitiesListArticle("Classes", null, mentity
.mclassdefs
)
274 else if mentity
isa MClass then
275 var mprops
= mentity
.collect_intro_mproperties
(mentity
.public_view
)
276 v
.add_article
new MEntitiesListArticle("Methods", null, mprops
.to_a
)
277 else if mentity
isa MClassDef then
278 v
.add_article
new MEntitiesListArticle("Methods", null, mentity
.mpropdefs
)
284 # A section found in a README.
286 # Produced by markdown headlines like `## Section 1.1`.
290 # The depth is based on the markdown headline depth.
293 # Markdown processor used to process the section title.
294 var markdown_processor
: MarkdownProcessor
296 redef var is_hidden
= false
299 # An article found in a README file.
301 # Basically, everything found in a README that is not a headline.
305 # Markdown processor used to process the article content.
306 var markdown_processor
: MarkdownProcessor
308 # Markdown content of this article extracted from the README file.
309 var md
= new FlatBuffer
311 redef fun is_hidden
do return super and md
.trim
.is_empty
314 # Documentation Article to introduce from the directive `doc: Something`.
316 # TODO merge with DefinitionArticle once the html is simplified
317 class DocumentationArticle
320 redef var is_hidden
= false
323 redef class MDLocation
324 # Translate a Markdown location in Nit location.
325 private fun to_location
(file
: nullable SourceFile): Location do
326 return new Location(file
, line_start
, line_end
, column_start
, column_end
)