diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d539aa2e..fe576b0d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -22,11 +22,11 @@ jobs: python -m pip install --upgrade pip pip install . - name: Create Examples - run: PYTHONPATH=$(pwd)/src:$PYTHONPATH cd src/wireviz/ && python build_examples.py + run: PYTHONPATH=$(pwd)/src:$PYTHONPATH && python src/wireviz/tools/build_examples.py - name: Upload examples, demos, and tutorials uses: actions/upload-artifact@v2 with: name: examples-and-tutorials path: | examples/ - tutorial/ \ No newline at end of file + tutorial/ diff --git a/src/wireviz/svgembed.py b/src/wireviz/svgembed.py deleted file mode 100644 index ab6b9f1e..00000000 --- a/src/wireviz/svgembed.py +++ /dev/null @@ -1,52 +0,0 @@ -# -*- coding: utf-8 -*- - -import base64 -import re -from pathlib import Path -from typing import Union - -mime_subtype_replacements = {"jpg": "jpeg", "tif": "tiff"} - - -def embed_svg_images(svg_in: str, base_path: Union[str, Path] = Path.cwd()) -> str: - images_b64 = {} # cache of base64-encoded images - - def image_tag(pre: str, url: str, post: str) -> str: - return f'' - - def replace(match: re.Match) -> str: - imgurl = match["URL"] - if not imgurl in images_b64: # only encode/cache every unique URL once - imgurl_abs = (Path(base_path) / imgurl).resolve() - image = imgurl_abs.read_bytes() - images_b64[imgurl] = base64.b64encode(image).decode("utf-8") - return image_tag( - match["PRE"] or "", - f"data:image/{get_mime_subtype(imgurl)};base64, {images_b64[imgurl]}", - match["POST"] or "", - ) - - pattern = re.compile( - image_tag(r"(?P
 [^>]*?)?", r'(?P[^"]*?)', r"(?P [^>]*?)?"),
-        re.IGNORECASE,
-    )
-    return pattern.sub(replace, svg_in)
-
-
-def get_mime_subtype(filename: Union[str, Path]) -> str:
-    mime_subtype = Path(filename).suffix.lstrip(".").lower()
-    if mime_subtype in mime_subtype_replacements:
-        mime_subtype = mime_subtype_replacements[mime_subtype]
-    return mime_subtype
-
-
-def embed_svg_images_file(
-    filename_in: Union[str, Path], overwrite: bool = True
-) -> None:
-    filename_in = Path(filename_in).resolve()
-    filename_out = filename_in.with_suffix(".b64.svg")
-    filename_out.write_text(
-        embed_svg_images(filename_in.read_text(), filename_in.parent)
-    )
-    if overwrite:
-        filename_out.replace(filename_in)
diff --git a/src/wireviz/build_examples.py b/src/wireviz/tools/build_examples.py
similarity index 96%
rename from src/wireviz/build_examples.py
rename to src/wireviz/tools/build_examples.py
index e54d0f5c..5ce9fe42 100755
--- a/src/wireviz/build_examples.py
+++ b/src/wireviz/tools/build_examples.py
@@ -7,13 +7,12 @@
 from pathlib import Path
 
 script_path = Path(__file__).absolute()
-
-sys.path.insert(0, str(script_path.parent.parent))  # to find wireviz module
-from wv_helper import open_file_append, open_file_read, open_file_write
+sys.path.insert(0, str(script_path.parent.parent.parent))  # to find wireviz module
 
 from wireviz import APP_NAME, __version__, wireviz
+from wireviz.wv_utils import open_file_append, open_file_read, open_file_write
 
-dir = script_path.parent.parent.parent
+dir = script_path.parent.parent.parent.parent
 readme = "readme.md"
 groups = {
     "examples": {
diff --git a/src/wireviz/wireviz.py b/src/wireviz/wireviz.py
index f36b12bf..4dd9b091 100755
--- a/src/wireviz/wireviz.py
+++ b/src/wireviz/wireviz.py
@@ -10,9 +10,9 @@
 if __name__ == "__main__":
     sys.path.insert(0, str(Path(__file__).parent.parent))  # add src/wireviz to PATH
 
-from wireviz.DataClasses import AUTOGENERATED_PREFIX, Metadata, Options, Tweak
-from wireviz.Harness import Harness
-from wireviz.wv_helper import (
+from wireviz.wv_dataclasses import AUTOGENERATED_PREFIX, Metadata, Options, Tweak
+from wireviz.wv_harness import Harness
+from wireviz.wv_utils import (
     expand,
     get_single_key_and_value,
     is_arrow,
@@ -396,19 +396,12 @@ def alternate_type():  # flip between connector and cable/arrow
 def _get_yaml_data_and_path(inp: Union[str, Path, Dict]) -> (Dict, Path):
     # determine whether inp is a file path, a YAML string, or a Dict
     if not isinstance(inp, Dict):  # received a str or a Path
-        try:
+        if isinstance(inp, Path) or (isinstance(inp, str) and not "\n" in inp):
             yaml_path = Path(inp).expanduser().resolve(strict=True)
-            # if no FileNotFoundError exception happens, get file contents
             yaml_str = open_file_read(yaml_path).read()
-        except (FileNotFoundError, OSError) as e:
-            # if inp is a long YAML string, Pathlib will raise OSError: [Errno 63]
-            # when trying to expand and resolve it as a path.
-            # Catch this error, but raise any others
-            if type(e) is OSError and e.errno != 63:
-                raise e
-            # file does not exist; assume inp is a YAML string
-            yaml_str = inp
+        else:
             yaml_path = None
+            yaml_str = inp
         yaml_data = yaml.safe_load(yaml_str)
     else:
         # received a Dict, use as-is
diff --git a/src/wireviz/wv_bom.py b/src/wireviz/wv_bom.py
index 9cc0fbed..0cb7a3ea 100644
--- a/src/wireviz/wv_bom.py
+++ b/src/wireviz/wv_bom.py
@@ -5,7 +5,7 @@
 from enum import Enum
 from typing import List, Optional, Union
 
-from wireviz.wv_helper import html_line_breaks
+from wireviz.wv_utils import html_line_breaks
 
 BOM_HASH_FIELDS = "description unit partnumbers"
 BomHash = namedtuple("BomHash", BOM_HASH_FIELDS)
diff --git a/src/wireviz/wv_cli.py b/src/wireviz/wv_cli.py
index 07aac12a..eb0f33c7 100644
--- a/src/wireviz/wv_cli.py
+++ b/src/wireviz/wv_cli.py
@@ -11,7 +11,7 @@
 
 import wireviz.wireviz as wv
 from wireviz import APP_NAME, __version__
-from wireviz.wv_helper import open_file_read
+from wireviz.wv_utils import open_file_read
 
 format_codes = {
     "c": "csv",
@@ -23,10 +23,11 @@
     "t": "tsv",
 }
 
+
 epilog = (
     "The -f or --format option accepts a string containing one or more of the "
     "following characters to specify which file types to output:\n"
-    f", ".join([f"{key} ({value.upper()})" for key, value in format_codes.items()])
+    + f", ".join([f"{key} ({value.upper()})" for key, value in format_codes.items()])
 )
 
 
diff --git a/src/wireviz/DataClasses.py b/src/wireviz/wv_dataclasses.py
similarity index 99%
rename from src/wireviz/DataClasses.py
rename to src/wireviz/wv_dataclasses.py
index 70a20790..9afaaf07 100644
--- a/src/wireviz/DataClasses.py
+++ b/src/wireviz/wv_dataclasses.py
@@ -14,7 +14,7 @@
     SingleColor,
     get_color_by_colorcode_index,
 )
-from wireviz.wv_helper import aspect_ratio, awg_equiv, mm2_equiv, remove_links
+from wireviz.wv_utils import aspect_ratio, awg_equiv, mm2_equiv, remove_links
 
 # Each type alias have their legal values described in comments
 # - validation might be implemented in the future
diff --git a/src/wireviz/wv_gv_html.py b/src/wireviz/wv_graphviz.py
similarity index 99%
rename from src/wireviz/wv_gv_html.py
rename to src/wireviz/wv_graphviz.py
index 7d63ca97..e65ed2ae 100644
--- a/src/wireviz/wv_gv_html.py
+++ b/src/wireviz/wv_graphviz.py
@@ -5,7 +5,8 @@
 from typing import Any, List, Union
 
 from wireviz import APP_NAME, APP_URL, __version__
-from wireviz.DataClasses import (
+from wireviz.wv_bom import partnumbers_to_list
+from wireviz.wv_dataclasses import (
     ArrowDirection,
     ArrowWeight,
     Cable,
@@ -18,9 +19,8 @@
     ShieldClass,
     WireClass,
 )
-from wireviz.wv_bom import partnumbers_to_list
-from wireviz.wv_helper import html_line_breaks, remove_links
-from wireviz.wv_table_util import *  # TODO: explicitly import each needed tag later
+from wireviz.wv_html import Img, Table, Td, Tr
+from wireviz.wv_utils import html_line_breaks, remove_links
 
 
 def gv_node_component(component: Component) -> Table:
diff --git a/src/wireviz/Harness.py b/src/wireviz/wv_harness.py
similarity index 98%
rename from src/wireviz/Harness.py
rename to src/wireviz/wv_harness.py
index 1d722e79..7c4143e6 100644
--- a/src/wireviz/Harness.py
+++ b/src/wireviz/wv_harness.py
@@ -8,7 +8,7 @@
 from graphviz import Graph
 
 import wireviz.wv_colors
-from wireviz.DataClasses import (
+from wireviz.wv_dataclasses import (
     AUTOGENERATED_PREFIX,
     AdditionalComponent,
     Arrow,
@@ -23,8 +23,7 @@
     Side,
     Tweak,
 )
-from wireviz.svgembed import embed_svg_images_file
-from wireviz.wv_gv_html import (
+from wireviz.wv_graphviz import (
     apply_dot_tweaks,
     calculate_node_bgcolor,
     gv_connector_loops,
@@ -34,8 +33,8 @@
     parse_arrow_str,
     set_dot_basics,
 )
-from wireviz.wv_helper import open_file_write, tuplelist2tsv
-from wireviz.wv_html import generate_html_output
+from wireviz.wv_output import embed_svg_images_file, generate_html_output
+from wireviz.wv_utils import open_file_write, tuplelist2tsv
 
 
 @dataclass
diff --git a/src/wireviz/wv_html.py b/src/wireviz/wv_html.py
index 16a5d585..1c4b750e 100644
--- a/src/wireviz/wv_html.py
+++ b/src/wireviz/wv_html.py
@@ -1,120 +1,125 @@
 # -*- coding: utf-8 -*-
 
-import re
-from pathlib import Path
-from typing import Dict, List, Union
-
-from wireviz import APP_NAME, APP_URL, __version__, wv_colors
-from wireviz.DataClasses import Metadata, Options
-from wireviz.wv_helper import (
-    flatten2d,
-    html_line_breaks,
-    open_file_read,
-    open_file_write,
-    smart_file_resolve,
-)
-
-
-def generate_html_output(
-    filename: Union[str, Path],
-    bom_list: List[List[str]],
-    metadata: Metadata,
-    options: Options,
-):
-
-    # load HTML template
-    templatename = metadata.get("template", {}).get("name")
-    if templatename:
-        # if relative path to template was provided,
-        # check directory of YAML file first, fall back to built-in template directory
-        templatefile = smart_file_resolve(
-            f"{templatename}.html",
-            [Path(filename).parent, Path(__file__).parent / "templates"],
-        )
-    else:
-        # fall back to built-in simple template if no template was provided
-        templatefile = Path(__file__).parent / "templates/simple.html"
-
-    html = open_file_read(templatefile).read()
-
-    # embed SVG diagram
-    with open_file_read(f"{filename}.tmp.svg") as file:
-        svgdata = re.sub(
-            "^<[?]xml [^?>]*[?]>[^<]*]*>",
-            "",
-            file.read(),
-            1,
-        )
-
-    # generate BOM table
-    bom = flatten2d(bom_list)
-
-    # generate BOM header (may be at the top or bottom of the table)
-    bom_header_html = "  \n"
-    for item in bom[0]:
-        th_class = f"bom_col_{item.lower()}"
-        bom_header_html = f'{bom_header_html}    {item}\n'
-    bom_header_html = f"{bom_header_html}  \n"
-
-    # generate BOM contents
-    bom_contents = []
-    for row in bom[1:]:
-        row_html = "  \n"
-        for i, item in enumerate(row):
-            td_class = f"bom_col_{bom[0][i].lower()}"
-            row_html = f'{row_html}    {item}\n'
-        row_html = f"{row_html}  \n"
-        bom_contents.append(row_html)
-
-    bom_html = (
-        '\n' + bom_header_html + "".join(bom_contents) + "
\n" - ) - bom_html_reversed = ( - '\n' - + "".join(list(reversed(bom_contents))) - + bom_header_html - + "
\n" - ) - - # prepare simple replacements - replacements = { - "": f"{APP_NAME} {__version__} - {APP_URL}", - "": options.fontname, - "": options.bgcolor.html, - "": svgdata, - "": bom_html, - "": bom_html_reversed, - "": "1", # TODO: handle multi-page documents - "": "1", # TODO: handle multi-page documents - } - - # prepare metadata replacements - if metadata: - for item, contents in metadata.items(): - if isinstance(contents, (str, int, float)): - replacements[f""] = html_line_breaks(str(contents)) - elif isinstance(contents, Dict): # useful for authors, revisions - for index, (category, entry) in enumerate(contents.items()): - if isinstance(entry, Dict): - replacements[f""] = str(category) - for entry_key, entry_value in entry.items(): - replacements[ - f"" - ] = html_line_breaks(str(entry_value)) - - replacements['"sheetsize_default"'] = '"{}"'.format( - metadata.get("template", {}).get("sheetsize", "") - ) - # include quotes so no replacement happens within