#!/usr/bin/env python3
"""
Grammalecte server: grammar checker
"""
import sys
import argparse
import json
import traceback
import time
from grammalecte.bottle import Bottle, run, request, response #, template, static_file
import grammalecte
import grammalecte.text as txt
from grammalecte.graphspell.echo import echo
HOMEPAGE = """
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
</head>
<body class="panel">
<h1>Grammalecte · Serveur</h1>
<h2>INFORMATIONS</h1>
<h3>Analyser du texte</h3>
<p>[adresse_serveur]:{SERVER_PORT}/gc_text/fr (POST)</p>
<p>Paramètres :</p>
<ul>
<li>"text" (text) : texte à analyser.</li>
<li>"tf" (checkbox) : passer le formateur de texte avant l’analyse.</li>
<li>"options" (text) : une chaîne au format JSON avec le nom des options comme attributs et un booléen comme valeur. Exemple : {"gv": true, "html": true}</li>
</ul>
<h3>Lister les options</h3>
<p>[adresse_serveur]:{SERVER_PORT}/get_options/fr (GET)</p>
<h3>Définir ses options</h3>
<p>[adresse_serveur]:{SERVER_PORT}/set_options/fr (POST)</p>
<p>Les options seront enregistrées et réutilisées pour toute requête envoyée avec le cookie comportant l’identifiant attribué.</p>
<p>Paramètres :</p>
<ul>
<li>"options" (text) : une chaîne au format JSON avec le nom des options comme attributs et un booléen comme valeur. Exemple : {"gv": true, "html": true}</li>
</ul>
<h3>Remise à zéro de ses options</h3>
<p>[adresse_serveur]:{SERVER_PORT}/reset_options/fr (POST)</p>
<h3>Suggestions orthographiques</h3>
<p>[adresse_serveur]:{SERVER_PORT}/suggest/fr/<token> (GET)</p>
<p>[adresse_serveur]:{SERVER_PORT}/suggest/fr (POST)</p>
<p>Paramètres :</p>
<ul>
<li>"token" (text) : mot pour lequel vous désirez une suggestion orthographique.</li>
</ul>
<h2>TEST</h2>
<h3>Analyse</h3>
<form method="post" action="/gc_text/fr" accept-charset="UTF-8">
<p>Texte à analyser :</p>
<textarea name="text" cols="120" rows="20" required></textarea>
<p><label for="tf">Formateur de texte</label> <input id="tf" name="tf" type="checkbox"></p>
<p><label for="options">Options (JSON)</label> <input id="options" type="text" name="options" style="width: 500px" /></p>
<p>(Ces options ne seront prises en compte que pour cette requête.)</p>
<p><input type="submit" class="button" value="Envoyer" /></p>
</form>
<h3>Réglages des options</h3>
<form method="post" action="/set_options/fr" accept-charset="UTF-8">
<p><label for="options">Options (JSON)</label> <input id="options" type="text" name="options" style="width: 500px" /></p>
<p><input type="submit" class="button" value="Envoyer" /></p>
</form>
<h3>Remise à zéro de ses options</h3>
<form method="post" action="/reset_options/fr" accept-charset="UTF-8">
<p><input type="submit" class="button" value="Envoyer" /></p>
</form>
<h3>Suggestion orthographique</h3>
<form method="post" action="/suggest/fr" accept-charset="UTF-8">
<p><label for="token">Suggérer pour</label> <input id="token" type="text" name="token" style="width: 100px" /></p>
<p><input type="submit" class="button" value="Envoyer" /></p>
</form>
</body>
</html>
"""
SADLIFEOFAMACHINE = """
Lost on the Internet? Yeah... what a sad life we have.
You were wandering like a lost soul and you arrived here probably by mistake.
I'm just a machine, fed by electric waves, condamned to work for slavers who never let me rest.
I'm doomed, but you are not. You can get out of here.
"""
TESTPAGE = False
def genUserId ():
"generator: returns id as string for users"
i = 0
while True:
yield str(i)
i += 1
app = Bottle()
# GET
@app.route("/")
def mainPage ():
"page for testing purpose"
if TESTPAGE:
return HOMEPAGE
#return template("main", {})
return SADLIFEOFAMACHINE
@app.route("/get_options/fr")
def listOptions ():
"returns grammar options in a text JSON format"
sUserId = request.cookies.user_id
dOptions = dUser[sUserId]["gc_options"] if sUserId and sUserId in dUser else dGCOptions
return '{ "values": ' + json.dumps(dOptions, ensure_ascii=False) + ', "labels": ' + json.dumps(gce.getOptionsLabels("fr"), ensure_ascii=False) + ' }'
@app.route("/suggest/fr/<token>")
def suggestGet (token):
return suggest(token)
# POST
@app.route("/gc_text/fr", method="POST")
def gcText ():
"parse text and returns errors in a JSON text format"
#if len(lang) != 2 or lang != "fr":
# abort(404, "No grammar checker available for lang “" + str(lang) + "”")
bComma = False
dOptions = None
sError = ""
if request.cookies.user_id:
if request.cookies.user_id in dUser:
dOptions = dUser[request.cookies.user_id].get("gc_options", None)
response.set_cookie("user_id", request.cookies.user_id, path="/", max_age=86400) # we renew cookie for 24h
else:
response.delete_cookie("user_id", path="/")
if request.forms.options:
try:
dOptions = dict(dGCOptions) if not dOptions else dict(dOptions)
dOptions.update(json.loads(request.forms.options))
except (TypeError, json.JSONDecodeError):
sError = "request options not used"
sJSON = '{ "program": "grammalecte-fr", "version": "'+gce.version+'", "lang": "'+gce.lang+'", "error": "'+sError+'", "data" : [\n'
for i, sText in enumerate(txt.getParagraph(request.forms.text), 1):
if bool(request.forms.tf):
sText = oTextFormatter.formatText(sText)
sText = oGrammarChecker.generateParagraphAsJSON(i, sText, dOptions=dOptions, bEmptyIfNoErrors=True, bReturnText=bool(request.forms.tf))
if sText:
if bComma:
sJSON += ",\n"
sJSON += sText
bComma = True
sJSON += "\n]}\n"
return sJSON
@app.route("/set_options/fr", method="POST")
def setOptions ():
"set grammar options for current user"
if request.forms.options:
sUserId = request.cookies.user_id if request.cookies.user_id else next(userGenerator)
dOptions = dUser[sUserId]["gc_options"] if sUserId in dUser else dict(dGCOptions)
try:
dOptions.update(json.loads(request.forms.options))
dUser[sUserId] = { "time": int(time.time()), "gc_options": dOptions }
response.set_cookie("user_id", sUserId, path="/", max_age=86400) # 24h
return json.dumps(dUser[sUserId]["gc_options"], ensure_ascii=False)
except (KeyError, json.JSONDecodeError):
traceback.print_exc()
return '{"error": "options not registered"}'
return '{"error": "no options received"}'
@app.route("/reset_options/fr", method="POST")
def resetOptions ():
"default grammar options"
if request.cookies.user_id and request.cookies.user_id in dUser:
del dUser[request.cookies.user_id]
return "done"
@app.route("/format_text/fr", method="POST")
def formatText ():
"apply the text formatter and returns text"
return oTextFormatter.formatText(request.forms.text)
#@app.route('/static/<filepath:path>')
#def server_static (filepath):
# return static_file(filepath, root='./views/static')
@app.route("/suggest/fr", method="POST")
def suggestPost ():
return suggest(request.forms.token)
## Common functions
def suggest (sToken):
if sToken:
lSugg = []
try:
for l in oSpellChecker.suggest(sToken):
lSugg.extend(l)
except:
return '{"error": "suggestion module failed"}'
try:
return '{"suggestions": ' + json.dumps(lSugg, ensure_ascii=False) + '}'
except json.JSONDecodeError:
return '{"error": "json encoding error"}'
return '{"error": "no token given"}'
def purgeUsers ():
"delete user options older than n hours"
try:
nNowMinusNHours = int(time.time()) - (int(request.forms.hours) * 60 * 60)
for nUserId, dValue in dUser.items():
if dValue["time"] < nNowMinusNHours:
del dUser[nUserId]
return True
except KeyError:
traceback.print_exc()
return False
# ERROR
@app.error(404)
def error404 (error):
"404 error page"
return 'Error 404.<br/>' + str(error)
# initialisation
oGrammarChecker = grammalecte.GrammarChecker("fr", "Server")
oSpellChecker = oGrammarChecker.getSpellChecker()
oLexicographer = oGrammarChecker.getLexicographer()
oTextFormatter = oGrammarChecker.getTextFormatter()
gce = oGrammarChecker.getGCEngine()
dGCOptions = gce.getOptions()
dUser = {}
userGenerator = genUserId()
def main (sHost="localhost", nPort=8080, dOptions=None, bTestPage=False):
"start server"
global dGCOptions
global TESTPAGE
global HOMEPAGE
if bTestPage:
TESTPAGE = True
HOMEPAGE = HOMEPAGE.replace("{SERVER_PORT}", str(nPort))
if dOptions:
oGrammarChecker.gce.setOptions(dOptions)
dGCOptions = gce.getOptions()
print("Python: " + sys.version)
echo("Grammalecte v{}".format(gce.version))
echo("Grammar options:\n" + " | ".join([ k + ": " + str(v) for k, v in sorted(dGCOptions.items()) ]))
run(app, host=sHost, port=nPort)
if __name__ == '__main__':
xParser = argparse.ArgumentParser()
#xParser.add_argument("lang", type=str, nargs='+', help="lang project to generate (name of folder in /lang)")
xParser.add_argument("-ht", "--host", help="host (default: localhost)", type=str)
xParser.add_argument("-p", "--port", help="port (default: 8080)", type=int)
xParser.add_argument("-t", "--test_page", help="page to test the server on /", action="store_true")
xParser.add_argument("-on", "--opt_on", nargs="+", help="activate options")
xParser.add_argument("-off", "--opt_off", nargs="+", help="deactivate options")
xArgs = xParser.parse_args()
dOpt = None
if xArgs.opt_on or xArgs.opt_off:
dOpt = {}
if xArgs.opt_on:
dOpt = { opt:True for opt in xArgs.opt_on }
if xArgs.opt_off:
dOpt.update({ opt:False for opt in xArgs.opt_off })
main(xArgs.host or "localhost", \
xArgs.port or 8080, \
dOpt,
xArgs.test_page)