igntui 0.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- igntui/__init__.py +57 -0
- igntui/__main__.py +10 -0
- igntui/app.py +37 -0
- igntui/cli/__init__.py +31 -0
- igntui/cli/base.py +104 -0
- igntui/cli/commands/__init__.py +17 -0
- igntui/cli/commands/cache_cmd.py +95 -0
- igntui/cli/commands/generate_cmd.py +85 -0
- igntui/cli/commands/list_cmd.py +66 -0
- igntui/cli/commands/test_cmd.py +64 -0
- igntui/cli/commands/tui_cmd.py +34 -0
- igntui/cli/parser.py +162 -0
- igntui/cli/setup.py +71 -0
- igntui/core/__init__.py +8 -0
- igntui/core/api/__init__.py +16 -0
- igntui/core/api/client.py +222 -0
- igntui/core/api/errors.py +29 -0
- igntui/core/api/rate_limiter.py +30 -0
- igntui/core/api/request_handler.py +124 -0
- igntui/core/api/response.py +16 -0
- igntui/core/cache.py +284 -0
- igntui/core/config.py +174 -0
- igntui/core/search.py +354 -0
- igntui/main.py +74 -0
- igntui/tui/__init__.py +22 -0
- igntui/tui/actions.py +254 -0
- igntui/tui/app.py +246 -0
- igntui/tui/curses_setup.py +88 -0
- igntui/tui/event_handler.py +310 -0
- igntui/tui/lifecycle.py +102 -0
- igntui/tui/renderer.py +108 -0
- igntui/tui/state.py +77 -0
- igntui/ui/__init__.py +33 -0
- igntui/ui/components/__init__.py +31 -0
- igntui/ui/components/base_dialog.py +75 -0
- igntui/ui/components/base_panel.py +85 -0
- igntui/ui/components/confirm_dialog.py +100 -0
- igntui/ui/components/content_panel.py +77 -0
- igntui/ui/components/export_dialog.py +139 -0
- igntui/ui/components/help_dialog.py +161 -0
- igntui/ui/components/save_dialog.py +109 -0
- igntui/ui/components/search_panel.py +52 -0
- igntui/ui/components/selected_panel.py +107 -0
- igntui/ui/components/splash.py +236 -0
- igntui/ui/components/status_bar.py +78 -0
- igntui/ui/components/templates_panel.py +153 -0
- igntui/utils/__init__.py +19 -0
- igntui/utils/logging.py +288 -0
- igntui-0.0.0.dist-info/METADATA +425 -0
- igntui-0.0.0.dist-info/RECORD +52 -0
- igntui-0.0.0.dist-info/WHEEL +4 -0
- igntui-0.0.0.dist-info/entry_points.txt +4 -0
igntui/__init__.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
import platform
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
__version__ = "0.0.0"
|
|
9
|
+
__author__ = "Mohammad Abu Mattar"
|
|
10
|
+
__email__ = "info@mkabumattar.com"
|
|
11
|
+
__description__ = """A professional TUI application for generating .gitignore files from templates
|
|
12
|
+
provided by gitignore.io with advanced search, caching, and performance features.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def get_version_string() -> str:
|
|
17
|
+
python_version = (
|
|
18
|
+
f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
|
|
19
|
+
)
|
|
20
|
+
os_name = platform.system()
|
|
21
|
+
os_version = platform.release()
|
|
22
|
+
return f"igntui/{__version__} Python/{python_version} {os_name}/{os_version}"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
from .core.api import GitIgnoreAPI, APIResponse
|
|
26
|
+
from .core.config import config
|
|
27
|
+
from .core.search import SearchManager, SearchMode
|
|
28
|
+
from .core.cache import CacheManager, TemplateCache
|
|
29
|
+
|
|
30
|
+
from .main import cli_main, tui_main
|
|
31
|
+
|
|
32
|
+
try:
|
|
33
|
+
from .app import run_tui
|
|
34
|
+
|
|
35
|
+
TUI_AVAILABLE = True
|
|
36
|
+
except ImportError:
|
|
37
|
+
run_tui = None
|
|
38
|
+
TUI_AVAILABLE = False
|
|
39
|
+
|
|
40
|
+
__all__ = [
|
|
41
|
+
"__version__",
|
|
42
|
+
"__author__",
|
|
43
|
+
"__email__",
|
|
44
|
+
"__description__",
|
|
45
|
+
"get_version_string",
|
|
46
|
+
"GitIgnoreAPI",
|
|
47
|
+
"APIResponse",
|
|
48
|
+
"config",
|
|
49
|
+
"SearchManager",
|
|
50
|
+
"SearchMode",
|
|
51
|
+
"CacheManager",
|
|
52
|
+
"TemplateCache",
|
|
53
|
+
"cli_main",
|
|
54
|
+
"tui_main",
|
|
55
|
+
"run_tui",
|
|
56
|
+
"TUI_AVAILABLE",
|
|
57
|
+
]
|
igntui/__main__.py
ADDED
igntui/app.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
import curses
|
|
6
|
+
import sys
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def run_tui(show_splash: bool = True) -> int:
|
|
11
|
+
try:
|
|
12
|
+
from .tui import GitIgnoreTUI
|
|
13
|
+
|
|
14
|
+
def main(stdscr):
|
|
15
|
+
tui = GitIgnoreTUI(stdscr, show_splash=show_splash)
|
|
16
|
+
return tui.run()
|
|
17
|
+
|
|
18
|
+
exit_code = curses.wrapper(main)
|
|
19
|
+
return exit_code if exit_code is not None else 0
|
|
20
|
+
|
|
21
|
+
except ImportError as e:
|
|
22
|
+
if "curses" in str(e):
|
|
23
|
+
print("Error: curses module not available.")
|
|
24
|
+
print("On Windows, install: pip install windows-curses")
|
|
25
|
+
return 1
|
|
26
|
+
else:
|
|
27
|
+
raise
|
|
28
|
+
except KeyboardInterrupt:
|
|
29
|
+
print("\nOperation cancelled by user")
|
|
30
|
+
return 1
|
|
31
|
+
except Exception as e:
|
|
32
|
+
print(f"Error running TUI: {e}")
|
|
33
|
+
return 1
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
if __name__ == "__main__":
|
|
37
|
+
sys.exit(run_tui())
|
igntui/cli/__init__.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
from .base import BaseCLI, CLICommand, safe_exit
|
|
6
|
+
from .setup import setup_logging, check_curses_availability, print_curses_error
|
|
7
|
+
from .parser import create_base_parser, create_command_parser, get_command_instance
|
|
8
|
+
from .commands import (
|
|
9
|
+
ListCommand,
|
|
10
|
+
GenerateCommand,
|
|
11
|
+
TUICommand,
|
|
12
|
+
CacheCommand,
|
|
13
|
+
TestCommand,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"BaseCLI",
|
|
18
|
+
"CLICommand",
|
|
19
|
+
"safe_exit",
|
|
20
|
+
"setup_logging",
|
|
21
|
+
"check_curses_availability",
|
|
22
|
+
"print_curses_error",
|
|
23
|
+
"create_base_parser",
|
|
24
|
+
"create_command_parser",
|
|
25
|
+
"get_command_instance",
|
|
26
|
+
"ListCommand",
|
|
27
|
+
"GenerateCommand",
|
|
28
|
+
"TUICommand",
|
|
29
|
+
"CacheCommand",
|
|
30
|
+
"TestCommand",
|
|
31
|
+
]
|
igntui/cli/base.py
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import logging
|
|
7
|
+
import sys
|
|
8
|
+
from typing import List, Optional
|
|
9
|
+
|
|
10
|
+
from .. import __version__, get_version_string
|
|
11
|
+
from ..core.api import GitIgnoreAPI
|
|
12
|
+
from ..core.config import Config
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class BaseCLI:
|
|
18
|
+
def __init__(self):
|
|
19
|
+
self.config = Config()
|
|
20
|
+
self.api = GitIgnoreAPI()
|
|
21
|
+
|
|
22
|
+
def setup_logging(self, verbose: bool = False):
|
|
23
|
+
level = logging.DEBUG if verbose else logging.INFO
|
|
24
|
+
logging.basicConfig(
|
|
25
|
+
level=level, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
def check_terminal_requirements(self) -> bool:
|
|
29
|
+
try:
|
|
30
|
+
import curses
|
|
31
|
+
|
|
32
|
+
stdscr = curses.initscr()
|
|
33
|
+
curses.endwin()
|
|
34
|
+
return True
|
|
35
|
+
except ImportError:
|
|
36
|
+
print("Error: curses module not available.")
|
|
37
|
+
print("On Windows, install: pip install windows-curses")
|
|
38
|
+
return False
|
|
39
|
+
except Exception as e:
|
|
40
|
+
print(f"Error: Terminal not compatible with curses: {e}")
|
|
41
|
+
return False
|
|
42
|
+
|
|
43
|
+
def handle_api_error(self, error: Exception) -> None:
|
|
44
|
+
if "connection" in str(error).lower() or "timeout" in str(error).lower():
|
|
45
|
+
print("Error: Cannot connect to gitignore.io API")
|
|
46
|
+
print("Please check your internet connection and try again.")
|
|
47
|
+
elif "not found" in str(error).lower() or "404" in str(error):
|
|
48
|
+
print("Error: Template not found")
|
|
49
|
+
print("Use 'igntui list' to see available templates.")
|
|
50
|
+
else:
|
|
51
|
+
print(f"Error: {error}")
|
|
52
|
+
|
|
53
|
+
logger.error(f"API error: {error}")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class CLICommand:
|
|
57
|
+
def __init__(self, cli: BaseCLI):
|
|
58
|
+
self.cli = cli
|
|
59
|
+
|
|
60
|
+
def add_arguments(self, parser: argparse.ArgumentParser) -> None:
|
|
61
|
+
raise NotImplementedError
|
|
62
|
+
|
|
63
|
+
def execute(self, args: argparse.Namespace) -> int:
|
|
64
|
+
raise NotImplementedError
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def create_base_parser() -> argparse.ArgumentParser:
|
|
68
|
+
parser = argparse.ArgumentParser(
|
|
69
|
+
prog="igntui",
|
|
70
|
+
description="Interactive TUI for generating .gitignore files from gitignore.io templates",
|
|
71
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
72
|
+
epilog="""
|
|
73
|
+
Examples:
|
|
74
|
+
igntui # Launch interactive TUI
|
|
75
|
+
igntui list # List available templates
|
|
76
|
+
igntui generate python # Generate .gitignore for Python
|
|
77
|
+
igntui test # Test API connection
|
|
78
|
+
|
|
79
|
+
For more information, visit: https://github.com/toptal/gitignore.io
|
|
80
|
+
""",
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
parser.add_argument("--version", action="version", version=get_version_string())
|
|
84
|
+
|
|
85
|
+
parser.add_argument(
|
|
86
|
+
"-v", "--verbose", action="store_true", help="Enable verbose output"
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
parser.add_argument(
|
|
90
|
+
"--no-color", action="store_true", help="Disable colored output"
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
return parser
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def safe_exit(code: int = 0) -> None:
|
|
97
|
+
try:
|
|
98
|
+
import curses
|
|
99
|
+
|
|
100
|
+
curses.endwin()
|
|
101
|
+
except:
|
|
102
|
+
pass
|
|
103
|
+
|
|
104
|
+
sys.exit(code)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
from .list_cmd import ListCommand
|
|
6
|
+
from .generate_cmd import GenerateCommand
|
|
7
|
+
from .tui_cmd import TUICommand
|
|
8
|
+
from .cache_cmd import CacheCommand
|
|
9
|
+
from .test_cmd import TestCommand
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"ListCommand",
|
|
13
|
+
"GenerateCommand",
|
|
14
|
+
"TUICommand",
|
|
15
|
+
"CacheCommand",
|
|
16
|
+
"TestCommand",
|
|
17
|
+
]
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
|
|
8
|
+
from ..base import CLICommand
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class CacheCommand(CLICommand):
|
|
12
|
+
def add_arguments(self, parser: argparse.ArgumentParser) -> None:
|
|
13
|
+
subparsers = parser.add_subparsers(dest="cache_action", help="Cache action")
|
|
14
|
+
|
|
15
|
+
clear_parser = subparsers.add_parser("clear", help="Clear cache")
|
|
16
|
+
clear_parser.add_argument(
|
|
17
|
+
"--force", action="store_true", help="Skip confirmation prompt"
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
subparsers.add_parser("stats", help="Show cache statistics")
|
|
21
|
+
subparsers.add_parser("info", help="Show cache information")
|
|
22
|
+
|
|
23
|
+
def execute(self, args: argparse.Namespace) -> int:
|
|
24
|
+
try:
|
|
25
|
+
from ...core.cache import CacheManager
|
|
26
|
+
from ...core.config import config
|
|
27
|
+
|
|
28
|
+
cache_dir = config.get_cache_dir()
|
|
29
|
+
cache = CacheManager(cache_dir=str(cache_dir))
|
|
30
|
+
|
|
31
|
+
if not args.cache_action or args.cache_action == "info":
|
|
32
|
+
return self._show_info(cache)
|
|
33
|
+
elif args.cache_action == "stats":
|
|
34
|
+
return self._show_stats(cache)
|
|
35
|
+
elif args.cache_action == "clear":
|
|
36
|
+
return self._clear_cache(
|
|
37
|
+
cache, args.force if hasattr(args, "force") else False
|
|
38
|
+
)
|
|
39
|
+
else:
|
|
40
|
+
print(f"Unknown cache action: {args.cache_action}")
|
|
41
|
+
return 1
|
|
42
|
+
|
|
43
|
+
except Exception as e:
|
|
44
|
+
print(f"Error managing cache: {e}")
|
|
45
|
+
if hasattr(args, "verbose") and args.verbose:
|
|
46
|
+
import traceback
|
|
47
|
+
|
|
48
|
+
traceback.print_exc()
|
|
49
|
+
return 1
|
|
50
|
+
|
|
51
|
+
def _show_info(self, cache: "CacheManager") -> int:
|
|
52
|
+
print("Cache Information:")
|
|
53
|
+
print(f" Location: {cache.cache_dir}")
|
|
54
|
+
print(f" TTL: {cache.default_ttl} seconds")
|
|
55
|
+
|
|
56
|
+
try:
|
|
57
|
+
from pathlib import Path
|
|
58
|
+
|
|
59
|
+
template_cache_file = Path(cache.cache_dir) / "templates.json"
|
|
60
|
+
if template_cache_file.exists():
|
|
61
|
+
import json
|
|
62
|
+
|
|
63
|
+
with open(template_cache_file, "r") as f:
|
|
64
|
+
data = json.load(f)
|
|
65
|
+
templates = data.get("templates", [])
|
|
66
|
+
print(f" Template cache: Valid")
|
|
67
|
+
print(f" Templates cached: {len(templates)}")
|
|
68
|
+
else:
|
|
69
|
+
print(f" Template cache: Not found")
|
|
70
|
+
except Exception as e:
|
|
71
|
+
print(f" Template cache: Error reading ({e})")
|
|
72
|
+
|
|
73
|
+
return 0
|
|
74
|
+
|
|
75
|
+
def _show_stats(self, cache: "CacheManager") -> int:
|
|
76
|
+
stats = cache.get_stats()
|
|
77
|
+
|
|
78
|
+
print("Cache Statistics:")
|
|
79
|
+
for key, value in stats.items():
|
|
80
|
+
if isinstance(value, datetime):
|
|
81
|
+
value = value.strftime("%Y-%m-%d %H:%M:%S")
|
|
82
|
+
print(f" {key}: {value}")
|
|
83
|
+
|
|
84
|
+
return 0
|
|
85
|
+
|
|
86
|
+
def _clear_cache(self, cache: "CacheManager", force: bool = False) -> int:
|
|
87
|
+
if not force:
|
|
88
|
+
response = input("Clear cache? This will remove all cached data. (y/N): ")
|
|
89
|
+
if response.lower() not in ["y", "yes"]:
|
|
90
|
+
print("Cache clear cancelled")
|
|
91
|
+
return 1
|
|
92
|
+
|
|
93
|
+
cache.clear_all()
|
|
94
|
+
print("Cache cleared successfully")
|
|
95
|
+
return 0
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import os
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from ..base import CLICommand
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class GenerateCommand(CLICommand):
|
|
13
|
+
def add_arguments(self, parser: argparse.ArgumentParser) -> None:
|
|
14
|
+
parser.add_argument(
|
|
15
|
+
"templates", nargs="+", help="Template names to generate (space-separated)"
|
|
16
|
+
)
|
|
17
|
+
parser.add_argument(
|
|
18
|
+
"--output",
|
|
19
|
+
"-o",
|
|
20
|
+
type=Path,
|
|
21
|
+
metavar="FILE",
|
|
22
|
+
help="Output file path (default: print to stdout)",
|
|
23
|
+
)
|
|
24
|
+
parser.add_argument(
|
|
25
|
+
"--append",
|
|
26
|
+
"-a",
|
|
27
|
+
action="store_true",
|
|
28
|
+
help="Append to existing file instead of overwriting",
|
|
29
|
+
)
|
|
30
|
+
parser.add_argument(
|
|
31
|
+
"--force",
|
|
32
|
+
"-f",
|
|
33
|
+
action="store_true",
|
|
34
|
+
help="Force overwrite without confirmation",
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
def execute(self, args: argparse.Namespace) -> int:
|
|
38
|
+
try:
|
|
39
|
+
if hasattr(args, "verbose") and args.verbose:
|
|
40
|
+
print(f"Generating .gitignore for: {', '.join(args.templates)}")
|
|
41
|
+
|
|
42
|
+
response = self.cli.api.get_templates(args.templates)
|
|
43
|
+
|
|
44
|
+
if not response.success:
|
|
45
|
+
print(f"Error: {response.error_message}")
|
|
46
|
+
return 1
|
|
47
|
+
|
|
48
|
+
content = response.data
|
|
49
|
+
|
|
50
|
+
if not content.strip():
|
|
51
|
+
print("Warning: Generated content is empty")
|
|
52
|
+
return 1
|
|
53
|
+
|
|
54
|
+
if not args.output:
|
|
55
|
+
print(content)
|
|
56
|
+
return 0
|
|
57
|
+
|
|
58
|
+
output_file = args.output
|
|
59
|
+
|
|
60
|
+
if output_file.exists() and not args.append and not args.force:
|
|
61
|
+
response_text = input(
|
|
62
|
+
f"File '{output_file}' exists. Overwrite? (y/N): "
|
|
63
|
+
)
|
|
64
|
+
if response_text.lower() not in ("y", "yes"):
|
|
65
|
+
print("Operation cancelled")
|
|
66
|
+
return 1
|
|
67
|
+
|
|
68
|
+
mode = "a" if args.append else "w"
|
|
69
|
+
try:
|
|
70
|
+
with open(output_file, mode, encoding="utf-8") as f:
|
|
71
|
+
if args.append and output_file.stat().st_size > 0:
|
|
72
|
+
f.write("\n\n# Generated by igntui\n")
|
|
73
|
+
f.write(content)
|
|
74
|
+
|
|
75
|
+
action = "Appended to" if args.append else "Generated"
|
|
76
|
+
print(f"✓ {action} {output_file}")
|
|
77
|
+
return 0
|
|
78
|
+
|
|
79
|
+
except IOError as e:
|
|
80
|
+
print(f"Error writing file: {e}")
|
|
81
|
+
return 1
|
|
82
|
+
|
|
83
|
+
except Exception as e:
|
|
84
|
+
self.cli.handle_api_error(e)
|
|
85
|
+
return 1
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
|
|
7
|
+
from ..base import CLICommand
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ListCommand(CLICommand):
|
|
11
|
+
def add_arguments(self, parser: argparse.ArgumentParser) -> None:
|
|
12
|
+
parser.add_argument(
|
|
13
|
+
"--filter", "-f", metavar="PATTERN", help="Filter templates by pattern"
|
|
14
|
+
)
|
|
15
|
+
parser.add_argument(
|
|
16
|
+
"--count", "-c", action="store_true", help="Show count only"
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
def execute(self, args: argparse.Namespace) -> int:
|
|
20
|
+
try:
|
|
21
|
+
if hasattr(args, "verbose") and args.verbose:
|
|
22
|
+
print("Fetching templates from gitignore.io...")
|
|
23
|
+
|
|
24
|
+
response = self.cli.api.list_templates()
|
|
25
|
+
|
|
26
|
+
if not response.success:
|
|
27
|
+
print(f"Error: {response.error_message}")
|
|
28
|
+
return 1
|
|
29
|
+
|
|
30
|
+
templates = response.data
|
|
31
|
+
|
|
32
|
+
if hasattr(args, "filter") and args.filter:
|
|
33
|
+
pattern = args.filter.lower()
|
|
34
|
+
templates = [t for t in templates if pattern in t.lower()]
|
|
35
|
+
|
|
36
|
+
if hasattr(args, "count") and args.count:
|
|
37
|
+
print(f"Found {len(templates)} templates")
|
|
38
|
+
return 0
|
|
39
|
+
|
|
40
|
+
if not templates:
|
|
41
|
+
if hasattr(args, "filter") and args.filter:
|
|
42
|
+
print(f"No templates found matching '{args.filter}'")
|
|
43
|
+
else:
|
|
44
|
+
print("No templates found")
|
|
45
|
+
return 1
|
|
46
|
+
|
|
47
|
+
print(f"Available templates ({len(templates)}):")
|
|
48
|
+
print()
|
|
49
|
+
|
|
50
|
+
max_width = 80
|
|
51
|
+
col_width = max(len(t) for t in templates) + 2
|
|
52
|
+
cols = max(1, max_width // col_width)
|
|
53
|
+
|
|
54
|
+
for i, template in enumerate(templates):
|
|
55
|
+
print(f"{template:<{col_width}}", end="")
|
|
56
|
+
if (i + 1) % cols == 0:
|
|
57
|
+
print()
|
|
58
|
+
|
|
59
|
+
if len(templates) % cols != 0:
|
|
60
|
+
print()
|
|
61
|
+
|
|
62
|
+
return 0
|
|
63
|
+
|
|
64
|
+
except Exception as e:
|
|
65
|
+
self.cli.handle_api_error(e)
|
|
66
|
+
return 1
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
from ..base import CLICommand
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TestCommand(CLICommand):
|
|
12
|
+
def add_arguments(self, parser: argparse.ArgumentParser) -> None:
|
|
13
|
+
parser.add_argument(
|
|
14
|
+
"--timeout",
|
|
15
|
+
type=int,
|
|
16
|
+
default=10,
|
|
17
|
+
help="Connection timeout in seconds (default: 10)",
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
def execute(self, args: argparse.Namespace) -> int:
|
|
21
|
+
try:
|
|
22
|
+
from ...core.api import GitIgnoreAPI
|
|
23
|
+
|
|
24
|
+
print("Testing connection to gitignore.io API...")
|
|
25
|
+
print(f"Timeout: {args.timeout} seconds")
|
|
26
|
+
print()
|
|
27
|
+
|
|
28
|
+
api = GitIgnoreAPI()
|
|
29
|
+
|
|
30
|
+
print("Attempting to connect...", end="", flush=True)
|
|
31
|
+
response = api.test_connection()
|
|
32
|
+
|
|
33
|
+
if not response.success:
|
|
34
|
+
print(" FAILED")
|
|
35
|
+
print(f"Error: {response.error_message}")
|
|
36
|
+
return 1
|
|
37
|
+
|
|
38
|
+
print(" SUCCESS")
|
|
39
|
+
print()
|
|
40
|
+
|
|
41
|
+
print("API Response:")
|
|
42
|
+
response_time = (
|
|
43
|
+
response.response_time if response.response_time is not None else 0.0
|
|
44
|
+
)
|
|
45
|
+
print(f" Response time: {response_time:.3f}s")
|
|
46
|
+
print(f" From cache: {'Yes' if response.from_cache else 'No'}")
|
|
47
|
+
print(
|
|
48
|
+
f" Status code: {response.status_code if response.status_code else 'N/A'}"
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
print()
|
|
52
|
+
print("✓ API is working correctly")
|
|
53
|
+
return 0
|
|
54
|
+
|
|
55
|
+
except KeyboardInterrupt:
|
|
56
|
+
print("\n\nOperation cancelled by user")
|
|
57
|
+
return 1
|
|
58
|
+
except Exception as e:
|
|
59
|
+
print(f"\n\nError testing API: {e}")
|
|
60
|
+
if hasattr(args, "verbose") and args.verbose:
|
|
61
|
+
import traceback
|
|
62
|
+
|
|
63
|
+
traceback.print_exc()
|
|
64
|
+
return 1
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
|
|
7
|
+
from ..base import CLICommand
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TUICommand(CLICommand):
|
|
11
|
+
def add_arguments(self, parser: argparse.ArgumentParser) -> None:
|
|
12
|
+
parser.add_argument(
|
|
13
|
+
"--no-splash", action="store_true", help="Skip splash screen on startup"
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
def execute(self, args: argparse.Namespace) -> int:
|
|
17
|
+
if not self.cli.check_terminal_requirements():
|
|
18
|
+
return 1
|
|
19
|
+
|
|
20
|
+
try:
|
|
21
|
+
from ...app import run_tui
|
|
22
|
+
|
|
23
|
+
return run_tui(show_splash=not args.no_splash)
|
|
24
|
+
|
|
25
|
+
except KeyboardInterrupt:
|
|
26
|
+
print("\nOperation cancelled by user")
|
|
27
|
+
return 1
|
|
28
|
+
except Exception as e:
|
|
29
|
+
print(f"Error launching TUI: {e}")
|
|
30
|
+
if hasattr(args, "verbose") and args.verbose:
|
|
31
|
+
import traceback
|
|
32
|
+
|
|
33
|
+
traceback.print_exc()
|
|
34
|
+
return 1
|