Skip to content

Commit

Permalink
resolves #320 read from stdin and write to stdout
Browse files Browse the repository at this point in the history
  • Loading branch information
ggrossetie committed Jul 14, 2023
1 parent 92af905 commit 76fa388
Show file tree
Hide file tree
Showing 9 changed files with 156 additions and 136 deletions.
6 changes: 3 additions & 3 deletions src/wireviz/DataClasses.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-

import sys
from dataclasses import InitVar, dataclass, field
from enum import Enum, auto
from pathlib import Path
Expand Down Expand Up @@ -280,7 +280,7 @@ def __post_init__(self) -> None:
self.gauge = g

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}"
)
if u.upper() == "AWG":
Expand All @@ -304,7 +304,7 @@ def __post_init__(self) -> None:
)
self.length = L
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}"
)
self.length_unit = u
Expand Down
106 changes: 56 additions & 50 deletions src/wireviz/Harness.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
# -*- coding: utf-8 -*-

import re
import sys
from collections import Counter
from dataclasses import dataclass
from itertools import zip_longest
from pathlib import Path
from typing import Any, List, Union
from typing import Any, List, Union, Optional

from graphviz import Graph

Expand All @@ -20,13 +21,12 @@
Tweak,
Side,
)
from wireviz.svgembed import embed_svg_images_file
from wireviz.svgembed import embed_svg_images
from wireviz.wv_bom import (
HEADER_MPN,
HEADER_PN,
HEADER_SPN,
bom_list,
component_table_entry,
generate_bom,
get_additional_component_table,
pn_info_string,
Expand Down Expand Up @@ -543,11 +543,11 @@ def typecheck(name: str, value: Any, expect: type) -> None:
f'( +)?{attr}=("[^"]*"|[^] ]*)(?(1)| *)', "", entry
)
if n_subs < 1:
print(
sys.stderr.write(
f"Harness.create_graph() warning: {attr} not found in {keyword}!"
)
elif n_subs > 1:
print(
sys.stderr.write(
f"Harness.create_graph() warning: {attr} removed {n_subs} times in {keyword}!"
)
continue
Expand All @@ -562,7 +562,7 @@ def typecheck(name: str, value: Any, expect: type) -> None:
# If attr not found, then append it
entry = re.sub(r"\]$", f" {attr}={value}]", entry)
elif n_subs > 1:
print(
sys.stderr.write(
f"Harness.create_graph() warning: {attr} overridden {n_subs} times in {keyword}!"
)

Expand Down Expand Up @@ -650,55 +650,61 @@ def svg(self):
graph = self.graph
return embed_svg_images(graph.pipe(format="svg").decode("utf-8"), Path.cwd())


def output(
self,
filename: (str, Path),
view: bool = False,
cleanup: bool = True,
fmt: tuple = ("html", "png", "svg", "tsv"),
self,
output_dir: Optional[Union[str, Path]],
output_name: Optional[Union[str, Path]],
formats: List[str] | tuple[str]
) -> None:
# graphical output
graph = self.graph
svg_already_exists = Path(
f"{filename}.svg"
).exists() # if SVG already exists, do not delete later
# graphical output
for f in fmt:
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:

if "csv" in formats:
# TODO: implement CSV output (preferably using CSV library)
sys.stderr.write("CSV output is not yet supported")
if "pdf" in formats:
# TODO: implement PDF output
print("PDF output is not yet supported")
# delete SVG if not needed
if "html" in fmt and not "svg" in fmt:
# SVG file was just needed to generate HTML
Path(f"{filename}.tmp.svg").unlink()
elif "svg" in fmt:
Path(f"{filename}.tmp.svg").replace(f"{filename}.svg")
sys.stderr.write("PDF output is not yet supported")

outputs = {}
if "svg" in formats or "html" in formats:
# embed images into SVG output
outputs["svg"] = embed_svg_images(graph.pipe(format="svg", encoding="utf8"))

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):
if not self._bom:
Expand Down
4 changes: 2 additions & 2 deletions src/wireviz/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@
__version__ = "0.4-dev"

CMD_NAME = "wireviz" # Lower case command and module name
APP_NAME = "WireViz" # Application name in texts meant to be human readable
APP_URL = "https://github.com/formatc1702/WireViz"
APP_NAME = "WireViz" # Application name in texts meant to be human-readable
APP_URL = "https://github.com/wireviz/WireViz"
12 changes: 0 additions & 12 deletions src/wireviz/svgembed.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,3 @@ def get_mime_subtype(filename: Union[str, Path]) -> str:
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)
30 changes: 17 additions & 13 deletions src/wireviz/wireviz.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def parse(
return_types (optional):
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.
output_formats (optional):
output_formats (Tuple[str], optional):
One of the supported output types (see above), or a tuple of multiple output formats.
If set to None, no files are generated.
output_dir (Path | str, optional):
Expand Down Expand Up @@ -87,15 +87,18 @@ def parse(

yaml_data, yaml_file = _get_yaml_data_and_path(inp)
if output_formats:
# need to write data to file, determine output directory and filename
output_dir = _get_output_dir(yaml_file, output_dir)
output_name = _get_output_name(yaml_file, output_name)
output_file = output_dir / output_name
if str(output_dir) == "-":
# write to stdout
output_dir = None
else:
# write to directory
output_dir = _get_output_dir(yaml_file, output_dir)
output_name = _get_output_name(yaml_file, output_name)

if yaml_file:
# if reading from file, ensure that input file's parent directory is included in image_paths
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)

# define variables =========================================================
Expand Down Expand Up @@ -362,11 +365,11 @@ def alternate_type(): # flip between connector and cable/arrow
harness.add_bom_item(line)

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:
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 = [t.lower() for t in return_types]
Expand All @@ -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
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]
# 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:
# it can also raise OSError: [Errno 36] File name too long.
# Catch these errors, but raise any others.
if type(e) is OSError and e.errno != 63 and e.errno != 36:
raise e
# file does not exist; assume inp is a YAML string
yaml_str = inp
Expand All @@ -417,7 +421,7 @@ def _get_output_dir(input_file: Path, default_output_dir: Path) -> Path:
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
output_name = default_output_name
else: # auto-determine appropriate output name
Expand All @@ -429,7 +433,7 @@ def _get_output_name(input_file: Path, default_output_name: Path) -> str:


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__":
Expand Down
Loading

0 comments on commit 76fa388

Please sign in to comment.