refactor: Make formatters functions
Formatters have been classes so far which contained some data (the tamplate to use for formatting and the annotations and documents to format) and the actual formatting logic (an execute function). However, we can inject the annotations to be formatted and the templates so far are static only, so they can be simple variables (we can think about how to inject them at another point should it come up, no bikeshedding now). This way, we can simply pass around one function per formatter, which should make the code much lighter, easier to add to and especially less stateful which means less areas of broken interactions to worry about.
This commit is contained in:
parent
929e70d7ac
commit
7ee8d4911e
3 changed files with 70 additions and 110 deletions
|
@ -8,12 +8,7 @@ import papis.strings
|
||||||
from papis.document import Document
|
from papis.document import Document
|
||||||
|
|
||||||
from papis_extract import extractor, exporter
|
from papis_extract import extractor, exporter
|
||||||
from papis_extract.formatter import (
|
from papis_extract.formatter import Formatter, format_count, format_csv, format_markdown
|
||||||
CountFormatter,
|
|
||||||
CsvFormatter,
|
|
||||||
MarkdownFormatter,
|
|
||||||
Formatter,
|
|
||||||
)
|
|
||||||
|
|
||||||
logger = papis.logging.get_logger(__name__)
|
logger = papis.logging.get_logger(__name__)
|
||||||
|
|
||||||
|
@ -82,11 +77,11 @@ def main(
|
||||||
return
|
return
|
||||||
|
|
||||||
if template == "csv":
|
if template == "csv":
|
||||||
formatter = CsvFormatter()
|
formatter = format_csv
|
||||||
elif template == "count":
|
elif template == "count":
|
||||||
formatter = CountFormatter()
|
formatter = format_count
|
||||||
else:
|
else:
|
||||||
formatter = MarkdownFormatter()
|
formatter = format_markdown
|
||||||
|
|
||||||
run(documents, edit=manual, write=write, git=git, formatter=formatter)
|
run(documents, edit=manual, write=write, git=git, formatter=formatter)
|
||||||
|
|
||||||
|
@ -98,8 +93,10 @@ def run(
|
||||||
write: bool = False,
|
write: bool = False,
|
||||||
git: bool = False,
|
git: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
formatter.annotated_docs = extractor.start(documents)
|
annotated_docs = extractor.start(documents)
|
||||||
if write:
|
if write:
|
||||||
exporter.to_notes(formatter, edit=edit, git=git)
|
exporter.to_notes(
|
||||||
|
formatter=formatter, annotated_docs=annotated_docs, edit=edit, git=git
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
exporter.to_stdout(formatter)
|
exporter.to_stdout(formatter=formatter, annotated_docs=annotated_docs)
|
||||||
|
|
|
@ -6,33 +6,35 @@ import papis.api
|
||||||
import papis.git
|
import papis.git
|
||||||
import papis.config
|
import papis.config
|
||||||
import Levenshtein
|
import Levenshtein
|
||||||
|
from papis_extract.annotation import AnnotatedDocument
|
||||||
|
|
||||||
from papis_extract.formatter import Formatter
|
from papis_extract.formatter import Formatter
|
||||||
|
|
||||||
logger = papis.logging.get_logger(__name__)
|
logger = papis.logging.get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def to_stdout(formatter: Formatter) -> None:
|
def to_stdout(formatter: Formatter, annotated_docs: list[AnnotatedDocument]) -> None:
|
||||||
"""Pretty print annotations to stdout.
|
"""Pretty print annotations to stdout.
|
||||||
|
|
||||||
Gives a nice human-readable representations of
|
Gives a nice human-readable representations of
|
||||||
the annotations in somewhat of a list form.
|
the annotations in somewhat of a list form.
|
||||||
Not intended for machine-readability.
|
Not intended for machine-readability.
|
||||||
"""
|
"""
|
||||||
output:str = formatter.execute()
|
output: str = formatter(annotated_docs)
|
||||||
print(output.rstrip('\n'))
|
print(output.rstrip("\n"))
|
||||||
|
|
||||||
|
|
||||||
def to_notes(formatter: Formatter, edit: bool, git: bool) -> None:
|
def to_notes(
|
||||||
|
formatter: Formatter, annotated_docs: list[AnnotatedDocument], edit: bool, git: bool
|
||||||
|
) -> None:
|
||||||
"""Write annotations into document notes.
|
"""Write annotations into document notes.
|
||||||
|
|
||||||
Permanently writes the given annotations into notes
|
Permanently writes the given annotations into notes
|
||||||
belonging to papis documents. Creates new notes for
|
belonging to papis documents. Creates new notes for
|
||||||
documents missing a note field or appends to existing.
|
documents missing a note field or appends to existing.
|
||||||
"""
|
"""
|
||||||
annotated_docs = formatter.annotated_docs
|
|
||||||
for entry in annotated_docs:
|
for entry in annotated_docs:
|
||||||
formatted_annotations = formatter.execute(entry).split("\n")
|
formatted_annotations = formatter([entry]).split("\n")
|
||||||
if formatted_annotations:
|
if formatted_annotations:
|
||||||
_add_annots_to_note(entry.document, formatted_annotations)
|
_add_annots_to_note(entry.document, formatted_annotations)
|
||||||
|
|
||||||
|
@ -67,7 +69,8 @@ def _add_annots_to_note(
|
||||||
# add newline if theres no empty space at file end
|
# add newline if theres no empty space at file end
|
||||||
if len(existing) > 0 and existing[-1].strip() != "":
|
if len(existing) > 0 and existing[-1].strip() != "":
|
||||||
f.write("\n")
|
f.write("\n")
|
||||||
f.write("\n".join(new_annotations))
|
print(new_annotations)
|
||||||
|
f.write("\n\n".join(new_annotations))
|
||||||
f.write("\n")
|
f.write("\n")
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Wrote {len(new_annotations)} "
|
f"Wrote {len(new_annotations)} "
|
||||||
|
|
|
@ -1,114 +1,74 @@
|
||||||
from dataclasses import dataclass, field
|
from collections.abc import Callable
|
||||||
from typing import Protocol
|
|
||||||
|
|
||||||
from papis_extract.annotation import AnnotatedDocument
|
from papis_extract.annotation import AnnotatedDocument
|
||||||
|
|
||||||
|
Formatter = Callable[[list[AnnotatedDocument]], str]
|
||||||
@dataclass
|
|
||||||
class Formatter(Protocol):
|
|
||||||
annotated_docs: list[AnnotatedDocument]
|
|
||||||
header: str
|
|
||||||
string: str
|
|
||||||
footer: str
|
|
||||||
|
|
||||||
def execute(self, doc: AnnotatedDocument | None = None) -> str:
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
def format_markdown(docs: list[AnnotatedDocument] = []) -> str:
|
||||||
class MarkdownFormatter:
|
template = (
|
||||||
annotated_docs: list[AnnotatedDocument] = field(default_factory=lambda: list())
|
|
||||||
header: str = ""
|
|
||||||
string: str = (
|
|
||||||
"{{#tag}}#{{tag}}\n{{/tag}}"
|
"{{#tag}}#{{tag}}\n{{/tag}}"
|
||||||
"{{#quote}}> {{quote}}{{/quote}} {{#page}}[p. {{page}}]{{/page}}\n"
|
"{{#quote}}> {{quote}}{{/quote}} {{#page}}[p. {{page}}]{{/page}}"
|
||||||
"{{#note}} NOTE: {{note}}{{/note}}"
|
"\n{{#note}} NOTE: {{note}}{{/note}}"
|
||||||
)
|
)
|
||||||
footer: str = ""
|
output = ""
|
||||||
|
for entry in docs:
|
||||||
|
if not entry.annotations:
|
||||||
|
continue
|
||||||
|
|
||||||
def execute(self, doc: AnnotatedDocument | None = None) -> str:
|
title_decoration = (
|
||||||
output = ""
|
f"{'=' * len(entry.document.get('title', ''))} "
|
||||||
documents = self.annotated_docs if doc is None else [doc]
|
f"{'-' * len(entry.document.get('author', ''))}"
|
||||||
last = documents[-1]
|
)
|
||||||
for entry in documents:
|
output += (
|
||||||
if not entry.annotations:
|
f"{title_decoration}\n"
|
||||||
continue
|
f"{entry.document['title']} - {entry.document['author']}\n"
|
||||||
|
f"{title_decoration}\n\n"
|
||||||
|
)
|
||||||
|
for a in entry.annotations:
|
||||||
|
output += a.format(template)
|
||||||
|
output += "\n"
|
||||||
|
|
||||||
title_decoration = (
|
output += "\n\n\n"
|
||||||
f"{'=' * len(entry.document.get('title', ''))} "
|
|
||||||
f"{'-' * len(entry.document.get('author', ''))}"
|
|
||||||
)
|
|
||||||
output += (
|
|
||||||
f"{title_decoration}\n"
|
|
||||||
f"{entry.document['title']} - {entry.document['author']}\n"
|
|
||||||
f"{title_decoration}\n\n"
|
|
||||||
)
|
|
||||||
for a in entry.annotations:
|
|
||||||
output += a.format(self.string)
|
|
||||||
|
|
||||||
if entry != last:
|
return output
|
||||||
output += "\n\n\n"
|
|
||||||
|
|
||||||
return output
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
def format_count(docs: list[AnnotatedDocument] = []) -> str:
|
||||||
class CountFormatter:
|
output = ""
|
||||||
annotated_docs: list[AnnotatedDocument] = field(default_factory=lambda: list())
|
for entry in docs:
|
||||||
header: str = ""
|
if not entry.annotations:
|
||||||
string: str = ""
|
continue
|
||||||
footer: str = ""
|
|
||||||
|
|
||||||
def execute(self, doc: AnnotatedDocument | None = None) -> str:
|
count = 0
|
||||||
documents = self.annotated_docs if doc is None else [doc]
|
for _ in entry.annotations:
|
||||||
output = ""
|
count += 1
|
||||||
for entry in documents:
|
|
||||||
if not entry.annotations:
|
|
||||||
continue
|
|
||||||
|
|
||||||
count = 0
|
d = entry.document
|
||||||
for _ in entry.annotations:
|
output += (
|
||||||
count += 1
|
f"{d['author'] if 'author' in d else ''}"
|
||||||
|
f"{' - ' if 'author' in d else ''}" # only put separator if author
|
||||||
|
f"{entry.document['title'] if 'title' in d else ''}: "
|
||||||
|
f"{count}\n"
|
||||||
|
)
|
||||||
|
|
||||||
d = entry.document
|
return output
|
||||||
output += (
|
|
||||||
f"{d['author'] if 'author' in d else ''}"
|
|
||||||
f"{' - ' if 'author' in d else ''}" # only put separator if author
|
|
||||||
f"{entry.document['title'] if 'title' in d else ''}: "
|
|
||||||
f"{count}\n"
|
|
||||||
)
|
|
||||||
|
|
||||||
return output
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
def format_csv(docs: list[AnnotatedDocument] = []) -> str:
|
||||||
class CsvFormatter:
|
|
||||||
annotated_docs: list[AnnotatedDocument] = field(default_factory=lambda: list())
|
|
||||||
header: str = "type,tag,page,quote,note,author,title,ref,file"
|
header: str = "type,tag,page,quote,note,author,title,ref,file"
|
||||||
string: str = (
|
template: str = (
|
||||||
'{{type}},{{tag}},{{page}},"{{quote}}","{{note}}",'
|
'{{type}},{{tag}},{{page}},"{{quote}}","{{note}}",'
|
||||||
'"{{doc.author}}","{{doc.title}}","{{doc.ref}}","{{file}}"'
|
'"{{doc.author}}","{{doc.title}}","{{doc.ref}}","{{file}}"'
|
||||||
)
|
)
|
||||||
footer: str = ""
|
output = f"{header}\n"
|
||||||
|
for entry in docs:
|
||||||
|
if not entry.annotations:
|
||||||
|
continue
|
||||||
|
|
||||||
def execute(self, doc: AnnotatedDocument | None = None) -> str:
|
d = entry.document
|
||||||
documents = self.annotated_docs if doc is None else [doc]
|
for a in entry.annotations:
|
||||||
output = f"{self.header}\n"
|
output += a.format(template, doc=d)
|
||||||
for entry in documents:
|
output += "\n"
|
||||||
if not entry.annotations:
|
|
||||||
continue
|
|
||||||
|
|
||||||
d = entry.document
|
return output
|
||||||
for a in entry.annotations:
|
|
||||||
output += a.format(self.string, doc=d)
|
|
||||||
output += "\n"
|
|
||||||
|
|
||||||
return output
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class CustomFormatter:
|
|
||||||
def __init__(self, header: str = "", string: str = "", footer: str = "") -> None:
|
|
||||||
self.header = header
|
|
||||||
self.string = string
|
|
||||||
self.footer = footer
|
|
||||||
|
|
Loading…
Reference in a new issue