Skip to content

Commit

Permalink
Refactor style inputs to a dataclass (#15)
Browse files Browse the repository at this point in the history
  • Loading branch information
ReinderVosDeWael authored May 10, 2024
1 parent c144f41 commit 6630a34
Show file tree
Hide file tree
Showing 9 changed files with 106 additions and 77 deletions.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "cmi_docx"
version = "0.1.6"
version = "0.2.0"
description = ".docx utilities"
authors = ["Reinder Vos de Wael <[email protected]>"]
license = "LGPL-2.1"
Expand Down
1 change: 1 addition & 0 deletions src/cmi_docx/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
from cmi_docx.document import ExtendDocument # noqa: F401
from cmi_docx.paragraph import ExtendParagraph, FindParagraph # noqa: F401
from cmi_docx.run import ExtendRun, FindRun # noqa: F401
from cmi_docx.styles import ParagraphStyle, RunStyle, TableStyle # noqa: F401
from cmi_docx.table import ExtendCell # noqa: F401
5 changes: 2 additions & 3 deletions src/cmi_docx/document.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
"""Extends a python-docx Word document with additional functionality."""

import pathlib
from typing import Any

from docx import document
from docx.text import paragraph as docx_paragraph

from cmi_docx import paragraph, run
from cmi_docx import paragraph, run, styles


class ExtendDocument:
Expand Down Expand Up @@ -46,7 +45,7 @@ def find_in_runs(self, needle: str) -> list[run.FindRun]:
]

def replace(
self, needle: str, replace: str, style: dict[str, Any] | None = None
self, needle: str, replace: str, style: styles.RunStyle | None = None
) -> None:
"""Finds and replaces text in a Word document.
Expand Down
19 changes: 10 additions & 9 deletions src/cmi_docx/paragraph.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,12 @@
import dataclasses
import itertools
import re
from typing import Any

from docx.enum import text
from docx.text import paragraph as docx_paragraph
from docx.text import run as docx_run

from cmi_docx import run
from cmi_docx import run, styles


@dataclasses.dataclass
Expand Down Expand Up @@ -99,7 +98,7 @@ def find_in_runs(self, needle: str) -> list[run.FindRun]:
return run_finds

def replace(
self, needle: str, replace: str, style: dict[str, Any] | None = None
self, needle: str, replace: str, style: styles.RunStyle | None = None
) -> None:
"""Finds and replaces text in a Word paragraph.
Expand All @@ -116,7 +115,7 @@ def replace(
for run_find in run_finder:
run_find.replace(replace, style)

def insert_run(self, index: int, text: str, style: dict[str, Any]) -> docx_run.Run:
def insert_run(self, index: int, text: str, style: styles.RunStyle) -> docx_run.Run:
"""Inserts a run into a paragraph.
Args:
Expand All @@ -137,7 +136,7 @@ def insert_run(self, index: int, text: str, style: dict[str, Any]) -> docx_run.R
else:
self.paragraph.runs[index]._element.addprevious(new_run)

run.ExtendRun(self.paragraph.runs[index]).format(**style)
run.ExtendRun(self.paragraph.runs[index]).format(style)
return self.paragraph.runs[index]

def format(
Expand Down Expand Up @@ -178,8 +177,10 @@ def format(

for paragraph_run in self.paragraph.runs:
run.ExtendRun(paragraph_run).format(
bold=bold,
italics=italics,
font_size=font_size,
font_rgb=font_rgb,
styles.RunStyle(
bold=bold,
italic=italics,
font_size=font_size,
font_rgb=font_rgb,
)
)
84 changes: 34 additions & 50 deletions src/cmi_docx/run.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
"""Module for extending python-docx Run objects."""

from typing import Any

from docx import shared
from docx.text import paragraph as docx_paragraph

from cmi_docx import styles


class FindRun:
"""Data class for maintaing find results in runs.
Expand Down Expand Up @@ -40,7 +40,7 @@ def runs(self) -> list[docx_paragraph.Run]:
"""Returns the runs containing the text."""
return self.paragraph.runs[self.run_indices[0] : self.run_indices[1] + 1]

def replace(self, replace: str, style: dict[str, Any] | None = None) -> None:
def replace(self, replace: str, style: styles.RunStyle | None = None) -> None:
"""Replaces the text in the runs with the replacement text.
Args:
Expand Down Expand Up @@ -79,7 +79,7 @@ def _replace_without_style(self, replace: str) -> None:
run.clear()
self.runs[-1].text = self.runs[-1].text[end:]

def _replace_with_style(self, replace: str, style: dict[str, Any]) -> None:
def _replace_with_style(self, replace: str, style: styles.RunStyle) -> None:
"""Replaces the text in the runs with the replacement text and style.
Args:
Expand All @@ -95,13 +95,13 @@ def _replace_with_style(self, replace: str, style: dict[str, Any]) -> None:
new_run = self.paragraph._element._new_r()
new_run.text = replace
self.paragraph.runs[self.run_indices[0]]._element.addnext(new_run)
ExtendRun(self.paragraph.runs[self.run_indices[0] + 1]).format(**style)
ExtendRun(self.paragraph.runs[self.run_indices[0] + 1]).format(style)

post_run = self.paragraph._element._new_r()
post_run.text = post
self.paragraph.runs[self.run_indices[0] + 1]._element.addnext(post_run)
pre_style = ExtendRun(self.paragraph.runs[self.run_indices[0]]).get_format()
ExtendRun(self.paragraph.runs[self.run_indices[0] + 2]).format(**pre_style)
ExtendRun(self.paragraph.runs[self.run_indices[0] + 2]).format(pre_style)

def __lt__(self, other: "FindRun") -> bool:
"""Sorts FindRun in order of appearance in the paragraph.
Expand Down Expand Up @@ -135,59 +135,43 @@ def __init__(self, run: docx_paragraph.Run) -> None:
"""
self.run = run

def format(
self,
*,
bold: bool | None = None,
italics: bool | None = None,
underline: bool | None = None,
superscript: bool | None = None,
subscript: bool | None = None,
font_size: int | None = None,
font_rgb: tuple[int, int, int] | None = None,
) -> None:
def format(self, style: styles.RunStyle) -> None:
"""Formats a run in a Word document.
Args:
bold: Whether to bold the run.
italics: Whether to italicize the run.
underline: Whether to underline the run.
superscript: Whether to superscript the run.
subscript: Whether to subscript the run.
font_size: The font size of the run.
font_rgb: The font color of the run.
style: The style to apply to the run.
"""
if superscript and subscript:
if style.superscript and style.subscript:
msg = "Cannot have superscript and subscript at the same time."
raise ValueError(msg)

if bold is not None:
self.run.bold = bold
if italics is not None:
self.run.italic = italics
if underline is not None:
self.run.underline = underline
if superscript is not None:
self.run.font.superscript = superscript
if subscript is not None:
self.run.font.subscript = subscript
if font_size is not None:
self.run.font.size = font_size
if font_rgb is not None:
self.run.font.color.rgb = shared.RGBColor(*font_rgb)

def get_format(self) -> dict[str, Any]:
if style.bold is not None:
self.run.bold = style.bold
if style.italic is not None:
self.run.italic = style.italic
if style.underline is not None:
self.run.underline = style.underline
if style.superscript is not None:
self.run.font.superscript = style.superscript
if style.subscript is not None:
self.run.font.subscript = style.subscript
if style.font_size is not None:
self.run.font.size = style.font_size
if style.font_rgb is not None:
self.run.font.color.rgb = shared.RGBColor(*style.font_rgb)

def get_format(self) -> styles.RunStyle:
"""Returns the formatting of the run.
Returns:
The formatting of the run.
"""
return {
"bold": self.run.bold,
"italics": self.run.italic,
"underline": self.run.underline,
"superscript": self.run.font.superscript,
"subscript": self.run.font.subscript,
"font_size": self.run.font.size,
"font_rgb": self.run.font.color.rgb,
}
return styles.RunStyle(
bold=self.run.bold,
italic=self.run.italic,
underline=self.run.underline,
superscript=self.run.font.superscript,
subscript=self.run.font.subscript,
font_size=self.run.font.size,
font_rgb=self.run.font.color.rgb,
)
42 changes: 42 additions & 0 deletions src/cmi_docx/styles.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"""Style interfaces for document properties."""

import dataclasses

from docx.enum import text


@dataclasses.dataclass
class RunStyle:
"""Dataclass for run style arguments."""

bold: bool | None = None
italic: bool | None = None
underline: bool | None = None
superscript: bool | None = None
subscript: bool | None = None
font_size: int | None = None
font_rgb: tuple[int, int, int] | None = None


@dataclasses.dataclass
class ParagraphStyle:
"""Dataclass for paragraph style arguments."""

bold: bool | None = None
italic: bool | None = None
font_size: int | None = None
font_rgb: tuple[int, int, int] | None = None
line_spacing: float | None = None
space_before: float | None = None
space_after: float | None = None
alignment: text.WD_PARAGRAPH_ALIGNMENT | None = None


@dataclasses.dataclass
class TableStyle:
"""Dataclass for table style arguments."""

space_before: float | None = None
space_after: float | None = None
background_rgb: tuple[int, int, int] | None = None
paragraph_style: ParagraphStyle | None = None
4 changes: 2 additions & 2 deletions tests/test_document.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import docx
import pytest

from cmi_docx import document
from cmi_docx import document, styles


def test_find_in_paragraphs() -> None:
Expand Down Expand Up @@ -96,7 +96,7 @@ def test_replace_with_style() -> None:
doc.add_paragraph("Hello, world!")
extend_document = document.ExtendDocument(doc)

extend_document.replace("Hello", "Goodbye", {"bold": True})
extend_document.replace("Hello", "Goodbye", styles.RunStyle(bold=True))

assert doc.paragraphs[0].text == "Goodbye, world!"
assert not doc.paragraphs[0].runs[0].bold
Expand Down
10 changes: 5 additions & 5 deletions tests/test_paragraph.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import pytest
from docx.text import paragraph as docx_paragraph

from cmi_docx import paragraph
from cmi_docx import paragraph, styles


@pytest.fixture
Expand Down Expand Up @@ -96,7 +96,7 @@ def test_insert_run_middle() -> None:
para.add_run("world!")
extend_paragraph = paragraph.ExtendParagraph(para)

extend_paragraph.insert_run(1, "beautiful ", {"bold": True})
extend_paragraph.insert_run(1, "beautiful ", styles.RunStyle(bold=True))

assert para.text == "Hello beautiful world!"
assert para.runs[1].bold
Expand All @@ -108,7 +108,7 @@ def test_insert_run_start() -> None:
para = document.add_paragraph("world!")
extend_paragraph = paragraph.ExtendParagraph(para)

extend_paragraph.insert_run(0, "Hello ", {"bold": True})
extend_paragraph.insert_run(0, "Hello ", styles.RunStyle(bold=True))

assert para.text == "Hello world!"
assert para.runs[0].bold
Expand All @@ -121,7 +121,7 @@ def test_insert_run_end(index: int) -> None:
para = document.add_paragraph("Hello")
extend_paragraph = paragraph.ExtendParagraph(para)

extend_paragraph.insert_run(index, " world!", {"bold": True})
extend_paragraph.insert_run(index, " world!", styles.RunStyle(bold=True))

assert para.text == "Hello world!"
assert para.runs[1].bold
Expand All @@ -133,7 +133,7 @@ def test_insert_run_empty() -> None:
para = document.add_paragraph("")
extend_paragraph = paragraph.ExtendParagraph(para)

extend_paragraph.insert_run(0, "Hello", {"bold": True})
extend_paragraph.insert_run(0, "Hello", styles.RunStyle(bold=True))

assert para.text == "Hello"
assert para.runs[0].bold
16 changes: 9 additions & 7 deletions tests/test_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import docx
import pytest

from cmi_docx import run
from cmi_docx import run, styles


def test_find_run_lt_same_paragraph() -> None:
Expand Down Expand Up @@ -75,7 +75,7 @@ def test_find_run_replace_with_style() -> None:
paragraph.add_run("Hello, world!")

find_run = run.FindRun(paragraph, (0, 1), (0, 5))
find_run.replace("Goodbye", {"bold": True})
find_run.replace("Goodbye", styles.RunStyle(bold=True))

assert paragraph.text == "Goodbye, world!"
assert not paragraph.runs[0].bold
Expand All @@ -91,11 +91,13 @@ def test_extend_run_format() -> None:

extend_run = run.ExtendRun(paragraph_run)
extend_run.format(
bold=True,
italics=True,
underline=True,
superscript=True,
font_rgb=(1, 0, 0),
styles.RunStyle(
bold=True,
italic=True,
underline=True,
superscript=True,
font_rgb=(1, 0, 0),
)
)

assert paragraph_run.bold
Expand Down

0 comments on commit 6630a34

Please sign in to comment.