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 # Nitdoc specific Markdown format handling for Nitweb
19 intrude import doc_down
20 intrude import markdown
::wikilinks
22 import model
::model_index
24 redef class NitwebConfig
25 # Specific Markdown processor to use within Nitweb
26 var md_processor
: MarkdownProcessor is lazy
do
27 var proc
= new MarkdownProcessor
28 proc
.decorator
= new NitwebDecorator(view
, modelbuilder
)
36 use
("/docdown/", new APIDocdown(config
))
40 # Docdown handler accept docdown as POST data and render it as HTML
44 redef fun post
(req
, res
) do
45 res
.html config
.md_processor
.process
(req
.body
)
49 # Specific Markdown decorator for Nitweb
51 # We reuse all the implementation of the NitdocDecorator and add the wikilinks handling.
55 # View used by wikilink commands to find model entities
58 # Modelbuilder used to access code
59 var modelbuilder
: ModelBuilder
61 redef fun add_span_code
(v
, buffer
, from
, to
) do
62 var text
= new FlatBuffer
63 buffer
.read
(text
, from
, to
)
64 var name
= text
.write_to_string
65 name
= name
.replace
("nullable ", "")
66 var mentity
= try_find_mentity
(view
, name
)
67 if mentity
== null then
71 v
.write_mentity_link
(mentity
, text
.write_to_string
)
76 private fun try_find_mentity
(view
: ModelView, text
: String): nullable MEntity do
77 var mentity
= view
.mentity_by_full_name
(text
)
78 if mentity
!= null then return mentity
80 var mentities
= view
.mentities_by_name
(text
)
81 if mentities
.is_empty
then
83 else if mentities
.length
> 1 then
84 # TODO smart resolve conflicts
86 return mentities
.first
89 redef fun add_wikilink
(v
, token
) do
90 v
.render_wikilink
(token
, view
)
94 # Same as `InlineDecorator` but with wikilink commands handling
95 class NitwebInlineDecorator
98 # View used by wikilink commands to find model entities
101 # Modelbuilder used to access code
102 var modelbuilder
: ModelBuilder
104 redef fun add_wikilink
(v
, token
) do
105 v
.render_wikilink
(token
, view
)
109 redef class MarkdownProcessor
111 # Parser used to process doc commands
112 var parser
= new DocCommandParser
115 fun render_wikilink
(token
: TokenWikiLink, model
: ModelView) do
116 var link
= token
.link
117 if link
== null then return
118 var name
= token
.name
119 if name
!= null then link
= "{name} | {link}"
120 var cmd
= parser
.parse
(link
.write_to_string
)
122 var full_name
= if token
.link
!= null then token
.link
.as(not null).write_to_string
.trim
else null
123 if full_name
== null or full_name
.is_empty
then
124 write_error
("empty wikilink")
127 var mentity
= find_mentity
(model
, full_name
)
128 if mentity
== null then return
129 name
= if token
.name
!= null then token
.name
.as(not null).to_s
else null
130 write_mentity_link
(mentity
, name
)
133 for message
in parser
.errors
do
134 if message
.level
== 1 then
135 write_error
(message
.message
)
136 else if message
.level
> 1 then
137 write_warning
(message
.message
)
141 cmd
.render
(self, token
, model
)
144 # Find the MEntity that matches `name`.
146 # Write an error if the entity is not found
147 fun find_mentity
(model
: ModelView, name
: nullable String): nullable MEntity do
149 write_error
("no MEntity found")
152 # Lookup by full name
153 var mentity
= model
.mentity_by_full_name
(name
)
154 if mentity
!= null then return mentity
156 var mentities
= model
.mentities_by_name
(name
)
157 if mentities
.is_empty
then
158 var suggest
= model
.find
(name
, 3)
160 msg
.append
"no MEntity found for name `{name}`"
161 if suggest
.not_empty
then
162 msg
.append
" (suggestions: "
165 msg
.append
"`{s.full_name}`"
166 if i
< suggest
.length
- 1 then msg
.append
", "
171 write_error
(msg
.write_to_string
)
173 else if mentities
.length
> 1 then
175 msg
.append
"conflicts for name `{name}`"
176 msg
.append
" (conflicts: "
178 for s
in mentities
do
179 msg
.append
"`{s.full_name}`"
180 if i
< mentities
.length
- 1 then msg
.append
", "
184 write_warning
(msg
.write_to_string
)
186 return mentities
.first
189 # Write a warning in the output
190 fun write_warning
(text
: String) do
191 emit_text
"<p class='text-warning'>Warning: {text}</p>"
194 # Write an error in the output
195 fun write_error
(text
: String) do
196 emit_text
"<p class='text-danger'>Error: {text}</p>"
199 # Write a link to a mentity in the output
200 fun write_mentity_link
(mentity
: MEntity, text
: nullable String) do
201 var link
= mentity
.web_url
202 var name
= text
or else mentity
.name
203 var mdoc
= mentity
.mdoc_or_fallback
205 if mdoc
!= null then comment
= mdoc
.synopsis
206 decorator
.add_link
(self, link
, name
, comment
)
209 # Write a mentity list in the output
210 fun write_mentity_list
(mentities
: Collection[MEntity]) do
212 for mentity
in mentities
do
213 var mdoc
= mentity
.mdoc_or_fallback
215 write_mentity_link
(mentity
)
218 emit_text mdoc
.synopsis
226 redef class DocCommand
228 # Emit the HTML related to the execution of this doc command
229 fun render
(v
: MarkdownProcessor, token
: TokenWikiLink, model
: ModelView) do
230 v
.write_error
("not yet implemented command `{token.link or else "null"}`")
234 redef class CommentCommand
235 redef fun render
(v
, token
, model
) do
237 var mentity
= v
.find_mentity
(model
, name
)
238 if mentity
== null then return
239 var mdoc
= mentity
.mdoc_or_fallback
241 v
.write_warning
("no MDoc for mentity `{name}`")
245 if not opts
.has_key
("no-link") then
246 v
.write_mentity_link
(mentity
)
248 if not opts
.has_key
("no-link") and not opts
.has_key
("no-synopsis") then
251 if not opts
.has_key
("no-synopsis") then
252 v
.emit_text mdoc
.html_synopsis
.write_to_string
255 if not opts
.has_key
("no-comment") then
256 v
.add v
.process
(mdoc
.comment
).write_to_string
261 redef class ListCommand
262 redef fun render
(v
, token
, model
) do
264 var mentity
= v
.find_mentity
(model
, name
)
265 if mentity
== null then return
266 if mentity
isa MPackage then
267 v
.write_mentity_list
(mentity
.mgroups
)
268 else if mentity
isa MGroup then
269 var res
= new Array[MEntity]
270 res
.add_all mentity
.in_nesting
.smallers
271 res
.add_all mentity
.mmodules
272 v
.write_mentity_list
(res
)
273 else if mentity
isa MModule then
274 v
.write_mentity_list
(mentity
.mclassdefs
)
275 else if mentity
isa MClass then
276 v
.write_mentity_list
(mentity
.collect_intro_mproperties
(model
))
277 else if mentity
isa MClassDef then
278 v
.write_mentity_list
(mentity
.mpropdefs
)
279 else if mentity
isa MProperty then
280 v
.write_mentity_list
(mentity
.mpropdefs
)
282 v
.write_error
("no list found for name `{name}`")
287 redef class CodeCommand
288 redef fun render
(v
, token
, model
) do
290 var mentity
= v
.find_mentity
(model
, name
)
291 if mentity
== null then return
292 if mentity
isa MClass then mentity
= mentity
.intro
293 if mentity
isa MProperty then mentity
= mentity
.intro
294 var source
= render_source
(mentity
, v
.decorator
.as(NitwebDecorator).modelbuilder
)
295 if source
== null then
296 v
.write_error
("no source for MEntity `{name}`")
304 # Highlight `mentity` source code.
305 private fun render_source
(mentity
: MEntity, modelbuilder
: ModelBuilder): nullable HTMLTag do
306 var node
= modelbuilder
.mentity2node
(mentity
)
307 if node
== null then return null
308 var hl
= new HtmlightVisitor
309 hl
.highlight_node node
314 redef class GraphCommand
315 redef fun render
(v
, token
, model
) do
317 var mentity
= v
.find_mentity
(model
, name
)
318 if mentity
== null then return
319 var g
= new InheritanceGraph(mentity
, model
)
320 var pdepth
= if opts
.has_key
("pdepth") and opts
["pdepth"].is_int
then
321 opts
["pdepth"].to_i
else 3
322 var cdepth
= if opts
.has_key
("cdepth") and opts
["cdepth"].is_int
then
323 opts
["cdepth"].to_i
else 3
324 v
.add g
.draw
(pdepth
, cdepth
).to_svg
329 # Read `self` between `nstart` and `nend` (excluded) and writte chars to `out`.
330 private fun read
(out
: FlatBuffer, nstart
, nend
: Int): Int do
332 while pos
< length
and pos
< nend
do
336 if pos
== length
then return -1