Skip to content

topmark.pipeline.views

View abstractions for large, phase-scoped pipeline data.

This module defines lightweight, typed "views" that expose file and header data without committing to a concrete backing representation. Implementations can be list-backed today and evolve to memory-mapped or generator-based forms later, while keeping step contracts stable and memory usage low.

The views are intentionally minimal: callers iterate or count lines instead of materializing whole images, and rich blocks/mappings are grouped in small dataclasses per phase.

ViewSlot

Bases: str, Enum

Named view slots that pipeline steps may consume.

The values are intentionally plain strings so they remain stable in logs, tests, and machine-oriented diagnostics if exposed later.

Releasable

Bases: Protocol

Protocol for views that can release large in-memory buffers.

This optional lifecycle hook allows memory-heavy views to discard their materialized state (e.g., lists of lines) after downstream steps no longer need them. The pipeline runner invokes release() when pruning is enabled to keep peak memory usage low.

Implementers should make release() idempotent: calling it multiple times must be safe and should not raise. Views that do not hold large buffers can implement a no-op release() to satisfy the protocol if needed.

Examples:

  • ListFileImageView clears its backing list[str] reference.
  • A memory-mapped view could close or unmap its file handle.

release

release()

Release any materialized buffers to reduce memory usage.

Source code in src/topmark/pipeline/views.py
def release(self) -> None:
    """Release any materialized buffers to reduce memory usage."""
    ...

FileImageView

Bases: Releasable, Protocol

Protocol for read-only access to a file's logical lines.

FileImageView extends Releasable so implementations must provide a release() method that frees materialized buffers when called by the pipeline runner during pruning.

line_count

line_count()

Return the number of logical lines in the file image.

Returns:

Name Type Description
int int

Total number of lines available via iter_lines().

Source code in src/topmark/pipeline/views.py
def line_count(self) -> int:
    """Return the number of logical lines in the file image.

    Returns:
        int: Total number of lines available via ``iter_lines()``.
    """
    ...

iter_lines

iter_lines()

Iterate the file's logical lines, preserving original line endings.

Returns:

Type Description
Iterable[str]

Iterable[str]: An iterator over the file's lines. The iterator

Iterable[str]

must yield strings exactly as they appear in the source (e.g.,

Iterable[str]

with \n/\r\n kept as read).

Source code in src/topmark/pipeline/views.py
def iter_lines(self) -> Iterable[str]:
    r"""Iterate the file's logical lines, preserving original line endings.

    Returns:
        Iterable[str]: An iterator over the file's lines. The iterator
        must yield strings exactly as they appear in the source (e.g.,
        with ``\n``/``\r\n`` kept as read).
    """
    ...

ListFileImageView dataclass

ListFileImageView(lines)

List-backed FileImageView implementation (and Releasable).

This view wraps an in-memory list[str] where each element represents a logical line including its original newline sequence (keepends semantics). Calling release discards the backing list to free memory; subsequent iteration yields an empty sequence.

Parameters:

Name Type Description Default
lines list[str]

Source lines to expose. The list is not copied; the caller retains ownership and must not mutate it while the view is used.

required
Source code in src/topmark/pipeline/views.py
def __init__(self, lines: list[str]) -> None:
    self._lines: list[str] | None = lines

line_count

line_count()

Return the number of lines in the underlying list.

Returns:

Name Type Description
int int

Total line count.

Source code in src/topmark/pipeline/views.py
def line_count(self) -> int:
    """Return the number of lines in the underlying list.

    Returns:
        int: Total line count.
    """
    return 0 if self._lines is None else len(self._lines)

iter_lines

iter_lines()

Iterate lines from the underlying list without copying.

Returns:

Type Description
Iterable[str]

Iterable[str]: An iterator over the stored lines.

Source code in src/topmark/pipeline/views.py
def iter_lines(self) -> Iterable[str]:
    """Iterate lines from the underlying list without copying.

    Returns:
        Iterable[str]: An iterator over the stored lines.
    """
    return iter(self._lines or ())

release

release()

Release materialized lines.

Source code in src/topmark/pipeline/views.py
def release(self) -> None:
    """Release materialized lines."""
    self._lines = None

HeaderView dataclass

HeaderView(
    *,
    range,
    lines,
    block,
    mapping,
    success_count=0,
    error_count=0,
)

Bases: Releasable

Structured view of the existing header detected by the scanner.

Attributes:

Name Type Description
range tuple[int, int] | None

Inclusive (start, end) line indices of the detected header block within the file, or None when absent.

lines Sequence[str] | None

Header lines exactly as found (keepends), or None when not captured.

block str | None

Concatenated header text ("".join(lines)), or None when not captured.

mapping Mapping[str, str] | None

Parsed field mapping extracted from the header, or None when parsing was not performed.

success_count int

The number of header lines that were successfully parsed and added to the mapping. Defaults to 0.

error_count int

The number of header lines that were malformed (e.g., missing a colon, or having an empty field name). Defaults to 0.

release

release()

Release header buffers (lines, block, mapping).

Source code in src/topmark/pipeline/views.py
def release(self) -> None:
    """Release header buffers (lines, block, mapping)."""
    self.lines = None
    self.block = None
    self.mapping = None

BuilderView dataclass

BuilderView(*, builtins, selected)

Bases: Releasable

Structured view of field dictionaries produced by the builder step.

Attributes:

Name Type Description
builtins Mapping[str, str] | None

Derived built-in fields (e.g., file, relpath).

selected Mapping[str, str] | None

The subset (and overrides) selected for rendering, aligned with the configuration's header_fields order.

Notes

The contained mappings are exposed read-only through abstract mapping types. Calling release() clears the references to allow pruning.

release

release()

Release the diff payload to reduce memory usage.

Source code in src/topmark/pipeline/views.py
def release(self) -> None:
    """Release the diff payload to reduce memory usage."""
    self.builtins = None
    self.selected = None

RenderView dataclass

RenderView(*, lines, block)

Bases: Releasable

Structured view of the expected header produced by the renderer.

Attributes:

Name Type Description
lines Sequence[str] | None

Rendered header lines (keepends), or None when not rendered.

block str | None

Concatenated rendered header text, or None.

Notes

Large buffers may be pruned by calling release(), which clears lines and block.

release

release()

Release the renderer payload to reduce memory usage.

Source code in src/topmark/pipeline/views.py
def release(self) -> None:
    """Release the renderer payload to reduce memory usage."""
    self.lines = None
    self.block = None

UpdatedContent

Bases: Protocol

Repeatable updated-file content abstraction.

Implementations expose updated file lines without requiring callers to own one eagerly materialized list[str]. Unlike a bare iterator, each call to iter_lines must return a fresh iterator so comparer, patcher, and writer can consume the same updated content independently.

iter_lines

iter_lines()

Iterate updated file lines repeatably.

Source code in src/topmark/pipeline/views.py
def iter_lines(self) -> Iterable[str]:
    """Iterate updated file lines repeatably."""
    ...

SegmentUpdatedContent dataclass

SegmentUpdatedContent(*, segments)

Segment-backed repeatable updated file content.

The content is represented as ordered line segments, typically slices of the original image plus a rendered-header segment. The segments themselves are not copied by this class. Each iteration walks the segments in order.

Attributes:

Name Type Description
segments tuple[Sequence[str], ...]

Ordered, repeatable line segments composing the updated image.

iter_lines

iter_lines()

Iterate the composed updated image without materializing it as one list.

Returns:

Type Description
Iterable[str]

Iterable[str]: Updated lines from every segment in order.

Source code in src/topmark/pipeline/views.py
def iter_lines(self) -> Iterable[str]:
    """Iterate the composed updated image without materializing it as one list.

    Returns:
        Iterable[str]: Updated lines from every segment in order.
    """
    return iter(self)

PlanEditKind

Bases: str, Enum

Kind of contiguous edit planned for an updated file image.

PlannedEdit dataclass

PlannedEdit(*, kind, old_start, old_end, new_lines)

Single contiguous splice between the original and updated file images.

Attributes:

Name Type Description
kind PlanEditKind

Logical edit kind. This is descriptive; old_start, old_end, and new_lines define the actual splice.

old_start int

Inclusive zero-based start index in the original image.

old_end int

Exclusive zero-based end index in the original image.

new_lines tuple[str, ...]

Replacement lines inserted at old_start after removing original_lines[old_start:old_end].

EditView dataclass

EditView(*, edits)

Bases: Releasable

Structured view of planned contiguous edits.

The first implementation uses this as metadata for shadow diff validation. It deliberately allows multiple edits so the model can grow to multi-hunk rendering later, while the initial structured renderer only consumes a single edit and falls back to generic diffing otherwise.

Attributes:

Name Type Description
edits tuple[PlannedEdit, ...]

Planned edits in ascending original-file order.

release

release()

Release planned-edit metadata.

Source code in src/topmark/pipeline/views.py
def release(self) -> None:
    """Release planned-edit metadata."""
    self.edits = ()

UpdatedView dataclass

UpdatedView(*, lines)

Bases: Releasable

View of the pipeline's updated file image.

lines may be a sequence or an UpdatedContent implementation. Bare iterables remain accepted for backward compatibility, but repeatable UpdatedContent is preferred for pipeline-generated updates.

Attributes:

Name Type Description
lines UpdatedContent | Sequence[str] | None

Updated file image as a sequence or iterable of lines, or None when no update was produced.

Notes

Pruning is handled by calling release(), which clears the updated file image reference. Pipeline-generated lazy content should be repeatable; arbitrary caller-provided iterables may still be single-pass.

release

release()

Release the updated file image payload to reduce memory usage.

Source code in src/topmark/pipeline/views.py
def release(self) -> None:
    """Release the updated file image payload to reduce memory usage."""
    self.lines = None

DiffView dataclass

DiffView(*, text)

Bases: Releasable

Unified diff view for CLI/CI consumption.

Attributes:

Name Type Description
text str | None

Unified diff as a single string, or None when no diff was generated.

Notes

Pruning is done by calling release(), which nulls text to free memory.

release

release()

Release the diff payload to reduce memory usage.

Source code in src/topmark/pipeline/views.py
def release(self) -> None:
    """Release the diff payload to reduce memory usage."""
    self.text = None

Views dataclass

Views(
    *,
    image=None,
    header=None,
    build=None,
    render=None,
    updated=None,
    edit=None,
    diff=None,
)

Bundle of phase-scoped, releasable views for a single file.

Notes

The bundle itself provides release_all() to prune memory after a run. Individual views remain responsible for their own release() behavior.

release_all

release_all()

Release all non-None views safely (idempotent).

Source code in src/topmark/pipeline/views.py
def release_all(
    self,
) -> None:
    """Release all non-None views safely (idempotent)."""
    self.release_image()
    self.release_header()
    self.release_build()
    self.release_render()
    self.release_updated()
    self.release_edit()
    self.release_diff()

release_image

release_image()

Release the original file image view when it is no longer needed.

Source code in src/topmark/pipeline/views.py
def release_image(self) -> None:
    """Release the original file image view when it is no longer needed."""
    if self.image:
        self.image.release()

release_header

release_header()

Release detected-header payloads when they are no longer needed.

Source code in src/topmark/pipeline/views.py
def release_header(self) -> None:
    """Release detected-header payloads when they are no longer needed."""
    if self.header:
        self.header.release()

release_build

release_build()

Release builder payloads when they are no longer needed.

Source code in src/topmark/pipeline/views.py
def release_build(self) -> None:
    """Release builder payloads when they are no longer needed."""
    if self.build:
        self.build.release()

release_render

release_render()

Release rendered-header payloads when they are no longer needed.

Source code in src/topmark/pipeline/views.py
def release_render(self) -> None:
    """Release rendered-header payloads when they are no longer needed."""
    if self.render:
        self.render.release()

release_updated

release_updated()

Release updated-file payloads when they are no longer needed.

Source code in src/topmark/pipeline/views.py
def release_updated(self) -> None:
    """Release updated-file payloads when they are no longer needed."""
    if self.updated:
        self.updated.release()

release_edit

release_edit()

Release planned-edit metadata when it is no longer needed.

Source code in src/topmark/pipeline/views.py
def release_edit(self) -> None:
    """Release planned-edit metadata when it is no longer needed."""
    if self.edit:
        self.edit.release()

release_diff

release_diff()

Release diff payloads when they are no longer needed.

Source code in src/topmark/pipeline/views.py
def release_diff(self) -> None:
    """Release diff payloads when they are no longer needed."""
    if self.diff:
        self.diff.release()

release_consumed

release_consumed(
    *, remaining_view_consumers, keep_diff_view=False
)

Release views that no remaining pipeline step declares as consumed.

Parameters:

Name Type Description Default
remaining_view_consumers set[ViewSlot]

View slots consumed by steps that have not run yet.

required
keep_diff_view bool

Whether to preserve the diff view for callers that render it after the pipeline completes.

False
Source code in src/topmark/pipeline/views.py
def release_consumed(
    self,
    *,
    remaining_view_consumers: set[ViewSlot],
    keep_diff_view: bool = False,
) -> None:
    """Release views that no remaining pipeline step declares as consumed.

    Args:
        remaining_view_consumers: View slots consumed by steps that have not run yet.
        keep_diff_view: Whether to preserve the diff view for callers that render it after the
            pipeline completes.
    """
    if ViewSlot.IMAGE not in remaining_view_consumers:
        self.release_image()

    if ViewSlot.HEADER not in remaining_view_consumers:
        self.release_header()

    if ViewSlot.BUILD not in remaining_view_consumers:
        self.release_build()

    if ViewSlot.RENDER not in remaining_view_consumers:
        self.release_render()

    if ViewSlot.UPDATED not in remaining_view_consumers:
        self.release_updated()

    if ViewSlot.EDIT not in remaining_view_consumers:
        self.release_edit()

    if not keep_diff_view and ViewSlot.DIFF not in remaining_view_consumers:
        self.release_diff()

as_dict

as_dict()

Short machine-friendly summary; avoid heavy text blobs.

Source code in src/topmark/pipeline/views.py
def as_dict(self) -> dict[str, object]:
    """Short machine-friendly summary; avoid heavy text blobs."""
    return {
        "image_lines": self.image.line_count() if self.image else 0,
        "header_range": getattr(self.header, "range", None),
        "header_fields": dict(self.header.mapping or {}) if self.header else None,
        "build_selected": dict(self.build.selected or {}) if self.build else None,
        "render_line_count": (
            len(self.render.lines) if (self.render and self.render.lines is not None) else 0
        ),
        "updated_has_lines": self.updated is not None and self.updated.lines is not None,
        "edit_count": len(self.edit.edits) if self.edit else 0,
        "diff_present": bool(self.diff and self.diff.text),
    }

compose_updated_content

compose_updated_content(*segments)

Create a repeatable segment-backed updated-content view.

Parameters:

Name Type Description Default
*segments Sequence[str]

Ordered line segments. Empty segments are retained only when all segments are empty so an explicitly empty updated image remains representable.

()

Returns:

Name Type Description
SegmentUpdatedContent SegmentUpdatedContent

Repeatable content composed from the supplied

SegmentUpdatedContent

segments.

Source code in src/topmark/pipeline/views.py
def compose_updated_content(*segments: Sequence[str]) -> SegmentUpdatedContent:
    """Create a repeatable segment-backed updated-content view.

    Args:
        *segments: Ordered line segments. Empty segments are retained only when
            all segments are empty so an explicitly empty updated image remains
            representable.

    Returns:
        SegmentUpdatedContent: Repeatable content composed from the supplied
        segments.
    """
    return SegmentUpdatedContent(segments=tuple(segments))

infer_single_planned_edit

infer_single_planned_edit(
    *, kind, original_lines, updated_lines
)

Infer the minimal contiguous splice that transforms original into updated.

Parameters:

Name Type Description Default
kind PlanEditKind

Logical edit kind to associate with the inferred splice.

required
original_lines Sequence[str]

Original file image lines.

required
updated_lines Sequence[str]

Updated file image lines.

required

Returns:

Type Description
PlannedEdit | None

PlannedEdit | None: The inferred splice, or None when the images are

PlannedEdit | None

identical and therefore no edit exists.

Source code in src/topmark/pipeline/views.py
def infer_single_planned_edit(
    *,
    kind: PlanEditKind,
    original_lines: Sequence[str],
    updated_lines: Sequence[str],
) -> PlannedEdit | None:
    """Infer the minimal contiguous splice that transforms original into updated.

    Args:
        kind: Logical edit kind to associate with the inferred splice.
        original_lines: Original file image lines.
        updated_lines: Updated file image lines.

    Returns:
        PlannedEdit | None: The inferred splice, or ``None`` when the images are
        identical and therefore no edit exists.
    """
    prefix_len: int = 0
    max_prefix_len: int = min(len(original_lines), len(updated_lines))
    while prefix_len < max_prefix_len and original_lines[prefix_len] == updated_lines[prefix_len]:
        prefix_len += 1

    original_suffix: int = len(original_lines)
    updated_suffix: int = len(updated_lines)
    while (
        original_suffix > prefix_len
        and updated_suffix > prefix_len
        and original_lines[original_suffix - 1] == updated_lines[updated_suffix - 1]
    ):
        original_suffix -= 1
        updated_suffix -= 1

    if prefix_len == len(original_lines) and prefix_len == len(updated_lines):
        return None

    return PlannedEdit(
        kind=kind,
        old_start=prefix_len,
        old_end=original_suffix,
        new_lines=tuple(updated_lines[prefix_len:updated_suffix]),
    )