# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import html import mimetypes import os import posixpath import re from pathlib import Path from urllib.parse import quote, unquote from trytond.protocols.wrappers import ( HTTPStatus, Response, abort, send_file, with_pool, with_transaction) from trytond.wsgi import app DOCUMENTATION_ROOT = Path(__file__).with_name('process_documentation') MARKDOWN_LINK = re.compile(r'(!?)\[([^\]]*)\]\(([^)]+)\)') @app.route('//purchase_trade/processes', methods={'GET'}) @app.route( '//purchase_trade/processes/', methods={'GET'}) @app.auth_required @with_pool @with_transaction(user='request', context=dict(_check_access=True)) def process_documentation(request, pool, filename='README.md'): target = _documentation_path(filename) if not target.exists() or not target.is_file(): abort(HTTPStatus.NOT_FOUND) if target.suffix.lower() == '.md': relative_name = _relative_name(target) markdown = target.read_text(encoding='utf-8') content = _markdown_to_html(markdown, relative_name, pool.database_name) return Response(content, content_type='text/html; charset=utf-8') mimetype = mimetypes.guess_type(str(target))[0] return send_file( str(target), request.environ, mimetype=mimetype, download_name=target.name) def _documentation_path(filename): filename = unquote(filename or 'README.md').replace('\\', '/') filename = posixpath.normpath(filename).lstrip('/') if filename in {'.', ''}: filename = 'README.md' if filename.startswith('../'): abort(HTTPStatus.NOT_FOUND) root = DOCUMENTATION_ROOT.resolve() target = (root / Path(*filename.split('/'))).resolve() common_path = os.path.commonpath([str(root), str(target)]) if target != root and common_path != str(root): abort(HTTPStatus.NOT_FOUND) return target def _relative_name(path): return path.relative_to(DOCUMENTATION_ROOT.resolve()).as_posix() def _documentation_url(database_name, filename): filename = posixpath.normpath(filename).lstrip('/') return '/%s/purchase_trade/processes/%s' % ( quote(database_name), quote(filename, safe='/')) def _resolve_link(current_file, target): if target.startswith(('#', 'http://', 'https://', 'mailto:')): return target current_dir = posixpath.dirname(current_file) return posixpath.normpath(posixpath.join(current_dir, target)).lstrip('/') def _inline(markdown, current_file, database_name): parts = [] last = 0 for match in MARKDOWN_LINK.finditer(markdown): parts.append(html.escape(markdown[last:match.start()])) is_image, label, target = match.groups() resolved = _resolve_link(current_file, target) url = target if resolved == target and target.startswith( ('#', 'http://', 'https://', 'mailto:')) else _documentation_url( database_name, resolved) label = html.escape(label) url = html.escape(url, quote=True) if is_image: parts.append('%s' % (url, label)) else: parts.append('%s' % (url, label)) last = match.end() parts.append(html.escape(markdown[last:])) return ''.join(parts) def _markdown_to_html(markdown, current_file, database_name): body = [] table = [] in_list = False def close_list(): nonlocal in_list if in_list: body.append('') in_list = False def flush_table(): nonlocal table if table: close_list() body.append(_render_table(table, current_file, database_name)) table = [] for line in markdown.splitlines(): stripped = line.strip() if stripped.startswith('|') and stripped.endswith('|'): table.append(stripped) continue flush_table() if not stripped: close_list() continue if stripped.startswith('#'): close_list() level = len(stripped) - len(stripped.lstrip('#')) level = min(level, 6) title = stripped[level:].strip() body.append('%s' % ( level, _inline(title, current_file, database_name), level)) elif stripped.startswith('- '): if not in_list: body.append('
    ') in_list = True body.append('
  • %s
  • ' % _inline( stripped[2:].strip(), current_file, database_name)) else: close_list() body.append('

    %s

    ' % _inline( stripped, current_file, database_name)) flush_table() close_list() back_link = '' if current_file != 'README.md': back_link = '

    Back to index

    ' % html.escape( _documentation_url(database_name, 'README.md'), quote=True) return HTML_TEMPLATE % { 'title': html.escape('Tradon Processes'), 'back_link': back_link, 'body': '\n'.join(body), } def _render_table(lines, current_file, database_name): rows = [ [cell.strip() for cell in line.strip('|').split('|')] for line in lines ] header = rows[0] if rows else [] data = rows[1:] if data and all(set(cell) <= {'-', ':'} for cell in data[0]): data = data[1:] output = ['', ''] for cell in header: output.append('' % _inline( cell, current_file, database_name)) output.append('') for row in data: output.append('') for cell in row: output.append('' % _inline( cell, current_file, database_name)) output.append('') output.append('
    %s
    %s
    ') return ''.join(output) HTML_TEMPLATE = ''' %(title)s
    %(back_link)s %(body)s
    '''