]> git.ipfire.org Git - ipfire.org.git/blame - src/backend/wiki.py
wiki: Hide top level in breadcrumbs
[ipfire.org.git] / src / backend / wiki.py
CommitLineData
181d08f3
MT
1#!/usr/bin/python3
2
3import logging
6ac7e934 4import os.path
181d08f3
MT
5import re
6
7from . import misc
8from .decorators import *
9
181d08f3
MT
10class Wiki(misc.Object):
11 def _get_pages(self, query, *args):
12 res = self.db.query(query, *args)
13
14 for row in res:
15 yield Page(self.backend, row.id, data=row)
16
d398ca08
MT
17 def _get_page(self, query, *args):
18 res = self.db.get(query, *args)
19
20 if res:
21 return Page(self.backend, res.id, data=res)
22
6ac7e934
MT
23 def get_page_title(self, page, default=None):
24 doc = self.get_page(page)
25 if doc:
26 return doc.title
27
28 return default
29
181d08f3
MT
30 def get_page(self, page, revision=None):
31 page = Page.sanitise_page_name(page)
32 assert page
33
34 if revision:
d398ca08 35 return self._get_page("SELECT * FROM wiki WHERE page = %s \
181d08f3
MT
36 AND timestamp = %s", page, revision)
37 else:
d398ca08 38 return self._get_page("SELECT * FROM wiki WHERE page = %s \
181d08f3
MT
39 ORDER BY timestamp DESC LIMIT 1", page)
40
f9db574a 41 def get_recent_changes(self, limit=None):
181d08f3 42 return self._get_pages("SELECT * FROM wiki \
f9db574a
MT
43 WHERE timestamp >= NOW() - INTERVAL '4 weeks' \
44 ORDER BY timestamp DESC LIMIT %s", limit)
181d08f3 45
495e9dc4 46 def create_page(self, page, author, content, changes=None, address=None):
181d08f3
MT
47 page = Page.sanitise_page_name(page)
48
495e9dc4 49 return self._get_page("INSERT INTO wiki(page, author_uid, markdown, changes, address) \
df01767e 50 VALUES(%s, %s, %s, %s, %s) RETURNING *", page, author.uid, content or None, changes, address)
181d08f3 51
495e9dc4 52 def delete_page(self, page, author, **kwargs):
181d08f3
MT
53 # Do nothing if the page does not exist
54 if not self.get_page(page):
55 return
56
57 # Just creates a blank last version of the page
495e9dc4 58 self.create_page(page, author=author, content=None, **kwargs)
181d08f3
MT
59
60 @staticmethod
61 def _split_url(url):
62 parts = list(e for e in url.split("/") if e)
63
64 num_parts = len(parts)
bd296ca1 65 for i in range(1, num_parts):
181d08f3
MT
66 yield "/".join(parts[:i])
67
68 def make_breadcrumbs(self, url):
69 for part in self._split_url(url):
70 title = self.get_page_title(part, os.path.basename(part))
71
72 yield ("/%s" % part, title)
73
74
75class Page(misc.Object):
76 def init(self, id, data=None):
77 self.id = id
78 self.data = data
79
80 def __lt__(self, other):
81 if isinstance(other, self.__class__):
82 if self.page == other.page:
83 return self.timestamp < other.timestamp
84
85 return self.page < other.page
86
87 @staticmethod
88 def sanitise_page_name(page):
89 if not page:
90 return "/"
91
92 # Make sure that the page name does NOT end with a /
93 if page.endswith("/"):
94 page = page[:-1]
95
96 # Make sure the page name starts with a /
97 if not page.startswith("/"):
98 page = "/%s" % page
99
100 # Remove any double slashes
101 page = page.replace("//", "/")
102
103 return page
104
105 @property
106 def url(self):
db8448d9 107 return self.page
181d08f3
MT
108
109 @property
110 def page(self):
111 return self.data.page
112
113 @property
114 def title(self):
115 return self._title or self.page[1:]
116
117 @property
118 def _title(self):
119 if not self.markdown:
120 return
121
122 # Find first H1 headline in markdown
123 markdown = self.markdown.splitlines()
124
125 m = re.match(r"^# (.*)( #)?$", markdown[0])
126 if m:
127 return m.group(1)
128
3b05ef6e
MT
129 @lazy_property
130 def author(self):
131 if self.data.author_uid:
132 return self.backend.accounts.get_by_uid(self.data.author_uid)
133
181d08f3
MT
134 def _render(self, text):
135 logging.debug("Rendering %s" % self)
136
574794da
MT
137 patterns = (
138 (r"\[\[([\w\d\/]+)(?:\|([\w\d\s]+))\]\]", r"/\1", r"\2", None, None),
139 (r"\[\[([\w\d\/\-]+)\]\]", r"/\1", r"\1", self.backend.wiki.get_page_title, r"\1"),
140 )
141
142 for pattern, link, title, repl, args in patterns:
143 replacements = []
144
145 for match in re.finditer(pattern, text):
146 l = match.expand(link)
147 t = match.expand(title)
148
149 if callable(repl):
150 t = repl(match.expand(args)) or t
151
152 replacements.append((match.span(), t or l, l))
153
154 # Apply all replacements
155 for (start, end), t, l in reversed(replacements):
156 text = text[:start] + "[%s](%s)" % (t, l) + text[end:]
157
045ea3db
MT
158 # Borrow this from the blog
159 return self.backend.blog._render_text(text, lang="markdown")
181d08f3
MT
160
161 @property
162 def markdown(self):
163 return self.data.markdown
164
165 @property
166 def html(self):
167 return self.data.html or self._render(self.markdown)
168
169 @property
170 def timestamp(self):
171 return self.data.timestamp
172
173 def was_deleted(self):
174 return self.markdown is None
175
176 @lazy_property
177 def breadcrumbs(self):
178 return self.backend.wiki.make_breadcrumbs(self.page)
179
180 def get_latest_revision(self):
7d699684
MT
181 revisions = self.get_revisions()
182
183 # Return first object
184 for rev in revisions:
185 return rev
186
187 def get_revisions(self):
188 return self.backend.wiki._get_pages("SELECT * FROM wiki \
189 WHERE page = %s ORDER BY timestamp DESC", self.page)
091ac36b 190
d398ca08
MT
191 @property
192 def changes(self):
193 return self.data.changes
194
091ac36b
MT
195 # Sidebar
196
197 @lazy_property
198 def sidebar(self):
199 parts = self.page.split("/")
200
201 while parts:
202 sidebar = self.backend.wiki.get_page(os.path.join(*parts, "sidebar"))
203 if sidebar:
204 return sidebar
205
206 parts.pop()