]> git.ipfire.org Git - ipfire.org.git/blame - src/backend/wiki.py
wiki: Hide breadcrumbs on lower levels
[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 59
3168788e
MT
60 def make_breadcrumbs(self, url):
61 # Split and strip all empty elements (double slashes)
181d08f3
MT
62 parts = list(e for e in url.split("/") if e)
63
3168788e 64 ret = []
b1bf7d48 65 for part in ("/".join(parts[:i]) for i in range(1, len(parts))):
3168788e 66 ret.append(("/%s" % part, self.get_page_title(part, os.path.basename(part))))
181d08f3 67
3168788e 68 return ret
181d08f3
MT
69
70
71class Page(misc.Object):
72 def init(self, id, data=None):
73 self.id = id
74 self.data = data
75
76 def __lt__(self, other):
77 if isinstance(other, self.__class__):
78 if self.page == other.page:
79 return self.timestamp < other.timestamp
80
81 return self.page < other.page
82
83 @staticmethod
84 def sanitise_page_name(page):
85 if not page:
86 return "/"
87
88 # Make sure that the page name does NOT end with a /
89 if page.endswith("/"):
90 page = page[:-1]
91
92 # Make sure the page name starts with a /
93 if not page.startswith("/"):
94 page = "/%s" % page
95
96 # Remove any double slashes
97 page = page.replace("//", "/")
98
99 return page
100
101 @property
102 def url(self):
db8448d9 103 return self.page
181d08f3
MT
104
105 @property
106 def page(self):
107 return self.data.page
108
109 @property
110 def title(self):
111 return self._title or self.page[1:]
112
113 @property
114 def _title(self):
115 if not self.markdown:
116 return
117
118 # Find first H1 headline in markdown
119 markdown = self.markdown.splitlines()
120
121 m = re.match(r"^# (.*)( #)?$", markdown[0])
122 if m:
123 return m.group(1)
124
3b05ef6e
MT
125 @lazy_property
126 def author(self):
127 if self.data.author_uid:
128 return self.backend.accounts.get_by_uid(self.data.author_uid)
129
181d08f3
MT
130 def _render(self, text):
131 logging.debug("Rendering %s" % self)
132
574794da
MT
133 patterns = (
134 (r"\[\[([\w\d\/]+)(?:\|([\w\d\s]+))\]\]", r"/\1", r"\2", None, None),
135 (r"\[\[([\w\d\/\-]+)\]\]", r"/\1", r"\1", self.backend.wiki.get_page_title, r"\1"),
136 )
137
138 for pattern, link, title, repl, args in patterns:
139 replacements = []
140
141 for match in re.finditer(pattern, text):
142 l = match.expand(link)
143 t = match.expand(title)
144
145 if callable(repl):
146 t = repl(match.expand(args)) or t
147
148 replacements.append((match.span(), t or l, l))
149
150 # Apply all replacements
151 for (start, end), t, l in reversed(replacements):
152 text = text[:start] + "[%s](%s)" % (t, l) + text[end:]
153
045ea3db
MT
154 # Borrow this from the blog
155 return self.backend.blog._render_text(text, lang="markdown")
181d08f3
MT
156
157 @property
158 def markdown(self):
159 return self.data.markdown
160
161 @property
162 def html(self):
163 return self.data.html or self._render(self.markdown)
164
165 @property
166 def timestamp(self):
167 return self.data.timestamp
168
169 def was_deleted(self):
170 return self.markdown is None
171
172 @lazy_property
173 def breadcrumbs(self):
174 return self.backend.wiki.make_breadcrumbs(self.page)
175
176 def get_latest_revision(self):
7d699684
MT
177 revisions = self.get_revisions()
178
179 # Return first object
180 for rev in revisions:
181 return rev
182
183 def get_revisions(self):
184 return self.backend.wiki._get_pages("SELECT * FROM wiki \
185 WHERE page = %s ORDER BY timestamp DESC", self.page)
091ac36b 186
d398ca08
MT
187 @property
188 def changes(self):
189 return self.data.changes
190
091ac36b
MT
191 # Sidebar
192
193 @lazy_property
194 def sidebar(self):
195 parts = self.page.split("/")
196
197 while parts:
198 sidebar = self.backend.wiki.get_page(os.path.join(*parts, "sidebar"))
199 if sidebar:
200 return sidebar
201
202 parts.pop()