Doesn't work yet with block logic, still working on it, just saving it.
This commit is contained in:
parent
b4d161f2ab
commit
bc0bcfa65f
2
GUIDE.md
2
GUIDE.md
|
@ -63,7 +63,7 @@ You can change the color of the text using the `[color=xxx]` (or `[xxx]`
|
|||
directly for simple colors, where `xxx` represents the color) tag. This
|
||||
tag accepts several types of values:
|
||||
|
||||
- simple color names (inspired from CSS <TODO: link required>) as `red`,
|
||||
- simple color names (inspired from CSS <TODO: link required>) such as `red`,
|
||||
`blue`, `green`, `transparent`;
|
||||
- color hex codes using `#` followed by hex digits, e.g. `#01020F`,
|
||||
where the first group of hex digits represents the red component from
|
||||
|
|
23
README.md
23
README.md
|
@ -6,26 +6,34 @@ utility named `textout()`.
|
|||
I, Thomas “Cakeisalie5” Touhey, rewrote 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, and improve the language parsing to be more practicle and
|
||||
utility in Python, and improve the language parsing to be more practical and
|
||||
add features that are in the original BBcode markup language.
|
||||
|
||||
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:
|
||||
project and the online version of the transcoder.
|
||||
|
||||
## Usage
|
||||
To use this module, simply use the `to<language>()` functions once imported:
|
||||
|
||||
#!/usr/bin/env python3
|
||||
import textoutpc
|
||||
|
||||
text = "Hello, [i]beautiful [b]world[/i]!"
|
||||
print(textoutpc.tohtml(text))
|
||||
print("---")
|
||||
print(textoutpc.tolightscript(text))
|
||||
|
||||
Some day, this module should be able to convert to other formats such
|
||||
as [lightscript][ls], a markdown-like formatting language made by another
|
||||
_Planète Casio_ admin, who also made the native Python module.
|
||||
The supported output types are:
|
||||
|
||||
- `html`: HTML compatible output, requiring some additional style and script
|
||||
(TODO: document the required CSS classes and JS scripts);
|
||||
- `lightscript`: Markdown-like language
|
||||
([official topic on Planète Casio][lstp], [source repository][ls]).
|
||||
|
||||
## What is left to do
|
||||
|
||||
- Manage paragraph and inline tags differently;
|
||||
- Manage blocks superseeding each other;
|
||||
- Implement BBcode lists using `[*]`, `[**]`, …;
|
||||
- Manage lightscript (or even markdown?) as output languages;
|
||||
- Check where the errors are to display them to the user:
|
||||
|
@ -38,4 +46,5 @@ _Planète Casio_ admin, who also made the native Python module.
|
|||
- Look for security flaws (we don't want stored XSS here!).
|
||||
|
||||
[pc]: https://www.planet-casio.com/Fr/
|
||||
[ls]: https://bitbucket.org/Lephenixnoir/lightscript
|
||||
[ls]: https://git.planet-casio.com/lephe/lightscript
|
||||
[lstp]: https://planet-casio.com/Fr/forums/lecture_sujet.php?id=15022
|
||||
|
|
3
TAGS.md
3
TAGS.md
|
@ -23,7 +23,8 @@ There are a few public members you can define as a tag:
|
|||
It is defined as `True` by default for all tags;
|
||||
- `notempty`: ignore the tag when its content is empty. By default, this
|
||||
value is `False`;
|
||||
- `super`: is a super-block (for blocks), adds a paragraph tag implicitely.
|
||||
- `superblock`: is a super-block (for blocks), adds a paragraph
|
||||
tag implicitely.
|
||||
|
||||
So for example, if I want to make the inline tag `[hello]` as an example,
|
||||
with the alternate name `[hai]`, I'd start off by writing:
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
""" textout to HTML converter for the command line. """
|
||||
|
||||
import sys, argparse
|
||||
|
|
1
setup.py
1
setup.py
|
@ -1,5 +1,4 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
""" Setup script for the textoutpc Python package and script. """
|
||||
|
||||
from setuptools import setup
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
""" Unit tests for the `textoutpc` Python module. """
|
||||
|
||||
# ---
|
||||
# Simple file to indicate that this folder is a Python module.
|
||||
# Doesn't actually contain code.
|
||||
# ---
|
||||
# This file is only there to indicate that the folder is a module.
|
||||
# It doesn't actually contain code.
|
||||
|
||||
# End of file.
|
||||
|
|
|
@ -9,60 +9,98 @@ import textoutpc
|
|||
# Define the tests.
|
||||
|
||||
__test_cases = {
|
||||
# Really basic tests.
|
||||
# Basic text.
|
||||
'': '',
|
||||
'lol': '<p>lol</p>',
|
||||
'<script>alert(1);</script>': \
|
||||
'<p><script>alert(1);</script></p>',
|
||||
|
||||
# Funny tests.
|
||||
'<script>alert(1);</script>': '<p><script>alert(1);</script></p>',
|
||||
'[progress=lol]mdr[/progress]': '<p>[progress=lol]mdr[/progress]</p>',
|
||||
'[u][b][a][i][/b]': "<p><u><b>[a]<i></i></b></u></p>",
|
||||
'[u][b]a[/]mdr': '<p><u><b>a</b>mdr</u></p>',
|
||||
'[blue]text[/blue]': '<p><span style="color: #0000FF">text</span></p>',
|
||||
'[rot13]obawbhe[/rot13]': '<p>bonjour</p>',
|
||||
|
||||
# Links.
|
||||
'[url=http://hey.org/lol[]>"a]': '<p><a href="http://hey.org/lol[]>' \
|
||||
'"a" target="_blank" rel="noopener">' \
|
||||
'http://hey.org/lol[]>"a</a></p>',
|
||||
'[url]javascript:alert(1)[/url]': '<p>[url]javascript:alert(1)[/url]</p>',
|
||||
'(http://www.example.org/some-[damn-url]-(youknow))': \
|
||||
'<p>(<a href="http://www.example.org/some-[damn-url]-(youknow)" ' \
|
||||
'target="_blank" rel="noopener">' \
|
||||
'http://www.example.org/some-[damn-url]-(youknow)</a>)</p>',
|
||||
|
||||
# No evaluation.
|
||||
"[b]a[noeval]b[/b]c[/noeval]d": "<p><b>ab[/b]cd</b></p>",
|
||||
"a[noeval]b[noeval]c[/noeval]d[/noeval]e": "<p>ab[noeval]c[/noeval]de</p>",
|
||||
"`[code]`": '<p><span style="font-family: monospace;">[code]</span></p>',
|
||||
"[noeval]``[/noeval]": "<p>``</p>",
|
||||
"[inlinecode]`[/inlinecode]": \
|
||||
'<p><span style="font-family: monospace;">`</span></p>',
|
||||
'[noeval]<>[/noeval]': '<p><></p>',
|
||||
|
||||
# Completion.
|
||||
# Other tests. (?)
|
||||
'[a][c][/a]': '<p>[a][c][/a]</p>',
|
||||
'[a][a]': '<p>[a][a]</p>',
|
||||
"[<>]><[/<>]": "<p>[<>]><[/<>]</p>",
|
||||
|
||||
# Autolinking.
|
||||
'(http://www.example.org/some-[damn-url]-(youknow))': \
|
||||
'<p>(<a href="http://www.example.org/some-[damn-url]-(youknow)" ' \
|
||||
'target="_blank" rel="noopener">' \
|
||||
'http://www.example.org/some-[damn-url]-(youknow)</a>)</p>',
|
||||
'https://thomas.touhey.fr/, tu vois ?': \
|
||||
'<p><a href="https://thomas.touhey.fr/" target="_blank" ' \
|
||||
'rel="noopener">https://thomas.touhey.fr/</a>, tu vois ?</p>',
|
||||
|
||||
# Basic text styling.
|
||||
'[u][b][a][i][/b]': "<p><u><b>[a]<i></i></b></u></p>",
|
||||
'[u][b]a[/]mdr': '<p><u><b>a</b>mdr</u></p>',
|
||||
|
||||
# Blocks, alignment.
|
||||
'[left]lol[/]hi': '<p class="align-left">lol</p><p>hi</p>',
|
||||
'a[justify]b': '<p>a</p><p class="align-justify">b</p>',
|
||||
'a[i]k[center][b]b[justify]c[/center]d[/]wouhou': \
|
||||
'<p>a<i>k</i></p>' \
|
||||
'<p class="align-center"><i><b>b</b></i></p>' \
|
||||
'<p class="align-justify"><i><b>c</b></i></p>' \
|
||||
'<p><i>d</i>wouhou</p>',
|
||||
|
||||
# Titles.
|
||||
'lolk[title]smth': '<p>lolk</p>' '<h4>smth</h4>',
|
||||
'[subtitle]<>': '<h5><></h5>',
|
||||
|
||||
# Fonts.
|
||||
'[arial]test': '<p><span style="font-family: arial">test</span></p>',
|
||||
'[font=mono]stereo': \
|
||||
'<p><span style="font-family: monospace">stereo</span></p>',
|
||||
'[haettenschweiler]': '',
|
||||
'[font=hello]yea': '<p>[font=hello]yea</p>',
|
||||
|
||||
# Color.
|
||||
'yea[color=blue]dabadee': \
|
||||
'<p>yea<span style="color: #0000FF">dabadee</span></p>',
|
||||
'[color=#12345F]a': '<p><span style="color: #12345F">a</span></p>',
|
||||
'[color=#123]a': '<p><span style="color: #112233">a</span></p>',
|
||||
'[color=123]a': '<p><span style="color: #010203">a</span></p>',
|
||||
'[color=chucknorris]a': '<p><span style="color: #C00000">a</span></p>',
|
||||
'[color=rgb(1, 22,242)]a': '<p><span style="color: #0116F2">a</span></p>',
|
||||
'[color= rgb (1,22, 242 , 50.0% )]a': '<p><span style="color: #0116F2; ' \
|
||||
'color: rgba(1, 22, 242, 0.5)">a</span></p>',
|
||||
'[color=rgba(1,22,242,0.500)]a': '<p><span style="color: #0116F2; ' \
|
||||
'color: rgba(1, 22, 242, 0.5)">a</span></p>',
|
||||
'[color=hsl(0, 1,50.0%)]r': '<p><span style="color: #FF0000">r</span></p>',
|
||||
# TODO: hls, hwb
|
||||
|
||||
# Links.
|
||||
'[url]<script>alert(1);</script>[/url]': \
|
||||
'<p>[url]<script>alert(1);</script>[/url]</p>',
|
||||
'[url]': '<p>[url]</p>',
|
||||
'[url=https://thomas.touhey.fr/]mon profil est le meilleur[/url]':
|
||||
'<p><a href="https://thomas.touhey.fr/" target="_blank" ' \
|
||||
'rel="noopener">mon profil est le meilleur</a></p>',
|
||||
'[url=https://thomas.touhey.fr/]': \
|
||||
'<p><a href="https://thomas.touhey.fr/" target="_blank" ' \
|
||||
'rel="noopener">https://thomas.touhey.fr/</a></p>',
|
||||
'https://thomas.touhey.fr/, tu vois ?': \
|
||||
'<p><a href="https://thomas.touhey.fr/" target="_blank" ' \
|
||||
'rel="noopener">https://thomas.touhey.fr/</a>, tu vois ?</p>',
|
||||
'[url=http://hey.org/lol[]>"a]': '<p><a href="http://hey.org/lol[]>' \
|
||||
'"a" target="_blank" rel="noopener">' \
|
||||
'http://hey.org/lol[]>"a</a></p>',
|
||||
'[url]javascript:alert(1)[/url]': '<p>[url]javascript:alert(1)[/url]</p>',
|
||||
'[url]<script>alert(1);</script>[/url]': \
|
||||
'<p>[url]<script>alert(1);</script>[/url]</p>',
|
||||
|
||||
# Pictures.
|
||||
'[img]"incroyable<>"[/img]': \
|
||||
'<p>[img]"incroyable<>"[/img]</p>',
|
||||
'[profil]cake[/profil]': \
|
||||
'<p><a href="https://www.planet-casio.com/Fr/compte/voir_profil.php' \
|
||||
'?membre=cake">cake</a></p>',
|
||||
'[profile]ekac': \
|
||||
'<p><a href="https://www.planet-casio.com/Fr/compte/voir_profil.php' \
|
||||
'?membre=ekac">ekac</a></p>',
|
||||
|
||||
# Quotes.
|
||||
'[quote]': '',
|
||||
'[quote]a': \
|
||||
'<div class="citation"><p>a</p></div>',
|
||||
'[quote=Test 1 :)]lel[/quote]': \
|
||||
'<div class="citation"><p><b>Test 1 ' \
|
||||
'<img src="/images/smileys/smile.gif"> a écrit :</b><br />' \
|
||||
'lel</p></div>',
|
||||
|
||||
# Spoilers.
|
||||
'[spoiler]': '',
|
||||
'[spoiler=Hello|world> :D]Close this, quick![/spoiler]': \
|
||||
'<div class="spoiler"><div class="title on" ' \
|
||||
'onclick="toggleSpoiler(this.parentNode, ' "'open'" ');">Hello' \
|
||||
|
@ -71,6 +109,24 @@ __test_cases = {
|
|||
'> <img src="/images/smileys/grin.gif"></div><div class="off">' \
|
||||
'Close this, quick!</div></div>',
|
||||
|
||||
# Code.
|
||||
'[code]': '',
|
||||
"`[code]`": '<p><span style="font-family: monospace;">[code]</span></p>',
|
||||
|
||||
'[inlinecode]': '',
|
||||
"[inlinecode]`[/inlinecode]": \
|
||||
'<p><span style="font-family: monospace;">`</span></p>',
|
||||
|
||||
"[b]a[noeval]b[/b]c[/noeval]d": "<p><b>ab[/b]cd</b></p>",
|
||||
"a[noeval]b[noeval]c[/noeval]d[/noeval]e": "<p>ab[noeval]c[/noeval]de</p>",
|
||||
"[noeval]``[/noeval]": "<p>``</p>",
|
||||
'[noeval]<>[/noeval]': '<p><></p>',
|
||||
|
||||
# Pictures.
|
||||
'[img]': '<p>[img]</p>',
|
||||
'[img]"incroyable<>"[/img]': \
|
||||
'<p>[img]"incroyable<>"[/img]</p>',
|
||||
|
||||
# Videos.
|
||||
'[video]"><script>alert(1)</script>[/video]': \
|
||||
'<p>[video]"><script>alert(1)</script>' \
|
||||
|
@ -88,18 +144,11 @@ __test_cases = {
|
|||
'https://www.youtube.com/watch?v=<script>alert(1)' \
|
||||
'</script></a></p>',
|
||||
|
||||
# Quotes.
|
||||
'[quote=Test 1 :)]lel[/quote]': \
|
||||
'<div class="citation"><b>Test 1 ' \
|
||||
'<img src="/images/smileys/smile.gif"> a écrit :</b><br />lel</div>',
|
||||
|
||||
# Paragraphs.
|
||||
'a[i]k[center][b]b[justify]c[/center]d[/]wouhou': \
|
||||
'<p>a<i>k</i></p>' \
|
||||
'<p class="align-center"><i><b>b</b></i></p>' \
|
||||
'<p class="align-justify"><i><b>c</b></i></p>' \
|
||||
'<p><i>d</i>wouhou</p>',
|
||||
# Progress bars.
|
||||
'[progress=lol]mdr[/progress]': '<p>[progress=lol]mdr[/progress]</p>',
|
||||
|
||||
# Text rotation obfuscation.
|
||||
'[rot13]obawbhe[/rot13]': '<p>bonjour</p>',
|
||||
}
|
||||
|
||||
# Define the tests wrapper, and define the classes.
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
#!/usr/bin/env python3
|
||||
""" Unit tests for the Python version of textout, lightscript-related funcs.
|
||||
Uses the builtin `unittest` module.
|
||||
"""
|
||||
|
||||
import unittest
|
||||
import textoutpc
|
||||
|
||||
# Define the tests.
|
||||
|
||||
__test_cases = {
|
||||
# Basic text.
|
||||
'': '',
|
||||
}
|
||||
|
||||
# Define the tests wrapper, and define the classes.
|
||||
|
||||
_cnt = 0
|
||||
_len = len(str(len(__test_cases)))
|
||||
_templ = """\
|
||||
def test_lightscript{n:0>{l}}(self):
|
||||
self.assertEqual({r}, textoutpc.tolightscript({i}))
|
||||
"""
|
||||
|
||||
def _wrap_test(inp, res):
|
||||
global _cnt
|
||||
|
||||
_cnt += 1
|
||||
return _templ.format(n = _cnt, l = _len, i = repr(inp), r = repr(res))
|
||||
|
||||
exec("class TextoutLightscriptTest(unittest.TestCase):\n maxDiff = None\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.
|
|
@ -17,4 +17,12 @@ def tohtml(message):
|
|||
return _Translator(_io.StringIO(message), _io.StringIO(), 'html') \
|
||||
.process().getvalue()
|
||||
|
||||
def tolightscript(message):
|
||||
""" Converts textout BBcode to Lightscript.
|
||||
Receives a string, returns a string. """
|
||||
|
||||
return "" # TODO: real thing one day
|
||||
return _Translator(_io.StringIO(message), _io.StringIO(), 'lightscript') \
|
||||
.process().getvalue()
|
||||
|
||||
# End of file.
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
#!/usr/bin/env python3
|
||||
""" HTML/CSS-like color parsing, mainly for the `[color]` tag.
|
||||
Defines the `get_color()` function which returns an rgba value.
|
||||
|
||||
The functions in this module do not aim at being totally compliant with
|
||||
the W3C standards, although it is inspired from it.
|
||||
"""
|
||||
|
||||
from .read import get_color
|
||||
|
||||
__all__ = ["get_color"]
|
||||
|
||||
# End of file.
|
|
@ -0,0 +1,164 @@
|
|||
#!/usr/bin/env python3
|
||||
""" Named colors definitions. Color names are case-insensitive.
|
||||
Taken from: https://www.w3schools.com/cssref/css_colors.asp """
|
||||
|
||||
__all__ = ["colors"]
|
||||
|
||||
colors = {
|
||||
# Standard CSS3 named colors, ordered by hex code.
|
||||
|
||||
'black': '#000000',
|
||||
'navy': '#000080',
|
||||
'darkblue': '#00008B',
|
||||
'mediumblue': '#0000CD',
|
||||
'blue': '#0000FF',
|
||||
'darkgreen': '#006400',
|
||||
'green': '#008000',
|
||||
'teal': '#008080',
|
||||
'darkcyan': '#008B8B',
|
||||
'deepskyblue': '#00BFFF',
|
||||
'darkturquoise': '#00CED1',
|
||||
'mediumspringgreen': '#00FA9A',
|
||||
'lime': '#00FF00',
|
||||
'springgreen': '#00FF7F',
|
||||
'aqua': '#00FFFF',
|
||||
'cyan': '#00FFFF',
|
||||
'midnightblue': '#191970',
|
||||
'dodgerblue': '#1E90FF',
|
||||
'lightseagreen': '#20B2AA',
|
||||
'forestgreen': '#228B22',
|
||||
'seagreen': '#2E8B57',
|
||||
'darkslategray': '#2F4F4F',
|
||||
'darkslategrey': '#2F4F4F',
|
||||
'limegreen': '#32CD32',
|
||||
'mediumseagreen': '#3CB371',
|
||||
'turquoise': '#40E0D0',
|
||||
'royalblue': '#4169E1',
|
||||
'steelblue': '#4682B4',
|
||||
'darkslateblue': '#483D8B',
|
||||
'mediumturquoise': '#48D1CC',
|
||||
'indigo': '#4B0082',
|
||||
'darkolivegreen': '#556B2F',
|
||||
'cadetblue': '#5F9EA0',
|
||||
'cornflowerblue': '#6495ED',
|
||||
'rebeccapurple': '#663399',
|
||||
'mediumaquamarine': '#66CDAA',
|
||||
'dimgray': '#696969',
|
||||
'dimgrey': '#696969',
|
||||
'slateblue': '#6A5ACD',
|
||||
'olivedrab': '#6B8E23',
|
||||
'slategray': '#708090',
|
||||
'slategrey': '#708090',
|
||||
'lightslategray': '#778899',
|
||||
'lightslategrey': '#778899',
|
||||
'mediumslateblue': '#7B68EE',
|
||||
'lawngreen': '#7CFC00',
|
||||
'chartreuse': '#7FFF00',
|
||||
'aquamarine': '#7FFFD4',
|
||||
'maroon': '#800000',
|
||||
'purple': '#800080',
|
||||
'olive': '#808000',
|
||||
'gray': '#808080',
|
||||
'grey': '#808080',
|
||||
'skyblue': '#87CEEB',
|
||||
'lightskyblue': '#87CEFA',
|
||||
'blueviolet': '#8A2BE2',
|
||||
'darkred': '#8B0000',
|
||||
'darkmagenta': '#8B008B',
|
||||
'saddlebrown': '#8B4513',
|
||||
'darkseagreen': '#8FBC8F',
|
||||
'lightgreen': '#90EE90',
|
||||
'mediumpurple': '#9370DB',
|
||||
'darkviolet': '#9400D3',
|
||||
'palegreen': '#98FB98',
|
||||
'darkorchid': '#9932CC',
|
||||
'yellowgreen': '#9ACD32',
|
||||
'sienna': '#A0522D',
|
||||
'brown': '#A52A2A',
|
||||
'darkgray': '#A9A9A9',
|
||||
'darkgrey': '#A9A9A9',
|
||||
'lightblue': '#ADD8E6',
|
||||
'greenyellow': '#ADFF2F',
|
||||
'paleturquoise': '#AFEEEE',
|
||||
'lightsteelblue': '#B0C4DE',
|
||||
'powderblue': '#B0E0E6',
|
||||
'firebrick': '#B22222',
|
||||
'darkgoldenrod': '#B8860B',
|
||||
'mediumorchid': '#BA55D3',
|
||||
'rosybrown': '#BC8F8F',
|
||||
'darkkhaki': '#BDB76B',
|
||||
'silver': '#C0C0C0',
|
||||
'mediumvioletred': '#C71585',
|
||||
'indianred': '#CD5C5C',
|
||||
'peru': '#CD853F',
|
||||
'chocolate': '#D2691E',
|
||||
'tan': '#D2B48C',
|
||||
'lightgray': '#D3D3D3',
|
||||
'lightgrey': '#D3D3D3',
|
||||
'thistle': '#D8BFD8',
|
||||
'orchid': '#DA70D6',
|
||||
'goldenrod': '#DAA520',
|
||||
'palevioletred': '#DB7093',
|
||||
'crimson': '#DC143C',
|
||||
'gainsboro': '#DCDCDC',
|
||||
'plum': '#DDA0DD',
|
||||
'burlywood': '#DEB887',
|
||||
'lightcyan': '#E0FFFF',
|
||||
'lavender': '#E6E6FA',
|
||||
'darksalmon': '#E9967A',
|
||||
'violet': '#EE82EE',
|
||||
'palegoldenrod': '#EEE8AA',
|
||||
'lightcoral': '#F08080',
|
||||
'khaki': '#F0E68C',
|
||||
'aliceblue': '#F0F8FF',
|
||||
'honeydew': '#F0FFF0',
|
||||
'azure': '#F0FFFF',
|
||||
'sandybrown': '#F4A460',
|
||||
'wheat': '#F5DEB3',
|
||||
'beige': '#F5F5DC',
|
||||
'whitesmoke': '#F5F5F5',
|
||||
'mintcream': '#F5FFFA',
|
||||
'ghostwhite': '#F8F8FF',
|
||||
'salmon': '#FA8072',
|
||||
'antiquewhite': '#FAEBD7',
|
||||
'linen': '#FAF0E6',
|
||||
'lightgoldenrodyellow': '#FAFAD2',
|
||||
'oldlace': '#FDF5E6',
|
||||
'red': '#FF0000',
|
||||
'magenta': '#FF00FF',
|
||||
'fuchsia': '#FF00FF',
|
||||
'deeppink': '#FF1493',
|
||||
'orangered': '#FF4500',
|
||||
'tomato': '#FF6347',
|
||||
'hotpink': '#FF69B4',
|
||||
'coral': '#FF7F50',
|
||||
'darkorange': '#FF8C00',
|
||||
'lightsalmon': '#FFA07A',
|
||||
'orange': '#FFA500',
|
||||
'lightpink': '#FFB6C1',
|
||||
'pink': '#FFC0CB',
|
||||
'gold': '#FFD700',
|
||||
'peachpuff': '#FFDAB9',
|
||||
'navajowhite': '#FFDEAD',
|
||||
'moccasin': '#FFE4B5',
|
||||
'bisque': '#FFE4C4',
|
||||
'mistyrose': '#FFE4E1',
|
||||
'blanchedalmond': '#FFEBCD',
|
||||
'papayawhip': '#FFEFD5',
|
||||
'lavenderblush': '#FFF0F5',
|
||||
'seashell': '#FFF5EE',
|
||||
'cornsilk': '#FFF8DC',
|
||||
'lemonchiffon': '#FFFACD',
|
||||
'floralwhite': '#FFFAF0',
|
||||
'snow': '#FFFAFA',
|
||||
'yellow': '#FFFF00',
|
||||
'lightyellow': '#FFFFE0',
|
||||
'ivory': '#FFFFF0',
|
||||
'white': '#FFFFFF',
|
||||
|
||||
# Keyword/special named color.
|
||||
|
||||
'transparent': 'rgba(0,0,0,0)',
|
||||
}
|
||||
|
||||
# End of file.
|
|
@ -0,0 +1,250 @@
|
|||
#!/usr/bin/env python3
|
||||
""" HTML/CSS like color parsing, mainly for the `[color]` tag.
|
||||
Defines the `get_color()` function which returns an rgba value.
|
||||
"""
|
||||
|
||||
import re as _re
|
||||
import math as _math
|
||||
|
||||
from .named import colors as _named_colors
|
||||
from .sys import hls_to_rgb as _hls_to_rgb, hwb_to_rgb as _hwb_to_rgb
|
||||
|
||||
__all__ = ["get_color"]
|
||||
|
||||
_cr = _re.compile("""
|
||||
rgba?\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*(,
|
||||
\s* ((?P<rgb_a_per> ([0-9]+\.?|[0-9]*\.[0-9]+) )%
|
||||
|(?P<rgb_a_flt> (0*[01]\.?|0*\.[0-9]+) )) \s*)?
|
||||
\)|
|
||||
hsla?\s*\(
|
||||
\s* (?P<hsl_hue>-? ([0-9]+\.?|[0-9]*\.[0-9]+) )
|
||||
(?P<hsl_agl>deg|grad|rad|turn|) \s*[,\\s]
|
||||
\s* ((?P<hsl_sat_per> ([0-9]+\.?|[0-9]*\.[0-9]+) )%
|
||||
|(?P<hsl_sat_flt> (0*[01]\.?|0*\.[0-9]+) )) \s*[,\\s]
|
||||
\s* ((?P<hsl_lgt_per> ([0-9]+\.?|[0-9]*\.[0-9]+) )%
|
||||
|(?P<hsl_lgt_flt> (0*[01]\.?|0*\.[0-9]+) )) \s*([,\\s/]
|
||||
\s* ((?P<hsl_aph_per> ([0-9]+\.?|[0-9]*\.[0-9]+) )%
|
||||
|(?P<hsl_aph_flt> (0*[01]\.?|0*\.[0-9]+) )) \s*)?
|
||||
\)|
|
||||
hlsa?\s*\(
|
||||
\s* (?P<hls_hue>-? ([0-9]+\.?|[0-9]*\.[0-9]+) )
|
||||
(?P<hls_agl>deg|grad|rad|turn|) \s*[,\\s]
|
||||
\s* ((?P<hls_lgt_per> ([0-9]+\.?|[0-9]*\.[0-9]+) )%
|
||||
|(?P<hls_lgt_flt> (0*[01]\.?|0*\.[0-9]+) )) \s*[,\\s]
|
||||
\s* ((?P<hls_sat_per> ([0-9]+\.?|[0-9]*\.[0-9]+) )%
|
||||
|(?P<hls_sat_flt> (0*[01]\.?|0*\.[0-9]+) )) \s*([,\\s/]
|
||||
\s* ((?P<hls_aph_per> ([0-9]+\.?|[0-9]*\.[0-9]+) )%
|
||||
|(?P<hls_aph_flt> (0*[01]\.?|0*\.[0-9]+) )) \s*)?
|
||||
\)|
|
||||
hwb\s*\(
|
||||
\s* (?P<hwb_hue>-? ([0-9]+\.?|[0-9]*\.[0-9]+) )
|
||||
(?P<hwb_agl>deg|grad|rad|turn|) \s*[,\\s]
|
||||
\s* ((?P<hwb_wht_per> ([0-9]+\.?|[0-9]*\.[0-9]+) )%
|
||||
|(?P<hwb_wht_flt> (0*[01]\.?|0*\.[0-9]+) )) \s*[,\\s]
|
||||
\s* ((?P<hwb_blk_per> ([0-9]+\.?|[0-9]*\.[0-9]+) )%
|
||||
|(?P<hwb_blk_flt> (0*[01]\.?|0*\.[0-9]+) )) \s*([,\\s/]
|
||||
\s* ((?P<hwb_aph_per> ([0-9]+\.?|[0-9]*\.[0-9]+) )%
|
||||
|(?P<hwb_aph_flt> (0*[01]\.?|0*\.[0-9]+) )) \s*)?
|
||||
\)|
|
||||
\# (?P<hex_digits> [0-9a-f]+)
|
||||
|
|
||||
(?P<legacy_chars> [0-9a-z]+)
|
||||
""", _re.VERBOSE | _re.I | _re.M)
|
||||
|
||||
def get_color(value):
|
||||
""" Get a color from a string.
|
||||
Returns an (r, g, b, a) color.
|
||||
Raises an exception if the color could not be read. """
|
||||
|
||||
# Check if is a color name.
|
||||
try: value = _named_colors[value.lower()]
|
||||
except: pass
|
||||
|
||||
# Initialize the alpha.
|
||||
alpha = 1.0
|
||||
|
||||
# Get the match.
|
||||
match = _cr.match(value).groupdict()
|
||||
if match['hex_digits'] or match['legacy_chars']:
|
||||
# Imitate the Netscape behaviour. Find more about this here:
|
||||
# https://stackoverflow.com/a/8333464
|
||||
#
|
||||
# I've also extended the thing as I could to introduce more
|
||||
# modern syntaxes described on the dedicated MDN page:
|
||||
# https://developer.mozilla.org/en-US/docs/Web/CSS/color_value
|
||||
#
|
||||
# First of all, depending on our source, we will act differently:
|
||||
# - if we are using the `hex_digits` source, then we use the modern
|
||||
# behaviour and do the fancy things such as `#ABC -> #AABBCC`
|
||||
# management and possible alpha decoding;
|
||||
# - if we are using the `legacy_chars` source, then we sanitize our
|
||||
# input by replacing invalid characters by '0' characters (the
|
||||
# 0xFFFF limit is due to how UTF-16 was managed at the time).
|
||||
# We shall also truncate our input to 128 characters.
|
||||
#
|
||||
# After these sanitization options, we will keep the same method as
|
||||
# for legacy color decoding. It should work and be tolerant enough…
|
||||
|
||||
members = 3
|
||||
if match['hex_digits']:
|
||||
hx = match['hex_digits'].lower()
|
||||
|
||||
# RGB and RGBA (3 and 4 char.) notations.
|
||||
if len(hx) in (3, 4):
|
||||
hx = hx[0:1] * 2 + hx[1:2] * 2 + hx[2:3] * 2 + hx[3:4] * 2
|
||||
|
||||
# Check if there is transparency or not.
|
||||
if len(hx) % 3 != 0 and len(hx) % 4 == 0:
|
||||
members = 4
|
||||
|
||||
else: # our source is `legacy_chars`
|
||||
hx = match['legacy_chars'].lower()
|
||||
hx = ''.join(c if c in '0123456789abcdef' \
|
||||
else ('0', '00')[ord(c) > 0xFFFF] for c in hx[:128])[:128]
|
||||
|
||||
# First, calculate some values we're going to need.
|
||||
# `iv` is the size of the zone for a member.
|
||||
# `sz` is the size of the digits slice to take in that zone (max. 8).
|
||||
# `of` is the offset in the zone of the slice to take.
|
||||
|
||||
iv = _math.ceil(len(hx) / members)
|
||||
of = iv - 8 if iv > 8 else 0
|
||||
sz = iv - of
|
||||
|
||||
# Then isolate the slices using the values calculated above.
|
||||
# `gr` will be an array of 3 or 4 digit strings (depending on the
|
||||
# number of members).
|
||||
|
||||
gr = list(map(lambda i: hx[i * iv + of:i * iv + iv] \
|
||||
.ljust(sz, '0'), range(members)))
|
||||
|
||||
# Check how many digits we can skip at the beginning of each slice.
|
||||
|
||||
pre = min(map(lambda x: len(x) - len(x.lstrip('0')), gr))
|
||||
pre = min(pre, sz - 2)
|
||||
|
||||
# Then extract the values.
|
||||
|
||||
it = map(lambda x: int('0' + x[pre:pre + 2], 16), gr)
|
||||
if members == 3:
|
||||
r, g, b = it
|
||||
else:
|
||||
r, g, b, alpha = it
|
||||
alpha /= 255.0
|
||||
elif match['rgb_r']:
|
||||
# Extract the values.
|
||||
|
||||
r = int(match['rgb_r'])
|
||||
g = int(match['rgb_g'])
|
||||
b = int(match['rgb_b'])
|
||||
|
||||
if match['rgb_a_per']:
|
||||
alpha = float(match['rgb_a_per']) / 100.0
|
||||
elif match['rgb_a_flt']:
|
||||
alpha = float(match['rgb_a_flt'])
|
||||
elif match['hsl_hue'] or match['hls_hue']:
|
||||
# Extract the values.
|
||||
|
||||
if match['hsl_hue']:
|
||||
hue = float(match['hsl_hue'])
|
||||
agl = match['hsl_agl']
|
||||
|
||||
# Saturation.
|
||||
if match['hsl_sat_per']:
|
||||
sat = float(match['hsl_sat_per']) / 100.0
|
||||
else:
|
||||
sat = float(match['hsl_sat_flt'])
|
||||
if sat > 1.0:
|
||||
sat /= 100.0
|
||||
|
||||
# Light.
|
||||
if match['hsl_lgt_per']:
|
||||
lgt = float(match['hsl_lgt_per']) / 100.0
|
||||
else:
|
||||
lgt = float(match['hsl_lgt_flt'])
|
||||
if lgt > 1.0:
|
||||
lgt /= 100.0
|
||||
|
||||
# Alpha value.
|
||||
if match['hsl_aph_per']:
|
||||
alpha = float(match['hsl_aph_per']) / 100.0
|
||||
elif match['hsl_aph_flt']:
|
||||
alpha = float(match['hsl_aph_flt'])
|
||||
else:
|
||||
hue = float(match['hls_hue'])
|
||||
agl = match['hls_agl']
|
||||
|
||||
# Saturation.
|
||||
if match['hls_sat_per']:
|
||||
sat = float(match['hls_sat_per']) / 100.0
|
||||
else:
|
||||
sat = float(match['hls_sat_flt'])
|
||||
|
||||
# Light.
|
||||
if match['hls_lgt_per']:
|
||||
lgt = float(match['hls_lgt_per']) / 100.0
|
||||
else:
|
||||
lgt = float(match['hls_lgt_flt'])
|
||||
|
||||
# Alpha value.
|
||||
if match['hls_aph_per']:
|
||||
alpha = float(match['hls_aph_per']) / 100.0
|
||||
elif match['hls_aph_flt']:
|
||||
alpha = float(match['hls_aph_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 sat > 1 or lgt > 1:
|
||||
raise Exception
|
||||
|
||||
r, g, b = _hls_to_rgb(hue, lgt, sat)
|
||||
r, g, b = map(lambda x:int(round(x * 255)), (r, g, b))
|
||||
elif match['hwb_hue']:
|
||||
hue = float(match['hwb_hue'])
|
||||
agl = match['hwb_agl']
|
||||
|
||||
# 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
|
||||
|
||||
# Saturation.
|
||||
if match['hwb_wht_per']:
|
||||
wht = float(match['hwb_wht_per']) / 100.0
|
||||
else:
|
||||
wht = float(match['hwb_wht_flt'])
|
||||
|
||||
# Light.
|
||||
if match['hwb_blk_per']:
|
||||
blk = float(match['hwb_blk_per']) / 100.0
|
||||
else:
|
||||
blk = float(match['hwb_blk_flt'])
|
||||
|
||||
if wht > 1 or blk > 1:
|
||||
raise Exception
|
||||
r, g, b = _hwb_to_rgb(hue, wht, blk)
|
||||
r, g, b = map(lambda x: int(round(x * 255)), (r, g, b))
|
||||
|
||||
if r < 0 or r > 255 or g < 0 or g > 255 or b < 0 or b > 255:
|
||||
raise Exception
|
||||
if alpha < 0.0 or alpha > 1.0:
|
||||
raise Exception
|
||||
alpha = round(alpha, 4)
|
||||
|
||||
return (r, g, b, alpha)
|
||||
|
||||
# End of file.
|
|
@ -0,0 +1,18 @@
|
|||
#!/usr/bin/env python3
|
||||
""" Conversions between color systems. """
|
||||
|
||||
from colorsys import hls_to_rgb
|
||||
|
||||
__all__ = ["hls_to_rgb", "hwb_to_rgb"]
|
||||
|
||||
def hwb_to_rgb(hue, w, b):
|
||||
""" Convert HWB to RGB color.
|
||||
https://drafts.csswg.org/css-color/#hwb-to-rgb """
|
||||
|
||||
r, g, b = hls_to_rgb(hue, 0.5, 1.0)
|
||||
f = lambda x: x * (1 - w - b) + w
|
||||
r, g, b = f(r), f(g), f(b)
|
||||
|
||||
return r, g, b
|
||||
|
||||
# End of file.
|
|
@ -1,5 +1,4 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
""" Smiley conversion.
|
||||
Just convert them™.
|
||||
"""
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
""" Layer on top of the character stream.
|
||||
|
||||
See the `TextoutStream` class description for more information.
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
""" Tag helpers.
|
||||
As we ought to be able to make separate tag modules, this tag is
|
||||
""" Tag helpers for the translate utilities.
|
||||
As we ought to be able to make separate tag modules, this module
|
||||
does not hardcode the imports and makes it possible to import any
|
||||
custom module to isolate some tags from the others and make the
|
||||
`textoutpc` a generic module for BBcode.
|
||||
"""
|
||||
|
||||
from inspect import ismodule as _ismod, isclass as _isclass
|
||||
from .base import TextoutTag as _TextoutTag, TextoutBlockTag, TextoutInlineTag
|
||||
from .paragraph import TextoutParagraphTag
|
||||
from .base import TextoutTag as _TextoutTag, TextoutBlockTag, \
|
||||
TextoutInlineTag, TextoutParagraphTag
|
||||
|
||||
__all__ = ["TextoutParagraphTag", "TextoutBlockTag", "TextoutInlineTag",
|
||||
"get_tag"]
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
""" Base class for textout tags. For your class to be used as a textout tag,
|
||||
you have to make it inherit one of these (usually `TextoutBlockTag`
|
||||
or `TextoutInlineTag`). """
|
||||
|
||||
from functools import partial as _p
|
||||
from inspect import getargspec as _getargspec
|
||||
|
||||
__all__ = ["TextoutTag", "TextoutBlockTag", "TextoutInlineTag"]
|
||||
__all__ = ["TextoutTag", "TextoutBlockTag", "TextoutInlineTag",
|
||||
"TextoutParagraphTag"]
|
||||
|
||||
# ---
|
||||
# Main base tag class.
|
||||
|
@ -26,13 +30,25 @@ class TextoutTag:
|
|||
# Store internal data.
|
||||
|
||||
self.__output_type = ot
|
||||
self.output_type = ot
|
||||
|
||||
# Call both prepare functions.
|
||||
|
||||
if hasattr(self, 'prepare_' + ot):
|
||||
self.prepare = getattr(self, 'prepare_' + ot)
|
||||
if hasattr(self, 'prepare'):
|
||||
self.prepare(name, value)
|
||||
try:
|
||||
assert len(_getargspec(self.prepare).args) == 4
|
||||
args = (name, value, ot)
|
||||
except:
|
||||
args = (name, value)
|
||||
self.prepare(*args)
|
||||
if hasattr(self, 'prepare_' + ot):
|
||||
prep = getattr(self, 'prepare_' + ot)
|
||||
try:
|
||||
assert len(_getargspec(prep).args) == 4
|
||||
args = (name, value, ot)
|
||||
except:
|
||||
args = (name, value)
|
||||
prep(*args)
|
||||
|
||||
# Prepare the preprocessing elements.
|
||||
if hasattr(self, 'preprocess'):
|
||||
|
@ -93,4 +109,17 @@ class TextoutBlockTag(TextoutTag):
|
|||
class TextoutInlineTag(TextoutTag):
|
||||
pass
|
||||
|
||||
# ---
|
||||
# Default tag: paragraph.
|
||||
# ---
|
||||
|
||||
class TextoutParagraphTag(TextoutBlockTag):
|
||||
""" Main tag for basic paragraphs. """
|
||||
|
||||
def begin_html(self):
|
||||
return '<p>'
|
||||
|
||||
def end_html(self):
|
||||
return '</p>'
|
||||
|
||||
# End of file.
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from ..base import *
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from ..base import *
|
||||
|
||||
|
@ -18,18 +17,18 @@ class TextoutCodeTag(TextoutBlockTag):
|
|||
generic = False
|
||||
raw = True
|
||||
|
||||
def begin_text(self):
|
||||
return "```\n"
|
||||
|
||||
def end_text(self):
|
||||
return "\n```\n"
|
||||
|
||||
def begin_html(self):
|
||||
return '<div class="code">'
|
||||
|
||||
def end_html(self):
|
||||
return '</div>'
|
||||
|
||||
def begin_lightscript(self):
|
||||
return '```\n'
|
||||
|
||||
def end_lightscript(self):
|
||||
return '```\n'
|
||||
|
||||
class TextoutInlineCodeTag(TextoutInlineTag):
|
||||
""" Inline code tag, doesn't display a box, simply doesn't evaluate
|
||||
the content and uses monospace font.
|
||||
|
@ -44,18 +43,18 @@ class TextoutInlineCodeTag(TextoutInlineTag):
|
|||
generic = False
|
||||
raw = True
|
||||
|
||||
def begin_text(self):
|
||||
return "`"
|
||||
|
||||
def end_text(self):
|
||||
return "`"
|
||||
|
||||
def begin_html(self):
|
||||
return '<span style="font-family: monospace;">'
|
||||
|
||||
def end_html(self):
|
||||
return '</span>'
|
||||
|
||||
def begin_lightscript(self):
|
||||
return '`'
|
||||
|
||||
def end_lightscript(self):
|
||||
return '`'
|
||||
|
||||
class TextoutNoEvalTag(TextoutInlineTag):
|
||||
""" Inline code tag, simply doesn't evaluate the content.
|
||||
Example uses:
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from ..base import *
|
||||
from urllib.parse import urlparse
|
||||
|
@ -56,9 +55,6 @@ class TextoutImageTag(TextoutBlockTag):
|
|||
|
||||
self._url = content
|
||||
|
||||
def content_text(self):
|
||||
return '![ {} ]!'.format(self._url)
|
||||
|
||||
def content_html(self):
|
||||
style = []
|
||||
cls = []
|
||||
|
@ -80,6 +76,10 @@ class TextoutImageTag(TextoutBlockTag):
|
|||
' class="{}"'.format(' '.join(cls)) if cls else '',
|
||||
' style="{}"'.format('; '.join(style)) if style else '')
|
||||
|
||||
def content_lightscript(self):
|
||||
url = self._url.replace('[', '%5B').replace(']', '%5D')
|
||||
return '[[image:{}]]'.format(url)
|
||||
|
||||
class TextoutAdminImageTag(TextoutImageTag):
|
||||
""" This tag is special for Planète Casio, as it takes images from
|
||||
the `ad`ministration's image folder.
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from ..base import *
|
||||
import re as _re
|
||||
|
@ -24,9 +23,6 @@ class TextoutLabelTag(TextoutInlineTag):
|
|||
raise Exception
|
||||
self._label = value
|
||||
|
||||
def begin_text(self):
|
||||
return '[{}]: '.format(self._label)
|
||||
|
||||
def begin_html(self):
|
||||
#name = 'label-{}'.format(self._label)
|
||||
#if _v42compat:
|
||||
|
@ -48,9 +44,6 @@ class TextoutTargetTag(TextoutInlineTag):
|
|||
raise Exception
|
||||
self._label = value
|
||||
|
||||
def end_text(self):
|
||||
return ' (voir "{}")'.format(self._label)
|
||||
|
||||
def begin_html(self):
|
||||
#name = 'label-' + self._label
|
||||
name = self._label if _v42compat else 'label-' + self._label
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from ..base import *
|
||||
from html import escape as _htmlescape
|
||||
|
@ -46,9 +45,6 @@ class TextoutLinkTag(TextoutInlineTag):
|
|||
self._url = content
|
||||
self._validate()
|
||||
|
||||
def end_text(self):
|
||||
return ' (voir "{}")'.format(self._url)
|
||||
|
||||
def begin_html(self):
|
||||
return '<a href="{}" target="_blank" rel="noopener">' \
|
||||
.format(_htmlescape(self._url))
|
||||
|
@ -56,6 +52,13 @@ class TextoutLinkTag(TextoutInlineTag):
|
|||
def end_html(self):
|
||||
return '</a>'
|
||||
|
||||
def begin_lightscript(self):
|
||||
return '['
|
||||
|
||||
def end_lightscript(self):
|
||||
url = self._url.replace('(', '%28').replace(')', '%29')
|
||||
return ']({})'.format(url)
|
||||
|
||||
class TextoutProfileTag(TextoutLinkTag):
|
||||
""" A special link tag for Planète Casio's profiles.
|
||||
Adds the prefix to the content, and sets the value.
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from ..base import *
|
||||
|
||||
|
@ -20,10 +19,6 @@ class TextoutProgressTag(TextoutBlockTag):
|
|||
if self._val < 0 or self._val > 100:
|
||||
raise Exception("progress value should be between 0 and 100 incl.")
|
||||
|
||||
def end_text(self):
|
||||
val = self._val / 5
|
||||
return '\n|{}|\n'.format('X' * val + '-' * (20 - val))
|
||||
|
||||
def begin_html(self):
|
||||
return '<div>'
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from ..base import *
|
||||
from html import escape as _htmlescape
|
||||
|
@ -19,21 +18,11 @@ class TextoutQuoteTag(TextoutBlockTag):
|
|||
"""
|
||||
|
||||
aliases = ('[quote]',)
|
||||
superblock = True
|
||||
|
||||
def prepare(self, name, value):
|
||||
self._value = value
|
||||
|
||||
def preprocess_text(self, content):
|
||||
self._content = content
|
||||
|
||||
def begin_text(self):
|
||||
if not self._value:
|
||||
return ''
|
||||
return '{} a écrit :\n'.format(self._value)
|
||||
|
||||
def content_text(self):
|
||||
return "> " + '\n> '.join(self._content.split('\n'))
|
||||
|
||||
def begin_html(self):
|
||||
f = '<div class="citation">'
|
||||
if self._value:
|
||||
|
@ -44,4 +33,13 @@ class TextoutQuoteTag(TextoutBlockTag):
|
|||
def end_html(self):
|
||||
return '</div>'
|
||||
|
||||
def begin_lightscript(self):
|
||||
text = '<<<'
|
||||
if self._value:
|
||||
text += ' ' + self._value
|
||||
return text + '\n'
|
||||
|
||||
def end_lightscript(self):
|
||||
return '<<<\n'
|
||||
|
||||
# End of file.
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from ..base import *
|
||||
import string as _string
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from ..base import *
|
||||
from html import escape as _htmlescape
|
||||
|
@ -14,6 +13,7 @@ class TextoutShowTag(TextoutBlockTag):
|
|||
"""
|
||||
|
||||
aliases = ('[show]',)
|
||||
superblock = True
|
||||
generic = False
|
||||
raw = False
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from ..base import *
|
||||
from html import escape as _htmlescape
|
||||
|
@ -19,6 +18,7 @@ class TextoutSpoilerTag(TextoutBlockTag):
|
|||
"""
|
||||
|
||||
aliases = ('[spoiler]',)
|
||||
superblock = True
|
||||
|
||||
def prepare(self, name, value):
|
||||
self._closed = "Cliquez pour découvrir"
|
||||
|
@ -31,15 +31,6 @@ class TextoutSpoilerTag(TextoutBlockTag):
|
|||
if len(titles) >= 2 and titles[1]:
|
||||
self._open = titles[1]
|
||||
|
||||
def begin_text(self):
|
||||
return self._closed + "\n"
|
||||
|
||||
def preprocess_text(self, content):
|
||||
self._content = content
|
||||
|
||||
def content_text(self):
|
||||
return "| " + "\n| ".join(self._content.split('\n'))
|
||||
|
||||
def begin_html(self):
|
||||
return '<div class="spoiler">' \
|
||||
'<div class="title on" onclick="toggleSpoiler(this.parentNode, ' \
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from ..base import *
|
||||
from .__color__ import *
|
||||
from ...color import get_color
|
||||
|
||||
__all__ = ["TextoutTextTag"]
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from ..base import *
|
||||
|
||||
|
@ -19,13 +18,13 @@ class TextoutTitleTag(TextoutBlockTag):
|
|||
def prepare(self, name, value):
|
||||
self._level = name[1:-1]
|
||||
|
||||
def begin_text(self):
|
||||
return ('## ', '# ')[self._level == "title"]
|
||||
|
||||
def begin_html(self):
|
||||
return ('<h5>', '<h4>')[self._level == "title"]
|
||||
|
||||
def end_html(self):
|
||||
return ('</h5>', '</h4>')[self._level == "title"]
|
||||
|
||||
def begin_lightscript(self):
|
||||
return '#' * ((self._level == "subtitle") + 1) + ' '
|
||||
|
||||
# End of file.
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from ..base import *
|
||||
import re as _re
|
||||
|
@ -63,6 +62,8 @@ class TextoutVideoTag(TextoutBlockTag):
|
|||
raise Exception
|
||||
|
||||
def preprocess(self, content):
|
||||
self._url = content
|
||||
|
||||
try:
|
||||
self._getvideo(content)
|
||||
except:
|
||||
|
@ -70,7 +71,6 @@ class TextoutVideoTag(TextoutBlockTag):
|
|||
if not url.scheme in ('http', 'https'):
|
||||
raise Exception("No allowed prefix!")
|
||||
self._type = None
|
||||
self._url = content
|
||||
|
||||
def content_html(self):
|
||||
""" Produce the embed code for the given type. """
|
||||
|
@ -99,4 +99,8 @@ class TextoutVideoTag(TextoutBlockTag):
|
|||
|
||||
return code + '</div>'
|
||||
|
||||
def content_lightscript(self):
|
||||
url = self._url.replace('[', '%5B').replace(']', '%5D')
|
||||
return '[[image:{}]]'.format(url)
|
||||
|
||||
# End of file.
|
||||
|
|
|
@ -1,278 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
""" HTML/CSS like color parsing, mainly for the `[color]` tag.
|
||||
Defines the `get_color()` function which returns an rgba value.
|
||||
"""
|
||||
|
||||
import re as _re
|
||||
import colorsys as _color
|
||||
import math as _math
|
||||
|
||||
__all__ = ["get_color"]
|
||||
|
||||
# ---
|
||||
# Color names and correspondances.
|
||||
# ---
|
||||
|
||||
_colors = {
|
||||
# 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',
|
||||
|
||||
# Planète Casio special colors.
|
||||
'transparent': 'rgba(0, 0, 0, 0)',
|
||||
}
|
||||
|
||||
_cr = _re.compile(''
|
||||
'rgba?\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*(,'
|
||||
'\s*' '((?P<rgb_a_per>0*[0-9]{0,3}(\.[0-9]*)?)%'
|
||||
'|(?P<rgb_a_flt>0*(\.[0-9]*)?))' '\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}(\.[0-9]*)?)%'
|
||||
'|(?P<hsl_sat_flt>0*(\.[0-9]*)?))' '\s*[,\\s]'
|
||||
'\s*' '((?P<hsl_lgt_per>0*[0-9]{1,3}(\.[0-9]*)?)%'
|
||||
'|(?P<hsl_lgt_flt>0*(\.[0-9]*)?))' '\s*([,\\s]'
|
||||
'\s*' '((?P<hsl_aph_per>0*[0-9]{0,3}(\.[0-9]*)?)%'
|
||||
'|(?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}(\.[0-9]*)?)%'
|
||||
'|(?P<hls_lgt_flt>0*(\.[0-9]*)?))' '\s*[,\\s]'
|
||||
'\s*' '((?P<hls_sat_per>0*[0-9]{1,3}(\.[0-9]*)?)%'
|
||||
'|(?P<hls_sat_flt>0*(\.[0-9]*)?))' '\s*([,\\s]'
|
||||
'\s*' '((?P<hls_aph_per>0*[0-9]{0,3}(\.[0-9]*)?)%'
|
||||
'|(?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*'
|
||||
'\)'
|
||||
'|(?P<hex_hash>\#?)'
|
||||
'(?P<hex_digits>[0-9a-z]*)'
|
||||
'', _re.I)
|
||||
|
||||
# ---
|
||||
# Utilitaires.
|
||||
# ---
|
||||
|
||||
def _hwb_to_rgb(h, w, b):
|
||||
""" Convert HWB to RGB color.
|
||||
https://drafts.csswg.org/css-color/#hwb-to-rgb """
|
||||
|
||||
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 function.
|
||||
# ---
|
||||
|
||||
def get_color(value):
|
||||
""" Get a color from a string.
|
||||
Returns an (r, g, b, a) color.
|
||||
Raises an exception if there's a problem. """
|
||||
|
||||
# Check if is a color name.
|
||||
try: value = _colors[value.lower()]
|
||||
except: pass
|
||||
|
||||
# Initialize the alpha.
|
||||
alpha = 1.0
|
||||
|
||||
# Get the match.
|
||||
match = _cr.match(value).groupdict()
|
||||
if match['hex_digits']:
|
||||
# Imitate the Netscape behaviour. Find more about this here:
|
||||
# https://stackoverflow.com/a/8333464
|
||||
|
||||
hx = match['hex_digits'].lower()
|
||||
hx = ''.join(c if c in '0123456789abcdef' \
|
||||
else ('0', '00')[ord(c) > 0xFFFF] for c in hx)[:128]
|
||||
if match['hex_hash'] and len(hx) == 3:
|
||||
hx = hx[0] * 2 + hx[1] * 2 + hx[2] * 2
|
||||
|
||||
iv = _math.ceil(len(hx) / 3)
|
||||
of = iv - 8 if iv > 8 else 0
|
||||
sz = iv - of
|
||||
gr = list(map(lambda i: hx[i * iv + of:i * iv + iv] \
|
||||
.ljust(sz, '0'), range(3)))
|
||||
pre = min(map(lambda x: len(x) - len(x.lstrip('0')), gr))
|
||||
pre = min(pre, sz - 2)
|
||||
|
||||
r, g, b = map(lambda x: int('0' + x[pre:pre + 2], 16), gr)
|
||||
elif match['rgb_r']:
|
||||
r = int(match['rgb_r'])
|
||||
g = int(match['rgb_g'])
|
||||
b = int(match['rgb_b'])
|
||||
|
||||
# Alpha value.
|
||||
if match['rgb_a_per']:
|
||||
alpha = float(match['rgb_a_per']) / 100.0
|
||||
elif match['rgb_a_flt']:
|
||||
alpha = float(match['rgb_a_flt'])
|
||||
elif match['hsl_hue'] or match['hls_hue']:
|
||||
if match['hsl_hue']:
|
||||
hue = float(match['hsl_hue'])
|
||||
agl = match['hsl_agl']
|
||||
|
||||
# Saturation.
|
||||
if match['hsl_sat_per']:
|
||||
sat = float(match['hsl_sat_per']) / 100.0
|
||||
else:
|
||||
sat = float(match['hsl_sat_flt'])
|
||||
if sat > 1.0:
|
||||
sat /= 100.0
|
||||
|
||||
# Light.
|
||||
if match['hsl_lgt_per']:
|
||||
lgt = float(match['hsl_lgt_per']) / 100.0
|
||||
else:
|
||||
lgt = float(match['hsl_lgt_flt'])
|
||||
if lgt > 1.0:
|
||||
lgt /= 100.0
|
||||
|
||||
# Alpha value.
|
||||
if match['hsl_aph_per']:
|
||||
alpha = float(match['hsl_aph_per']) / 100.0
|
||||
elif match['hsl_aph_flt']:
|
||||
alpha = float(match['hsl_aph_flt'])
|
||||
else:
|
||||
hue = float(match['hls_hue'])
|
||||
agl = match['hls_agl']
|
||||
|
||||
# Saturation.
|
||||
if match['hls_sat_per']:
|
||||
sat = float(match['hls_sat_per']) / 100.0
|
||||
else:
|
||||
sat = float(match['hls_sat_flt'])
|
||||
|
||||
# Light.
|
||||
if match['hls_lgt_per']:
|
||||
lgt = float(match['hls_lgt_per']) / 100.0
|
||||
else:
|
||||
lgt = float(match['hls_lgt_flt'])
|
||||
|
||||
# Alpha value.
|
||||
if match['hls_aph_per']:
|
||||
alpha = float(match['hls_aph_per']) / 100.0
|
||||
elif match['hls_aph_flt']:
|
||||
alpha = float(match['hls_aph_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 sat > 1 or lgt > 1:
|
||||
raise Exception
|
||||
|
||||
r, g, b = _color.hls_to_rgb(hue, lgt, sat)
|
||||
r, g, b = map(lambda x:int(round(x * 255)), (r, g, b))
|
||||
elif match['hwb_hue']:
|
||||
hue = float(match['hwb_hue'])
|
||||
agl = match['hwb_agl']
|
||||
|
||||
# Saturation.
|
||||
if match['hwb_wht_per']:
|
||||
wht = float(match['hwb_wht_per']) / 100.0
|
||||
else:
|
||||
wht = float(match['hwb_wht_flt'])
|
||||
|
||||
# Light.
|
||||
if match['hwb_blk_per']:
|
||||
blk = float(match['hwb_blk_per']) / 100.0
|
||||
else:
|
||||
blk = float(match['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)
|
||||
r, g, b = map(lambda x: int(round(x * 255)), (r, g, b))
|
||||
|
||||
if r < 0 or r > 255 or g < 0 or g > 255 or b < 0 or b > 255:
|
||||
raise Exception
|
||||
if alpha < 0.0 or alpha > 1.0:
|
||||
raise Exception
|
||||
|
||||
return (r, g, b, alpha)
|
||||
|
||||
# End of file.
|
|
@ -1,5 +1,4 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
""" Built-in tags for the `textoutpc` module.
|
||||
Some of these tags will probably have to move to a separate module
|
||||
Planète Casio-specific, but still, here we are.
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from .base import *
|
||||
|
||||
__all__ = ["TextoutParagraphTag"]
|
||||
|
||||
class TextoutParagraphTag(TextoutBlockTag):
|
||||
""" Main tag for basic paragraphs. """
|
||||
|
||||
def begin_html(self):
|
||||
return '<p>'
|
||||
|
||||
def end_html(self):
|
||||
return '</p>'
|
||||
|
||||
# End of file.
|
|
@ -1,10 +1,10 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
""" Main translation function.
|
||||
See the `Translator` class documentation for more information.
|
||||
"""
|
||||
|
||||
import regex as _re
|
||||
from copy import deepcopy as _deepcopy
|
||||
from html import escape as _htmlescape
|
||||
from .tags import TextoutInlineTag, TextoutBlockTag, \
|
||||
TextoutParagraphTag, get_tag
|
||||
|
@ -19,6 +19,9 @@ __all__ = ["Translator"]
|
|||
# ---
|
||||
|
||||
class _TagData:
|
||||
BLOCK = 1
|
||||
INLINE = 2
|
||||
|
||||
def __init__(self, tag, name, full):
|
||||
""" Tag data initialization.
|
||||
Here, we prepare all of the attributes from the tag's
|
||||
|
@ -28,15 +31,28 @@ class _TagData:
|
|||
# `full` is the full tag beginning mark.
|
||||
|
||||
self.name = name
|
||||
self.type = self.BLOCK if isinstance(tag, TextoutBlockTag) \
|
||||
else self.INLINE
|
||||
self.full = full
|
||||
|
||||
# Tag beginning displaying.
|
||||
# `notempty` is the moment when (and if) to start displaying the
|
||||
# tag's code and content.
|
||||
# `started` is whether the tag's beginning has been processed,
|
||||
# i.e. if the content is no longer processed.
|
||||
|
||||
self.notempty = bool(tag.notempty) if hasattr(tag, 'notempty') \
|
||||
else False
|
||||
self.started = False
|
||||
|
||||
# `tag` is the actual tag object returned by `get_tag()`.
|
||||
# XXX: because the tag is going to be called with different contents,
|
||||
# we might as well do deepcopies from a `self.base` each time we
|
||||
# want to start a new instance. But while this is not worked on,
|
||||
# `self.tag` will be enough.
|
||||
|
||||
self.tag = tag
|
||||
self.base = tag
|
||||
self.tag = _deepcopy(tag)
|
||||
|
||||
# Flags and properties calculated from the tag's attributes, using the
|
||||
# rules given in `TAGS.md`.
|
||||
|
@ -55,8 +71,8 @@ class _TagData:
|
|||
self.raw = bool(tag.raw) if hasattr(tag, 'raw') \
|
||||
else hasattr(tag, 'preprocess')
|
||||
|
||||
self.notempty = bool(tag.notempty) if hasattr(tag, 'notempty') \
|
||||
else not self.ign and hasattr(tag, 'default')
|
||||
self.super = bool(tag.superblock) if hasattr(tag, 'superblock') \
|
||||
else False
|
||||
|
||||
# Content processing utilities.
|
||||
# `last` is the content of the tag. A boolean indicates that we
|
||||
|
@ -118,6 +134,10 @@ class Translator:
|
|||
self.raw_mode = False
|
||||
self.raw_deg = 0
|
||||
|
||||
# ---
|
||||
# Text and code outputting utilities.
|
||||
# ---
|
||||
|
||||
def process_text_group(self):
|
||||
""" Process text groups for naked URLs and stuff. """
|
||||
|
||||
|
@ -148,17 +168,11 @@ class Translator:
|
|||
if not code or self.cign > 0:
|
||||
return
|
||||
|
||||
# If we ought to put code, that means that the paragraph content
|
||||
# is starting and that we might have to put the start of paragraph.
|
||||
|
||||
self.start_block()
|
||||
|
||||
# The last queue is composed of booleans (does the group contain
|
||||
# something or not) and texts.
|
||||
# As in `flush_text()`, the last queue is composed of booleans.
|
||||
# We want to set all of the booleans to True until the first text
|
||||
# group, to which we want to add the current text. We mustn't set
|
||||
# the booleans after the first text as the content might be replaced
|
||||
# after that point!
|
||||
# group, to which we want to add the current text.
|
||||
# If there is no content preprocessing and we have to output it,
|
||||
# we want to start the tags first: `dat == None` will be our signal!
|
||||
|
||||
for dat in self.queue:
|
||||
if isinstance(dat.last, bool):
|
||||
|
@ -167,6 +181,15 @@ class Translator:
|
|||
dat.last += code
|
||||
break
|
||||
else:
|
||||
dat = None
|
||||
|
||||
# Start the tags that haven't been started, and stuff.
|
||||
|
||||
self.start_tags()
|
||||
|
||||
# If the content has to be written, we ought to.
|
||||
|
||||
if dat == None:
|
||||
self.outp.write(code)
|
||||
|
||||
def put_newline(self):
|
||||
|
@ -211,17 +234,12 @@ class Translator:
|
|||
if not self.text_group or self.cign > 0:
|
||||
return
|
||||
|
||||
# If we ought to put text, that means that the paragraph content
|
||||
# is starting and that we might have to put the start of paragraph.
|
||||
|
||||
self.start_block()
|
||||
|
||||
# The last queue is composed of booleans (does the group contain
|
||||
# something or not) and texts.
|
||||
# something or not) and texts for content processing.
|
||||
# We want to set all of the booleans to True until the first text
|
||||
# group, to which we want to add the current text. We mustn't set
|
||||
# the booleans after the first text as the content might be replaced
|
||||
# after that point!
|
||||
# group, to which we want to add the current text.
|
||||
# If there is no content preprocessing and we have to output it,
|
||||
# we want to start the tags first: `dat == None` will be our signal!
|
||||
|
||||
for dat in self.queue:
|
||||
if isinstance(dat.last, bool):
|
||||
|
@ -230,6 +248,15 @@ class Translator:
|
|||
dat.last += self.text_group
|
||||
break
|
||||
else:
|
||||
dat = None
|
||||
|
||||
# Start the tags that haven't been started, and stuff.
|
||||
|
||||
self.start_tags()
|
||||
|
||||
# If the content has to be written, we ought to.
|
||||
|
||||
if dat == None:
|
||||
self.outp.write(self.process_text_group())
|
||||
|
||||
# Don't forget to reset the `text_group`, as its content has been
|
||||
|
@ -237,33 +264,9 @@ class Translator:
|
|||
|
||||
self.text_group = ""
|
||||
|
||||
def start_block(self):
|
||||
""" Start the block paragraph if not started. """
|
||||
|
||||
return # TODO
|
||||
|
||||
# First of all, we ought to check the `opened_group` member to
|
||||
# check if the paragraph has started or not.
|
||||
|
||||
if self.opened_group:
|
||||
return
|
||||
self.opened_group = True
|
||||
|
||||
# TODO: other things
|
||||
|
||||
def end_block(self):
|
||||
""" End the block paragraph if not ended. """
|
||||
|
||||
return # TODO
|
||||
|
||||
# First of all, we ought to check the `opened_group` member to
|
||||
# check if the paragraph has ended or not.
|
||||
|
||||
if not self.opened_group:
|
||||
return
|
||||
self.opened_group = False
|
||||
|
||||
# TODO: other things
|
||||
# ---
|
||||
# Tag queue management.
|
||||
# ---
|
||||
|
||||
def push_tag(self, dat):
|
||||
""" Push a tag onto the tag stack. """
|
||||
|
@ -276,16 +279,22 @@ class Translator:
|
|||
if dat.ign:
|
||||
self.cign += 1
|
||||
|
||||
# If there is no content processing, let's put the beginning as soon
|
||||
# as we can, which means right now.
|
||||
# If it is a block, end the current block.
|
||||
|
||||
if not hasattr(tag, 'preprocess') and hasattr(tag, 'begin'):
|
||||
self.put_code(tag.begin())
|
||||
if dat.type == dat.BLOCK:
|
||||
self.end_block()
|
||||
|
||||
# Insert the tag into the queue.
|
||||
|
||||
self.queue.insert(0, dat)
|
||||
|
||||
# Start the tag (and parent tags) if required.
|
||||
|
||||
self.start_tags()
|
||||
|
||||
# Don't forget to add the tag to the queue, and to enable raw
|
||||
# mode if the tag expects a raw content (e.g. `[code]`).
|
||||
|
||||
self.queue.insert(0, dat)
|
||||
if dat.raw:
|
||||
self.raw_mode = True
|
||||
self.raw_deg = 0
|
||||
|
@ -355,7 +364,9 @@ class Translator:
|
|||
# just put the content that we got earlier.
|
||||
|
||||
if hasattr(tag, 'begin'):
|
||||
dat.started = True
|
||||
self.put_code(tag.begin())
|
||||
|
||||
if hasattr(tag, 'content'):
|
||||
self.put_code(tag.content())
|
||||
elif dat.raw:
|
||||
|
@ -385,15 +396,13 @@ class Translator:
|
|||
# Let's put the raw things again as when there is
|
||||
# content processing.
|
||||
|
||||
self.put_text(tag._full)
|
||||
self.put_text(dat.full)
|
||||
self.put_text(end)
|
||||
return
|
||||
|
||||
# Don't forget to put the end of the tag, as well.
|
||||
# That's for when there is content processing or not.
|
||||
# Don't forget to end the tag!
|
||||
|
||||
if hasattr(tag, 'end'):
|
||||
self.put_code(tag.end())
|
||||
self.end_last_tag()
|
||||
|
||||
# Disable raw mode if it was a raw tag (which means that it enabled it,
|
||||
# as tags into raw tags cannot be processed).
|
||||
|
@ -401,8 +410,111 @@ class Translator:
|
|||
if dat.raw:
|
||||
self.raw_mode = False
|
||||
|
||||
# ---
|
||||
# Automatically start and end tags.
|
||||
# ---
|
||||
|
||||
def start_tags(self):
|
||||
""" Start the tags that haven't been started yet.
|
||||
This is usually called when content is output, for tags that
|
||||
aren't empty. """
|
||||
|
||||
# First, get the references to the block and inline tags that need
|
||||
# to be started.
|
||||
|
||||
blocks = []
|
||||
inlines = []
|
||||
for dat in self.queue:
|
||||
# Check if the tag hasn't already been started or doesn't call
|
||||
# for content processing.
|
||||
|
||||
if type(dat.last) != bool: break
|
||||
if dat.notempty and not dat.last: break
|
||||
if dat.started: continue
|
||||
|
||||
# Then put the tag in the appropriate queue, and set it as
|
||||
# started for methods as `put_code()` that call this method
|
||||
# back not to re-put anything.
|
||||
|
||||
dat.started = True
|
||||
if dat.type == dat.BLOCK:
|
||||
blocks.insert(0, dat)
|
||||
else:
|
||||
inlines.insert(0, dat)
|
||||
|
||||
# Then, put the tag beginnings.
|
||||
|
||||
for dat in blocks + inlines:
|
||||
dat.started = True
|
||||
self.put_code(dat.tag.begin())
|
||||
|
||||
def end_block(self):
|
||||
""" End the current block. """
|
||||
|
||||
# We want to collect inline and block tags, in the order they
|
||||
# were inserted, reversed.
|
||||
|
||||
blocks = []
|
||||
inlines = []
|
||||
for dat in self.queue:
|
||||
# Check if the tag has been started and if it is a super
|
||||
# block (which means we want to stop here).
|
||||
|
||||
if not isinstance(dat.last, bool) or dat.super: break
|
||||
|
||||
# Then put the tag in the appropriate queue, and set it as
|
||||
# unstarted for safety reasons.
|
||||
|
||||
dat.started = False
|
||||
if dat.type == dat.BLOCK:
|
||||
blocks.append(dat)
|
||||
else:
|
||||
inlines.append(dat)
|
||||
|
||||
# Then we want to end the tags, and reset them in case we're going
|
||||
# to use them.
|
||||
|
||||
for dat in inlines + blocks:
|
||||
tag = dat.tag
|
||||
if hasattr(tag, 'end'):
|
||||
self.put_code(tag.end())
|
||||
|
||||
dat.tag = _deepcopy(dat.base)
|
||||
dat.started = False
|
||||
dat.last = False
|
||||
|
||||
def end_last_tag(self):
|
||||
""" End the latest tag entered in the queue. """
|
||||
|
||||
if not self.queue:
|
||||
return
|
||||
|
||||
# If the tag hasn't been started, then it shouldn't be ended.
|
||||
|
||||
dat = self.queue[0]
|
||||
if not dat.started:
|
||||
return
|
||||
|
||||
# If it is not a block, then we should just end it like that.
|
||||
|
||||
if dat.type != dat.BLOCK:
|
||||
dat.started = False
|
||||
tag = dat.tag
|
||||
if hasattr(tag, 'end'):
|
||||
self.put_code(tag.end())
|
||||
return
|
||||
|
||||
# If we have arrived there, then the tag to end is a block.
|
||||
# We want to end the whole current block.
|
||||
|
||||
self.end_block()
|
||||
|
||||
# ---
|
||||
# Main function.
|
||||
# ---
|
||||
|
||||
def process(self):
|
||||
""" Main function of the translator. """
|
||||
""" Main function of the textout translator. """
|
||||
|
||||
# By default, everything is in a paragraph.
|
||||
# Other blocks will supplant this by being further in the queue.
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
""" Autolinking (URL extraction from raw text) in HTML. """
|
||||
|
||||
import regex as _re
|
||||
from html import escape as _htmlescape
|
||||
|
||||
__all__ = ["htmlurls"]
|
||||
__all__ = ["htmlurls", "lightscripturls"]
|
||||
|
||||
# ---
|
||||
# Autolinking regex.
|
||||
# ---
|
||||
|
||||
def _sub(m):
|
||||
def _sub_html(m):
|
||||
sp = m.group('sp')
|
||||
url = m.group('url')
|
||||
aft = ''
|
||||
|
@ -24,6 +23,20 @@ def _sub(m):
|
|||
.format(sp, url, url, aft)
|
||||
return text
|
||||
|
||||
def _sub_lightscript(m):
|
||||
sp = m.group('sp')
|
||||
url = m.group('url')
|
||||
aft = ''
|
||||
|
||||
# Hack for the last comma.
|
||||
if url[-1] == ',':
|
||||
url, aft = url[:-1], ','
|
||||
|
||||
url = url.replace('<', '%3C')
|
||||
url = url.replace('>', '%3E')
|
||||
text = '{}<{}>{}'.format(sp, url, aft)
|
||||
return text
|
||||
|
||||
_reg = _re.compile("""\
|
||||
(?P<sp>^|\s|[[:punct:]])
|
||||
(?P<url>(https?|ftp):
|
||||
|
@ -32,10 +45,13 @@ _reg = _re.compile("""\
|
|||
""", _re.VERBOSE | _re.M)
|
||||
|
||||
# ---
|
||||
# Main function.
|
||||
# Main functions.
|
||||
# ---
|
||||
|
||||
def htmlurls(text):
|
||||
return _reg.sub(_sub, text)
|
||||
return _reg.sub(_sub_html, text)
|
||||
|
||||
def lightscripturls(text):
|
||||
return _reg.sub(_sub_lightscript, text)
|
||||
|
||||
# End of file.
|
||||
|
|
Loading…
Reference in New Issue