# This file is part of NIT ( http://www.nitlanguage.org ).
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Nitdoc specific Markdown format handling for Nitweb
module api_docdown
import api_graph
intrude import doc_down
intrude import markdown::wikilinks
import doc_commands
import model::model_index
redef class NitwebConfig
# Specific Markdown processor to use within Nitweb
var md_processor: MarkdownProcessor is lazy do
var proc = new MarkdownProcessor
proc.emitter.decorator = new NitwebDecorator(view, modelbuilder)
return proc
end
end
redef class APIRouter
redef init do
super
use("/docdown/", new APIDocdown(config))
end
end
# Docdown handler accept docdown as POST data and render it as HTML
class APIDocdown
super APIHandler
redef fun post(req, res) do
res.html config.md_processor.process(req.body)
end
end
# Specific Markdown decorator for Nitweb
#
# We reuse all the implementation of the NitdocDecorator and add the wikilinks handling.
class NitwebDecorator
super NitdocDecorator
# View used by wikilink commands to find model entities
var view: ModelView
# Modelbuilder used to access code
var modelbuilder: ModelBuilder
redef fun add_span_code(v, buffer, from, to) do
var text = new FlatBuffer
buffer.read(text, from, to)
var name = text.write_to_string
name = name.replace("nullable ", "")
var mentity = try_find_mentity(view, name)
if mentity == null then
super
else
v.add ""
v.write_mentity_link(mentity, text.write_to_string)
v.add "
"
end
end
private fun try_find_mentity(view: ModelView, text: String): nullable MEntity do
var mentity = view.mentity_by_full_name(text)
if mentity != null then return mentity
var mentities = view.mentities_by_name(text)
if mentities.is_empty then
return null
else if mentities.length > 1 then
# TODO smart resolve conflicts
end
return mentities.first
end
redef fun add_wikilink(v, token) do
v.render_wikilink(token, view)
end
end
# Same as `InlineDecorator` but with wikilink commands handling
class NitwebInlineDecorator
super InlineDecorator
# View used by wikilink commands to find model entities
var view: ModelView
# Modelbuilder used to access code
var modelbuilder: ModelBuilder
redef fun add_wikilink(v, token) do
v.render_wikilink(token, view)
end
end
redef class MarkdownEmitter
# Parser used to process doc commands
var parser = new DocCommandParser
# Render a wikilink
fun render_wikilink(token: TokenWikiLink, model: ModelView) do
var link = token.link
if link == null then return
var name = token.name
if name != null then link = "{name} | {link}"
var cmd = parser.parse(link.write_to_string)
if cmd == null then
var full_name = if token.link != null then token.link.as(not null).write_to_string.trim else null
if full_name == null or full_name.is_empty then
write_error("empty wikilink")
return
end
var mentity = find_mentity(model, full_name)
if mentity == null then return
name = if token.name != null then token.name.as(not null).to_s else null
write_mentity_link(mentity, name)
return
else
for message in parser.errors do
if message.level == 1 then
write_error(message.message)
else if message.level > 1 then
write_warning(message.message)
end
end
end
cmd.render(self, token, model)
end
# Find the MEntity that matches `name`.
#
# Write an error if the entity is not found
fun find_mentity(model: ModelView, name: nullable String): nullable MEntity do
if name == null then
write_error("no MEntity found")
return null
end
# Lookup by full name
var mentity = model.mentity_by_full_name(name)
if mentity != null then return mentity
var mentities = model.mentities_by_name(name)
if mentities.is_empty then
var suggest = model.find(name, 3)
var msg = new Buffer
msg.append "no MEntity found for name `{name}`"
if suggest.not_empty then
msg.append " (suggestions: "
var i = 0
for s in suggest do
msg.append "`{s.full_name}`"
if i < suggest.length - 1 then msg.append ", "
i += 1
end
msg.append ")"
end
write_error(msg.write_to_string)
return null
else if mentities.length > 1 then
var msg = new Buffer
msg.append "conflicts for name `{name}`"
msg.append " (conflicts: "
var i = 0
for s in mentities do
msg.append "`{s.full_name}`"
if i < mentities.length - 1 then msg.append ", "
i += 1
end
msg.append ")"
write_warning(msg.write_to_string)
end
return mentities.first
end
# Write a warning in the output
fun write_warning(text: String) do
emit_text "
Warning: {text}
" end # Write an error in the output fun write_error(text: String) do emit_text "Error: {text}
" end # Write a link to a mentity in the output fun write_mentity_link(mentity: MEntity, text: nullable String) do var link = mentity.web_url var name = text or else mentity.name var mdoc = mentity.mdoc_or_fallback var comment = null if mdoc != null then comment = mdoc.synopsis decorator.add_link(self, link, name, comment) end # Write a mentity list in the output fun write_mentity_list(mentities: Collection[MEntity]) do add "" v.add source v.add "" end # Highlight `mentity` source code. private fun render_source(mentity: MEntity, modelbuilder: ModelBuilder): nullable HTMLTag do var node = modelbuilder.mentity2node(mentity) if node == null then return null var hl = new HighlightVisitor hl.enter_visit node return hl.html end end redef class GraphCommand redef fun render(v, token, model) do var name = arg var mentity = v.find_mentity(model, name) if mentity == null then return var g = new InheritanceGraph(mentity, model) var pdepth = if opts.has_key("pdepth") and opts["pdepth"].is_int then opts["pdepth"].to_i else 3 var cdepth = if opts.has_key("cdepth") and opts["cdepth"].is_int then opts["cdepth"].to_i else 3 v.add g.draw(pdepth, cdepth).to_svg end end redef class Text # Read `self` between `nstart` and `nend` (excluded) and writte chars to `out`. private fun read(out: FlatBuffer, nstart, nend: Int): Int do var pos = nstart while pos < length and pos < nend do out.add self[pos] pos += 1 end if pos == length then return -1 return pos end end