From f1e0166e0a05089412d845c3eb108938f644360d Mon Sep 17 00:00:00 2001 From: Kyle Bowman Date: Sat, 18 Jan 2025 23:24:19 -0500 Subject: [PATCH] abstract ListItem and List --- src/nom/base.py | 39 +++++++++++++++++++++++++++++++ src/nom/entry.py | 51 +++++++++++++++-------------------------- src/nom/feed.py | 28 ++++++++++++++++------ src/nom/main.py | 2 +- tests/data/entry.csv | 2 -- tests/data/feedlist.csv | 2 ++ tests/test_feed.py | 10 ++++++++ 7 files changed, 92 insertions(+), 42 deletions(-) create mode 100644 src/nom/base.py delete mode 100644 tests/data/entry.csv create mode 100644 tests/data/feedlist.csv create mode 100644 tests/test_feed.py diff --git a/src/nom/base.py b/src/nom/base.py new file mode 100644 index 0000000..4f90a1e --- /dev/null +++ b/src/nom/base.py @@ -0,0 +1,39 @@ +from pathlib import Path +from csv import DictReader, DictWriter, excel_tab + +from nom.utils import NomError + + +class NomListItem: + + + # TODO: This technically doesn't follow the __repr__ pattern. + # TODO: What if there's a pipe in one of the fields? + def to_str(self, delimiter: str ='|'): + return delimiter.join([v for v in self.__dict__.values()]) + + def to_dict(self): + return self.__dict__ + + @classmethod + def from_dict(cls, dct: dict): + return cls(**dct) + + +# NOTE: Implementation requires self.delimiter. +class NomList: + + @classmethod + def from_csv(cls, file: Path): + pass + + def to_csv(self, file: Path): + if not self.dicts: + raise NomError("There are no entries to write.") + + with open(file, "w") as f: + dialect = excel_tab + dialect.delimiter=self.delimiter + writer = DictWriter(f, fieldnames=self.fieldnames, dialect=dialect) + writer.writeheader() + writer.writerows(self.dicts) \ No newline at end of file diff --git a/src/nom/entry.py b/src/nom/entry.py index fdb34f9..4aead27 100644 --- a/src/nom/entry.py +++ b/src/nom/entry.py @@ -1,36 +1,26 @@ -from csv import DictReader, DictWriter, excel_tab from dataclasses import dataclass from pathlib import Path from typing import Optional from nom.utils import NomError +from nom.base import NomList, NomListItem + # TODO: Use proper types, not strings. (Pydantic?) @dataclass -class EntryListItem: +class EntryListItem(NomListItem): id_: str title: str - url: Optional[str] = None + url: Optional[str] = "" date: Optional[str] = "test" - feed_url: Optional[str] = None - feed_alias: Optional[str] = None - viewed: Optional[bool] = False - summary: Optional[str] = None # TODO: Add this when you feel like stripping HTML - - # TODO: What if there's a pipe in one of the fields? - def to_str(self, delimiter: str ='|'): - # values = [value for value in self.__dict__.values()] - return delimiter.join([self.title, self.url, self.date]) + feed_url: Optional[str] = "" + feed_alias: Optional[str] = "" + viewed: Optional[bool] = "False" + summary: Optional[str] = "" # TODO: Add this when you feel like stripping HTML - def to_dict(self): - return self.__dict__ - @classmethod - def from_dict(cls, dct: dict): - return cls(**dct) - -class EntryList: +class EntryList(NomList): def __init__(self, delimiter='|'): self.entries : list[EntryListItem] = [] @@ -41,23 +31,20 @@ class EntryList: def add_entry(self, entry): self.entries.append(entry) - # TODO: "Append" doesn't feel right. - def append_feed(self,feed): + def update_from_feeds(self, feedlist): pass - def from_file(self, file: Path): + def update_from_feed(self, feed): pass - def to_file(self, file: Path): - if not self.dicts: - raise NomError("There are no entries to write.") + def from_stdout(self): + pass - with open(file, "w") as f: - dialect = excel_tab - dialect.delimiter="|" - writer = DictWriter(f, fieldnames=self.fieldnames, dialect=dialect) - writer.writeheader() - writer.writerows(self.dicts) + # TODO: Fix this with command line option + def to_stdout(self): + for entry in self.entries: + if entry: + print(entry.to_str()) if __name__ == "__main__": @@ -67,4 +54,4 @@ if __name__ == "__main__": entry = EntryListItem.from_dict(dct) elist.add_entry(entry) elist.dicts = [entry.__dict__ for entry in elist.entries] - elist.to_file(path) + elist.to_csv(path) diff --git a/src/nom/feed.py b/src/nom/feed.py index f2ee124..1baa8d3 100644 --- a/src/nom/feed.py +++ b/src/nom/feed.py @@ -1,12 +1,14 @@ import os from pathlib import Path from typing import Optional +from dataclasses import dataclass import feedparser import requests from nom.entry import EntryListItem from nom.utils import url2filename +from nom.base import NomListItem, NomList class Feed: @@ -23,20 +25,32 @@ class Feed: e.updated ) for e in d.entries] - # TODO: Fix this with command line option - def to_stdout(self, file: Optional[Path]=None): + def to_stdout(self): for entry in self.entries: if entry: print(entry.to_str()) -class FeedList: +@dataclass +class FeedListItem(NomListItem): + id_: str + url: str + alias: str - def __init__(self, file: Path): + +class FeedList(NomList): + + # TODO: Make this follow the NomList pattern + def __init__(self, name, urls): + self.name = name + self.urls = urls + #self.feeds = [] + + # TODO: Rewrite this with CSV and FeedListItem parsing + @classmethod + def from_file(cls, file: Path): with open(file, 'r') as f: urls = f.read().splitlines() - - self.name = file.name - self.urls = urls + return cls(file.name, urls) def fetch_feeds(self, save_dir: Path): if not os.path.exists(save_dir): diff --git a/src/nom/main.py b/src/nom/main.py index a66f314..24acb69 100644 --- a/src/nom/main.py +++ b/src/nom/main.py @@ -15,7 +15,7 @@ def main(): args = parser.parse_args() # Direct Logic - feedlist=FeedList(FEED_LIST) + feedlist=FeedList.from_file(FEED_LIST) if args.command == "entry" and args.entry_command == "show": for url in feedlist.urls: feed=Feed(str(FEED_CACHE / url2filename(url))) diff --git a/tests/data/entry.csv b/tests/data/entry.csv deleted file mode 100644 index 3d7f348..0000000 --- a/tests/data/entry.csv +++ /dev/null @@ -1,2 +0,0 @@ -id_|title|url|date|feed_url|feed_alias|viewed|summary -1|Kyle was here!|||||False| diff --git a/tests/data/feedlist.csv b/tests/data/feedlist.csv new file mode 100644 index 0000000..f0c612c --- /dev/null +++ b/tests/data/feedlist.csv @@ -0,0 +1,2 @@ +https://simonwillison.net/atom/everything/ +https://jvns.ca/atom.xml \ No newline at end of file diff --git a/tests/test_feed.py b/tests/test_feed.py new file mode 100644 index 0000000..b4176da --- /dev/null +++ b/tests/test_feed.py @@ -0,0 +1,10 @@ +from pathlib import Path + +from nom.feed import * + +FEED_LIST=Path(__file__).parent / "data" / "feedlist.csv" + +def test_from_file(): + feedlist = FeedList.from_file(FEED_LIST) + assert len(feedlist.urls) == 2 + assert "jvn" in feedlist.urls[1] \ No newline at end of file -- 2.39.5