Skip to content

Commit 966d0c7

Browse files
committed
resolves #320 read from stdin and write to stdout
1 parent 92af905 commit 966d0c7

File tree

9 files changed

+154
-136
lines changed

9 files changed

+154
-136
lines changed

src/wireviz/DataClasses.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# -*- coding: utf-8 -*-
2-
2+
import sys
33
from dataclasses import InitVar, dataclass, field
44
from enum import Enum, auto
55
from pathlib import Path
@@ -280,7 +280,7 @@ def __post_init__(self) -> None:
280280
self.gauge = g
281281

282282
if self.gauge_unit is not None:
283-
print(
283+
sys.stderr.write(
284284
f"Warning: Cable {self.name} gauge_unit={self.gauge_unit} is ignored because its gauge contains {u}"
285285
)
286286
if u.upper() == "AWG":
@@ -304,7 +304,7 @@ def __post_init__(self) -> None:
304304
)
305305
self.length = L
306306
if self.length_unit is not None:
307-
print(
307+
sys.stderr.write(
308308
f"Warning: Cable {self.name} length_unit={self.length_unit} is ignored because its length contains {u}"
309309
)
310310
self.length_unit = u

src/wireviz/Harness.py

Lines changed: 53 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
# -*- coding: utf-8 -*-
22

33
import re
4+
import sys
45
from collections import Counter
56
from dataclasses import dataclass
67
from itertools import zip_longest
78
from pathlib import Path
8-
from typing import Any, List, Union
9+
from typing import Any, List, Union, Optional
910

1011
from graphviz import Graph
1112

@@ -20,13 +21,12 @@
2021
Tweak,
2122
Side,
2223
)
23-
from wireviz.svgembed import embed_svg_images_file
24+
from wireviz.svgembed import embed_svg_images
2425
from wireviz.wv_bom import (
2526
HEADER_MPN,
2627
HEADER_PN,
2728
HEADER_SPN,
2829
bom_list,
29-
component_table_entry,
3030
generate_bom,
3131
get_additional_component_table,
3232
pn_info_string,
@@ -543,11 +543,11 @@ def typecheck(name: str, value: Any, expect: type) -> None:
543543
f'( +)?{attr}=("[^"]*"|[^] ]*)(?(1)| *)', "", entry
544544
)
545545
if n_subs < 1:
546-
print(
546+
sys.stderr.write(
547547
f"Harness.create_graph() warning: {attr} not found in {keyword}!"
548548
)
549549
elif n_subs > 1:
550-
print(
550+
sys.stderr.write(
551551
f"Harness.create_graph() warning: {attr} removed {n_subs} times in {keyword}!"
552552
)
553553
continue
@@ -562,7 +562,7 @@ def typecheck(name: str, value: Any, expect: type) -> None:
562562
# If attr not found, then append it
563563
entry = re.sub(r"\]$", f" {attr}={value}]", entry)
564564
elif n_subs > 1:
565-
print(
565+
sys.stderr.write(
566566
f"Harness.create_graph() warning: {attr} overridden {n_subs} times in {keyword}!"
567567
)
568568

@@ -650,55 +650,58 @@ def svg(self):
650650
graph = self.graph
651651
return embed_svg_images(graph.pipe(format="svg").decode("utf-8"), Path.cwd())
652652

653-
654653
def output(
655-
self,
656-
filename: (str, Path),
657-
view: bool = False,
658-
cleanup: bool = True,
659-
fmt: tuple = ("html", "png", "svg", "tsv"),
654+
self,
655+
output_dir: Optional[Union[str, Path]],
656+
output_name: Optional[Union[str, Path]],
657+
formats: List[str] | tuple[str]
660658
) -> None:
661659
# graphical output
662660
graph = self.graph
663-
svg_already_exists = Path(
664-
f"{filename}.svg"
665-
).exists() # if SVG already exists, do not delete later
666-
# graphical output
667-
for f in fmt:
668-
if f in ("png", "svg", "html"):
669-
if f == "html": # if HTML format is specified,
670-
f = "svg" # generate SVG for embedding into HTML
671-
# SVG file will be renamed/deleted later
672-
_filename = f"{filename}.tmp" if f == "svg" else filename
673-
# TODO: prevent rendering SVG twice when both SVG and HTML are specified
674-
graph.format = f
675-
graph.render(filename=_filename, view=view, cleanup=cleanup)
676-
# embed images into SVG output
677-
if "svg" in fmt or "html" in fmt:
678-
embed_svg_images_file(f"{filename}.tmp.svg")
679-
# GraphViz output
680-
if "gv" in fmt:
681-
graph.save(filename=f"{filename}.gv")
682-
# BOM output
683-
bomlist = bom_list(self.bom())
684-
if "tsv" in fmt:
685-
open_file_write(f"{filename}.bom.tsv").write(tuplelist2tsv(bomlist))
686-
if "csv" in fmt:
687-
# TODO: implement CSV output (preferrably using CSV library)
688-
print("CSV output is not yet supported")
689-
# HTML output
690-
if "html" in fmt:
691-
generate_html_output(filename, bomlist, self.metadata, self.options)
692-
# PDF output
693-
if "pdf" in fmt:
661+
662+
if "csv" in formats:
663+
# TODO: implement CSV output (preferably using CSV library)
664+
sys.stderr.write("CSV output is not yet supported")
665+
if "pdf" in formats:
694666
# TODO: implement PDF output
695-
print("PDF output is not yet supported")
696-
# delete SVG if not needed
697-
if "html" in fmt and not "svg" in fmt:
698-
# SVG file was just needed to generate HTML
699-
Path(f"{filename}.tmp.svg").unlink()
700-
elif "svg" in fmt:
701-
Path(f"{filename}.tmp.svg").replace(f"{filename}.svg")
667+
sys.stderr.write("PDF output is not yet supported")
668+
669+
outputs = {}
670+
if "svg" in formats or "html" in formats:
671+
# embed images into SVG output
672+
outputs["svg"] = embed_svg_images(graph.pipe(format="svg", encoding="utf8"))
673+
674+
if "png" in formats:
675+
outputs["png"] = graph.pipe(format="png")
676+
677+
# GraphViz output
678+
if "gv" in formats:
679+
outputs["gv"] = graph.pipe(format="gv")
680+
681+
if "tsv" in formats or "html" in formats:
682+
bomlist = bom_list(self.bom())
683+
# BOM output
684+
if "tsv" in formats:
685+
outputs["tsv"] = tuplelist2tsv(bomlist)
686+
687+
# HTML output
688+
if "html" in formats and "svg" in outputs:
689+
outputs["html"] = generate_html_output(outputs["svg"], output_dir, bomlist, self.metadata, self.options)
690+
691+
# print to stdout or write files in order
692+
for f in formats:
693+
if f in outputs:
694+
output = outputs[f]
695+
if output_dir is None or output_name is None:
696+
sys.stdout.write(output)
697+
else:
698+
file = f"{output_dir}/{output_name}.{f}"
699+
if isinstance(output, (bytes, bytearray)):
700+
with open(file, "wb") as binary_file:
701+
binary_file.write(output)
702+
else:
703+
with open(file, "w") as binary_file:
704+
binary_file.write(output)
702705

703706
def bom(self):
704707
if not self._bom:

src/wireviz/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@
44
__version__ = "0.4-dev"
55

66
CMD_NAME = "wireviz" # Lower case command and module name
7-
APP_NAME = "WireViz" # Application name in texts meant to be human readable
8-
APP_URL = "https://github.com/formatc1702/WireViz"
7+
APP_NAME = "WireViz" # Application name in texts meant to be human-readable
8+
APP_URL = "https://github.com/wireviz/WireViz"

src/wireviz/svgembed.py

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,3 @@ def get_mime_subtype(filename: Union[str, Path]) -> str:
3838
if mime_subtype in mime_subtype_replacements:
3939
mime_subtype = mime_subtype_replacements[mime_subtype]
4040
return mime_subtype
41-
42-
43-
def embed_svg_images_file(
44-
filename_in: Union[str, Path], overwrite: bool = True
45-
) -> None:
46-
filename_in = Path(filename_in).resolve()
47-
filename_out = filename_in.with_suffix(".b64.svg")
48-
filename_out.write_text(
49-
embed_svg_images(filename_in.read_text(), filename_in.parent)
50-
)
51-
if overwrite:
52-
filename_out.replace(filename_in)

src/wireviz/wireviz.py

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ def parse(
5858
return_types (optional):
5959
One of the supported return types (see above), or a tuple of multiple return types.
6060
If set to None, no output is returned by the function.
61-
output_formats (optional):
61+
output_formats (Tuple[str], optional):
6262
One of the supported output types (see above), or a tuple of multiple output formats.
6363
If set to None, no files are generated.
6464
output_dir (Path | str, optional):
@@ -87,15 +87,18 @@ def parse(
8787

8888
yaml_data, yaml_file = _get_yaml_data_and_path(inp)
8989
if output_formats:
90-
# need to write data to file, determine output directory and filename
91-
output_dir = _get_output_dir(yaml_file, output_dir)
92-
output_name = _get_output_name(yaml_file, output_name)
93-
output_file = output_dir / output_name
90+
if str(output_dir) == "-":
91+
# write to stdout
92+
output_dir = None
93+
else:
94+
# write to directory
95+
output_dir = _get_output_dir(yaml_file, output_dir)
96+
output_name = _get_output_name(yaml_file, output_name)
9497

9598
if yaml_file:
9699
# if reading from file, ensure that input file's parent directory is included in image_paths
97100
default_image_path = yaml_file.parent.resolve()
98-
if not default_image_path in [Path(x).resolve() for x in image_paths]:
101+
if default_image_path not in [Path(x).resolve() for x in image_paths]:
99102
image_paths.append(default_image_path)
100103

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

364367
if output_formats:
365-
harness.output(filename=output_file, fmt=output_formats, view=False)
368+
harness.output(formats=output_formats, output_dir=output_dir, output_name=output_name)
366369

367370
if return_types:
368371
returns = []
369-
if isinstance(return_types, str): # only one return type speficied
372+
if isinstance(return_types, str): # only one return type specified
370373
return_types = [return_types]
371374

372375
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):
390393
# if no FileNotFoundError exception happens, get file contents
391394
yaml_str = open_file_read(yaml_path).read()
392395
except (FileNotFoundError, OSError) as e:
393-
# if inp is a long YAML string, Pathlib will raise OSError: [Errno 63]
396+
# if inp is a long YAML string, Pathlib will raise OSError: [Errno 63].
394397
# when trying to expand and resolve it as a path.
395-
# Catch this error, but raise any others
396-
if type(e) is OSError and e.errno != 63:
398+
# it can also raise OSError: [Errno 36] File name too long.
399+
# Catch these errors, but raise any others.
400+
if type(e) is OSError and e.errno != 63 and e.errno != 36:
397401
raise e
398402
# file does not exist; assume inp is a YAML string
399403
yaml_str = inp
@@ -417,7 +421,7 @@ def _get_output_dir(input_file: Path, default_output_dir: Path) -> Path:
417421
return output_dir.resolve()
418422

419423

420-
def _get_output_name(input_file: Path, default_output_name: Path) -> str:
424+
def _get_output_name(input_file: Path, default_output_name: Union[None, str]) -> str:
421425
if default_output_name: # user-specified output name
422426
output_name = default_output_name
423427
else: # auto-determine appropriate output name
@@ -429,7 +433,7 @@ def _get_output_name(input_file: Path, default_output_name: Path) -> str:
429433

430434

431435
def main():
432-
print("When running from the command line, please use wv_cli.py instead.")
436+
sys.stderr.write("When running from the command line, please use wv_cli.py instead.")
433437

434438

435439
if __name__ == "__main__":

0 commit comments

Comments
 (0)