2
0
Fork 0

Initial commit.

This commit is contained in:
Thomas Touhey 2018-01-02 18:57:04 +01:00
commit bcad5e9f32
No known key found for this signature in database
GPG Key ID: 2ECEB0517AD947FB
22 changed files with 1408 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
__pycache__
/test.py
/*.egg-info

25
README.md Normal file
View File

@ -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

2
setup.cfg Normal file
View File

@ -0,0 +1,2 @@
[wheel]
universal = True

18
setup.py Executable file
View File

@ -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'
]
)

7
test/__init__.py Executable file
View File

@ -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.

103
test/test_html.py Executable file
View 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>': '&lt;script&gt;alert(1);&lt;/script&gt;',
'[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[]&gt;&quot;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]': '&lt;&gt;',
# Completion.
'[a][c][/a]': '[a][c][/a]',
'[a][a]': '[a][a]',
"[<>]><[/<>]": "[&lt;&gt;]&gt;&lt;[/&lt;&gt;]",
# Links.
'[url]<script>alert(1);</script>[/url]': \
'[url]&lt;script&gt;alert(1);&lt;/script&gt;[/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' \
'&gt; <img src="/images/smileys/grin.gif"></div><div class="off">' \
'Close this, quick!</div></div>',
# Videos.
'[video]"><script>alert(1)</script>[/video]': \
'[video]&quot;&gt;&lt;script&gt;alert(1)&lt;/script&gt;[/video]',
'[video]<script>alert(document.cookie)</script>[/video]': \
'[video]&lt;script&gt;alert(document.cookie)&lt;/script&gt;[/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=' \
'&lt;script&gt;alert(1)&lt;/script&gt;</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.

169
textoutpc/Decoder.py Executable file
View 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.

43
textoutpc/Elements.py Normal file
View 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.

56
textoutpc/Tags/Code.py Executable file
View 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.

287
textoutpc/Tags/Color.py Executable file
View 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.

42
textoutpc/Tags/Font.py Executable file
View 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.

83
textoutpc/Tags/Image.py Executable file
View 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.

52
textoutpc/Tags/Label.py Executable file
View 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.

66
textoutpc/Tags/Link.py Executable file
View 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.

34
textoutpc/Tags/Progress.py Executable file
View 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.

40
textoutpc/Tags/Quote.py Executable file
View 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.

115
textoutpc/Tags/Simple.py Executable file
View 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.

46
textoutpc/Tags/Spoiler.py Executable file
View 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.

70
textoutpc/Tags/Video.py Executable file
View 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.

59
textoutpc/Tags/__base__.py Executable file
View 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.

72
textoutpc/Tags/__init__.py Executable file
View 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.

16
textoutpc/__init__.py Executable file
View 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.