Source code for generate_changelog.actions.file_processing

"""File reading and writing actions."""
import re
from dataclasses import dataclass
from pathlib import Path

import typer

from generate_changelog.actions import register_builtin
from generate_changelog.configuration import StrOrCallable
from generate_changelog.utilities import eval_if_callable


[docs] @register_builtin @dataclass(frozen=True) class ReadFile: """Return a file's contents when called.""" filename: StrOrCallable """The file name to read when called.""" create_if_missing: bool = True """When True, create a missing file. Otherwise returns an error."""
[docs] def __call__(self, *args, **kwargs) -> str: """Return the contents of the file.""" filepath = Path(eval_if_callable(self.filename)) if self.create_if_missing: filepath.touch() if not filepath.exists(): typer.echo(f"The file '{filepath}' does not exist.", err=True) raise typer.Exit(1) return filepath.read_text() or ""
[docs] @register_builtin @dataclass(frozen=True) class WriteFile: """Write the passed string to a file when called.""" filename: StrOrCallable """The file name to write when called."""
[docs] def __call__(self, input_text: StrOrCallable) -> StrOrCallable: """Writes input_text to the pre-configured file.""" filepath = Path(eval_if_callable(self.filename)) text = eval_if_callable(input_text) filepath.write_text(text) return input_text
[docs] @register_builtin def stdout(content: str) -> str: """Write content to stdout.""" typer.echo(content) return content
[docs] @register_builtin @dataclass(frozen=True) class IncrementalFileInsert: """Replace the start of a file with text.""" filename: StrOrCallable """The file name to write when called.""" last_heading_pattern: StrOrCallable """A regular expression to detect the last heading. Content before this position is re-rendered and inserted."""
[docs] def __call__(self, input_text: StrOrCallable) -> StrOrCallable: """ Replace the beginning of the file up to ``last_heading_pattern`` with ``input_text`` . Args: input_text: The text to insert. Returns: The same ``input_text`` """ filename = Path(eval_if_callable(self.filename)) pattern = eval_if_callable(self.last_heading_pattern) text = eval_if_callable(input_text) existing_text = filename.read_text() if filename.exists() else "" match = re.search(pattern, existing_text, re.MULTILINE) if match: new_text = f"{text}\n{existing_text[match.start():]}" else: new_text = text filename.write_text(new_text) return input_text