def render_syntax_error(project: 'projects.Project', code: str, error: SyntaxError) -> dict: """ :param project: :param code: :param error: :return: """ stack = [ dict(filename=error.filename, location=None, line_number=error.lineno, line=error.text.rstrip()) ] render_data = dict(type=error.__class__.__name__, message='{}'.format(error), stack=stack) return dict(success=False, error=error, message=templating.render_template('user-code-error.txt', **render_data), html_message=templating.render_template( 'user-code-error.html', **render_data))
def render_error(project: 'projects.Project', error: Exception, stack: typing.List[dict] = None) -> dict: """ Renders an Exception to an error response that includes rendered text and html error messages for display. :param project: Currently open project. :param error: The SyntaxError to be rendered to html and text for display. :param stack: Optionally specify a parsed stack. If this value is None the standard Cauldron stack frames will be rendered. :return: A dictionary containing the error response with rendered display messages for both text and html output. """ data = dict(type=error.__class__.__name__, message='{}'.format(error), stack=(stack if stack is not None else render_stack.get_formatted_stack_frame(project))) return dict(success=False, error=error, message=templating.render_template('user-code-error.txt', **data), html_message=templating.render_template( 'user-code-error.html', **data))
def plotly( data: list = None, layout: dict = None, scale: float = 0.5, figure: dict = None, static: bool = False ) -> str: """ :param data: :param layout: :param scale: :param figure: :param static: :return: """ environ.abort_thread() try: import plotly as plotly_lib except ImportError: plotly_lib = None if plotly_lib is None: return templating.render_template( template_name='import-error.html', library_name='Plotly' ) source = figure if figure else {'data': data, 'layout': layout} dom = plotly_lib.offline.plot( figure_or_data=source, output_type='div', include_plotlyjs=False ) found = re.search(r'id="(?P<id>[^"]+)"', dom) dom_id = found.group('id') insert_index = dom.index('"showLink":') dom = ''.join([ dom[:insert_index], '"staticPlot": {}, '.format('true' if static else 'false'), dom[insert_index:] ]) return templating.render_template( 'plotly-component.html', dom=dom, scale=scale, min_height=round(100.0 * scale), id=dom_id )
def bokeh_plot(model, scale: float = 0.7, responsive: bool = True) -> str: """ :param model: :param scale: :param responsive: :return: """ environ.abort_thread() try: from bokeh import embed except Exception: embed = None if responsive: model.sizing_mode = "scale_width" # model.responsive = True model.plot_width = 800 model.plot_height = round((scale * 9 / 16) * 800) results = embed.components(model) script = results[0] \ .split('>', 1)[1] \ .rsplit('</', 1)[0] return templating.render_template( 'bokeh_component.html', script=script, dom=results[1], scale=round(100 * scale) if scale is not None else 1000)
def list_grid( source: list, expand_full: bool = False, column_count: int = 2, row_spacing: float = 1 ): """ :param source: :param expand_full: :param column_count: :param row_spacing: :return: """ environ.abort_thread() max_width = 1400 if expand_full else 900 column_width = '{}px'.format( max(50, int(math.floor(max_width / column_count))) ) return templating.render_template( 'list_grid.html', items=['{}'.format(x) for x in source], css_modifier='full' if expand_full else 'limited', column_width=column_width, row_spacing=row_spacing )
def code_block( block: str = None, path: str = None, language: str = None, title: str = None, caption: str = None ) -> str: """ :param block: :param path: :param language: :param title: :param caption: :return: """ environ.abort_thread() code_dom = ( code_file(path, language=language, is_code_block=True) if path else code(block, language=language, is_code_block=True) ) return templating.render_template( 'code-block.html', code=code_dom, title=title, caption=caption )
def run( project: 'projects.Project', step: 'projects.ProjectStep' ) -> dict: """ Runs the markdown file and renders the contents to the notebook display :param project: :param step: :return: A run response dictionary containing """ with open(step.source_path, 'r') as f: code = f.read() try: cauldron.display.markdown(code, **project.shared.fetch(None)) return {'success': True} except Exception as err: return dict( success=False, html_message=templating.render_template( 'markdown-error.html', error=err ) )
def create(project: 'projects.Project', destination_directory, destination_filename: str = None) -> file_io.FILE_WRITE_ENTRY: """ Creates a FILE_WRITE_ENTRY for the rendered HTML file for the given project that will be saved in the destination directory with the given filename. :param project: The project for which the rendered HTML file will be created :param destination_directory: The absolute path to the folder where the HTML file will be saved :param destination_filename: The name of the HTML file to be written in the destination directory. Defaults to the project uuid. :return: A FILE_WRITE_ENTRY for the project's HTML file output """ template_path = environ.paths.resources('web', 'project.html') with open(template_path, 'r') as f: dom = f.read() dom = dom.replace( '<!-- CAULDRON:EXPORT -->', templating.render_template('notebook-script-header.html', uuid=project.uuid, version=environ.version)) filename = (destination_filename if destination_filename else '{}.html'.format(project.uuid)) html_out_path = os.path.join(destination_directory, filename) return file_io.FILE_WRITE_ENTRY(path=html_out_path, contents=dom)
def render_intro(): return '\n{}\n'.format( templating.render_template( 'shell_introduction.txt', version=package_data['version'] ) )
def render_stop_display(step: 'projects.ProjectStep', message: str): """Renders a stop action to the Cauldron display.""" stack = render_stack.get_formatted_stack_frame( project=step.project, error_stack=False ) try: names = [frame['filename'] for frame in stack] index = names.index(os.path.realpath(__file__)) frame = stack[index - 1] except Exception: frame = {} stop_message = ( '{}'.format(message) if message else 'This step was explicitly stopped prior to its completion' ) dom = templating.render_template( 'step-stop.html', message=stop_message, frame=frame ) step.report.append_body(dom)
def table(data_frame, scale: float = 0.7, include_index: bool = False, max_rows: int = 500) -> str: """ :param data_frame: :param scale: :param include_index: :param max_rows: :return: """ environ.abort_thread() table_id = 'table-{}-{}'.format(datetime.utcnow().strftime('%H-%M-%S-%f'), random.randint(0, 1e8)) df_source = (data_frame.head(max_rows) if len(data_frame) > max_rows else data_frame) if include_index: df_source = df_source.reset_index() column_headers = ['"{}"'.format(x) for x in df_source.columns.tolist()] data = df_source.values.tolist() json_data = json_internal.dumps(data, cls=encoding.ComplexJsonEncoder) return templating.render_template('table.html', id=table_id, scale=min(0.95, max(0.05, scale)), data=json_data, column_headers=', '.join(column_headers))
def plotly(data: list = None, layout: dict = None, scale: float = 0.5, figure: dict = None, static: bool = False) -> str: """ :param data: :param layout: :param scale: :param figure: :param static: :return: """ environ.abort_thread() try: import plotly as plotly_lib except ImportError: plotly_lib = None if plotly_lib is None: return templating.render_template(template_name='import-error.html', library_name='Plotly') source = figure if figure else {'data': data, 'layout': layout} dom = plotly_lib.offline.plot(figure_or_data=source, output_type='div', include_plotlyjs=False) found = re.search(r'id="(?P<id>[^"]+)"', dom) dom_id = found.group('id') insert_index = dom.index('"showLink":') dom = ''.join([ dom[:insert_index], '"staticPlot": {}, '.format('true' if static else 'false'), dom[insert_index:] ]) return templating.render_template('plotly-component.html', dom=dom, scale=scale, min_height=round(100.0 * scale), id=dom_id)
def test_render_template(self): """ :return: """ result = templating.render_template('unit_test.html', value='hello') self.assertEqual(result, 'hello')
def dumps(self) -> str: """Writes the step information to an HTML-formatted string""" code_file_path = os.path.join(self.project.source_directory, self.filename) code = dict(filename=self.filename, path=code_file_path, code=render.code_file(code_file_path)) if not self.is_running: # If no longer running, make sure to flush the stdout buffer so # any print statements at the end of the step get included in # the body self.report.flush_stdout() # Create a copy of the body for dumping body = self.report.body[:] if self.is_running: # If still running add a temporary copy of anything not flushed # from the stdout buffer to the copy of the body for display. Do # not flush the buffer though until the step is done running or # it gets flushed by another display call. body.append(self.report.read_stdout()) body = ''.join(body) has_body = len(body) > 0 and ( body.find('<div') != -1 or body.find('<span') != -1 or body.find('<p') != -1 or body.find('<pre') != -1 or body.find('<h') != -1 or body.find('<ol') != -1 or body.find('<ul') != -1 or body.find('<li') != -1) std_err = (self.report.read_stderr() if self.is_running else self.report.flush_stderr()).strip('\n').rstrip() dom = templating.render_template( 'step-body.html', last_display_update=self.report.last_update_time, elapsed_time=self.get_elapsed_timestamp(), code=code, body=body, has_body=has_body, id=self.definition.name, title=self.report.title, subtitle=self.report.subtitle, summary=self.report.summary, error=self.error, index=self.index, is_running=self.is_running, progress_message=self.progress_message, progress=int(round(max(0, min(100, 100 * self.progress)))), sub_progress_message=self.sub_progress_message, sub_progress=int(round(max(0, min(100, 100 * self.sub_progress)))), std_err=std_err) if not self.is_running: self.dom = dom return dom
def render_error(project: 'projects.Project', error: Exception) -> dict: """ :param project: :param error: :return: """ render_data = dict( type=error.__class__.__name__, message='{}'.format(error), stack=[format_stack_frame(f, project) for f in get_stack_frames()]) return dict(success=False, error=error, message=templating.render_template('user-code-error.txt', **render_data), html_message=templating.render_template( 'user-code-error.html', **render_data))
def elapsed_time(seconds: float) -> str: """Displays the elapsed time since the current step started running.""" environ.abort_thread() parts = ('{}'.format(timedelta(seconds=seconds)).rsplit('.', 1)) hours, minutes, seconds = parts[0].split(':') return templating.render_template( 'elapsed_time.html', hours=hours.zfill(2), minutes=minutes.zfill(2), seconds=seconds.zfill(2), microseconds=parts[-1] if len(parts) > 1 else '')
def latex(source: str, inline: bool = False) -> str: """ :param source: :param inline: :return: """ environ.abort_thread() return templating.render_template('katex.html', source=render_utils.format_latex(source), inline=inline)
def json(**kwargs) -> str: """ :param kwargs: :return: """ environ.abort_thread() return templating.render_template( 'json_include.html', data=json_internal.dumps(kwargs, cls=encoding.ComplexJsonEncoder) )
def status( data: dict, values: bool = True, types: bool = True ) -> str: """ :param data: :param values: :param types: :return: """ environ.abort_thread() out = [] keys = list(data.keys()) keys.sort() for key in keys: value = data[key] value_type = getattr( value, '__class__', {'__name__': 'Unknown'} ).__name__ if hasattr(value, 'head'): try: value = value.head(5) except Exception: pass elif isinstance(value, dict): temp_value = [] for k, v in value.items(): temp_value.append('{}: {}'.format(k, v)) value = '\n'.join(temp_value) elif isinstance(value, (list, tuple)): value = '\n'.join(['{}'.format(v) for v in value]) value = '<pre>{}</pre>'.format( render_utils.html_escape('{}'.format(value))[:600] ) out.append(templating.render_template( 'status-variable.template.html', name=key, values=values, types=types, type=value_type, value=value )) return ''.join(out)
def json(**kwargs) -> str: """ :param kwargs: :return: """ environ.abort_thread() return templating.render_template('json_include.html', data=json_internal.dumps( kwargs, cls=encoding.ComplexJsonEncoder))
def latex(source: str, inline: bool = False) -> str: """ :param source: :param inline: :return: """ environ.abort_thread() return templating.render_template( 'katex.html', source=render_utils.format_latex(source), inline=inline )
def render_error( project: 'projects.Project', error: Exception, stack: typing.List[dict] = None ) -> dict: """ Renders an Exception to an error response that includes rendered text and html error messages for display. :param project: Currently open project. :param error: The SyntaxError to be rendered to html and text for display. :param stack: Optionally specify a parsed stack. If this value is None the standard Cauldron stack frames will be rendered. :return: A dictionary containing the error response with rendered display messages for both text and html output. """ data = dict( type=error.__class__.__name__, message='{}'.format(error), stack=( stack if stack is not None else render_stack.get_formatted_stack_frame(project) ) ) return dict( success=False, error=error, message=templating.render_template('user-code-error.txt', **data), html_message=templating.render_template('user-code-error.html', **data) )
def elapsed_time(seconds: float) -> str: """Displays the elapsed time since the current step started running.""" environ.abort_thread() parts = ( '{}'.format(timedelta(seconds=seconds)) .rsplit('.', 1) ) hours, minutes, seconds = parts[0].split(':') return templating.render_template( 'elapsed_time.html', hours=hours.zfill(2), minutes=minutes.zfill(2), seconds=seconds.zfill(2), microseconds=parts[-1] if len(parts) > 1 else '' )
def load_step_file(source_path: str) -> str: """ Loads the source for a step file at the given path location and then renders it in a template to add additional footer data. The footer is used to force the display to flush the print buffer and breathe the step to open things up for resolution. This shouldn't be necessary, but it seems there's an async race condition with print buffers that is hard to reproduce and so this is in place to fix the problem. """ return templating.render_template( template_name='embedded-step.py.txt', source_contents=get_file_contents(source_path))
def image( rendered_path: str, width: int = None, height: int = None, justify: str = None ) -> str: """Renders an image block""" environ.abort_thread() return templating.render_template( 'image.html', path=rendered_path, width=width, height=height, justification=(justify or 'left').lower() )
def table( data_frame, scale: float = 0.7, include_index: bool = False, max_rows: int = 500 ) -> str: """ :param data_frame: :param scale: :param include_index: :param max_rows: :return: """ environ.abort_thread() table_id = 'table-{}-{}'.format( datetime.utcnow().strftime('%H-%M-%S-%f'), random.randint(0, 1e8) ) df_source = ( data_frame.head(max_rows) if len(data_frame) > max_rows else data_frame ) if include_index: df_source = df_source.reset_index() try: column_headers = ['"{}"'.format(x) for x in df_source.columns.tolist()] data = df_source.values.tolist() except AttributeError: # If no columns are found assume that it is a Series instead of a # DataFrame and reformat the data to match the expected structure. column_headers = ['"{}"'.format(df_source.name)] data = df_source.values.reshape([len(df_source), 1]).tolist() json_data = json_internal.dumps(data, cls=encoding.ComplexJsonEncoder) return templating.render_template( 'table.html', id=table_id, scale=min(0.95, max(0.05, scale)), data=json_data, column_headers=', '.join(column_headers) )
def load_step_file(source_path: str) -> str: """ Loads the source for a step file at the given path location and then renders it in a template to add additional footer data. The footer is used to force the display to flush the print buffer and breathe the step to open things up for resolution. This shouldn't be necessary, but it seems there's an async race condition with print buffers that is hard to reproduce and so this is in place to fix the problem. """ return templating.render_template( template_name='embedded-step.py.txt', source_contents=get_file_contents(source_path) )
def listing(source: list, ordered: bool = False, expand_full: bool = False) -> str: """ :param source: :param ordered: :param expand_full: :return: """ environ.abort_thread() return templating.render_template( 'listing.html', type='ol' if ordered else 'ul', items=['{}'.format(x) for x in source], css_modifier='full' if expand_full else 'limited')
def create( project: 'projects.Project', destination_directory, destination_filename: str = None ) -> file_io.FILE_WRITE_ENTRY: """ Creates a FILE_WRITE_ENTRY for the rendered HTML file for the given project that will be saved in the destination directory with the given filename. :param project: The project for which the rendered HTML file will be created :param destination_directory: The absolute path to the folder where the HTML file will be saved :param destination_filename: The name of the HTML file to be written in the destination directory. Defaults to the project uuid. :return: A FILE_WRITE_ENTRY for the project's HTML file output """ template_path = environ.paths.resources('web', 'project.html') with open(template_path, 'r') as f: dom = f.read() dom = dom.replace( '<!-- CAULDRON:EXPORT -->', templating.render_template( 'notebook-script-header.html', uuid=project.uuid, version=environ.version ) ) filename = ( destination_filename if destination_filename else '{}.html'.format(project.uuid) ) html_out_path = os.path.join(destination_directory, filename) return file_io.FILE_WRITE_ENTRY( path=html_out_path, contents=dom )
def status(data: dict, values: bool = True, types: bool = True) -> str: """ :param data: :param values: :param types: :return: """ environ.abort_thread() out = [] keys = list(data.keys()) keys.sort() for key in keys: value = data[key] value_type = getattr(value, '__class__', { '__name__': 'Unknown' }).__name__ if hasattr(value, 'head'): try: value = value.head(5) except Exception: pass elif isinstance(value, dict): temp_value = [] for k, v in value.items(): temp_value.append('{}: {}'.format(k, v)) value = '\n'.join(temp_value) elif isinstance(value, (list, tuple)): value = '\n'.join(['{}'.format(v) for v in value]) value = '<pre>{}</pre>'.format( render_utils.html_escape('{}'.format(value))[:600]) out.append( templating.render_template('status-variable.template.html', name=key, values=values, types=types, type=value_type, value=value)) return ''.join(out)
def to_write_list(project: 'projects.Project') -> typing.List[tuple]: """ :param project: :return: """ project_component = components.project_component.create_many( project, project.settings.fetch('web_includes', []) ) steps_data = [step_writer.serialize(s) for s in project.steps] file_writes = [item for sd in steps_data for item in sd.file_writes] file_writes.extend(project_component.files) def to_step_dict(step: step_writer.STEP_DATA) -> dict: out = step._asdict() del out['file_writes'] return out project_includes = [inc._asdict() for inc in project_component.includes] file_writes.append(file_io.FILE_WRITE_ENTRY( path=project.output_path, contents=templating.render_template( 'report.js.template', DATA=json.dumps({ 'steps': [to_step_dict(sd) for sd in steps_data], 'includes': project_includes, 'settings': project.settings.fetch(None), 'cauldron_version': list(environ.version_info) }) ) )) file_writes.extend(list_asset_writes(project)) file_writes.append(html.create( project, project.results_path, 'display.html' )) return file_writes
def render_stop_display(step: 'projects.ProjectStep', message: str): """Renders a stop action to the Cauldron display.""" stack = render_stack.get_formatted_stack_frame(project=step.project, error_stack=False) try: names = [frame['filename'] for frame in stack] index = names.index(os.path.realpath(__file__)) frame = stack[index - 1] except Exception: frame = {} stop_message = ('{}'.format(message) if message else 'This step was explicitly stopped prior to its completion') dom = templating.render_template('step-stop.html', message=stop_message, frame=frame) step.report.append_body(dom)
def listing( source: list, ordered: bool = False, expand_full: bool = False ) -> str: """ :param source: :param ordered: :param expand_full: :return: """ environ.abort_thread() return templating.render_template( 'listing.html', type='ol' if ordered else 'ul', items=['{}'.format(x) for x in source], css_modifier='full' if expand_full else 'limited' )
def run(project: 'projects.Project', step: 'projects.ProjectStep') -> dict: """ Runs the markdown file and renders the contents to the notebook display :param project: :param step: :return: A run response dictionary containing """ with open(step.source_path, 'r') as f: code = f.read() try: cauldron.display.markdown(code, **project.shared.fetch(None)) return {'success': True} except Exception as err: return dict(success=False, html_message=templating.render_template( 'markdown-error.html', error=err))
def to_write_list(project: 'projects.Project') -> typing.List[tuple]: """ :param project: :return: """ project_component = components.project_component.create_many( project, project.settings.fetch('web_includes', [])) steps_data = [step_writer.serialize(s) for s in project.steps] file_writes = [item for sd in steps_data for item in sd.file_writes] file_writes.extend(project_component.files) def to_step_dict(step: step_writer.STEP_DATA) -> dict: out = step._asdict() del out['file_writes'] return out project_includes = [inc._asdict() for inc in project_component.includes] file_writes.append( file_io.FILE_WRITE_ENTRY( path=project.output_path, contents=templating.render_template( 'report.js.template', DATA=json.dumps({ 'steps': [to_step_dict(sd) for sd in steps_data], 'includes': project_includes, 'settings': project.settings.fetch(None), 'cauldron_version': list(environ.version_info) })))) file_writes.extend(list_asset_writes(project)) file_writes.append( html.create(project, project.results_path, 'display.html')) return file_writes
def stop(self, message: str = None, silent: bool = False): """ Stops the execution of the current step immediately without raising an error. Use this to abort the step running process if you want to return early. :param message: A custom display message to include in the display for the stop action. This message is ignored if silent is set to True. :param silent: When True nothing will be shown in the notebook display when the step is stopped. When False, the notebook display will include information relating to the stopped action. """ step = self._step if not step: return if not silent: stack = render_stack.get_formatted_stack_frame( project=step.project, error_stack=False) try: names = [frame['filename'] for frame in stack] index = names.index(os.path.realpath(__file__)) frame = stack[index - 1] except Exception: frame = {} stop_message = ( '{}'.format(message) if message else 'This step was explicitly stopped prior to its completion') dom = templating.render_template('step-stop.html', message=stop_message, frame=frame) step.report.append_body(dom) raise UserAbortError()
def table(data_frame, scale: float = 0.7, include_index: bool = False, max_rows: int = 500) -> str: """ :param data_frame: :param scale: :param include_index: :param max_rows: :return: """ environ.abort_thread() table_id = 'table-{}-{}'.format(datetime.utcnow().strftime('%H-%M-%S-%f'), random.randint(0, 1e8)) df_source = (data_frame.head(max_rows) if len(data_frame) > max_rows else data_frame) if include_index: df_source = df_source.reset_index() try: column_headers = ['"{}"'.format(x) for x in df_source.columns.tolist()] data = df_source.values.tolist() except AttributeError: # If no columns are found assume that it is a Series instead of a # DataFrame and reformat the data to match the expected structure. column_headers = ['"{}"'.format(df_source.name)] data = df_source.values.reshape([len(df_source), 1]).tolist() json_data = json_internal.dumps(data, cls=encoding.ComplexJsonEncoder) return templating.render_template('table.html', id=table_id, scale=min(0.95, max(0.05, scale)), data=json_data, column_headers=', '.join(column_headers))
def code_block(block: str = None, path: str = None, language: str = None, title: str = None, caption: str = None) -> str: """ :param block: :param path: :param language: :param title: :param caption: :return: """ code_dom = (code_file(path, language=language, is_code_block=True) if path else code(block, language=language, is_code_block=True)) return templating.render_template('code-block.html', code=code_dom, title=title, caption=caption)
def render_tree(inspected_data: dict): """ :param inspected_data: :return: """ environ.abort_thread() def to_jstree_node(d: dict) -> dict: children = d.get('structure', []) if isinstance(children, (list, tuple)): children = [to_jstree_node(x) for x in children] else: children = [] return dict(text='{} ({})'.format(d['key'], d['type']), children=children) data = [to_jstree_node(v) for v in inspected_data['structure']] return templating.render_template('tree.html', data=data)
def bokeh_plot( model, scale: float = 0.7, responsive: bool = True ) -> str: """ :param model: :param scale: :param responsive: :return: """ environ.abort_thread() try: from bokeh import embed except Exception: embed = None if responsive: model.sizing_mode = "scale_width" # model.responsive = True model.plot_width = 800 model.plot_height = round((scale * 9 / 16) * 800) results = embed.components(model) script = results[0] \ .split('>', 1)[1] \ .rsplit('</', 1)[0] return templating.render_template( 'bokeh_component.html', script=script, dom=results[1], scale=round(100 * scale) if scale is not None else 1000 )
def list_grid(source: list, expand_full: bool = False, column_count: int = 2, row_spacing: float = 1): """ :param source: :param expand_full: :param column_count: :param row_spacing: :return: """ environ.abort_thread() max_width = 1400 if expand_full else 900 column_width = '{}px'.format( max(50, int(math.floor(max_width / column_count)))) return templating.render_template( 'list_grid.html', items=['{}'.format(x) for x in source], css_modifier='full' if expand_full else 'limited', column_width=column_width, row_spacing=row_spacing)
def render_tree(inspected_data: dict): """ :param inspected_data: :return: """ environ.abort_thread() def to_jstree_node(d: dict) -> dict: children = d.get('structure', []) if isinstance(children, (list, tuple)): children = [to_jstree_node(x) for x in children] else: children = [] return dict( text='{} ({})'.format(d['key'], d['type']), children=children ) data = [to_jstree_node(v) for v in inspected_data['structure']] return templating.render_template('tree.html', data=data)
def markdown(source: str = None, source_path: str = None, preserve_lines: bool = False, font_size: float = None, **kwargs) -> dict: """ Renders a markdown file with support for Jinja2 templating. Any keyword arguments will be passed to Jinja2 for templating prior to rendering the markdown to HTML for display within the notebook. :param source: A string of markdown text that should be rendered to HTML for notebook display. :param source_path: The path to a markdown file that should be rendered to HTML for notebook display. :param preserve_lines: If True, all line breaks will be treated as hard breaks. Use this for pre-formatted markdown text where newlines should be retained during rendering. :param font_size: Specifies a relative font size adjustment. The default value is 1.0, which preserves the inherited font size values. Set it to a value below 1.0 for smaller font-size rendering and greater than 1.0 for larger font size rendering. :return: The HTML results of rendering the specified markdown string or file. """ environ.abort_thread() library_includes = [] rendered = textwrap.dedent( templating.render_file(source_path, **kwargs) if source_path else templating.render(source or '', **kwargs)) if md is None: raise ImportError('Unable to import the markdown package') offset = 0 while offset < len(rendered): bound_chars = '$$' start_index = rendered.find(bound_chars, offset) if start_index < 0: break inline = rendered[start_index + 2] != '$' bound_chars = '$$' if inline else '$$$' end_index = rendered.find(bound_chars, start_index + len(bound_chars)) if end_index < 0: break end_index += len(bound_chars) chunk = rendered[start_index: end_index] \ .strip('$') \ .strip() \ .replace('@', '\\') if inline: chunk = chunk.replace('\\', '\\\\') chunk = latex(chunk, inline) rendered = '{pre}{gap}{latex}{gap}{post}'.format( pre=rendered[:start_index], latex=chunk, post=rendered[end_index:], gap='' if inline else '\n\n') if 'katex' not in library_includes: library_includes.append('katex') offset = end_index extensions = [ 'markdown.extensions.extra', 'markdown.extensions.admonition', 'markdown.extensions.sane_lists', 'markdown.extensions.nl2br' if preserve_lines else None ] body = templating.render_template( 'markdown-block.html', text=md.markdown(rendered, extensions=[e for e in extensions if e is not None]), font_size=font_size) pattern = re.compile('src="(?P<url>[^"]+)"') body = pattern.sub('data-src="\g<url>"', body) return dict(body=body, library_includes=library_includes, rendered=rendered)
def markdown( source: str = None, source_path: str = None, preserve_lines: bool = False, font_size: float = None, **kwargs ) -> dict: """ Renders a markdown file with support for Jinja2 templating. Any keyword arguments will be passed to Jinja2 for templating prior to rendering the markdown to HTML for display within the notebook. :param source: A string of markdown text that should be rendered to HTML for notebook display. :param source_path: The path to a markdown file that should be rendered to HTML for notebook display. :param preserve_lines: If True, all line breaks will be treated as hard breaks. Use this for pre-formatted markdown text where newlines should be retained during rendering. :param font_size: Specifies a relative font size adjustment. The default value is 1.0, which preserves the inherited font size values. Set it to a value below 1.0 for smaller font-size rendering and greater than 1.0 for larger font size rendering. :return: The HTML results of rendering the specified markdown string or file. """ environ.abort_thread() library_includes = [] rendered = textwrap.dedent( templating.render_file(source_path, **kwargs) if source_path else templating.render(source or '', **kwargs) ) if md is None: raise ImportError('Unable to import the markdown package') offset = 0 while offset < len(rendered): bound_chars = '$$' start_index = rendered.find(bound_chars, offset) if start_index < 0: break inline = rendered[start_index + 2] != '$' bound_chars = '$$' if inline else '$$$' end_index = rendered.find( bound_chars, start_index + len(bound_chars) ) if end_index < 0: break end_index += len(bound_chars) chunk = rendered[start_index: end_index] \ .strip('$') \ .strip() \ .replace('@', '\\') if inline: chunk = chunk.replace('\\', '\\\\') chunk = latex(chunk, inline) rendered = '{pre}{gap}{latex}{gap}{post}'.format( pre=rendered[:start_index], latex=chunk, post=rendered[end_index:], gap='' if inline else '\n\n' ) if 'katex' not in library_includes: library_includes.append('katex') offset = end_index extensions = [ 'markdown.extensions.extra', 'markdown.extensions.admonition', 'markdown.extensions.sane_lists', 'markdown.extensions.nl2br' if preserve_lines else None ] body = templating.render_template( 'markdown-block.html', text=md.markdown(rendered, extensions=[ e for e in extensions if e is not None ]), font_size=font_size ) pattern = re.compile('src="(?P<url>[^"]+)"') body = pattern.sub(r'data-src="\g<url>"', body) return dict( body=body, library_includes=library_includes, rendered=rendered )
def render_intro(): return '\n{}\n'.format( templating.render_template('shell_introduction.txt', version=package_data['version']))
def dumps(self) -> str: """Writes the step information to an HTML-formatted string""" code_file_path = os.path.join( self.project.source_directory, self.filename ) code = dict( filename=self.filename, path=code_file_path, code=render.code_file(code_file_path) ) if not self.is_running: # If no longer running, make sure to flush the stdout buffer so # any print statements at the end of the step get included in # the body self.report.flush_stdout() # Create a copy of the body for dumping body = self.report.body[:] if self.is_running: # If still running add a temporary copy of anything not flushed # from the stdout buffer to the copy of the body for display. Do # not flush the buffer though until the step is done running or # it gets flushed by another display call. body.append(self.report.read_stdout()) body = ''.join(body) has_body = len(body) > 0 and ( body.find('<div') != -1 or body.find('<span') != -1 or body.find('<p') != -1 or body.find('<pre') != -1 or body.find('<h') != -1 or body.find('<ol') != -1 or body.find('<ul') != -1 or body.find('<li') != -1 ) std_err = ( self.report.read_stderr() if self.is_running else self.report.flush_stderr() ).strip('\n').rstrip() # The step will be visible in the display if any of the following # conditions are true. is_visible = self.is_visible or self.is_running or self.error dom = templating.render_template( 'step-body.html', last_display_update=self.report.last_update_time, elapsed_time=self.get_elapsed_timestamp(), code=code, body=body, has_body=has_body, id=self.definition.name, title=self.report.title, subtitle=self.report.subtitle, summary=self.report.summary, error=self.error, index=self.index, is_running=self.is_running, is_visible=is_visible, progress_message=self.progress_message, progress=int(round(max(0, min(100, 100 * self.progress)))), sub_progress_message=self.sub_progress_message, sub_progress=int(round(max(0, min(100, 100 * self.sub_progress)))), std_err=std_err ) if not self.is_running: self.dom = dom return dom