From a524db17223d5eac0bdf53a64120233bab125f71 Mon Sep 17 00:00:00 2001 From: Denis Nutiu Date: Wed, 31 May 2023 18:30:30 +0300 Subject: [PATCH] abstract IO for easier testing in the future --- app/converter/converter.py | 10 ++++-- app/converter/wordpress_markdown.py | 47 +++++++++++--------------- app/io/__init__.py | 0 app/io/reader.py | 39 ++++++++++++++++++++++ app/io/writer.py | 52 +++++++++++++++++++++++++++++ 5 files changed, 119 insertions(+), 29 deletions(-) create mode 100644 app/io/__init__.py create mode 100644 app/io/reader.py create mode 100644 app/io/writer.py diff --git a/app/converter/converter.py b/app/converter/converter.py index 4f0e608..d7c43d8 100644 --- a/app/converter/converter.py +++ b/app/converter/converter.py @@ -5,6 +5,8 @@ from pathlib import Path from app import utils from app.config import Configurator from app.converter.wordpress_markdown import WordpressMarkdownConverter +from app.io.reader import FileReader +from app.io.writer import FileWriter class Converter: @@ -44,7 +46,11 @@ class Converter: _, _, files = next(os.walk(source_path)) for file in files: source_abs_path = source_path / Path(file) + + file_reader = FileReader(str(source_abs_path)) + file_writer = FileWriter(output_path.joinpath(source_abs_path.name)) + self.markdown_converter.convert_jekyll_to_hugo( - source_abs_path, - output_path, + file_reader, + file_writer, ) diff --git a/app/converter/wordpress_markdown.py b/app/converter/wordpress_markdown.py index 5cb989e..42e5915 100644 --- a/app/converter/wordpress_markdown.py +++ b/app/converter/wordpress_markdown.py @@ -1,10 +1,10 @@ -from pathlib import Path - import yaml from bs4 import BeautifulSoup, Tag from app import utils from app.config import Configurator +from app.io.reader import IoReader +from app.io.writer import IoWriter from app.utils import key_error_silence @@ -106,53 +106,46 @@ class WordpressMarkdownConverter: return "\n".join(fixed_lines) - def read_jekyll_post(self, path: Path): + def read_jekyll_post(self, reader: IoReader): """ - Read a Jekyll post from the specified path + Read a Jekyll post from the reader. Parameters ---------- - path : Path - The path to the Jekyll post + reader : IoReader + The IoReader instance for reading. """ # read source - with open(path, "r") as fh: - contents = fh.read() - return contents + return reader.read() - def write_hugo_post(self, output_path, post_header: dict, post_content: str): + def write_hugo_post(self, writer: IoWriter, post_header: dict, post_content: str): """ - Write a Hugo post to the specified path + Write a Hugo post to the specified writer. Parameters ---------- - output_path : Path - The path to the Hugo post + writer : IoWriter + The IoWriter instance for writing. post_header : dict The post header post_content : str The post content """ - # ensure that output path exists - output_path.parent.mkdir(parents=True, exist_ok=True) + data = ["---\n", yaml.dump(post_header), "---\n", post_content] + writer.write("".join(data)) - with open(output_path, "w") as fo: - header = ["---\n", yaml.dump(post_header), "---\n"] - fo.writelines(header) - fo.write(post_content) - - def convert_jekyll_to_hugo(self, jekyll_post_path: Path, hugo_post_output: Path): + def convert_jekyll_to_hugo(self, reader: IoReader, writer: IoWriter): """ Convert a Jekyll post to a Hugo post Parameters ---------- - jekyll_post_path : Path - The path to the Jekyll post - hugo_post_output : Path - The path to the Hugo post + reader : IoReader + The IoReader instance for reading. + writer : IoWriter + The IoWriter instance for writing. """ - contents = self.read_jekyll_post(jekyll_post_path) + contents = self.read_jekyll_post(reader) # fix header header = yaml.safe_load(contents.split("---")[1]) @@ -162,7 +155,7 @@ class WordpressMarkdownConverter: fixed_post_content = self.convert_post_content(post_content) self.write_hugo_post( - hugo_post_output.joinpath(jekyll_post_path.name), + writer, fixed_header, fixed_post_content, ) diff --git a/app/io/__init__.py b/app/io/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/io/reader.py b/app/io/reader.py new file mode 100644 index 0000000..52166e1 --- /dev/null +++ b/app/io/reader.py @@ -0,0 +1,39 @@ +from abc import ABCMeta, abstractmethod + + +class IoReader(metaclass=ABCMeta): + """ + Abstract class for reading posts. + """ + + @abstractmethod + def read(self) -> str: + """ + Reads a post. + """ + raise NotImplementedError + + +class StringReader(IoReader): + """ + Reads a post from a string. + """ + + def __init__(self, content: str): + self._content = content + + def read(self) -> str: + return self._content + + +class FileReader(IoReader): + """ + Reads a post from a local file. + """ + + def __init__(self, file_path: str): + self._file_path = file_path + + def read(self) -> str: + with open(self._file_path, "r") as file: + return file.read() diff --git a/app/io/writer.py b/app/io/writer.py new file mode 100644 index 0000000..eb7a9e8 --- /dev/null +++ b/app/io/writer.py @@ -0,0 +1,52 @@ +from abc import abstractmethod, ABCMeta +from pathlib import Path +from typing import Callable +from app import utils + + +class IoWriter(metaclass=ABCMeta): + """ + Abstract class for writing posts. + """ + + @abstractmethod + def write(self, data: str): + """ + Write a post + + Parameters + ---------- + data: str + The post data to write + """ + raise NotImplementedError + + +class FileWriter(IoWriter): + """ + Writes a post to a file. + """ + + def __init__(self, output_path: Path): + utils.guard_against_none(output_path, "output_path") + + self.output_path = output_path + output_path.parent.mkdir(parents=True, exist_ok=True) + + def write(self, data: str): + with open(self.output_path, "w") as fo: + fo.write(data) + + +class CallbackWriter(IoWriter): + """ + Writes a post to a string. + """ + + def __init__(self, callback: Callable[[str], None]): + utils.guard_against_none(callback, "callback") + + self.callback = callback + + def write(self, data: str): + self.callback(data)