# 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 base64 import html import mimetypes import os import posixpath import re from pathlib import Path from urllib.parse import unquote from trytond.model import ModelSingleton, ModelSQL, ModelView, fields DOCUMENTATION_ROOT = Path(__file__).with_name('process_documentation') MARKDOWN_LINK = re.compile(r'(!?)\[([^\]]*)\]\(([^)]+)\)') class ProcessDocumentation(ModelSingleton, ModelSQL, ModelView): "Process Documentation" __name__ = 'purchase_trade.process.documentation' content = fields.Function(fields.Text('Content'), 'get_content') @staticmethod def default_content(): return _html_content() def get_content(self, name): return _html_content() def _html_content(): return HTML_TEMPLATE % { 'content': _documentation_content(), } def _documentation_content(): sections = [] index = DOCUMENTATION_ROOT / 'README.md' if index.exists(): sections.append(_render_page(index)) for path in sorted(DOCUMENTATION_ROOT.rglob('*.md')): if path == index: continue sections.append(_render_page(path)) return '\n'.join(sections) def _render_page(path): relative_name = _relative_name(path) markdown = path.read_text(encoding='utf-8') return '
%s
' % ( _anchor(relative_name), _markdown_to_html(markdown, relative_name)) def _relative_name(path): return path.relative_to(DOCUMENTATION_ROOT.resolve()).as_posix() def _anchor(filename): return 'doc-' + re.sub(r'[^a-zA-Z0-9_-]+', '-', filename) def _resolve_link(current_file, target): if target.startswith(('#', 'http://', 'https://', 'mailto:')): return target target = unquote(target).replace('\\', '/') current_dir = posixpath.dirname(current_file) return posixpath.normpath(posixpath.join(current_dir, target)).lstrip('/') def _asset_data_uri(filename): path = (DOCUMENTATION_ROOT / Path(*filename.split('/'))).resolve() root = DOCUMENTATION_ROOT.resolve() common_path = os.path.commonpath([str(root), str(path)]) if path != root and common_path == str(root): if path.exists() and path.is_file(): mimetype = mimetypes.guess_type(str(path))[0] mimetype = mimetype or 'application/octet-stream' data = base64.b64encode(path.read_bytes()).decode('ascii') return 'data:%s;base64,%s' % (mimetype, data) def _inline(markdown, current_file): 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) label = html.escape(label) if is_image: source = _asset_data_uri(resolved) if source: parts.append('%s' % ( html.escape(source, quote=True), label)) else: parts.append(label) elif resolved.endswith('.md'): parts.append('%s' % ( html.escape(_anchor(resolved), quote=True), label)) elif resolved.startswith(('#', 'http://', 'https://', 'mailto:')): parts.append('%s' % ( html.escape(resolved, quote=True), label)) else: parts.append(html.escape(match.group(0))) last = match.end() parts.append(html.escape(markdown[last:])) return ''.join(parts) def _markdown_to_html(markdown, current_file): 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)) 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), level)) elif stripped.startswith('- '): if not in_list: body.append('