CardFileFormatter: Tags and assignments WIP
Some checks failed
Test Python / Test (push) Failing after 26s
Some checks failed
Test Python / Test (push) Failing after 26s
This commit is contained in:
parent
9d2cd93ed3
commit
d4cfe4da22
|
@ -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,
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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']],
|
||||
)
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -13,25 +13,44 @@ 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:
|
||||
def format_card_contents(self, card: CardContents) -> str:
|
||||
ls = []
|
||||
|
||||
# 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 OBSIDIAN_MODE:
|
||||
if card.name:
|
||||
if frontmatter_data:
|
||||
ls.append('---\n')
|
||||
# TODO: Tags
|
||||
ls.append('aliases:\n')
|
||||
for key, values in frontmatter_data.items():
|
||||
ls.append(key)
|
||||
ls.append(':\n')
|
||||
for v in values:
|
||||
ls.append(' - ')
|
||||
ls.append(card.name)
|
||||
ls.append(v)
|
||||
ls.append('\n')
|
||||
ls.append('---\n\n')
|
||||
|
||||
|
@ -46,7 +65,7 @@ def format_card_contents(card: CardContents) -> str:
|
|||
ls.append(card.description)
|
||||
return ''.join(ls)
|
||||
|
||||
def parse_card_contents(contents: str) -> CardContents:
|
||||
def parse_card_contents(self, contents: str) -> CardContents:
|
||||
"""
|
||||
1. Strips frontmatter
|
||||
2. Parses header
|
||||
|
@ -55,18 +74,21 @@ def parse_card_contents(contents: str) -> CardContents:
|
|||
fm = frontmatter.loads(contents)
|
||||
del contents
|
||||
|
||||
document = markdown.parse(fm.content.strip())
|
||||
document = self.markdown.parse(fm.content.strip())
|
||||
name = None
|
||||
for elem in document.children:
|
||||
if isinstance(elem, marko.block.Heading):
|
||||
name = renderer.render_children(elem)
|
||||
name = self.renderer.render_children(elem)
|
||||
document.children.remove(elem)
|
||||
break
|
||||
|
||||
description = self.renderer.render_children(document).strip()
|
||||
return CardContents(
|
||||
name,
|
||||
renderer.render_children(document).strip(),
|
||||
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
12
favro_sync/secrets.py
Normal 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')
|
|
@ -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
1
test/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
"""Testing package."""
|
22
test/test_client.py
Normal file
22
test/test_client.py
Normal 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
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue
Block a user