src: mass rename project->package
[nit.git] / src / doc / doc_phases / doc_readme.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
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
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
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.
14
15 # This phase parses README files.
16 module doc_readme
17
18 import markdown::decorators
19 intrude import markdown::wikilinks
20 import doc_commands
21 import doc_down
22 import doc_intros_redefs
23
24 # Generate content of `ReadmePage`.
25 #
26 # This phase extracts the structure of a `ReadmePage` from the markdown content
27 # of the README file.
28 # It also resolves Wikilinks and commands.
29 class ReadmePhase
30 super DocPhase
31
32 redef fun apply do
33 for page in doc.pages.values do page.build_content(self, doc)
34 end
35
36 # Display a warning about something wrong in the readme file.
37 fun warning(location: nullable MDLocation, page: ReadmePage, message: String) do
38 var loc = null
39 if location != null then
40 loc = location.to_location(page.mentity.mdoc.location.file)
41 end
42 ctx.warning(loc, "readme-warning", message)
43 end
44 end
45
46 redef class DocPage
47 # Build content of `ReadmePage` based on the content of the readme file.
48 private fun build_content(v: ReadmePhase, doc: DocModel) do end
49 end
50
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}`")
55 return
56 end
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")
61 proc.process(md)
62 end
63 end
64
65 # Markdown emitter used to produce the `ReadmeArticle`.
66 class ReadmeMdEmitter
67 super MarkdownEmitter
68
69 # Readme page being decorated.
70 var page: ReadmePage
71
72 # Phase used to access doc model and toolcontext.
73 var phase: ReadmePhase
74
75 init do open_article
76
77 # Push the article template on top of the buffer stack.
78 #
79 # Subsequent markdown writting will be done in the article template.
80 #
81 # See `ReadmeArticle::md`.
82 private fun push_article(article: ReadmeArticle) do
83 buffer_stack.add article.md
84 end
85
86 private var context = new Array[DocComposite]
87
88 # Creates a new ReadmeSection in `self.toc.page`.
89 #
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)
95 else
96 current_section.add_child(section)
97 end
98 current_section = section
99 context.add section
100 end
101 private var current_section: nullable ReadmeSection is noinit
102
103 # Close the current section.
104 #
105 # Ensure `context.last isa ReadmeSection`.
106 private fun close_section do
107 assert context.last isa ReadmeSection
108 context.pop
109 if context.is_empty then
110 current_section = null
111 else
112 current_section = context.last.as(ReadmeSection)
113 end
114 end
115
116 # Add an article at current location.
117 #
118 # This closes the current article, inserts `article` then opens a new article.
119 private fun add_article(article: DocArticle) do
120 close_article
121 if current_section == null then
122 page.root.add_child(article)
123 else
124 current_section.add_child(article)
125 end
126 open_article
127 end
128
129 # Creates a new ReadmeArticle in `self.toc.page`.
130 #
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)
137 context.add article
138 push_article article
139 end
140
141 # Close the current article.
142 #
143 # Ensure `context.last isa ReadmeArticle`.
144 fun close_article do
145 assert context.last isa ReadmeArticle
146 context.pop
147 pop_buffer
148 end
149 end
150
151 # MarkdownDecorator used to decorated the Readme file with links between doc entities.
152 class ReadmeDecorator
153 super MdDecorator
154
155 redef type EMITTER: ReadmeMdEmitter
156
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
161 v.close_article
162 while v.current_section != null do
163 if v.current_section.depth < lvl then break
164 v.close_section
165 end
166 end
167 v.open_section(lvl, txt)
168 v.open_article
169 end
170
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
178 if res.is_empty then
179 v.phase.warning(token.location, v.page, "Link to unknown entity `{link}`")
180 super
181 else
182 add_mentity_link(v, res.first, token.name, token.comment)
183 end
184 return
185 end
186 cmd.render(v, token)
187 end
188
189 # Renders a link to a mentity.
190 private fun add_mentity_link(v: EMITTER, mentity: MEntity, name, comment: nullable Text) do
191 # TODO real link
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
196 end
197 add_link(v, link, name, comment)
198 end
199 end
200
201 redef interface DocCommand
202
203 # Render the content of the doc command.
204 fun render(v: ReadmeMdEmitter, token: TokenWikiLink) is abstract
205
206 # Search `doc` model for mentities match `string`.
207 fun search_model(doc: DocModel, string: String): Array[MEntity] do
208 var res
209 if string.has("::") then
210 res = doc.mentities_by_namespace(string).to_a
211 else
212 res = doc.mentities_by_name(string).to_a
213 end
214 return res
215 end
216 end
217
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)
223 if res.is_empty then
224 v.phase.warning(
225 token.location, v.page,
226 "Try to include documentation of unknown entity `{args.first}`")
227 return
228 end
229 if res.length > 1 then
230 v.phase.warning(token.location, v.page, "conflicting article for `{args.first}` (choices : {res.join(", ")})")
231 end
232 v.add_article new DocumentationArticle("readme", "Readme", res.first)
233 end
234
235 private fun filter_results(res: Array[MEntity]): Array[MEntity] do
236 var out = new Array[MEntity]
237 for e in res do
238 if e isa MPackage then continue
239 if e isa MGroup then continue
240 out.add e
241 end
242 return out
243 end
244 end
245
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)
250 if res.is_empty then
251 v.phase.warning(token.location, v.page, "include article for unknown entity `{args.first}`")
252 return
253 end
254 if res.length > 1 then
255 v.phase.warning(token.location, v.page, "conflicting article for `{args.first}` (choices : {res.join(", ")})")
256 end
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)
265 end
266 end
267 end
268
269
270 # A section found in a README.
271 #
272 # Produced by markdown headlines like `## Section 1.1`.
273 class ReadmeSection
274 super DocSection
275
276 # The depth is based on the markdown headline depth.
277 redef var depth
278
279 # Markdown processor used to process the section title.
280 var markdown_processor: MarkdownProcessor
281
282 redef var is_hidden = false
283 end
284
285 # An article found in a README file.
286 #
287 # Basically, everything found in a README that is not a headline.
288 class ReadmeArticle
289 super DocArticle
290
291 # Markdown processor used to process the article content.
292 var markdown_processor: MarkdownProcessor
293
294 # Markdown content of this article extracted from the README file.
295 var md = new FlatBuffer
296
297 redef fun is_hidden do return super and md.trim.is_empty
298 end
299
300 # Documentation Article to introduce from the directive `doc: Something`.
301 #
302 # TODO merge with DefinitionArticle once the html is simplified
303 class DocumentationArticle
304 super MEntityArticle
305
306 redef var is_hidden = false
307 end
308
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)
313 end
314 end