This commit is contained in:
parent
412a20b006
commit
c09245bd1c
53
README.md
53
README.md
|
@ -15,16 +15,29 @@ your organization's payment plan.
|
|||
|
||||
Uses [`python-fuse`](https://github.com/libfuse/python-fuse) library.
|
||||
|
||||
Features:
|
||||
|
||||
- Local access to cards in todolist.
|
||||
- Read card features:
|
||||
- Title
|
||||
- Description
|
||||
- Tags
|
||||
- Assignees
|
||||
- Dependencies
|
||||
- Change card features:
|
||||
- Title
|
||||
- Description
|
||||
- [Obsidian](https://obsidian.md/) compatibility:
|
||||
- Mountable within your vault.
|
||||
- Link to cards by either card number or card title.
|
||||
- Tags and dependencies are integrated.
|
||||
|
||||
Limitations:
|
||||
|
||||
- Only cards in todolist is fetched at the moment.
|
||||
- Doesn't include title anywhere.
|
||||
- Tasks cannot be updated or changed.
|
||||
- Slow, due to inefficient use of caches.
|
||||
|
||||
A more complete implementation will probably require a Markdown parser, to
|
||||
parse the saved input, and distribute it across the various Card fields (card
|
||||
name, description, tasks, etc...)
|
||||
- Tasks (checklists on cards) cannot be updated or changed.
|
||||
- Images cannot be updated or changed.
|
||||
- You cannot create new cards, nor any other files.
|
||||
|
||||
## Usage
|
||||
|
||||
|
@ -35,23 +48,27 @@ name, description, tasks, etc...)
|
|||
[`python-fuse`](https://github.com/libfuse/python-fuse) implements a whole
|
||||
bunch automatically.)
|
||||
|
||||
## Architecture
|
||||
|
||||
## Dependencies
|
||||
- `FavroFuse`
|
||||
- Markdown Parser/Renderer
|
||||
- `FavroClient`
|
||||
- `CardCache`
|
||||
|
||||
All requirements can be installed easily using:
|
||||
## Work in Progress
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
Following features are work in progress:
|
||||
|
||||
Full list of requirements:
|
||||
- [requests](https://pypi.org/project/requests/)
|
||||
- [requests-cache](https://pypi.org/project/requests-cache/)
|
||||
- [fuse-python](https://pypi.org/project/fuse-python/)
|
||||
- [secret_loader](https://gitfub.space/Jmaa/secret_loader)
|
||||
- [ ] Frontmatter: Update Tags
|
||||
- [ ] Frontmatter: Updated assigned members
|
||||
- [ ] Frontmatter: Arbitrary structured data? Read-only.
|
||||
- [ ] Frontmatter: Dependencies. As vault links in Obsidian mode.
|
||||
- [ ] Allow users to toggle Obsidian mode, instead of being default.
|
||||
- [ ] Get the correct last-modified date.
|
||||
- [ ] Improve cache behaviour. User and tags can have much longer cache times.
|
||||
|
||||
|
||||
## License
|
||||
# License
|
||||
|
||||
```
|
||||
MIT License
|
||||
|
|
|
@ -36,28 +36,28 @@ CARD_FILENAME_REGEX = r'^\/PAR\-(\d+)\.md$'
|
|||
OFFICIAL_URL='https://favro.com/organization/{org_id}?card=par-{seq_id}'
|
||||
|
||||
@dataclasses.dataclass(frozen=True)
|
||||
class Thing:
|
||||
class FileSystemItem:
|
||||
@staticmethod
|
||||
def from_path(path_str: str) -> 'Thing | None':
|
||||
def from_path(path_str: str) -> 'FileSystemItem | None':
|
||||
if path_str == '/':
|
||||
return RootThing()
|
||||
return RootFileSystemItem()
|
||||
if m := re.match(CARD_FILENAME_REGEX, path_str):
|
||||
return CardThing(SeqId(int(m.group(1))))
|
||||
return CardFileSystemItem(SeqId(int(m.group(1))))
|
||||
return None
|
||||
|
||||
|
||||
@dataclasses.dataclass(frozen=True)
|
||||
class RootThing(Thing):
|
||||
class RootFileSystemItem(FileSystemItem):
|
||||
pass
|
||||
|
||||
|
||||
@dataclasses.dataclass(frozen=True)
|
||||
class CardThing(Thing):
|
||||
class CardFileSystemItem(FileSystemItem):
|
||||
seq_id: SeqId
|
||||
|
||||
|
||||
class FavroFuse(fuse.Fuse):
|
||||
"""Favro Filesystem in Userspace."""
|
||||
"""Favro FileSystem in Userspace."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
@ -67,16 +67,17 @@ class FavroFuse(fuse.Fuse):
|
|||
):
|
||||
self.favro_client = favro_client
|
||||
self.formatter = formatter
|
||||
self.wiped_cards = set()
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def getattr(self, path: str) -> FavroStat | int:
|
||||
thing = Thing.from_path(path)
|
||||
thing = FileSystemItem.from_path(path)
|
||||
|
||||
st = FavroStat()
|
||||
if isinstance(thing, RootThing):
|
||||
if isinstance(thing, RootFileSystemItem):
|
||||
st.st_mode = stat.S_IFDIR | 0o755
|
||||
st.st_nlink = 2
|
||||
elif isinstance(thing, CardThing):
|
||||
elif isinstance(thing, CardFileSystemItem):
|
||||
card = self.favro_client.get_card(thing.seq_id)
|
||||
|
||||
st.st_mode = stat.S_IFREG | 0o666
|
||||
|
@ -97,19 +98,20 @@ class FavroFuse(fuse.Fuse):
|
|||
yield fuse.Direntry(CARD_FILENAME_FORMAT.format(seq_id=card.seq_id.raw_id))
|
||||
|
||||
def open(self, path: str, flags: int) -> int | None:
|
||||
thing = Thing.from_path(path)
|
||||
if not isinstance(thing, CardThing):
|
||||
thing = FileSystemItem.from_path(path)
|
||||
if not isinstance(thing, CardFileSystemItem):
|
||||
return -errno.ENOENT
|
||||
return None
|
||||
|
||||
def read(self, path: str, size: int, offset: int) -> bytes | int:
|
||||
# Check that this is a card thing.
|
||||
thing = Thing.from_path(path)
|
||||
if not isinstance(thing, CardThing):
|
||||
thing = FileSystemItem.from_path(path)
|
||||
if not isinstance(thing, CardFileSystemItem):
|
||||
return -errno.ENOENT
|
||||
|
||||
card = self.favro_client.get_card(thing.seq_id)
|
||||
|
||||
|
||||
contents_str = self._format_card_file(card)
|
||||
contents = bytes(contents_str, 'utf8')
|
||||
|
||||
|
@ -124,8 +126,8 @@ class FavroFuse(fuse.Fuse):
|
|||
|
||||
def write(self, path: str, written_buffer: bytes, offset: int) -> int:
|
||||
# Check that this is a card thing.
|
||||
thing = Thing.from_path(path)
|
||||
if not isinstance(thing, CardThing):
|
||||
thing = FileSystemItem.from_path(path)
|
||||
if not isinstance(thing, CardFileSystemItem):
|
||||
return -errno.ENOENT
|
||||
|
||||
card = self.favro_client.get_card(thing.seq_id)
|
||||
|
@ -140,13 +142,15 @@ class FavroFuse(fuse.Fuse):
|
|||
card_updated = self.formatter.parse_card_contents(contents_str)
|
||||
self.favro_client.update_card_contents(card.card_id, card_updated)
|
||||
|
||||
self.wiped_cards.remove(thing.seq_id)
|
||||
|
||||
# Return amount written
|
||||
return len(written_buffer)
|
||||
|
||||
def truncate(self, path: str, new_size: int):
|
||||
# Check that this is a card thing.
|
||||
thing = Thing.from_path(path)
|
||||
if not isinstance(thing, CardThing):
|
||||
thing = FileSystemItem.from_path(path)
|
||||
if not isinstance(thing, CardFileSystemItem):
|
||||
return -errno.ENOENT
|
||||
|
||||
card = self.favro_client.get_card(thing.seq_id)
|
||||
|
@ -163,10 +167,15 @@ class FavroFuse(fuse.Fuse):
|
|||
card_updated = self.formatter.parse_card_contents(contents_str)
|
||||
self.favro_client.update_card_contents_locally(card.card_id, card_updated)
|
||||
|
||||
self.wiped_cards.add(thing.seq_id)
|
||||
|
||||
# Return amount written
|
||||
return 0
|
||||
|
||||
def _format_card_file(self, card: Card) -> str:
|
||||
if card.seq_id in self.wiped_cards:
|
||||
return ''
|
||||
|
||||
tags = [self.favro_client.get_tag(tag_id).name for tag_id in card.tags]
|
||||
assignments = [
|
||||
self.favro_client.get_user(assignment.user).name
|
||||
|
|
|
@ -39,8 +39,10 @@ class CardFileFormatter:
|
|||
frontmatter_data = {}
|
||||
if card.name and self.obsidian_mode:
|
||||
frontmatter_data['aliases'] = [card.name]
|
||||
frontmatter_data['tags'] = card.tags
|
||||
frontmatter_data['url'] = card.url
|
||||
if card.tags:
|
||||
frontmatter_data['tags'] = card.tags
|
||||
if card.url and self.obsidian_mode:
|
||||
frontmatter_data['url'] = card.url
|
||||
if card.assignments:
|
||||
frontmatter_data['assignments'] = card.assignments
|
||||
if self.obsidian_mode:
|
||||
|
@ -94,6 +96,8 @@ class CardFileFormatter:
|
|||
card_dependencies: list[str] = fm.metadata.get('dependencies', [])
|
||||
card_dependencies = [parse_obsidian_link(text) for text in card_dependencies]
|
||||
|
||||
url: list[str] = fm.metadata.get('url')
|
||||
|
||||
description = self.renderer.render_children(document).strip()
|
||||
return CardContents(
|
||||
name,
|
||||
|
@ -101,5 +105,5 @@ class CardFileFormatter:
|
|||
tags=tags,
|
||||
assignments=assignments,
|
||||
card_dependencies=card_dependencies,
|
||||
url='', # TODO
|
||||
url=url,
|
||||
)
|
||||
|
|
|
@ -40,6 +40,7 @@ EXAMPLE_TEXT_3 = """
|
|||
---
|
||||
aliases:
|
||||
- 'Card: The Adventure of Card'
|
||||
url: https://example.org
|
||||
---
|
||||
|
||||
# Card: The Adventure of Card
|
||||
|
|
Loading…
Reference in New Issue
Block a user