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 intrude import markdown
22 # Looks up a WikiEntry by its `name`.
25 # 1. Looks in the current section
26 # 2. Looks in the current section children
27 # 3. Looks in the current section parent
28 # 4. Looks up to wiki root
30 # Returns `null` if no article can be found.
31 fun lookup_entry_by_name
(context
: WikiEntry, name
: String): nullable WikiEntry do
32 var section
= context
.parent
33 var res
= section
.lookup_entry_by_name
(name
)
34 if res
!= null then return res
35 while section
!= null do
36 if section
.name
== name
then return section
37 if section
.children
.has_key
(name
) then return section
.children
[name
]
38 section
= section
.parent
43 # Looks up a WikiEntry by its `title`.
46 # 1. Looks in the current section
47 # 2. Looks in the current section children
48 # 3. Looks in the current section parent
49 # 4. Looks up to wiki root
51 # Returns `null` if no article can be found.
52 fun lookup_entry_by_title
(context
: WikiEntry, title
: String): nullable WikiEntry do
53 var section
= context
.parent
54 var res
= section
.lookup_entry_by_title
(title
)
55 if res
!= null then return res
56 while section
!= null do
57 if section
.title
== title
then return section
58 for child
in section
.children
.values
do
59 if child
.title
== title
then return child
61 section
= section
.parent
66 # Looks up a WikiEntry by its `path`.
68 # Path can be relative from `context` like `context/entry`.
69 # Or absolute like `/entry1/entry2`.
71 # Returns `null` if no article can be found.
72 fun lookup_entry_by_path
(context
: WikiEntry, path
: String): nullable WikiEntry do
73 var entry
= context
.parent
74 var parts
= path
.split_with
("/")
75 if path
.has_prefix
("/") then
77 if parts
.is_empty
then return root_section
.index
80 while not parts
.is_empty
do
81 var name
= parts
.shift
82 if name
.is_empty
then continue
83 if not entry
.children
.has_key
(name
) then return null
84 entry
= entry
.children
[name
]
92 # Url to `self` once generated.
93 fun url
: String do return wiki
.config
.root_url
.join_path
(breadcrumbs
.join
("/"))
97 if not is_dirty
and not wiki
.force_render
then return
100 # Search in `self` then `self.children` if an entry has the name `name`.
101 fun lookup_entry_by_name
(name
: String): nullable WikiEntry do
102 if children
.has_key
(name
) then return children
[name
]
103 for child
in children
.values
do
104 var res
= child
.lookup_entry_by_name
(name
)
105 if res
!= null then return res
110 # Search in `self` then `self.children` if an entry has the title `title`.
111 fun lookup_entry_by_title
(title
: String): nullable WikiEntry do
112 for child
in children
.values
do
113 if child
.title
== title
then return child
115 for child
in children
.values
do
116 var res
= child
.lookup_entry_by_title
(title
)
117 if res
!= null then return res
123 redef class WikiSection
125 # The index page for this section.
127 # If no file `index.md` exists for this section,
128 # a summary is generated using contained articles.
129 var index
: WikiArticle is lazy
do
130 for child
in children
.values
do
131 if child
isa WikiArticle and child
.is_index
then return child
133 return new WikiSectionIndex(wiki
, "index", self)
137 redef class WikiArticle
139 # Headlines ids and titles.
140 var headlines
= new ArrayMap[String, HeadLine]
142 # Is `self` an index page?
144 # Checks if `self.name == "index"`.
145 fun is_index
: Bool do return name
== "index"
148 if parent
== null then
149 return wiki
.config
.root_url
.join_path
("{name}.html")
151 return parent
.url
.join_path
("{name}.html")
157 if not is_dirty
and not wiki
.force_render
or not has_source
then return
158 var md_proc
= new NitiwikiMdProcessor(wiki
, self)
159 content
= md_proc
.process
(md
.as(not null))
160 headlines
.recover_with
(md_proc
.emitter
.decorator
.headlines
)
164 # A `WikiArticle` that contains the section index tree.
165 class WikiSectionIndex
168 # The section described by `self`.
169 var section
: WikiSection
171 redef fun title
do return section
.title
173 redef fun url
do return section
.url
176 # A MarkdownProcessor able to parse wiki links.
177 class NitiwikiMdProcessor
178 super MarkdownProcessor
180 # Wiki used to resolve links.
183 # Article parsed by `self`.
185 # Used to contextualize links.
186 var context
: WikiArticle
189 emitter
= new MarkdownEmitter(self)
190 emitter
.decorator
= new NitiwikiDecorator(wiki
, context
)
193 redef fun token_at
(text
, pos
) do
195 if not token
isa TokenLink then return token
196 if pos
+ 1 < text
.length
then
197 var c
= text
[pos
+ 1]
198 if c
== '[' then return new TokenWikiLink(pos
, c
)
204 private class NitiwikiDecorator
207 # Wiki used to resolve links.
210 # Article used to contextualize links.
211 var context
: WikiArticle
213 fun add_wikilink
(v
: MarkdownEmitter, link
: Text, name
, comment
: nullable Text) do
214 var wiki
= v
.processor
.as(NitiwikiMdProcessor).wiki
215 var target
: nullable WikiEntry = null
216 var anchor
: nullable String = null
217 if link
.has
("#") then
218 var parts
= link
.split_with
("#")
220 anchor
= parts
.subarray
(1, parts
.length
- 1).join
("#")
222 if link
.has
("/") then
223 target
= wiki
.lookup_entry_by_path
(context
, link
.to_s
)
225 target
= wiki
.lookup_entry_by_name
(context
, link
.to_s
)
226 if target
== null then
227 target
= wiki
.lookup_entry_by_title
(context
, link
.to_s
)
231 if target
!= null then
232 if name
== null then name
= target
.title
235 wiki
.message
("Warning: unknown wikilink `{link}` (in {context.src_path.as(not null)})", 0)
236 v
.add
"class=\"broken\
" "
239 append_value(v, link)
240 if anchor != null then append_value(v, "#{anchor}")
242 if comment != null and not comment.is_empty then
244 append_value
(v
, comment
)
248 if name == null then name = link
254 # A NitiWiki link token.
256 # Something of the form `[[foo]]`.
261 # * `[[Wikilink/Bar]]`
262 # * `[[Wikilink#foo]]`
263 # * `[[Wikilink/Bar#foo]]`
264 # * `[[title|Wikilink]]`
265 # * `[[title|Wikilink/Bar]]`
266 # * `[[title|Wikilink/Bar#foo]]`
270 redef fun emit_hyper(v) do
271 v.decorator.as(NitiwikiDecorator).add_wikilink(v, link.as(not null), name, comment)
274 redef fun check_link(v, out, start, token) do
275 var md = v.current_text
277 var tmp = new FlatBuffer
278 pos = md.read_md_link_id(tmp, pos)
279 if pos < start then return -1
280 var name = tmp.write_to_string
281 if name.has("|") then
282 var parts = name.split_once_on("|")
283 self.name = parts.first
290 pos = md.skip_spaces(pos)
291 if pos < start then return -1