From 83b1c9b5c3c73608d57dfb328196ceeb11e4ad28 Mon Sep 17 00:00:00 2001 From: "Jason R. Jones" Date: Wed, 15 Jul 2020 06:44:33 -0400 Subject: [PATCH 01/30] Add type hinting in several, but not all, places. --- src/wireviz/Harness.py | 12 ++++++------ src/wireviz/wireviz.py | 7 ++++--- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/wireviz/Harness.py b/src/wireviz/Harness.py index ca9f8e62..be1632fa 100644 --- a/src/wireviz/Harness.py +++ b/src/wireviz/Harness.py @@ -20,16 +20,16 @@ def __init__(self): self.cables = {} self.additional_bom_items = [] - def add_connector(self, name, *args, **kwargs): + def add_connector(self, name: str, *args, **kwargs) -> None: self.connectors[name] = Connector(name, *args, **kwargs) - def add_cable(self, name, *args, **kwargs): + def add_cable(self, name: str, *args, **kwargs) -> None: self.cables[name] = Cable(name, *args, **kwargs) - def add_bom_item(self, item): + def add_bom_item(self, item: str) -> None: self.additional_bom_items.append(item) - def connect(self, from_name, from_pin, via_name, via_pin, to_name, to_pin): + def connect(self, from_name: str, from_pin: int, via_name: str, via_pin: (int, str), to_name: str, to_pin: int) -> None: for (name, pin) in zip([from_name, to_name], [from_pin, to_pin]): # check from and to connectors if name is not None and name in self.connectors: connector = self.connectors[name] @@ -58,7 +58,7 @@ def connect(self, from_name, from_pin, via_name, via_pin, to_name, to_pin): if to_name in self.connectors: self.connectors[to_name].activate_pin(to_pin) - def create_graph(self): + def create_graph(self) -> Graph: dot = Graph() dot.body.append('// Graph generated by WireViz') dot.body.append('// https://github.com/formatc1702/WireViz') @@ -280,7 +280,7 @@ def svg(self): data.seek(0) return data.read() - def output(self, filename: (str, Path), view=False, cleanup=True, fmt=('pdf', )): + def output(self, filename: (str, Path), view: bool = False, cleanup: bool = True, fmt: tuple = ('pdf', )) -> None: # graphical output graph = self.create_graph() for f in fmt: diff --git a/src/wireviz/wireviz.py b/src/wireviz/wireviz.py index d52d3b12..6c9a1869 100755 --- a/src/wireviz/wireviz.py +++ b/src/wireviz/wireviz.py @@ -3,8 +3,9 @@ import argparse import os +from pathlib import Path import sys -from typing import Tuple +from typing import Any, Tuple import yaml @@ -16,7 +17,7 @@ from wireviz.wv_helper import expand -def parse(yaml_input, file_out=None, return_types: (None, str, Tuple[str]) = None): +def parse(yaml_input: str, file_out: (str, Path) = None, return_types: (None, str, Tuple[str]) = None) -> Any: """ Parses yaml input string and does the high-level harness conversion @@ -196,7 +197,7 @@ def check_designators(what, where): # helper function return tuple(returns) if len(returns) != 1 else returns[0] -def parse_file(yaml_file, file_out=None): +def parse_file(yaml_file: str, file_out: (str, Path) = None) -> None: with open(yaml_file, 'r') as file: yaml_input = file.read() From 38938fc5a3b68a7f3c81706b9f6f3dc5c54791c5 Mon Sep 17 00:00:00 2001 From: "Jason R. Jones" Date: Wed, 15 Jul 2020 13:25:01 -0400 Subject: [PATCH 02/30] Fix type hinting --- src/wireviz/Harness.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wireviz/Harness.py b/src/wireviz/Harness.py index be1632fa..95e7a2a5 100644 --- a/src/wireviz/Harness.py +++ b/src/wireviz/Harness.py @@ -26,10 +26,10 @@ def add_connector(self, name: str, *args, **kwargs) -> None: def add_cable(self, name: str, *args, **kwargs) -> None: self.cables[name] = Cable(name, *args, **kwargs) - def add_bom_item(self, item: str) -> None: + def add_bom_item(self, item: dict) -> None: self.additional_bom_items.append(item) - def connect(self, from_name: str, from_pin: int, via_name: str, via_pin: (int, str), to_name: str, to_pin: int) -> None: + def connect(self, from_name: str, from_pin: (int, str), via_name: str, via_pin: (int, str), to_name: str, to_pin: (int, str)) -> None: for (name, pin) in zip([from_name, to_name], [from_pin, to_pin]): # check from and to connectors if name is not None and name in self.connectors: connector = self.connectors[name] From 8f6b8a7e848fbb51c672df9b08f47c5898edcc0f Mon Sep 17 00:00:00 2001 From: Jason Date: Wed, 15 Jul 2020 14:21:09 -0400 Subject: [PATCH 03/30] Add initial contribution guidelines --- CONTRIBUTING.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..7a94ac7e --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,18 @@ +# Contribution Guidelines + +When contributing to this repository, please first discuss the change you +wish to make via issue, email, or any other method with the owners of this +repository before making a change. + +## Pull Requests + +1. Fork this repository to your repository +1. Clone the repository to your local machine +1. Checkout the `dev` branch +1. Make any changes to the code on the `dev` branch +1. Push your changes to your fork +1. Create new pull request + +## Documentation Strings + +Documentation strings are to follow the Google Style ([examples](https://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_google.html)). From 83929a119505a89178c0d0b59b0b0e8552754b8a Mon Sep 17 00:00:00 2001 From: "Jason R. Jones" Date: Thu, 16 Jul 2020 12:31:41 -0400 Subject: [PATCH 04/30] Fix utf-8 encoding problem in BOM portion of HTML files. --- src/wireviz/Harness.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/wireviz/Harness.py b/src/wireviz/Harness.py index 3461cc96..1a992369 100644 --- a/src/wireviz/Harness.py +++ b/src/wireviz/Harness.py @@ -321,8 +321,9 @@ def output(self, filename: (str, Path), view: bool = False, cleanup: bool = True for row in listy[1:]: file.write('') for i, item in enumerate(row): + item_str = item.replace('\u00b2', '2') align = 'align="right"' if listy[0][i] == 'Qty' else '' - file.write(f'{item}') + file.write(f'{item_str}') file.write('') file.write('') From 35fcd112edf309513bea01d1474d82ae82eadca5 Mon Sep 17 00:00:00 2001 From: "Jason R. Jones" Date: Thu, 16 Jul 2020 13:02:36 -0400 Subject: [PATCH 05/30] Translate encountered instances of "\n" to "
" within the html-generated BOM. --- src/wireviz/Harness.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/wireviz/Harness.py b/src/wireviz/Harness.py index 1a992369..67d9e256 100644 --- a/src/wireviz/Harness.py +++ b/src/wireviz/Harness.py @@ -322,6 +322,7 @@ def output(self, filename: (str, Path), view: bool = False, cleanup: bool = True file.write('') for i, item in enumerate(row): item_str = item.replace('\u00b2', '2') + item_str = item_str.replace('\\n', '
') align = 'align="right"' if listy[0][i] == 'Qty' else '' file.write(f'{item_str}') file.write('') From be10908a5d7518094febee4cfd49d3b4f0242c69 Mon Sep 17 00:00:00 2001 From: "Jason R. Jones" Date: Fri, 17 Jul 2020 06:17:40 -0400 Subject: [PATCH 06/30] Change '' to '²' --- src/wireviz/Harness.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wireviz/Harness.py b/src/wireviz/Harness.py index 67d9e256..bcf0a389 100644 --- a/src/wireviz/Harness.py +++ b/src/wireviz/Harness.py @@ -321,7 +321,7 @@ def output(self, filename: (str, Path), view: bool = False, cleanup: bool = True for row in listy[1:]: file.write('') for i, item in enumerate(row): - item_str = item.replace('\u00b2', '2') + item_str = item.replace('\u00b2', '²') item_str = item_str.replace('\\n', '
') align = 'align="right"' if listy[0][i] == 'Qty' else '' file.write(f'{item_str}') From 5a7bc7399b395acf9a5c47d38230fa5f250f9519 Mon Sep 17 00:00:00 2001 From: "Jason R. Jones" Date: Mon, 20 Jul 2020 06:46:09 -0400 Subject: [PATCH 07/30] Removing '\\n' character, as requested. --- src/wireviz/Harness.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/wireviz/Harness.py b/src/wireviz/Harness.py index bcf0a389..1d418bf0 100644 --- a/src/wireviz/Harness.py +++ b/src/wireviz/Harness.py @@ -322,7 +322,6 @@ def output(self, filename: (str, Path), view: bool = False, cleanup: bool = True file.write('') for i, item in enumerate(row): item_str = item.replace('\u00b2', '²') - item_str = item_str.replace('\\n', '
') align = 'align="right"' if listy[0][i] == 'Qty' else '' file.write(f'{item_str}') file.write('') From c2d96e8e4c7392da77c719fcd8eb30603d0f037c Mon Sep 17 00:00:00 2001 From: Andrew Katz Date: Mon, 20 Jul 2020 19:55:24 -0400 Subject: [PATCH 08/30] Add more robust routines for BOM file output --- src/wireviz/Harness.py | 8 +++---- src/wireviz/bom_helper.py | 42 +++++++++++++++++++++++++++++++++++ src/wireviz/build_examples.py | 5 +++-- src/wireviz/wv_helper.py | 4 ++-- 4 files changed, 51 insertions(+), 8 deletions(-) create mode 100644 src/wireviz/bom_helper.py diff --git a/src/wireviz/Harness.py b/src/wireviz/Harness.py index b9804838..c0b598b6 100644 --- a/src/wireviz/Harness.py +++ b/src/wireviz/Harness.py @@ -3,9 +3,9 @@ from wireviz.DataClasses import Connector, Cable from graphviz import Graph -from wireviz import wv_colors, wv_helper +from wireviz import wv_colors, wv_helper, bom_helper from wireviz.wv_colors import get_color_hex -from wireviz.wv_helper import awg_equiv, mm2_equiv, tuplelist2tsv, \ +from wireviz.wv_helper import awg_equiv, mm2_equiv, \ nested_html_table, flatten2d, index_if_list, html_line_breaks, \ graphviz_line_breaks, remove_line_breaks, open_file_read, open_file_write from collections import Counter @@ -297,8 +297,8 @@ def output(self, filename: (str, Path), view: bool = False, cleanup: bool = True graph.save(filename=f'{filename}.gv') # bom output bom_list = self.bom_list() - with open_file_write(f'{filename}.bom.tsv') as file: - file.write(tuplelist2tsv(bom_list)) + # todo: support user choices of BOM format (probably also graphviz outputs, html outputs) + bom_helper.generate_bom_outputs(filename,bom_list,bom_helper.WIREVIZ_TSV, bom_helper.EXCEL_CSV) # HTML output with open_file_write(f'{filename}.html') as file: file.write('\n') diff --git a/src/wireviz/bom_helper.py b/src/wireviz/bom_helper.py new file mode 100644 index 00000000..d1e704f9 --- /dev/null +++ b/src/wireviz/bom_helper.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import csv +from wireviz import wv_helper +from wireviz.wv_helper import open_file_write + +EXCEL_CSV = csv.excel +EXCEL_TSV = csv.excel_tab +UNIX_CSV = csv.unix_dialect +WIREVIZ_TSV = type('Wireviz BOM', (csv.Dialect, object), dict( + delimiter='\t', + doublequote=True, + escapechar=None, + lineterminator='\n', + quoting=0, + skipinitialspace=False, + strict=False, + quotechar='"' +)) +csv.register_dialect('Wireviz BOM', WIREVIZ_TSV) + +_csv_formats = { EXCEL_CSV, UNIX_CSV } +_tsv_formats = { EXCEL_TSV, WIREVIZ_TSV } + +_csv_ext = '.bom.csv' +_tsv_ext = '.bom.tsv' + +def generate_bom_outputs(base_filename, bomdata, *argv): + expanded_csv_names = len(_csv_formats.intersection(set(argv))) > 1 + expanded_tsv_names = len(_tsv_formats.intersection(set(argv))) > 1 + for fmt in argv: + if fmt in _csv_formats: + file = csv.writer(open_file_write(base_filename + ("_" + fmt.__name__ if expanded_csv_names else "") + _csv_ext, fmt.lineterminator), fmt) + + elif fmt in _tsv_formats: + file = csv.writer(open_file_write(base_filename + ("_"+fmt.__name__ if expanded_tsv_names else "") + _tsv_ext, fmt.lineterminator), fmt) + else: + raise KeyError("Unknown BOM Format Specified") + file.writerows(wv_helper.flatten2d(bomdata)) + +# TODO: Possibly refactor other BOM output operations, such as HTML, into here? \ No newline at end of file diff --git a/src/wireviz/build_examples.py b/src/wireviz/build_examples.py index ee59ded8..9238e7db 100755 --- a/src/wireviz/build_examples.py +++ b/src/wireviz/build_examples.py @@ -74,10 +74,11 @@ def build_tutorials(): file.write(f'![](tutorial{outfile_name}.png)\n\n') - file.write(f'[Bill of Materials](tutorial{outfile_name}.bom.tsv)\n\n\n') + file.write(f'[Bill of Materials - TSV](tutorial{outfile_name}.bom.tsv)\n\n') + file.write(f'[Bill of Materials - CSV](tutorial{outfile_name}.bom.csv)\n\n\n') def clean_examples(): - generated_extensions = ['.gv', '.png', '.svg', '.html', '.bom.tsv'] + generated_extensions = ['.gv', '.png', '.svg', '.html', '.bom.tsv', '.bom.csv'] for filepath in [examples_path, demos_path, tutorials_path]: print(filepath) diff --git a/src/wireviz/wv_helper.py b/src/wireviz/wv_helper.py index 024c3ef4..966080c8 100644 --- a/src/wireviz/wv_helper.py +++ b/src/wireviz/wv_helper.py @@ -116,5 +116,5 @@ def open_file_read(filename): # TODO: Intelligently determine encoding return open(filename, 'r', encoding='UTF-8') -def open_file_write(filename): - return open(filename, 'w', encoding='UTF-8') \ No newline at end of file +def open_file_write(filename, newline='\n'): + return open(filename, 'w', encoding='UTF-8', newline=newline) \ No newline at end of file From a6344933feed8d48c26fcade2e071fbbfdb3c69e Mon Sep 17 00:00:00 2001 From: Andrew Katz Date: Mon, 20 Jul 2020 20:22:57 -0400 Subject: [PATCH 09/30] Fix todo in file read --- src/wireviz/wv_helper.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wireviz/wv_helper.py b/src/wireviz/wv_helper.py index 966080c8..17aede04 100644 --- a/src/wireviz/wv_helper.py +++ b/src/wireviz/wv_helper.py @@ -113,8 +113,8 @@ def remove_line_breaks(inp): return inp.replace('\n', ' ').rstrip() if isinstance(inp, str) else inp def open_file_read(filename): - # TODO: Intelligently determine encoding + # TODO: Intelligently determine encoding (UnicodeDammit, Chardet, cchardet are not very reliable in testing) return open(filename, 'r', encoding='UTF-8') def open_file_write(filename, newline='\n'): - return open(filename, 'w', encoding='UTF-8', newline=newline) \ No newline at end of file + return open(filename, 'w', encoding='UTF-8', newline=newline) From 5b8b043a70610b5790d1a6df914042413f6247f0 Mon Sep 17 00:00:00 2001 From: Andrew Katz Date: Mon, 20 Jul 2020 20:53:00 -0400 Subject: [PATCH 10/30] Update README.md Add csv file to generated outputs --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ed2b4df3..903e4ac2 100644 --- a/README.md +++ b/README.md @@ -99,10 +99,11 @@ $ wireviz ~/path/to/file/mywire.yml This will output the following files ``` -mywire.gv GraphViz output +mywire.gv Raw GraphViz DOT file output of wiring diagram mywire.svg Wiring diagram as vector image mywire.png Wiring diagram as raster image mywire.bom.tsv BOM (bill of materials) as tab-separated text file +mywire.bom.csv BOM (bill of materials) as comma-separated, excel-format text file mywire.html HTML page with wiring diagram and BOM embedded ``` From 54da2f2d373d14256e624091386a2d6b28d96750 Mon Sep 17 00:00:00 2001 From: "Jason R. Jones" Date: Fri, 24 Jul 2020 09:28:10 -0400 Subject: [PATCH 11/30] Add click to make CLI easier --- requirements.txt | 1 + src/wireviz/wireviz.py | 60 ++++++++++++++++++++---------------------- 2 files changed, 30 insertions(+), 31 deletions(-) diff --git a/requirements.txt b/requirements.txt index 93394812..5a6667c4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ . +click graphviz pyyaml setuptools \ No newline at end of file diff --git a/src/wireviz/wireviz.py b/src/wireviz/wireviz.py index 3adc0782..946111fb 100755 --- a/src/wireviz/wireviz.py +++ b/src/wireviz/wireviz.py @@ -7,6 +7,7 @@ import sys from typing import Any, Tuple +import click import yaml if __name__ == '__main__': @@ -58,7 +59,6 @@ def parse(yaml_input: str, file_out: (str, Path) = None, return_types: (None, st yaml_data[sec] = [] # add connections - def check_designators(what, where): # helper function for i, x in enumerate(what): if x not in yaml_data[where[i]]: @@ -207,45 +207,43 @@ def parse_file(yaml_file: str, file_out: (str, Path) = None) -> None: parse(yaml_input, file_out=file_out) -def parse_cmdline(): - parser = argparse.ArgumentParser( - description='Generate cable and wiring harness documentation from YAML descriptions', - ) - parser.add_argument('input_file', action='store', type=str, metavar='YAML_FILE') - parser.add_argument('-o', '--output_file', action='store', type=str, metavar='OUTPUT') - parser.add_argument('--generate-bom', action='store_true', default=True) - parser.add_argument('--prepend-file', action='store', type=str, metavar='YAML_FILE') - return parser.parse_args() - - -def main(): - - args = parse_cmdline() - - if not os.path.exists(args.input_file): - print(f'Error: input file {args.input_file} inaccessible or does not exist, check path') +@click.command() +@click.argument('input_file', required=True) +@click.option('--prepend', '-p', default=None, help='a YAML file containing a library of templates and parts that may be referenced in the `input_file`') +@click.option('--out_types', '-o', multiple=True, default=['png'], help='the output formats to be generated') +def main(input_file, prepend, out_types): + if not os.path.exists(input_file): + print(f'Error: input file {input_file} inaccessible or does not exist, check path') sys.exit(1) - with open_file_read(args.input_file) as fh: + with open_file_read(input_file) as fh: yaml_input = fh.read() - if args.prepend_file: - if not os.path.exists(args.prepend_file): - print(f'Error: prepend input file {args.prepend_file} inaccessible or does not exist, check path') + if prepend: + if not os.path.exists(prepend): + print(f'Error: prepend input file {prepend} inaccessible or does not exist, check path') sys.exit(1) - with open_file_read(args.prepend_file) as fh: + with open_file_read(prepend) as fh: prepend = fh.read() yaml_input = prepend + yaml_input - if not args.output_file: - file_out = args.input_file - pre, _ = os.path.splitext(file_out) - file_out = pre # extension will be added by graphviz output function + input_file = Path(input_file) + base_file_name = input_file.name.replace(input_file.suffix, '') + + # the parse function may return a single instance or a tuple, thus, the + # if/then determines if there is only one thing that must be saved or multiple + filedatas = parse(yaml_input, return_types=out_types) + if isinstance(filedatas, tuple): + for ext, data in zip(out_types, filedatas): + fname = f'{base_file_name}.{ext}' + with open(fname, 'wb') as f: + f.write(data) else: - file_out = args.output_file - file_out = os.path.abspath(file_out) - - parse(yaml_input, file_out=file_out) + ext = out_types[0] + data = filedatas + fname = f'{base_file_name}.{ext}' + with open(fname, 'wb') as f: + f.write(data) if __name__ == '__main__': From 4fe84b4926e82d6d5f9744db3214afddd39c83ae Mon Sep 17 00:00:00 2001 From: "Jason R. Jones" Date: Fri, 24 Jul 2020 09:50:42 -0400 Subject: [PATCH 12/30] Remove unused dependency --- src/wireviz/wireviz.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/wireviz/wireviz.py b/src/wireviz/wireviz.py index 946111fb..3596b1ed 100755 --- a/src/wireviz/wireviz.py +++ b/src/wireviz/wireviz.py @@ -1,7 +1,6 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -import argparse import os from pathlib import Path import sys From 028f2953312c84e9cf17aee1849b1f294f4a157e Mon Sep 17 00:00:00 2001 From: "Jason R. Jones" Date: Fri, 24 Jul 2020 09:58:01 -0400 Subject: [PATCH 13/30] Updating setup.py to include `click` --- setup.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index 09621bb7..f642a8be 100644 --- a/setup.py +++ b/setup.py @@ -6,6 +6,12 @@ project_name = 'wireviz' +# utility function to read the requirements.txt file +with open('requirements.txt', 'r') as f: + requirements = f.readlines() + requirements = [r.strip() for r in requirements] + requirements = [r for r in requirements if r != '.'] + # Utility function to read the README file. # Used for the long_description. It's nice, because now 1) we have a top level # README file and 2) it's easier to type in the README file than to put a raw @@ -13,6 +19,7 @@ def read(fname): return open(os.path.join(os.path.dirname(__file__), fname)).read() + setup( name=project_name, version='0.1', @@ -21,10 +28,7 @@ def read(fname): description='Easily document cables and wiring harnesses', long_description=read(os.path.join(os.path.dirname(__file__), 'README.md')), long_description_content_type='text/markdown', - install_requires=[ - 'pyyaml', - 'graphviz', - ], + install_requires=requirements, license='GPLv3', keywords='cable connector hardware harness wiring wiring-diagram wiring-harness', url='https://github.com/formatc1702/WireViz', From 3009a1725d69091af2ef741ba3a6134adaea4533 Mon Sep 17 00:00:00 2001 From: "Jason R. Jones" Date: Fri, 24 Jul 2020 10:16:45 -0400 Subject: [PATCH 14/30] Better document string --- src/wireviz/wireviz.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/wireviz/wireviz.py b/src/wireviz/wireviz.py index 3596b1ed..c947a714 100755 --- a/src/wireviz/wireviz.py +++ b/src/wireviz/wireviz.py @@ -209,8 +209,14 @@ def parse_file(yaml_file: str, file_out: (str, Path) = None) -> None: @click.command() @click.argument('input_file', required=True) @click.option('--prepend', '-p', default=None, help='a YAML file containing a library of templates and parts that may be referenced in the `input_file`') -@click.option('--out_types', '-o', multiple=True, default=['png'], help='the output formats to be generated') -def main(input_file, prepend, out_types): +@click.option('--out', '-o', multiple=True, default=['png'], help='specify one or more output types to be generated; currently supports "png" "svg"') +def main(input_file, prepend, out): + """ + Take a YAML file containing a harness specification and, utilizing GraphViz, + translate that specification into various output formats. Example: + + $>wireviz my_input_file.yml -o png -o svg + """ if not os.path.exists(input_file): print(f'Error: input file {input_file} inaccessible or does not exist, check path') sys.exit(1) @@ -231,14 +237,14 @@ def main(input_file, prepend, out_types): # the parse function may return a single instance or a tuple, thus, the # if/then determines if there is only one thing that must be saved or multiple - filedatas = parse(yaml_input, return_types=out_types) + filedatas = parse(yaml_input, return_types=out) if isinstance(filedatas, tuple): - for ext, data in zip(out_types, filedatas): + for ext, data in zip(out, filedatas): fname = f'{base_file_name}.{ext}' with open(fname, 'wb') as f: f.write(data) else: - ext = out_types[0] + ext = out[0] data = filedatas fname = f'{base_file_name}.{ext}' with open(fname, 'wb') as f: From 6b3a89edebb08271235a6f7703f40c0147589536 Mon Sep 17 00:00:00 2001 From: Andrew Katz Date: Fri, 24 Jul 2020 15:35:58 -0400 Subject: [PATCH 15/30] Addressed review comments --- src/wireviz/Harness.py | 2 +- src/wireviz/bom_helper.py | 15 +++++++++++---- src/wireviz/wv_helper.py | 1 - 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/wireviz/Harness.py b/src/wireviz/Harness.py index c0b598b6..80d250a4 100644 --- a/src/wireviz/Harness.py +++ b/src/wireviz/Harness.py @@ -298,7 +298,7 @@ def output(self, filename: (str, Path), view: bool = False, cleanup: bool = True # bom output bom_list = self.bom_list() # todo: support user choices of BOM format (probably also graphviz outputs, html outputs) - bom_helper.generate_bom_outputs(filename,bom_list,bom_helper.WIREVIZ_TSV, bom_helper.EXCEL_CSV) + bom_helper.generate_bom_outputs(filename,bom_list, [bom_helper.WIREVIZ_TSV, bom_helper.EXCEL_CSV]) # HTML output with open_file_write(f'{filename}.html') as file: file.write('\n') diff --git a/src/wireviz/bom_helper.py b/src/wireviz/bom_helper.py index d1e704f9..218b4137 100644 --- a/src/wireviz/bom_helper.py +++ b/src/wireviz/bom_helper.py @@ -26,10 +26,17 @@ _csv_ext = '.bom.csv' _tsv_ext = '.bom.tsv' -def generate_bom_outputs(base_filename, bomdata, *argv): - expanded_csv_names = len(_csv_formats.intersection(set(argv))) > 1 - expanded_tsv_names = len(_tsv_formats.intersection(set(argv))) > 1 - for fmt in argv: +def generate_bom_outputs(base_filename, bomdata, formats=None): + if formats is None: + formats = [EXCEL_CSV, WIREVIZ_TSV] + elif isinstance(formats, csv.Dialect): + formats = [formats] + elif not isinstance(formats, list): + raise TypeError + expanded_csv_names = len(_csv_formats.intersection(set(formats))) > 1 + expanded_tsv_names = len(_tsv_formats.intersection(set(formats))) > 1 + + for fmt in formats: if fmt in _csv_formats: file = csv.writer(open_file_write(base_filename + ("_" + fmt.__name__ if expanded_csv_names else "") + _csv_ext, fmt.lineterminator), fmt) diff --git a/src/wireviz/wv_helper.py b/src/wireviz/wv_helper.py index 17aede04..703eb8ca 100644 --- a/src/wireviz/wv_helper.py +++ b/src/wireviz/wv_helper.py @@ -113,7 +113,6 @@ def remove_line_breaks(inp): return inp.replace('\n', ' ').rstrip() if isinstance(inp, str) else inp def open_file_read(filename): - # TODO: Intelligently determine encoding (UnicodeDammit, Chardet, cchardet are not very reliable in testing) return open(filename, 'r', encoding='UTF-8') def open_file_write(filename, newline='\n'): From bac99f0d807b27dca6f37413e3bbf6435aaa0abb Mon Sep 17 00:00:00 2001 From: Andrew Katz Date: Fri, 24 Jul 2020 15:57:32 -0400 Subject: [PATCH 16/30] Add ability to specify default color --- src/wireviz/Harness.py | 4 ++-- src/wireviz/wv_colors.py | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/wireviz/Harness.py b/src/wireviz/Harness.py index 80d250a4..2792c2c2 100644 --- a/src/wireviz/Harness.py +++ b/src/wireviz/Harness.py @@ -67,7 +67,7 @@ def create_graph(self) -> Graph: font = 'arial' dot.attr('graph', rankdir='LR', ranksep='2', - bgcolor='white', + bgcolor=wv_colors.COLOR_BACKGROUND, nodesep='0.33', fontname=font) dot.attr('node', shape='record', @@ -302,7 +302,7 @@ def output(self, filename: (str, Path), view: bool = False, cleanup: bool = True # HTML output with open_file_write(f'{filename}.html') as file: file.write('\n') - file.write('') + file.write(f'') file.write('

Diagram

') with open_file_read(f'{filename}.svg') as svg: diff --git a/src/wireviz/wv_colors.py b/src/wireviz/wv_colors.py index 9e49d425..e7b7c6dc 100644 --- a/src/wireviz/wv_colors.py +++ b/src/wireviz/wv_colors.py @@ -98,12 +98,13 @@ } -color_default = '#ffffff' +COLOR_DEFAULT = '#ffffff' +COLOR_BACKGROUND = '#ffffff' def get_color_hex(input, pad=False): if input is None or input == '': - return [color_default] + return [COLOR_DEFAULT] if len(input) == 4: # give wires with EXACTLY 2 colors that striped/banded look input = input + input[:2] # hacky style fix: give single color wires a triple-up so that wires are the same size @@ -113,7 +114,7 @@ def get_color_hex(input, pad=False): output = [_color_hex[input[i:i + 2]] for i in range(0, len(input), 2)] except KeyError: print("Unknown color specified") - output = [color_default] + output = [COLOR_DEFAULT] return output From bbf92bb5fedb56d3bfe8c7958c9e843e0ba76edf Mon Sep 17 00:00:00 2001 From: "Jason R. Jones" Date: Fri, 24 Jul 2020 19:22:22 -0400 Subject: [PATCH 17/30] Remove unused function --- src/wireviz/wireviz.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/wireviz/wireviz.py b/src/wireviz/wireviz.py index c947a714..04814f2c 100755 --- a/src/wireviz/wireviz.py +++ b/src/wireviz/wireviz.py @@ -57,13 +57,6 @@ def parse(yaml_input: str, file_out: (str, Path) = None, return_types: (None, st elif ty == list: yaml_data[sec] = [] - # add connections - def check_designators(what, where): # helper function - for i, x in enumerate(what): - if x not in yaml_data[where[i]]: - return False - return True - autogenerated_ids = {} for connection in yaml_data['connections']: # find first component (potentially nested inside list or dict) From 126f525c18040b753ad252c0418271bcf89f60a1 Mon Sep 17 00:00:00 2001 From: "Jason R. Jones" Date: Fri, 24 Jul 2020 20:01:57 -0400 Subject: [PATCH 18/30] Add `html` sttribute to `Harness`, using it in the `parse` function --- src/wireviz/Harness.py | 40 +++++++++++++++++++++++++++++++++++++++ src/wireviz/bom_helper.py | 1 + src/wireviz/wireviz.py | 2 ++ 3 files changed, 43 insertions(+) diff --git a/src/wireviz/Harness.py b/src/wireviz/Harness.py index c2ce5de3..29d0e0bb 100644 --- a/src/wireviz/Harness.py +++ b/src/wireviz/Harness.py @@ -288,6 +288,46 @@ def svg(self): data.seek(0) return data.read() + @property + def html(self): + self.create_graph() + + bom_list = self.bom_list() + string = '' + + string += '\n' + string += f'' + + string += '

Diagram

' + + svg = self.svg.decode('utf-8') + svg = re.sub( + '^<[?]xml [^?>]*[?]>[^<]*]*>', + '', + svg) + for svgdata in svg: + string += svgdata + + string += '

Bill of Materials

' + listy = flatten2d(bom_list) + string += '' + string += '' + for item in listy[0]: + string += f'' + string += '' + for row in listy[1:]: + string += '' + for i, item in enumerate(row): + item_str = item.replace('\u00b2', '²') + align = 'align="right"' if listy[0][i] == 'Qty' else '' + string += f'' + string += '' + string += '
{item}
{item_str}
' + + string += '' + + return bytes(string, encoding='utf-8') + def output(self, filename: (str, Path), view: bool = False, cleanup: bool = True, fmt: tuple = ('pdf', )) -> None: # graphical output graph = self.create_graph() diff --git a/src/wireviz/bom_helper.py b/src/wireviz/bom_helper.py index 218b4137..2bcc27cc 100644 --- a/src/wireviz/bom_helper.py +++ b/src/wireviz/bom_helper.py @@ -26,6 +26,7 @@ _csv_ext = '.bom.csv' _tsv_ext = '.bom.tsv' + def generate_bom_outputs(base_filename, bomdata, formats=None): if formats is None: formats = [EXCEL_CSV, WIREVIZ_TSV] diff --git a/src/wireviz/wireviz.py b/src/wireviz/wireviz.py index 04814f2c..e6aed081 100755 --- a/src/wireviz/wireviz.py +++ b/src/wireviz/wireviz.py @@ -181,6 +181,8 @@ def parse(yaml_input: str, file_out: (str, Path) = None, return_types: (None, st returns.append(harness.png) if rt == 'svg': returns.append(harness.svg) + if rt == 'html': + returns.append(harness.html) if rt == 'harness': returns.append(harness) From 2d9c6e6536c5b163bfed54977b8b36db22dedd30 Mon Sep 17 00:00:00 2001 From: "Jason R. Jones" Date: Fri, 24 Jul 2020 20:13:56 -0400 Subject: [PATCH 19/30] Add skeleton of csv file outputs. --- src/wireviz/Harness.py | 20 ++++++++++++++++++++ src/wireviz/wireviz.py | 22 ++++++++++++++++++---- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/src/wireviz/Harness.py b/src/wireviz/Harness.py index 29d0e0bb..1c62052a 100644 --- a/src/wireviz/Harness.py +++ b/src/wireviz/Harness.py @@ -328,6 +328,26 @@ def html(self): return bytes(string, encoding='utf-8') + @property + def csv(self): + raise NotImplementedError + + @property + def csv_excel(self): + raise NotImplementedError + + @property + def csv_unix(self): + raise NotImplementedError + + @property + def tsv(self): + raise NotImplementedError + + @property + def tsv_excel(self): + raise NotImplementedError + def output(self, filename: (str, Path), view: bool = False, cleanup: bool = True, fmt: tuple = ('pdf', )) -> None: # graphical output graph = self.create_graph() diff --git a/src/wireviz/wireviz.py b/src/wireviz/wireviz.py index e6aed081..6d3fcecf 100755 --- a/src/wireviz/wireviz.py +++ b/src/wireviz/wireviz.py @@ -179,12 +179,24 @@ def parse(yaml_input: str, file_out: (str, Path) = None, return_types: (None, st for rt in return_types: if rt == 'png': returns.append(harness.png) - if rt == 'svg': + elif rt == 'svg': returns.append(harness.svg) - if rt == 'html': + elif rt == 'html': returns.append(harness.html) - if rt == 'harness': + elif rt == 'csv': + returns.append(harness.csv) + elif rt == 'csv_excel': + returns.append(harness.csv_excel) + elif rt == 'csv_unix': + returns.append(harness.csv_unix) + elif rt == 'tsv': + returns.append(harness.tsv) + elif rt == 'tsv_excel': + returns.append(harness.tsv_excel) + elif rt == 'harness': returns.append(harness) + else: + raise ValueError(f'return type "{rt}" not supported') return tuple(returns) if len(returns) != 1 else returns[0] @@ -208,7 +220,9 @@ def parse_file(yaml_file: str, file_out: (str, Path) = None) -> None: def main(input_file, prepend, out): """ Take a YAML file containing a harness specification and, utilizing GraphViz, - translate that specification into various output formats. Example: + translate that specification into various output formats. + + Example: $>wireviz my_input_file.yml -o png -o svg """ From 1f535bc52d87702573c87c65ddd422f0257b1ffd Mon Sep 17 00:00:00 2001 From: "Jason R. Jones" Date: Fri, 24 Jul 2020 20:16:55 -0400 Subject: [PATCH 20/30] Add html to document string --- src/wireviz/wireviz.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wireviz/wireviz.py b/src/wireviz/wireviz.py index 6d3fcecf..0797ef42 100755 --- a/src/wireviz/wireviz.py +++ b/src/wireviz/wireviz.py @@ -216,7 +216,7 @@ def parse_file(yaml_file: str, file_out: (str, Path) = None) -> None: @click.command() @click.argument('input_file', required=True) @click.option('--prepend', '-p', default=None, help='a YAML file containing a library of templates and parts that may be referenced in the `input_file`') -@click.option('--out', '-o', multiple=True, default=['png'], help='specify one or more output types to be generated; currently supports "png" "svg"') +@click.option('--out', '-o', multiple=True, default=['png'], help='specify one or more output types to be generated; currently supports "png" "svg", "html"') def main(input_file, prepend, out): """ Take a YAML file containing a harness specification and, utilizing GraphViz, From 8ba17d83a680e6981b7b545c003975223369240f Mon Sep 17 00:00:00 2001 From: "Jason R. Jones" Date: Fri, 24 Jul 2020 20:20:34 -0400 Subject: [PATCH 21/30] Remove unnecessary method --- src/wireviz/Harness.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/wireviz/Harness.py b/src/wireviz/Harness.py index 1c62052a..d7e08591 100644 --- a/src/wireviz/Harness.py +++ b/src/wireviz/Harness.py @@ -290,8 +290,6 @@ def svg(self): @property def html(self): - self.create_graph() - bom_list = self.bom_list() string = '' From def8f133314a926c028933166935585ac25f4f53 Mon Sep 17 00:00:00 2001 From: "Jason R. Jones" Date: Fri, 24 Jul 2020 21:01:52 -0400 Subject: [PATCH 22/30] Fill out each BOM attribute, enable BOM output in various formats. --- src/wireviz/Harness.py | 27 +++++++++++++++++------ src/wireviz/bom_helper.py | 45 +++++++++++++++++---------------------- src/wireviz/wireviz.py | 8 ++++++- 3 files changed, 47 insertions(+), 33 deletions(-) diff --git a/src/wireviz/Harness.py b/src/wireviz/Harness.py index d7e08591..4d9fd519 100644 --- a/src/wireviz/Harness.py +++ b/src/wireviz/Harness.py @@ -328,23 +328,38 @@ def html(self): @property def csv(self): - raise NotImplementedError + return bom_helper.generate_bom_outputs( + self.bom_list(), + 'csv' + ) @property def csv_excel(self): - raise NotImplementedError + return bom_helper.generate_bom_outputs( + self.bom_list(), + 'csv_excel' + ) @property def csv_unix(self): - raise NotImplementedError + return bom_helper.generate_bom_outputs( + self.bom_list(), + 'csv_unix' + ) @property def tsv(self): - raise NotImplementedError + return bom_helper.generate_bom_outputs( + self.bom_list(), + 'tsv' + ) @property def tsv_excel(self): - raise NotImplementedError + return bom_helper.generate_bom_outputs( + self.bom_list(), + 'tsv_excel' + ) def output(self, filename: (str, Path), view: bool = False, cleanup: bool = True, fmt: tuple = ('pdf', )) -> None: # graphical output @@ -356,7 +371,7 @@ def output(self, filename: (str, Path), view: bool = False, cleanup: bool = True # bom output bom_list = self.bom_list() # todo: support user choices of BOM format (probably also graphviz outputs, html outputs) - bom_helper.generate_bom_outputs(filename,bom_list, [bom_helper.WIREVIZ_TSV, bom_helper.EXCEL_CSV]) + bom_helper.generate_bom_outputs(filename, bom_list, [bom_helper.WIREVIZ_TSV, bom_helper.EXCEL_CSV]) # HTML output with open_file_write(f'{filename}.html') as file: file.write('\n') diff --git a/src/wireviz/bom_helper.py b/src/wireviz/bom_helper.py index 2bcc27cc..c6b3cbcd 100644 --- a/src/wireviz/bom_helper.py +++ b/src/wireviz/bom_helper.py @@ -2,12 +2,10 @@ # -*- coding: utf-8 -*- import csv +from io import StringIO + from wireviz import wv_helper -from wireviz.wv_helper import open_file_write -EXCEL_CSV = csv.excel -EXCEL_TSV = csv.excel_tab -UNIX_CSV = csv.unix_dialect WIREVIZ_TSV = type('Wireviz BOM', (csv.Dialect, object), dict( delimiter='\t', doublequote=True, @@ -20,31 +18,26 @@ )) csv.register_dialect('Wireviz BOM', WIREVIZ_TSV) -_csv_formats = { EXCEL_CSV, UNIX_CSV } -_tsv_formats = { EXCEL_TSV, WIREVIZ_TSV } - -_csv_ext = '.bom.csv' -_tsv_ext = '.bom.tsv' +def generate_bom_outputs(bomdata, dialect='tsv'): + dialect = dialect.strip().lower() + dialect_lookup = { + 'csv': csv.unix_dialect, + 'csv_unix': csv.unix_dialect, + 'csv_excel': csv.excel, + 'tsv': WIREVIZ_TSV, + 'tsv_excel': csv.excel_tab + } + valid_dialects = [k for k in dialect_lookup.keys()] + if dialect not in valid_dialects: + raise ValueError(f'dialect "{dialect}" not supported') -def generate_bom_outputs(base_filename, bomdata, formats=None): - if formats is None: - formats = [EXCEL_CSV, WIREVIZ_TSV] - elif isinstance(formats, csv.Dialect): - formats = [formats] - elif not isinstance(formats, list): - raise TypeError - expanded_csv_names = len(_csv_formats.intersection(set(formats))) > 1 - expanded_tsv_names = len(_tsv_formats.intersection(set(formats))) > 1 + output = StringIO() + writer = csv.writer(output, dialect=dialect_lookup[dialect]) + writer.writerows(wv_helper.flatten2d(bomdata)) - for fmt in formats: - if fmt in _csv_formats: - file = csv.writer(open_file_write(base_filename + ("_" + fmt.__name__ if expanded_csv_names else "") + _csv_ext, fmt.lineterminator), fmt) + output.seek(0) - elif fmt in _tsv_formats: - file = csv.writer(open_file_write(base_filename + ("_"+fmt.__name__ if expanded_tsv_names else "") + _tsv_ext, fmt.lineterminator), fmt) - else: - raise KeyError("Unknown BOM Format Specified") - file.writerows(wv_helper.flatten2d(bomdata)) + return bytes(output.read(), encoding='utf-8') # TODO: Possibly refactor other BOM output operations, such as HTML, into here? \ No newline at end of file diff --git a/src/wireviz/wireviz.py b/src/wireviz/wireviz.py index 0797ef42..9f9697b5 100755 --- a/src/wireviz/wireviz.py +++ b/src/wireviz/wireviz.py @@ -249,7 +249,13 @@ def main(input_file, prepend, out): filedatas = parse(yaml_input, return_types=out) if isinstance(filedatas, tuple): for ext, data in zip(out, filedatas): - fname = f'{base_file_name}.{ext}' + if 'csv' in ext: + extension = 'bom.csv' + elif 'tsv' in ext: + extension = 'bom.tsv' + else: + extension = ext + fname = f'{base_file_name}.{extension}' with open(fname, 'wb') as f: f.write(data) else: From 4747f2e173a5be44077b90e277eeaf2611b6296c Mon Sep 17 00:00:00 2001 From: "Jason R. Jones" Date: Fri, 24 Jul 2020 21:06:57 -0400 Subject: [PATCH 23/30] Refactor `bom()` and `bom_list` methods into private methods. --- src/wireviz/Harness.py | 61 +++++++----------------------------------- 1 file changed, 9 insertions(+), 52 deletions(-) diff --git a/src/wireviz/Harness.py b/src/wireviz/Harness.py index 4d9fd519..a1153a06 100644 --- a/src/wireviz/Harness.py +++ b/src/wireviz/Harness.py @@ -290,7 +290,7 @@ def svg(self): @property def html(self): - bom_list = self.bom_list() + bom_list = self._bom_list() string = '' string += '\n' @@ -329,82 +329,39 @@ def html(self): @property def csv(self): return bom_helper.generate_bom_outputs( - self.bom_list(), + self._bom_list(), 'csv' ) @property def csv_excel(self): return bom_helper.generate_bom_outputs( - self.bom_list(), + self._bom_list(), 'csv_excel' ) @property def csv_unix(self): return bom_helper.generate_bom_outputs( - self.bom_list(), + self._bom_list(), 'csv_unix' ) @property def tsv(self): return bom_helper.generate_bom_outputs( - self.bom_list(), + self._bom_list(), 'tsv' ) @property def tsv_excel(self): return bom_helper.generate_bom_outputs( - self.bom_list(), + self._bom_list(), 'tsv_excel' ) - def output(self, filename: (str, Path), view: bool = False, cleanup: bool = True, fmt: tuple = ('pdf', )) -> None: - # graphical output - graph = self.create_graph() - for f in fmt: - graph.format = f - graph.render(filename=filename, view=view, cleanup=cleanup) - graph.save(filename=f'{filename}.gv') - # bom output - bom_list = self.bom_list() - # todo: support user choices of BOM format (probably also graphviz outputs, html outputs) - bom_helper.generate_bom_outputs(filename, bom_list, [bom_helper.WIREVIZ_TSV, bom_helper.EXCEL_CSV]) - # HTML output - with open_file_write(f'{filename}.html') as file: - file.write('\n') - file.write(f'') - - file.write('

Diagram

') - with open_file_read(f'{filename}.svg') as svg: - file.write(re.sub( - '^<[?]xml [^?>]*[?]>[^<]*]*>', - '', - svg.read(1024), 1)) - for svgdata in svg: - file.write(svgdata) - - file.write('

Bill of Materials

') - listy = flatten2d(bom_list) - file.write('') - file.write('') - for item in listy[0]: - file.write(f'') - file.write('') - for row in listy[1:]: - file.write('') - for i, item in enumerate(row): - item_str = item.replace('\u00b2', '²') - align = 'align="right"' if listy[0][i] == 'Qty' else '' - file.write(f'') - file.write('') - file.write('
{item}
{item_str}
') - - file.write('') - - def bom(self): + def _bom(self): bom = [] bom_connectors = [] bom_cables = [] @@ -484,8 +441,8 @@ def bom(self): bom.extend(bom_extra) return bom - def bom_list(self): - bom = self.bom() + def _bom_list(self): + bom = self._bom() keys = ['item', 'qty', 'unit', 'designators'] # these BOM columns will always be included for fieldname in ['manufacturer', 'manufacturer part number', 'internal part number']: # these optional BOM columns will only be included if at least one BOM item actually uses them if any(fieldname in x and x.get(fieldname, None) for x in bom): From f64fac0b62db9cf9d88237912766a0f571d958c2 Mon Sep 17 00:00:00 2001 From: "Jason R. Jones" Date: Fri, 24 Jul 2020 21:10:24 -0400 Subject: [PATCH 24/30] Remove unused parameter --- src/wireviz/wireviz.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/wireviz/wireviz.py b/src/wireviz/wireviz.py index 9f9697b5..b9f1f056 100755 --- a/src/wireviz/wireviz.py +++ b/src/wireviz/wireviz.py @@ -17,12 +17,11 @@ from wireviz.wv_helper import expand, open_file_read -def parse(yaml_input: str, file_out: (str, Path) = None, return_types: (None, str, Tuple[str]) = None) -> Any: +def parse(yaml_input: str, return_types: (None, str, Tuple[str]) = None) -> Any: """ Parses yaml input string and does the high-level harness conversion :param yaml_input: a string containing the yaml input data - :param file_out: :param return_types: if None, then returns None; if the value is a string, then a corresponding data format will be returned; if the value is a tuple of strings, then for every valid format in the `return_types` tuple, another return type @@ -166,9 +165,6 @@ def parse(yaml_input: str, file_out: (str, Path) = None, return_types: (None, st for line in yaml_data["additional_bom_items"]: harness.add_bom_item(line) - if file_out is not None: - harness.output(filename=file_out, fmt=('png', 'svg'), view=False) - if return_types is not None: returns = [] if isinstance(return_types, str): # only one return type speficied From 4e1d716df9fd66fae327ebaf400ce3ee07e5a414 Mon Sep 17 00:00:00 2001 From: "Jason R. Jones" Date: Fri, 24 Jul 2020 21:15:52 -0400 Subject: [PATCH 25/30] Add placeholder for gv output --- src/wireviz/Harness.py | 5 +++++ src/wireviz/wireviz.py | 2 ++ 2 files changed, 7 insertions(+) diff --git a/src/wireviz/Harness.py b/src/wireviz/Harness.py index a1153a06..36f55317 100644 --- a/src/wireviz/Harness.py +++ b/src/wireviz/Harness.py @@ -270,6 +270,11 @@ def create_graph(self) -> Graph: return dot + @property + def gv(self): + # self.create_graph().save() + raise NotImplementedError + @property def png(self): from io import BytesIO diff --git a/src/wireviz/wireviz.py b/src/wireviz/wireviz.py index b9f1f056..c1699a91 100755 --- a/src/wireviz/wireviz.py +++ b/src/wireviz/wireviz.py @@ -189,6 +189,8 @@ def parse(yaml_input: str, return_types: (None, str, Tuple[str]) = None) -> Any: returns.append(harness.tsv) elif rt == 'tsv_excel': returns.append(harness.tsv_excel) + elif rt == 'gv': + returns.append(harness.gv) elif rt == 'harness': returns.append(harness) else: From 6cb86172b38cc283ad6a97d9a403c462de7dfba7 Mon Sep 17 00:00:00 2001 From: "Jason R. Jones" Date: Fri, 24 Jul 2020 21:17:56 -0400 Subject: [PATCH 26/30] Add better help string --- src/wireviz/wireviz.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wireviz/wireviz.py b/src/wireviz/wireviz.py index c1699a91..d8c4c1c4 100755 --- a/src/wireviz/wireviz.py +++ b/src/wireviz/wireviz.py @@ -214,7 +214,7 @@ def parse_file(yaml_file: str, file_out: (str, Path) = None) -> None: @click.command() @click.argument('input_file', required=True) @click.option('--prepend', '-p', default=None, help='a YAML file containing a library of templates and parts that may be referenced in the `input_file`') -@click.option('--out', '-o', multiple=True, default=['png'], help='specify one or more output types to be generated; currently supports "png" "svg", "html"') +@click.option('--out', '-o', multiple=True, default=['png'], help='specify one or more output types to be generated; currently supports "png" "svg", "html", "csv", "csv_excel", "csv_unix", "tsv", "tsv_excel"') def main(input_file, prepend, out): """ Take a YAML file containing a harness specification and, utilizing GraphViz, From 5197298cafd1a754f705b9ef08a7c14f713976f2 Mon Sep 17 00:00:00 2001 From: "Jason R. Jones" Date: Fri, 24 Jul 2020 21:23:15 -0400 Subject: [PATCH 27/30] More concise saving of data --- src/wireviz/wireviz.py | 39 ++++++++++++--------------------------- 1 file changed, 12 insertions(+), 27 deletions(-) diff --git a/src/wireviz/wireviz.py b/src/wireviz/wireviz.py index d8c4c1c4..fe51cc73 100755 --- a/src/wireviz/wireviz.py +++ b/src/wireviz/wireviz.py @@ -199,18 +199,6 @@ def parse(yaml_input: str, return_types: (None, str, Tuple[str]) = None) -> Any: return tuple(returns) if len(returns) != 1 else returns[0] -def parse_file(yaml_file: str, file_out: (str, Path) = None) -> None: - with open_file_read(yaml_file) as file: - yaml_input = file.read() - - if not file_out: - fn, fext = os.path.splitext(yaml_file) - file_out = fn - file_out = os.path.abspath(file_out) - - parse(yaml_input, file_out=file_out) - - @click.command() @click.argument('input_file', required=True) @click.option('--prepend', '-p', default=None, help='a YAML file containing a library of templates and parts that may be referenced in the `input_file`') @@ -245,21 +233,18 @@ def main(input_file, prepend, out): # the parse function may return a single instance or a tuple, thus, the # if/then determines if there is only one thing that must be saved or multiple filedatas = parse(yaml_input, return_types=out) - if isinstance(filedatas, tuple): - for ext, data in zip(out, filedatas): - if 'csv' in ext: - extension = 'bom.csv' - elif 'tsv' in ext: - extension = 'bom.tsv' - else: - extension = ext - fname = f'{base_file_name}.{extension}' - with open(fname, 'wb') as f: - f.write(data) - else: - ext = out[0] - data = filedatas - fname = f'{base_file_name}.{ext}' + if not isinstance(filedatas, tuple): + filedatas = (filedatas, ) + + for ext, data in zip(out, filedatas): + if 'csv' in ext: + extension = 'bom.csv' + elif 'tsv' in ext: + extension = 'bom.tsv' + else: + extension = ext + + fname = f'{base_file_name}.{extension}' with open(fname, 'wb') as f: f.write(data) From 52f79fee40bc49598fcc72a929a271dc2dde21fd Mon Sep 17 00:00:00 2001 From: "Jason R. Jones" Date: Fri, 24 Jul 2020 21:27:55 -0400 Subject: [PATCH 28/30] Refactoring to build the examples --- src/wireviz/build_examples.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/wireviz/build_examples.py b/src/wireviz/build_examples.py index 9238e7db..f0480722 100755 --- a/src/wireviz/build_examples.py +++ b/src/wireviz/build_examples.py @@ -25,7 +25,8 @@ def build_demos(): abspath = os.path.join(demos_path, fn) print(abspath) - wireviz.parse_file(abspath) + wireviz.main(abspath, prepend=None, out=['png', 'svg', 'html', 'csv']) + def build_examples(): with open_file_write(os.path.join(examples_path, readme)) as file: @@ -37,14 +38,14 @@ def build_examples(): abspath = os.path.join(examples_path, fn) outfile_name = abspath.split(".yml")[0] - print(abspath) - wireviz.parse_file(abspath) + wireviz.main(abspath, prepend=None, out=['png', 'svg', 'html', 'csv']) file.write(f'## Example {i}\n') file.write(f'![]({outfile_name}.png)\n\n') file.write(f'[Source]({fn}) - [Bill of Materials]({outfile_name}.bom.tsv)\n\n\n') + def build_tutorials(): with open_file_write(os.path.join(tutorials_path, readme)) as file: file.write('# WireViz Tutorial\n') @@ -54,7 +55,7 @@ def build_tutorials(): abspath = os.path.join(tutorials_path, fn) print(abspath) - wireviz.parse_file(abspath) + wireviz.main(abspath, prepend=None, out=['png', 'svg', 'html', 'csv']) outfile_name = abspath.split(".yml")[0] @@ -77,6 +78,7 @@ def build_tutorials(): file.write(f'[Bill of Materials - TSV](tutorial{outfile_name}.bom.tsv)\n\n') file.write(f'[Bill of Materials - CSV](tutorial{outfile_name}.bom.csv)\n\n\n') + def clean_examples(): generated_extensions = ['.gv', '.png', '.svg', '.html', '.bom.tsv', '.bom.csv'] @@ -96,6 +98,8 @@ def parse_args(): parser.add_argument('action', nargs='?', action='store', default='build') parser.add_argument('-generate', nargs='*', choices=['examples', 'demos', 'tutorials'], default=['examples', 'demos', 'tutorials']) return parser.parse_args() + + def main(): args = parse_args() if args.action == 'build': From 50ff1a046a742647a1af3abc5f035f643a0523c3 Mon Sep 17 00:00:00 2001 From: "Jason R. Jones" Date: Fri, 24 Jul 2020 22:01:56 -0400 Subject: [PATCH 29/30] Separated into `cli()` and `main()` to enable build script to call `main()` --- src/wireviz/wireviz.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/wireviz/wireviz.py b/src/wireviz/wireviz.py index fe51cc73..d589a8ba 100755 --- a/src/wireviz/wireviz.py +++ b/src/wireviz/wireviz.py @@ -203,7 +203,7 @@ def parse(yaml_input: str, return_types: (None, str, Tuple[str]) = None) -> Any: @click.argument('input_file', required=True) @click.option('--prepend', '-p', default=None, help='a YAML file containing a library of templates and parts that may be referenced in the `input_file`') @click.option('--out', '-o', multiple=True, default=['png'], help='specify one or more output types to be generated; currently supports "png" "svg", "html", "csv", "csv_excel", "csv_unix", "tsv", "tsv_excel"') -def main(input_file, prepend, out): +def cli(input_file, prepend, out): """ Take a YAML file containing a harness specification and, utilizing GraphViz, translate that specification into various output formats. @@ -212,8 +212,12 @@ def main(input_file, prepend, out): $>wireviz my_input_file.yml -o png -o svg """ + main(input_file, prepend, out) + + +def main(input_file, prepend, out): if not os.path.exists(input_file): - print(f'Error: input file {input_file} inaccessible or does not exist, check path') + print(f'Error: input file "{input_file}" inaccessible or does not exist, check path') sys.exit(1) with open_file_read(input_file) as fh: @@ -250,4 +254,4 @@ def main(input_file, prepend, out): if __name__ == '__main__': - main() + cli() From 97e77f55b1c6995284da05a84b0ce7e82236c30b Mon Sep 17 00:00:00 2001 From: "Jason R. Jones" Date: Fri, 24 Jul 2020 22:03:34 -0400 Subject: [PATCH 30/30] Fix `build_examples.py` --- src/wireviz/build_examples.py | 39 +++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/src/wireviz/build_examples.py b/src/wireviz/build_examples.py index f0480722..9d5cf8a9 100755 --- a/src/wireviz/build_examples.py +++ b/src/wireviz/build_examples.py @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- import argparse import os +from pathlib import Path import sys from fnmatch import fnmatch @@ -12,8 +13,8 @@ from wireviz import wireviz -examples_path = os.path.join('..','..','examples') -tutorials_path = os.path.join('..','..','tutorial') +examples_path = Path('../../examples').absolute() +tutorials_path = Path('../../tutorial').absolute() demos_path = examples_path readme = 'readme.md' @@ -22,24 +23,25 @@ def build_demos(): for fn in sorted(os.listdir(demos_path)): if fnmatch(fn, "demo*.yml"): - abspath = os.path.join(demos_path, fn) + path = Path(os.path.join(demos_path, fn)) - print(abspath) - wireviz.main(abspath, prepend=None, out=['png', 'svg', 'html', 'csv']) + print(path) + wireviz.main(path.absolute(), prepend=None, out=['png', 'svg', 'html', 'csv']) def build_examples(): - with open_file_write(os.path.join(examples_path, readme)) as file: + with open_file_write(examples_path / readme) as file: file.write('# Example gallery\n') for fn in sorted(os.listdir(examples_path)): if fnmatch(fn, "ex*.yml"): i = ''.join(filter(str.isdigit, fn)) - abspath = os.path.join(examples_path, fn) - outfile_name = abspath.split(".yml")[0] + path = examples_path / f'{fn}' + os.chdir(path.parent.absolute()) + outfile_name = path.name.replace('.yml', '') - print(abspath) - wireviz.main(abspath, prepend=None, out=['png', 'svg', 'html', 'csv']) + print(path) + wireviz.main(path, prepend=None, out=['png', 'svg', 'html', 'csv']) file.write(f'## Example {i}\n') file.write(f'![]({outfile_name}.png)\n\n') @@ -52,19 +54,21 @@ def build_tutorials(): for fn in sorted(os.listdir(tutorials_path)): if fnmatch(fn, "tutorial*.yml"): i = ''.join(filter(str.isdigit, fn)) - abspath = os.path.join(tutorials_path, fn) - print(abspath) - wireviz.main(abspath, prepend=None, out=['png', 'svg', 'html', 'csv']) + path = tutorials_path / f'{fn}' + os.chdir(path.parent.absolute()) + outfile_name = path.name.replace('.yml', '') - outfile_name = abspath.split(".yml")[0] + print(path) + + wireviz.main(path, prepend=None, out=['png', 'svg', 'html', 'csv']) with open_file_read(outfile_name + '.md') as info: for line in info: file.write(line.replace('## ', '## {} - '.format(i))) file.write(f'\n[Source]({fn}):\n\n') - with open_file_read(abspath) as src: + with open_file_read(path) as src: file.write('```yaml\n') for line in src: file.write(line) @@ -108,11 +112,14 @@ def main(): 'demos': build_demos, 'tutorials': build_tutorials } + for gentype in args.generate: if gentype in generate_types: - generate_types.get(gentype) () + generate_types.get(gentype)() + elif args.action == 'clean': clean_examples() + if __name__ == '__main__': main()