Esempio n. 1
0
    def from_obj(cls, obj: any) -> Objdict:
        """
        Recursively convert an object to object dictionaries, even if embedded in lists.
          Used primarily for accessing configurations, so it doesn't need to convert class objects.
        """
        # CASE: list. Convert each item in the list.
        if isinstance(obj, list):
            value = [cls.from_obj(item) for item in obj]

        # CASE: dictionary. Convert each item in the dictionary.
        elif isinstance(obj, dict):
            d = {k: cls.from_obj(v) for k, v in obj.items()}
            value = cls(**d)

        # CASE: basic number or string. Use the item "as is"
        elif (isinstance(obj, str) or isinstance(obj, Number)
              or isinstance(obj, date) or obj is None):
            value = obj

        # CASE: object with an internal dictionary. Treat like a dictionary.
        elif hasattr(obj, "__dict__"):
            value = cls.from_obj(obj.__dict__)

        # OTHERWISE: we need to figure it out.
        else:
            raise DocumentException(
                f"Objdict.from_dict: can't convert value {obj}")

        return value
Esempio n. 2
0
    def __init__(self, gen: Iterable[Element]):
        if gen is INVALID:
            raise DocumentException("Attempting to query an INVALID")

        if isinstance(gen, QueryStream):
            self.gen = gen.gen
        else:
            self.gen = iter(gen)
Esempio n. 3
0
def dataURL(file: str, _file_=None, **kwargs) -> str:
    """
    Convert an image file into an inlined data url.
    """
    path = Path(pathLookup(file, _file_))
    if not path.exists():
        raise DocumentException(f"Image file {file} can't be found")

    return dataUrlFromBytes(path.read_bytes(), path.suffix)
Esempio n. 4
0
    def optional(self) -> Element:
        """
        Verify the stream has at most one element and return it (or None)
        """
        element = next(self, INVALID)
        if next(self, INVALID) is not INVALID:
            raise DocumentException(f"scribble query returned more than one choice")

        return element
Esempio n. 5
0
    def one(self) -> Element:
        """
        Verify the stream has exactly one element and return it.
        """
        unit = self.optional()
        if unit is INVALID:
            raise DocumentException(f"scribble query 'one' encountered an empty list.")

        return unit
Esempio n. 6
0
def dataUrlFromBytes(img: bytes, suffix: str) -> str:
    """
    Convert a sequence of image bytes into an inlined data url
    """
    if suffix not in mimeHeader:
        raise DocumentException(
            "embedImg: Unable to embed images of type {type")

    src = f"data:image/{mimeHeader[suffix]},{base64(img)}"
    return src
Esempio n. 7
0
def Image(img: Union[str, bytes],
          *,
          alt="",
          width="",
          suffix="",
          **kwargs) -> Text:
    """
    A document section consisting solely if an image.
    """
    # Create a data source url from the image data
    if isinstance(img, str):
        src = dataURL(img, **kwargs)
    elif isinstance(img, bytes) and suffix:
        src = dataUrlFromBytes(img, suffix)
    else:
        raise DocumentException(
            f"Image - must pass file name or bytes+suffix  id={id} alt={alt}")

    # Create the asciidoc "image::...." with an optional width.
    w = f", width={width}" if width else ""  # optional width
    img = f"image::{src}[{alt}{w}]"
    return Text(img)
Esempio n. 8
0
def consistent_values(elements: List[Element], *paths: str):
    """
    Verifies all the elements have equal data items. Throws exception on failure.
       We are checking for consistent values, NOT for invalid data.
       It is OK if the value is missing, as long as it is missing on all of the elements.
    :param elements: A List of similar elements
    :param paths: the reference paths which must match
    """
    # if there is more than one element
    if len(elements) > 1:

        # For each of the given paths
        for path in paths:

            # Compare the first element with all the subsequent ones
            first = elements[0].get_path(path)
            for subsequent in elements[1:]:
                current = subsequent.get_path(path)

                # Raise an exception if they have different values.
                if first != current:
                    raise DocumentException(
                        f"Inconsistent values: {path} -- {first}|{current}")
Esempio n. 9
0
 def __next__(self):
     raise DocumentException(
         f"Attempting to iterate a non-existent value MISSING={MISSING}")
Esempio n. 10
0
 def __str__(self):
     raise DocumentException(
         f"Attempting to display a non-existent value MISSING={MISSING}")
Esempio n. 11
0
def setup(
    config: str,
    output: str,
    design_file: str,
    sections: List[str],
    directories: List[str],
    values: List[str],
):
    """
    Read in the document and design data needed to create a document.
    """
    # Read in the document configuration (or start from scratch)
    if config:
        doc = Element.read(config)  # config cannot have interpolations.
        doc.config = config
    else:
        doc = Element()

    # Update high level document fields if passed in command line.
    if design_file:
        doc.design_file = design_file
    if output:
        doc.output_name = output

    # Merge document sections and snippets from both the command line and the configuration
    doc.document_sections = [*sections, *doc.document_sections]
    doc.directories = [*directories, *doc.directories]

    # We should not have both a design and a design file
    if doc.design and doc.design_file:
        raise DocumentException(
            f"The document should not have both a design and a design file {doc.design_file}."
        )

    # We must have at least one document section
    if not doc.document_sections:
        raise DocumentException(
            "The document must have at least one document-section")

    # Apply the command line values to the document. These override any values in the config.
    for assignment in values:
        [path, value] = assignment.split("=", 1)
        doc.set_path(path, value)

    # Set up interpolations for this document. Process {here} immediately.
    def dir(name):
        return name and dirname(name)

    initPathInterpolation(
        dir(doc.config) or "",
        config=dir(doc.config) or "",
        design=dir(doc.design_file) or "",
        output=dir(doc.output_name) or "",
        **(doc.paths or {}),
    )

    # Read in the design file (if present)
    if doc.design_file:
        doc.design = Element.read(pathLookup(doc.design_file, doc.config))

    # Add the additional document directories to sys.path so we can find sections.
    if doc.directories:
        directories = [
            pathLookup(directory, doc.config) for directory in doc.directories
        ]
        addImportPath(*directories)

    # Keep track of which directories contain component snippets. Mainly to help with Fixups.
    doc.snippets = [
        path for path in sys.path if Path(f"{path}/components").is_dir()
    ]

    # Prepare to load jinja2 templates as Python modules.
    JinjaFileLoader.install()

    # Add some necessary procedures so templates can access them.
    template.GLOBALS.update(Section=Section,
                            Snippet=Snippet,
                            Figure=Figure,
                            Image=Image)

    # Apply fixups to the document.
    fixup_document(doc)

    # Finished. Our design/document tree is set up. No more changes to the design tree.
    #  We can "memoize" future queries against the tree.
    MemoizedQueryStream.enable()
    return doc