resolves #320 read from stdin and write to stdout
This commit is contained in:
parent
92af90518c
commit
76fa38857f
@ -1,5 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
import sys
|
||||||
from dataclasses import InitVar, dataclass, field
|
from dataclasses import InitVar, dataclass, field
|
||||||
from enum import Enum, auto
|
from enum import Enum, auto
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@ -280,7 +280,7 @@ class Cable:
|
|||||||
self.gauge = g
|
self.gauge = g
|
||||||
|
|
||||||
if self.gauge_unit is not None:
|
if self.gauge_unit is not None:
|
||||||
print(
|
sys.stderr.write(
|
||||||
f"Warning: Cable {self.name} gauge_unit={self.gauge_unit} is ignored because its gauge contains {u}"
|
f"Warning: Cable {self.name} gauge_unit={self.gauge_unit} is ignored because its gauge contains {u}"
|
||||||
)
|
)
|
||||||
if u.upper() == "AWG":
|
if u.upper() == "AWG":
|
||||||
@ -304,7 +304,7 @@ class Cable:
|
|||||||
)
|
)
|
||||||
self.length = L
|
self.length = L
|
||||||
if self.length_unit is not None:
|
if self.length_unit is not None:
|
||||||
print(
|
sys.stderr.write(
|
||||||
f"Warning: Cable {self.name} length_unit={self.length_unit} is ignored because its length contains {u}"
|
f"Warning: Cable {self.name} length_unit={self.length_unit} is ignored because its length contains {u}"
|
||||||
)
|
)
|
||||||
self.length_unit = u
|
self.length_unit = u
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
import sys
|
||||||
from collections import Counter
|
from collections import Counter
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from itertools import zip_longest
|
from itertools import zip_longest
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, List, Union
|
from typing import Any, List, Union, Optional
|
||||||
|
|
||||||
from graphviz import Graph
|
from graphviz import Graph
|
||||||
|
|
||||||
@ -20,13 +21,12 @@ from wireviz.DataClasses import (
|
|||||||
Tweak,
|
Tweak,
|
||||||
Side,
|
Side,
|
||||||
)
|
)
|
||||||
from wireviz.svgembed import embed_svg_images_file
|
from wireviz.svgembed import embed_svg_images
|
||||||
from wireviz.wv_bom import (
|
from wireviz.wv_bom import (
|
||||||
HEADER_MPN,
|
HEADER_MPN,
|
||||||
HEADER_PN,
|
HEADER_PN,
|
||||||
HEADER_SPN,
|
HEADER_SPN,
|
||||||
bom_list,
|
bom_list,
|
||||||
component_table_entry,
|
|
||||||
generate_bom,
|
generate_bom,
|
||||||
get_additional_component_table,
|
get_additional_component_table,
|
||||||
pn_info_string,
|
pn_info_string,
|
||||||
@ -543,11 +543,11 @@ class Harness:
|
|||||||
f'( +)?{attr}=("[^"]*"|[^] ]*)(?(1)| *)', "", entry
|
f'( +)?{attr}=("[^"]*"|[^] ]*)(?(1)| *)', "", entry
|
||||||
)
|
)
|
||||||
if n_subs < 1:
|
if n_subs < 1:
|
||||||
print(
|
sys.stderr.write(
|
||||||
f"Harness.create_graph() warning: {attr} not found in {keyword}!"
|
f"Harness.create_graph() warning: {attr} not found in {keyword}!"
|
||||||
)
|
)
|
||||||
elif n_subs > 1:
|
elif n_subs > 1:
|
||||||
print(
|
sys.stderr.write(
|
||||||
f"Harness.create_graph() warning: {attr} removed {n_subs} times in {keyword}!"
|
f"Harness.create_graph() warning: {attr} removed {n_subs} times in {keyword}!"
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
@ -562,7 +562,7 @@ class Harness:
|
|||||||
# If attr not found, then append it
|
# If attr not found, then append it
|
||||||
entry = re.sub(r"\]$", f" {attr}={value}]", entry)
|
entry = re.sub(r"\]$", f" {attr}={value}]", entry)
|
||||||
elif n_subs > 1:
|
elif n_subs > 1:
|
||||||
print(
|
sys.stderr.write(
|
||||||
f"Harness.create_graph() warning: {attr} overridden {n_subs} times in {keyword}!"
|
f"Harness.create_graph() warning: {attr} overridden {n_subs} times in {keyword}!"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -650,55 +650,61 @@ class Harness:
|
|||||||
graph = self.graph
|
graph = self.graph
|
||||||
return embed_svg_images(graph.pipe(format="svg").decode("utf-8"), Path.cwd())
|
return embed_svg_images(graph.pipe(format="svg").decode("utf-8"), Path.cwd())
|
||||||
|
|
||||||
|
|
||||||
def output(
|
def output(
|
||||||
self,
|
self,
|
||||||
filename: (str, Path),
|
output_dir: Optional[Union[str, Path]],
|
||||||
view: bool = False,
|
output_name: Optional[Union[str, Path]],
|
||||||
cleanup: bool = True,
|
formats: List[str] | tuple[str]
|
||||||
fmt: tuple = ("html", "png", "svg", "tsv"),
|
|
||||||
) -> None:
|
) -> None:
|
||||||
# graphical output
|
# graphical output
|
||||||
graph = self.graph
|
graph = self.graph
|
||||||
svg_already_exists = Path(
|
|
||||||
f"{filename}.svg"
|
if "csv" in formats:
|
||||||
).exists() # if SVG already exists, do not delete later
|
# TODO: implement CSV output (preferably using CSV library)
|
||||||
# graphical output
|
sys.stderr.write("CSV output is not yet supported")
|
||||||
for f in fmt:
|
if "pdf" in formats:
|
||||||
if f in ("png", "svg", "html"):
|
|
||||||
if f == "html": # if HTML format is specified,
|
|
||||||
f = "svg" # generate SVG for embedding into HTML
|
|
||||||
# SVG file will be renamed/deleted later
|
|
||||||
_filename = f"{filename}.tmp" if f == "svg" else filename
|
|
||||||
# TODO: prevent rendering SVG twice when both SVG and HTML are specified
|
|
||||||
graph.format = f
|
|
||||||
graph.render(filename=_filename, view=view, cleanup=cleanup)
|
|
||||||
# embed images into SVG output
|
|
||||||
if "svg" in fmt or "html" in fmt:
|
|
||||||
embed_svg_images_file(f"{filename}.tmp.svg")
|
|
||||||
# GraphViz output
|
|
||||||
if "gv" in fmt:
|
|
||||||
graph.save(filename=f"{filename}.gv")
|
|
||||||
# BOM output
|
|
||||||
bomlist = bom_list(self.bom())
|
|
||||||
if "tsv" in fmt:
|
|
||||||
open_file_write(f"{filename}.bom.tsv").write(tuplelist2tsv(bomlist))
|
|
||||||
if "csv" in fmt:
|
|
||||||
# TODO: implement CSV output (preferrably using CSV library)
|
|
||||||
print("CSV output is not yet supported")
|
|
||||||
# HTML output
|
|
||||||
if "html" in fmt:
|
|
||||||
generate_html_output(filename, bomlist, self.metadata, self.options)
|
|
||||||
# PDF output
|
|
||||||
if "pdf" in fmt:
|
|
||||||
# TODO: implement PDF output
|
# TODO: implement PDF output
|
||||||
print("PDF output is not yet supported")
|
sys.stderr.write("PDF output is not yet supported")
|
||||||
# delete SVG if not needed
|
|
||||||
if "html" in fmt and not "svg" in fmt:
|
outputs = {}
|
||||||
# SVG file was just needed to generate HTML
|
if "svg" in formats or "html" in formats:
|
||||||
Path(f"{filename}.tmp.svg").unlink()
|
# embed images into SVG output
|
||||||
elif "svg" in fmt:
|
outputs["svg"] = embed_svg_images(graph.pipe(format="svg", encoding="utf8"))
|
||||||
Path(f"{filename}.tmp.svg").replace(f"{filename}.svg")
|
|
||||||
|
if "png" in formats:
|
||||||
|
outputs["png"] = graph.pipe(format="png")
|
||||||
|
|
||||||
|
# GraphViz output
|
||||||
|
if "gv" in formats:
|
||||||
|
outputs["gv"] = graph.pipe(format="gv")
|
||||||
|
|
||||||
|
if "tsv" in formats or "html" in formats:
|
||||||
|
bomlist = bom_list(self.bom())
|
||||||
|
# BOM output
|
||||||
|
if "tsv" in formats:
|
||||||
|
outputs["tsv"] = tuplelist2tsv(bomlist)
|
||||||
|
|
||||||
|
# HTML output
|
||||||
|
if "html" in formats and "svg" in outputs:
|
||||||
|
outputs["html"] = generate_html_output(outputs["svg"], output_dir, bomlist, self.metadata, self.options)
|
||||||
|
|
||||||
|
# print to stdout or write files in order
|
||||||
|
for f in formats:
|
||||||
|
if f in outputs:
|
||||||
|
output = outputs[f]
|
||||||
|
if output_dir is None or output_name is None:
|
||||||
|
if isinstance(output, (bytes, bytearray)):
|
||||||
|
sys.stdout.buffer.write(output)
|
||||||
|
else:
|
||||||
|
sys.stdout.write(output)
|
||||||
|
else:
|
||||||
|
file = f"{output_dir}/{output_name}.{f}"
|
||||||
|
if isinstance(output, (bytes, bytearray)):
|
||||||
|
with open(file, "wb") as binary_file:
|
||||||
|
binary_file.write(output)
|
||||||
|
else:
|
||||||
|
with open(file, "w") as binary_file:
|
||||||
|
binary_file.write(output)
|
||||||
|
|
||||||
def bom(self):
|
def bom(self):
|
||||||
if not self._bom:
|
if not self._bom:
|
||||||
|
|||||||
@ -4,5 +4,5 @@
|
|||||||
__version__ = "0.4-dev"
|
__version__ = "0.4-dev"
|
||||||
|
|
||||||
CMD_NAME = "wireviz" # Lower case command and module name
|
CMD_NAME = "wireviz" # Lower case command and module name
|
||||||
APP_NAME = "WireViz" # Application name in texts meant to be human readable
|
APP_NAME = "WireViz" # Application name in texts meant to be human-readable
|
||||||
APP_URL = "https://github.com/formatc1702/WireViz"
|
APP_URL = "https://github.com/wireviz/WireViz"
|
||||||
|
|||||||
@ -38,15 +38,3 @@ def get_mime_subtype(filename: Union[str, Path]) -> str:
|
|||||||
if mime_subtype in mime_subtype_replacements:
|
if mime_subtype in mime_subtype_replacements:
|
||||||
mime_subtype = mime_subtype_replacements[mime_subtype]
|
mime_subtype = mime_subtype_replacements[mime_subtype]
|
||||||
return 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)
|
|
||||||
|
|||||||
@ -58,7 +58,7 @@ def parse(
|
|||||||
return_types (optional):
|
return_types (optional):
|
||||||
One of the supported return types (see above), or a tuple of multiple return types.
|
One of the supported return types (see above), or a tuple of multiple return types.
|
||||||
If set to None, no output is returned by the function.
|
If set to None, no output is returned by the function.
|
||||||
output_formats (optional):
|
output_formats (Tuple[str], optional):
|
||||||
One of the supported output types (see above), or a tuple of multiple output formats.
|
One of the supported output types (see above), or a tuple of multiple output formats.
|
||||||
If set to None, no files are generated.
|
If set to None, no files are generated.
|
||||||
output_dir (Path | str, optional):
|
output_dir (Path | str, optional):
|
||||||
@ -87,15 +87,18 @@ def parse(
|
|||||||
|
|
||||||
yaml_data, yaml_file = _get_yaml_data_and_path(inp)
|
yaml_data, yaml_file = _get_yaml_data_and_path(inp)
|
||||||
if output_formats:
|
if output_formats:
|
||||||
# need to write data to file, determine output directory and filename
|
if str(output_dir) == "-":
|
||||||
|
# write to stdout
|
||||||
|
output_dir = None
|
||||||
|
else:
|
||||||
|
# write to directory
|
||||||
output_dir = _get_output_dir(yaml_file, output_dir)
|
output_dir = _get_output_dir(yaml_file, output_dir)
|
||||||
output_name = _get_output_name(yaml_file, output_name)
|
output_name = _get_output_name(yaml_file, output_name)
|
||||||
output_file = output_dir / output_name
|
|
||||||
|
|
||||||
if yaml_file:
|
if yaml_file:
|
||||||
# if reading from file, ensure that input file's parent directory is included in image_paths
|
# if reading from file, ensure that input file's parent directory is included in image_paths
|
||||||
default_image_path = yaml_file.parent.resolve()
|
default_image_path = yaml_file.parent.resolve()
|
||||||
if not default_image_path in [Path(x).resolve() for x in image_paths]:
|
if default_image_path not in [Path(x).resolve() for x in image_paths]:
|
||||||
image_paths.append(default_image_path)
|
image_paths.append(default_image_path)
|
||||||
|
|
||||||
# define variables =========================================================
|
# define variables =========================================================
|
||||||
@ -362,11 +365,11 @@ def parse(
|
|||||||
harness.add_bom_item(line)
|
harness.add_bom_item(line)
|
||||||
|
|
||||||
if output_formats:
|
if output_formats:
|
||||||
harness.output(filename=output_file, fmt=output_formats, view=False)
|
harness.output(formats=output_formats, output_dir=output_dir, output_name=output_name)
|
||||||
|
|
||||||
if return_types:
|
if return_types:
|
||||||
returns = []
|
returns = []
|
||||||
if isinstance(return_types, str): # only one return type speficied
|
if isinstance(return_types, str): # only one return type specified
|
||||||
return_types = [return_types]
|
return_types = [return_types]
|
||||||
|
|
||||||
return_types = [t.lower() for t in return_types]
|
return_types = [t.lower() for t in return_types]
|
||||||
@ -390,10 +393,11 @@ def _get_yaml_data_and_path(inp: Union[str, Path, Dict]) -> (Dict, Path):
|
|||||||
# if no FileNotFoundError exception happens, get file contents
|
# if no FileNotFoundError exception happens, get file contents
|
||||||
yaml_str = open_file_read(yaml_path).read()
|
yaml_str = open_file_read(yaml_path).read()
|
||||||
except (FileNotFoundError, OSError) as e:
|
except (FileNotFoundError, OSError) as e:
|
||||||
# if inp is a long YAML string, Pathlib will raise OSError: [Errno 63]
|
# if inp is a long YAML string, Pathlib will raise OSError: [Errno 63].
|
||||||
# when trying to expand and resolve it as a path.
|
# when trying to expand and resolve it as a path.
|
||||||
# Catch this error, but raise any others
|
# it can also raise OSError: [Errno 36] File name too long.
|
||||||
if type(e) is OSError and e.errno != 63:
|
# Catch these errors, but raise any others.
|
||||||
|
if type(e) is OSError and e.errno != 63 and e.errno != 36:
|
||||||
raise e
|
raise e
|
||||||
# file does not exist; assume inp is a YAML string
|
# file does not exist; assume inp is a YAML string
|
||||||
yaml_str = inp
|
yaml_str = inp
|
||||||
@ -417,7 +421,7 @@ def _get_output_dir(input_file: Path, default_output_dir: Path) -> Path:
|
|||||||
return output_dir.resolve()
|
return output_dir.resolve()
|
||||||
|
|
||||||
|
|
||||||
def _get_output_name(input_file: Path, default_output_name: Path) -> str:
|
def _get_output_name(input_file: Path, default_output_name: Union[None, str]) -> str:
|
||||||
if default_output_name: # user-specified output name
|
if default_output_name: # user-specified output name
|
||||||
output_name = default_output_name
|
output_name = default_output_name
|
||||||
else: # auto-determine appropriate output name
|
else: # auto-determine appropriate output name
|
||||||
@ -429,7 +433,7 @@ def _get_output_name(input_file: Path, default_output_name: Path) -> str:
|
|||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
print("When running from the command line, please use wv_cli.py instead.")
|
sys.stderr.write("When running from the command line, please use wv_cli.py instead.")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
import click
|
import click
|
||||||
|
|
||||||
@ -67,12 +68,11 @@ epilog += ", ".join([f"{key} ({value.upper()})" for key, value in format_codes.i
|
|||||||
default=False,
|
default=False,
|
||||||
help=f"Output {APP_NAME} version and exit.",
|
help=f"Output {APP_NAME} version and exit.",
|
||||||
)
|
)
|
||||||
def wireviz(file, format, prepend, output_dir, output_name, version):
|
def wireviz(file, format, prepend, output_dir: Union[Path, None], output_name, version):
|
||||||
"""
|
"""
|
||||||
Parses the provided FILE and generates the specified outputs.
|
Parses the provided FILE and generates the specified outputs.
|
||||||
"""
|
"""
|
||||||
print()
|
sys.stderr.write(f"{APP_NAME} {__version__}\n")
|
||||||
print(f"{APP_NAME} {__version__}")
|
|
||||||
if version:
|
if version:
|
||||||
return # print version number only and exit
|
return # print version number only and exit
|
||||||
|
|
||||||
@ -88,10 +88,13 @@ def wireviz(file, format, prepend, output_dir, output_name, version):
|
|||||||
output_formats = []
|
output_formats = []
|
||||||
for code in format:
|
for code in format:
|
||||||
if code in format_codes:
|
if code in format_codes:
|
||||||
output_formats.append(format_codes[code])
|
output_format: str = format_codes[code]
|
||||||
|
# unique
|
||||||
|
if output_format not in output_formats:
|
||||||
|
output_formats.append(output_format)
|
||||||
else:
|
else:
|
||||||
raise Exception(f"Unknown output format: {code}")
|
raise Exception(f"Unknown output format: {code}")
|
||||||
output_formats = tuple(sorted(set(output_formats)))
|
|
||||||
output_formats_str = (
|
output_formats_str = (
|
||||||
f'[{"|".join(output_formats)}]'
|
f'[{"|".join(output_formats)}]'
|
||||||
if len(output_formats) > 1
|
if len(output_formats) > 1
|
||||||
@ -105,44 +108,63 @@ def wireviz(file, format, prepend, output_dir, output_name, version):
|
|||||||
prepend_file = Path(prepend_file)
|
prepend_file = Path(prepend_file)
|
||||||
if not prepend_file.exists():
|
if not prepend_file.exists():
|
||||||
raise Exception(f"File does not exist:\n{prepend_file}")
|
raise Exception(f"File does not exist:\n{prepend_file}")
|
||||||
print("Prepend file:", prepend_file)
|
|
||||||
|
sys.stderr.write(f"Prepend file: {prepend_file}")
|
||||||
|
|
||||||
prepend_input += open_file_read(prepend_file).read() + "\n"
|
prepend_input += open_file_read(prepend_file).read() + "\n"
|
||||||
else:
|
else:
|
||||||
prepend_input = ""
|
prepend_input = ""
|
||||||
|
|
||||||
# run WireVIz on each input file
|
output_stdout = str(output_dir) == "-" or output_name == "-"
|
||||||
|
if output_stdout and len(filepaths) == 0:
|
||||||
|
wv.parse(
|
||||||
|
sys.stdin.read(),
|
||||||
|
output_dir="-",
|
||||||
|
output_formats=tuple(output_formats),
|
||||||
|
)
|
||||||
|
|
||||||
|
# run WireViz on each input file
|
||||||
for file in filepaths:
|
for file in filepaths:
|
||||||
|
if file == "-":
|
||||||
|
# read from stdin
|
||||||
|
yaml_input = prepend_input + sys.stdin.read()
|
||||||
|
image_paths = []
|
||||||
|
else:
|
||||||
file = Path(file)
|
file = Path(file)
|
||||||
if not file.exists():
|
if not file.exists():
|
||||||
raise Exception(f"File does not exist:\n{file}")
|
raise Exception(f"File does not exist:\n{file}")
|
||||||
|
|
||||||
# file_out = file.with_suffix("") if not output_file else output_file
|
sys.stderr.write(f"Input file: {file}\n")
|
||||||
_output_dir = file.parent if not output_dir else output_dir
|
|
||||||
_output_name = file.stem if not output_name else output_name
|
|
||||||
|
|
||||||
print("Input file: ", file)
|
|
||||||
print(
|
|
||||||
"Output file: ", f"{Path(_output_dir / _output_name)}.{output_formats_str}"
|
|
||||||
)
|
|
||||||
|
|
||||||
yaml_input = open_file_read(file).read()
|
yaml_input = open_file_read(file).read()
|
||||||
file_dir = file.parent
|
file_dir = file.parent
|
||||||
|
|
||||||
yaml_input = prepend_input + yaml_input
|
yaml_input = prepend_input + yaml_input
|
||||||
image_paths = {file_dir}
|
image_paths = {file_dir}
|
||||||
|
|
||||||
for p in prepend:
|
for p in prepend:
|
||||||
image_paths.add(Path(p).parent)
|
image_paths.add(Path(p).parent)
|
||||||
|
|
||||||
|
# file_out = file.with_suffix("") if not output_file else output_file
|
||||||
|
if output_stdout or file == "-":
|
||||||
|
sys.stderr.write(f"Output: <stdout>.{output_formats_str}\n")
|
||||||
|
_output_dir = "-"
|
||||||
|
_output_name = None
|
||||||
|
else:
|
||||||
|
_output_dir = file.parent if not output_dir else output_dir
|
||||||
|
_output_name = file.stem if not output_name else output_name
|
||||||
|
sys.stderr.write(
|
||||||
|
f"Output file: {Path(_output_dir / _output_name)}.{output_formats_str}"
|
||||||
|
)
|
||||||
|
|
||||||
wv.parse(
|
wv.parse(
|
||||||
yaml_input,
|
yaml_input,
|
||||||
output_formats=output_formats,
|
output_formats=tuple(output_formats),
|
||||||
output_dir=_output_dir,
|
output_dir=_output_dir,
|
||||||
output_name=_output_name,
|
output_name=_output_name,
|
||||||
image_paths=list(image_paths),
|
image_paths=list(image_paths),
|
||||||
)
|
)
|
||||||
|
|
||||||
print()
|
sys.stderr.write('')
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
import sys
|
||||||
from typing import Dict, List
|
from typing import Dict, List
|
||||||
|
|
||||||
COLOR_CODES = {
|
COLOR_CODES = {
|
||||||
@ -138,7 +138,7 @@ def get_color_hex(input: Colors, pad: bool = False) -> List[str]:
|
|||||||
if c[0] != "#" or not all(d in _hex_digits for d in c[1:]):
|
if c[0] != "#" or not all(d in _hex_digits for d in c[1:]):
|
||||||
if c != input:
|
if c != input:
|
||||||
c += f" in input: {input}"
|
c += f" in input: {input}"
|
||||||
print(f"Invalid hex color: {c}")
|
sys.stderr.write(f"Invalid hex color: {c}")
|
||||||
output[i] = color_default
|
output[i] = color_default
|
||||||
else: # Color name(s)
|
else: # Color name(s)
|
||||||
|
|
||||||
@ -148,7 +148,7 @@ def get_color_hex(input: Colors, pad: bool = False) -> List[str]:
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
if c != input:
|
if c != input:
|
||||||
c += f" in input: {input}"
|
c += f" in input: {input}"
|
||||||
print(f"Unknown color name: {c}")
|
sys.stderr.write(f"Unknown color name: {c}")
|
||||||
return color_default
|
return color_default
|
||||||
|
|
||||||
output = [lookup(input[i : i + 2]) for i in range(0, len(input), 2)]
|
output = [lookup(input[i : i + 2]) for i in range(0, len(input), 2)]
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict, List
|
from typing import Dict, List
|
||||||
|
|
||||||
@ -147,10 +148,10 @@ def aspect_ratio(image_src):
|
|||||||
image = Image.open(image_src)
|
image = Image.open(image_src)
|
||||||
if image.width > 0 and image.height > 0:
|
if image.width > 0 and image.height > 0:
|
||||||
return image.width / image.height
|
return image.width / image.height
|
||||||
print(f"aspect_ratio(): Invalid image size {image.width} x {image.height}")
|
sys.stderr.write(f"aspect_ratio(): Invalid image size {image.width} x {image.height}")
|
||||||
# ModuleNotFoundError and FileNotFoundError are the most expected, but all are handled equally.
|
# ModuleNotFoundError and FileNotFoundError are the most expected, but all are handled equally.
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
print(f"aspect_ratio(): {type(error).__name__}: {error}")
|
sys.stderr.write(f"aspect_ratio(): {type(error).__name__}: {error}")
|
||||||
return 1 # Assume 1:1 when unable to read actual image size
|
return 1 # Assume 1:1 when unable to read actual image size
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import re
|
import re
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict, List, Union
|
from typing import Dict, List, Optional
|
||||||
|
|
||||||
from wireviz import APP_NAME, APP_URL, __version__, wv_colors
|
from wireviz import APP_NAME, APP_URL, __version__, wv_colors
|
||||||
from wireviz.DataClasses import Metadata, Options
|
from wireviz.DataClasses import Metadata, Options
|
||||||
@ -10,45 +10,46 @@ from wireviz.wv_gv_html import html_line_breaks
|
|||||||
from wireviz.wv_helper import (
|
from wireviz.wv_helper import (
|
||||||
flatten2d,
|
flatten2d,
|
||||||
open_file_read,
|
open_file_read,
|
||||||
open_file_write,
|
|
||||||
smart_file_resolve,
|
smart_file_resolve,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def generate_html_output(
|
def generate_html_output(
|
||||||
filename: Union[str, Path],
|
svg_input: str,
|
||||||
|
output_dir: Optional[Path],
|
||||||
bom_list: List[List[str]],
|
bom_list: List[List[str]],
|
||||||
metadata: Metadata,
|
metadata: Metadata,
|
||||||
options: Options,
|
options: Options,
|
||||||
):
|
) -> str:
|
||||||
|
|
||||||
# load HTML template
|
# load HTML template
|
||||||
templatename = metadata.get("template", {}).get("name")
|
template_name = metadata.get("template", {}).get("name")
|
||||||
if templatename:
|
builtin_template_directory = Path(__file__).parent / "templates" # built-in template directory
|
||||||
# if relative path to template was provided, check directory of YAML file first, fall back to built-in template directory
|
if template_name:
|
||||||
templatefile = smart_file_resolve(
|
possible_paths = []
|
||||||
f"{templatename}.html",
|
# if relative path to template was provided, check directory of YAML file first
|
||||||
[Path(filename).parent, Path(__file__).parent / "templates"],
|
if output_dir is not None:
|
||||||
)
|
possible_paths.append(output_dir)
|
||||||
|
|
||||||
|
possible_paths.append(builtin_template_directory) # fallback
|
||||||
|
template_file = smart_file_resolve(f"{template_name}.html", possible_paths)
|
||||||
else:
|
else:
|
||||||
# fallback to built-in simple template if no template was provided
|
# fallback to built-in simple template if no template was provided
|
||||||
templatefile = Path(__file__).parent / "templates/simple.html"
|
template_file = builtin_template_directory / "simple.html"
|
||||||
|
|
||||||
html = open_file_read(templatefile).read()
|
html = open_file_read(template_file).read()
|
||||||
|
|
||||||
# embed SVG diagram
|
# embed SVG diagram
|
||||||
with open_file_read(f"{filename}.tmp.svg") as file:
|
svg_data = re.sub(
|
||||||
svgdata = re.sub(
|
|
||||||
"^<[?]xml [^?>]*[?]>[^<]*<!DOCTYPE [^>]*>",
|
"^<[?]xml [^?>]*[?]>[^<]*<!DOCTYPE [^>]*>",
|
||||||
"<!-- XML and DOCTYPE declarations from SVG file removed -->",
|
"<!-- XML and DOCTYPE declarations from SVG file removed -->",
|
||||||
file.read(),
|
svg_input,
|
||||||
1,
|
1,
|
||||||
)
|
)
|
||||||
|
|
||||||
# generate BOM table
|
# generate BOM table
|
||||||
bom = flatten2d(bom_list)
|
bom = flatten2d(bom_list)
|
||||||
|
|
||||||
# generate BOM header (may be at the top or bottom of the table)
|
# generate BOM header (might be at the top or bottom of the table)
|
||||||
bom_header_html = " <tr>\n"
|
bom_header_html = " <tr>\n"
|
||||||
for item in bom[0]:
|
for item in bom[0]:
|
||||||
th_class = f"bom_col_{item.lower()}"
|
th_class = f"bom_col_{item.lower()}"
|
||||||
@ -80,7 +81,7 @@ def generate_html_output(
|
|||||||
"<!-- %generator% -->": f"{APP_NAME} {__version__} - {APP_URL}",
|
"<!-- %generator% -->": f"{APP_NAME} {__version__} - {APP_URL}",
|
||||||
"<!-- %fontname% -->": options.fontname,
|
"<!-- %fontname% -->": options.fontname,
|
||||||
"<!-- %bgcolor% -->": wv_colors.translate_color(options.bgcolor, "hex"),
|
"<!-- %bgcolor% -->": wv_colors.translate_color(options.bgcolor, "hex"),
|
||||||
"<!-- %diagram% -->": svgdata,
|
"<!-- %diagram% -->": svg_data,
|
||||||
"<!-- %bom% -->": bom_html,
|
"<!-- %bom% -->": bom_html,
|
||||||
"<!-- %bom_reversed% -->": bom_html_reversed,
|
"<!-- %bom_reversed% -->": bom_html_reversed,
|
||||||
"<!-- %sheet_current% -->": "1", # TODO: handle multi-page documents
|
"<!-- %sheet_current% -->": "1", # TODO: handle multi-page documents
|
||||||
@ -114,6 +115,4 @@ def generate_html_output(
|
|||||||
replacements_sorted = sorted(replacements, key=len, reverse=True)
|
replacements_sorted = sorted(replacements, key=len, reverse=True)
|
||||||
replacements_escaped = map(re.escape, replacements_sorted)
|
replacements_escaped = map(re.escape, replacements_sorted)
|
||||||
pattern = re.compile("|".join(replacements_escaped))
|
pattern = re.compile("|".join(replacements_escaped))
|
||||||
html = pattern.sub(lambda match: replacements[match.group(0)], html)
|
return pattern.sub(lambda match: replacements[match.group(0)], html)
|
||||||
|
|
||||||
open_file_write(f"{filename}.html").write(html)
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user