Skip to content

Commit f061a64

Browse files
authored
Merge pull request #73 from Matars/ProviderLogic
Provider logic
2 parents 2107bed + 332f8c3 commit f061a64

File tree

5 files changed

+256
-55
lines changed

5 files changed

+256
-55
lines changed

docs/providers.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,17 @@ gitfetch supports multiple Git hosting platforms with different authentication m
1818
2. Run `gh auth login` to authenticate
1919
3. gitfetch will detect and use your GitHub credentials
2020

21+
**Token Configuration**:
22+
23+
You can also set a token via:
24+
- Config file: set `token` in `[github]` section of `~/.config/gitfetch/gitfetch.conf`
25+
- Environment variable: `GH_TOKEN`
26+
27+
**Rate Limits**:
28+
29+
- **No token**: 60 requests/hour (limited, may hit limits)
30+
- **With token**: 5,000 requests/hour (recommended)
31+
2132
## GitLab
2233

2334
**Authentication**: Uses GitLab CLI (glab)
@@ -28,6 +39,12 @@ gitfetch supports multiple Git hosting platforms with different authentication m
2839
2. Run `glab auth login` to authenticate
2940
3. gitfetch will detect and use your GitLab credentials
3041

42+
**Token Configuration**:
43+
44+
You can also set a token via:
45+
- Config file: set `token` in `[gitlab]` section of `~/.config/gitfetch/gitfetch.conf`
46+
- Environment variable: `GITLAB_TOKEN`
47+
3148
## Gitea/Forgejo/Codeberg
3249

3350
**Authentication**: Personal access tokens

src/gitfetch/cli.py

Lines changed: 61 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from .display import DisplayFormatter
1313
from .cache import CacheManager
1414
from .config import ConfigManager
15+
from .providers import ProviderConfig, PROVIDER_ENV_VARS, PROVIDER_DEFAULT_URLS
1516
from . import __version__
1617

1718

@@ -25,16 +26,16 @@ def _background_refresh_cache_subprocess(username: str) -> None:
2526
config_manager = ConfigManager()
2627
cache_expiry = config_manager.get_cache_expiry_minutes()
2728
cache_manager = CacheManager(cache_expiry_minutes=cache_expiry)
28-
provider = config_manager.get_provider()
29-
provider_url = config_manager.get_provider_url()
30-
token = config_manager.get_token()
31-
if provider == None:
32-
print("Provider not set")
29+
30+
provider_config = config_manager.get_provider_config()
31+
if not provider_config:
3332
exit(1)
34-
if provider_url == None:
35-
print("Provider url not set")
33+
if not provider_config.url:
3634
exit(1)
37-
fetcher = _create_fetcher(provider, provider_url, token)
35+
36+
fetcher = _create_fetcher(
37+
provider_config.name, provider_config.url, provider_config.token or None
38+
)
3839

3940
fresh_user_data = fetcher.fetch_user_data(username)
4041
fresh_stats = fetcher.fetch_user_stats(username, fresh_user_data)
@@ -264,17 +265,18 @@ def main() -> int:
264265
# Initialize components
265266
cache_expiry = config_manager.get_cache_expiry_minutes()
266267
cache_manager = CacheManager(cache_expiry_minutes=cache_expiry)
267-
provider = config_manager.get_provider()
268-
provider_url = config_manager.get_provider_url()
269-
token = config_manager.get_token()
270-
if provider == None:
271-
print("Provider not set")
268+
269+
provider_config = config_manager.get_provider_config()
270+
if not provider_config:
271+
print("Provider not set. Run: gitfetch --change-provider")
272272
return 1
273-
if provider_url == None:
274-
print("Provider url not set")
273+
if not provider_config.url:
274+
print("Provider URL not set. Run: gitfetch --change-provider")
275275
return 1
276276

277-
fetcher = _create_fetcher(provider, provider_url, token)
277+
fetcher = _create_fetcher(
278+
provider_config.name, provider_config.url, provider_config.token or None
279+
)
278280

279281
# Handle custom box character
280282
custom_box = args.custom_box
@@ -565,54 +567,66 @@ def _initialize_gitfetch(config_manager: ConfigManager) -> bool:
565567
if not provider:
566568
return False
567569

568-
config_manager.set_provider(provider)
569-
570-
# Set default URL for known providers
570+
# Determine URL for provider
571571
if provider == 'github':
572-
config_manager.set_provider_url('https://api.github.com')
572+
url = PROVIDER_DEFAULT_URLS.get('github', 'https://api.github.com')
573573
elif provider == 'gitlab':
574-
config_manager.set_provider_url('https://gitlab.com')
574+
url = PROVIDER_DEFAULT_URLS.get('gitlab', 'https://gitlab.com')
575575
elif provider == 'gitea':
576576
url = input("Enter Gitea/Forgejo/Codeberg URL: ").strip()
577577
if not url:
578578
print("Provider URL required", file=sys.stderr)
579579
return False
580-
config_manager.set_provider_url(url)
581580
elif provider == 'sourcehut':
582-
config_manager.set_provider_url('https://git.sr.ht')
583-
584-
# Ask for token if needed
585-
token = None
586-
if provider in ['gitlab', 'gitea', 'sourcehut', 'github']:
587-
token_input = input(
588-
f"Enter your {provider} personal access token{', needed for private repositories' if provider == 'github' else ''}\n"
589-
+
590-
"(optional, press Enter to skip): "
591-
).strip()
592-
if token_input:
593-
token = token_input
594-
config_manager.set_token(token)
595-
596-
# Create appropriate fetcher
597-
url = config_manager.get_provider_url()
598-
if url == None:
599-
print("Provider url could not be found.", file=sys.stderr)
581+
url = PROVIDER_DEFAULT_URLS.get('sourcehut', 'https://git.sr.ht')
582+
else:
583+
print(f"Unsupported provider: {provider}", file=sys.stderr)
600584
return False
601585

602-
fetcher = _create_fetcher(
603-
provider, url, token
586+
# Ask for token (optional - only for extra rate limits)
587+
token = ''
588+
env_var = PROVIDER_ENV_VARS.get(provider, '')
589+
590+
# Make it clear CLI is required, token is optional for rate limits
591+
if provider in ['github', 'gitlab']:
592+
cli_name = 'gh' if provider == 'github' else 'glab'
593+
print(f"\nNote: {cli_name} CLI is required for authentication.")
594+
print(f"Token is optional but increases rate limits.\n")
595+
596+
token_msg = f"Enter your {provider} personal access token"
597+
token_msg += f"\n(optional - for higher rate limits, press Enter to skip"
598+
if env_var:
599+
token_msg += f", or set {env_var} env var"
600+
token_msg += "): "
601+
602+
token_input = input(token_msg).strip()
603+
if token_input:
604+
token = token_input
605+
606+
# Create provider config
607+
provider_config = ProviderConfig(
608+
name=provider,
609+
username='', # Will be set after fetcher auth
610+
url=url,
611+
token=token
604612
)
605613

614+
# Create fetcher to get authenticated user
615+
fetcher = _create_fetcher(provider, url, token or None)
616+
606617
# Try to get authenticated user
607618
try:
608619
username = fetcher.get_authenticated_user()
609-
print(f"Using authenticated user: {username}")
620+
# Show auth status with token info
621+
token_status = "with token" if token else "without token (limited rate)"
622+
print(f"✓ Authenticated as: {username} ({token_status})")
623+
provider_config.username = username
610624
except Exception as e:
611625
print(f"Could not get authenticated user: {e}")
612626
if provider == 'github':
613-
print("Please authenticate with: gh auth login")
627+
print("Please install gh CLI and run: gh auth login")
614628
elif provider == 'gitlab':
615-
print("Please authenticate with: glab auth login")
629+
print("Please install glab CLI and run: glab auth login")
616630
else:
617631
print("Please ensure you have a valid token configured")
618632
return False
@@ -634,7 +648,8 @@ def _initialize_gitfetch(config_manager: ConfigManager) -> bool:
634648
else:
635649
config_manager.set_cache_expiry_minutes(15)
636650

637-
# Save configuration
651+
# Save configuration using new provider config system
652+
config_manager.set_provider_config(provider_config)
638653
config_manager.set_default_username(username)
639654
config_manager.save()
640655

src/gitfetch/config.py

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,13 @@
33
"""
44

55
import configparser
6+
import os
67
from pathlib import Path
78
from typing import Optional
89
import webcolors
910

11+
from .providers import ProviderConfig, PROVIDER_ENV_VARS, PROVIDER_DEFAULT_URLS
12+
1013
class ConfigManager:
1114
"""Manages gitfetch configuration."""
1215

@@ -276,6 +279,69 @@ def set_token(self, token: str) -> None:
276279
self.config['DEFAULT'] = {}
277280
self.config['DEFAULT']['token'] = token
278281

282+
def get_provider_config(self) -> Optional[ProviderConfig]:
283+
"""
284+
Get complete provider configuration with token resolution.
285+
286+
Token resolution chain:
287+
1. Read from provider section
288+
2. Fall back to environment variable
289+
290+
Returns:
291+
ProviderConfig with resolved token, or None if no provider set
292+
"""
293+
provider_name = self.get_provider()
294+
if not provider_name:
295+
return None
296+
297+
section = provider_name # e.g., "github"
298+
299+
# Try provider section first, fall back to DEFAULT for backward compat
300+
if self.config.has_section(section):
301+
username = self.config.get(section, 'username', fallback='')
302+
url = self.config.get(section, 'url', fallback='')
303+
token = self.config.get(section, 'token', fallback='')
304+
else:
305+
# Backward compatibility: read from DEFAULT
306+
username = self.get_default_username() or ''
307+
url = self.get_provider_url() or ''
308+
token = self.get_token() or ''
309+
310+
# Token resolution: config -> env var
311+
if not token:
312+
env_var = PROVIDER_ENV_VARS.get(provider_name, '')
313+
if env_var:
314+
token = os.getenv(env_var, '') or ''
315+
316+
# Use default URL if not specified
317+
if not url:
318+
url = PROVIDER_DEFAULT_URLS.get(provider_name, '')
319+
320+
return ProviderConfig(
321+
name=provider_name,
322+
username=username,
323+
url=url,
324+
token=token
325+
)
326+
327+
def set_provider_config(self, config: ProviderConfig) -> None:
328+
"""
329+
Save provider configuration to its dedicated section.
330+
331+
Args:
332+
config: ProviderConfig to save
333+
"""
334+
section = config.name
335+
if not self.config.has_section(section):
336+
self.config.add_section(section)
337+
338+
self.config.set(section, 'username', config.username)
339+
self.config.set(section, 'url', config.url)
340+
self.config.set(section, 'token', config.token)
341+
342+
# Also set provider in DEFAULT
343+
self.set_provider(config.name)
344+
279345
def save(self) -> None:
280346
"""Save configuration to file."""
281347
import os
@@ -318,6 +384,31 @@ def save(self) -> None:
318384

319385
f.write("\n")
320386

387+
# Write all provider sections (empty if not configured)
388+
# Use known default URLs for providers
389+
known_providers = ['github', 'gitlab', 'gitea', 'sourcehut']
390+
for provider_section in known_providers:
391+
f.write(f"[{provider_section}]\n")
392+
has_section = self.config.has_section(provider_section)
393+
394+
# Username
395+
username = self.config.get(provider_section, 'username',
396+
fallback='') if has_section else ''
397+
f.write(f"username = {username}\n")
398+
399+
# URL - use default if not set
400+
url = self.config.get(provider_section, 'url',
401+
fallback='') if has_section else ''
402+
if not url:
403+
url = PROVIDER_DEFAULT_URLS.get(provider_section, '')
404+
f.write(f"url = {url}\n")
405+
406+
# Token
407+
token = self.config.get(provider_section, 'token',
408+
fallback='') if has_section else ''
409+
f.write(f"token = {token}\n")
410+
f.write("\n")
411+
321412
if 'COLORS' in self.config._sections:
322413
f.write("[COLORS]\n")
323414
# Find the longest key for alignment

0 commit comments

Comments
 (0)