]>
Commit | Line | Data |
---|---|---|
181d08f3 MT |
1 | #!/usr/bin/python3 |
2 | ||
3 | import logging | |
4 | import markdown2 | |
6ac7e934 | 5 | import os.path |
181d08f3 MT |
6 | import re |
7 | ||
8 | from . import misc | |
9 | from .decorators import * | |
10 | ||
11 | # Used to automatically link some things | |
12 | link_patterns = ( | |
13 | # Find bug reports | |
14 | (re.compile(r"(?:#(\d+))", re.I), r"https://bugzilla.ipfire.org/show_bug.cgi?id=\1"), | |
15 | ||
16 | # Email Addresses | |
17 | (re.compile(r"([a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+)"), r"mailto:\1"), | |
18 | ||
19 | # CVE Numbers | |
20 | (re.compile(r"(?:CVE)[\s\-](\d{4}\-\d+)"), r"https://cve.mitre.org/cgi-bin/cvename.cgi?name=\1"), | |
21 | ) | |
22 | ||
23 | class Wiki(misc.Object): | |
24 | def _get_pages(self, query, *args): | |
25 | res = self.db.query(query, *args) | |
26 | ||
27 | for row in res: | |
28 | yield Page(self.backend, row.id, data=row) | |
29 | ||
6ac7e934 MT |
30 | def get_page_title(self, page, default=None): |
31 | doc = self.get_page(page) | |
32 | if doc: | |
33 | return doc.title | |
34 | ||
35 | return default | |
36 | ||
181d08f3 MT |
37 | def get_page(self, page, revision=None): |
38 | page = Page.sanitise_page_name(page) | |
39 | assert page | |
40 | ||
41 | if revision: | |
42 | res = self.db.get("SELECT * FROM wiki WHERE page = %s \ | |
43 | AND timestamp = %s", page, revision) | |
44 | else: | |
45 | res = self.db.get("SELECT * FROM wiki WHERE page = %s \ | |
46 | ORDER BY timestamp DESC LIMIT 1", page) | |
47 | ||
48 | if res: | |
49 | return Page(self.backend, res.id, data=res) | |
50 | ||
51 | def get_recent_changes(self): | |
52 | return self._get_pages("SELECT * FROM wiki \ | |
53 | WHERE timestamp >= NOW() - INTERVAL '4 weeks' ORDER BY timestamp DESC") | |
54 | ||
55 | def create_page(self, page, author, markdown): | |
56 | page = Page.sanitise_page_name(page) | |
57 | ||
58 | res = self.db.get("INSERT INTO wiki(page, author_id, markdown) \ | |
59 | VALUES(%s, %s, %s) RETURNING id", page, author.id, markdown) | |
60 | ||
61 | if res: | |
62 | return self.get_page_by_id(res.id) | |
63 | ||
64 | def delete_page(self, page, author): | |
65 | # Do nothing if the page does not exist | |
66 | if not self.get_page(page): | |
67 | return | |
68 | ||
69 | # Just creates a blank last version of the page | |
70 | self.create_page(page, author, None) | |
71 | ||
72 | @staticmethod | |
73 | def _split_url(url): | |
74 | parts = list(e for e in url.split("/") if e) | |
75 | ||
76 | num_parts = len(parts) | |
77 | for i in range(num_parts): | |
78 | yield "/".join(parts[:i]) | |
79 | ||
80 | def make_breadcrumbs(self, url): | |
81 | for part in self._split_url(url): | |
82 | title = self.get_page_title(part, os.path.basename(part)) | |
83 | ||
84 | yield ("/%s" % part, title) | |
85 | ||
86 | ||
87 | class Page(misc.Object): | |
88 | def init(self, id, data=None): | |
89 | self.id = id | |
90 | self.data = data | |
91 | ||
92 | def __lt__(self, other): | |
93 | if isinstance(other, self.__class__): | |
94 | if self.page == other.page: | |
95 | return self.timestamp < other.timestamp | |
96 | ||
97 | return self.page < other.page | |
98 | ||
99 | @staticmethod | |
100 | def sanitise_page_name(page): | |
101 | if not page: | |
102 | return "/" | |
103 | ||
104 | # Make sure that the page name does NOT end with a / | |
105 | if page.endswith("/"): | |
106 | page = page[:-1] | |
107 | ||
108 | # Make sure the page name starts with a / | |
109 | if not page.startswith("/"): | |
110 | page = "/%s" % page | |
111 | ||
112 | # Remove any double slashes | |
113 | page = page.replace("//", "/") | |
114 | ||
115 | return page | |
116 | ||
117 | @property | |
118 | def url(self): | |
119 | return "/%s" % self.page | |
120 | ||
121 | @property | |
122 | def page(self): | |
123 | return self.data.page | |
124 | ||
125 | @property | |
126 | def title(self): | |
127 | return self._title or self.page[1:] | |
128 | ||
129 | @property | |
130 | def _title(self): | |
131 | if not self.markdown: | |
132 | return | |
133 | ||
134 | # Find first H1 headline in markdown | |
135 | markdown = self.markdown.splitlines() | |
136 | ||
137 | m = re.match(r"^# (.*)( #)?$", markdown[0]) | |
138 | if m: | |
139 | return m.group(1) | |
140 | ||
141 | def _render(self, text): | |
142 | logging.debug("Rendering %s" % self) | |
143 | ||
144 | return markdown2.markdown(text, link_patterns=link_patterns, | |
145 | extras=["footnotes", "link-patterns", "wiki-tables"]) | |
146 | ||
147 | @property | |
148 | def markdown(self): | |
149 | return self.data.markdown | |
150 | ||
151 | @property | |
152 | def html(self): | |
153 | return self.data.html or self._render(self.markdown) | |
154 | ||
155 | @property | |
156 | def timestamp(self): | |
157 | return self.data.timestamp | |
158 | ||
159 | def was_deleted(self): | |
160 | return self.markdown is None | |
161 | ||
162 | @lazy_property | |
163 | def breadcrumbs(self): | |
164 | return self.backend.wiki.make_breadcrumbs(self.page) | |
165 | ||
166 | def get_latest_revision(self): | |
167 | return self.backend.wiki.get_page(self.page) |