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 loc
= location
.to_location
(page
.mentity
.mdoc
.location
.file
)
43 ctx
.warning
(loc
, "readme-warning", message
)
48 # Build content of `ReadmePage` based on the content of the readme file.
49 private fun build_content
(v
: ReadmePhase, doc
: DocModel) do end
52 redef class ReadmePage
53 redef fun build_content
(v
, doc
) do
54 if mentity
.mdoc
== null then
55 v
.warning
(null, self, "Empty README for group `{mentity}`")
58 var proc
= new MarkdownProcessor
59 proc
.emitter
= new ReadmeMdEmitter(proc
, self, v
)
60 proc
.emitter
.decorator
= new ReadmeDecorator
61 var md
= mentity
.mdoc
.content
.join
("\n")
66 # Markdown emitter used to produce the `ReadmeArticle`.
70 # Readme page being decorated.
73 # Phase used to access doc model and toolcontext.
74 var phase
: ReadmePhase
78 # Push the article template on top of the buffer stack.
80 # Subsequent markdown writting will be done in the article template.
82 # See `ReadmeArticle::md`.
83 private fun push_article
(article
: ReadmeArticle) do
84 buffer_stack
.add article
.md
87 private var context
= new Array[DocComposite]
89 # Creates a new ReadmeSection in `self.toc.page`.
91 # Called from `add_headline`.
92 private fun open_section
(lvl
: Int, title
: String) do
93 var section
= new ReadmeSection(title
.escape_to_c
, title
, lvl
, processor
)
94 if current_section
== null then
95 page
.root
.add_child
(section
)
97 current_section
.add_child
(section
)
99 current_section
= section
102 private var current_section
: nullable ReadmeSection is noinit
104 # Close the current section.
106 # Ensure `context.last isa ReadmeSection`.
107 private fun close_section
do
108 assert context
.last
isa ReadmeSection
110 if context
.is_empty
then
111 current_section
= null
113 current_section
= context
.last
.as(ReadmeSection)
117 # Add an article at current location.
119 # This closes the current article, inserts `article` then opens a new article.
120 private fun add_article
(article
: DocArticle) do
122 if current_section
== null then
123 page
.root
.add_child
(article
)
125 current_section
.add_child
(article
)
130 # Creates a new ReadmeArticle in `self.toc.page`.
132 # Called from `add_headline`.
133 private fun open_article
do
134 var section
: DocComposite = page
.root
135 if current_section
!= null then section
= current_section
.as(not null)
136 var article
= new ReadmeArticle("mdarticle-{section.children.length}", null, processor
)
137 section
.add_child
(article
)
142 # Close the current article.
144 # Ensure `context.last isa ReadmeArticle`.
146 assert context
.last
isa ReadmeArticle
151 # Find mentities matching `query`.
152 fun find_mentities
(query
: String): Array[MEntity] do
153 # search MEntities by full_name
154 var mentity
= phase
.doc
.mentity_by_full_name
(query
)
155 if mentity
!= null then return [mentity
]
156 # search MEntities by name
157 return phase
.doc
.mentities_by_name
(query
)
160 # Suggest mentities based on `query`.
161 fun suggest_mentities
(query
: String): Array[MEntity] do
162 return phase
.doc
.find
(query
, 3)
165 # Display a warning message with suggestions.
166 fun warn
(token
: TokenWikiLink, message
: String, suggest
: nullable Array[MEntity]) do
169 if suggest
!= null and suggest
.not_empty
then
170 msg
.append
" (suggestions: "
173 msg
.append
"`{s.full_name}`"
174 if i
< suggest
.length
- 1 then msg
.append
", "
179 phase
.warning
(token
.location
, page
, msg
.write_to_string
)
183 # MarkdownDecorator used to decorated the Readme file with links between doc entities.
184 class ReadmeDecorator
187 redef type EMITTER: ReadmeMdEmitter
189 redef fun add_headline
(v
, block
) do
190 var txt
= block
.block
.first_line
.value
191 var lvl
= block
.depth
192 if not v
.context
.is_empty
then
194 while v
.current_section
!= null do
195 if v
.current_section
.depth
< lvl
then break
199 v
.open_section
(lvl
, txt
)
203 redef fun add_wikilink
(v
, token
) do
204 var link
= token
.link
.to_s
205 var cmd
= new DocCommand(link
)
206 if cmd
isa UnknownCommand then
207 # search MEntities by name
208 var res
= v
.find_mentities
(link
.to_s
)
209 # no match, print warning and display wikilink as is
211 v
.warn
(token
, "Link to unknown entity `{link}`", v
.suggest_mentities
(link
.to_s
))
214 add_mentity_link
(v
, res
.first
, token
.name
, token
.comment
)
221 # Renders a link to a mentity.
222 private fun add_mentity_link
(v
: EMITTER, mentity
: MEntity, name
, comment
: nullable Text) do
224 var link
= mentity
.full_name
225 if name
== null then name
= mentity
.name
226 if comment
== null and mentity
.mdoc
!= null then
227 comment
= mentity
.mdoc
.synopsis
229 add_link
(v
, link
, name
, comment
)
233 redef interface DocCommand
235 # Render the content of the doc command.
236 fun render
(v
: ReadmeMdEmitter, token
: TokenWikiLink) is abstract
239 redef class ArticleCommand
240 redef fun render
(v
, token
) do
241 var string
= args
.first
242 var res
= v
.find_mentities
(string
)
245 "Try to include documentation of unknown entity `{string}`",
246 v
.suggest_mentities
(string
))
249 v
.add_article
new DocumentationArticle("readme", "Readme", res
.first
)
253 redef class ListCommand
254 redef fun render
(v
, token
) do
255 var string
= args
.first
256 var res
= v
.find_mentities
(string
)
259 "Try to include article of unknown entity `{string}`",
260 v
.suggest_mentities
(string
))
263 if res
.length
> 1 then
264 v
.warn
(token
, "Conflicting article for `{args.first}`", res
)
266 var mentity
= res
.first
267 if mentity
isa MModule then
268 v
.add_article
new MEntitiesListArticle("Classes", null, mentity
.mclassdefs
)
269 else if mentity
isa MClass then
270 var mprops
= mentity
.collect_intro_mproperties
(mentity
.public_view
)
271 v
.add_article
new MEntitiesListArticle("Methods", null, mprops
.to_a
)
272 else if mentity
isa MClassDef then
273 v
.add_article
new MEntitiesListArticle("Methods", null, mentity
.mpropdefs
)
279 # A section found in a README.
281 # Produced by markdown headlines like `## Section 1.1`.
285 # The depth is based on the markdown headline depth.
288 # Markdown processor used to process the section title.
289 var markdown_processor
: MarkdownProcessor
291 redef var is_hidden
= false
294 # An article found in a README file.
296 # Basically, everything found in a README that is not a headline.
300 # Markdown processor used to process the article content.
301 var markdown_processor
: MarkdownProcessor
303 # Markdown content of this article extracted from the README file.
304 var md
= new FlatBuffer
306 redef fun is_hidden
do return super and md
.trim
.is_empty
309 # Documentation Article to introduce from the directive `doc: Something`.
311 # TODO merge with DefinitionArticle once the html is simplified
312 class DocumentationArticle
315 redef var is_hidden
= false
318 redef class MDLocation
319 # Translate a Markdown location in Nit location.
320 private fun to_location
(file
: nullable SourceFile): Location do
321 return new Location(file
, line_start
, line_end
, column_start
, column_end
)