Initial commit.
This commit is contained in:
commit
bcad5e9f32
|
@ -0,0 +1,3 @@
|
|||
__pycache__
|
||||
/test.py
|
||||
/*.egg-info
|
|
@ -0,0 +1,25 @@
|
|||
# Python port of Planète Casio's textout utility
|
||||
On [Planète Casio][pc], which is coded in PHP, we have our own custom
|
||||
version of BBcode, which we pass through an internal utility named `textout()`.
|
||||
I, Thomas “Cakeisalie5” Touhey, remade it recently, and it works pretty well
|
||||
while being secure, but as the next version of Planète Casio (the ”v5”)
|
||||
will be written from scratch, I figured out I could rewrite the `textout()`
|
||||
utility in Python.
|
||||
|
||||
As this is a rewrite, the vulnerabilities and bug will not be common to this
|
||||
project and the online version of the transcoder. To use this module, simply
|
||||
use the `tohtml()` function once imported:
|
||||
|
||||
#!/usr/bin/env python3
|
||||
import textoutpc
|
||||
|
||||
text = "Hello, [i]beautiful [b]world[/i]!"
|
||||
print(textoutpc.tohtml(text))
|
||||
|
||||
Maybe some day, a full “textout bbcode” reference should be written.
|
||||
Also, some day, this module should be able to convert to other formats
|
||||
such as [lightscript][ls], a markdown-like formatting language made by
|
||||
another PC admin, which also made the native Python module.
|
||||
|
||||
[pc]: https://www.planet-casio.com/Fr/
|
||||
[ls]: https://bitbucket.org/Lephenixnoir/lightscript
|
|
@ -0,0 +1,18 @@
|
|||
#!/usr/bin/env python3
|
||||
from setuptools import setup
|
||||
|
||||
setup(name='textoutpc',
|
||||
version='0.1',
|
||||
description='Textout() equivalent from Planète Casio',
|
||||
author='Thomas "Cakeisalie5" Touhey',
|
||||
author_email='thomas@touhey.fr',
|
||||
url='https://github.com/cakeisalie5/PlaneteCasioTextout',
|
||||
keywords='planète casio textout bbcode',
|
||||
|
||||
packages=['textoutpc'],
|
||||
|
||||
classifiers = [
|
||||
'Development Status :: 2 - Pre-Alpha',
|
||||
'Topic :: Software Development :: Interpreters'
|
||||
]
|
||||
)
|
|
@ -0,0 +1,7 @@
|
|||
#!/usr/bin/env python3
|
||||
""" Placeholder to tell that this folder is actually a module.
|
||||
"""
|
||||
|
||||
from .test_html import *
|
||||
|
||||
# End of file.
|
|
@ -0,0 +1,103 @@
|
|||
#!/usr/bin/env python3
|
||||
""" Unit tests for the Python version of textout.
|
||||
Uses the builtin `unittest` module.
|
||||
"""
|
||||
|
||||
import textoutpc
|
||||
import unittest
|
||||
|
||||
# Define the tests.
|
||||
|
||||
__test_cases = {
|
||||
'<script>alert(1);</script>': '<script>alert(1);</script>',
|
||||
'[progress=lol]mdr[/progress]': '[progress=lol]mdr[/progress]',
|
||||
'[u][b][a][i][/b]': "<u><b>[a]<i></i></b></u>",
|
||||
|
||||
# Links.
|
||||
'[url=http://hey.org/lol[]>"a]': '<a href="http://hey.org/lol[]>%22a" ' \
|
||||
'target="_blank" rel="noopener">http://hey.org/lol[]>"a</a>',
|
||||
'[url]javascript:alert(1)[/url]': '[url]javascript:alert(1)[/url]',
|
||||
'(http://www.example.org/some-[damn-url]-(youknow))': \
|
||||
'(<a href="http://www.example.org/some-[damn-url]-(youknow)" ' \
|
||||
'target="_blank" rel="noopener">' \
|
||||
'http://www.example.org/some-[damn-url]-(youknow)</a>)',
|
||||
|
||||
# No evaluation.
|
||||
"[b]a[noeval]b[/b]c[/noeval]d": "<b>ab[/b]cd</b>",
|
||||
"a[noeval]b[noeval]c[/noeval]d[/noeval]e": "ab[noeval]c[/noeval]de",
|
||||
"`[code]`": '<span style="font-family: monospace;">[code]</span>',
|
||||
"[noeval]``[/noeval]": "``",
|
||||
"[inlinecode]`[/inlinecode]": \
|
||||
'<span style="font-family: monospace;">`</span>',
|
||||
'[noeval]<>[/noeval]': '<>',
|
||||
|
||||
# Completion.
|
||||
'[a][c][/a]': '[a][c][/a]',
|
||||
'[a][a]': '[a][a]',
|
||||
"[<>]><[/<>]": "[<>]><[/<>]",
|
||||
|
||||
# Links.
|
||||
'[url]<script>alert(1);</script>[/url]': \
|
||||
'[url]<script>alert(1);</script>[/url]',
|
||||
'[url=https://thomas.touhey.fr/]mon profil est le meilleur[/url]':
|
||||
'<a href="https://thomas.touhey.fr/" target="_blank" rel="noopener">' \
|
||||
'mon profil est le meilleur</a>',
|
||||
'https://thomas.touhey.fr/, tu vois ?': \
|
||||
'<a href="https://thomas.touhey.fr/" target="_blank" rel="noopener">' \
|
||||
'https://thomas.touhey.fr/</a>, tu vois ?',
|
||||
|
||||
# Pictures.
|
||||
'[img]"incroyable"[/img]': '<img src="incroyable">',
|
||||
|
||||
# Spoilers.
|
||||
'[spoiler=Hello|world> :D]Close this, quick![/spoiler]': \
|
||||
'<div class="spoiler"><div class="title on" ' \
|
||||
'onclick="toggleSpoiler(this.parentNode, ' "'open'" ');">Hello' \
|
||||
'</div><div class="title off" ' \
|
||||
'onclick="toggleSpoiler(this.parentNode, ' "'close'" ');">world' \
|
||||
'> <img src="/images/smileys/grin.gif"></div><div class="off">' \
|
||||
'Close this, quick!</div></div>',
|
||||
|
||||
# Videos.
|
||||
'[video]"><script>alert(1)</script>[/video]': \
|
||||
'[video]"><script>alert(1)</script>[/video]',
|
||||
'[video]<script>alert(document.cookie)</script>[/video]': \
|
||||
'[video]<script>alert(document.cookie)</script>[/video]',
|
||||
'[video]https://www.youtube.com/watch?v=6odDOOyUawY[/video]': \
|
||||
'<iframe width="560" height="340" ' \
|
||||
'src="https://www.youtube.com/embed/6odDOOyUawY" ' \
|
||||
'frameborder="0" allowfullscreen></iframe>',
|
||||
'[video]https://www.youtube.com/watch?v=<script>alert(1)</script>' \
|
||||
'[/video]': \
|
||||
'<a href="https://www.youtube.com/watch?v=<script>alert(1)</script>"' \
|
||||
' target="_blank" rel="noopener">https://www.youtube.com/watch?v=' \
|
||||
'<script>alert(1)</script></a>',
|
||||
|
||||
# Quotes.
|
||||
'[quote=Test 1 :)]lel[/quote]': \
|
||||
'<div class="citation"><b>Test 1 ' \
|
||||
'<img src="/images/smileys/smile.gif"> a écrit :</b><br />lel</div>'
|
||||
}
|
||||
|
||||
# Define the tests wrapper, and define the classes.
|
||||
|
||||
_cnt = 0
|
||||
def _wrap_test(inp, res):
|
||||
global _cnt
|
||||
|
||||
_cnt += 1
|
||||
return """\
|
||||
def test_html{n}(self):
|
||||
self.assertEquals(textoutpc.tohtml({i}), {r})
|
||||
""".format(n = _cnt, i = repr(inp), r = repr(res))
|
||||
|
||||
exec("class TextoutHTMLTest(unittest.TestCase):\n" + \
|
||||
'\n'.join(map(lambda args: _wrap_test(*args), __test_cases.items())),
|
||||
globals())
|
||||
|
||||
# If run as main script, run the test function.
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
# End of file.
|
|
@ -0,0 +1,169 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
""" Main BBcode decoder.
|
||||
Decodes the given text into a list of tags.
|
||||
"""
|
||||
|
||||
import regex as _re
|
||||
from .Elements import *
|
||||
|
||||
__all__ = ["TextoutDecoder"]
|
||||
|
||||
class TextoutTagSearch:
|
||||
""" Tag search in a text data. """
|
||||
|
||||
_Tag = _re.compile("""\
|
||||
\[(?P<bname>
|
||||
(?P<bname_e>[^\/\[\]\=][^\[\]\=]* (\[(?&bname_e)\]?)*)*
|
||||
)(=(?P<value>
|
||||
(?P<value_e>[^\[\]]* (\[(?&value_e)\]?)*)*
|
||||
))?\]
|
||||
|
|
||||
\[/(?P<ename>
|
||||
(?P<ename_e>[^\/\[\]\=][^\[\]\=]* (\[(?&ename_e)\]?)*)*
|
||||
)\]
|
||||
|
|
||||
`
|
||||
""", _re.VERBOSE | _re.M)
|
||||
|
||||
BEGIN = 0
|
||||
END = 1
|
||||
SPECIAL = 2
|
||||
|
||||
def __init__(self, text):
|
||||
""" Get the next tag information. """
|
||||
|
||||
rr = self._Tag.search(text)
|
||||
self.__valid = True
|
||||
if not rr: self.__valid = False
|
||||
gr = rr.groupdict()
|
||||
|
||||
self.full = text[rr.start(0):rr.end(0)]
|
||||
self.offset = rr.start(0)
|
||||
|
||||
if gr['bname'] != None:
|
||||
self.type = self.BEGIN
|
||||
self.name = gr['bname']
|
||||
self.value = gr['value']
|
||||
elif gr['ename'] != None:
|
||||
self.type = self.END
|
||||
self.name = gr['ename']
|
||||
else:
|
||||
self.type = self.SPECIAL
|
||||
self.name = self.full
|
||||
|
||||
self.name = self.name.lower()
|
||||
|
||||
def __bool__(self):
|
||||
return self.__valid
|
||||
|
||||
class TextoutDecoder:
|
||||
""" Actual decoder for Planète Casio's BBcode.
|
||||
Initialize with the text, and use the `decode()` method to
|
||||
decode! """
|
||||
|
||||
def __init__(self, text, mode):
|
||||
""" Initialize the decoder. """
|
||||
|
||||
self.__raw = text
|
||||
self.__next = None
|
||||
self.__goback = 0
|
||||
|
||||
def __iter__(self):
|
||||
""" Use this object to iterate on it. """
|
||||
|
||||
return self
|
||||
|
||||
def __decode(self, emode = True, opened = []):
|
||||
""" Recursive function for decoding.
|
||||
|
||||
`emode` is a boolean, the “evaluation mode”, to see if
|
||||
we should just return a TextoutDocument (True), or
|
||||
some raw text (False).
|
||||
`opened` is the LIFO corresponding to the names of the
|
||||
opened tags.
|
||||
"""
|
||||
|
||||
raw = ""
|
||||
depth = 0
|
||||
|
||||
while self.text:
|
||||
# Get the next tag information.
|
||||
# If there is no tag ahead, copy the text to the end.
|
||||
cur = TextoutTagSearch(self.text)
|
||||
if not cur:
|
||||
raw += self.text
|
||||
self.text = ""
|
||||
break
|
||||
|
||||
# Copy the text, remove the text and the tag.
|
||||
raw += self.text[:cur.offset]
|
||||
self.text = self.text[cur.offset + len(cur.offset):]
|
||||
|
||||
# Check if it's an end.
|
||||
if cur.type in (cur.END, cur.SPECIAL):
|
||||
if not emode:
|
||||
if opened[0] != cur['name']:
|
||||
pass # display the tag
|
||||
elif depth > 0:
|
||||
depth -= 1
|
||||
else:
|
||||
self.goback = 0
|
||||
break
|
||||
else:
|
||||
# Try to autoclose ([one][two][three][/one])
|
||||
try:
|
||||
op_off = opened.index(cur['name'])
|
||||
self.goback = op_off
|
||||
break
|
||||
except ValueError: pass
|
||||
|
||||
if cur['type'] == TextoutEncoder.TEXTOUT_END:
|
||||
raw += cur['full']
|
||||
|
||||
# It is a beginning tag (or a special tag interpr. as a begin.)!
|
||||
# Check if the tag exists.
|
||||
if not evaluate or not cur['name'] in self.tags:
|
||||
raw += cur['full']
|
||||
if not evaluate and cur['name'] == opened[0]:
|
||||
depth += 1
|
||||
continue
|
||||
|
||||
# Get the tag.
|
||||
tag = self.tags[cur['name']]
|
||||
|
||||
# Empty the raw buffer into the encoded one.
|
||||
if evaluate:
|
||||
raw = self.__mode.escape(raw)
|
||||
encoded += raw
|
||||
raw = ""
|
||||
|
||||
# Get the subcontent.
|
||||
evaluate_sub = evaluate
|
||||
if not tag.evaluate:
|
||||
evaluate_sub = False
|
||||
sub = self._encode([cur['name']] + opened, evaluate_sub)
|
||||
|
||||
# Initialize the tag instance, get the content.
|
||||
try:
|
||||
tag = tag(cur['name'], cur['value'], sub)
|
||||
encoded += tag.html()
|
||||
except:
|
||||
raw += cur['full']
|
||||
|
||||
def __next__(self):
|
||||
""" Get the next element. """
|
||||
|
||||
# If there is no more text, just stop where we are.
|
||||
if not self.text:
|
||||
raise StopIteration
|
||||
|
||||
# XXX: this is completely TODO.
|
||||
raise StopIteration
|
||||
|
||||
def decode(self):
|
||||
""" Get the full thing. """
|
||||
|
||||
|
||||
|
||||
# End of file.
|
|
@ -0,0 +1,43 @@
|
|||
#!/usr/bin/env python3
|
||||
""" This file defines and includes all of the possible elements constituting
|
||||
a bbcode-encoded document.
|
||||
"""
|
||||
|
||||
import html as _html
|
||||
from .Tags import *
|
||||
|
||||
class TextoutText:
|
||||
""" The raw text element. """
|
||||
|
||||
def __init__(self, text):
|
||||
""" Initialize the textout tag with the documented members. """
|
||||
|
||||
self.text = text
|
||||
|
||||
def html(self):
|
||||
""" Convert the result to HTML. """
|
||||
|
||||
return _html.escape(self.text)
|
||||
|
||||
class TextoutDocument:
|
||||
""" The tag collection. """
|
||||
|
||||
def __init__(self):
|
||||
""" Initialize the textout document. """
|
||||
|
||||
self.elements = []
|
||||
|
||||
def add(self, element):
|
||||
""" Add an element to the list. """
|
||||
|
||||
self.elements.append(element)
|
||||
|
||||
def html(self):
|
||||
""" Make HTML out of our element. """
|
||||
|
||||
enc = ""
|
||||
for element in self.elements:
|
||||
enc += element.html()
|
||||
return enc
|
||||
|
||||
# End of file.
|
|
@ -0,0 +1,56 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from .__base__ import TextoutTag
|
||||
|
||||
__all__ = ["TextoutCodeTag", "TextoutInlineCodeTag"]
|
||||
|
||||
class TextoutCodeTag(TextoutTag):
|
||||
""" The basic code tag, for displaying code.
|
||||
Example uses:
|
||||
|
||||
[code]int main() {
|
||||
printf("hello, world");
|
||||
}[/code] """
|
||||
|
||||
def _prepare(self):
|
||||
self.eval = False
|
||||
|
||||
def begin_text(self):
|
||||
return "---\n"
|
||||
|
||||
def end_text(self):
|
||||
return self.content + "\n---\n"
|
||||
|
||||
def begin_html(self):
|
||||
return '<div class="code">'
|
||||
|
||||
def end_html(self):
|
||||
return _htmlescape(self.content) + '</div>'
|
||||
|
||||
class TextoutInlineCodeTag(TextoutTag):
|
||||
""" Inline code tag, doesn't display a box, simply doesn't evaluate
|
||||
the content and uses monospace font.
|
||||
Example uses:
|
||||
|
||||
`some_inline_code`
|
||||
[inlinecode][b]The tags will be shown verbatim.[/b][/inlinecode]
|
||||
[inlinecode][i]This also works![/inlinecode]
|
||||
"""
|
||||
|
||||
def _prepare(self):
|
||||
self.eval = False
|
||||
|
||||
def begin_text(self):
|
||||
return "`"
|
||||
|
||||
def end_text(self):
|
||||
return self.content + "`"
|
||||
|
||||
def begin_html(self):
|
||||
return '<span style="font-family: monospace;">'
|
||||
|
||||
def end_html(self):
|
||||
return _htmlescape(self.content) + '</span>'
|
||||
|
||||
# End of file.
|
|
@ -0,0 +1,287 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import re as _re, colorsys as _color, math as _math
|
||||
from .__base__ import TextoutTag
|
||||
|
||||
__all__ = ['TextoutColorTag']
|
||||
|
||||
# ---
|
||||
# Colors per color name.
|
||||
# ---
|
||||
|
||||
_cols = {
|
||||
# Standard CSS color names.
|
||||
'aliceblue': '#F0F8FF', 'antiquewhite': '#FAEBD7', 'aqua': '#00FFFF',
|
||||
'aquamarine': '#7FFFD4', 'azure': '#F0FFFF', 'beige': '#F5F5DC',
|
||||
'bisque': '#FFE4C4', 'black': '#000000', 'blanchedalmond': '#FFEBCD',
|
||||
'blue': '#0000FF', 'blueviolet': '#8A2BE2', 'brown': '#A52A2A',
|
||||
'burlywood': '#DEB887', 'cadetblue': '#5F9EA0', 'chartreuse': '#7FFF00',
|
||||
'chocolate': '#D2691E', 'coral': '#FF7F50', 'cornflowerblue': '#6495ED',
|
||||
'cornsilk': '#FFF8DC', 'crimson': '#DC143C', 'cyan': '#00FFFF',
|
||||
'darkblue': '#00008B', 'darkcyan': '#008B8B', 'darkgoldenrod': '#B8860B',
|
||||
'darkgray': '#A9A9A9', 'darkgrey': '#A9A9A9', 'darkgreen': '#006400',
|
||||
'darkkhaki': '#BDB76B', 'darkmagenta': '#8B008B',
|
||||
'darkolivegreen': '#556B2F', 'darkorange': '#FF8C00',
|
||||
'darkorchid': '#9932CC', 'darkred': '#8B0000', 'darksalmon': '#E9967A',
|
||||
'darkseagreen': '#8FBC8F', 'darkslateblue': '#483D8B',
|
||||
'darkslategray': '#2F4F4F', 'darkslategrey': '#2F4F4F',
|
||||
'darkturquoise': '#00CED1', 'darkviolet': '#9400D3',
|
||||
'deeppink': '#FF1493', 'deepskyblue': '#00BFFF',
|
||||
'dimgray': '#696969', 'dimgrey': '#696969', 'dodgerblue': '#1E90FF',
|
||||
'firebrick': '#B22222', 'floralwhite': '#FFFAF0', 'forestgreen': '#228B22',
|
||||
'fuchsia': '#FF00FF', 'gainsboro': '#DCDCDC', 'ghostwhite': '#F8F8FF',
|
||||
'gold': '#FFD700', 'goldenrod': '#DAA520', 'gray': '#808080',
|
||||
'grey': '#808080', 'green': '#008000', 'greenyellow': '#ADFF2F',
|
||||
'honeydew': '#F0FFF0', 'hotpink': '#FF69B4', 'indianred': '#CD5C5C',
|
||||
'indigo': '#4B0082', 'ivory': '#FFFFF0', 'khaki': '#F0E68C',
|
||||
'lavender': '#E6E6FA', 'lavenderblush': '#FFF0F5', 'lawngreen': '#7CFC00',
|
||||
'lemonchiffon': '#FFFACD', 'lightblue': '#ADD8E6', 'lightcoral': '#F08080',
|
||||
'lightcyan': '#E0FFFF', 'lightgoldenrodyellow': '#FAFAD2',
|
||||
'lightgray': '#D3D3D3', 'lightgrey': '#D3D3D3', 'lightgreen': '#90EE90',
|
||||
'lightpink': '#FFB6C1', 'lightsalmon': '#FFA07A',
|
||||
'lightseagreen': '#20B2AA', 'lightskyblue': '#87CEFA',
|
||||
'lightslategray': '#778899', 'lightslategrey': '#778899',
|
||||
'lightsteelblue': '#B0C4DE', 'lightyellow': '#FFFFE0',
|
||||
'lime': '#00FF00', 'limegreen': '#32CD32', 'linen': '#FAF0E6',
|
||||
'magenta': '#FF00FF', 'maroon': '#800000', 'mediumaquamarine': '#66CDAA',
|
||||
'mediumblue': '#0000CD', 'mediumorchid': '#BA55D3',
|
||||
'mediumpurple': '#9370DB', 'mediumseagreen': '#3CB371',
|
||||
'mediumslateblue': '#7B68EE', 'mediumspringgreen': '#00FA9A',
|
||||
'mediumturquoise': '#48D1CC', 'mediumvioletred': '#C71585',
|
||||
'midnightblue': '#191970', 'mintcream': '#F5FFFA', 'mistyrose': '#FFE4E1',
|
||||
'moccasin': '#FFE4B5', 'navajowhite': '#FFDEAD', 'navy': '#000080',
|
||||
'oldlace': '#FDF5E6', 'olive': '#808000', 'olivedrab': '#6B8E23',
|
||||
'orange': '#FFA500', 'olivedrab': '#6B8E23', 'orange': '#FFA500',
|
||||
'orangered': '#FF4500', 'orchid': '#DA70D6', 'palegoldenrod': '#EEE8AA',
|
||||
'palegreen': '#98FB98', 'paleturquoise': '#AFEEEE',
|
||||
'palevioletred': '#DB7093', 'papayawhip': '#FFEFD5',
|
||||
'peachpuff': '#FFDAB9', 'peru': '#CD853F', 'pink': '#FFC0CB',
|
||||
'plum': '#DDA0DD', 'powderblue': 'B0E0E6', 'purple': '#800080',
|
||||
'rebeccapurple': '#663399', 'red': '#FF0000', 'rosybrown': '#BC8F8F',
|
||||
'royalblue': '#4169E1', 'saddlebrown': '#8B4513', 'salmon': '#FA8072',
|
||||
'sandybrown': '#F4A460', 'seagreen': '#2E8B57', 'seashell': '#FFF5EE',
|
||||
'sienna': '#A0522D', 'silver': '#C0C0C0', 'skyblue': '#87CEEB',
|
||||
'slateblue': '#6A5ACD', 'slategray': '#708090', 'slategrey': '#708090',
|
||||
'snow': '#FFFAFA', 'springgreen': '#00FF7F', 'steelblue': '#4682B4',
|
||||
'tan': '#D2B48C', 'teal': '#008080', 'thistle': '#D8BFD8',
|
||||
'tomato': '#FF6347', 'turquoise': '#40E0D0', 'violet': '#EE82EE',
|
||||
'wheat': '#F5DEB3', 'white': '#FFFFFF', 'whitesmoke': '#F5F5F5',
|
||||
'yellow': '#FFFF00', 'yellowgreen': '#9ACD32'
|
||||
}
|
||||
|
||||
# ---
|
||||
# Main CSS color expression.
|
||||
# ---
|
||||
|
||||
_cr = _re.compile('^\s*('
|
||||
'#'
|
||||
'(?P<thr_r>[0-9a-f])'
|
||||
'(?P<thr_g>[0-9a-f])'
|
||||
'(?P<thr_b>[0-9a-f])'
|
||||
'|#'
|
||||
'(?P<six_r>[0-9a-f]{2})'
|
||||
'(?P<six_g>[0-9a-f]{2})'
|
||||
'(?P<six_b>[0-9a-f]{2})'
|
||||
'|rgb\s*\('
|
||||
'\s*' '(?P<rgb_r>[0-9]{1,3})' '\s*,'
|
||||
'\s*' '(?P<rgb_g>[0-9]{1,3})' '\s*,'
|
||||
'\s*' '(?P<rgb_b>[0-9]{1,3})' '\s*'
|
||||
'\)'
|
||||
'|hsla?\s*\('
|
||||
'\s*' '(?P<hsl_hue>-?0*[0-9]{1,3}(\.[0-9]*)?)'
|
||||
'(?P<hsl_agl>deg|grad|rad|turn|)' '\s*[,\\s]'
|
||||
'\s*' '((?P<hsl_sat_per>0*[0-9]{1,3})%'
|
||||
'|(?P<hsl_sat_flt>0*(\.[0-9]*)?))' '\s*[,\\s]'
|
||||
'\s*' '((?P<hsl_lgt_per>0*[0-9]{1,3})%'
|
||||
'|(?P<hsl_lgt_flt>0*(\.[0-9]*)?))' '\s*([,\\s]'
|
||||
'\s*' '((?P<hsl_aph_per>0*[0-9]{0,3})%?'
|
||||
'|(?P<hsl_aph_flt>0*(\.[0-9]*)?))' '\s*)?'
|
||||
'\)'
|
||||
'|hlsa?\s*\('
|
||||
'\s*' '(?P<hls_hue>-?0*[0-9]{1,3}(\.[0-9]*)?)'
|
||||
'(?P<hls_agl>deg|grad|rad|turn|)' '\s*[,\\s]'
|
||||
'\s*' '((?P<hls_lgt_per>0*[0-9]{1,3})%'
|
||||
'|(?P<hls_lgt_flt>0*(\.[0-9]*)?))' '\s*[,\\s]'
|
||||
'\s*' '((?P<hls_sat_per>0*[0-9]{1,3})%'
|
||||
'|(?P<hls_sat_flt>0*(\.[0-9]*)?))' '\s*([,\\s]'
|
||||
'\s*' '((?P<hls_aph_per>0*[0-9]{0,3})%?'
|
||||
'|(?P<hls_aph_flt>0*(\.[0-9]*)?))' '\s*)?'
|
||||
'\)'
|
||||
# '|hwb\s*\('
|
||||
# '\s*' '(?P<hwb_hue>-?0*[0-9]{1,3}(\.[0-9]*)?)'
|
||||
# '(?P<hwb_agl>deg|grad|rad|turn|)' '\s*[,\\s]'
|
||||
# '\s*' '((?P<hwb_wht_per>0*[0-9]{1,3})%'
|
||||
# '|(?P<hwb_wht_flt>0*(\.[0-9]*)?))' '\s*[,\\s]'
|
||||
# '\s*' '((?P<hwb_blk_per>0*[0-9]{1,3})%'
|
||||
# '|(?P<hwb_blk_flt>0*(\.[0-9]*)?))' '\s*'
|
||||
# '\)'
|
||||
')\s*$', _re.I)
|
||||
|
||||
# ---
|
||||
# HWB to RGB.
|
||||
# Algorithm taken from here: https://drafts.csswg.org/css-color/#hwb-to-rgb
|
||||
# ---
|
||||
|
||||
def _hwb_to_rgb(h, w, b):
|
||||
""" Convert HWB to RGB color. """
|
||||
|
||||
r, g, b = _color.hls_to_rgb(h, 0.5, 1.0)
|
||||
f = lambda x: x * (1 - w - b) + w
|
||||
return f(r), f(g), f(b)
|
||||
|
||||
# ---
|
||||
# Main tag definition.
|
||||
# ---
|
||||
|
||||
class TextoutColorTag(TextoutTag):
|
||||
""" The main tag for setting the text color.
|
||||
Example uses:
|
||||
|
||||
[blue]This will be in blue[/blue]
|
||||
[color=blue]This as well[/color]
|
||||
[color=rgb(255, 255, 255)]BLACKNESS[/color]
|
||||
[color=hsl(0, 100%, 0.5)]This will be red.[/color]
|
||||
"""
|
||||
|
||||
def _prepare(self):
|
||||
if self.name != 'color':
|
||||
self.value = self.name
|
||||
|
||||
# Check if is a color name.
|
||||
value = self.value
|
||||
try: value = _cols[self.value]
|
||||
except: pass
|
||||
|
||||
# Initialize the alpha.
|
||||
self.a = None
|
||||
|
||||
# Get the match.
|
||||
g = _cr.match(value).groupdict()
|
||||
if g['thr_r']:
|
||||
self.r = int(g['thr_r'] * 2, 16)
|
||||
self.g = int(g['thr_g'] * 2, 16)
|
||||
self.b = int(g['thr_b'] * 2, 16)
|
||||
elif g['six_r']:
|
||||
self.r = int(g['six_r'], 16)
|
||||
self.g = int(g['six_g'], 16)
|
||||
self.b = int(g['six_b'], 16)
|
||||
elif g['rgb_r']:
|
||||
self.r = int(g['rgb_r'])
|
||||
self.g = int(g['rgb_g'])
|
||||
self.b = int(g['rgb_b'])
|
||||
|
||||
if self.r > 255 or self.g > 255 or self.b > 255:
|
||||
raise Exception
|
||||
elif g['hsl_hue'] or g['hls_hue']:
|
||||
if g['hsl_hue']:
|
||||
hue = float(g['hsl_hue'])
|
||||
agl = g['hsl_agl']
|
||||
|
||||
# Saturation.
|
||||
if g['hsl_sat_per']:
|
||||
sat = float(g['hsl_sat_per']) / 100.0
|
||||
else:
|
||||
sat = float(g['hsl_sat_flt'])
|
||||
if sat > 1.0:
|
||||
sat /= 100.0
|
||||
|
||||
# Light.
|
||||
if g['hsl_lgt_per']:
|
||||
lgt = float(g['hsl_lgt_per']) / 100.0
|
||||
else:
|
||||
lgt = float(g['hsl_lgt_flt'])
|
||||
if lgt > 1.0:
|
||||
lgt /= 100.0
|
||||
|
||||
# Alpha value.
|
||||
if g['hsl_aph_per']:
|
||||
alpha = float(g['hsl_aph_per']) / 100.0
|
||||
elif g['hsl_aph_flt']:
|
||||
alpha = float(g['hsl_aph_flt'])
|
||||
else:
|
||||
alpha = None
|
||||
else:
|
||||
hue = float(g['hls_hue'])
|
||||
agl = g['hls_agl']
|
||||
|
||||
# Saturation.
|
||||
if g['hls_sat_per']:
|
||||
sat = float(g['hls_sat_per']) / 100.0
|
||||
else:
|
||||
sat = float(g['hls_sat_flt'])
|
||||
|
||||
# Light.
|
||||
if g['hls_lgt_per']:
|
||||
lgt = float(g['hls_lgt_per']) / 100.0
|
||||
else:
|
||||
lgt = float(g['hls_lgt_flt'])
|
||||
|
||||
# Alpha value.
|
||||
if g['hls_aph_per']:
|
||||
alpha = float(g['hls_aph_per']) / 100.0
|
||||
elif g['hls_aph_flt']:
|
||||
alpha = float(g['hls_aph_flt'])
|
||||
else:
|
||||
alpha = None
|
||||
|
||||
# Prepare the angle.
|
||||
if agl == 'grad':
|
||||
hue = hue * 400.0
|
||||
elif agl == 'rad':
|
||||
hue = hue / (2 * math.pi)
|
||||
elif not agl or agl == 'deg':
|
||||
hue = hue / 360.0
|
||||
hue = hue % 1.0
|
||||
|
||||
if sat > 1 or lgt > 1:
|
||||
raise Exception
|
||||
|
||||
r, g, b = _color.hls_to_rgb(hue, lgt, sat)
|
||||
self.r, self.g, self.b = map(lambda x:int(round(x * 255)),
|
||||
(r, g, b))
|
||||
self.a = alpha
|
||||
elif g['hwb_hue']:
|
||||
hue = float(g['hwb_hue'])
|
||||
agl = g['hwb_agl']
|
||||
|
||||
# Saturation.
|
||||
if g['hwb_wht_per']:
|
||||
wht = float(g['hwb_wht_per']) / 100.0
|
||||
else:
|
||||
wht = float(g['hwb_wht_flt'])
|
||||
|
||||
# Light.
|
||||
if g['hwb_blk_per']:
|
||||
blk = float(g['hwb_blk_per']) / 100.0
|
||||
else:
|
||||
blk = float(g['hwb_blk_flt'])
|
||||
|
||||
# Prepare the angle.
|
||||
if agl == 'grad':
|
||||
hue = hue * 400.0
|
||||
elif agl == 'rad':
|
||||
hue = hue / (2 * math.pi)
|
||||
elif not agl or agl == 'deg':
|
||||
hue = hue / 360.0
|
||||
hue = hue % 1.0
|
||||
|
||||
if wht > 1 or blk > 1:
|
||||
raise Exception
|
||||
r, g, b = _hwb_to_rgb(hue, wht, blk)
|
||||
self.r, self.g, self.b = map(lambda x:int(round(x * 255)),
|
||||
(r, g, b))
|
||||
|
||||
def begin_html(self):
|
||||
if self.a != None:
|
||||
color = "rgba({}, {}, {}, {})".format(self.r, self.g, self.b,
|
||||
self.a)
|
||||
else:
|
||||
color = "#%02X%02X%02X" % (self.r, self.g, self.b)
|
||||
|
||||
return '<span style="color: {}">'.format(color)
|
||||
|
||||
def end_html(self):
|
||||
return self.content + '</span>'
|
||||
|
||||
# End of file.
|
|
@ -0,0 +1,42 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from .__base__ import TextoutTag
|
||||
|
||||
__all__ = ["TextoutFontTag"]
|
||||
|
||||
_fonts = {
|
||||
"arial": "Arial",
|
||||
"comic": "Comic MS",
|
||||
"tahoma": "Tahoma",
|
||||
"courier": "Courier",
|
||||
"haettenschweiler": "Haettenschweiler"
|
||||
}
|
||||
|
||||
class TextoutFontTag(TextoutTag):
|
||||
""" The main tag for setting the text font of the content.
|
||||
Example uses:
|
||||
|
||||
[font=arial]This will be in arial[/font]
|
||||
[arial]This as well[/arial]
|
||||
"""
|
||||
|
||||
def _prepare(self):
|
||||
if self.name == "font":
|
||||
name = self.value
|
||||
else:
|
||||
if self.value != None:
|
||||
raise Exception
|
||||
name = self.name
|
||||
|
||||
self.font = _fonts[name.lower()]
|
||||
|
||||
def begin_html(self):
|
||||
if not self.font:
|
||||
return ""
|
||||
return '<span style="font-family: {};">'.format(font)
|
||||
|
||||
def end_html(self):
|
||||
return self.content + '</span>'
|
||||
|
||||
# End of file.
|
|
@ -0,0 +1,83 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from urllib.parse import urlparse
|
||||
from html import escape as _htmlescape
|
||||
from .__base__ import TextoutTag
|
||||
|
||||
__all__ = ["TextoutImageTag", "TextoutAdminImageTag"]
|
||||
|
||||
class TextoutImageTag(TextoutTag):
|
||||
""" The main tag for displaying an image.
|
||||
Example uses:
|
||||
|
||||
[img]picture_url[/img]
|
||||
[img=center]picture_url[/img]
|
||||
[img=12x24]picture_url[/img]
|
||||
[img=center|12x24]picture_url[/img]
|
||||
[img=x24|right]picture_url[/img]
|
||||
"""
|
||||
|
||||
def _prepare(self):
|
||||
self.eval = False
|
||||
|
||||
self.width = None
|
||||
self.height = None
|
||||
self.align = None
|
||||
|
||||
val = self.value if self.value else ""
|
||||
for arg in val.split('|'):
|
||||
if not arg:
|
||||
pass
|
||||
elif arg[0] in '0123456789x':
|
||||
dim = arg.split('x')
|
||||
try: self.width = int(dim[0])
|
||||
except: pass
|
||||
try: self.height = int(dim[1])
|
||||
except: pass
|
||||
elif arg in ('center', 'right', 'float-left', 'float-right'):
|
||||
self.align = arg
|
||||
|
||||
def end_text(self):
|
||||
return '![ {} ]!'.format(self.content)
|
||||
|
||||
def end_html(self):
|
||||
url = self.content
|
||||
if not url:
|
||||
return ""
|
||||
|
||||
u = urlparse(self.content)
|
||||
if u.scheme and not u.scheme in ('http', 'https', 'ftp'):
|
||||
return ""
|
||||
url = _htmlescape(url)
|
||||
|
||||
style = []
|
||||
if self.width:
|
||||
style.append('width: {}px'.format(self.width))
|
||||
elif self.height:
|
||||
style.append('width: auto')
|
||||
if self.height:
|
||||
style.append('height: {}px'.format(self.height))
|
||||
elif self.width:
|
||||
style.append('height: auto')
|
||||
|
||||
return '<img src="{}"{}{}>'.format(self.content,
|
||||
' class="{}"'.format(self.align) if self.align else '',
|
||||
' style="{}"'.format('; '.join(style)) if style else '')
|
||||
|
||||
class TextoutAdminImageTag(TextoutImageTag):
|
||||
""" This tag is special for Planète Casio, as it takes images from
|
||||
the `ad`ministration's image folder.
|
||||
It just adds this folder's prefix.
|
||||
Example uses:
|
||||
|
||||
[adimg]some_picture.png[/adimg]
|
||||
[adimg=center|x24]some_picture_with_attributes.png[/adimg]
|
||||
"""
|
||||
|
||||
def _set(self):
|
||||
self.content = 'https://www.planet-casio.com/images/ad/' \
|
||||
+ self.content
|
||||
super(TextoutAdminImageTag, self)._set()
|
||||
|
||||
# End of file.
|
|
@ -0,0 +1,52 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import re as _re
|
||||
from .__base__ import TextoutTag
|
||||
|
||||
__all__ = ["TextoutLabelTag", "TextoutTargetTag"]
|
||||
|
||||
_v42compat = True
|
||||
_labelexpr = _re.compile('^[a-z0-9]{1,16}$', _re.I)
|
||||
|
||||
class TextoutLabelTag(TextoutTag):
|
||||
""" The label tag, defines an anchor at a point of the post.
|
||||
Example uses:
|
||||
|
||||
[label=installation]Installation de tel logiciel... (no ending)
|
||||
[label=compilation][/label] Compilation de tel logiciel...
|
||||
"""
|
||||
|
||||
def _prepare(self):
|
||||
if not _labelexpr.match(self.value):
|
||||
raise Exception
|
||||
|
||||
def begin_text(self):
|
||||
return '[{}]: '.format(self.value)
|
||||
|
||||
def begin_html(self):
|
||||
name = self.value if _v42compat else 'label-{}'.format(self.value)
|
||||
return '<a name="{}"></a>'.format(name)
|
||||
|
||||
class TextoutTargetTag(TextoutTag):
|
||||
""" The goto tag, links to an anchor defined in the post.
|
||||
Example uses:
|
||||
|
||||
[target=installation]Check out the installation manual[/target]!
|
||||
"""
|
||||
|
||||
def _prepare(self):
|
||||
if not _labelexpr.match(self.value):
|
||||
raise Exception
|
||||
|
||||
def end_text(self):
|
||||
return self.content + ' (voir "{}")'.format(self.value)
|
||||
|
||||
def begin_html(self):
|
||||
name = self.value if _v42compat else 'label-{}'.format(self.value)
|
||||
return '<a href="#{}">'.format(name)
|
||||
|
||||
def end_html(self):
|
||||
return self.content + '</a>'
|
||||
|
||||
# End of file.
|
|
@ -0,0 +1,66 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from .__base__ import TextoutTag
|
||||
|
||||
__all__ = ["TextoutLinkTag", "TextoutProfileTag"]
|
||||
|
||||
class TextoutLinkTag(TextoutTag):
|
||||
""" The main link tag.
|
||||
Example uses:
|
||||
|
||||
[url=https://example.org/hi]Go to example.org[/url]!
|
||||
[url=/Fr/index.php][/url]
|
||||
[url]https://random.org/randomize.php[/url]
|
||||
"""
|
||||
|
||||
def _validate(self):
|
||||
for prefix in ('http://', 'https://', 'ftp://', '/', '#'):
|
||||
if self.url.startswith(prefix):
|
||||
break
|
||||
else:
|
||||
raise Exception("No allowed prefix!")
|
||||
|
||||
def _prepare(self):
|
||||
self.url = None
|
||||
if self.value != None:
|
||||
self.url = self.value
|
||||
self._validate()
|
||||
|
||||
def _set(self):
|
||||
if not self.url:
|
||||
self.url = self.content
|
||||
self._validate()
|
||||
if not self.content:
|
||||
self.content = self.url
|
||||
|
||||
def end_text(self):
|
||||
return self.content + ' (voir "{}")'.format(self.value)
|
||||
|
||||
def begin_html(self):
|
||||
return '<a href="{}" target="_blank" rel="noopener">' \
|
||||
.format(self.value)
|
||||
|
||||
def end_html(self):
|
||||
return self.content + '</a>'
|
||||
|
||||
class TextoutProfileTag(TextoutLinkTag):
|
||||
""" A special link tag for Planète Casio's profiles.
|
||||
Adds the prefix to the content, and sets the value.
|
||||
Example uses:
|
||||
|
||||
[profil]Cakeisalie5[/profil]
|
||||
"""
|
||||
|
||||
def _prepare(self):
|
||||
# parent `_prepare()` is called in `_set()`.
|
||||
pass
|
||||
|
||||
def _set(self):
|
||||
# TODO: check if valid username?
|
||||
self.value = 'https://www.planet-casio.com/Fr/compte/voir_profil.php' \
|
||||
'?membre={}'.format(self.content)
|
||||
super(TextoutProfileTag, self)._prepare()
|
||||
super(TextoutProfileTag, self)._set()
|
||||
|
||||
# End of file.
|
|
@ -0,0 +1,34 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from .__base__ import TextoutTag
|
||||
|
||||
__all__ = ["TextoutProgressTag"]
|
||||
|
||||
class TextoutProgressTag(TextoutTag):
|
||||
""" Progress tag, used to display the progress on anything.
|
||||
Usage:
|
||||
|
||||
[progress=50]My great progress bar[/progress]
|
||||
[progress=100][/progress] """
|
||||
|
||||
def _prepare(self):
|
||||
self.val = int(self.value)
|
||||
if self.val < 0 or self.val > 100:
|
||||
raise Exception("progress value should be between 0 and 100 incl.")
|
||||
|
||||
def end_text(self, mode):
|
||||
val /= 5
|
||||
return '{}{}|{}|{}'.format(self.content, mode.newline,
|
||||
'X' * val + '-' * (20 - val), mode.newline)
|
||||
|
||||
def end_html(self, mode):
|
||||
return '<div>{}' \
|
||||
'<div style="background-color: white; border: 1px solid black; ' \
|
||||
'width: 50%; margin-top: 2px; text-align: left;">' \
|
||||
'<div style="background-color: #FF3E28; color: black; ' \
|
||||
'font-weight: bold; max-width: 100%; width: {}%;' \
|
||||
'height: 18px;"> {}%' \
|
||||
'</div></div></div>'.format(self.content, self.val, self.val)
|
||||
|
||||
# End of file.
|
|
@ -0,0 +1,40 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from .__base__ import TextoutTag
|
||||
|
||||
__all__ = ["TextoutQuoteTag"]
|
||||
|
||||
class TextoutQuoteTag(TextoutTag):
|
||||
""" The main tag for quoting someone.
|
||||
Example uses:
|
||||
|
||||
[quote]Hey, I said that![/quote]
|
||||
[quote=[color=red]Someone important[/color]]I said something important,
|
||||
and it's multiline and [b]formatted[/b]!
|
||||
[quote=Someone else]Heck, he's even quoting me in his quote![/quote]
|
||||
[/quote]
|
||||
"""
|
||||
|
||||
def begin_text(self):
|
||||
f = ''
|
||||
if self.value:
|
||||
f += '{} a écrit :\n'.format(self.value)
|
||||
return f + '---\n'
|
||||
|
||||
def end_text(self):
|
||||
f = ''
|
||||
f += self.content
|
||||
f += '---\n'
|
||||
return f
|
||||
|
||||
def begin_html(self):
|
||||
f = '<div class="citation">'
|
||||
if self.value:
|
||||
f += '<b>{} a écrit :</b> <br />'.format(self.value)
|
||||
return f
|
||||
|
||||
def end_html(self):
|
||||
return self.content + '</div>'
|
||||
|
||||
# End of file.
|
|
@ -0,0 +1,115 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from .__base__ import TextoutTag
|
||||
|
||||
__all__ = ["TextoutBoldTag", "TextoutItalicTag", "TextoutUnderlineTag",
|
||||
"TextoutStrikeTag", "TextoutMonoTag", "TextoutAlignTag", "TextoutSizeTag"]
|
||||
|
||||
class TextoutBoldTag(TextoutTag):
|
||||
""" Main tag for setting the weight to bold.
|
||||
Example uses:
|
||||
|
||||
[b]Bold text.[/b]
|
||||
"""
|
||||
|
||||
def begin_html(self):
|
||||
return "<b>"
|
||||
|
||||
def end_html(self):
|
||||
return self.content + "</b>"
|
||||
|
||||
class TextoutItalicTag(TextoutTag):
|
||||
""" Main tag for displaying text in italic.
|
||||
Example uses:
|
||||
|
||||
[i]Italic text.[/i]
|
||||
"""
|
||||
|
||||
def begin_html(self):
|
||||
return "<i>"
|
||||
|
||||
def end_html(self):
|
||||
return self.content "</i>"
|
||||
|
||||
class TextoutUnderlineTag(TextoutTag):
|
||||
""" Main tag for underlining text.
|
||||
Example uses:
|
||||
|
||||
[u]Underlined text.[/u]
|
||||
"""
|
||||
|
||||
def begin_html(self):
|
||||
return "<u>"
|
||||
|
||||
def html(self):
|
||||
return self.content + "</u>"
|
||||
|
||||
class TextoutStrikeTag(TextoutTag):
|
||||
""" Main tag for striking text.
|
||||
Example uses:
|
||||
|
||||
[strike]Cakeisalie5 is ugly.[/strike]
|
||||
"""
|
||||
|
||||
def begin_html(self):
|
||||
return "<strike>"
|
||||
|
||||
def end_html(self):
|
||||
return self.content + "</strike>"
|
||||
|
||||
class TextoutMonoTag(TextoutTag):
|
||||
""" Main tag for setting a monospace to text.
|
||||
Example uses:
|
||||
|
||||
[mono]Monospace text![/mono]
|
||||
"""
|
||||
|
||||
def begin_html(self):
|
||||
return '<span style="font-family: monospace;">"
|
||||
|
||||
def end_html(self):
|
||||
return self.content + '</span>'
|
||||
|
||||
class TextoutAlignTag(TextoutTag):
|
||||
""" Main tag for aligning text.
|
||||
Example uses:
|
||||
|
||||
[align=center]This text is centered horizontally.[/align]
|
||||
[justify]This text is justified.[/justify]
|
||||
"""
|
||||
|
||||
def _prepare(self):
|
||||
if self.name != 'align':
|
||||
self.value = self.name
|
||||
if not self.value in ('center', 'justify'):
|
||||
raise Exception
|
||||
|
||||
def begin_html(self):
|
||||
return '<div style="text-align: {};">'.format(self.value)
|
||||
|
||||
def end_html(self):
|
||||
return self.content + '</div>'
|
||||
|
||||
class TextoutSizeTag(TextoutTag):
|
||||
""" Main tag for setting the font size.
|
||||
Example uses:
|
||||
|
||||
[big]WOW, THIS IS BIG.[/big]
|
||||
[size=small]and this is tiny.[/size]
|
||||
"""
|
||||
|
||||
def _prepare(self):
|
||||
if self.name != 'size':
|
||||
self.value = self.name
|
||||
if not self.value in ('small', 'big'):
|
||||
raise Exception
|
||||
|
||||
def begin_html(self):
|
||||
return '<span style="font-size: {};">' \
|
||||
.format({'big': '15px', 'small': '9px'}[self.value])
|
||||
|
||||
def end_html(self):
|
||||
return self.content + '</span>'
|
||||
|
||||
# End of file.
|
|
@ -0,0 +1,46 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from .__base__ import TextoutTag
|
||||
|
||||
__all__ = ["TextoutSpoilerTag"]
|
||||
|
||||
class TextoutSpoilerTag(TextoutTag):
|
||||
""" Hide content at first glance, force people to click to read content.
|
||||
These elements can contain 'secret' elements such as solutions, source
|
||||
code, or various other things.
|
||||
Example uses:
|
||||
|
||||
[spoiler]This is hidden![/spoiler]
|
||||
[spoiler=Y a quelque chose de caché !|Ah, bah en fait non :)]:E
|
||||
And it's multiline, [big]and formatted[/big], as usual :D[/spoiler]
|
||||
"""
|
||||
|
||||
def prepare(self):
|
||||
self.closed = "Cliquez pour découvrir"
|
||||
self.open = "Cliquez pour recouvrir"
|
||||
|
||||
if self.value:
|
||||
titles = self.value.split('|')
|
||||
if titles[0]:
|
||||
self.closed = titles[0]
|
||||
if len(titles) >= 2 and titles[1]:
|
||||
self.open = titles[1]
|
||||
|
||||
def begin_text(self):
|
||||
return "[[ spoiler ]] >>\n"
|
||||
|
||||
def end_text(self):
|
||||
return self.content + "\n<<<\n"
|
||||
|
||||
def begin_html(self):
|
||||
return '<div class="spoiler">' \
|
||||
'<div class="title on" onclick="toggleSpoiler(this.parentNode, ' \
|
||||
'\'open\');">{}</div>' \
|
||||
'<div class="title off" onclick="toggleSpoiler(this.parentNode, ' \
|
||||
'\'close\');">{}</div>'.format(self.closed, self.open)
|
||||
|
||||
def end_html(self):
|
||||
retrn '<div class="off">' + self.content + '</div></div>'
|
||||
|
||||
# End of file.
|
|
@ -0,0 +1,70 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import re as _re
|
||||
import urllib.parse as _urlparse
|
||||
from .__base__ import TextoutTag
|
||||
from .link import TextoutLinkTag
|
||||
|
||||
__all__ = ["TextoutVideoTag"]
|
||||
|
||||
_hexcode = _re.compile('^[a-zA-Z0-9_]+$')
|
||||
_numcode = _re.compile('^/[0-9]+$')
|
||||
_dailypath = _re.compile('^/video/([a-z0-9]+)$')
|
||||
|
||||
class TextoutVideoTag(TextoutTag):
|
||||
""" The video tag, puts a preview of the video whose URL is given.
|
||||
Only a few 'big' services are supported for now.
|
||||
Example uses:
|
||||
|
||||
[video]video_url[/video]
|
||||
[video tiny]video_url[/video tiny]
|
||||
[video]https://www.youtube.com/watch?v=yhXpV8hRKxQ[/video]
|
||||
"""
|
||||
|
||||
def prepare(self):
|
||||
self.type = None
|
||||
self.w, self.h = (470, 300) if "tiny" in self.name else (560, 340)
|
||||
|
||||
url = _urlparse.urlparse(self.content)
|
||||
if url.host == "youtu.be":
|
||||
self.id = url.path[1:]
|
||||
if not _hexcode.match(self.id):
|
||||
raise Exception
|
||||
self.type = "youtube"
|
||||
elif url.host in ('youtube.com', 'www.youtube.com'):
|
||||
if url.path != '/watch':
|
||||
raise Exception
|
||||
self.id = _urlparse.parse_qs(url.query)['v']
|
||||
if not _hexcode.match(self.id):
|
||||
raise Exception
|
||||
self.type = "youtube"
|
||||
elif url.host in ('dailymotion.com', 'www.dailymotion.com'):
|
||||
self.code = _dailypath.match(url.path).groups()[0]
|
||||
self.type = "dailymotion"
|
||||
elif url.host in ('vimeo.com', 'www.vimeo.com'):
|
||||
self.code = url.path[1:]
|
||||
if not _numcode.match(self.code):
|
||||
raise Exception
|
||||
self.type = "vimeo"
|
||||
else:
|
||||
self.link = TextoutLinkTag('url', self.value, self.value)
|
||||
|
||||
def html(self):
|
||||
if self.type == "youtube":
|
||||
return '<iframe width="{}" height="{}" ' \
|
||||
'src="https://www.youtube.com/embed/{}" frameborder="0" ' \
|
||||
'allowfullscreen></iframe>'.format(self.w, self.h, self.code)
|
||||
elif self.type == "dailymotion":
|
||||
return '<iframe frameborder="0" width="{}" height="{}" ' \
|
||||
'src="https://www.dailymotion.com/embed/video/{}">' \
|
||||
'</iframe>'.format(self.w, self.h, self.code)
|
||||
elif self.type == "vimeo":
|
||||
return '<iframe src="https://player.vimeo.com/video/{}' \
|
||||
'?title=0&byline=0&portrait=0" width="{}" height="{}" ' \
|
||||
'frameborder="0" webkitAllowFullScreen allowFullScreen>' \
|
||||
'</iframe>'.format(self.id, self.w, self.h)
|
||||
else:
|
||||
return self.link.html(mode)
|
||||
|
||||
# End of file.
|
|
@ -0,0 +1,59 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from html import escape as _htmlescape
|
||||
|
||||
__all__ = ["_htmlescape", "TextoutTag"]
|
||||
|
||||
class TextoutTag:
|
||||
""" The textout tag base class.
|
||||
Is initialized with these values:
|
||||
[<name>=<value>]<content>[/<name>] """
|
||||
|
||||
def __init__(self, name, value):
|
||||
""" Initialize the textout tag with the documented members. """
|
||||
|
||||
self.name = name
|
||||
self.value = value
|
||||
self.content = None
|
||||
self.eval = False
|
||||
self._prepare()
|
||||
|
||||
def _prepare(self):
|
||||
""" If the tag needs to prepare things when the name and value are set,
|
||||
supplant this. By default, this method checks that there is
|
||||
no value set. """
|
||||
|
||||
if self.value != None
|
||||
raise Exception
|
||||
|
||||
def _set(self):
|
||||
""" If the tag needs to prepare things after the content is set,
|
||||
supplant this. By default, this method does nothing.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
def begin_text(self):
|
||||
""" The text output beginning method. """
|
||||
|
||||
return "[{}{}]".format(self.name,
|
||||
'=' + self.value if self.value != None else "")
|
||||
|
||||
def end_text(self):
|
||||
""" The text output beginning method. """
|
||||
|
||||
return self.content + "[/{}]".format(self.name)
|
||||
|
||||
def begin_html(self):
|
||||
""" The HTML beginning method. """
|
||||
|
||||
return _htmlescape("[{}{}]".format,
|
||||
'=' + self.value if self.value != None else "")
|
||||
|
||||
def end_html(self):
|
||||
""" The HTML ending method. """
|
||||
|
||||
return _htmlescape(self.content + "[/{}]".format(self.name))
|
||||
|
||||
# End of file.
|
|
@ -0,0 +1,72 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from .Simple import *
|
||||
from .Color import *
|
||||
from .Font import *
|
||||
from .Label import *
|
||||
from .Link import *
|
||||
|
||||
from .Image import *
|
||||
from .Code import *
|
||||
from .Progress import *
|
||||
from .Quote import *
|
||||
from .Spoiler import *
|
||||
from .Video import *
|
||||
|
||||
__all__ = ["tags"]
|
||||
|
||||
tags = {
|
||||
'b': TextoutBoldTag,
|
||||
'i': TextoutItalicTag,
|
||||
'u': TextoutUnderlineTag,
|
||||
'strike': TextoutStrikeTag,
|
||||
'monospace': TextoutMonoTag,
|
||||
'mono': TextoutMonoTag,
|
||||
'`': TextoutInlineCodeTag,
|
||||
'inlinecode': TextoutInlineCodeTag,
|
||||
# 'noeval': TextoutRepeatTag,
|
||||
|
||||
'arial': TextoutFontTag,
|
||||
'comic': TextoutFontTag,
|
||||
'tahoma': TextoutFontTag,
|
||||
'courier': TextoutFontTag,
|
||||
'haettenschweiler': TextoutFontTag,
|
||||
|
||||
'color': TextoutColorTag,
|
||||
'red': TextoutColorTag,
|
||||
'blue': TextoutColorTag,
|
||||
'green': TextoutColorTag,
|
||||
'yellow': TextoutColorTag,
|
||||
'maroon': TextoutColorTag,
|
||||
'purple': TextoutColorTag,
|
||||
'gray': TextoutColorTag,
|
||||
'grey': TextoutColorTag,
|
||||
'brown': TextoutColorTag,
|
||||
|
||||
'url': TextoutLinkTag,
|
||||
'profil': TextoutProfileTag,
|
||||
'profile': TextoutProfileTag,
|
||||
|
||||
'img': TextoutImageTag,
|
||||
'adimg': TextoutAdminImageTag,
|
||||
'progress': TextoutProgressTag,
|
||||
'spoiler': TextoutSpoilerTag,
|
||||
'code': TextoutCodeTag,
|
||||
'quote': TextoutQuoteTag,
|
||||
|
||||
'big': TextoutSizeTag,
|
||||
'size': TextoutSizeTag,
|
||||
'small': TextoutSizeTag,
|
||||
'align': TextoutAlignTag,
|
||||
'center': TextoutAlignTag,
|
||||
'justify': TextoutAlignTag,
|
||||
|
||||
'video': TextoutVideoTag,
|
||||
'video tiny': TextoutVideoTag,
|
||||
|
||||
'label': TextoutLabelTag,
|
||||
'target': TextoutTargetTag
|
||||
}
|
||||
|
||||
# End of file.
|
|
@ -0,0 +1,16 @@
|
|||
#!/usr/bin/env python3
|
||||
""" Functions for the user.
|
||||
Really simplifies the thing.
|
||||
"""
|
||||
|
||||
from .Decoder import *
|
||||
|
||||
__all__ = ["tohtml"]
|
||||
|
||||
def tohtml(message):
|
||||
""" Converts textout BBcode to HTML.
|
||||
Receives a string, returns a string. """
|
||||
|
||||
return TextoutDecoder(message).decode().html()
|
||||
|
||||
# End of file.
|
Loading…
Reference in New Issue