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 # Wiki internal links handling.
19 import markdown
::wikilinks
23 # Looks up a WikiEntry by its `name`.
26 # 1. Looks in the current section
27 # 2. Looks in the current section children
28 # 3. Looks in the current section parent
29 # 4. Looks up to wiki root
31 # Returns `null` if no article can be found.
32 fun lookup_entry_by_name
(context
: WikiEntry, name
: String): nullable WikiEntry do
33 var section
: nullable WikiEntry = context
.parent
or else context
34 var res
= section
.lookup_entry_by_name
(name
)
35 if res
!= null then return res
36 while section
!= null do
37 if section
.name
== name
then return section
38 if section
.children
.has_key
(name
) then return section
.children
[name
]
39 section
= section
.parent
44 # Looks up a WikiEntry by its `title`.
47 # 1. Looks in the current section
48 # 2. Looks in the current section children
49 # 3. Looks in the current section parent
50 # 4. Looks up to wiki root
52 # Returns `null` if no article can be found.
53 fun lookup_entry_by_title
(context
: WikiEntry, title
: String): nullable WikiEntry do
54 var section
: nullable WikiEntry = context
.parent
or else context
55 var res
= section
.lookup_entry_by_title
(title
)
56 if res
!= null then return res
57 while section
!= null do
58 if section
.title
.to_lower
== title
.to_lower
then return section
59 for child
in section
.children
.values
do
60 if child
.title
.to_lower
== title
.to_lower
then return child
62 section
= section
.parent
67 # Looks up a WikiEntry by its `path`.
69 # Path can be relative from `context` like `context/entry`.
70 # Or absolute like `/entry1/entry2`.
72 # Returns `null` if no article can be found.
73 fun lookup_entry_by_path
(context
: WikiEntry, path
: String): nullable WikiEntry do
74 var entry
= context
.parent
or else context
75 var parts
= path
.split_with
("/")
76 if path
.has_prefix
("/") then
78 if parts
.is_empty
then return root_section
.index
81 while not parts
.is_empty
do
82 var name
= parts
.shift
83 if name
.is_empty
then continue
84 if entry
.name
== name
then continue
85 if not entry
.children
.has_key
(name
) then return null
86 entry
= entry
.children
[name
]
91 # Trails between pages
93 # Trails are represented as a forest of entries.
94 # This way it is possible to represent a flat-trail as a visit of a tree.
95 var trails
= new OrderedTree[WikiEntry]
100 # Relative path to `self` from the target root_url
101 fun href
: String do return breadcrumbs
.join
("/")
103 # Relative path to the directory `self` from the target root_url
104 fun dir_href
: String do return href
.dirname
106 # Relative path to the root url from `self`
107 fun root_href
: String do
108 var root_dir
= dir_href
.relpath
("")
109 # Avoid issues if used as a macro just followed by a `/` (as with url prefix)
110 if root_dir
== "" then root_dir
= "."
114 # A relative `href` to `self` from the page `context`.
116 # Should be used to navigate between documents.
117 fun href_from
(context
: WikiEntry): String
119 var res
= context
.dir_href
.relpath
(href
)
123 # A relative hyperlink <a> to `self` from the page `context`.
125 # If `text` is not given, `title` will be used instead.
126 fun a_from
(context
: WikiEntry, text
: nullable Text): Writable
128 var title
= title
.html_escape
129 if text
== null then text
= title
else text
= text
.html_escape
130 var href
= href_from
(context
)
131 return """<a href="{{{href}}}" title="{{{title}}}">{{{text}}}</a>"""
136 if not is_dirty
and not wiki
.force_render
then return
137 render_sidebar_wikilinks
140 # Search in `self` then `self.children` if an entry has the name `name`.
141 fun lookup_entry_by_name
(name
: String): nullable WikiEntry do
142 if children
.has_key
(name
) then return children
[name
]
143 for child
in children
.values
do
144 var res
= child
.lookup_entry_by_name
(name
)
145 if res
!= null then return res
150 # Search in `self` then `self.children` if an entry has the title `title`.
151 fun lookup_entry_by_title
(title
: String): nullable WikiEntry do
152 for child
in children
.values
do
153 if child
.title
.to_lower
== title
.to_lower
then return child
155 for child
in children
.values
do
156 var res
= child
.lookup_entry_by_title
(title
)
157 if res
!= null then return res
162 private var md_proc
: NitiwikiMdProcessor is lazy
do
163 return new NitiwikiMdProcessor(wiki
, self)
166 # Process wikilinks from sidebar.
167 private fun render_sidebar_wikilinks
do
168 var blocks
= sidebar
.blocks
169 for i
in [0..blocks
.length
[ do
170 blocks
[i
] = md_proc
.process
(blocks
[i
].to_s
).write_to_string
171 md_proc
.emitter
.decorator
.headlines
.clear
176 redef class WikiSection
178 # The index page for this section.
180 # If no file `index.md` exists for this section,
181 # a summary is generated using contained articles.
182 var index
: WikiArticle is lazy
do
183 for child
in children
.values
do
184 if child
isa WikiArticle and child
.is_index
then return child
186 return new WikiSectionIndex(wiki
, "index", self)
189 redef fun dir_href
do return href
192 redef class WikiArticle
194 # Headlines ids and titles.
195 var headlines
= new ArrayMap[String, HeadLine]
197 # Is `self` an index page?
199 # Checks if `self.name == "index"`.
200 fun is_index
: Bool do return name
== "index"
203 if parent
== null then
206 return parent
.href
.join_path
("{name}.html")
212 if not is_dirty
and not wiki
.force_render
or not has_source
then return
213 content
= md_proc
.process
(md
.as(not null))
214 headlines
.add_all
(md_proc
.emitter
.decorator
.headlines
)
218 # A `WikiArticle` that contains the section index tree.
219 class WikiSectionIndex
222 # The section described by `self`.
223 var section
: WikiSection
225 redef fun title
do return section
.title
227 redef fun href
do return section
.href
229 redef fun dir_href
do return section
.dir_href
232 # A MarkdownProcessor able to parse wiki links.
233 class NitiwikiMdProcessor
234 super MarkdownProcessor
236 # Wiki used to resolve links.
239 # Article parsed by `self`.
241 # Used to contextualize links.
242 var context
: WikiEntry
245 emitter
= new MarkdownEmitter(self)
246 emitter
.decorator
= new NitiwikiDecorator(wiki
, context
)
250 # The decorator associated to `MarkdownProcessor`.
251 class NitiwikiDecorator
254 # Wiki used to resolve links.
257 # Article used to contextualize links.
258 var context
: WikiEntry
260 redef fun add_wikilink
(v
, token
) do
261 var wiki
= v
.processor
.as(NitiwikiMdProcessor).wiki
262 var target
: nullable WikiEntry = null
263 var anchor
: nullable String = null
264 var link
= token
.link
265 if link
== null then return
266 var name
= token
.name
268 if not link
.has_prefix
("http://") and not link
.has_prefix
("https://") then
269 # Extract commands from the link.
271 var command_split
= link
.split_once_on
(":")
272 if command_split
.length
> 1 then
273 command
= command_split
[0].trim
274 link
= command_split
[1].trim
277 if link
.has
("#") then
278 var parts
= link
.split_with
("#")
280 anchor
= parts
.subarray
(1, parts
.length
- 1).join
("#")
282 if link
.has
("/") then
283 target
= wiki
.lookup_entry_by_path
(context
, link
.to_s
)
285 target
= wiki
.lookup_entry_by_name
(context
, link
.to_s
)
286 if target
== null then
287 target
= wiki
.lookup_entry_by_title
(context
, link
.to_s
)
290 if target
!= null then
291 if name
== null then name
= target
.title
292 link
= target
.href_from
(context
)
294 if command
== "trail" then
295 if target
isa WikiSection then target
= target
.index
296 wiki
.trails
.add
(context
, target
)
299 wiki
.message
("Warning: unknown wikilink `{link}` (in {context.src_path.as(not null)})", 0)
300 v
.add
"class=\"broken\
" "
304 append_value(v, link)
305 if anchor != null then append_value(v, "#{anchor}")
307 var comment = token.comment
308 if comment != null and not comment.is_empty then
310 append_value
(v
, comment
)
314 if name == null then name = link