plint

French poetry validator (local mirror of https://gitlab.com/a3nm/plint)
git clone https://a3nm.net/git/plint/
Log | Files | Refs | README

plint_web.py (7114B)


      1 #!/usr/bin/python3 -Ou
      2 # encoding: utf8
      3 
      4 from plint import localization, error, template, diaeresis
      5 import re
      6 from plint.bottle import run, Bottle, request, static_file, redirect, response
      7 from jinja2 import Environment, PackageLoader
      8 from json import dumps
      9 import time
     10 
     11 env = Environment(loader=PackageLoader('plint_web', 'views'))
     12 
     13 
     14 # force HTTPS usage
     15 # http://bottlepy.org/docs/dev/faq.html#problems-with-reverse-proxies
     16 # because bottle makes absolute redirects
     17 # https://github.com/bottlepy/bottle/blob/9fe68c89e465004a5e6babed0955bc1eeba88002/bottle.py#L2637
     18 # even though relative Location: is now allowed
     19 # http://stackoverflow.com/a/25643550
     20 def fix_https(app):
     21     def fixed_app(environ, start_response):
     22         environ['wsgi.url_scheme'] = 'https'
     23         return app(environ, start_response)
     24 
     25     return fixed_app
     26 
     27 
     28 app = Bottle()
     29 app.wsgi = fix_https(app.wsgi)
     30 
     31 THROTTLE_DELAY = 2
     32 throttle = set()
     33 
     34 
     35 def best_match(matches, header):
     36     # inspired by http://www.xml.com/pub/a/2005/06/08/restful.html
     37 
     38     def parse_one(t):
     39         parts = t.split(";")
     40         d = {}
     41         for param in parts[1:]:
     42             spl = param.split("=")
     43             if (len(spl) != 2):
     44                 # this should be formatted as key=value
     45                 # so ignore it
     46                 continue
     47             k, v = spl
     48             d[k.strip().lower()] = v.strip()
     49         if 'q' not in d.keys():
     50             d['q'] = "1"
     51         return (parts[0], d)
     52 
     53     parts = []
     54     for p in header.split(","):
     55         parsed = parse_one(p)
     56         try:
     57             value = float(parsed[1]['q'])
     58         except ValueError:
     59             # q value should be a float; set it to 0
     60             value = 0
     61         parts.append((value, parsed[0].split("-")))
     62     for lang in [x[1] for x in sorted(parts, reverse=True)]:
     63         for match in matches:
     64             if match in lang:
     65                 return match
     66     return matches[0]
     67 
     68 
     69 def get_locale():
     70     header = request.headers.get('Accept-Language')
     71     print(header)
     72     try:
     73         return best_match(['fr', 'en'], header)
     74     except AttributeError:
     75         return 'en'
     76 
     77 
     78 def get_title(lang):
     79     if lang == 'fr':
     80         return "plint -- vérification formelle de poèmes"
     81     else:
     82         return "plint -- French poetry checker"
     83 
     84 
     85 @app.route('/static/tpl/<filename>')
     86 def server_static(filename):
     87     return static_file(filename, root="./static/tpl", mimetype="text/plain")
     88 
     89 
     90 @app.route('/<lang>/static/img/<filename>')
     91 def server_static(filename, lang=None):
     92     return static_file(filename, root="./static/img")
     93 
     94 
     95 @app.route('/<lang>/static/tpl/<filename>')
     96 def server_static(filename, lang=None):
     97     return static_file(filename, root="./static/tpl", mimetype="text/plain")
     98 
     99 
    100 @app.route('/static/<filename>')
    101 def server_static(filename):
    102     return static_file(filename, root="./static")
    103 
    104 
    105 @app.route('/<lang>/static/<filename>')
    106 def server_static(filename, lang=None):
    107     return static_file(filename, root="./static")
    108 
    109 
    110 @app.route('/')
    111 def root():
    112     redirect('/' + get_locale() + '/')
    113 
    114 
    115 @app.route('/<page>')
    116 def paged(page):
    117     redirect('/' + get_locale() + '/' + page)
    118 
    119 
    120 @app.route('/<lang>/')
    121 def root(lang):
    122     if lang not in ['fr', 'en']:
    123         return paged(lang)
    124     return env.get_template('index.html').render(title=get_title(lang),
    125                                                  lang=lang, path="")
    126 
    127 
    128 @app.route('/<lang>/about')
    129 def about(lang):
    130     return env.get_template('about.html').render(title=get_title(lang),
    131                                                  lang=lang, path="about")
    132 
    133 
    134 MAX_POEM_LEN = 8192
    135 MAX_LINE_LEN = 512
    136 
    137 
    138 class TooBigException(Exception):
    139     pass
    140 
    141 
    142 class TooLongLinesException(Exception):
    143     pass
    144 
    145 
    146 def check(poem):
    147     if len(poem) > MAX_POEM_LEN:
    148         raise TooBigException
    149     s = poem.split("\n")
    150     for x in range(len(s)):
    151         if len(s[x]) > MAX_LINE_LEN:
    152             raise TooLongLinesException
    153         s[x] = s[x].strip()
    154     return s
    155 
    156 
    157 @app.route('/<lang>/checkjs', method='POST')
    158 def q(lang):
    159     global throttle
    160     # necessary when serving with lighttpd proxy-core
    161     ip = request.environ.get('HTTP_X_FORWARDED_FOR')
    162     if not ip:
    163         # fallback; this is 127.0.0.1 with proxy-core
    164         ip = request.environ.get('REMOTE_ADDR')
    165     t = time.time()
    166     print("== %s %s ==" % (ip, t))
    167     response.content_type = 'application/json'
    168     localization.init_locale(lang)
    169     throttle = set(x for x in throttle if t - x[1] < THROTTLE_DELAY)
    170     if ip in (x[0] for x in throttle):
    171         if lang == 'fr':
    172             msg = (("Trop de requêtes pour vérifier le poème,"
    173                     + " veuillez réessayer dans %d secondes") %
    174                    THROTTLE_DELAY)
    175         else:
    176             msg = (("Too many requests to check poem,"
    177                     + " please try again in %d seconds") %
    178                    THROTTLE_DELAY)
    179         return dumps({'error': msg})
    180     throttle.add((ip, t))
    181     poem = re.sub(r'<>&', '', request.forms.get('poem'))
    182     print(poem)
    183 
    184     # default message
    185     if lang == 'fr':
    186         msg = "Le poème est vide"
    187     else:
    188         msg = "Poem is empty"
    189 
    190     try:
    191         poem = check(poem)
    192     except TooBigException:
    193         poem = None
    194         if lang == 'fr':
    195             msg = "Le poème est trop long (maximum %d caractères)" % MAX_POEM_LEN
    196         else:
    197             msg = "Poem is too long (maximum %d characters)" % MAX_POEM_LEN
    198     except TooLongLinesException:
    199         poem = None
    200         if lang == 'fr':
    201             msg = "Certaines lignes du poème sont trop longues (maximum %d caractères)" % MAX_LINE_LEN
    202         else:
    203             msg = "Some lines of the poem are too long (maximum %d characters)" % MAX_LINE_LEN
    204     if not poem or len(poem) == 0 or (len(poem) == 1 and len(poem[0]) == 0):
    205         return dumps({'error': msg})
    206     templateName = re.sub(r'[^a-z_]', '', request.forms.get('template'))
    207     print(templateName)
    208     if templateName == 'custom':
    209         x = request.forms.get('custom_template')
    210     else:
    211         try:
    212             f = open("static/tpl/" + templateName + ".tpl")
    213             x = f.read()
    214             f.close()
    215         except IOError:
    216             if lang == 'fr':
    217                 msg = "Modèle inexistant"
    218             else:
    219                 msg = "No such template"
    220             return dumps({'error': msg})
    221     print(x)
    222     try:
    223         templ = template.Template(x)
    224     except error.TemplateLoadError as e:
    225         if lang == 'fr':
    226             msg = "Erreur à la lecture du modèle : " + e.msg
    227         else:
    228             msg = "Error when reading template: " + e.msg
    229         return dumps({'error': msg})
    230     poem.append(None)
    231     r = []
    232     i = 0
    233     d = {}
    234     for line in poem:
    235         i += 1
    236         last = False
    237         if line == None:
    238             line = ""
    239             last = True
    240         errors = templ.check(line, last=last)
    241         if errors:
    242             r.append({
    243                 'line': line,
    244                 'num': i,
    245                 'errors': sum(errors.lines(short=True), [])
    246             })
    247     d['result'] = r
    248     return dumps(d)
    249 
    250 
    251 if __name__ == '__main__':
    252     run(app, port='5000', server="cherrypy", host="::")