# Copyright (C) 2011-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
# (only), as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see
"""
renderer = self._detect_renderer(source, filename)
readme_data = renderer(source)
return readme_data
@classmethod
def urlify_text(cls, text):
def url_func(match_obj):
url_full = match_obj.groups()[0]
return f'{url_full}'
return cls.URL_PAT.sub(url_func, text)
@classmethod
def convert_mentions(cls, text, mode):
mention_pat = cls.MENTION_PAT
def wrapp(match_obj):
uname = match_obj.groups()[0]
hovercard_url = "pyroutes.url('hovercard_username', {'username': '%s'});" % uname
if mode == 'markdown':
tmpl = '@{uname}'
elif mode == 'rst':
tmpl = ' **@{uname}** '
else:
raise ValueError('mode must be rst or markdown')
return tmpl.format(**{'uname': uname,
'hovercard_url': hovercard_url})
return mention_pat.sub(wrapp, text).strip()
@classmethod
def plain(cls, source, universal_newline=True, leading_newline=True):
source = safe_str(source)
if universal_newline:
newline = '\n'
source = newline.join(source.splitlines())
rendered_source = cls.urlify_text(source)
source = ''
if leading_newline:
source += '
'
source += rendered_source.replace("\n", '
')
rendered = cls.sanitize_html(source)
return rendered
@classmethod
def markdown(cls, source, safe=True, flavored=True, mentions=False,
clean_html=True):
"""
returns markdown rendered code cleaned by the bleach library
"""
if flavored:
markdown_renderer = get_markdown_renderer_flavored(
cls.extensions, cls.output_format)
else:
markdown_renderer = get_markdown_renderer(
cls.extensions, cls.output_format)
if mentions:
mention_hl = cls.convert_mentions(source, mode='markdown')
# we extracted mentions render with this using Mentions false
return cls.markdown(mention_hl, safe=safe, flavored=flavored,
mentions=False)
try:
rendered = markdown_renderer.convert(source)
except Exception:
log.exception('Error when rendering Markdown')
if safe:
log.debug('Fallback to render in plain mode')
rendered = cls.plain(source)
else:
raise
if clean_html:
rendered = cls.sanitize_html(rendered)
return rendered
@classmethod
def rst(cls, source, safe=True, mentions=False, clean_html=False):
if mentions:
mention_hl = cls.convert_mentions(source, mode='rst')
# we extracted mentions render with this using Mentions false
return cls.rst(mention_hl, safe=safe, mentions=False)
source = safe_str(source)
try:
docutils_settings = dict(
[(alias, None) for alias in
cls.RESTRUCTUREDTEXT_DISALLOWED_DIRECTIVES])
docutils_settings.update({
'input_encoding': 'unicode',
'report_level': 4,
'syntax_highlight': 'short',
})
for k, v in list(docutils_settings.items()):
directives.register_directive(k, v)
parts = publish_parts(source=source,
writer=RhodeCodeWriter(),
settings_overrides=docutils_settings)
rendered = parts["fragment"]
if clean_html:
rendered = cls.sanitize_html(rendered)
return parts['html_title'] + rendered
except Exception:
log.exception('Error when rendering RST')
if safe:
log.debug('Fallback to render in plain mode')
return cls.plain(source)
else:
raise
@classmethod
def jupyter(cls, source, safe=True):
from rhodecode.lib import helpers
from .html_sanitizer_defs import markdown_attrs, all_tags, all_styles
from traitlets import default, config
import nbformat
from nbconvert import HTMLExporter
from nbconvert.preprocessors import Preprocessor
from nbconvert.preprocessors.sanitize import SanitizeHTML
class CustomHTMLExporter(HTMLExporter):
@default("template_file")
def _template_file_default(self):
if self.template_extension:
return "basic/index" + self.template_extension
class Sandbox(Preprocessor):
def preprocess_cell(self, cell, resources, cell_index):
if not safe:
return cell, resources
sandbox_text = 'SandBoxed(IPython.core.display.Javascript object)'
if cell.cell_type == "markdown":
cell.source = cls.sanitize_html(cell.source)
return cell, resources
for cell_output in cell.get('outputs', []):
if 'data' in cell_output:
if 'application/javascript' in cell_output['data']:
cell_output['data']['text/plain'] = sandbox_text
cell_output['data'].pop('application/javascript', None)
return cell, resources
def _sanitize_resources(input_resources):
"""
Skip/sanitize some of the CSS generated and included in jupyter
so it doesn't mess up UI so much
"""
# TODO(marcink): probably we should replace this with whole custom
# CSS set that doesn't screw up, but jupyter generated html has some
# special markers, so it requires Custom HTML exporter template with
# _default_template_path_default, to achieve that
# strip the reset CSS
input_resources[0] = input_resources[0][input_resources[0].find('/*! Source'):]
return input_resources
def as_html(notebook):
conf = config.Config()
# TODO: Keep an eye on the order of preprocessors
conf.CustomHTMLExporter.default_preprocessors = [Sandbox, SanitizeHTML]
conf.Sandbox.enabled = True
conf.SanitizeHTML.enabled = True
conf.SanitizeHTML.attributes = markdown_attrs
conf.SanitizeHTML.tags = all_tags
conf.SanitizeHTML.styles = all_styles
conf.SanitizeHTML.sanitized_output_types = {
"text/html",
"text/markdown",
}
conf.SanitizeHTML.safe_output_keys = {
"metadata",
"text/plain",
"text/latex",
"application/json",
"image/png",
"image/jpg"
"image/jpeg",
"image/svg",
"image/svg+xml"
}
html_exporter = CustomHTMLExporter(config=conf)
(body, resources) = html_exporter.from_notebook_node(notebook)
header = ''
js = MakoTemplate(r'''
''').render(h=helpers)
css = MakoTemplate(r'''
''').render(h=helpers, ver='ver1')
body = '\n'.join([header, css, js, body])
return body, resources
captured_errors = {}
error_body = """
{}