utils.py 9.3 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
# -*- coding:utf-8 -*
#
# Copyright 2016,2017
# - Skia <skia@libskia.so>
#
# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# http://ae.utbm.fr.
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License a published by the Free Software
# Foundation; either version 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple
# Place - Suite 330, Boston, MA 02111-1307, USA.
#
#

Skia's avatar
Skia committed
25 26
import re

27 28 29
# Image utils

from io import BytesIO
Krophil's avatar
Krophil committed
30
from datetime import date
31

Krophil's avatar
Krophil committed
32
from PIL import ExifTags
Sli's avatar
Sli committed
33

Krophil's avatar
Krophil committed
34
# from exceptions import IOError
Skia's avatar
Skia committed
35
import PIL
36 37

from django.conf import settings
Skia's avatar
Skia committed
38
from django.core.files.base import ContentFile
39

40 41 42 43 44 45 46 47 48 49 50 51 52 53

def get_start_of_semester(d=date.today()):
    """
    This function computes the start date of the semester with respect to the given date (default is today),
    and the start date given in settings.SITH_START_DATE.
    It takes the nearest past start date.
    Exemples: with SITH_START_DATE = (8, 15)
        Today      -> Start date
        2015-03-17 -> 2015-02-15
        2015-01-11 -> 2014-08-15
    """
    today = d
    year = today.year
    start = date(year, settings.SITH_START_DATE[0], settings.SITH_START_DATE[1])
Krophil's avatar
Krophil committed
54
    start2 = start.replace(month=(start.month + 6) % 12)
55 56 57
    if start > start2:
        start, start2 = start2, start
    if today < start:
Krophil's avatar
Krophil committed
58
        return start2.replace(year=year - 1)
59 60 61 62 63
    elif today < start2:
        return start
    else:
        return start2

Krophil's avatar
Krophil committed
64

Skia's avatar
Skia committed
65 66 67 68 69 70 71
def get_semester(d=date.today()):
    start = get_start_of_semester(d)
    if start.month <= 6:
        return "P" + str(start.year)[-2:]
    else:
        return "A" + str(start.year)[-2:]

Krophil's avatar
Krophil committed
72

73 74
def scale_dimension(width, height, long_edge):
    if width > height:
Sli's avatar
Sli committed
75
        ratio = long_edge * 1.0 / width
76
    else:
Sli's avatar
Sli committed
77
        ratio = long_edge * 1.0 / height
78 79
    return int(width * ratio), int(height * ratio)

Krophil's avatar
Krophil committed
80

Skia's avatar
Skia committed
81
def resize_image(im, edge, format):
82 83 84
    (w, h) = im.size
    (width, height) = scale_dimension(w, h, long_edge=edge)
    content = BytesIO()
Skia's avatar
Skia committed
85 86
    im = im.resize((width, height), PIL.Image.ANTIALIAS)
    try:
Sli's avatar
Sli committed
87 88 89 90 91 92 93
        im.save(
            fp=content,
            format=format.upper(),
            quality=90,
            optimize=True,
            progressive=True,
        )
Skia's avatar
Skia committed
94 95
    except IOError:
        PIL.ImageFile.MAXBLOCK = im.size[0] * im.size[1]
Sli's avatar
Sli committed
96 97 98 99 100 101 102
        im.save(
            fp=content,
            format=format.upper(),
            quality=90,
            optimize=True,
            progressive=True,
        )
103 104
    return ContentFile(content.getvalue())

Skia's avatar
Skia committed
105

Krophil's avatar
Krophil committed
106 107
def exif_auto_rotate(image):
    for orientation in ExifTags.TAGS.keys():
Sli's avatar
Sli committed
108
        if ExifTags.TAGS[orientation] == "Orientation":
Krophil's avatar
Krophil committed
109 110 111 112 113 114 115 116 117
            break
    exif = dict(image._getexif().items())

    if exif[orientation] == 3:
        image = image.rotate(180, expand=True)
    elif exif[orientation] == 6:
        image = image.rotate(270, expand=True)
    elif exif[orientation] == 8:
        image = image.rotate(90, expand=True)
Skia's avatar
Skia committed
118 119

    return image
Skia's avatar
Skia committed
120

Krophil's avatar
Krophil committed
121

Skia's avatar
Skia committed
122
def doku_to_markdown(text):
Skia's avatar
Skia committed
123
    """This is a quite correct doku translator"""
Sli's avatar
Sli committed
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
    text = re.sub(
        r"([^:]|^)\/\/(.*?)\/\/", r"*\2*", text
    )  # Italic (prevents protocol:// conflict)
    text = re.sub(
        r"<del>(.*?)<\/del>", r"~~\1~~", text, flags=re.DOTALL
    )  # Strike (may be multiline)
    text = re.sub(
        r"<sup>(.*?)<\/sup>", r"^\1^", text
    )  # Superscript (multiline not supported, because almost never used)
    text = re.sub(r"<sub>(.*?)<\/sub>", r"_\1_", text)  # Subscript (idem)

    text = re.sub(r"^======(.*?)======", r"#\1", text, flags=re.MULTILINE)  # Titles
    text = re.sub(r"^=====(.*?)=====", r"##\1", text, flags=re.MULTILINE)
    text = re.sub(r"^====(.*?)====", r"###\1", text, flags=re.MULTILINE)
    text = re.sub(r"^===(.*?)===", r"####\1", text, flags=re.MULTILINE)
    text = re.sub(r"^==(.*?)==", r"#####\1", text, flags=re.MULTILINE)
    text = re.sub(r"^=(.*?)=", r"######\1", text, flags=re.MULTILINE)

    text = re.sub(r"<nowiki>", r"<nosyntax>", text)
    text = re.sub(r"</nowiki>", r"</nosyntax>", text)
    text = re.sub(r"<code>", r"```\n", text)
    text = re.sub(r"</code>", r"\n```", text)
    text = re.sub(r"article://", r"page://", text)
    text = re.sub(r"dfile://", r"file://", text)
Skia's avatar
Skia committed
148 149

    i = 1
Sli's avatar
Sli committed
150 151
    for fn in re.findall(r"\(\((.*?)\)\)", text):  # Footnotes
        text = re.sub(r"\(\((.*?)\)\)", r"[^%s]" % i, text, count=1)
Skia's avatar
Skia committed
152 153 154
        text += "\n[^%s]: %s\n" % (i, fn)
        i += 1

Sli's avatar
Sli committed
155
    text = re.sub(r"\\{2,}[\s]", r"   \n", text)  # Carriage return
Skia's avatar
Skia committed
156

Sli's avatar
Sli committed
157 158 159 160 161 162 163
    text = re.sub(r"\[\[(.*?)\|(.*?)\]\]", r"[\2](\1)", text)  # Links
    text = re.sub(r"\[\[(.*?)\]\]", r"[\1](\1)", text)  # Links 2
    text = re.sub(r"{{(.*?)\|(.*?)}}", r'![\2](\1 "\2")', text)  # Images
    text = re.sub(r"{{(.*?)(\|(.*?))?}}", r'![\1](\1 "\1")', text)  # Images 2
    text = re.sub(
        r"{\[(.*?)(\|(.*?))?\]}", r"[\1](\1)", text
    )  # Video (transform to classic links, since we can't integrate them)
Skia's avatar
Skia committed
164

Sli's avatar
Sli committed
165
    text = re.sub(r"###(\d*?)###", r"[[[\1]]]", text)  # Progress bar
Skia's avatar
Skia committed
166

Sli's avatar
Sli committed
167 168 169
    text = re.sub(
        r"(\n +[^* -][^\n]*(\n +[^* -][^\n]*)*)", r"```\1\n```", text, flags=re.DOTALL
    )  # Block code without lists
Skia's avatar
Skia committed
170

Sli's avatar
Sli committed
171
    text = re.sub(r"( +)-(.*)", r"1.\2", text)  # Ordered lists
Skia's avatar
Skia committed
172 173 174

    new_text = []
    quote_level = 0
Krophil's avatar
Krophil committed
175
    for line in text.splitlines():  # Tables and quotes
Sli's avatar
Sli committed
176 177 178 179
        enter = re.finditer(r"\[quote(=(.+?))?\]", line)
        quit = re.finditer(r"\[/quote\]", line)
        if re.search(r"\A\s*\^(([^\^]*?)\^)*", line):  # Table part
            line = line.replace("^", "|")
Skia's avatar
Skia committed
180
            new_text.append("> " * quote_level + line)
Sli's avatar
Sli committed
181 182 183
            new_text.append(
                "> " * quote_level + "|---|"
            )  # Don't keep the text alignement in tables it's really too complex for what it's worth
Krophil's avatar
Krophil committed
184 185
        elif enter or quit:  # Quote part
            for quote in enter:  # Enter quotes (support multiple at a time)
Skia's avatar
Skia committed
186 187 188 189 190
                quote_level += 1
                try:
                    new_text.append("> " * quote_level + "##### " + quote.group(2))
                except:
                    new_text.append("> " * quote_level)
Sli's avatar
Sli committed
191
                line = line.replace(quote.group(0), "")
Sli's avatar
Sli committed
192
            final_quote_level = quote_level  # Store quote_level to use at the end, since it will be modified during quit iteration
Skia's avatar
Skia committed
193
            final_newline = False
Krophil's avatar
Krophil committed
194
            for quote in quit:  # Quit quotes (support multiple at a time)
Sli's avatar
Sli committed
195
                line = line.replace(quote.group(0), "")
Skia's avatar
Skia committed
196 197
                quote_level -= 1
                final_newline = True
Krophil's avatar
Krophil committed
198 199
            new_text.append("> " * final_quote_level + line)  # Finally append the line
            if final_newline:
Sli's avatar
Sli committed
200 201 202
                new_text.append(
                    "\n"
                )  # Add a new line to ensure the separation between the quote and the following text
Skia's avatar
Skia committed
203 204 205 206 207
        else:
            new_text.append(line)

    return "\n".join(new_text)

Krophil's avatar
Krophil committed
208

Skia's avatar
Skia committed
209 210
def bbcode_to_markdown(text):
    """This is a very basic BBcode translator"""
Sli's avatar
Sli committed
211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226
    text = re.sub(r"\[b\](.*?)\[\/b\]", r"**\1**", text, flags=re.DOTALL)  # Bold
    text = re.sub(r"\[i\](.*?)\[\/i\]", r"*\1*", text, flags=re.DOTALL)  # Italic
    text = re.sub(r"\[u\](.*?)\[\/u\]", r"__\1__", text, flags=re.DOTALL)  # Underline
    text = re.sub(
        r"\[s\](.*?)\[\/s\]", r"~~\1~~", text, flags=re.DOTALL
    )  # Strike (may be multiline)
    text = re.sub(
        r"\[strike\](.*?)\[\/strike\]", r"~~\1~~", text, flags=re.DOTALL
    )  # Strike 2

    text = re.sub(r"article://", r"page://", text)
    text = re.sub(r"dfile://", r"file://", text)

    text = re.sub(r"\[url=(.*?)\](.*)\[\/url\]", r"[\2](\1)", text)  # Links
    text = re.sub(r"\[url\](.*)\[\/url\]", r"\1", text)  # Links 2
    text = re.sub(r"\[img\](.*)\[\/img\]", r'![\1](\1 "\1")', text)  # Images
Skia's avatar
Skia committed
227 228 229

    new_text = []
    quote_level = 0
Krophil's avatar
Krophil committed
230
    for line in text.splitlines():  # Tables and quotes
Sli's avatar
Sli committed
231 232
        enter = re.finditer(r"\[quote(=(.+?))?\]", line)
        quit = re.finditer(r"\[/quote\]", line)
Krophil's avatar
Krophil committed
233 234
        if enter or quit:  # Quote part
            for quote in enter:  # Enter quotes (support multiple at a time)
Skia's avatar
Skia committed
235 236 237 238 239
                quote_level += 1
                try:
                    new_text.append("> " * quote_level + "##### " + quote.group(2))
                except:
                    new_text.append("> " * quote_level)
Sli's avatar
Sli committed
240
                line = line.replace(quote.group(0), "")
Sli's avatar
Sli committed
241
            final_quote_level = quote_level  # Store quote_level to use at the end, since it will be modified during quit iteration
Skia's avatar
Skia committed
242
            final_newline = False
Krophil's avatar
Krophil committed
243
            for quote in quit:  # Quit quotes (support multiple at a time)
Sli's avatar
Sli committed
244
                line = line.replace(quote.group(0), "")
Skia's avatar
Skia committed
245
                quote_level -= 1
Skia's avatar
Skia committed
246
                final_newline = True
Krophil's avatar
Krophil committed
247 248
            new_text.append("> " * final_quote_level + line)  # Finally append the line
            if final_newline:
Sli's avatar
Sli committed
249 250 251
                new_text.append(
                    "\n"
                )  # Add a new line to ensure the separation between the quote and the following text
Skia's avatar
Skia committed
252 253 254 255
        else:
            new_text.append(line)

    return "\n".join(new_text)