Skip to content

raesl.doc

ESL to Doc

A Python package to process specifications in the form of .esl files into regular document formats such as .pdf.

convert

convert(
    *paths: Union[str, Path],
    output: Union[str, Path] = OUTPUT,
    language: str = LANGUAGE,
    title: str = TITLE,
    prologue: Optional[Union[str, Path]] = PROLOGUE,
    epilogue: Optional[Union[str, Path]] = EPILOGUE,
    var_table: bool = VARTABLE,
    rich: Optional[str] = RICH,
    rich_opts: Optional[Dict[str, Any]] = None,
    force: bool = FORCE,
    dry: bool = DRY,
    **metadata
)

Convert ESL files and/or directories to a formatted document.

Parameters:

Name Type Description Default
paths Union[str, Path]

Paths to resolve into ESL files. May be any number of files and directories to scan.

()
output Union[str, Path]

Optional output file (i.e. Markdown, PDF, DOCX).

OUTPUT
language str

Output document language.

LANGUAGE
title str

Output document title.

TITLE
prologue Optional[Union[str, Path]]

Optional prologue document to include (Markdown).

PROLOGUE
epilogue Optional[Union[str, Path]]

Optional epilogue document to include (Markdown).

EPILOGUE
var_table bool

Add table with all variables within appendix.

VARTABLE
rich Optional[str]

Format of rich output to use. One of "tex", "md" or "off".

RICH
rich_opts Optional[Dict[str, Any]]

Extra options for selected rich output.

None
force bool

Whether to overwrite the output file or raise an error if the file already exists.

FORCE
dry bool

Dry run. Skip creating an output document.

DRY
Source code in src/raesl/doc/__init__.py
def convert(
    *paths: Union[str, Path],
    output: Union[str, Path] = OUTPUT,
    language: str = LANGUAGE,
    title: str = TITLE,
    prologue: Optional[Union[str, Path]] = PROLOGUE,
    epilogue: Optional[Union[str, Path]] = EPILOGUE,
    var_table: bool = VARTABLE,
    rich: Optional[str] = RICH,
    rich_opts: Optional[Dict[str, Any]] = None,
    force: bool = FORCE,
    dry: bool = DRY,
    **metadata,
):
    """Convert ESL files and/or directories to a formatted document.

    Arguments:
        paths: Paths to resolve into ESL files. May be any number of files and
            directories to scan.
        output: Optional output file (i.e. Markdown, PDF, DOCX).
        language: Output document language.
        title: Output document title.
        prologue: Optional prologue document to include (Markdown).
        epilogue: Optional epilogue document to include (Markdown).
        var_table: Add table with all variables within appendix.
        rich: Format of rich output to use. One of "tex", "md" or "off".
        rich_opts: Extra options for selected rich output.
        force: Whether to overwrite the output file or raise an error if the file
            already exists.
        dry: Dry run. Skip creating an output document.
    """
    output = Path(output)
    prologue = Path(prologue) if prologue else None
    epilogue = Path(epilogue) if epilogue else None

    logger.debug("Creating Doc object...")
    doc = Doc(
        *paths,
        language=language,
        title=title,
        prologue=prologue,
        epilogue=epilogue,
        var_table=var_table,
        rich=rich,
        rich_opts=rich_opts,
        **metadata,
    )
    logger.debug("Created Doc object!")

    if output:
        output = check_output_path(output, force)
        if dry:
            logger.debug("Dry run. Skipped writing to file.")
            return None
        doc.save(output)

cli

ESL to Doc Command Line Interface.

doc

doc(
    paths: Iterable[str],
    output: str,
    language: str,
    title: str,
    prologue: Optional[str],
    epilogue: Optional[str],
    rich: Optional[str],
    force: bool,
    dry: bool,
)

Convert ESL files and/or directories to a formatted document.

Source code in src/raesl/doc/cli.py
@click.command("doc")
@click.argument("paths", nargs=-1, type=click.Path(exists=True, file_okay=True, dir_okay=True))
@click.option(
    "--output",
    "-o",
    default=raesl.doc.OUTPUT,
    type=click.Path(file_okay=True, dir_okay=False, writable=True),
    help="Output file to write to.",
)
@click.option(
    "--language",
    "-l",
    default=raesl.doc.LANGUAGE,
    type=click.Choice(["en", "nl"], case_sensitive=False),
    help="Output document language.",
)
@click.option(
    "--title",
    "-t",
    default=raesl.doc.TITLE,
    type=click.STRING,
    help="Output document title.",
)
@click.option(
    "--prologue",
    "-p",
    default=raesl.doc.PROLOGUE,
    type=click.Path(exists=True, file_okay=True, dir_okay=False, readable=True),
    help="Optional prologue document to include (Markdown).",
)
@click.option(
    "--epilogue",
    "-e",
    default=raesl.doc.EPILOGUE,
    type=click.Path(exists=True, file_okay=True, dir_okay=False, readable=True),
    help="Optional epilogue document to include (Markdown).",
)
@click.option(
    "--rich",
    "-r",
    default=raesl.doc.RICH,
    type=click.Choice(["tex", "md", "off"], case_sensitive=False),
    help="Format of rich output to use.",
)
@click.option(
    "--force",
    "-f",
    default=raesl.doc.FORCE,
    is_flag=True,
    help="Force overwrite of output file.",
)
@click.option(
    "--dry",
    "-d",
    default=raesl.doc.DRY,
    is_flag=True,
    help="Dry run. Skip creating an output document.",
)
def doc(
    paths: Iterable[str],
    output: str,
    language: str,
    title: str,
    prologue: Optional[str],
    epilogue: Optional[str],
    rich: Optional[str],
    force: bool,
    dry: bool,
):
    """Convert ESL files and/or directories to a formatted document."""
    logger.info("This is the Ratio ESL Doc command line utility.")
    logger.info(f"Populating '{output}', titled as '{title}' in language '{language}'...")
    try:
        run(
            *paths,
            output=output,
            language=language,
            title=title,
            prologue=prologue,
            epilogue=epilogue,
            rich=rich,
            force=force,
            dry=dry,
        )
        logger.info("Doc generation done!")
    except Exception as e:
        logger.error(str(e))
        sys.exit(1)

doc

Document module.

Doc

Doc(
    *paths: Union[str, Path],
    language: str = "en",
    prologue: Optional[Path] = None,
    epilogue: Optional[Path] = None,
    goal_section: bool = True,
    transformation_section: bool = True,
    behavior_section: bool = True,
    design_section: bool = True,
    need_section: bool = True,
    relation_section: bool = True,
    var_table: bool = True,
    rich: Optional[str] = "tex",
    rich_opts: Optional[Dict[str, Any]] = None,
    esl_paths: Optional[
        Union[List[str], List[Path]]
    ] = None,
    **metadata
)

Output document.

Parameters:

Name Type Description Default
paths Union[str, Path]

ESL input files.

()
language str

Output language.

'en'
prologue Optional[Path]

Markdown document to include as a prologue.

None
epilogue Optional[Path]

Markdown document to include as a conclusion.

None
goal_section bool

Goal section toggle.

True
transformation_section bool

Transformation section toggle.

True
behavior_section bool

Behavior section toggle.

True
design_section bool

Design section toggle.

True
need_section bool

Need section toggle.

True
relation_section bool

Need section toggle.

True
var_table bool

Var table toggle.

True
rich Optional[str]

Format to create rich output content in, defaults to 'tex'.

'tex'
rich_opts Optional[Dict[str, Any]]

Rich output generation options.

None

Keyword Arguments: Document metadata. See pandoc documentation.

Source code in src/raesl/doc/doc.py
def __init__(
    self,
    *paths: Union[str, Path],
    language: str = "en",
    prologue: Optional[Path] = None,
    epilogue: Optional[Path] = None,
    goal_section: bool = True,
    transformation_section: bool = True,
    behavior_section: bool = True,
    design_section: bool = True,
    need_section: bool = True,
    relation_section: bool = True,
    var_table: bool = True,
    rich: Optional[str] = "tex",
    rich_opts: Optional[Dict[str, Any]] = None,
    esl_paths: Optional[Union[List[str], List[Path]]] = None,
    **metadata,
):
    if esl_paths is not None:
        msg = " ".join(
            (
                "The 'esl_paths' keyword argument will be deprecated.",
                "Please use your file and directory paths as (any number of)",
                "positional arguments to this function.",
            )
        )
        logger.warning(msg)
        paths = tuple(esl_paths)

    self.language = language
    register_locale(language)

    self.prologue = Path(prologue).read_text(encoding="utf-8") if prologue else None
    self.epilogue = Path(epilogue).read_text(encoding="utf-8") if epilogue else None

    self.goal_section = goal_section
    self.transformation_section = transformation_section
    self.behavior_section = behavior_section
    self.design_section = design_section
    self.need_section = need_section
    self.relation_section = relation_section
    self.var_table = var_table

    self.rich = rich
    self.rich_opts = rich_opts or dict()

    logger.debug("Parsing metadata {}...".format(metadata))
    self.metadata = {
        "book": True,
        "documentclass": "scrbook",
        "geometry": "margin=2.5cm",
        "graphics": True,
        "papersize": "a4",
        "titlepage": True,
        "titlepage-color": "1c3b6c",
        "titlepage-text-color": "ffffff",
        "titlepage-rule-color": "ffffff",
        "titlepage-background": (TEMPLATES / "background.pdf").as_posix(),
        "toc-own-page": True,
        "top-level-division": "chapter",
        "first-chapter": 1,
    }

    if metadata:
        self.metadata.update(metadata)

    self.metadata["lang"] = self.language

    logger.debug("Parsing ESL files...")

    self.diag_store, _, self.graph = cli.run(*paths)

    if self.diag_store.diagnostics:
        self.diag_store.dump()

    logger.debug("Parsing graph to a document...")
    self.pars: List[Par] = []
    self.parse_esl()

as_markdown property

as_markdown: str

Markdown representation of this document.

get_bundle_name_parts

get_bundle_name_parts() -> Set[str]

Get set of all name parts of bundles used within the spec

Returns:

Type Description
Set[str]

Set of all name parts of bundles.

Source code in src/raesl/doc/doc.py
def get_bundle_name_parts(self) -> Set[str]:
    """Get set of all name parts of bundles used within the spec

    Returns:
        Set of all name parts of bundles.
    """
    vnames = [n.name for n in self.graph.get_nodes_by_kind("variable")]
    cnames = [n.name for n in self.graph.get_nodes_by_kind("component")]

    cname_parts = set()
    for name in cnames:
        cname_parts.update(set(name.split(".")))

    vname_parts = set()
    for name in vnames:
        vname_parts.update(set(name.split(".")[:-1]))

    return vname_parts - cname_parts

parse_esl

parse_esl()

Parse an ESL output Graph

Source code in src/raesl/doc/doc.py
def parse_esl(self):
    """Parse an ESL output Graph"""
    if self.prologue:
        self.pars = [Par([self.prologue])]

    g = self.graph

    comps = g["world"].children
    comps = sorted(comps, key=lambda x: x.name)
    depth = 0
    h = 1

    while comps:
        # Add chapter introduction.
        logger.debug("Processing nodes at level {}...".format(depth))
        self.pars.append(
            Par(
                secs.node_decomp_level(
                    depth, comps, h=h, rich=self.rich, rich_opts=self.rich_opts
                )
            )
        )
        # Add MDM figure in rich mode.
        if self.rich:
            logger.debug("Adding MDM image at level {}...".format(depth))
            self.pars.append(
                Par(
                    rich.mdm(
                        graph=self.graph,
                        depth=depth,
                        rich=self.rich,
                        rich_opts=self.rich_opts,
                    )
                )
            )

        # Add bundle information
        self.set_var_bundle_roots()

        for comp in comps:
            logger.debug("Adding sections for node {}...".format(comp.name))
            self.pars.append(Par(lns.lines(comp, h=h + 1)))
            self.pars.append(Par(secs.comp_node_props(comp, g, h + 2)))
            if self.goal_section:
                self.pars.append(Par(secs.comp_node_goal_reqs(comp, g, h + 2)))
                self.pars.append(Par(secs.comp_node_goal_cons(comp, g, h + 2)))

            if self.transformation_section:
                self.pars.append(Par(secs.comp_node_transformation_reqs(comp, g, h + 2)))
                self.pars.append(Par(secs.comp_node_transformation_cons(comp, g, h + 2)))

            if self.behavior_section:
                self.pars.append(Par(secs.comp_node_behavior_reqs(comp, g, h + 2)))
                self.pars.append(Par(secs.comp_node_behavior_cons(comp, g, h + 2)))

            if self.design_section:
                self.pars.append(Par(secs.comp_node_design_reqs(comp, g, h + 2)))
                self.pars.append(Par(secs.comp_node_design_cons(comp, g, h + 2)))

            if self.need_section:
                self.pars.append(Par(secs.comp_node_needs(comp, g, h + 2)))

            if self.relation_section:
                self.pars.append(Par(secs.comp_node_relations(comp, g, h + 2)))

            self.pars.append(Par(secs.comp_node_subcomps(comp, h + 2)))

        children = []
        for comp in comps:
            children.extend(comp.children)
        comps = children
        depth += 1

    # Check for any miscellaneous needs and design specifications that haven't
    # been printed yet.
    gn = [line for line in secs.global_needs(g, h + 1) if self.need_section]
    gdr = [line for line in secs.global_design_reqs(g, h + 1) if self.design_section]
    gdc = [line for line in secs.global_design_cons(g, h + 1) if self.design_section]
    if len(gn) > 0 or len(gdr) > 0 or len(gdc) > 0:
        self.pars.append(Par(secs.global_needs_and_designs(h)))
        self.pars.append(Par(gn))
        self.pars.append(Par(gdr))
        self.pars.append(Par(gdc))

    if self.epilogue:
        logger.debug("Adding epilogue...")
        self.pars.append(Par([self.epilogue]))

    # Appendices
    if self.var_table:
        logger.debug("Adding appendices...")
        self.pars.append(Par(["\\appendix{}", "\\appendixpage{}"]))
        self.pars.append(Par(secs.var_node_table(g, h=1)))

save

save(
    path: Union[Path, str],
    to: Optional[str] = None,
    pandoc_args: List[str] = [
        "--standalone",
        "--number-sections",
        "--toc",
        "--listings",
        "--self-contained",
    ],
    filters: List[str] = ["pandoc-fignos"],
)

Save document as a file.

Parameters:

Name Type Description Default
path Union[Path, str]

Path to save to.

required
to Optional[str]

Optional format to save to. Normally derived from path.

None
pandoc_args List[str]

Additional arguments for pandoc conversion tool.

['--standalone', '--number-sections', '--toc', '--listings', '--self-contained']
filters List[str]

Pandoc filters to use.

['pandoc-fignos']
Source code in src/raesl/doc/doc.py
def save(
    self,
    path: Union[Path, str],
    to: Optional[str] = None,
    pandoc_args: List[str] = [
        "--standalone",
        "--number-sections",
        "--toc",
        "--listings",
        "--self-contained",
    ],
    filters: List[str] = ["pandoc-fignos"],
):
    """Save document as a file.

    Arguments:
        path: Path to save to.
        to: Optional format to save to. Normally derived from path.
        pandoc_args: Additional arguments for pandoc conversion tool.
        filters: Pandoc filters to use.
    """
    import pypandoc

    path = Path(path)
    if to is None:
        to = path.suffix.lstrip(".")
        if to == "html":
            to == "html5"
    pandoc_args = pandoc_args.copy()
    filters = filters.copy()

    # Fix LaTeX incompatibilities.
    if (to == "tex" or to == "pdf") and self.rich == "md":
        self.rich = "tex"
        self.parse_esl()

    if logger.level <= logging.DEBUG and to != "md":
        logger.debug("Saving Markdown file for debugging purposes...")
        try:
            pypandoc.convert_text(
                self.as_markdown,
                self.markdown_file_format,
                format=self.markdown_generated,
                outputfile=str(path.with_suffix(".md")),
                encoding="utf-8",
                extra_args=pandoc_args,
                filters=filters,
            )
        except RuntimeError as exc:
            logger.error(str(exc))

    if to == "pdf" or to == "latex":
        template = (TEMPLATES / "eisvogel.latex").resolve()
        pandoc_args.extend(["--template", str(template)])

    if logger.level <= logging.DEBUG and to != "latex":
        try:
            logger.debug("Saving LaTeX file for debugging purposes...")
            pypandoc.convert_text(
                self.as_markdown,
                "latex",
                format=self.markdown_generated,
                outputfile=str(path.with_suffix(".tex")),
                encoding="utf-8",
                extra_args=pandoc_args,
                filters=filters,
            )
        except RuntimeError as exc:
            logger.error(str(exc))

    logger.debug("Converting to {}...".format(to.upper()))

    # Convert and save.
    try:
        pypandoc.convert_text(
            self.as_markdown,
            to,
            format=self.markdown_generated,
            outputfile=str(path),
            encoding="utf-8",
            extra_args=pandoc_args,
            filters=filters,
        )
    except RuntimeError as exc:
        logger.error(str(exc))
        raise exc

set_var_bundle_roots

set_var_bundle_roots()

Set the bundle root of variables if they originate from a bundle.

Source code in src/raesl/doc/doc.py
def set_var_bundle_roots(self):
    """Set the bundle root of variables if they originate from a bundle."""
    bnps = self.get_bundle_name_parts()
    for v in self.graph.get_nodes_by_kind("variable"):
        v.annotations.esl_info["bundle_root_name"] = get_bundle_root(
            vname=v.name, bundle_name_parts=bnps
        )

yield_markdown

yield_markdown() -> Generator[str, None, None]

Yield markdown lines.

Source code in src/raesl/doc/doc.py
def yield_markdown(self) -> Generator[str, None, None]:
    """Yield markdown lines."""
    yield from self.yield_metadata()
    yield from self.yield_pars()

yield_metadata

yield_metadata() -> Generator[str, None, None]

Yield metadata lines.

Source code in src/raesl/doc/doc.py
def yield_metadata(self) -> Generator[str, None, None]:
    """Yield metadata lines."""
    yield "---"
    for key, value in self.metadata.items():
        yield "{}: {}".format(key, value)
    yield "---"

yield_pars

yield_pars() -> Generator[str, None, None]

Yield all paragraph texts.

Source code in src/raesl/doc/doc.py
def yield_pars(self) -> Generator[str, None, None]:
    """Yield all paragraph texts."""
    for par in self.pars:
        yield par.md

Par

Par(lines: Iterable[str], obj: Optional[Any] = None)

Paragraph.

Parameters:

Name Type Description Default
lines Iterable[str]

Lines of this paragraph.

required
obj Optional[Any]

Object of this paragraph.

None

Attributes:

Name Type Description
md

Markdown representation.

Source code in src/raesl/doc/doc.py
def __init__(self, lines: Iterable[str], obj: Optional[Any] = None):
    self.md = "\n".join(lines) + "\n"
    self.md = self.md.replace("_", " ").replace(" ->", " &rarr;")
    self.md = self.md.replace("-underscore-", "_")
    self.obj = obj

get_bundle_root

get_bundle_root(
    vname: str, bundle_name_parts: Set
) -> Union[None, str]

Check if variable originates from a bundle and return the name of the root of the bundle.

Parameters:

Name Type Description Default
vname str

Name of the variable to check.

required
bundle_name_parts Set

Set of strings the comprise bundle names.

required

Returns:

Type Description
Union[None, str]

None or Name of the root of the bundle from which the variable originates.

Source code in src/raesl/doc/doc.py
def get_bundle_root(vname: str, bundle_name_parts: Set) -> Union[None, str]:
    """Check if variable originates from a bundle and return the name
    of the root of the bundle.

    Arguments:
        vname: Name of the variable to check.
        bundle_name_parts: Set of strings the comprise bundle names.

    Returns:
       None or Name of the root of the bundle from which the variable originates.
    """
    bundle_root = None
    name_parts = vname.split(".")

    if name_parts[-2] not in bundle_name_parts:
        # Variable did not originate from a bundle:
        return bundle_root

    for idx in range(2, len(name_parts) + 1):
        if name_parts[-idx] in bundle_name_parts:
            bundle_root = name_parts[-idx]
        else:
            # Location part of varaible name has been entered
            break

    return bundle_root

lines

Module to generate text lines that describe ESL objects.

Hookspecs

behavior_spec_node

behavior_spec_node(
    node: Node, graph: Graph, html: bool
) -> LineGen

Yield the behavior spec in natural language.

Source code in src/raesl/doc/lines.py
@hookspec(firstresult=True)
def behavior_spec_node(node: Node, graph: Graph, html: bool) -> LineGen:
    """Yield the behavior spec in natural language."""

design_spec_node

design_spec_node(
    node: Node, graph: Graph, html: bool
) -> LineGen

Yield the design spec in natural language.

Source code in src/raesl/doc/lines.py
@hookspec(firstresult=True)
def design_spec_node(node: Node, graph: Graph, html: bool) -> LineGen:
    """Yield the design spec in natural language."""

function_spec_node

function_spec_node(
    node: Node, graph: Graph, html: bool
) -> LineGen

Yield the function spec in natural language.

Source code in src/raesl/doc/lines.py
@hookspec(firstresult=True)
def function_spec_node(node: Node, graph: Graph, html: bool) -> LineGen:
    """Yield the function spec in natural language."""

linguistic_enumeration

linguistic_enumeration(items: List[str]) -> str

Get a natural language enumeration of items.

Source code in src/raesl/doc/lines.py
@hookspec(firstresult=True)
def linguistic_enumeration(items: List[str]) -> str:
    """Get a natural language enumeration of items."""

linguistic_options

linguistic_options(items: List[str]) -> str

Get a natural language enumeration of options.

Source code in src/raesl/doc/lines.py
@hookspec(firstresult=True)
def linguistic_options(items: List[str]) -> str:
    """Get a natural language enumeration of options."""

need_node

need_node(node: Node, graph: Graph, html: bool) -> LineGen

Yield the need spec in natural language.

Source code in src/raesl/doc/lines.py
@hookspec(firstresult=True)
def need_node(node: Node, graph: Graph, html: bool) -> LineGen:
    """Yield the need spec in natural language."""

bold

bold(text: str, html: bool = False) -> str

Return text as bold (markdown or html).

Source code in src/raesl/doc/lines.py
def bold(text: str, html: bool = False) -> str:
    """Return text as bold (markdown or html)."""
    return "<b>{}</b>".format(text.strip()) if html else "**{}**".format(text.strip())

boldhead

boldhead(
    text: str,
    capitalize: bool = True,
    newlines: bool = True,
    html: bool = False,
) -> str

Get a bold header (without numbering) with surrounding whitespace.

Source code in src/raesl/doc/lines.py
def boldhead(text: str, capitalize: bool = True, newlines: bool = True, html: bool = False) -> str:
    """Get a bold header (without numbering) with surrounding whitespace."""
    if capitalize:
        text = cap(text)

    text = bold(text, html=html)

    if newlines:
        text = "\n{}\n".format(text)

    return text

bundle_clarification

bundle_clarification(
    brvdict: Dict[str, List[Node]], html: bool = False
)

Yield bundle clarification section

Source code in src/raesl/doc/lines.py
def bundle_clarification(brvdict: Dict[str, List[Node]], html: bool = False):
    """Yield bundle clarification section"""
    yield "\n"
    verbs = _("is a bundle") if len(brvdict) == 1 else _("are bundles")
    yield cap(
        _("where {} {} of which the following variables are used:").format(
            pm.hook.linguistic_enumeration(items=list(brvdict.keys())), verbs
        )
    )
    yield "\n"
    yield from unordered(
        [bundle_path(path=vname, root=key) for key in brvdict for vname in brvdict[key]], html=html
    )

bundle_path

bundle_path(
    path: str,
    root: str,
    italic: bool = False,
    arrows: bool = True,
    skip: str = "world",
)

Get a friendly representation of a bundle path.

Source code in src/raesl/doc/lines.py
def bundle_path(
    path: str, root: str, italic: bool = False, arrows: bool = True, skip: str = "world"
):
    """Get a friendly representation of a bundle path."""
    path_parts = path.split("." + root + ".")

    if path_parts[0] in skip:
        path = root + "." + path_parts[-1]
    else:
        path = (
            node_path(path=path_parts[0] + ".", italic=False, arrows=arrows, skip=skip)
            + " "
            + root
            + "."
            + path_parts[-1]
        )

    return "*" + path + "*" if italic else path

cap

cap(text: str) -> str

Capitalize first char of text.

Source code in src/raesl/doc/lines.py
def cap(text: str) -> str:
    """Capitalize first char of text."""
    return text[0].upper() + text[1:]

component_node

component_node(node: Node, h: int) -> LineGen

Yield component section

Source code in src/raesl/doc/lines.py
def component_node(node: Node, h: int) -> LineGen:
    """Yield component section"""
    nameparts = node.name.split(".")
    yield header(h, "{}".format(nameparts[-1]))
    yield snt(_("this section describes **{}**".format(nameparts[-1])))
    if node.parent.name != "world":
        yield snt(_("this component is a sub-component of {}").format(node_path(node.parent.name)))

    comments = node.annotations.esl_info.get("comments", [])
    if comments:
        yield "\n"
        yield boldhead(_("comments")).replace("\n", "")
        yield "\n"
        yield from comments

    for key, comments in node.annotations.esl_info.get("tagged_comments", {}).items():
        yield "\n"
        yield boldhead(key).replace("\n", "")
        yield "\n"
        yield from comments

emph

emph(text: str, html: bool = False) -> str

Return text as emphasized (Markdown or html).

Source code in src/raesl/doc/lines.py
def emph(text: str, html: bool = False) -> str:
    """Return text as emphasized (Markdown or html)."""
    return "<em>{}</em>".format(text.strip()) if html else "*{}*".format(text.strip())

get_design_rule_line_vars

get_design_rule_line_vars(
    rules: List[Dict[str, str]], g: Graph
)

Get variables that are used within a design rule line

Source code in src/raesl/doc/lines.py
def get_design_rule_line_vars(rules: List[Dict[str, str]], g: Graph):
    """Get variables that are used within a design rule line"""
    vrs = [
        g[r["subject"]]
        for r in rules
        if g[r["subject"]].annotations.esl_info.get("bundle_root_name")
    ]

    vrs += [
        g[r["bound"]["value"]]
        for r in rules
        if (
            r["comparison"] not in ["minimized", "maximized"]
            and g.node_dict.get(r["bound"]["value"], None)
        )
        and g[r["bound"]["value"]].annotations.esl_info.get("bundle_root_name")
    ]

    return vrs

header

header(
    h: int,
    text: str,
    capitalize: bool = True,
    html: bool = False,
) -> str

Get a header with surrounding whitespace and optional capitalization.

Source code in src/raesl/doc/lines.py
def header(h: int, text: str, capitalize: bool = True, html: bool = False) -> str:
    """Get a header with surrounding whitespace and optional capitalization."""
    if capitalize:
        text = cap(text)
    return "<h{}>{}</h{}>".format(h, text, h) if html else "\n\n{} {}\n\n".format(hs(h), text)

hs

hs(h: int)

Get header pound (#) signs.

Source code in src/raesl/doc/lines.py
def hs(h: int):
    """Get header pound (#) signs."""
    return h * "#"

image

image(
    path: str,
    caption: Optional[str] = None,
    label: Optional[str] = None,
    sizing: Optional[str] = None,
) -> str

Get Pandoc Markdown for an image.

Source code in src/raesl/doc/lines.py
def image(
    path: str,
    caption: Optional[str] = None,
    label: Optional[str] = None,
    sizing: Optional[str] = None,
) -> str:
    """Get Pandoc Markdown for an image."""
    line = "\n\n![{}]({})".format(caption, path)
    attrs = ""
    if label:
        attrs += "#fig:{}".format(label)
    if sizing:
        attrs += " {}".format(sizing)
    if attrs:
        line += "{" + attrs + "}"
    return line + "\n\n"

lines

lines(node: Node, **kwargs) -> LineGen

Yield lines that describes a Node.

Parameters:

Name Type Description Default
node Node

Node to generate the lines from.

required
Source code in src/raesl/doc/lines.py
def lines(node: Node, **kwargs) -> LineGen:
    """Yield lines that describes a Node.

    Arguments:
        node: Node to generate the lines from.
    """

    genfunc = kind_mapping[node.kind]

    if not kwargs.get("html") and node.kind != "component":
        kwargs["html"] = False

    yield from genfunc(node=node, **kwargs)

node_path

node_path(
    path: str,
    italic: bool = False,
    arrows: bool = True,
    skip: str = "world",
)

Get a friendly representation of a node path.

Source code in src/raesl/doc/lines.py
def node_path(path: str, italic: bool = False, arrows: bool = True, skip: str = "world"):
    """Get a friendly representation of a node path."""
    skips = skip.split(".")
    for skip in skips:
        if path.startswith(skip):
            idx = len(skip) + 1
            path = path[idx:]
        else:
            break

    if arrows:
        path = path.replace(".", " &rarr; ")
    path = path.strip()
    if italic:
        return "*" + path + "*"
    else:
        return path

ordered

ordered(
    items: Iterable[str],
    indent: int = 0,
    html: bool = False,
) -> LineGen

Generate an ordered Markdown or html list.

Source code in src/raesl/doc/lines.py
def ordered(items: Iterable[str], indent: int = 0, html: bool = False) -> LineGen:
    """Generate an ordered Markdown or html list."""
    yield ""
    for item in items:
        if html:
            yield "<ol style='list-style-type:disc;padding-left:20px;'>"
        if type(item) is str:
            yield (
                "<li>{}</li>".format(item) if html else "{}1. {}".format(indent * " ", item)
            )  # Markdown handles numbering.
        else:
            yield from ordered(item, indent=2)
        if html:
            yield "</ol>"
    if indent == 0 and not html:
        yield "\n<!-- end of list -->\n"  # Ensures list is ended here.

snt

snt(text: str) -> str

Text to a sentence. Capitalizes first character and adds a period.

Source code in src/raesl/doc/lines.py
def snt(text: str) -> str:
    """Text to a sentence. Capitalizes first character and adds a period."""
    if not text.endswith("."):
        text += "."
    return cap(text)

unordered

unordered(
    items: Iterable[str],
    indent: int = 0,
    html: bool = False,
) -> LineGen

Generate an unordered Markdown or html list.

Source code in src/raesl/doc/lines.py
def unordered(items: Iterable[str], indent: int = 0, html: bool = False) -> LineGen:
    """Generate an unordered Markdown or html list."""
    yield ""
    for item in items:
        if html:
            yield "<ul style='list-style-type:disc;padding-left:20px;margin-top:0;margin-bottom:0;'>"  # noqa
        if type(item) is str:
            yield (
                "<li style='margin-top:-0.2em; margin-bottom:-0.2em'>{}</li>".format(item)
                if html
                else "{}* {}".format(indent * " ", item)
            )
        else:
            yield from unordered(item, indent=2)
        if html:
            yield "</ul>"
    if indent == 0 and not html:
        yield "\n<!-- end of list -->\n"  # Ensures list is ended here.

var_clarification

var_clarification(bvars: List[Node], html: bool = False)

Yield variable clarification section.

Source code in src/raesl/doc/lines.py
def var_clarification(bvars: List[Node], html: bool = False):
    """Yield variable clarification section."""
    if len(bvars) == 1:
        yield "\n"
        yield snt(
            _("where the full name of variable {} is {}").format(
                bvars[0].name.split(".")[-1],
                bundle_path(
                    path=bvars[0].name,
                    root=bvars[0].annotations.esl_info["bundle_root_name"],
                ),
            )
        )
    elif len(bvars) > 1:
        yield "\n"
        yield cap(_("where, respectively:"))
        yield from unordered(
            [
                _("variable {} has full name {}").format(
                    v.name.split(".")[-1],
                    bundle_path(
                        path=v.name,
                        root=v.annotations.esl_info["bundle_root_name"],
                    ),
                )
                for v in bvars
            ],
            html=html,
        )

var_path

var_path(
    v: Node,
    italic: bool = False,
    arrows: bool = True,
    skip: str = "world",
)

Get a friendly representation of a variable path.

Source code in src/raesl/doc/lines.py
def var_path(v: Node, italic: bool = False, arrows: bool = True, skip: str = "world"):
    """Get a friendly representation of a variable path."""
    if v.annotations.esl_info.get("bundle_root_name"):
        return bundle_path(
            path=v.name,
            root=v.annotations.esl_info["bundle_root_name"],
            arrows=arrows,
            skip=skip,
            italic=italic,
        )
    else:
        return node_path(v.name, italic=italic, arrows=arrows, skip=skip)

locales

Localization module.

Hookspecs

gettext

gettext(key: str)

Get text translation.

Source code in src/raesl/doc/locales/__init__.py
@hookspec(firstresult=True)
def gettext(key: str):
    """Get text translation."""

list_locales

list_locales()

List available locales.

Source code in src/raesl/doc/locales/__init__.py
def list_locales():
    """List available locales."""
    from pathlib import Path

    here = Path(__file__).parent
    return list(
        p.stem for p in here.glob("*") if p.suffix == ".py" and p.name not in {"__init__.py"}
    )

register_default_locale

register_default_locale(locale: str = 'en')

Register default locale if no other locale is set.

Source code in src/raesl/doc/locales/__init__.py
def register_default_locale(locale: str = "en"):
    """Register default locale if no other locale is set."""
    logger.debug("Registering default locale '{}'...".format(locale))
    if not pm.get_plugins():
        register_locale(locale)
    else:
        logger.debug("Default locale already registered.")

register_locale

register_locale(locale: str = 'en')

Register a locale. Existing locales are re-registered.

Source code in src/raesl/doc/locales/__init__.py
def register_locale(locale: str = "en"):
    """Register a locale. Existing locales are re-registered."""
    if pm.has_plugin(locale):
        pm.unregister(name=locale)
        logger.debug("Unregistered locale: {}.".format(locale))

    mod = import_module("raesl.doc.locales.{}".format(locale))
    pm.register(mod, name=locale)
    logger.debug("Registered locale: {}.".format(locale))

en

English localization.

gettext

gettext(key: str)

Get translated string.

Source code in src/raesl/doc/locales/en.py
@hookimpl
def gettext(key: str):
    """Get translated string."""
    if key in translations:
        return translations[key]
    return key

subclause_line

subclause_line(s: Dict[str, Any])

Yield subclause line.

Source code in src/raesl/doc/locales/en.py
def subclause_line(s: Dict[str, Any]):
    """Yield subclause line."""

    yield " or ".join([design_rule_line(r=r) for r in s["body"]])

nl

Dutch locale overrides.

gettext

gettext(key: str)

Get translated string.

Source code in src/raesl/doc/locales/nl.py
@hookimpl
def gettext(key: str):
    """Get translated string."""
    if key in translations:
        return translations[key]
    logger.debug("Cannot find a translation for '{}' in {}.".format(key, __file__))
    return key

make_predicate

make_predicate(verb: str) -> str

Check if a "splitsend werkwoord" has been used and return predicate.

Argument

Returns Predicate of the sentence.

Source code in src/raesl/doc/locales/nl.py
def make_predicate(verb: str) -> str:
    """Check if a "splitsend werkwoord" has been used and return predicate.

    Argument:
      verb: the used verb

    Returns
      Predicate of the sentence.
    """

    preps = [
        "aan",
        "in",
        "op",
        "om",
        "na",
        "tegen",
        "tussen",
        "uit",
        "bij",
        "mee",
        "af",
        "mee",
        "terug",
    ]

    for prep in preps:
        if verb.startswith(prep):
            return prep + " te " + verb[len(prep) :]

    return " te " + verb

subclause_line

subclause_line(s: Dict[str, Any])

Yield subclause line.

Source code in src/raesl/doc/locales/nl.py
def subclause_line(s: Dict[str, Any]):
    """Yield subclause line."""

    yield " of ".join([subclause_rule_line(r=r) for r in s["body"]])

then_clause_line

then_clause_line(s: Dict[str, Any])

Yield subclause line.

Source code in src/raesl/doc/locales/nl.py
def then_clause_line(s: Dict[str, Any]):
    """Yield subclause line."""

    yield " of ".join([then_clause_rule_line(r=r) for r in s["body"]])

rich

Rich document content.

mdm

mdm(
    graph: Graph,
    depth: int,
    rich: str = "tex",
    rich_opts: Dict[str, Any] = {},
    img_dir: Optional[Union[Path, str]] = None,
) -> Generator[str, None, None]

Generate an Multi-Domain Matrix.

Yields:

Type Description
str

Rich output lines.

Source code in src/raesl/doc/rich.py
def mdm(
    graph: Graph,
    depth: int,
    rich: str = "tex",
    rich_opts: Dict[str, Any] = {},
    img_dir: Optional[Union[Path, str]] = None,
) -> Generator[str, None, None]:
    """Generate an Multi-Domain Matrix.


    Yields:
        Rich output lines.
    """
    logger.debug("Generating MDM for depth {}...".format(depth))

    img_dir = Path(img_dir if img_dir is not None else rich_opts.get("img_dir", IMG_DIR))
    img_dir.mkdir(parents=True, exist_ok=True)

    level = depth + 1

    style = raesl.plot.Style(
        ragraph=dict(
            piemap={
                "display": "labels",
                "mode": rich_opts.get("pie_mode", DEFAULT_PIE_MODE),
            }
        )
    )

    fig = raesl.plot.mdm(
        graph,
        node_kinds=rich_opts.get("node_kinds", DEFAULT_NODE_KINDS),
        edge_kinds=rich_opts.get("edge_kinds", DEFAULT_EDGE_KINDS),
        depth=level,
        style=style,
    )

    # Width of a pixel in mm on A4 paper with 96 dpi print quality.
    pix_in_mm = 0.26458
    # Width of a line on A4 paper with 20 mm side margins
    line_width = 170

    if fig.layout.width * pix_in_mm < line_width:
        fig_size = "width={:.3f}\\linewidth".format(fig.layout.width * pix_in_mm / line_width)
        angle = 0
    else:
        fig_size = "height={:.3f}\\linewidth".format(
            min(1.0, fig.layout.height * pix_in_mm / line_width)
        )
        angle = 90

    caption = _("{} dependency matrix of decomposition level {}.").format(
        " -- ".join(rich_opts.get("node_kinds", DEFAULT_NODE_KINDS)), level
    )

    img_path = str(img_dir / "level-{}".format(level))
    if not img_dir.is_absolute():
        img_path == "./" + img_path

    logger.debug("Generating image using Plotly's Kaleido...")
    try:
        import platform

        machine = platform.machine()
        if "arm" in machine or "aarch" in machine:
            import plotly.io as pio

            if "--single-process" not in pio.kaleido.scope.chromium_args:
                pio.kaleido.scope.chromium_args += ("--single-process",)

        if rich == "tex":
            img_path = f"{img_path}.pdf"
            fig.write_image(img_path)
            latex_path = img_path.replace("\\", "/").replace("_", "-underscore-")
            yield "\\begin{figure}[!htbp]"
            yield "\\centering"
            yield "\\includegraphics[{}, angle={}]{{{}}}".format(fig_size, angle, latex_path)
            yield "\\caption{{{}}}\\label{{{}}}".format(caption, "fig:mdmlevel" + str(level))
            yield "\\end{figure}"
            logger.debug("Included image as {}.".format(rich))

        elif rich == "md":
            img_path = f"{img_path}.svg"
            fig.write_image(img_path)
            yield "![{}\\label{{{}}}]({})".format(
                caption,
                "fig:mdmlevel" + str(level),
                img_path.replace("\\", "/").replace("_", "-underscore-"),
            )
            yield "\n"
            logger.debug("Included image as {}.".format(rich))
    except Exception as e:
        logger.error(
            "Something went wrong when generating images using Plotly's Kaleido. "
            + "Kaleido might not be available. "
            + "You can also disable rich output generation for now. "
        )
        raise e

sections

comp_node_behavior_cons

comp_node_behavior_cons(
    node: Node, g: Graph, h: int = 1
) -> LineGen

Yield a behavior constraint section for a component node.

Source code in src/raesl/doc/sections.py
def comp_node_behavior_cons(node: Node, g: Graph, h: int = 1) -> LineGen:
    """Yield a behavior constraint section for a component node."""
    bhs = utils.get_component_behaviors(node, g, constraint=True)
    if not bhs:
        return

    yield lns.header(h, _("behavior constraints"))

    for b in bhs:
        table = get_node_table(node=b, graph=g)
        yield from table

comp_node_behavior_reqs

comp_node_behavior_reqs(
    node: Node, g: Graph, h: int = 1
) -> LineGen

Yield a behavior requirement section for a component node.

Source code in src/raesl/doc/sections.py
def comp_node_behavior_reqs(node: Node, g: Graph, h: int = 1) -> LineGen:
    """Yield a behavior requirement section for a component node."""
    bhs = utils.get_component_behaviors(node, g, constraint=False)
    if not bhs:
        return

    yield lns.header(h, _("behavior requirements"))

    for b in bhs:
        table = get_node_table(node=b, graph=g)
        yield from table

comp_node_design_cons

comp_node_design_cons(
    node: Node, g: Graph, h: int = 1
) -> LineGen

Yield a design constraint section for a component node.

Source code in src/raesl/doc/sections.py
def comp_node_design_cons(node: Node, g: Graph, h: int = 1) -> LineGen:
    """Yield a design constraint section for a component node."""
    dcs = utils.get_component_designs(node, g, constraint=True)
    if not dcs:
        return

    yield lns.header(h, _("quantitative design constraints"))

    for d in dcs:
        table = get_node_table(node=d, graph=g)
        yield from table

comp_node_design_reqs

comp_node_design_reqs(
    node: Node, g: Graph, h: int = 1
) -> LineGen

Yield a design requirements section for a component node.

Source code in src/raesl/doc/sections.py
def comp_node_design_reqs(node: Node, g: Graph, h: int = 1) -> LineGen:
    """Yield a design requirements section for a component node."""
    drs = utils.get_component_designs(node, g, constraint=False)
    if not drs:
        return

    yield lns.header(h, _("quantitative design requirements"))

    for d in drs:
        table = get_node_table(node=d, graph=g)
        yield from table

comp_node_goal_cons

comp_node_goal_cons(
    node: Node, g: Graph, h: int = 1
) -> LineGen

Yield a goal-constraint section for a component

Source code in src/raesl/doc/sections.py
def comp_node_goal_cons(node: Node, g: Graph, h: int = 1) -> LineGen:
    """Yield a goal-constraint section for a component"""
    glsn = utils.get_component_goals(node, g, constraint=True, inherited=False)
    glsa = utils.get_component_goals(node, g, constraint=True, inherited=True)
    if not glsn and not glsa:
        return

    yield lns.header(h, _("goal function constraints"))

    for gl in glsn:
        table = get_node_table(node=gl, graph=g)
        yield from table

    for gl in glsa:
        temp = deepcopy(gl)
        temp.annotations.esl_info["comments"].append(
            lns.snt(
                _("this goal function constraint automatically migrated from {}").format(
                    lns.node_path(temp.annotations.esl_info["body"]["active"])
                )
            )
        )
        temp.annotations.esl_info["body"]["active"] = node.name
        table = get_node_table(node=temp, graph=g)
        yield from table

comp_node_goal_reqs

comp_node_goal_reqs(
    node: Node, g: Graph, h: int = 1
) -> LineGen

Yield a goal-requirement section for a component

Source code in src/raesl/doc/sections.py
def comp_node_goal_reqs(node: Node, g: Graph, h: int = 1) -> LineGen:
    """Yield a goal-requirement section for a component"""
    glsn = utils.get_component_goals(node, g, constraint=False, inherited=False)
    glsa = utils.get_component_goals(node, g, constraint=False, inherited=True)

    if not glsn and not glsa:
        return

    yield lns.header(h, _("goal function requirements"))

    for gl in glsn:
        table = get_node_table(node=gl, graph=g)
        yield from table

    for gl in glsa:
        temp = deepcopy(gl)
        temp.annotations.esl_info["comments"].append(
            lns.snt(
                _("this goal function requirement automatically migrated from {}").format(
                    lns.node_path(temp.annotations.esl_info["body"]["active"])
                )
            )
        )
        temp.annotations.esl_info["body"]["active"] = node.name
        table = get_node_table(node=temp, graph=g)
        yield from table

comp_node_needs

comp_node_needs(
    node: Node, g: Graph, h: int = 1
) -> LineGen

Yield a need section for a component node.

Source code in src/raesl/doc/sections.py
def comp_node_needs(node: Node, g: Graph, h: int = 1) -> LineGen:
    """Yield a need section for a component node."""
    nds = utils.get_component_needs(node, g)
    if not nds:
        return

    yield lns.header(h, _("qualitative design requirements"))

    for n in nds:
        table = get_node_table(node=n, graph=g)
        yield from table

comp_node_props

comp_node_props(node: Node, g: Graph, h: int) -> LineGen

Yield a properties section for a component node

Source code in src/raesl/doc/sections.py
def comp_node_props(node: Node, g: Graph, h: int) -> LineGen:
    """Yield a properties section for a component node"""
    props = utils.get_component_properties(node, g)
    if not props:
        return

    name = node.name.split(".")[-1]
    yield lns.boldhead(_("properties") + ":")
    yield lns.cap(_("the following properties are specified for {}").format(name)) + ":"
    yield from lns.unordered(sorted([p.name.split(".")[-1] for p in props]))

comp_node_relations

comp_node_relations(
    node: Node, g: Graph, h: int = 1
) -> LineGen

Yield a relation section for a component.

Source code in src/raesl/doc/sections.py
def comp_node_relations(node: Node, g: Graph, h: int = 1) -> LineGen:
    """Yield a relation section for a component."""
    rls = [n for n in g.nodes if n.kind == "relation_spec"]

    c_rls = []
    for r in rls:
        if g[node.name, r.name]:
            c_rls.append(r)

    if not c_rls:
        return

    c_rls = sorted(c_rls, key=lambda x: lns.node_path(x.name))

    yield lns.header(h, _("external models"))

    for r in c_rls:
        table = relation_node_table(r=r, g=g)
        yield from table

comp_node_subcomps

comp_node_subcomps(node: Node, h: int = 1)

Yield a subcomponents sections for a component.

Source code in src/raesl/doc/sections.py
def comp_node_subcomps(node: Node, h: int = 1):
    """Yield a subcomponents sections for a component."""
    if not node.children:
        return

    yield lns.header(h, _("sub-components"))

    child_names = [c.name.split(".")[-1] for c in node.children]

    name = node.name.split(".")[-1]

    yield lns.cap(_("{} is composed of the following sub-components:").format(name))

    yield from lns.unordered(sorted(child_names))

comp_node_transformation_cons

comp_node_transformation_cons(
    node: Node, g: Graph, h: int = 1
) -> LineGen

Yield a transformation constraints section for a component.

Source code in src/raesl/doc/sections.py
def comp_node_transformation_cons(node: Node, g: Graph, h: int = 1) -> LineGen:
    """Yield a transformation constraints section for a component."""
    trs = utils.get_component_transformations(node, g, constraint=True)
    if not trs:
        return

    trs = sorted(trs, key=lambda x: lns.node_path(x.name))

    yield lns.header(h, _("transformation function constraints"))

    for tr in trs:
        subs = [
            e.target.name for e in g.edges if e.source == tr and e.kind == "traceability_dependency"
        ]
        table = get_node_table(tr, graph=g, sub_nodes=subs)
        yield from table

comp_node_transformation_reqs

comp_node_transformation_reqs(
    node: Node, g: Graph, h: int = 1
) -> LineGen

Yield a transformation requirements section for a component.

Source code in src/raesl/doc/sections.py
def comp_node_transformation_reqs(node: Node, g: Graph, h: int = 1) -> LineGen:
    """Yield a transformation requirements section for a component."""
    trs = utils.get_component_transformations(node, g, constraint=False)
    if not trs:
        return

    trs = sorted(trs, key=lambda x: lns.node_path(x.name))

    yield lns.header(h, _("transformation function requirements"))

    for tr in trs:
        subs = set(
            [
                e.target.name
                for e in g.edges
                if e.source == tr and e.kind == "traceability_dependency"
            ]
        )
        table = get_node_table(tr, graph=g, sub_nodes=subs)
        yield from table

get_node_table

get_node_table(
    node: Node,
    graph: Optional[Graph] = None,
    sub_nodes: Optional[List[str]] = None,
) -> List

Returns a Markdown grid table.

Source code in src/raesl/doc/sections.py
def get_node_table(
    node: Node, graph: Optional[Graph] = None, sub_nodes: Optional[List[str]] = None
) -> List:
    """Returns a Markdown grid table."""
    table = []
    table.append(TABLE_SINGLE)
    table.append("| {} |".format(lns.bold(lns.node_path(node.name))))
    table.append(TABLE_LEFT_DOUBLE)
    lines = list(lns.lines(node, graph=graph))
    main_clause = lines[0].replace("\n", "")
    table.append("| {} |".format(main_clause))
    if len(lines) > 1:
        for line in lines[1:]:
            table.append("| {} |".format(line.replace("\n", "")))
        table.append("|    |")
    table.append(TABLE_SINGLE)

    if sub_nodes:
        table.append(
            "| {} |".format(
                lns.boldhead(_("subordinate function specifications")).replace("\n", "")
            )
        )
        table.append(TABLE_SINGLE)
        for s in sub_nodes:
            table.append("| {} |".format(lns.node_path(s).replace("\n", "")))
        table.append("|    |")

        table.append(TABLE_SINGLE)

    plain_comments = (
        [("comments", node.annotations.esl_info.get("comments", []))]
        if node.annotations.esl_info.get("comments", [])
        else []
    )

    for key, comments in plain_comments + list(
        node.annotations.esl_info["tagged_comments"].items()
    ):
        table.append("| {} |".format(lns.boldhead(_(key)).replace("\n", "")))
        table.append(TABLE_SINGLE)
        for comment in comments:
            table.append("| {} |".format(comment.replace("\n", "")))
        table.append(TABLE_SINGLE)

    table.append("")

    return table

get_var_table_row_elements

get_var_table_row_elements(g: Graph, v: Node) -> str

Yield elements of a row in a var table.

Source code in src/raesl/doc/sections.py
def get_var_table_row_elements(g: Graph, v: Node) -> str:
    """Yield elements of a row in a var table."""
    if v.annotations.esl_info["comments"]:
        comments = " ".join(v.annotations.esl_info["comments"])
    else:
        comments = ""

    for key, value in v.annotations.esl_info["tagged_comments"].items():
        comments += " ".join([" ", lns.bold(lns.cap(key + ":"))] + value)

    tname = v.annotations.esl_info["type_ref"]
    t = g[tname]
    if t.annotations.esl_info.get("domain"):
        intervals = []
        for ival in t.annotations.esl_info["domain"]:
            if ival["lowerbound"]["value"] != ival["upperbound"]["value"]:
                is_enum = False

                iv = ""
                if ival["lowerbound"]["value"]:
                    unit = ival["lowerbound"]["unit"]
                    if not unit:
                        unit = ""
                    iv += "{} {} $\\leq$ x".format(ival["lowerbound"]["value"], unit)

                if ival["upperbound"]["value"] and ival["lowerbound"]["value"]:
                    unit = ival["upperbound"]["unit"]
                    if not unit:
                        unit = ""
                    iv += "$\\leq$ {} {}".format(ival["upperbound"]["value"], unit)
                elif ival["upperbound"]["value"] and not ival["lowerbound"]["value"]:
                    unit = ival["upperbound"]["unit"]
                    if not unit:
                        unit = ""
                    iv += "x $\\leq$ {} {}".format(ival["upperbound"]["value"], unit)
                intervals.append(iv)
            else:
                is_enum = True
                value = ival["lowerbound"]["value"]
                if ival["lowerbound"]["unit"]:
                    value += " " + ival["lowerbound"]["unit"]
                intervals.append(value)
        if not is_enum:
            domain = ", ".join(intervals)
        else:
            domain = _("enumeration of ") + pm.hook.linguistic_enumeration(items=intervals)
    else:
        domain = ""
    if t.annotations.esl_info.get("units"):
        units = ",".join(t.annotations.esl_info.get("units"))
    else:
        units = ""

    return v.name, tname, domain, units, comments

global_design_cons

global_design_cons(g: Graph, h: int = 1) -> LineGen

Yield a global design constraint section.

Source code in src/raesl/doc/sections.py
def global_design_cons(g: Graph, h: int = 1) -> LineGen:
    """Yield a global design constraint section."""
    dcs = utils.get_global_designs(g, constraint=True)
    if not dcs:
        return

    yield lns.header(h, _("quantitative design constraints"))

    for d in dcs:
        table = get_node_table(node=d, graph=g)
        yield from table

global_design_reqs

global_design_reqs(g: Graph, h: int = 1) -> LineGen

Yield a global design requirements section.

Source code in src/raesl/doc/sections.py
def global_design_reqs(g: Graph, h: int = 1) -> LineGen:
    """Yield a global design requirements section."""
    drs = utils.get_global_designs(g, constraint=False)
    if not drs:
        return

    yield lns.header(h, _("quantitative design requirements"))

    for d in drs:
        table = get_node_table(node=d, graph=g)
        yield from table

global_needs

global_needs(g: Graph, h: int = 1) -> LineGen

Yield a global need section.

Source code in src/raesl/doc/sections.py
def global_needs(g: Graph, h: int = 1) -> LineGen:
    """Yield a global need section."""
    nds = utils.get_global_needs(g)
    if not nds:
        return

    yield lns.header(h, _("qualitative design requirements"))

    for n in nds:
        table = get_node_table(node=n, graph=g)
        yield from table

global_needs_and_designs

global_needs_and_designs(h: int = 1)

Yield a global need and design specification intro section.

Source code in src/raesl/doc/sections.py
def global_needs_and_designs(h: int = 1):
    """Yield a global need and design specification intro section."""
    yield lns.header(h, "{}".format(_("system-wide qualitative and quantitative specifications")))

    yield lns.snt(
        _(
            "this chapter lists all qualitative and quantitative design specifications "
            "that cannot be linked to a component"
        )
    )

node_decomp_level

node_decomp_level(
    depth: int,
    comps: List[Node],
    h: int = 1,
    rich: str = None,
    rich_opts: dict = {},
)

Yield a decomposition level intro section.

Source code in src/raesl/doc/sections.py
def node_decomp_level(
    depth: int, comps: List[Node], h: int = 1, rich: str = None, rich_opts: dict = {}
):
    """Yield a decomposition level intro section."""
    yield lns.header(
        h,
        "{} {} \\label{{{}}}".format(
            _("system specification decomposition level"),
            depth + 1,
            "chp:esl" + str(depth + 1),
        ),
    )
    if depth == 0:
        leader = lns.snt(
            _(
                "this chapters describes the system of interest at the first "
                "decomposition level. That is, it describes {} components which play a "
                "role within the environment in which the system of interest must "
                "operate and the (functional) interactions between those components"
            ).format(len(comps))
        )
    elif depth > 0 and len(comps) == 1:
        leader = lns.snt(
            _(
                "this chapters describes the system of interest at decomposition level "
                "{} and introduces one additional component"
            ).format(depth + 1)
        )
    else:
        leader = lns.snt(
            _(
                "this chapters describes the system of interest at decomposition level "
                "{} and introduces {} additional components"
            ).format(depth + 1, len(comps))
        )
    yield leader

    if rich == "tex" or rich == "md":
        yield lns.snt(
            _(
                "in Figure \\ref{{{}}} the associated design-structure-matrix (DSM) is " "shown"
            ).format("fig:mdmlevel" + str(depth + 1))
        )
        yield lns.snt(
            _(
                "the DSM shows the dependencies between the elements that are relevant "
                "to this decomposition level"
            )
        )

var_node_table

var_node_table(
    g: Graph, h: int = 1, reference_list: bool = False
) -> LineGen

Yield a variable table section.

Source code in src/raesl/doc/sections.py
def var_node_table(g: Graph, h: int = 1, reference_list: bool = False) -> LineGen:
    """Yield a variable table section."""
    vrs = [n for n in g.nodes if n.kind == "variable"]
    if not vrs:
        return

    vrs = sorted(vrs, key=lambda x: lns.node_path(x.name))

    yield lns.header(h, _("list of variables"))

    yield lns.header(h + 1, _("definitions"))

    # yield "\\begin{landscape}"
    yield ""
    yield "| {} | {} | {} | {} | {} |".format(
        lns.boldhead(_("variable"), newlines=False),
        lns.boldhead(_("type"), newlines=False),
        lns.boldhead(_("domain"), newlines=False),
        lns.boldhead(_("units"), newlines=False),
        lns.boldhead(_("clarification"), newlines=False),
    )
    yield "|:---|:---|:---|:---|:---|"

    for v in vrs:
        vname, tname, domain, units, comments = get_var_table_row_elements(g, v)
        yield "| {} | {} | {} | {} | {} |".format(
            lns.var_path(v), lns.cap(tname), domain, units, comments
        )
    yield ""
    # yield "\\end{landscape}"

    if reference_list:
        yield "\\newpage{}"
        yield lns.header(h + 1, _("variable reference list"))

        yield "| {} | {} |".format(
            lns.boldhead(_("variable"), newlines=False),
            lns.boldhead(_("related to"), newlines=False),
        )
        yield "|:---|:---|"
        for v in vrs:
            rels = [
                lns.node_path(e.source.name)
                for e in g.edges
                if e.kind == "mapping_dependency" and e.target == v
            ]
            rels += [
                lns.node_path(e.target.name)
                for e in g.edges
                if e.kind == "mapping_dependency" and e.source == v
            ]
            if rels:
                refs = pm.hook.linguistic_enumeration(items=rels)
            else:
                refs = ""
            yield "| {} | {} |".format(lns.node_path(v.name), refs)
        yield ""

utils

Doc generation utility functions.

get_component_behaviors

get_component_behaviors(
    component: Node, graph: Graph, constraint: bool = True
) -> List[Node]

Get relevant behavior requirements or constraints for a component.

Source code in src/raesl/doc/utils.py
def get_component_behaviors(component: Node, graph: Graph, constraint: bool = True) -> List[Node]:
    """Get relevant behavior requirements or constraints for a component."""
    form = "constraint" if constraint else "requirement"
    return [
        b
        for b in graph.nodes
        if b.kind == "behavior_spec"
        and b.annotations.esl_info.get("form") == form
        and graph[component.name, b.name]
    ]

get_component_designs

get_component_designs(
    component: Node, graph: Graph, constraint: bool = True
) -> List[Node]

Get relevant design requirements or constraints for a component.

Source code in src/raesl/doc/utils.py
def get_component_designs(component: Node, graph: Graph, constraint: bool = True) -> List[Node]:
    """Get relevant design requirements or constraints for a component."""
    form = "constraint" if constraint else "requirement"
    return [
        d
        for d in graph.nodes
        if d.kind == "design_spec"
        and d.annotations.esl_info.get("form") == form
        and graph[component.name, d.name]
    ]

get_component_goals

get_component_goals(
    component: Node,
    graph: Graph,
    constraint: bool = True,
    inherited: bool = True,
) -> List[Node]

Get relevant goal requirements or constraints for a component.

Source code in src/raesl/doc/utils.py
def get_component_goals(
    component: Node, graph: Graph, constraint: bool = True, inherited: bool = True
) -> List[Node]:
    """Get relevant goal requirements or constraints for a component."""
    form = "constraint" if constraint else "requirement"
    ancestors = set([a.name for a in component.ancestors]) if inherited else set()
    goals = [
        n
        for n in graph.nodes
        if n.kind == "function_spec"
        and n.annotations.esl_info.get("sub_kind") == "goal"
        and n.annotations.esl_info.get("form") == form
        and (
            n.annotations.esl_info["body"].get("active") in ancestors
            if inherited
            else n.annotations.esl_info["body"].get("active") == component.name
        )
        and [e for e in graph.edges_between(component, n) if e.kind == "mapping_dependency"]
    ]
    return goals

get_component_needs

get_component_needs(
    component: Node, graph: Graph
) -> List[Node]

Get relevant needs for a component.

Source code in src/raesl/doc/utils.py
def get_component_needs(component: Node, graph: Graph) -> List[Node]:
    """Get relevant needs for a component."""
    subjects = set([n.name for n in graph.targets_of(component) if n.kind != "component"])
    subjects.add(component.name)
    return [
        n for n in graph.nodes if n.kind == "need" and n.annotations.esl_info["subject"] in subjects
    ]

get_component_properties

get_component_properties(
    component: Node, graph: Graph
) -> List[Node]

Get relevant properties for a component.

Source code in src/raesl/doc/utils.py
def get_component_properties(component: Node, graph: Graph) -> List[Node]:
    """Get relevant properties for a component."""
    return [graph[prop] for prop in component.annotations.esl_info.get("property_variables", [])]

get_component_relations

get_component_relations(
    component: Node, graph: Graph
) -> List[Node]

Get relevant relations for a component.

Source code in src/raesl/doc/utils.py
def get_component_relations(component: Node, graph: Graph) -> List[Node]:
    """Get relevant relations for a component."""
    return [n for n in graph.nodes if n.kind == "relation_spec" and graph[component.name, n.name]]

get_component_transformations

get_component_transformations(
    component: Node, graph: Graph, constraint: bool = True
) -> List[Node]

Get relevant transformation requirements or constraints for a component.

Source code in src/raesl/doc/utils.py
def get_component_transformations(
    component: Node, graph: Graph, constraint: bool = True
) -> List[Node]:
    """Get relevant transformation requirements or constraints for a component."""
    form = "constraint" if constraint else "requirement"
    transformations = [
        n
        for n in graph.nodes
        if n.kind == "function_spec"
        and n.annotations.esl_info.get("sub_kind") == "transformation"
        and n.annotations.esl_info.get("form") == form
        and n.annotations.esl_info["body"].get("active") == component.name
    ]
    return transformations

get_global_designs

get_global_designs(
    graph: Graph, constraint: bool = True
) -> List[Node]

Get globally relevant design requirments or constraints.

Source code in src/raesl/doc/utils.py
def get_global_designs(graph: Graph, constraint: bool = True) -> List[Node]:
    """Get globally relevant design requirments or constraints."""
    form = "constraint" if constraint else "requirement"
    dc_dict = {}
    for e in graph.edges:
        if e.source.kind != "component":
            continue
        dc_dict[e.target.name] = e.source.name

    drs = [
        d
        for d in graph.nodes
        if d.kind == "design_spec"
        and d.annotations.esl_info.get("form") == form
        and d.name not in dc_dict
    ]

    return drs

get_global_needs

get_global_needs(graph: Graph) -> List[Node]

Get globally relevant needs.

Source code in src/raesl/doc/utils.py
def get_global_needs(graph: Graph) -> List[Node]:
    """Get globally relevant needs."""
    sc_dict = {}
    for e in graph.edges:
        if e.source.kind != "component":
            continue
        sc_dict[e.target.name] = e.source.name

    all_needs = graph.get_nodes_by_kind("need")

    def get_subject(need):
        return need.annotations.esl_info["subject"]

    return [
        need
        for need in all_needs
        if get_subject(need) not in sc_dict and graph[get_subject(need)].kind != "component"
    ]