diff --git a/src/wireviz/Harness.py b/src/wireviz/Harness.py index 1efef9fc..c77f8713 100644 --- a/src/wireviz/Harness.py +++ b/src/wireviz/Harness.py @@ -678,6 +678,7 @@ def output( cleanup: bool = True, output_dir: Optional[Union[str, Path]] = None, output_name: Optional[str] = None, + template_dir: Optional[Union[str, Path]] = None, ) -> None: """Render the harness in the requested formats. @@ -686,6 +687,26 @@ def output( ``filename`` is None, exactly one format must be requested and its bytes/text are written to stdout — supports piping the CLI into other tools. + + Args: + filename: Output base path (without extension). ``None`` + routes a single format to stdout instead of writing files. + fmt: One or more formats from ``html``, ``png``, ``svg``, + ``gv``, ``tsv``, ``csv``, ``pdf``. A bare string is + normalized to a one-tuple. + view: Reserved (unused — kept for API compatibility with the + pre-refactor signature). + cleanup: Reserved (unused — kept for API compatibility). + output_dir: Output directory. Used only to populate the + ```` HTML template placeholder and to + resolve a custom ``metadata.template.name`` reference. + output_name: Output base name (without extension). Used only + to populate the ```` HTML + template placeholder. + template_dir: Explicit directory to search first when + resolving a ``metadata.template.name`` reference. Falls + through to the YAML source directory, then ``output_dir``, + then the built-in templates shipped with WireViz. """ if isinstance(fmt, str): fmt = (fmt,) @@ -693,6 +714,7 @@ def output( fmt, output_dir=output_dir, output_name=output_name, + template_dir=template_dir, ) if "csv" in fmt: @@ -728,12 +750,33 @@ def _render( fmt: Union[str, Tuple[str, ...], List[str]], output_dir: Optional[Union[str, Path]] = None, output_name: Optional[str] = None, + template_dir: Optional[Union[str, Path]] = None, ) -> Dict[str, Union[str, bytes]]: """Produce in-memory representations of each requested format. Pipes graphviz once per binary output rather than via ``render()`` + temporary files so the caller can write files OR pipe to stdout without the SVG-file roundtrip the previous implementation used. + + Args: + fmt: One or more formats from ``html``, ``png``, ``svg``, + ``gv``, ``tsv``. ``csv`` and ``pdf`` are recognized at + the dispatch layer but not produced here. A bare string + is normalized to a one-tuple. + output_dir: Forwarded to ``generate_html_output`` for + ```` and ```` + template-placeholder resolution, and as the third-priority + directory in the custom-template search path. + output_name: Forwarded to ``generate_html_output`` for + ```` resolution. + template_dir: Forwarded to ``generate_html_output`` as the + first-priority directory in the custom-template search + path. + + Returns: + ``{format: bytes|str}``. Binary formats (``png``) yield + bytes; text formats (``svg``, ``html``, ``gv``, ``tsv``) + yield str. """ if isinstance(fmt, str): fmt = (fmt,) @@ -788,6 +831,7 @@ def _render( output_name=output_name, png_b64=png_b64, source_path=self.source_path, + template_dir=template_dir, ) return outputs diff --git a/src/wireviz/wireviz.py b/src/wireviz/wireviz.py index a4d20300..de7b7a11 100755 --- a/src/wireviz/wireviz.py +++ b/src/wireviz/wireviz.py @@ -32,6 +32,7 @@ def parse( output_name: Union[None, str] = None, image_paths: Union[Path, str, List] = [], source_path: Union[Path, str, None] = None, + template_dir: Union[Path, str, None] = None, ) -> Any: """ This function takes an input, parses it as a WireViz Harness file, @@ -76,6 +77,17 @@ def parse( Paths to use when resolving any image paths included in the data. Note: If inp is a path to a YAML file, its parent directory will automatically be included in the list. + source_path (Path | str, optional): + Path of the originating YAML file when ``inp`` is a string or dict. + Used to: (1) resolve a custom ``metadata.template.name`` reference + against the source's directory, and (2) resolve relative + ```` paths embedded in graphviz output. + When ``inp`` is itself a Path, this is filled in automatically. + template_dir (Path | str, optional): + Explicit first-priority directory to search when resolving a + ``metadata.template.name`` reference. Searched before the YAML + source directory and the output directory; the built-in + templates ship as the final fallback. Returns: Depending on the return_types parameter, may return: @@ -421,7 +433,12 @@ def alternate_type(): # flip between connector and cable/arrow raise ValueError( "Exactly one output format must be specified when writing to stdout." ) - harness.output(filename=None, fmt=output_formats, view=False) + harness.output( + filename=None, + fmt=output_formats, + view=False, + template_dir=template_dir, + ) else: harness.output( filename=output_file, @@ -429,6 +446,7 @@ def alternate_type(): # flip between connector and cable/arrow view=False, output_dir=output_dir, output_name=output_name, + template_dir=template_dir, ) if return_types: diff --git a/src/wireviz/wv_cli.py b/src/wireviz/wv_cli.py index ceb25224..ce298d45 100644 --- a/src/wireviz/wv_cli.py +++ b/src/wireviz/wv_cli.py @@ -64,6 +64,13 @@ type=str, help="File name (without extension) to use for output files, if different from input file name.", ) +@click.option( + "-t", + "--template-dir", + default=None, + type=Path, + help="Directory searched first when resolving a metadata.template.name reference.", +) @click.option( "-V", "--version", @@ -71,7 +78,7 @@ default=False, 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, output_name, template_dir, version): """ Parses the provided FILE and generates the specified outputs. @@ -165,6 +172,7 @@ def wireviz(file, format, prepend, output_dir, output_name, version): output_name=_output_name, image_paths=list(image_paths), source_path=file, + template_dir=template_dir, ) sys.stderr.write("\n") diff --git a/src/wireviz/wv_html.py b/src/wireviz/wv_html.py index 4a76d65b..58edaafb 100644 --- a/src/wireviz/wv_html.py +++ b/src/wireviz/wv_html.py @@ -24,19 +24,24 @@ def generate_html_output( output_name: Union[str, None] = None, png_b64: Union[str, None] = None, source_path: Union[str, Path, None] = None, + template_dir: Union[str, Path, None] = None, ) -> str: # load HTML template templatename = metadata.get("template", {}).get("name") builtin_template_dir = Path(__file__).parent / "templates" if templatename: - # custom template lookup order: directory of the input YAML - # (source_path), then the output directory, then the built-in - # templates shipped with WireViz. + # custom template lookup order, highest priority first: + # 1. explicit template_dir (CLI -t / parse template_dir) + # 2. YAML source directory (source_path.parent) + # 3. output directory + # 4. built-in templates shipped with WireViz search_paths = [builtin_template_dir] if output_dir is not None: search_paths.insert(0, Path(output_dir)) if source_path is not None: search_paths.insert(0, Path(source_path).parent) + if template_dir is not None: + search_paths.insert(0, Path(template_dir)) templatefile = smart_file_resolve( f"{templatename}.html", search_paths,