diff --git a/script/src/tikz_diagrams/tikz_diagram.py b/script/src/diagrams/diagram.py similarity index 72% rename from script/src/tikz_diagrams/tikz_diagram.py rename to script/src/diagrams/diagram.py index d1f770b0..3d699d96 100755 --- a/script/src/tikz_diagrams/tikz_diagram.py +++ b/script/src/diagrams/diagram.py @@ -1,6 +1,7 @@ #!/usr/bin/python3.7 import argparse +import enum import errno import find_block import glob @@ -15,7 +16,7 @@ """ Initialises logging. Logs to -tikz_diagrams.log +diagrams.log """ logger = logging.getLogger(__name__) logger.setLevel(logging.INFO) @@ -24,7 +25,7 @@ "%(asctime)s %(levelname)s %(name)s %(message)s") log_directory = os.environ["NLAB_LOG_DIRECTORY"] logging_file_handler = logging.FileHandler( - os.path.join(log_directory, "tikz_diagrams.log")) + os.path.join(log_directory, "diagrams.log")) logging_file_handler.setFormatter(logging_formatter) logger.addHandler(logging_file_handler) @@ -36,8 +37,12 @@ class SvgRenderingException(Exception): def __init__(self, message): super().__init__(message) +class DiagramType(enum.Enum): + TIKZ = "tikz" + XYPIC = "xypic" + def create_diagram_source_directory_if_needed(): - diagram_source_directory = os.environ["NLAB_TIKZ_DIAGRAM_SOURCE_DIRECTORY"] + diagram_source_directory = os.environ["NLAB_DIAGRAM_SOURCE_DIRECTORY"] try: os.mkdir(diagram_source_directory) except OSError as osError: @@ -75,24 +80,42 @@ def extract_error(output): break return "\n".join(error_description) -def create_pdf( - diagram_source_directory, - diagram_id, - tikz_diagram, - is_commutative_diagram): +def tikz_tex_source(diagram, is_commutative_diagram): tikz_diagram_template_path = os.environ["NLAB_TIKZ_DIAGRAM_TEMPLATE"] with open(tikz_diagram_template_path, "r") as tikz_diagram_template_file: tikz_diagram_template = tikz_diagram_template_file.read() if not is_commutative_diagram: libraries, tikz_diagram_without_tikz_libraries = extract_tikz_libraries( - tikz_diagram) - tex_source = string.Template(tikz_diagram_template).substitute( + diagram) + return string.Template(tikz_diagram_template).substitute( tikz_libraries = libraries, tikz_diagram = tikz_diagram_without_tikz_libraries) else: - tex_source = string.Template(tikz_diagram_template).substitute( + return string.Template(tikz_diagram_template).substitute( tikz_libraries = "\\usetikzlibrary{cd}", - tikz_diagram = tikz_diagram) + tikz_diagram = diagram) + +def xypic_tex_source(diagram): + xypic_diagram_template_path = os.environ["NLAB_XYPIC_DIAGRAM_TEMPLATE"] + with open(xypic_diagram_template_path, "r") as xypic_diagram_template_file: + xypic_diagram_template = xypic_diagram_template_file.read() + return string.Template(xypic_diagram_template).substitute( + xypic_diagram = diagram) + +def create_pdf( + diagram_source_directory, + diagram_id, + diagram, + diagram_type, + is_commutative_diagram = True): + if diagram_type == DiagramType.TIKZ: + tex_source = tikz_tex_source(diagram, is_commutative_diagram) + elif diagram_type == DiagramType.XYPIC: + tex_source = xypic_tex_source(diagram) + else: + raise ValueError( + "Following diagram type not handled: " + + diagram_type.value) tex_path = os.path.join(diagram_source_directory, diagram_id + ".tex") with open(tex_path, "w") as tex_source_file: tex_source_file.write(tex_source) @@ -128,8 +151,15 @@ def remove_diagram_files(diagram_source_directory, diagram_id): def argument_parser(): parser = argparse.ArgumentParser( description = ( - "Creates SVG source from tikz diagram code passed into stdin")) - parser.add_argument( + "Creates SVG source from diagram code passed into stdin")) + subparsers = parser.add_subparsers(dest = "subcommand") + parser_tikz = subparsers.add_parser( + "tikz", + help = "Tikz diagrams") + parser_xypic = subparsers.add_parser( + "xypic", + help = "XYPic diagrams") + parser_tikz.add_argument( "-c", "--commutative_diagram", action = "store_true", @@ -139,22 +169,30 @@ def argument_parser(): def main(): parser = argument_parser() arguments = parser.parse_args() - is_commutative_diagram = arguments.commutative_diagram - tikz_diagram = sys.stdin.read().strip() + diagram_type = DiagramType(arguments.subcommand) + diagram = sys.stdin.read().strip() diagram_source_directory = create_diagram_source_directory_if_needed() diagram_id = str(random.randint(10**8, (10**9) - 1)) try: - create_pdf( - diagram_source_directory, - diagram_id, - tikz_diagram, - is_commutative_diagram) - svg_diagram = create_svg(diagram_source_directory, diagram_id) + if diagram_type == DiagramType.TIKZ: + create_pdf( + diagram_source_directory, + diagram_id, + diagram, + diagram_type, + arguments.commutative_diagram) + else: + create_pdf( + diagram_source_directory, + diagram_id, + diagram, + diagram_type) + svg_diagram = create_svg(diagram_source_directory, diagram_id) except PdfRenderingException as pdfRenderingException: message = ( "An error occurred when running pdflatex on the following " + "diagram. \n" + - tikz_diagram + + diagram + "\nThe error was: " + str(pdfRenderingException)) logger.warning(message) @@ -163,17 +201,18 @@ def main(): message = ( "An error occurred when creating an SVG from a PDF for the " + "following diagram. \n" + - tikz_diagram) + diagram) logger.warning( message + "\nThe error was: " + str(svgRenderingException)) sys.exit(message) except Exception as exception: + raise exception message = ( "An unexpected error occurred when creating an SVG from a PDF " + "for the following diagram. \n" + - tikz_diagram) + diagram) logger.warning( message + "\nThe error was: " + @@ -183,7 +222,7 @@ def main(): remove_diagram_files(diagram_source_directory, diagram_id) logger.info( "Successfully created an SVG for the following diagram. \n" + - tikz_diagram) + diagram) print(svg_diagram) if __name__ == "__main__": diff --git a/script/src/tikz_diagrams/find_block.py b/script/src/diagrams/find_block.py similarity index 100% rename from script/src/tikz_diagrams/find_block.py rename to script/src/diagrams/find_block.py diff --git a/script/src/tikz_diagrams/tikz_diagram_template b/script/src/diagrams/tikz_diagram_template similarity index 100% rename from script/src/tikz_diagrams/tikz_diagram_template rename to script/src/diagrams/tikz_diagram_template diff --git a/script/src/diagrams/xypic_diagram_template b/script/src/diagrams/xypic_diagram_template new file mode 100644 index 00000000..e97ef416 --- /dev/null +++ b/script/src/diagrams/xypic_diagram_template @@ -0,0 +1,9 @@ +\documentclass[12pt]{standalone} + +\usepackage[all]{xy} + +\begin{document} + +$xypic_diagram + +\end{document} diff --git a/script/src/renderer/renderer.py b/script/src/renderer/renderer.py index 54ba9e8b..af31e4d2 100755 --- a/script/src/renderer/renderer.py +++ b/script/src/renderer/renderer.py @@ -26,6 +26,7 @@ import theorem_environment_blocks import tikz_diagram_block import time +import xypic_diagram_block """ Initialises logging. Logs to @@ -239,7 +240,8 @@ def _write_to_file_for_maruku(page_id, content_for_rendering): """ def render(page_id, page_content): page_content = centre_block.handle_initial_centring(page_content) - page_content = tikz_diagram_block.handle_commutative_diagrams(page_content) + page_content = tikz_diagram_block.handle_tikz_diagrams(page_content) + page_content = xypic_diagram_block.handle_xypic_diagrams(page_content) pages_to_re_render_and_expire = [] pages_to_render_to_include = [] references_before_rendering = _references_before_rendering(page_id) @@ -344,6 +346,8 @@ def initial_rendering(page_id, page_content): error_message = str(invalid_tex_exception) except tikz_diagram_block.TikzDiagramException as tikz_diagram_exception: error_message = str(tikz_diagram_exception) + except xypic_diagram_block.XyPicDiagramException as xypic_diagram_exception: + error_message = str(xypic_diagram_exception) except Exception as exception: error_message = "An unexpected error occurred when rendering the page" error = exception diff --git a/script/src/renderer/tikz_diagram_block.py b/script/src/renderer/tikz_diagram_block.py index fbfee3eb..063b5cdd 100644 --- a/script/src/renderer/tikz_diagram_block.py +++ b/script/src/renderer/tikz_diagram_block.py @@ -17,9 +17,9 @@ def tikz_diagram_processor(tikz_diagram): "\\begin{tikzpicture}" + tikz_diagram + "\\end{tikzpicture}") - tikz_diagram_api_path = os.environ["NLAB_TIKZ_DIAGRAM_API_PATH"] + diagram_api_path = os.environ["NLAB_DIAGRAM_API_PATH"] completed_tikz_diagram_process = subprocess.run( - [ tikz_diagram_api_path ], + [ diagram_api_path, "tikz" ], input = tikz_diagram, text = True, capture_output = True) @@ -40,9 +40,9 @@ def tikz_commutative_diagram_processor(tikz_diagram): "[row sep=huge, column sep=huge, transform shape, nodes = {scale=1.25}]" + tikz_diagram + "\\end{tikzcd}") - tikz_diagram_api_path = os.environ["NLAB_TIKZ_DIAGRAM_API_PATH"] + diagram_api_path = os.environ["NLAB_DIAGRAM_API_PATH"] completed_tikz_diagram_process = subprocess.run( - [ tikz_diagram_api_path, "-c" ], + [ diagram_api_path, "tikz", "-c" ], input = tikz_diagram, text = True, capture_output = True) @@ -65,7 +65,7 @@ def define_tikz_commutative_diagram(): tikz_commutative_diagram_processor, True) -def handle_commutative_diagrams(content): +def handle_tikz_diagrams(content): processor = find_block.Processor( [ define_tikz(), define_tikz_commutative_diagram() ]) return processor.process(content) diff --git a/script/src/renderer/xypic_diagram_block.py b/script/src/renderer/xypic_diagram_block.py new file mode 100644 index 00000000..ebcfd442 --- /dev/null +++ b/script/src/renderer/xypic_diagram_block.py @@ -0,0 +1,50 @@ +#!/usr/bin/python3 + +""" +For creating SVG code from a tikz diagram +""" + +import find_block +import os +import subprocess + +class XyPicDiagramException(Exception): + def __init__(self, message): + super().__init__(message) + +def xypic_diagram_processor(xypic_diagram): + xypic_diagram = ( + "\\xymatrix@" + + xypic_diagram + + "}") + diagram_api_path = os.environ["NLAB_DIAGRAM_API_PATH"] + completed_xypic_diagram_process = subprocess.run( + [ diagram_api_path, "xypic" ], + input = xypic_diagram, + text = True, + capture_output = True) + if completed_xypic_diagram_process.returncode != 0: + raise XyPicDiagramException( + completed_xypic_diagram_process.stderr) + return completed_xypic_diagram_process.stdout + +def define_xymatrix_default_size(): + return find_block.Block( + "\\xymatrix{", + "}", + lambda diagram: xypic_diagram_processor( + "=5em{" + diagram), + True) + +def define_xymatrix(): + return find_block.Block( + "\\xymatrix@", + "}", + xypic_diagram_processor, + True) + +def handle_xypic_diagrams(content): + processor = find_block.Processor( + [ define_xymatrix(), define_xymatrix_default_size() ]) + return processor.process(content) +