···991010from ...core.feed_parser import FeedParser
1111from ...core.git_store import GitStore
1212-from ...models import UserConfig
1312from ..main import app
1413from ..utils import (
1514 create_progress,
···1716 print_error,
1817 print_info,
1918 print_success,
2020- save_config,
2119)
22202321···7573 # Load configuration
7674 config = load_config(config_file)
77757676+ # Initialize Git store
7777+ git_store = GitStore(config.git_store)
7878+7879 # Check if user already exists
7979- existing_user = config.find_user(username)
8080+ existing_user = git_store.get_user(username)
8081 if existing_user:
8182 print_error(f"User '{username}' already exists")
8283 print_error("Use 'thicket add feed' to add additional feeds")
···8788 if auto_discover:
8889 discovered_metadata = asyncio.run(discover_feed_metadata(validated_feed_url))
89909090- # Create user config with manual overrides taking precedence
9191- user_config = UserConfig(
9292- username=username,
9393- feeds=[validated_feed_url],
9494- email=email or (discovered_metadata.author_email if discovered_metadata else None),
9595- homepage=HttpUrl(homepage) if homepage else (discovered_metadata.author_uri or discovered_metadata.link if discovered_metadata else None),
9696- icon=HttpUrl(icon) if icon else (discovered_metadata.logo or discovered_metadata.icon or discovered_metadata.image_url if discovered_metadata else None),
9797- display_name=display_name or (discovered_metadata.author_name or discovered_metadata.title if discovered_metadata else None),
9898- )
9999-100100- # Add user to configuration
101101- config.add_user(user_config)
102102-103103- # Save configuration
104104- save_config(config, config_file)
9191+ # Prepare user data with manual overrides taking precedence
9292+ user_display_name = display_name or (discovered_metadata.author_name or discovered_metadata.title if discovered_metadata else None)
9393+ user_email = email or (discovered_metadata.author_email if discovered_metadata else None)
9494+ user_homepage = homepage or (str(discovered_metadata.author_uri or discovered_metadata.link) if discovered_metadata else None)
9595+ user_icon = icon or (str(discovered_metadata.logo or discovered_metadata.icon or discovered_metadata.image_url) if discovered_metadata else None)
1059610697 # Add user to Git store
107107- git_store = GitStore(config.git_store)
10898 git_store.add_user(
10999 username=username,
110110- display_name=user_config.display_name,
111111- email=user_config.email,
112112- homepage=str(user_config.homepage) if user_config.homepage else None,
113113- icon=str(user_config.icon) if user_config.icon else None,
114114- feeds=[str(f) for f in user_config.feeds],
100100+ display_name=user_display_name,
101101+ email=user_email,
102102+ homepage=user_homepage,
103103+ icon=user_icon,
104104+ feeds=[str(validated_feed_url)],
115105 )
116106117107 # Commit changes
···121111122112 if discovered_metadata and auto_discover:
123113 print_info("Auto-discovered metadata:")
124124- if user_config.display_name:
125125- print_info(f" Display name: {user_config.display_name}")
126126- if user_config.email:
127127- print_info(f" Email: {user_config.email}")
128128- if user_config.homepage:
129129- print_info(f" Homepage: {user_config.homepage}")
130130- if user_config.icon:
131131- print_info(f" Icon: {user_config.icon}")
114114+ if user_display_name:
115115+ print_info(f" Display name: {user_display_name}")
116116+ if user_email:
117117+ print_info(f" Email: {user_email}")
118118+ if user_homepage:
119119+ print_info(f" Homepage: {user_homepage}")
120120+ if user_icon:
121121+ print_info(f" Icon: {user_icon}")
132122133123134124def add_feed(username: str, feed_url: Optional[str], config_file: Path) -> None:
···148138 # Load configuration
149139 config = load_config(config_file)
150140141141+ # Initialize Git store
142142+ git_store = GitStore(config.git_store)
143143+151144 # Check if user exists
152152- user = config.find_user(username)
145145+ user = git_store.get_user(username)
153146 if not user:
154147 print_error(f"User '{username}' not found")
155148 print_error("Use 'thicket add user' to add a new user")
156149 raise typer.Exit(1)
157150158151 # Check if feed already exists
159159- if validated_feed_url in user.feeds:
152152+ if str(validated_feed_url) in user.feeds:
160153 print_error(f"Feed already exists for user '{username}': {feed_url}")
161154 raise typer.Exit(1)
162155163156 # Add feed to user
164164- if config.add_feed_to_user(username, validated_feed_url):
165165- save_config(config, config_file)
166166-167167- # Update Git store
168168- git_store = GitStore(config.git_store)
169169- git_store.update_user(username, feeds=[str(f) for f in user.feeds])
157157+ updated_feeds = user.feeds + [str(validated_feed_url)]
158158+ if git_store.update_user(username, feeds=updated_feeds):
170159 git_store.commit_changes(f"Add feed to user {username}: {feed_url}")
171171-172160 print_success(f"Added feed to user '{username}': {feed_url}")
173161 else:
174162 print_error(f"Failed to add feed to user '{username}'")
+21-15
src/thicket/cli/commands/list_cmd.py
···1313 load_config,
1414 print_error,
1515 print_feeds_table,
1616+ print_feeds_table_from_git,
1617 print_info,
1718 print_users_table,
1919+ print_users_table_from_git,
1820)
19212022···3638 # Load configuration
3739 config = load_config(config_file)
38404141+ # Initialize Git store
4242+ git_store = GitStore(config.git_store)
4343+3944 if what == "users":
4040- list_users(config)
4545+ list_users(git_store)
4146 elif what == "feeds":
4242- list_feeds(config, user)
4747+ list_feeds(git_store, user)
4348 elif what == "entries":
4444- list_entries(config, user, limit)
4949+ list_entries(git_store, user, limit)
4550 else:
4651 print_error(f"Unknown list type: {what}")
4752 print_error("Use 'users', 'feeds', or 'entries'")
4853 raise typer.Exit(1)
495450555151-def list_users(config) -> None:
5656+def list_users(git_store: GitStore) -> None:
5257 """List all users."""
5353- if not config.users:
5858+ index = git_store._load_index()
5959+ users = list(index.users.values())
6060+6161+ if not users:
5462 print_info("No users configured")
5563 return
56645757- print_users_table(config)
6565+ print_users_table_from_git(users)
586659676060-def list_feeds(config, username: Optional[str] = None) -> None:
6868+def list_feeds(git_store: GitStore, username: Optional[str] = None) -> None:
6169 """List feeds, optionally filtered by user."""
6270 if username:
6363- user = config.find_user(username)
7171+ user = git_store.get_user(username)
6472 if not user:
6573 print_error(f"User '{username}' not found")
6674 raise typer.Exit(1)
···6977 print_info(f"No feeds configured for user '{username}'")
7078 return
71797272- print_feeds_table(config, username)
8080+ print_feeds_table_from_git(git_store, username)
738174827575-def list_entries(config, username: Optional[str] = None, limit: Optional[int] = None) -> None:
8383+def list_entries(git_store: GitStore, username: Optional[str] = None, limit: Optional[int] = None) -> None:
7684 """List entries, optionally filtered by user."""
77857878- # Initialize Git store
7979- git_store = GitStore(config.git_store)
8080-8186 if username:
8287 # List entries for specific user
8383- user = config.find_user(username)
8888+ user = git_store.get_user(username)
8489 if not user:
8590 print_error(f"User '{username}' not found")
8691 raise typer.Exit(1)
···97102 all_entries = []
98103 all_usernames = []
99104100100- for user in config.users:
105105+ index = git_store._load_index()
106106+ for user in index.users.values():
101107 entries = git_store.list_entries(user.username, limit)
102108 if entries:
103109 all_entries.append(entries)
+15-14
src/thicket/cli/commands/sync.py
···3838 # Load configuration
3939 config = load_config(config_file)
40404141- # Determine which users to sync
4141+ # Initialize Git store
4242+ git_store = GitStore(config.git_store)
4343+4444+ # Determine which users to sync from git repository
4245 users_to_sync = []
4346 if all_users:
4444- users_to_sync = config.users
4747+ index = git_store._load_index()
4848+ users_to_sync = list(index.users.values())
4549 elif user:
4646- user_config = config.find_user(user)
4747- if not user_config:
4848- print_error(f"User '{user}' not found")
5050+ user_metadata = git_store.get_user(user)
5151+ if not user_metadata:
5252+ print_error(f"User '{user}' not found in git repository")
4953 raise typer.Exit(1)
5050- users_to_sync = [user_config]
5454+ users_to_sync = [user_metadata]
5155 else:
5256 print_error("Specify --all to sync all users or --user to sync a specific user")
5357 raise typer.Exit(1)
···5559 if not users_to_sync:
5660 print_info("No users configured to sync")
5761 return
5858-5959- # Initialize Git store
6060- git_store = GitStore(config.git_store)
61626263 # Sync each user
6364 total_new_entries = 0
6465 total_updated_entries = 0
65666666- for user_config in users_to_sync:
6767- print_info(f"Syncing user: {user_config.username}")
6767+ for user_metadata in users_to_sync:
6868+ print_info(f"Syncing user: {user_metadata.username}")
68696970 user_new_entries = 0
7071 user_updated_entries = 0
71727273 # Sync each feed for the user
7373- for feed_url in track(user_config.feeds, description=f"Syncing {user_config.username}'s feeds"):
7474+ for feed_url in track(user_metadata.feeds, description=f"Syncing {user_metadata.username}'s feeds"):
7475 try:
7576 new_entries, updated_entries = asyncio.run(
7676- sync_feed(git_store, user_config.username, feed_url, dry_run)
7777+ sync_feed(git_store, user_metadata.username, feed_url, dry_run)
7778 )
7879 user_new_entries += new_entries
7980 user_updated_entries += updated_entries
···8283 print_error(f"Failed to sync feed {feed_url}: {e}")
8384 continue
84858585- print_info(f"User {user_config.username}: {user_new_entries} new, {user_updated_entries} updated")
8686+ print_info(f"User {user_metadata.username}: {user_new_entries} new, {user_updated_entries} updated")
8687 total_new_entries += user_new_entries
8788 total_updated_entries += user_updated_entries
8889
+1
src/thicket/cli/main.py
···383839394040# Import commands to register them
4141+from .commands import add, duplicates, init, list_cmd, sync
41424243if __name__ == "__main__":
4344 app()
+49-1
src/thicket/cli/utils.py
···88from rich.progress import Progress, SpinnerColumn, TextColumn
99from rich.table import Table
10101111-from ..models import ThicketConfig
1111+from ..models import ThicketConfig, UserMetadata
1212+from ..core.git_store import GitStore
12131314console = Console()
1415···123124def print_info(message: str) -> None:
124125 """Print an info message."""
125126 console.print(f"[blue]ℹ[/blue] {message}")
127127+128128+129129+def print_users_table_from_git(users: list[UserMetadata]) -> None:
130130+ """Print a table of users from git repository."""
131131+ table = Table(title="Users and Feeds")
132132+ table.add_column("Username", style="cyan", no_wrap=True)
133133+ table.add_column("Display Name", style="magenta")
134134+ table.add_column("Email", style="blue")
135135+ table.add_column("Homepage", style="green")
136136+ table.add_column("Feeds", style="yellow")
137137+138138+ for user in users:
139139+ feeds_str = "\n".join(user.feeds)
140140+ table.add_row(
141141+ user.username,
142142+ user.display_name or "",
143143+ user.email or "",
144144+ user.homepage or "",
145145+ feeds_str,
146146+ )
147147+148148+ console.print(table)
149149+150150+151151+def print_feeds_table_from_git(git_store: GitStore, username: Optional[str] = None) -> None:
152152+ """Print a table of feeds from git repository."""
153153+ table = Table(title=f"Feeds{f' for {username}' if username else ''}")
154154+ table.add_column("Username", style="cyan", no_wrap=True)
155155+ table.add_column("Feed URL", style="blue")
156156+ table.add_column("Status", style="green")
157157+158158+ if username:
159159+ user = git_store.get_user(username)
160160+ users = [user] if user else []
161161+ else:
162162+ index = git_store._load_index()
163163+ users = list(index.users.values())
164164+165165+ for user in users:
166166+ for feed in user.feeds:
167167+ table.add_row(
168168+ user.username,
169169+ feed,
170170+ "Active", # TODO: Add actual status checking
171171+ )
172172+173173+ console.print(table)
+3-14
src/thicket/core/git_store.py
···109109 last_updated=datetime.now(),
110110 )
111111112112- # Save user metadata
113113- metadata_path = user_dir / "metadata.json"
114114- with open(metadata_path, "w") as f:
115115- json.dump(user_metadata.model_dump(mode="json"), f, indent=2, default=str)
116112117113 # Update index
118114 index.add_user(user_metadata)
···140136141137 user.update_timestamp()
142138143143- # Save user metadata
144144- user_dir = self.repo_path / user.directory
145145- metadata_path = user_dir / "metadata.json"
146146- with open(metadata_path, "w") as f:
147147- json.dump(user.model_dump(mode="json"), f, indent=2, default=str)
148139149140 # Update index
150141 index.add_user(user)
···176167177168 # Update user metadata if new entry
178169 if not entry_exists:
179179- user.increment_entry_count()
180180- self.update_user(username, entry_count=user.entry_count)
170170+ index = self._load_index()
171171+ index.update_entry_count(username, 1)
172172+ self._save_index(index)
181173182174 return True
183175···214206 entries = []
215207 entry_files = sorted(user_dir.glob("*.json"), key=lambda p: p.stat().st_mtime, reverse=True)
216208217217- # Filter out metadata.json
218218- entry_files = [f for f in entry_files if f.name != "metadata.json"]
219209220210 if limit:
221211 entry_files = entry_files[:limit]
···289279 continue
290280291281 entry_files = user_dir.glob("*.json")
292292- entry_files = [f for f in entry_files if f.name != "metadata.json"]
293282294283 for entry_file in entry_files:
295284 try:
-38
src/thicket/models/config.py
···3131 git_store: Path
3232 cache_dir: Path
3333 users: list[UserConfig] = []
3434-3535- def find_user(self, username: str) -> Optional[UserConfig]:
3636- """Find a user by username."""
3737- for user in self.users:
3838- if user.username == username:
3939- return user
4040- return None
4141-4242- def add_user(self, user: UserConfig) -> None:
4343- """Add a new user or update existing user."""
4444- existing = self.find_user(user.username)
4545- if existing:
4646- # Update existing user
4747- existing.feeds = list(set(existing.feeds + user.feeds))
4848- existing.email = user.email or existing.email
4949- existing.homepage = user.homepage or existing.homepage
5050- existing.icon = user.icon or existing.icon
5151- existing.display_name = user.display_name or existing.display_name
5252- else:
5353- # Add new user
5454- self.users.append(user)
5555-5656- def remove_user(self, username: str) -> bool:
5757- """Remove a user by username. Returns True if user was found and removed."""
5858- for i, user in enumerate(self.users):
5959- if user.username == username:
6060- del self.users[i]
6161- return True
6262- return False
6363-6464- def add_feed_to_user(self, username: str, feed_url: HttpUrl) -> bool:
6565- """Add a feed to an existing user. Returns True if user was found."""
6666- user = self.find_user(username)
6767- if user:
6868- if feed_url not in user.feeds:
6969- user.feeds.append(feed_url)
7070- return True
7171- return False
+5-6
tests/test_git_store.py
···6565 # Check that user directory was created
6666 user_dir = store.repo_path / "testuser"
6767 assert user_dir.exists()
6868- assert (user_dir / "metadata.json").exists()
69687070- # Check metadata file content
7171- with open(user_dir / "metadata.json") as f:
7272- metadata = json.load(f)
7373- assert metadata["username"] == "testuser"
7474- assert metadata["display_name"] == "Test User"
6969+ # Check user exists in index
7070+ stored_user = store.get_user("testuser")
7171+ assert stored_user is not None
7272+ assert stored_user.username == "testuser"
7373+ assert stored_user.display_name == "Test User"
75747675 def test_get_user(self, temp_dir):
7776 """Test getting user metadata."""