diff --git a/src/formatters/base.py b/src/formatters/base.py index 2dc3ca3..bc84f1b 100644 --- a/src/formatters/base.py +++ b/src/formatters/base.py @@ -2,8 +2,8 @@ Базовые функции форматирования списка источников """ -from formatters.styles.base import BaseCitationStyle -from logger import get_logger +from src.formatters.styles.base import BaseCitationStyle +from src.logger import get_logger logger = get_logger(__name__) @@ -17,7 +17,6 @@ class BaseCitationFormatter: def __init__(self, formatted_items: list[BaseCitationStyle]) -> None: """ Конструктор. - :param formatted_items: Список объектов для итогового форматирования """ diff --git a/src/formatters/models.py b/src/formatters/models.py index c9236ca..aff6ed2 100644 --- a/src/formatters/models.py +++ b/src/formatters/models.py @@ -4,6 +4,7 @@ from typing import Optional + from pydantic import BaseModel, Field @@ -78,3 +79,57 @@ class ArticlesCollectionModel(BaseModel): publishing_house: str year: int = Field(..., gt=0) pages: str + + +class MagazineReaderModel(BaseModel): + + """ + Модель сборника статей из журнала: + + .. code-block:: + + MagazineReaderModel( + authors="Иванов И.М., Петров С.Н.", + article_title="Наука как искусство", + magazine_title="Образование и наука", + year=2020, + number=10, + pages="25-30", + ) + """ + + authors: str + article_title: str + magazine_title: str + year: int = Field(..., gt=0) + number: int = Field(..., gt=0) + pages: str + + +class DissertationReaderModel(BaseModel): + + """ + Модель диссертаций: + + .. code-block:: + + MagazineReaderModel( + authors="Иванов И.М., Петров С.Н.", + article_title="Наука как искусство", + academic_degree="д-р", + branch="экон.", + code="01.01.01", + city="СПб.", + year=2020, + pages="199", + ) + """ + + authors: str + article_title: str + academic_degree: str + branch: str + code: str + city: str + year: int = Field(..., gt=0) + pages: str diff --git a/src/formatters/styles/apa.py b/src/formatters/styles/apa.py new file mode 100644 index 0000000..88d8c6e --- /dev/null +++ b/src/formatters/styles/apa.py @@ -0,0 +1,189 @@ +""" +Стиль цитирования по ГОСТ Р 7.0.5-2008. +""" +from string import Template + +from pydantic import BaseModel + +from src.formatters.models import BookModel, InternetResourceModel, ArticlesCollectionModel, MagazineReaderModel, \ + DissertationReaderModel +from src.formatters.styles.base import BaseCitationStyle +from src.logger import get_logger + + +logger = get_logger(__name__) + + +class APABook(BaseCitationStyle): + """ + Форматирование для книг. + """ + + data: BookModel + + @property + def template(self) -> Template: + return Template( + "$authors ($year). $title. $publishing_house." + ) + + def substitute(self) -> str: + logger.info('Форматирование книги "%s" ...', self.data.title) + + return self.template.substitute( + authors=self.data.authors, + title=self.data.title, + publishing_house=self.data.publishing_house, + year=self.data.year, + ) + + def get_edition(self) -> str: + """ + Получение отформатированной информации об издательстве. + :return: Информация об издательстве. + """ + + return f"{self.data.edition} изд. – " if self.data.edition else "" + + +class APAInternetResource(BaseCitationStyle): + """ + Форматирование для интернет-ресурсов. + """ + + data: InternetResourceModel + + @property + def template(self) -> Template: + return Template( + "$article ($access_date) $website $link" + ) + + def substitute(self) -> str: + + logger.info('Форматирование интернет-ресурса "%s" ...', self.data.article) + + return self.template.substitute( + article=self.data.article, + website=self.data.website, + link=self.data.link, + access_date=self.data.access_date, + ) + + +class APACollectionArticle(BaseCitationStyle): + """ + Форматирование для статьи из сборника. + """ + + data: ArticlesCollectionModel + + @property + def template(self) -> Template: + return Template( + "$authors ($year) $article_title, $collection_title $city: $publishing_house, $pages p." + ) + + def substitute(self) -> str: + + logger.info('Форматирование сборника статей "%s" ...', self.data.article_title) + + return self.template.substitute( + authors=self.data.authors, + article_title=self.data.article_title, + collection_title=self.data.collection_title, + city=self.data.city, + publishing_house=self.data.publishing_house, + year=self.data.year, + pages=self.data.pages, + ) + + +class APAMagazineReader(BaseCitationStyle): + """ + Форматирование для статьи из сборника. + """ + + data: MagazineReaderModel + + @property + def template(self) -> Template: + return Template( + "$authors ($year) $article_title. $magazine_title, $number $pages p." + ) + + def substitute(self) -> str: + + logger.info('Форматирование сборника статей "%s" ...', self.data.article_title) + + return self.template.substitute( + authors=self.data.authors, + article_title=self.data.article_title, + magazine_title=self.data.magazine_title, + year=self.data.year, + number=self.data.number, + pages=self.data.pages, + ) + + +class APADissertationReader(BaseCitationStyle): + """ + Форматирование для статьи из сборника. + """ + + data: DissertationReaderModel + + @property + def template(self) -> Template: + return Template( + "$authors ($year) $article_title, дис. [$branch: $code] $city, $pages p." + ) + + def substitute(self) -> str: + + logger.info('Форматирование сборника статей "%s" ...', self.data.article_title) + + return self.template.substitute( + authors=self.data.authors, + article_title=self.data.article_title, + academic_degree=self.data.academic_degree, + branch=self.data.branch, + code=self.data.code, + city=self.data.city, + year=self.data.year, + pages=self.data.pages, + ) + + +class APACitationFormatter: + """ + Базовый класс для итогового форматирования списка источников. + """ + + formatters_map = { + BookModel.__name__: APABook, + InternetResourceModel.__name__: APAInternetResource, + ArticlesCollectionModel.__name__: APACollectionArticle, + DissertationReaderModel.__name__: APADissertationReader, + MagazineReaderModel.__name__: APAMagazineReader, + } + + def __init__(self, models: list[BaseModel]) -> None: + """ + Конструктор. + :param models: Список объектов для форматирования + """ + + formatted_items = [] + for model in models: + formatted_items.append(self.formatters_map.get(type(model).__name__)(model)) # type: ignore + + self.formatted_items = formatted_items + + def format(self) -> list[BaseCitationStyle]: + """ + Форматирование списка источников. + :return: + """ + + return sorted(self.formatted_items, key=lambda item: item.formatted) diff --git a/src/formatters/styles/gost.py b/src/formatters/styles/gost.py index b237f8a..7150cf3 100644 --- a/src/formatters/styles/gost.py +++ b/src/formatters/styles/gost.py @@ -5,9 +5,10 @@ from pydantic import BaseModel -from formatters.models import BookModel, InternetResourceModel, ArticlesCollectionModel -from formatters.styles.base import BaseCitationStyle -from logger import get_logger +from src.formatters.models import BookModel, InternetResourceModel, ArticlesCollectionModel, MagazineReaderModel, \ + DissertationReaderModel +from src.formatters.styles.base import BaseCitationStyle +from src.logger import get_logger logger = get_logger(__name__) @@ -103,6 +104,62 @@ def substitute(self) -> str: ) +class GOSTMagazineReader(BaseCitationStyle): + """ + Форматирование для статьи из сборника. + """ + + data: MagazineReaderModel + + @property + def template(self) -> Template: + return Template( + "$authors $article_title // $magazine_title. – $year. – №$number. – С. $pages." + ) + + def substitute(self) -> str: + + logger.info('Форматирование сборника статей "%s" ...', self.data.article_title) + + return self.template.substitute( + authors=self.data.authors, + article_title=self.data.article_title, + magazine_title=self.data.magazine_title, + year=self.data.year, + number=self.data.number, + pages=self.data.pages, + ) + + +class GOSTDissertationReader(BaseCitationStyle): + """ + Форматирование для статьи из сборника. + """ + + data: DissertationReaderModel + + @property + def template(self) -> Template: + return Template( + "$authors $article_title: $academic_degree $branch наук: $code. – $city, $year. – $pages с." + ) + + def substitute(self) -> str: + + logger.info('Форматирование сборника статей "%s" ...', self.data.article_title) + + return self.template.substitute( + authors=self.data.authors, + article_title=self.data.article_title, + academic_degree=self.data.academic_degree, + branch=self.data.branch, + code=self.data.code, + city=self.data.city, + year=self.data.year, + pages=self.data.pages, + ) + + class GOSTCitationFormatter: """ Базовый класс для итогового форматирования списка источников. @@ -112,12 +169,13 @@ class GOSTCitationFormatter: BookModel.__name__: GOSTBook, InternetResourceModel.__name__: GOSTInternetResource, ArticlesCollectionModel.__name__: GOSTCollectionArticle, + DissertationReaderModel.__name__: GOSTDissertationReader, + MagazineReaderModel.__name__: GOSTMagazineReader, } def __init__(self, models: list[BaseModel]) -> None: """ Конструктор. - :param models: Список объектов для форматирования """ @@ -130,7 +188,6 @@ def __init__(self, models: list[BaseModel]) -> None: def format(self) -> list[BaseCitationStyle]: """ Форматирование списка источников. - :return: """ diff --git a/src/main.py b/src/main.py index 7a9fa8e..c3614ce 100644 --- a/src/main.py +++ b/src/main.py @@ -6,6 +6,7 @@ import click from formatters.styles.gost import GOSTCitationFormatter +from formatters.styles.apa import APACitationFormatter from logger import get_logger from readers.reader import SourcesReader from renderer import Renderer @@ -31,7 +32,7 @@ class CitationEnum(Enum): "-c", "citation", type=click.Choice([item.name for item in CitationEnum], case_sensitive=False), - default=CitationEnum.GOST.name, + default=CitationEnum.APA.name, show_default=True, help="Стиль цитирования", ) @@ -54,7 +55,7 @@ class CitationEnum(Enum): help="Путь к выходному файлу", ) def process_input( - citation: str = CitationEnum.GOST.name, + citation: str = CitationEnum.APA.name, path_input: str = INPUT_FILE_PATH, path_output: str = OUTPUT_FILE_PATH, ) -> None: @@ -75,16 +76,21 @@ def process_input( path_input, path_output, ) - models = SourcesReader(path_input).read() - formatted_models = tuple( - str(item) for item in GOSTCitationFormatter(models).format() - ) - - logger.info("Генерация выходного файла ...") - Renderer(formatted_models).render(path_output) - - logger.info("Команда успешно завершена.") + if citation == CitationEnum.GOST.name: + formatted_models = tuple( + str(item) for item in GOSTCitationFormatter(models).format() + ) + logger.info("Генерация выходного файла GOST ...") + Renderer(formatted_models).render(path_output) + logger.info("Команда успешно завершена.") + elif citation == CitationEnum.APA.name: + formatted_models = tuple( + str(item) for item in APACitationFormatter(models).format() + ) + logger.info("Генерация выходного файла APA ...") + Renderer(formatted_models).render(path_output) + logger.info("Команда успешно завершена.") if __name__ == "__main__": diff --git a/src/readers/reader.py b/src/readers/reader.py index 9007a80..529bf5d 100644 --- a/src/readers/reader.py +++ b/src/readers/reader.py @@ -7,10 +7,11 @@ import openpyxl from openpyxl.workbook import Workbook -from formatters.models import BookModel, InternetResourceModel, ArticlesCollectionModel -from logger import get_logger -from readers.base import BaseReader +from src.logger import get_logger +from src.readers.base import BaseReader +from src.formatters.models import MagazineReaderModel, BookModel, InternetResourceModel, ArticlesCollectionModel, \ + DissertationReaderModel logger = get_logger(__name__) @@ -90,6 +91,58 @@ def attributes(self) -> dict: } +class MagazineReader(BaseReader): + """ + Чтение модели статья из журнала. + """ + + @property + def model(self) -> Type[MagazineReaderModel]: + return MagazineReaderModel + + @property + def sheet(self) -> str: + return "Статья из журнала" + + @property + def attributes(self) -> dict: + return { + "authors": {0: str}, + "article_title": {1: str}, + "magazine_title": {2: str}, + "year": {3: int}, + "number": {4: int}, + "pages": {5: str}, + } + + +class DissertationReader(BaseReader): + """ + Чтение модели статья из журнала. + """ + + @property + def model(self) -> Type[DissertationReaderModel]: + return DissertationReaderModel + + @property + def sheet(self) -> str: + return "Диссертация" + + @property + def attributes(self) -> dict: + return { + "authors": {0: str}, + "article_title": {1: str}, + "academic_degree": {2: str}, + "branch": {3: str}, + "code": {4: str}, + "city": {5: str}, + "year": {6: int}, + "pages": {7: str}, + } + + class SourcesReader: """ Чтение из источника данных. @@ -100,6 +153,8 @@ class SourcesReader: BookReader, InternetResourceReader, ArticlesCollectionReader, + MagazineReader, + DissertationReader, ] def __init__(self, path: str) -> None: diff --git a/src/renderer.py b/src/renderer.py index 0c3c3a4..19f8df0 100644 --- a/src/renderer.py +++ b/src/renderer.py @@ -4,10 +4,11 @@ from __future__ import annotations from pathlib import Path +from typing import Any from docx import Document from docx.enum.text import WD_ALIGN_PARAGRAPH # pylint: disable=E0611 -from docx.shared import Pt +from docx.shared import Pt, Cm class Renderer: