diff --git a/modules/purchase_trade/__init__.py b/modules/purchase_trade/__init__.py
index 007af58..fa4e8ee 100755
--- a/modules/purchase_trade/__init__.py
+++ b/modules/purchase_trade/__init__.py
@@ -34,6 +34,7 @@ from . import (
backtoback,
service,
invoice,
+ process_documentation,
routes,
)
@@ -60,6 +61,7 @@ def register():
lc.LCMessage,
lc.CreateLCStart,
global_reporting.GRConfiguration,
+ process_documentation.ProcessDocumentation,
pricing.ImportPricesStart,
pricing.ImportPricesResult,
module='purchase_trade', type_='model')
diff --git a/modules/purchase_trade/process_documentation.py b/modules/purchase_trade/process_documentation.py
new file mode 100644
index 0000000..cafde12
--- /dev/null
+++ b/modules/purchase_trade/process_documentation.py
@@ -0,0 +1,238 @@
+# 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 json
+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')
+
+ def get_content(self, name):
+ pages = _documentation_pages()
+ return HTML_TEMPLATE % {
+ 'initial': pages.get('README.md', ''),
+ 'pages': json.dumps(pages).replace('', '<\\/'),
+ }
+
+
+def _documentation_pages():
+ pages = {}
+ for path in sorted(DOCUMENTATION_ROOT.rglob('*.md')):
+ relative_name = _relative_name(path)
+ markdown = path.read_text(encoding='utf-8')
+ pages[relative_name] = _markdown_to_html(markdown, relative_name)
+ return pages
+
+
+def _relative_name(path):
+ return path.relative_to(DOCUMENTATION_ROOT.resolve()).as_posix()
+
+
+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('' % (
+ html.escape(source, quote=True), label))
+ else:
+ parts.append(label)
+ elif resolved.endswith('.md'):
+ parts.append('%s' % (
+ html.escape(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
' % _inline(stripped, current_file)) + + flush_table() + close_list() + + if current_file != 'README.md': + body.insert( + 0, '') + return '\n'.join(body) + + +def _render_table(lines, current_file): + 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 = ['| %s | ' % _inline(cell, current_file)) + output.append('
|---|
| %s | ' % _inline(cell, current_file)) + output.append('