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')))