Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for footnotes #48

Merged
merged 22 commits into from
Aug 11, 2022
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
c7f9a9c
Add plugin for supporting footnotes
bimbashrestha Aug 10, 2022
3e38994
Use n2y.plugins.footnotes as key instead of footntoes
bimbashrestha Aug 10, 2022
c32fe32
Raise UseNextClass when not footnote in paragraph
bimbashrestha Aug 10, 2022
6ecf742
Raise UseNextClass when not footnote in richtext
bimbashrestha Aug 10, 2022
dd14a26
Pre-fetch page blocks to ensure constructor called before to_pandoc
bimbashrestha Aug 10, 2022
b24863b
Add warning when footnote id is overloaded
bimbashrestha Aug 10, 2022
55b9eca
Add warning when footnote is missing
bimbashrestha Aug 10, 2022
4887705
Merge branch 'main' into footnotes-no-second-pass
bimbashrestha Aug 11, 2022
49c5482
Add block reference to plugin
bimbashrestha Aug 11, 2022
920a55d
Add plugin_data dict to Page class
bimbashrestha Aug 11, 2022
67d9bdf
Use page plugin_data instead of client plugin_data
bimbashrestha Aug 11, 2022
5ddecab
Revert pre-fetching of blocks inside pages
bimbashrestha Aug 11, 2022
55fac4c
Always return None because UseNextClass handles non-footnotes
bimbashrestha Aug 11, 2022
009b8b5
Clarify footnote stripping logic
bimbashrestha Aug 11, 2022
dc74d6b
Add warning for empty footnote
bimbashrestha Aug 11, 2022
1b4cf99
Prserve prefix and suffix of footnote containing token
bimbashrestha Aug 11, 2022
1b63360
Add footnotes test to end-to-end tests
bimbashrestha Aug 11, 2022
502574e
Add notion url to warning for missing footnote
bimbashrestha Aug 11, 2022
38b7832
Add notion url to warning for empty footnote
bimbashrestha Aug 11, 2022
c67e610
Warning re-word
bimbashrestha Aug 11, 2022
77430ee
Add documentation about n2y.plugins.footnotes
bimbashrestha Aug 11, 2022
5a804ec
Fix _footnote_empty() and rename
bimbashrestha Aug 11, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion n2y/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,11 +157,23 @@ def main(raw_args, access_token):
return 0


def get_database_pages_and_prefetch_blocks(database):
bimbashrestha marked this conversation as resolved.
Show resolved Hide resolved
pages = []
for page in database.children:
# We're doing this because plugins may need to do some preprocessing
# on blocks before they're rendred by to_pandoc(). Because page.block
# is a lazy-loaded property, we need to pre-fetch it to make sure all
# block contructors are called before all block to_pandoc() calls.
_ = page.block
pages.append(page)
return pages


def export_database_as_markdown_files(database, options):
os.makedirs(options.output, exist_ok=True)
seen_file_names = set()
counts = {'unnamed': 0, 'duplicate': 0}
for page in database.children:
for page in get_database_pages_and_prefetch_blocks(database):
if page.filename:
if page.filename not in seen_file_names:
seen_file_names.add(page.filename)
Expand Down
88 changes: 88 additions & 0 deletions n2y/plugins/footnotes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import re
import logging

from pandoc.types import Note, Str, Para

from n2y.rich_text import TextRichText
from n2y.blocks import ParagraphBlock
from n2y.errors import UseNextClass


plugin_data_key = "n2y.plugins.footnotes"

logger = logging.getLogger(__name__)


class ParagraphWithFootnoteBlock(ParagraphBlock):
def __init__(self, client, notion_data, page, get_children=True):
super().__init__(client, notion_data, page, get_children)
if self._is_footnote():
self._attach_footnote_data()
else:
raise UseNextClass()

def to_pandoc(self):
bimbashrestha marked this conversation as resolved.
Show resolved Hide resolved
return None if self._is_footnote() else super().to_pandoc()

def _attach_footnote_data(self):
if plugin_data_key not in self.client.plugin_data:
self.client.plugin_data[plugin_data_key] = {}
if self._footnote() not in self.client.plugin_data[plugin_data_key]:
self.client.plugin_data[plugin_data_key][self._footnote()] = self._footnote_ast()
bimbashrestha marked this conversation as resolved.
Show resolved Hide resolved
else:
msg = 'Multiple footnotes for "[%s]". Skipping latest (%s)'
bimbashrestha marked this conversation as resolved.
Show resolved Hide resolved
logger.warning(msg, self._footnote(), self.notion_url)

def _is_footnote(self):
return self._footnote() is not None

def _footnote(self):
first_str = self.rich_text.to_plain_text().split(" ")[0]
footnotes = re.findall(r"\[(\d+)\]:", first_str)
if len(footnotes) != 1:
return None
return footnotes[0]

def _footnote_ast(self):
ast = super().to_pandoc()
return Para(ast[0][2:]) if not isinstance(ast, list) else [Para(ast[0][0][2:])] + ast[1:]
bimbashrestha marked this conversation as resolved.
Show resolved Hide resolved


class TextRichTextWithFootnoteRef(TextRichText):
def __init__(self, client, notion_data):
super().__init__(client, notion_data)
if not self._is_footnote():
raise UseNextClass()

def to_pandoc(self):
bimbashrestha marked this conversation as resolved.
Show resolved Hide resolved
pandoc_ast = []
for token in super().to_pandoc():
ref = self._footnote_from_token(token)
if ref is None:
pandoc_ast.append(token)
continue
if ref not in self.client.plugin_data[plugin_data_key]:
pandoc_ast.append(token)
logger.warning('Missing footnote "[%s]". Rendering as plain text', ref)
bimbashrestha marked this conversation as resolved.
Show resolved Hide resolved
continue
block = self.client.plugin_data[plugin_data_key][ref]
footnote = Note(block) if isinstance(block, list) else Note([block])
pandoc_ast.append(footnote)
return pandoc_ast

bimbashrestha marked this conversation as resolved.
Show resolved Hide resolved
def _is_footnote(self):
return any(self._footnote_from_token(t) is not None for t in super().to_pandoc())

def _footnote_from_token(self, token):
if not isinstance(token, Str):
return None
refs = re.findall(r"\[\^(\d+)\]", token[0])
if len(refs) != 1:
return None
return refs[0]


notion_classes = {
"blocks": {"paragraph": ParagraphWithFootnoteBlock},
"rich_texts": {"text": TextRichTextWithFootnoteRef},
}