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 ReadmeMdProcessor(self, v
)
61 proc
.decorator
= new ReadmeDecorator
62 var md
= mdoc
.content
.join
("\n")
67 # Markdown emitter used to produce the `ReadmeArticle`.
68 class ReadmeMdProcessor
69 super MarkdownProcessor
71 # Readme page being decorated.
74 # Phase used to access doc model and toolcontext.
75 var phase
: ReadmePhase
79 # Push the article template on top of the buffer stack.
81 # Subsequent markdown writting will be done in the article template.
83 # See `ReadmeArticle::md`.
84 private fun push_article
(article
: ReadmeArticle) do
85 buffer_stack
.add article
.md
88 private var context
= new Array[DocComposite]
90 # Creates a new ReadmeSection in `self.toc.page`.
92 # Called from `add_headline`.
93 private fun open_section
(lvl
: Int, title
: String) do
94 var section
= new ReadmeSection(title
.escape_to_c
, title
, lvl
, self)
95 var current_section
= self.current_section
96 if current_section
== null then
97 page
.root
.add_child
(section
)
99 current_section
.add_child
(section
)
101 current_section
= section
104 private var current_section
: nullable ReadmeSection is noinit
106 # Close the current section.
108 # Ensure `context.last isa ReadmeSection`.
109 private fun close_section
do
110 assert context
.last
isa ReadmeSection
112 if context
.is_empty
then
113 current_section
= null
115 current_section
= context
.last
.as(ReadmeSection)
119 # Add an article at current location.
121 # This closes the current article, inserts `article` then opens a new article.
122 private fun add_article
(article
: DocArticle) do
124 var current_section
= self.current_section
125 if current_section
== null then
126 page
.root
.add_child
(article
)
128 current_section
.add_child
(article
)
133 # Creates a new ReadmeArticle in `self.toc.page`.
135 # Called from `add_headline`.
136 private fun open_article
do
137 var section
: DocComposite = page
.root
138 if current_section
!= null then section
= current_section
.as(not null)
139 var article
= new ReadmeArticle("mdarticle-{section.children.length}", null, self)
140 section
.add_child
(article
)
145 # Close the current article.
147 # Ensure `context.last isa ReadmeArticle`.
149 assert context
.last
isa ReadmeArticle
154 # Find mentities matching `query`.
155 fun find_mentities
(query
: String): Array[MEntity] do
156 # search MEntities by full_name
157 var mentity
= phase
.doc
.mentity_by_full_name
(query
)
158 if mentity
!= null then return [mentity
]
159 # search MEntities by name
160 return phase
.doc
.mentities_by_name
(query
)
163 # Suggest mentities based on `query`.
164 fun suggest_mentities
(query
: String): Array[MEntity] do
165 return phase
.doc
.find
(query
, 3)
168 # Display a warning message with suggestions.
169 fun warn
(token
: TokenWikiLink, message
: String, suggest
: nullable Array[MEntity]) do
172 if suggest
!= null and suggest
.not_empty
then
173 msg
.append
" (suggestions: "
176 msg
.append
"`{s.full_name}`"
177 if i
< suggest
.length
- 1 then msg
.append
", "
182 phase
.warning
(token
.location
, page
, msg
.write_to_string
)
186 # MarkdownDecorator used to decorated the Readme file with links between doc entities.
187 class ReadmeDecorator
190 # Parser used to process doc commands
191 var parser
= new DocCommandParser
193 redef type PROCESSOR: ReadmeMdProcessor
195 redef fun add_headline
(v
, block
) do
196 var txt
= block
.block
.first_line
.as(not null).value
197 var lvl
= block
.depth
198 if not v
.context
.is_empty
then
200 while v
.current_section
!= null do
201 if v
.current_section
.as(not null).depth
< lvl
then break
205 v
.open_section
(lvl
, txt
)
209 redef fun add_wikilink
(v
, token
) do
210 var link
= token
.link
.as(not null).to_s
211 var cmd
= parser
.parse
(link
)
213 # search MEntities by name
214 var res
= v
.find_mentities
(link
.to_s
)
215 # no match, print warning and display wikilink as is
217 v
.warn
(token
, "Link to unknown entity `{link}`", v
.suggest_mentities
(link
.to_s
))
220 add_mentity_link
(v
, res
.first
, token
.name
, token
.comment
)
227 # Renders a link to a mentity.
228 private fun add_mentity_link
(v
: PROCESSOR, mentity
: MEntity, name
, comment
: nullable Text) do
230 var link
= mentity
.full_name
231 if name
== null then name
= mentity
.name
232 if comment
== null then
233 var mdoc
= mentity
.mdoc
234 if mdoc
!= null then comment
= mdoc
.synopsis
236 add_link
(v
, link
, name
, comment
)
240 redef class DocCommand
242 # Render the content of the doc command.
243 fun render
(v
: ReadmeMdProcessor, token
: TokenWikiLink) is abstract
246 redef class CommentCommand
247 redef fun render
(v
, token
) do
248 var string
= args
.first
249 var res
= v
.find_mentities
(string
)
252 "Try to include documentation of unknown entity `{string}`",
253 v
.suggest_mentities
(string
))
256 v
.add_article
new DocumentationArticle("readme", "Readme", res
.first
)
260 redef class ListCommand
261 redef fun render
(v
, token
) do
262 var string
= args
.first
263 var res
= v
.find_mentities
(string
)
266 "Try to include article of unknown entity `{string}`",
267 v
.suggest_mentities
(string
))
270 if res
.length
> 1 then
271 v
.warn
(token
, "Conflicting article for `{args.first}`", res
)
273 var mentity
= res
.first
274 if mentity
isa MModule then
275 v
.add_article
new MEntitiesListArticle("Classes", null, mentity
.mclassdefs
)
276 else if mentity
isa MClass then
277 var mprops
= mentity
.collect_intro_mproperties
(v
.phase
.doc
)
278 v
.add_article
new MEntitiesListArticle("Methods", null, mprops
.to_a
)
279 else if mentity
isa MClassDef then
280 v
.add_article
new MEntitiesListArticle("Methods", null, mentity
.mpropdefs
)
286 # A section found in a README.
288 # Produced by markdown headlines like `## Section 1.1`.
292 # The depth is based on the markdown headline depth.
295 # Markdown processor used to process the section title.
296 var markdown_processor
: MarkdownProcessor
298 redef var is_hidden
= false
301 # An article found in a README file.
303 # Basically, everything found in a README that is not a headline.
307 # Markdown processor used to process the article content.
308 var markdown_processor
: MarkdownProcessor
310 # Markdown content of this article extracted from the README file.
311 var md
= new FlatBuffer
313 redef fun is_hidden
do return super and md
.trim
.is_empty
316 # Documentation Article to introduce from the directive `doc: Something`.
318 # TODO merge with DefinitionArticle once the html is simplified
319 class DocumentationArticle
322 redef var is_hidden
= false
325 redef class MDLocation
326 # Translate a Markdown location in Nit location.
327 private fun to_location
(file
: nullable SourceFile): Location do
328 return new Location(file
, line_start
, line_end
, column_start
, column_end
)