1
0

CardFileFormatter: Tags and assignments WIP
Some checks failed
Test Python / Test (push) Failing after 26s

This commit is contained in:
Jon Michael Aanes 2024-09-28 12:37:19 +02:00
parent 9d2cd93ed3
commit d4cfe4da22
Signed by: Jmaa
SSH Key Fingerprint: SHA256:Ab0GfHGCblESJx7JRE4fj4bFy/KRpeLhi41y4pF3sNA
10 changed files with 164 additions and 89 deletions

View File

@ -3,18 +3,14 @@ import tempfile
import logging
import requests_cache
import secret_loader
from .favro_client import FavroClient, OrganizationId
from .favro_fuse import start_favro_fuse
import secrets
def main():
logging.basicConfig()
secrets = secret_loader.SecretLoader()
favro_org_id = secrets.load_or_fail('FAVRO_ORGANIZATION_ID')
favro_username = secrets.load_or_fail('FAVRO_USERNAME')
favro_password = secrets.load_or_fail('FAVRO_PASSWORD')
read_only = False
@ -22,9 +18,9 @@ def main():
session = requests_cache.CachedSession( tmpdirname + '/http-cache.sqlite', expire_after=10,)
client = FavroClient(
favro_org_id=OrganizationId(favro_org_id),
favro_username=favro_username,
favro_password=favro_password,
favro_org_id=OrganizationId(secrets.favro_org_id()),
favro_username=secrets.favro_username(),
favro_password=secrets.favro_password(),
session=session,
read_only=read_only,
)

View File

@ -83,8 +83,9 @@ class FavroClient:
response.raise_for_status()
json = response.json()
# TODO: Pageination
# TODO: Add support for pageination
for entity_json in json['entities']:
print(entity_json)
card = Card.from_json(entity_json)
self.cache.add_card(card)
yield card

View File

@ -28,6 +28,38 @@ class OrganizationId:
raw_id: str
@dataclasses.dataclass(frozen=True)
class CardAssignment:
user: UserId
completed: bool
@staticmethod
def from_json(json: dict[str, Any]) -> 'CardAssignment':
return CardAssignment(
UserId(json['userId']),
json['completed'],
)
@dataclasses.dataclass(frozen=True)
class TagId:
raw_id: str
@dataclasses.dataclass(frozen=True)
class CardDependency:
card_id: CardId
card_common_id: CommonId
is_before: bool
reverse_card_id: CardId
@staticmethod
def from_json(json: dict[str, Any]) -> 'CardDependency':
return CardDependency(
CardId(json['cardId']),
CommonId(json['cardCommonId']),
json['isBefore'],
CardId(json['reverseCardId']),
)
@dataclasses.dataclass(frozen=True)
class Card:
card_id: CardId
@ -36,8 +68,9 @@ class Card:
organization_id: OrganizationId
is_archived: bool
name: str
dependencies: list[None] # TODO
tags: list[None] # TODO
dependencies: list[CardDependency]
assignments: list[CardAssignment]
tags: list[TagId]
todo_list_user_id: UserId | None
todo_list_completed: bool | None
creator_user_id: UserId
@ -45,23 +78,9 @@ class Card:
detailed_description: str | None
""" TODO, fieds:
'position': -399
'listPosition': -399
'isLane': False
'assignments': [{'userId': 'Faieomp8fuS8DrnyP' 'completed': True}]
'tasksTotal': 0
'tasksDone': 0
'attachments': []
'customFields':
'timeOnBoard': None
'timeOnColumns': None
'favroAttachments': []
"""
@staticmethod
def from_json(json: dict[str, Any]) -> 'Card':
todo_list_user_id = UserId(json['todoListUserId']) if 'todoListUserId' in json else None
return Card(
card_id=CardId(json['cardId']),
seq_id=SeqId(json['sequentialId']),
@ -70,12 +89,11 @@ class Card:
is_archived=json['archived'],
organization_id=OrganizationId(json['organizationId']),
name=json['name'],
todo_list_user_id=UserId(json['todoListUserId'])
if 'todoListUserId' in json
else None,
todo_list_user_id=todo_list_user_id,
todo_list_completed=json.get('todoListCompleted'),
dependencies=json['dependencies'],
tags=json['tags'],
tags=[TagId(tag) for tag in json['tags']],
creator_user_id=UserId(json['createdByUserId']),
creation_date=datetime.datetime.fromisoformat(json['createdAt']),
assignments = [CardAssignment.from_json(ass) for ass in json['assignments']],
)

View File

@ -9,7 +9,7 @@ import fuse
from .favro_client import FavroClient
from .favro_data_model import Card, SeqId
from .favro_markdown import format_card, CardContents, parse_card_contents
from .favro_markdown import CardContents, CardFileFormatter
logger = getLogger(__name__)
@ -56,8 +56,9 @@ class CardThing(Thing):
class FavroFuse(fuse.Fuse):
"""Favro Filesystem in Userspace."""
def __init__(self, favro_client: FavroClient, **kwargs):
def __init__(self, favro_client: FavroClient, formatter: CardFileFormatter, **kwargs):
self.favro_client = favro_client
self.formatter = formatter
super().__init__(**kwargs)
def getattr(self, path: str) -> FavroStat | int:
@ -72,7 +73,7 @@ class FavroFuse(fuse.Fuse):
st.st_mode = stat.S_IFREG | 0o666
st.st_nlink = 1
st.st_size = len(format_card(card))
st.st_size = len(self.formatter.format_card(card))
st.st_ctime = int(card.creation_date.timestamp())
st.st_mtime = st.st_ctime # TODO
else:
@ -101,7 +102,7 @@ class FavroFuse(fuse.Fuse):
card = self.favro_client.get_card(thing.seq_id)
contents_str = format_card(card)
contents_str = self.formatter.format_card(card)
contents = bytes(contents_str, 'utf8')
slen = len(contents)
@ -122,13 +123,13 @@ class FavroFuse(fuse.Fuse):
card = self.favro_client.get_card(thing.seq_id)
# Splice contents
contents_str = format_card(card)
contents_str = self.formatter.format_card(card)
contents = bytes(contents_str, 'utf8')
contents = splice(contents, written_buffer, offset)
contents_str = contents.decode('utf8')
# Write to favro
card_updated = parse_card_contents(contents_str)
card_updated = self.formatter.parse_card_contents(contents_str)
self.favro_client.update_card_contents(card.card_id, card_updated)
# Return amount written
@ -143,7 +144,7 @@ class FavroFuse(fuse.Fuse):
card = self.favro_client.get_card(thing.seq_id)
# Splice contents
contents_str = format_card(card)
contents_str = self.formatter.format_card(card)
contents = bytes(contents_str, 'utf8')
old_size = len(contents)
contents = contents[0:new_size] + b' ' * (old_size - new_size)
@ -151,7 +152,7 @@ class FavroFuse(fuse.Fuse):
contents_str = contents.decode('utf8')
# Write to favro
card_updated = parse_card_contents(contents_str)
card_updated = self.formatter.parse_card_contents(contents_str)
self.favro_client.update_card_contents_locally(card.card_id, card_updated)
# Return amount written
@ -179,6 +180,7 @@ def start_favro_fuse(favro_client: FavroClient):
# TODO:
server = FavroFuse(
favro_client=favro_client,
formatter=CardFileFormatter(),
version='%prog ' + fuse.__version__,
usage=HELP,
dash_s_do='setsingle',

View File

@ -13,60 +13,82 @@ from .favro_data_model import Card, SeqId
@dataclasses.dataclass(frozen=True)
class CardContents:
name: str
description: str
name: str | None
description: str | None
tags: list[str]
assignments: list[str]
markdown = marko.Markdown()
renderer = marko.md_renderer.MarkdownRenderer()
class CardFileFormatter:
"""Component for formatting and parsing card files """
OBSIDIAN_MODE = True
def __init__(self, obsidian_mode = True):
self.obsidian_mode = obsidian_mode
self.markdown = marko.Markdown()
self.renderer = marko.md_renderer.MarkdownRenderer()
def format_card_contents(card: CardContents) -> str:
ls = []
def format_card_contents(self, card: CardContents) -> str:
ls = []
# Frontmatter
if OBSIDIAN_MODE:
if card.name:
# Choose frontmatter data
frontmatter_data = {}
if card.name and self.obsidian_mode:
frontmatter_data['aliases'] = [card.name]
if card.tags:
frontmatter_data['tags'] = card.tags
if self.obsidian_mode:
frontmatter_data['tags'] = ['#'+t for t in frontmatter_data['tags']]
if card.assignments:
frontmatter_data['assignments'] = card.assignments
if self.obsidian_mode:
frontmatter_data['assignments'] = [f'[[{name}]]' for name in frontmatter_data['assignments']]
# Frontmatter
if frontmatter_data:
ls.append('---\n')
# TODO: Tags
ls.append('aliases:\n')
ls.append(' - ')
ls.append(card.name)
ls.append('\n')
for key, values in frontmatter_data.items():
ls.append(key)
ls.append(':\n')
for v in values:
ls.append(' - ')
ls.append(v)
ls.append('\n')
ls.append('---\n\n')
# Card name
if card.name:
ls.append('# ')
ls.append(card.name)
ls.append('\n\n')
# Card name
if card.name:
ls.append('# ')
ls.append(card.name)
ls.append('\n\n')
# Card contents
if card.description:
ls.append(card.description)
return ''.join(ls)
# Card contents
if card.description:
ls.append(card.description)
return ''.join(ls)
def parse_card_contents(contents: str) -> CardContents:
"""
1. Strips frontmatter
2. Parses header
3. Finds content.
"""
fm = frontmatter.loads(contents)
del contents
def parse_card_contents(self, contents: str) -> CardContents:
"""
1. Strips frontmatter
2. Parses header
3. Finds content.
"""
fm = frontmatter.loads(contents)
del contents
document = markdown.parse(fm.content.strip())
name = None
for elem in document.children:
if isinstance(elem, marko.block.Heading):
name = renderer.render_children(elem)
document.children.remove(elem)
break
document = self.markdown.parse(fm.content.strip())
name = None
for elem in document.children:
if isinstance(elem, marko.block.Heading):
name = self.renderer.render_children(elem)
document.children.remove(elem)
break
return CardContents(
name,
renderer.render_children(document).strip(),
)
description = self.renderer.render_children(document).strip()
return CardContents(
name,
description,
tags = [],
assignments = [],
)
def format_card(card: Card) -> str:
return format_card_contents(CardContents(card.name, card.detailed_description))
def format_card(self, card: Card) -> str:
return self.format_card_contents(CardContents(card.name, card.detailed_description))

12
favro_sync/secrets.py Normal file
View File

@ -0,0 +1,12 @@
import secret_loader
secrets = secret_loader.SecretLoader()
def favro_org_id():
return secrets.load_or_fail('FAVRO_ORGANIZATION_ID')
def favro_username():
return secrets.load_or_fail('FAVRO_USERNAME')
def favro_password():
return secrets.load_or_fail('FAVRO_PASSWORD')

View File

@ -2,3 +2,5 @@ requests
requests-cache
fuse-python
secret_loader @ git+https://gitfub.space/Jmaa/secret_loader
marko
python-frontmatter

1
test/__init__.py Normal file
View File

@ -0,0 +1 @@
"""Testing package."""

22
test/test_client.py Normal file
View File

@ -0,0 +1,22 @@
from favro_sync import secrets
from favro_sync .favro_client import FavroClient, OrganizationId
from favro_sync .favro_fuse import start_favro_fuse
# TODO: Skip if no secrets
def test_create_client():
client = FavroClient(
favro_org_id=OrganizationId(secrets.favro_org_id()),
favro_username=secrets.favro_username(),
favro_password=secrets.favro_password(),
read_only=True,
)
assert client is not None
client.check_logged_in()
return client
def test_get_card():
client = test_create_client()
card = next(client.get_todo_list_cards())
assert card is not None

View File

@ -1,4 +1,4 @@
from favro_sync.favro_markdown import parse_card_contents, format_card_contents
from favro_sync.favro_markdown import CardFileFormatter
EXAMPLE_TEXT_1 = '''
---
@ -17,12 +17,11 @@ Test description
3. Derp
'''.strip()
FORMATTER = CardFileFormatter()
def test_parse_and_render():
card_contents = parse_card_contents(EXAMPLE_TEXT_1)
card_contents = FORMATTER.parse_card_contents(EXAMPLE_TEXT_1)
assert card_contents.name == 'Hello World'
assert '---' not in card_contents.description
print(card_contents)
assert False
assert format_card_contents(card_contents) == EXAMPLE_TEXT_1
assert FORMATTER.format_card_contents(card_contents) == EXAMPLE_TEXT_1