Warning, file /wikitolearn/wikitolearn-pdf-backend/src/app.py was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 #!/usr/bin/env python3
0002 # -*- coding: utf-8 -*-
0003 from healthcheck import HealthCheck
0004 from flask_rq2 import RQ
0005 from flask_rq2.job import FlaskJob
0006 from flask import Flask, jsonify, request, app, send_from_directory, make_response, abort
0007 import json, uuid, os, pypandoc
0008 from pylatex import Document, Section, Command
0009 from pylatex.base_classes import Options, Arguments
0010 from pylatex.utils import NoEscape
0011 from pylatex.section import Chapter
0012 from pylatex.package import Package
0013 from pylatex.basic import NewPage
0014 
0015 redis_host = os.environ.get('REDIS_HOST')
0016 redis_port = os.environ.get('REDIS_PORT')
0017 redis_queue = os.environ.get('REDIS_QUEUE')
0018 
0019 languages = {
0020   'it': 'italian',
0021   'en': 'english',
0022   'fr': 'french',
0023   'es': 'spanish',
0024   'ca': 'catalan',
0025   'de': 'german',
0026 }
0027 
0028 app = Flask(__name__)
0029 app.config['RQ_REDIS_URL'] = 'redis://' + redis_host + ':' + redis_port + '/0'
0030 app.config['RQ_QUEUES'] = [redis_queue]
0031 rq = RQ(app)
0032 rq.default_queue = redis_queue
0033 health = HealthCheck(app, '/_meta/status')
0034 
0035 @app.route('/v1/math-verifier/inline', methods = ['POST'])
0036 def verify_math_formula_inline():
0037   formula = request.json['formula']
0038   return verify_math_formula(formula, '$')
0039 
0040 @app.route('/v1/math-verifier/block', methods = ['POST'])
0041 def verify_math_formula_block():
0042   formula = request.json['formula']
0043   return verify_math_formula(formula, '$$')
0044 
0045 @app.route('/v1/convert', methods = ['POST'])
0046 def convert():
0047   payload = request.json
0048   job_uuid = uuid.uuid4()
0049   conversion.queue(payload, job_id=str(job_uuid))
0050   return jsonify(job_id=job_uuid)
0051 
0052 @app.route('/v1/jobs/<uuid:job_uuid>', methods = ['GET'])
0053 def job_status(job_uuid):
0054   job = FlaskJob.fetch(str(job_uuid), rq.connection)
0055   if job.is_finished:
0056     result = {'status': 'finished', 'pdf_id': job.result}
0057   elif job.is_queued:
0058     result = {'status': 'queued'}
0059   elif job.is_started:
0060     result = {'status': 'started'}
0061   elif job.is_failed:
0062     result = {'status': 'failed'}
0063   app.logger.debug(result)
0064   return jsonify(result)
0065 
0066 @app.route('/v1/pdf/<path:pdf_id>', methods = ['GET'])
0067 def download(pdf_id):
0068   filepath = '/data/{}/'.format(pdf_id)
0069   for file in os.listdir(filepath):
0070     if file.endswith('.pdf'):
0071         filename = file
0072   app.logger.debug('{}{}'.format(filepath, filename))
0073   return send_from_directory(filepath, filename, as_attachment=True, attachment_filename=filename)
0074 
0075 @rq.job
0076 def conversion(payload):
0077   id = payload['_id']
0078   version = payload['_version']
0079   filepath = '/data/{}_{}'.format(id, version)
0080   return create_document(payload, filepath)
0081 
0082 def create_document(payload, filepath):
0083   doc = CustomReport()
0084 
0085   doc.preamble.append(Command('setdefaultlanguage', arguments=Arguments(languages[payload['language']])))
0086   doc.preamble.append(Command('title', payload['title']))
0087   doc.preamble.append(Command('author', 'WikiToLearn contributors'))
0088   doc.preamble.append(Command('date', NoEscape(r'\today')))
0089   doc.append(NoEscape(r'\maketitle'))
0090   doc.append(NoEscape(r'\tableofcontents'))
0091   doc.append(NewPage())
0092 
0093   for chapter in payload['chapters']:
0094     with doc.create(Chapter(chapter['title'])):
0095         for page in chapter['pages']:
0096           with doc.create(Section(page['title'])):
0097             converted_text = pypandoc.convert_text(page['text'], to='latex', format='html')
0098             # FIXME: pandoc uses some custom commands which are not recognized
0099             converted_text = converted_text.replace(r'\tightlist', r'')
0100             converted_text = converted_text.replace(r'\toprule', r'')
0101             converted_text = converted_text.replace(r'\midrule', r'')
0102             converted_text = converted_text.replace(r'\bottomrule', r'')
0103             doc.append(NoEscape(converted_text))
0104 
0105   doc.append(NoEscape(r'\begin{appendix}'))
0106   doc.append(NoEscape(r'\listoffigures'))
0107   doc.append(NoEscape(r'\listoftables'))
0108   doc.append(NoEscape(r'\end{appendix}'))
0109 
0110   app.logger.debug(doc.dumps())
0111   os.makedirs(filepath, exist_ok=True)
0112   complete_filepath ='{}/{}'.format(filepath, payload['title'])
0113   doc.generate_pdf(filepath=complete_filepath, compiler='xelatex', clean=True, clean_tex=True, compiler_args=[])
0114   return filepath
0115 
0116 def verify_math_formula(formula, decorator):
0117   os.makedirs('/data/math', exist_ok=True)
0118   id = uuid.uuid4()
0119   filepath = '/data/math/{}'.format(id)
0120 
0121   doc = CustomReport()
0122   full_formula = decorator + formula + decorator
0123   doc.append(NoEscape(full_formula))
0124   app.logger.debug(doc.dumps())
0125 
0126   try:
0127     doc.generate_pdf(filepath=filepath, compiler='xelatex', clean=True, clean_tex=True, compiler_args=[])
0128     os.remove('{}.pdf'.format(filepath))
0129     return jsonify(success=True)
0130   except Exception as e:
0131     return abort(make_response(jsonify(success=False, error=str(e)), 400))
0132 
0133 class CustomReport(Document):
0134   def __init__(self):
0135     super().__init__(
0136       fontenc=None,
0137       inputenc=None,
0138       documentclass='report',
0139       document_options=['11pt', 'onecolumn', 'oneside', 'fleqn', 'a4paper'],
0140       geometry_options=['a4paper', 'top=3cm', 'bottom=3cm', 'left=3.5cm', 'right=3.5cm', 'heightrounded', 'bindingoffset=5mm']
0141     )
0142     self.preamble.append(Package('polyglossia'))
0143     self.preamble.append(Package('hyperref'))
0144     self.preamble.append(Package('longtable'))
0145     self.preamble.append(Package('amsmath'))
0146     self.preamble.append(Package('amsthm'))
0147     self.preamble.append(Package('amstext'))
0148     self.preamble.append(Package('amssymb'))
0149     self.preamble.append(Package('amsfonts'))
0150     self.preamble.append(Command('setlength', arguments=Arguments(NoEscape(r'\mathindent'), '0pt')))
0151     self.preamble.append(Package('listings'))
0152     self.preamble.append(Package('fancyvrb'))
0153     self.preamble.append(Package('booktabs'))
0154     self.preamble.append(Package('graphicx'))
0155     self.preamble.append(Package('grffile'))
0156     self.preamble.append(Package('xcolor', options=Options('usenames', 'table')))
0157     self.preamble.append(Package('ulem'))
0158     # It is used by Pandoc but breaks xelatex with polyglossia
0159     # self.preamble.append(Package('csquotes'))
0160     self.preamble.append(Package('fontspec'))
0161     self.preamble.append(Package('xunicode'))
0162     self.preamble.append(Package('xltxtra'))
0163     self.preamble.append(Package('anyfontsize'))
0164     self.preamble.append(Package('tabularx'))
0165     self.preamble.append(Package('float'))
0166     self.preamble.append(Package('cancel'))
0167     self.preamble.append(Package('tikz'))
0168     self.preamble.append(Package('enumitem'))
0169     self.preamble.append(Package('datetime', options=Options('nodayofweek')))