Show More
@@ -0,0 +1,296 b'' | |||||
|
1 | # coding=utf-8 | |||
|
2 | from xml import etree | |||
|
3 | ||||
|
4 | import re | |||
|
5 | import random | |||
|
6 | import bbcode | |||
|
7 | ||||
|
8 | from urllib.parse import unquote | |||
|
9 | ||||
|
10 | from django.core.exceptions import ObjectDoesNotExist | |||
|
11 | from django.urls import reverse | |||
|
12 | from django.utils.translation import ugettext_lazy as _ | |||
|
13 | ||||
|
14 | import boards | |||
|
15 | from boards import settings | |||
|
16 | from swineboard.settings import ALLOWED_HOSTS | |||
|
17 | ||||
|
18 | ||||
|
19 | __author__ = 'neko259' | |||
|
20 | ||||
|
21 | ||||
|
22 | REFLINK_PATTERN = re.compile(r'^\d+$') | |||
|
23 | GLOBAL_REFLINK_PATTERN = re.compile(r'(\w+)::([^:]+)::(\d+)') | |||
|
24 | MULTI_NEWLINES_PATTERN = re.compile(r'(\r?\n){2,}') | |||
|
25 | ONE_NEWLINE = '\n' | |||
|
26 | REGEX_URL = re.compile(r'https?\://[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,3}(/\S*)?') | |||
|
27 | LINE_BREAK_HTML = '<div class="br"></div>' | |||
|
28 | SPOILER_SPACE = ' ' | |||
|
29 | REGEX_ANY_LINE_BREAK = re.compile(r'(' + LINE_BREAK_HTML + '|\r|\n)') | |||
|
30 | QUOTE_REPLACEMENT = '\g<1>>' | |||
|
31 | ||||
|
32 | MAX_SPOILER_MULTIPLIER = 2 | |||
|
33 | MAX_SPOILER_SPACE_COUNT = 20 | |||
|
34 | ||||
|
35 | ||||
|
36 | class TextFormatter: | |||
|
37 | """ | |||
|
38 | An interface for formatter that can be used in the text format panel | |||
|
39 | """ | |||
|
40 | ||||
|
41 | def __init__(self): | |||
|
42 | pass | |||
|
43 | ||||
|
44 | name = '' | |||
|
45 | ||||
|
46 | # Left and right tags for the button preview | |||
|
47 | preview_left = '' | |||
|
48 | preview_right = '' | |||
|
49 | ||||
|
50 | # Tag name to enclose the text in the form | |||
|
51 | tag_name = '' | |||
|
52 | ||||
|
53 | has_input = 'false' | |||
|
54 | input_prompt = '' | |||
|
55 | ||||
|
56 | ||||
|
57 | class AutolinkPattern: | |||
|
58 | def handleMatch(self, m): | |||
|
59 | link_element = etree.Element('a') | |||
|
60 | href = m.group(2) | |||
|
61 | link_element.set('href', href) | |||
|
62 | link_element.text = href | |||
|
63 | ||||
|
64 | return link_element | |||
|
65 | ||||
|
66 | ||||
|
67 | class QuotePattern(TextFormatter): | |||
|
68 | name = '>q' | |||
|
69 | preview_left = '<span class="quote">' | |||
|
70 | preview_right = '</span>' | |||
|
71 | ||||
|
72 | tag_name = 'quote' | |||
|
73 | ||||
|
74 | ||||
|
75 | class SpoilerPattern(TextFormatter): | |||
|
76 | name = 'spoiler' | |||
|
77 | preview_left = '<span class="spoiler">' | |||
|
78 | preview_right = '</span>' | |||
|
79 | ||||
|
80 | tag_name = 'spoiler' | |||
|
81 | ||||
|
82 | ||||
|
83 | class CommentPattern(TextFormatter): | |||
|
84 | name = '' | |||
|
85 | preview_left = '<span class="comment">// ' | |||
|
86 | preview_right = '</span>' | |||
|
87 | ||||
|
88 | tag_name = 'comment' | |||
|
89 | ||||
|
90 | ||||
|
91 | # TODO Use <s> tag here | |||
|
92 | class StrikeThroughPattern(TextFormatter): | |||
|
93 | name = 's' | |||
|
94 | preview_left = '<span class="strikethrough">' | |||
|
95 | preview_right = '</span>' | |||
|
96 | ||||
|
97 | tag_name = 's' | |||
|
98 | ||||
|
99 | ||||
|
100 | class ItalicPattern(TextFormatter): | |||
|
101 | name = 'i' | |||
|
102 | preview_left = '<i>' | |||
|
103 | preview_right = '</i>' | |||
|
104 | ||||
|
105 | tag_name = 'i' | |||
|
106 | ||||
|
107 | ||||
|
108 | class BoldPattern(TextFormatter): | |||
|
109 | name = 'b' | |||
|
110 | preview_left = '<b>' | |||
|
111 | preview_right = '</b>' | |||
|
112 | ||||
|
113 | tag_name = 'b' | |||
|
114 | ||||
|
115 | ||||
|
116 | class CodePattern(TextFormatter): | |||
|
117 | name = 'code' | |||
|
118 | preview_left = '<code>' | |||
|
119 | preview_right = '</code>' | |||
|
120 | ||||
|
121 | tag_name = 'code' | |||
|
122 | ||||
|
123 | ||||
|
124 | class HintPattern(TextFormatter): | |||
|
125 | name = 'hint' | |||
|
126 | preview_left = '<span class="hint">' | |||
|
127 | preview_right = '</span>' | |||
|
128 | ||||
|
129 | tag_name = 'hint' | |||
|
130 | ||||
|
131 | ||||
|
132 | class ColorPattern(TextFormatter): | |||
|
133 | name = 'color' | |||
|
134 | preview_left = '<span style="color:yellow;">' | |||
|
135 | preview_right = '</span>' | |||
|
136 | ||||
|
137 | tag_name = 'color' | |||
|
138 | ||||
|
139 | has_input = 'true' | |||
|
140 | input_prompt = _('Enter color (name or #ccc code)') | |||
|
141 | ||||
|
142 | ||||
|
143 | def render_reflink(tag_name, value, options, parent, context): | |||
|
144 | result = '>>%s' % value | |||
|
145 | ||||
|
146 | post = None | |||
|
147 | if REFLINK_PATTERN.match(value): | |||
|
148 | post_id = int(value) | |||
|
149 | ||||
|
150 | try: | |||
|
151 | post = boards.models.Post.objects.get(id=post_id) | |||
|
152 | ||||
|
153 | except ObjectDoesNotExist: | |||
|
154 | pass | |||
|
155 | elif GLOBAL_REFLINK_PATTERN.match(value): | |||
|
156 | match = GLOBAL_REFLINK_PATTERN.search(value) | |||
|
157 | try: | |||
|
158 | global_id = boards.models.GlobalId.objects.get( | |||
|
159 | key_type=match.group(1), key=match.group(2), | |||
|
160 | local_id=match.group(3)) | |||
|
161 | post = global_id.post | |||
|
162 | except ObjectDoesNotExist: | |||
|
163 | pass | |||
|
164 | ||||
|
165 | if post is not None: | |||
|
166 | result = post.get_link_view() | |||
|
167 | ||||
|
168 | return result | |||
|
169 | ||||
|
170 | ||||
|
171 | def render_quote(tag_name, value, options, parent, context): | |||
|
172 | source = options.get('quote') or options.get('source') | |||
|
173 | ||||
|
174 | if source: | |||
|
175 | result = '<div class="multiquote"><div class="quote-header">%s</div><div class="quote-text">%s</div></div>' % (source, value) | |||
|
176 | else: | |||
|
177 | # Insert a ">" at the start of every line | |||
|
178 | result = '<span class="quote">>{}</span>'.format( | |||
|
179 | REGEX_ANY_LINE_BREAK.sub(QUOTE_REPLACEMENT, value)) | |||
|
180 | ||||
|
181 | return result | |||
|
182 | ||||
|
183 | ||||
|
184 | def render_hint(tag_name, value, options, parent, context): | |||
|
185 | if 'hint' in options: | |||
|
186 | hint = options['hint'] | |||
|
187 | result = '<span class="hint" title="{}">{}</span>'.format(hint, value) | |||
|
188 | else: | |||
|
189 | result = value | |||
|
190 | return result | |||
|
191 | ||||
|
192 | ||||
|
193 | def render_notification(tag_name, value, options, parent, content): | |||
|
194 | username = value.lower() | |||
|
195 | ||||
|
196 | return '<a href="{}" class="user-cast">@{}</a>'.format( | |||
|
197 | reverse('notifications', kwargs={'username': username}), username) | |||
|
198 | ||||
|
199 | ||||
|
200 | def render_tag(tag_name, value, options, parent, context): | |||
|
201 | tag_name = value.lower() | |||
|
202 | ||||
|
203 | tag = boards.models.Tag.objects.get_by_alias(tag_name) | |||
|
204 | if tag: | |||
|
205 | url = tag.get_view() | |||
|
206 | else: | |||
|
207 | url = tag_name | |||
|
208 | ||||
|
209 | return url | |||
|
210 | ||||
|
211 | ||||
|
212 | def render_spoiler(tag_name, value, options, parent, context): | |||
|
213 | if settings.get_bool('Forms', 'AdditionalSpoilerSpaces'): | |||
|
214 | text_len = len(value) | |||
|
215 | space_count = min(random.randint(0, text_len * MAX_SPOILER_MULTIPLIER), | |||
|
216 | MAX_SPOILER_SPACE_COUNT) | |||
|
217 | side_spaces = SPOILER_SPACE * (space_count // 2) | |||
|
218 | else: | |||
|
219 | side_spaces = '' | |||
|
220 | return '<span class="spoiler">{}{}{}</span>'.format(side_spaces, | |||
|
221 | value, side_spaces) | |||
|
222 | ||||
|
223 | ||||
|
224 | formatters = [ | |||
|
225 | QuotePattern, | |||
|
226 | SpoilerPattern, | |||
|
227 | ItalicPattern, | |||
|
228 | BoldPattern, | |||
|
229 | CommentPattern, | |||
|
230 | StrikeThroughPattern, | |||
|
231 | CodePattern, | |||
|
232 | HintPattern, | |||
|
233 | ColorPattern, | |||
|
234 | ] | |||
|
235 | ||||
|
236 | ||||
|
237 | PREPARSE_PATTERNS = { | |||
|
238 | r'(?<!>)>>(\d+)': r'[post]\1[/post]', # Reflink ">>123" | |||
|
239 | r'^>([^>].+)': r'[quote]\1[/quote]', # Quote ">text" | |||
|
240 | r'^//\s?(.+)': r'[comment]\1[/comment]', # Comment "//text" | |||
|
241 | r'\B@(\w+)': r'[user]\1[/user]', # User notification "@user" | |||
|
242 | } | |||
|
243 | ||||
|
244 | for hostname in ALLOWED_HOSTS: | |||
|
245 | if hostname != '*': | |||
|
246 | PREPARSE_PATTERNS[r'https?://{}/thread/\d+/#(\d+)/?'.format(hostname)] = r'[post]\1[/post]' | |||
|
247 | PREPARSE_PATTERNS[r'https?://{}/thread/(\d+)/?'.format(hostname)] = r'[post]\1[/post]' | |||
|
248 | ||||
|
249 | ||||
|
250 | class Parser: | |||
|
251 | def __init__(self): | |||
|
252 | # The newline hack is added because br's margin does not work in all | |||
|
253 | # browsers except firefox, when the div's does. | |||
|
254 | self.parser = bbcode.Parser(newline=LINE_BREAK_HTML) | |||
|
255 | ||||
|
256 | self.parser.add_formatter('post', render_reflink, strip=True) | |||
|
257 | self.parser.add_formatter('quote', render_quote, strip=True) | |||
|
258 | self.parser.add_formatter('hint', render_hint, strip=True) | |||
|
259 | self.parser.add_formatter('user', render_notification, strip=True) | |||
|
260 | self.parser.add_formatter('tag', render_tag, strip=True) | |||
|
261 | self.parser.add_formatter('spoiler', render_spoiler, strip=True) | |||
|
262 | self.parser.add_simple_formatter( | |||
|
263 | 'comment', '<span class="comment">// %(value)s</span>', strip=True) | |||
|
264 | self.parser.add_simple_formatter( | |||
|
265 | 's', '<span class="strikethrough">%(value)s</span>') | |||
|
266 | self.parser.add_simple_formatter('code', | |||
|
267 | '<pre><code>%(value)s</pre></code>', | |||
|
268 | render_embedded=False, | |||
|
269 | escape_html=True, | |||
|
270 | replace_links=False, | |||
|
271 | replace_cosmetic=False) | |||
|
272 | ||||
|
273 | def preparse(self, text): | |||
|
274 | """ | |||
|
275 | Performs manual parsing before the bbcode parser is used. | |||
|
276 | Preparsed text is saved as raw and the text before preparsing is lost. | |||
|
277 | """ | |||
|
278 | new_text = MULTI_NEWLINES_PATTERN.sub(ONE_NEWLINE, text) | |||
|
279 | ||||
|
280 | for key, value in PREPARSE_PATTERNS.items(): | |||
|
281 | new_text = re.sub(key, value, new_text, flags=re.MULTILINE) | |||
|
282 | ||||
|
283 | for link in REGEX_URL.findall(text): | |||
|
284 | new_text = new_text.replace(link, unquote(link)) | |||
|
285 | ||||
|
286 | return new_text | |||
|
287 | ||||
|
288 | def parse(self, text): | |||
|
289 | return self.parser.format(text) | |||
|
290 | ||||
|
291 | ||||
|
292 | parser = Parser() | |||
|
293 | ||||
|
294 | ||||
|
295 | def get_parser(): | |||
|
296 | return parser |
@@ -0,0 +1,10 b'' | |||||
|
1 | [Unit] | |||
|
2 | Description=Swineboard imageboard | |||
|
3 | After=network.target | |||
|
4 | ||||
|
5 | [Service] | |||
|
6 | ExecStart=/usr/bin/uwsgi_python33 --ini uwsgi.ini | |||
|
7 | WorkingDirectory=<where is your swineboard located> | |||
|
8 | ||||
|
9 | [Install] | |||
|
10 | WantedBy=multi-user.target |
1 | NO CONTENT: file was removed |
|
NO CONTENT: file was removed |
1 | NO CONTENT: file was removed |
|
NO CONTENT: file was removed |
General Comments 0
You need to be logged in to leave comments.
Login now