Skip to content

Commit

Permalink
first full version
Browse files Browse the repository at this point in the history
  • Loading branch information
gilesknap committed Apr 9, 2024
1 parent 81dbe7a commit 4d78e9e
Show file tree
Hide file tree
Showing 87 changed files with 14,029 additions and 21 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,6 @@ lockfiles/

# ruff cache
.ruff_cache/

# generated files in tests
tests/data/*.template
62 changes: 50 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,64 @@

Converts EPICS vdct templates to pure msi compatible EPICS db templates

This is where you should write a short paragraph that describes what your module does,
how it does it, and why people should use it.
This tool is designed to modify an EPICS support module in order to remove
its dependency on the vdct tool. This is useful for support modules that we
want to build with the upstream vanilla EPICS base that does not include vdct.

Source | <https://github.com/epics-containers/vdct2template>
:---: | :---:
PyPI | `pip install vdct2template`
Releases | <https://github.com/epics-containers/vdct2template/releases>

This is where you should put some images or code snippets that illustrate
some relevant examples. If it is a library then you might put some
introductory code here:

```python
from vdct2template import __version__
## Installation

print(f"Hello vdct2template {__version__}")
```
To install the latest release from PyPI, create a virtual environment and
pip install like this:

Or if it is a commandline tool then you might put some example commands here:
```bash
python -m venv venv
source venv/bin/activate

pip install vdct2template
```
python -m vdct2template --version
```

## Usage

<pre>$ vdct2template --help
<b> </b>
<b> </b><font color="#A2734C"><b>Usage: </b></font><b>vdct2template [OPTIONS] FOLDER </b>
<b> </b>
<b>VDCT to template conversion function.</b>

<font color="#A2734C"><b> • </b></font><font color="#AAAAAA">This function assumes that all referenced VDCT files in the expand() blocks will be </font>
<font color="#A2734C"><b> </b></font><font color="#AAAAAA">in the same folder. </font>
<font color="#A2734C"><b> • </b></font><font color="#AAAAAA">We can use the builder.py file to check for direct references to template files Use </font>
<font color="#A2734C"><b> </b></font><font color="#AAAAAA">--no-use-builder to disable this feature. Direct references to a template file is an </font>
<font color="#A2734C"><b> </b></font><font color="#AAAAAA">error because we need to modify all macro names to add a _ prefix in templated files. </font>
<font color="#A2734C"><b> • </b></font><font color="#AAAAAA">Files referenced in expand() blocks will have their macro names updated to all have a </font>
<font color="#A2734C"><b> </b></font><font color="#AAAAAA">_ prefix, because MSI does not support substituting a macro with it&apos;s own name and </font>
<font color="#A2734C"><b> </b></font><font color="#AAAAAA">passing a default. This is a workaround to that limitation. </font>
<font color="#A2734C"><b> • </b></font><font color="#AAAAAA">The original expands() block is replaced with a series of substitute MSI directives </font>
<font color="#A2734C"><b> </b></font><font color="#AAAAAA">and an include MSI directive. </font>
<font color="#A2734C"><b> • </b></font><font color="#AAAAAA">The resulting set of templates can be expanded natively by MSI without the need for </font>
<font color="#A2734C"><b> </b></font><font color="#AAAAAA">VDCT. </font>
<font color="#A2734C"><b> • </b></font><font color="#AAAAAA">The DB files created by such an expansion should be equivalent to the original VDCT </font>
<font color="#A2734C"><b> </b></font><font color="#AAAAAA">generated ones. </font>

<font color="#AAAAAA">╭─ Arguments ────────────────────────────────────────────────────────────────────────────╮</font>
<font color="#AAAAAA">│ </font><font color="#C01C28">*</font> folder <font color="#A2734C"><b>DIRECTORY</b></font> folder of vdb files to convert to template files. │
<font color="#AAAAAA">│ [default: None] │</font>
<font color="#AAAAAA">│ </font><font color="#80121A">[required] </font> │
<font color="#AAAAAA">╰────────────────────────────────────────────────────────────────────────────────────────╯</font>
<font color="#AAAAAA">╭─ Options ──────────────────────────────────────────────────────────────────────────────╮</font>
<font color="#AAAAAA">│ </font><font color="#2AA1B3"><b>--version</b></font> <font color="#A2734C"><b> </b></font> Print the version and exit │
<font color="#AAAAAA">│ </font><font color="#2AA1B3"><b>--use-builder</b></font> <font color="#A347BA"><b>--no-use-builder</b></font> <font color="#A2734C"><b> </b></font> Use the builder.py file to look for │
<font color="#AAAAAA">│ direct references to template files. │</font>
<font color="#AAAAAA">│ [default: use-builder] │</font>
<font color="#AAAAAA">│ </font><font color="#2AA1B3"><b>--builder</b></font> <font color="#A2734C"><b>FILE</b></font> Path to the builder file. │
<font color="#AAAAAA">│ [default: None] │</font>
<font color="#AAAAAA">│ </font><font color="#2AA1B3"><b>--help</b></font> <font color="#A2734C"><b> </b></font> Show this message and exit. │
<font color="#AAAAAA">╰────────────────────────────────────────────────────────────────────────────────────────╯</font>

</pre>
5 changes: 3 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ classifiers = [
"Programming Language :: Python :: 3.11",
]
description = "Converts EPICS vdct templates to pure msi compatible EPICS db templates"
dependencies = [] # Add project dependencies here, e.g. ["click", "numpy"]
dependencies = ["typer", "ruamel.yaml", "jinja2"]
dynamic = ["version"]
license.file = "LICENSE"
readme = "README.md"
Expand All @@ -34,7 +34,7 @@ dev = [
]

[project.scripts]
vdct2template = "vdct2template.__main__:main"
vdct2template = "vdct2template.__main__:cli"

[project.urls]
GitHub = "https://github.com/epics-containers/vdct2template"
Expand Down Expand Up @@ -90,6 +90,7 @@ commands =

[tool.ruff]
src = ["src", "tests"]
ignore = ["B008"]
line-length = 88
lint.select = [
"B", # flake8-bugbear - https://docs.astral.sh/ruff/rules/#flake8-bugbear-b
Expand Down
84 changes: 77 additions & 7 deletions src/vdct2template/__main__.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,86 @@
from argparse import ArgumentParser
from pathlib import Path
from typing import Optional

import typer

from . import __version__
from .convert import convert

__all__ = ["main"]


def main(args=None):
parser = ArgumentParser()
parser.add_argument("-v", "--version", action="version", version=__version__)
args = parser.parse_args(args)
cli = typer.Typer(rich_markup_mode="markdown")


def version_callback(value: bool):
if value:
typer.echo(__version__)
raise typer.Exit()


@cli.command()
def main(
version: Optional[bool] = typer.Option(
None,
"--version",
callback=version_callback,
is_eager=True,
help="Print the version and exit",
),
folder: Path = typer.Argument(
...,
help="folder of vdb files to convert to template files.",
exists=True,
file_okay=False,
resolve_path=True,
),
use_builder: bool = typer.Option(
True,
help="Use the builder.py file to look for direct references to template files.",
),
builder: Optional[Path] = typer.Option(
None,
help="Path to the builder file.",
exists=True,
dir_okay=False,
resolve_path=True,
),
):
"""
### VDCT to template conversion function.
- This function assumes that all referenced VDCT files in the expand() blocks
will be in the same folder.
- We can use the builder.py file to check for direct references to template files
Use --no-use-builder to disable this feature. Direct references to a template
file is an error because we need to modify all macro names to add a _ prefix
in templated files.
- Files referenced in expand() blocks will have their macro names updated to
all have a _ prefix, because MSI does not support substituting a macro with
it's own name and passing a default. This is a workaround to that limitation.
- The original expands() block is replaced with a series of substitute
MSI directives and an include MSI directive.
- The resulting set of templates can be expanded natively by MSI without the
need for VDCT.
- The DB files created by such an expansion should be equivalent to the
original VDCT generated ones.
"""

if use_builder:
builder = builder or Path(folder.parent.parent / "etc" / "builder.py")
builder_txt = builder.read_text()
else:
builder_txt = ""

convert(folder, builder_txt)


# test with: python -m vdct2template
# test with:
# python -m vdct2template --version
if __name__ == "__main__":
main()
typer.run(main)
48 changes: 48 additions & 0 deletions src/vdct2template/convert.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from pathlib import Path

from .expansion import Expansion
from .regex import DROP


def convert(folder: Path, builder_txt: str):
"""
function to oversee conversion of a set of VDB files to template files.
"""
warning = False
targets = list(folder.glob("*.vdb"))

print(f"converting vdb files in {folder}\n ...")

for target in targets:
expansion = Expansion(target, folder)
if expansion.parse_expands() > 0:
print(f"writing expansion {expansion.template_path.name}")
expansion.template_path.write_text(expansion.text)

for file, text in expansion.process_includes():
print(f"writing template {file.name}")
file.write_text(text)
if file.name in builder_txt:
warning = True
print(f" WARNING: direct reference from builder.py to {file.name}")

# process the remaining (flat) vdbs
all_vdb_files = {target.name for target in targets}
unprocessed = all_vdb_files - set(Expansion.processed)

for file in unprocessed:
path = folder / file
template_path = path.with_suffix(".template")

text = path.read_text()
text = DROP.sub("", text)

print(f"writing flat {file}")
template_path.write_text(text)

# give warnings if there are inconsistent macro substitutions
warning |= Expansion.validate_includes()

if warning:
print("\n WARNINGS DETECTED: check above for details.")
exit(1)
106 changes: 106 additions & 0 deletions src/vdct2template/expansion.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
from pathlib import Path
from typing import Dict, List

from vdct2template.macros import Macros

from .regex import DROP, EXPAND


class Expansion:
"""
A class to represent a VDB file that contains expands() blocks.
Provides the necessary conversion to MSI include/substitute statements.
"""

# class level list of all Expansion instances created
expansions: List["Expansion"] = []
# class level list of all vdb files processed so far
processed: List[str] = []

def __init__(self, filename: Path, folder: Path) -> None:
"""
Constructor: set up properties
"""
self.vdb_path = filename.resolve()
self.folder = folder
self.template_path = filename.with_suffix(".template")
self.includes = []
self.text = filename.read_text()

Expansion.expansions.append(self)

def parse_expands(self) -> int:
"""
Parse the expands() blocks in the VDB file.
Updates the class attribute substitutions with the macro substitutions parsed.
Updates the class attribute text with the VDB file text with the expands()
blocks processed into MSI substitute/include statements.
returns the number of expands() blocks found.
"""

expands = EXPAND.findall(self.text)
if not expands:
return 0

for match in expands:
# match: 0=include path, 1=name, 2=macro text
include_path = self.folder / match[0]
macros = Macros(self.template_path, include_path, match[2])
self.includes.append(macros)

# replace the expands() block with the MSI directives
self.text = EXPAND.sub(macros.render_include(), self.text, 1)

# remove other extraneous VDB things
self.text = DROP.sub("", self.text)

self.processed.append(self.vdb_path.name)

return len(expands)

def process_includes(self):
"""
Process the included files for this VDB file.
"""
for include in self.includes:
if include.vdb_path.name not in Expansion.processed:
yield include.process()
Expansion.processed.append(include.vdb_path.name)

@classmethod
def validate_includes(cls) -> bool:
"""
Check that all included files are always using the same substitutions
every time they are included. If not then the the replacing of macro
names with _ prefix will be inconsistent between uses of the included
templates and this approach will fail.
"""
warning = False
index: Dict[str, Macros] = {}

print()
for expansion in cls.expansions:
for include in expansion.includes:
if include.template_path.name in index:
original = index[include.template_path.name]
if include.compare(original):
warning = True
print(
f" WARNING: inconsistent macros for "
f"{include.template_path.name}"
)
print(
f" {include.parent.name} missing:"
f"{original.missing_str(include)}"
)
print(
f" {original.parent.name} missing: "
f"{include.missing_str(original)}"
)
else:
index[include.template_path.name] = include

return warning
Loading

0 comments on commit 4d78e9e

Please sign in to comment.