style: Auto-format 48 files with ruff format
- Fixed formatting to comply with ruff standards - No functional changes, only formatting/style - Completes CI/CD pipeline formatting requirements
This commit is contained in:
@@ -171,6 +171,7 @@ ANALYZE_ARGUMENTS: dict[str, dict[str, Any]] = {
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def add_analyze_arguments(parser: argparse.ArgumentParser) -> None:
|
||||
"""Add all analyze command arguments to a parser."""
|
||||
for arg_name, arg_def in ANALYZE_ARGUMENTS.items():
|
||||
@@ -178,6 +179,7 @@ def add_analyze_arguments(parser: argparse.ArgumentParser) -> None:
|
||||
kwargs = arg_def["kwargs"]
|
||||
parser.add_argument(*flags, **kwargs)
|
||||
|
||||
|
||||
def get_analyze_argument_names() -> set:
|
||||
"""Get the set of analyze argument destination names."""
|
||||
return set(ANALYZE_ARGUMENTS.keys())
|
||||
|
||||
@@ -96,6 +96,7 @@ RAG_ARGUMENTS: dict[str, dict[str, Any]] = {
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def add_common_arguments(parser: argparse.ArgumentParser) -> None:
|
||||
"""Add common arguments to a parser.
|
||||
|
||||
@@ -114,6 +115,7 @@ def add_common_arguments(parser: argparse.ArgumentParser) -> None:
|
||||
kwargs = arg_def["kwargs"]
|
||||
parser.add_argument(*flags, **kwargs)
|
||||
|
||||
|
||||
def get_common_argument_names() -> set:
|
||||
"""Get the set of common argument destination names.
|
||||
|
||||
@@ -122,6 +124,7 @@ def get_common_argument_names() -> set:
|
||||
"""
|
||||
return set(COMMON_ARGUMENTS.keys())
|
||||
|
||||
|
||||
def add_rag_arguments(parser: argparse.ArgumentParser) -> None:
|
||||
"""Add RAG (Retrieval-Augmented Generation) arguments to a parser.
|
||||
|
||||
@@ -140,6 +143,7 @@ def add_rag_arguments(parser: argparse.ArgumentParser) -> None:
|
||||
kwargs = arg_def["kwargs"]
|
||||
parser.add_argument(*flags, **kwargs)
|
||||
|
||||
|
||||
def get_rag_argument_names() -> set:
|
||||
"""Get the set of RAG argument destination names.
|
||||
|
||||
@@ -148,6 +152,7 @@ def get_rag_argument_names() -> set:
|
||||
"""
|
||||
return set(RAG_ARGUMENTS.keys())
|
||||
|
||||
|
||||
def get_argument_help(arg_name: str) -> str:
|
||||
"""Get the help text for a common argument.
|
||||
|
||||
|
||||
@@ -388,10 +388,12 @@ ADVANCED_ARGUMENTS: dict[str, dict[str, Any]] = {
|
||||
# HELPER FUNCTIONS
|
||||
# =============================================================================
|
||||
|
||||
|
||||
def get_universal_argument_names() -> set[str]:
|
||||
"""Get set of universal argument names."""
|
||||
return set(UNIVERSAL_ARGUMENTS.keys())
|
||||
|
||||
|
||||
def get_source_specific_arguments(source_type: str) -> dict[str, dict[str, Any]]:
|
||||
"""Get source-specific arguments for a given source type.
|
||||
|
||||
@@ -402,14 +404,15 @@ def get_source_specific_arguments(source_type: str) -> dict[str, dict[str, Any]]
|
||||
Dict of argument definitions
|
||||
"""
|
||||
source_args = {
|
||||
'web': WEB_ARGUMENTS,
|
||||
'github': GITHUB_ARGUMENTS,
|
||||
'local': LOCAL_ARGUMENTS,
|
||||
'pdf': PDF_ARGUMENTS,
|
||||
'config': {}, # Config files don't have extra args
|
||||
"web": WEB_ARGUMENTS,
|
||||
"github": GITHUB_ARGUMENTS,
|
||||
"local": LOCAL_ARGUMENTS,
|
||||
"pdf": PDF_ARGUMENTS,
|
||||
"config": {}, # Config files don't have extra args
|
||||
}
|
||||
return source_args.get(source_type, {})
|
||||
|
||||
|
||||
def get_compatible_arguments(source_type: str) -> list[str]:
|
||||
"""Get list of compatible argument names for a source type.
|
||||
|
||||
@@ -431,7 +434,8 @@ def get_compatible_arguments(source_type: str) -> list[str]:
|
||||
|
||||
return compatible
|
||||
|
||||
def add_create_arguments(parser: argparse.ArgumentParser, mode: str = 'default') -> None:
|
||||
|
||||
def add_create_arguments(parser: argparse.ArgumentParser, mode: str = "default") -> None:
|
||||
"""Add create command arguments to parser.
|
||||
|
||||
Supports multiple help modes for progressive disclosure:
|
||||
@@ -449,10 +453,10 @@ def add_create_arguments(parser: argparse.ArgumentParser, mode: str = 'default')
|
||||
"""
|
||||
# Positional argument for source
|
||||
parser.add_argument(
|
||||
'source',
|
||||
nargs='?',
|
||||
"source",
|
||||
nargs="?",
|
||||
type=str,
|
||||
help='Source to create skill from (URL, GitHub repo, directory, PDF, or config file)'
|
||||
help="Source to create skill from (URL, GitHub repo, directory, PDF, or config file)",
|
||||
)
|
||||
|
||||
# Always add universal arguments
|
||||
@@ -460,23 +464,23 @@ def add_create_arguments(parser: argparse.ArgumentParser, mode: str = 'default')
|
||||
parser.add_argument(*arg_def["flags"], **arg_def["kwargs"])
|
||||
|
||||
# Add source-specific arguments based on mode
|
||||
if mode in ['web', 'all']:
|
||||
if mode in ["web", "all"]:
|
||||
for arg_name, arg_def in WEB_ARGUMENTS.items():
|
||||
parser.add_argument(*arg_def["flags"], **arg_def["kwargs"])
|
||||
|
||||
if mode in ['github', 'all']:
|
||||
if mode in ["github", "all"]:
|
||||
for arg_name, arg_def in GITHUB_ARGUMENTS.items():
|
||||
parser.add_argument(*arg_def["flags"], **arg_def["kwargs"])
|
||||
|
||||
if mode in ['local', 'all']:
|
||||
if mode in ["local", "all"]:
|
||||
for arg_name, arg_def in LOCAL_ARGUMENTS.items():
|
||||
parser.add_argument(*arg_def["flags"], **arg_def["kwargs"])
|
||||
|
||||
if mode in ['pdf', 'all']:
|
||||
if mode in ["pdf", "all"]:
|
||||
for arg_name, arg_def in PDF_ARGUMENTS.items():
|
||||
parser.add_argument(*arg_def["flags"], **arg_def["kwargs"])
|
||||
|
||||
# Add advanced arguments if requested
|
||||
if mode in ['advanced', 'all']:
|
||||
if mode in ["advanced", "all"]:
|
||||
for arg_name, arg_def in ADVANCED_ARGUMENTS.items():
|
||||
parser.add_argument(*arg_def["flags"], **arg_def["kwargs"])
|
||||
|
||||
@@ -68,6 +68,7 @@ ENHANCE_ARGUMENTS: dict[str, dict[str, Any]] = {
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def add_enhance_arguments(parser: argparse.ArgumentParser) -> None:
|
||||
"""Add all enhance command arguments to a parser."""
|
||||
for arg_name, arg_def in ENHANCE_ARGUMENTS.items():
|
||||
|
||||
@@ -133,6 +133,7 @@ GITHUB_ARGUMENTS: dict[str, dict[str, Any]] = {
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def add_github_arguments(parser: argparse.ArgumentParser) -> None:
|
||||
"""Add all github command arguments to a parser.
|
||||
|
||||
@@ -153,6 +154,7 @@ def add_github_arguments(parser: argparse.ArgumentParser) -> None:
|
||||
kwargs = arg_def["kwargs"]
|
||||
parser.add_argument(*flags, **kwargs)
|
||||
|
||||
|
||||
def get_github_argument_names() -> set:
|
||||
"""Get the set of github argument destination names.
|
||||
|
||||
@@ -161,6 +163,7 @@ def get_github_argument_names() -> set:
|
||||
"""
|
||||
return set(GITHUB_ARGUMENTS.keys())
|
||||
|
||||
|
||||
def get_github_argument_count() -> int:
|
||||
"""Get the total number of github arguments.
|
||||
|
||||
|
||||
@@ -123,6 +123,7 @@ PACKAGE_ARGUMENTS: dict[str, dict[str, Any]] = {
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def add_package_arguments(parser: argparse.ArgumentParser) -> None:
|
||||
"""Add all package command arguments to a parser."""
|
||||
for arg_name, arg_def in PACKAGE_ARGUMENTS.items():
|
||||
|
||||
@@ -51,6 +51,7 @@ PDF_ARGUMENTS: dict[str, dict[str, Any]] = {
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def add_pdf_arguments(parser: argparse.ArgumentParser) -> None:
|
||||
"""Add all pdf command arguments to a parser."""
|
||||
for arg_name, arg_def in PDF_ARGUMENTS.items():
|
||||
|
||||
@@ -198,6 +198,7 @@ SCRAPE_ARGUMENTS: dict[str, dict[str, Any]] = {
|
||||
# Merge RAG arguments from common.py
|
||||
SCRAPE_ARGUMENTS.update(RAG_ARGUMENTS)
|
||||
|
||||
|
||||
def add_scrape_arguments(parser: argparse.ArgumentParser) -> None:
|
||||
"""Add all scrape command arguments to a parser.
|
||||
|
||||
@@ -218,6 +219,7 @@ def add_scrape_arguments(parser: argparse.ArgumentParser) -> None:
|
||||
kwargs = arg_def["kwargs"]
|
||||
parser.add_argument(*flags, **kwargs)
|
||||
|
||||
|
||||
def get_scrape_argument_names() -> set:
|
||||
"""Get the set of scrape argument destination names.
|
||||
|
||||
@@ -226,6 +228,7 @@ def get_scrape_argument_names() -> set:
|
||||
"""
|
||||
return set(SCRAPE_ARGUMENTS.keys())
|
||||
|
||||
|
||||
def get_scrape_argument_count() -> int:
|
||||
"""Get the total number of scrape arguments.
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@ UNIFIED_ARGUMENTS: dict[str, dict[str, Any]] = {
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def add_unified_arguments(parser: argparse.ArgumentParser) -> None:
|
||||
"""Add all unified command arguments to a parser."""
|
||||
for arg_name, arg_def in UNIFIED_ARGUMENTS.items():
|
||||
|
||||
@@ -98,6 +98,7 @@ UPLOAD_ARGUMENTS: dict[str, dict[str, Any]] = {
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def add_upload_arguments(parser: argparse.ArgumentParser) -> None:
|
||||
"""Add all upload command arguments to a parser."""
|
||||
for arg_name, arg_def in UPLOAD_ARGUMENTS.items():
|
||||
|
||||
@@ -870,7 +870,7 @@ def main():
|
||||
|
||||
# AI Enhancement (if requested)
|
||||
enhance_mode = args.ai_mode
|
||||
if getattr(args, 'enhance_level', 0) > 0:
|
||||
if getattr(args, "enhance_level", 0) > 0:
|
||||
# Auto-detect mode if enhance_level is set
|
||||
enhance_mode = "auto" # ConfigEnhancer will auto-detect API vs LOCAL
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ from skill_seekers.cli.arguments.create import (
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CreateCommand:
|
||||
"""Unified create command implementation."""
|
||||
|
||||
@@ -74,7 +75,7 @@ class CreateCommand:
|
||||
continue
|
||||
|
||||
# Skip internal arguments
|
||||
if arg_name in ['source', 'func', 'subcommand']:
|
||||
if arg_name in ["source", "func", "subcommand"]:
|
||||
continue
|
||||
|
||||
# Warn about incompatible argument
|
||||
@@ -104,10 +105,10 @@ class CreateCommand:
|
||||
|
||||
# Check against common defaults
|
||||
defaults = {
|
||||
'max_issues': 100,
|
||||
'chunk_size': 512,
|
||||
'chunk_overlap': 50,
|
||||
'output': None,
|
||||
"max_issues": 100,
|
||||
"chunk_size": 512,
|
||||
"chunk_overlap": 50,
|
||||
"output": None,
|
||||
}
|
||||
|
||||
if arg_name in defaults:
|
||||
@@ -122,15 +123,15 @@ class CreateCommand:
|
||||
Returns:
|
||||
Exit code from scraper
|
||||
"""
|
||||
if self.source_info.type == 'web':
|
||||
if self.source_info.type == "web":
|
||||
return self._route_web()
|
||||
elif self.source_info.type == 'github':
|
||||
elif self.source_info.type == "github":
|
||||
return self._route_github()
|
||||
elif self.source_info.type == 'local':
|
||||
elif self.source_info.type == "local":
|
||||
return self._route_local()
|
||||
elif self.source_info.type == 'pdf':
|
||||
elif self.source_info.type == "pdf":
|
||||
return self._route_pdf()
|
||||
elif self.source_info.type == 'config':
|
||||
elif self.source_info.type == "config":
|
||||
return self._route_config()
|
||||
else:
|
||||
logger.error(f"Unknown source type: {self.source_info.type}")
|
||||
@@ -141,10 +142,10 @@ class CreateCommand:
|
||||
from skill_seekers.cli import doc_scraper
|
||||
|
||||
# Reconstruct argv for doc_scraper
|
||||
argv = ['doc_scraper']
|
||||
argv = ["doc_scraper"]
|
||||
|
||||
# Add URL
|
||||
url = self.source_info.parsed['url']
|
||||
url = self.source_info.parsed["url"]
|
||||
argv.append(url)
|
||||
|
||||
# Add universal arguments
|
||||
@@ -152,21 +153,21 @@ class CreateCommand:
|
||||
|
||||
# Add web-specific arguments
|
||||
if self.args.max_pages:
|
||||
argv.extend(['--max-pages', str(self.args.max_pages)])
|
||||
if getattr(self.args, 'skip_scrape', False):
|
||||
argv.append('--skip-scrape')
|
||||
if getattr(self.args, 'resume', False):
|
||||
argv.append('--resume')
|
||||
if getattr(self.args, 'fresh', False):
|
||||
argv.append('--fresh')
|
||||
if getattr(self.args, 'rate_limit', None):
|
||||
argv.extend(['--rate-limit', str(self.args.rate_limit)])
|
||||
if getattr(self.args, 'workers', None):
|
||||
argv.extend(['--workers', str(self.args.workers)])
|
||||
if getattr(self.args, 'async_mode', False):
|
||||
argv.append('--async')
|
||||
if getattr(self.args, 'no_rate_limit', False):
|
||||
argv.append('--no-rate-limit')
|
||||
argv.extend(["--max-pages", str(self.args.max_pages)])
|
||||
if getattr(self.args, "skip_scrape", False):
|
||||
argv.append("--skip-scrape")
|
||||
if getattr(self.args, "resume", False):
|
||||
argv.append("--resume")
|
||||
if getattr(self.args, "fresh", False):
|
||||
argv.append("--fresh")
|
||||
if getattr(self.args, "rate_limit", None):
|
||||
argv.extend(["--rate-limit", str(self.args.rate_limit)])
|
||||
if getattr(self.args, "workers", None):
|
||||
argv.extend(["--workers", str(self.args.workers)])
|
||||
if getattr(self.args, "async_mode", False):
|
||||
argv.append("--async")
|
||||
if getattr(self.args, "no_rate_limit", False):
|
||||
argv.append("--no-rate-limit")
|
||||
|
||||
# Call doc_scraper with modified argv
|
||||
logger.debug(f"Calling doc_scraper with argv: {argv}")
|
||||
@@ -182,32 +183,32 @@ class CreateCommand:
|
||||
from skill_seekers.cli import github_scraper
|
||||
|
||||
# Reconstruct argv for github_scraper
|
||||
argv = ['github_scraper']
|
||||
argv = ["github_scraper"]
|
||||
|
||||
# Add repo
|
||||
repo = self.source_info.parsed['repo']
|
||||
argv.extend(['--repo', repo])
|
||||
repo = self.source_info.parsed["repo"]
|
||||
argv.extend(["--repo", repo])
|
||||
|
||||
# Add universal arguments
|
||||
self._add_common_args(argv)
|
||||
|
||||
# Add GitHub-specific arguments
|
||||
if getattr(self.args, 'token', None):
|
||||
argv.extend(['--token', self.args.token])
|
||||
if getattr(self.args, 'profile', None):
|
||||
argv.extend(['--profile', self.args.profile])
|
||||
if getattr(self.args, 'non_interactive', False):
|
||||
argv.append('--non-interactive')
|
||||
if getattr(self.args, 'no_issues', False):
|
||||
argv.append('--no-issues')
|
||||
if getattr(self.args, 'no_changelog', False):
|
||||
argv.append('--no-changelog')
|
||||
if getattr(self.args, 'no_releases', False):
|
||||
argv.append('--no-releases')
|
||||
if getattr(self.args, 'max_issues', None) and self.args.max_issues != 100:
|
||||
argv.extend(['--max-issues', str(self.args.max_issues)])
|
||||
if getattr(self.args, 'scrape_only', False):
|
||||
argv.append('--scrape-only')
|
||||
if getattr(self.args, "token", None):
|
||||
argv.extend(["--token", self.args.token])
|
||||
if getattr(self.args, "profile", None):
|
||||
argv.extend(["--profile", self.args.profile])
|
||||
if getattr(self.args, "non_interactive", False):
|
||||
argv.append("--non-interactive")
|
||||
if getattr(self.args, "no_issues", False):
|
||||
argv.append("--no-issues")
|
||||
if getattr(self.args, "no_changelog", False):
|
||||
argv.append("--no-changelog")
|
||||
if getattr(self.args, "no_releases", False):
|
||||
argv.append("--no-releases")
|
||||
if getattr(self.args, "max_issues", None) and self.args.max_issues != 100:
|
||||
argv.extend(["--max-issues", str(self.args.max_issues)])
|
||||
if getattr(self.args, "scrape_only", False):
|
||||
argv.append("--scrape-only")
|
||||
|
||||
# Call github_scraper with modified argv
|
||||
logger.debug(f"Calling github_scraper with argv: {argv}")
|
||||
@@ -223,30 +224,30 @@ class CreateCommand:
|
||||
from skill_seekers.cli import codebase_scraper
|
||||
|
||||
# Reconstruct argv for codebase_scraper
|
||||
argv = ['codebase_scraper']
|
||||
argv = ["codebase_scraper"]
|
||||
|
||||
# Add directory
|
||||
directory = self.source_info.parsed['directory']
|
||||
argv.extend(['--directory', directory])
|
||||
directory = self.source_info.parsed["directory"]
|
||||
argv.extend(["--directory", directory])
|
||||
|
||||
# Add universal arguments
|
||||
self._add_common_args(argv)
|
||||
|
||||
# Add local-specific arguments
|
||||
if getattr(self.args, 'languages', None):
|
||||
argv.extend(['--languages', self.args.languages])
|
||||
if getattr(self.args, 'file_patterns', None):
|
||||
argv.extend(['--file-patterns', self.args.file_patterns])
|
||||
if getattr(self.args, 'skip_patterns', False):
|
||||
argv.append('--skip-patterns')
|
||||
if getattr(self.args, 'skip_test_examples', False):
|
||||
argv.append('--skip-test-examples')
|
||||
if getattr(self.args, 'skip_how_to_guides', False):
|
||||
argv.append('--skip-how-to-guides')
|
||||
if getattr(self.args, 'skip_config', False):
|
||||
argv.append('--skip-config')
|
||||
if getattr(self.args, 'skip_docs', False):
|
||||
argv.append('--skip-docs')
|
||||
if getattr(self.args, "languages", None):
|
||||
argv.extend(["--languages", self.args.languages])
|
||||
if getattr(self.args, "file_patterns", None):
|
||||
argv.extend(["--file-patterns", self.args.file_patterns])
|
||||
if getattr(self.args, "skip_patterns", False):
|
||||
argv.append("--skip-patterns")
|
||||
if getattr(self.args, "skip_test_examples", False):
|
||||
argv.append("--skip-test-examples")
|
||||
if getattr(self.args, "skip_how_to_guides", False):
|
||||
argv.append("--skip-how-to-guides")
|
||||
if getattr(self.args, "skip_config", False):
|
||||
argv.append("--skip-config")
|
||||
if getattr(self.args, "skip_docs", False):
|
||||
argv.append("--skip-docs")
|
||||
|
||||
# Call codebase_scraper with modified argv
|
||||
logger.debug(f"Calling codebase_scraper with argv: {argv}")
|
||||
@@ -262,20 +263,20 @@ class CreateCommand:
|
||||
from skill_seekers.cli import pdf_scraper
|
||||
|
||||
# Reconstruct argv for pdf_scraper
|
||||
argv = ['pdf_scraper']
|
||||
argv = ["pdf_scraper"]
|
||||
|
||||
# Add PDF file
|
||||
file_path = self.source_info.parsed['file_path']
|
||||
argv.extend(['--pdf', file_path])
|
||||
file_path = self.source_info.parsed["file_path"]
|
||||
argv.extend(["--pdf", file_path])
|
||||
|
||||
# Add universal arguments
|
||||
self._add_common_args(argv)
|
||||
|
||||
# Add PDF-specific arguments
|
||||
if getattr(self.args, 'ocr', False):
|
||||
argv.append('--ocr')
|
||||
if getattr(self.args, 'pages', None):
|
||||
argv.extend(['--pages', self.args.pages])
|
||||
if getattr(self.args, "ocr", False):
|
||||
argv.append("--ocr")
|
||||
if getattr(self.args, "pages", None):
|
||||
argv.extend(["--pages", self.args.pages])
|
||||
|
||||
# Call pdf_scraper with modified argv
|
||||
logger.debug(f"Calling pdf_scraper with argv: {argv}")
|
||||
@@ -291,11 +292,11 @@ class CreateCommand:
|
||||
from skill_seekers.cli import unified_scraper
|
||||
|
||||
# Reconstruct argv for unified_scraper
|
||||
argv = ['unified_scraper']
|
||||
argv = ["unified_scraper"]
|
||||
|
||||
# Add config file
|
||||
config_path = self.source_info.parsed['config_path']
|
||||
argv.extend(['--config', config_path])
|
||||
config_path = self.source_info.parsed["config_path"]
|
||||
argv.extend(["--config", config_path])
|
||||
|
||||
# Add universal arguments (unified scraper supports most)
|
||||
self._add_common_args(argv)
|
||||
@@ -317,53 +318,54 @@ class CreateCommand:
|
||||
"""
|
||||
# Identity arguments
|
||||
if self.args.name:
|
||||
argv.extend(['--name', self.args.name])
|
||||
elif hasattr(self, 'source_info') and self.source_info:
|
||||
argv.extend(["--name", self.args.name])
|
||||
elif hasattr(self, "source_info") and self.source_info:
|
||||
# Use suggested name from source detection
|
||||
argv.extend(['--name', self.source_info.suggested_name])
|
||||
argv.extend(["--name", self.source_info.suggested_name])
|
||||
|
||||
if self.args.description:
|
||||
argv.extend(['--description', self.args.description])
|
||||
argv.extend(["--description", self.args.description])
|
||||
if self.args.output:
|
||||
argv.extend(['--output', self.args.output])
|
||||
argv.extend(["--output", self.args.output])
|
||||
|
||||
# Enhancement arguments (consolidated to --enhance-level only)
|
||||
if self.args.enhance_level > 0:
|
||||
argv.extend(['--enhance-level', str(self.args.enhance_level)])
|
||||
argv.extend(["--enhance-level", str(self.args.enhance_level)])
|
||||
if self.args.api_key:
|
||||
argv.extend(['--api-key', self.args.api_key])
|
||||
argv.extend(["--api-key", self.args.api_key])
|
||||
|
||||
# Behavior arguments
|
||||
if self.args.dry_run:
|
||||
argv.append('--dry-run')
|
||||
argv.append("--dry-run")
|
||||
if self.args.verbose:
|
||||
argv.append('--verbose')
|
||||
argv.append("--verbose")
|
||||
if self.args.quiet:
|
||||
argv.append('--quiet')
|
||||
argv.append("--quiet")
|
||||
|
||||
# RAG arguments (NEW - universal!)
|
||||
if getattr(self.args, 'chunk_for_rag', False):
|
||||
argv.append('--chunk-for-rag')
|
||||
if getattr(self.args, 'chunk_size', None) and self.args.chunk_size != 512:
|
||||
argv.extend(['--chunk-size', str(self.args.chunk_size)])
|
||||
if getattr(self.args, 'chunk_overlap', None) and self.args.chunk_overlap != 50:
|
||||
argv.extend(['--chunk-overlap', str(self.args.chunk_overlap)])
|
||||
if getattr(self.args, "chunk_for_rag", False):
|
||||
argv.append("--chunk-for-rag")
|
||||
if getattr(self.args, "chunk_size", None) and self.args.chunk_size != 512:
|
||||
argv.extend(["--chunk-size", str(self.args.chunk_size)])
|
||||
if getattr(self.args, "chunk_overlap", None) and self.args.chunk_overlap != 50:
|
||||
argv.extend(["--chunk-overlap", str(self.args.chunk_overlap)])
|
||||
|
||||
# Preset argument
|
||||
if getattr(self.args, 'preset', None):
|
||||
argv.extend(['--preset', self.args.preset])
|
||||
if getattr(self.args, "preset", None):
|
||||
argv.extend(["--preset", self.args.preset])
|
||||
|
||||
# Config file
|
||||
if self.args.config:
|
||||
argv.extend(['--config', self.args.config])
|
||||
argv.extend(["--config", self.args.config])
|
||||
|
||||
# Advanced arguments
|
||||
if getattr(self.args, 'no_preserve_code_blocks', False):
|
||||
argv.append('--no-preserve-code-blocks')
|
||||
if getattr(self.args, 'no_preserve_paragraphs', False):
|
||||
argv.append('--no-preserve-paragraphs')
|
||||
if getattr(self.args, 'interactive_enhancement', False):
|
||||
argv.append('--interactive-enhancement')
|
||||
if getattr(self.args, "no_preserve_code_blocks", False):
|
||||
argv.append("--no-preserve-code-blocks")
|
||||
if getattr(self.args, "no_preserve_paragraphs", False):
|
||||
argv.append("--no-preserve-paragraphs")
|
||||
if getattr(self.args, "interactive_enhancement", False):
|
||||
argv.append("--interactive-enhancement")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
"""Entry point for create command.
|
||||
@@ -381,8 +383,8 @@ def main() -> int:
|
||||
return text.splitlines()
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
prog='skill-seekers create',
|
||||
description='Create skill from any source (auto-detects type)',
|
||||
prog="skill-seekers create",
|
||||
description="Create skill from any source (auto-detects type)",
|
||||
formatter_class=NoWrapFormatter,
|
||||
epilog=textwrap.dedent("""\
|
||||
Examples:
|
||||
@@ -416,19 +418,25 @@ Common Workflows:
|
||||
skill-seekers create <source> -p quick
|
||||
skill-seekers create <source> -p standard --enhance-level 2
|
||||
skill-seekers create <source> --chunk-for-rag
|
||||
""")
|
||||
"""),
|
||||
)
|
||||
|
||||
# Add arguments in default mode (universal only)
|
||||
add_create_arguments(parser, mode='default')
|
||||
add_create_arguments(parser, mode="default")
|
||||
|
||||
# Add hidden help mode flags (use underscore prefix to match CreateParser)
|
||||
parser.add_argument('--help-web', action='store_true', help=argparse.SUPPRESS, dest='_help_web')
|
||||
parser.add_argument('--help-github', action='store_true', help=argparse.SUPPRESS, dest='_help_github')
|
||||
parser.add_argument('--help-local', action='store_true', help=argparse.SUPPRESS, dest='_help_local')
|
||||
parser.add_argument('--help-pdf', action='store_true', help=argparse.SUPPRESS, dest='_help_pdf')
|
||||
parser.add_argument('--help-advanced', action='store_true', help=argparse.SUPPRESS, dest='_help_advanced')
|
||||
parser.add_argument('--help-all', action='store_true', help=argparse.SUPPRESS, dest='_help_all')
|
||||
parser.add_argument("--help-web", action="store_true", help=argparse.SUPPRESS, dest="_help_web")
|
||||
parser.add_argument(
|
||||
"--help-github", action="store_true", help=argparse.SUPPRESS, dest="_help_github"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--help-local", action="store_true", help=argparse.SUPPRESS, dest="_help_local"
|
||||
)
|
||||
parser.add_argument("--help-pdf", action="store_true", help=argparse.SUPPRESS, dest="_help_pdf")
|
||||
parser.add_argument(
|
||||
"--help-advanced", action="store_true", help=argparse.SUPPRESS, dest="_help_advanced"
|
||||
)
|
||||
parser.add_argument("--help-all", action="store_true", help=argparse.SUPPRESS, dest="_help_all")
|
||||
|
||||
# Parse arguments
|
||||
args = parser.parse_args()
|
||||
@@ -437,67 +445,62 @@ Common Workflows:
|
||||
if args._help_web:
|
||||
# Recreate parser with web-specific arguments
|
||||
parser_web = argparse.ArgumentParser(
|
||||
prog='skill-seekers create',
|
||||
description='Create skill from web documentation',
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter
|
||||
prog="skill-seekers create",
|
||||
description="Create skill from web documentation",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
)
|
||||
add_create_arguments(parser_web, mode='web')
|
||||
add_create_arguments(parser_web, mode="web")
|
||||
parser_web.print_help()
|
||||
return 0
|
||||
elif args._help_github:
|
||||
parser_github = argparse.ArgumentParser(
|
||||
prog='skill-seekers create',
|
||||
description='Create skill from GitHub repository',
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter
|
||||
prog="skill-seekers create",
|
||||
description="Create skill from GitHub repository",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
)
|
||||
add_create_arguments(parser_github, mode='github')
|
||||
add_create_arguments(parser_github, mode="github")
|
||||
parser_github.print_help()
|
||||
return 0
|
||||
elif args._help_local:
|
||||
parser_local = argparse.ArgumentParser(
|
||||
prog='skill-seekers create',
|
||||
description='Create skill from local codebase',
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter
|
||||
prog="skill-seekers create",
|
||||
description="Create skill from local codebase",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
)
|
||||
add_create_arguments(parser_local, mode='local')
|
||||
add_create_arguments(parser_local, mode="local")
|
||||
parser_local.print_help()
|
||||
return 0
|
||||
elif args._help_pdf:
|
||||
parser_pdf = argparse.ArgumentParser(
|
||||
prog='skill-seekers create',
|
||||
description='Create skill from PDF file',
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter
|
||||
prog="skill-seekers create",
|
||||
description="Create skill from PDF file",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
)
|
||||
add_create_arguments(parser_pdf, mode='pdf')
|
||||
add_create_arguments(parser_pdf, mode="pdf")
|
||||
parser_pdf.print_help()
|
||||
return 0
|
||||
elif args._help_advanced:
|
||||
parser_advanced = argparse.ArgumentParser(
|
||||
prog='skill-seekers create',
|
||||
description='Create skill - advanced options',
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter
|
||||
prog="skill-seekers create",
|
||||
description="Create skill - advanced options",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
)
|
||||
add_create_arguments(parser_advanced, mode='advanced')
|
||||
add_create_arguments(parser_advanced, mode="advanced")
|
||||
parser_advanced.print_help()
|
||||
return 0
|
||||
elif args._help_all:
|
||||
parser_all = argparse.ArgumentParser(
|
||||
prog='skill-seekers create',
|
||||
description='Create skill - all options',
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter
|
||||
prog="skill-seekers create",
|
||||
description="Create skill - all options",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
)
|
||||
add_create_arguments(parser_all, mode='all')
|
||||
add_create_arguments(parser_all, mode="all")
|
||||
parser_all.print_help()
|
||||
return 0
|
||||
|
||||
# Setup logging
|
||||
log_level = logging.DEBUG if args.verbose else (
|
||||
logging.WARNING if args.quiet else logging.INFO
|
||||
)
|
||||
logging.basicConfig(
|
||||
level=log_level,
|
||||
format='%(levelname)s: %(message)s'
|
||||
)
|
||||
log_level = logging.DEBUG if args.verbose else (logging.WARNING if args.quiet else logging.INFO)
|
||||
logging.basicConfig(level=log_level, format="%(levelname)s: %(message)s")
|
||||
|
||||
# Validate source provided
|
||||
if not args.source:
|
||||
@@ -507,5 +510,6 @@ Common Workflows:
|
||||
command = CreateCommand(args)
|
||||
return command.execute()
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
|
||||
@@ -2231,8 +2231,9 @@ def execute_enhancement(config: dict[str, Any], args: argparse.Namespace) -> Non
|
||||
import subprocess
|
||||
|
||||
# Optional enhancement with auto-detected mode (API or LOCAL)
|
||||
if getattr(args, 'enhance_level', 0) > 0:
|
||||
if getattr(args, "enhance_level", 0) > 0:
|
||||
import os
|
||||
|
||||
has_api_key = bool(os.environ.get("ANTHROPIC_API_KEY") or args.api_key)
|
||||
mode = "API" if has_api_key else "LOCAL"
|
||||
|
||||
@@ -2246,7 +2247,7 @@ def execute_enhancement(config: dict[str, Any], args: argparse.Namespace) -> Non
|
||||
|
||||
if args.api_key:
|
||||
enhance_cmd.extend(["--api-key", args.api_key])
|
||||
if getattr(args, 'interactive_enhancement', False):
|
||||
if getattr(args, "interactive_enhancement", False):
|
||||
enhance_cmd.append("--interactive-enhancement")
|
||||
|
||||
result = subprocess.run(enhance_cmd, check=True)
|
||||
@@ -2256,14 +2257,18 @@ def execute_enhancement(config: dict[str, Any], args: argparse.Namespace) -> Non
|
||||
logger.warning("\n⚠ Enhancement failed, but skill was still built")
|
||||
except FileNotFoundError:
|
||||
logger.warning("\n⚠ skill-seekers-enhance command not found. Run manually:")
|
||||
logger.info(" skill-seekers-enhance output/%s/ --enhance-level %d", config["name"], args.enhance_level)
|
||||
logger.info(
|
||||
" skill-seekers-enhance output/%s/ --enhance-level %d",
|
||||
config["name"],
|
||||
args.enhance_level,
|
||||
)
|
||||
|
||||
# Print packaging instructions
|
||||
logger.info("\n📦 Package your skill:")
|
||||
logger.info(" skill-seekers-package output/%s/", config["name"])
|
||||
|
||||
# Suggest enhancement if not done
|
||||
if getattr(args, 'enhance_level', 0) == 0:
|
||||
if getattr(args, "enhance_level", 0) == 0:
|
||||
logger.info("\n💡 Optional: Enhance SKILL.md with Claude:")
|
||||
logger.info(" skill-seekers-enhance output/%s/ --enhance-level 2", config["name"])
|
||||
logger.info(" or re-run with: --enhance-level 2 (auto-detects API vs LOCAL mode)")
|
||||
|
||||
@@ -100,6 +100,7 @@ EXCLUDED_DIRS = {
|
||||
".tmp",
|
||||
}
|
||||
|
||||
|
||||
def extract_description_from_readme(readme_content: str, repo_name: str) -> str:
|
||||
"""
|
||||
Extract a meaningful description from README content for skill description.
|
||||
@@ -180,6 +181,7 @@ def extract_description_from_readme(readme_content: str, repo_name: str) -> str:
|
||||
project_name = repo_name.split("/")[-1]
|
||||
return f"Use when working with {project_name}"
|
||||
|
||||
|
||||
class GitHubScraper:
|
||||
"""
|
||||
GitHub Repository Scraper (C1.1-C1.9)
|
||||
@@ -892,6 +894,7 @@ class GitHubScraper:
|
||||
|
||||
logger.info(f"Data saved to: {self.data_file}")
|
||||
|
||||
|
||||
class GitHubToSkillConverter:
|
||||
"""
|
||||
Convert extracted GitHub data to Claude skill format (C1.10).
|
||||
@@ -1347,6 +1350,7 @@ Use this skill when you need to:
|
||||
f.write(content)
|
||||
logger.info(f"Generated: {structure_path}")
|
||||
|
||||
|
||||
def setup_argument_parser() -> argparse.ArgumentParser:
|
||||
"""Setup and configure command-line argument parser.
|
||||
|
||||
@@ -1374,6 +1378,7 @@ Examples:
|
||||
|
||||
return parser
|
||||
|
||||
|
||||
def main():
|
||||
"""C1.10: CLI tool entry point."""
|
||||
parser = setup_argument_parser()
|
||||
@@ -1421,14 +1426,16 @@ def main():
|
||||
skill_dir = f"output/{skill_name}"
|
||||
|
||||
# Phase 3: Optional enhancement with auto-detected mode
|
||||
if getattr(args, 'enhance_level', 0) > 0:
|
||||
if getattr(args, "enhance_level", 0) > 0:
|
||||
import os
|
||||
|
||||
# Auto-detect mode based on API key availability
|
||||
api_key = args.api_key or os.environ.get("ANTHROPIC_API_KEY")
|
||||
mode = "API" if api_key else "LOCAL"
|
||||
|
||||
logger.info(f"\n📝 Enhancing SKILL.md with Claude ({mode} mode, level {args.enhance_level})...")
|
||||
logger.info(
|
||||
f"\n📝 Enhancing SKILL.md with Claude ({mode} mode, level {args.enhance_level})..."
|
||||
)
|
||||
|
||||
if api_key:
|
||||
# API-based enhancement
|
||||
@@ -1438,9 +1445,7 @@ def main():
|
||||
enhance_skill_md(skill_dir, api_key)
|
||||
logger.info("✅ API enhancement complete!")
|
||||
except ImportError:
|
||||
logger.error(
|
||||
"❌ API enhancement not available. Install: pip install anthropic"
|
||||
)
|
||||
logger.error("❌ API enhancement not available. Install: pip install anthropic")
|
||||
logger.info("💡 Falling back to LOCAL mode...")
|
||||
# Fall back to LOCAL mode
|
||||
from pathlib import Path
|
||||
@@ -1460,7 +1465,7 @@ def main():
|
||||
|
||||
logger.info(f"\n✅ Success! Skill created at: {skill_dir}/")
|
||||
|
||||
if getattr(args, 'enhance_level', 0) == 0:
|
||||
if getattr(args, "enhance_level", 0) == 0:
|
||||
logger.info("\n💡 Optional: Enhance SKILL.md with Claude:")
|
||||
logger.info(f" skill-seekers enhance {skill_dir}/ --enhance-level 2")
|
||||
logger.info(" (auto-detects API vs LOCAL mode based on ANTHROPIC_API_KEY)")
|
||||
@@ -1471,5 +1476,6 @@ def main():
|
||||
logger.error(f"Error: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -133,7 +133,7 @@ def _reconstruct_argv(command: str, args: argparse.Namespace) -> list[str]:
|
||||
if key.startswith("_help_"):
|
||||
if value:
|
||||
# Convert _help_web -> --help-web
|
||||
help_flag = key.replace('_help_', 'help-')
|
||||
help_flag = key.replace("_help_", "help-")
|
||||
argv.append(f"--{help_flag}")
|
||||
continue
|
||||
|
||||
@@ -181,6 +181,7 @@ def main(argv: list[str] | None = None) -> int:
|
||||
argv = sys.argv[1:]
|
||||
if len(argv) >= 2 and argv[0] == "analyze" and "--preset-list" in argv:
|
||||
from skill_seekers.cli.codebase_scraper import main as analyze_main
|
||||
|
||||
original_argv = sys.argv.copy()
|
||||
sys.argv = ["codebase_scraper.py", "--preset-list"]
|
||||
try:
|
||||
@@ -274,8 +275,8 @@ def _handle_analyze_command(args: argparse.Namespace) -> int:
|
||||
sys.argv.extend(["--depth", args.depth])
|
||||
|
||||
# Determine enhance_level (simplified - use default or override)
|
||||
enhance_level = getattr(args, 'enhance_level', 2) # Default is 2
|
||||
if getattr(args, 'quick', False):
|
||||
enhance_level = getattr(args, "enhance_level", 2) # Default is 2
|
||||
if getattr(args, "quick", False):
|
||||
enhance_level = 0 # Quick mode disables enhancement
|
||||
|
||||
sys.argv.extend(["--enhance-level", str(enhance_level)])
|
||||
|
||||
@@ -52,6 +52,7 @@ PARSERS = [
|
||||
QualityParser(),
|
||||
]
|
||||
|
||||
|
||||
def register_parsers(subparsers):
|
||||
"""Register all subcommand parsers.
|
||||
|
||||
@@ -64,6 +65,7 @@ def register_parsers(subparsers):
|
||||
for parser_instance in PARSERS:
|
||||
parser_instance.create_parser(subparsers)
|
||||
|
||||
|
||||
def get_parser_names():
|
||||
"""Get list of all subcommand names.
|
||||
|
||||
@@ -72,6 +74,7 @@ def get_parser_names():
|
||||
"""
|
||||
return [p.name for p in PARSERS]
|
||||
|
||||
|
||||
__all__ = [
|
||||
"SubcommandParser",
|
||||
"PARSERS",
|
||||
|
||||
@@ -9,6 +9,7 @@ Includes preset system support (Issue #268).
|
||||
from .base import SubcommandParser
|
||||
from skill_seekers.cli.arguments.analyze import add_analyze_arguments
|
||||
|
||||
|
||||
class AnalyzeParser(SubcommandParser):
|
||||
"""Parser for analyze subcommand."""
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
from abc import ABC, abstractmethod
|
||||
import argparse
|
||||
|
||||
|
||||
class SubcommandParser(ABC):
|
||||
"""Base class for subcommand parsers.
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
from .base import SubcommandParser
|
||||
|
||||
|
||||
class ConfigParser(SubcommandParser):
|
||||
"""Parser for config subcommand."""
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import argparse
|
||||
from .base import SubcommandParser
|
||||
from skill_seekers.cli.arguments.create import add_create_arguments
|
||||
|
||||
|
||||
class CreateParser(SubcommandParser):
|
||||
"""Parser for create subcommand with multi-mode help."""
|
||||
|
||||
@@ -54,45 +55,45 @@ Presets: -p quick (1-2min) | -p standard (5-10min) | -p comprehensive (20-60min)
|
||||
"""
|
||||
# Add all arguments in 'default' mode (universal only)
|
||||
# This keeps help text clean and focused
|
||||
add_create_arguments(parser, mode='default')
|
||||
add_create_arguments(parser, mode="default")
|
||||
|
||||
# Add hidden help mode flags
|
||||
# These won't show in default help but can be used to get source-specific help
|
||||
parser.add_argument(
|
||||
'--help-web',
|
||||
action='store_true',
|
||||
help='Show web scraping specific options',
|
||||
dest='_help_web'
|
||||
"--help-web",
|
||||
action="store_true",
|
||||
help="Show web scraping specific options",
|
||||
dest="_help_web",
|
||||
)
|
||||
parser.add_argument(
|
||||
'--help-github',
|
||||
action='store_true',
|
||||
help='Show GitHub repository specific options',
|
||||
dest='_help_github'
|
||||
"--help-github",
|
||||
action="store_true",
|
||||
help="Show GitHub repository specific options",
|
||||
dest="_help_github",
|
||||
)
|
||||
parser.add_argument(
|
||||
'--help-local',
|
||||
action='store_true',
|
||||
help='Show local codebase specific options',
|
||||
dest='_help_local'
|
||||
"--help-local",
|
||||
action="store_true",
|
||||
help="Show local codebase specific options",
|
||||
dest="_help_local",
|
||||
)
|
||||
parser.add_argument(
|
||||
'--help-pdf',
|
||||
action='store_true',
|
||||
help='Show PDF extraction specific options',
|
||||
dest='_help_pdf'
|
||||
"--help-pdf",
|
||||
action="store_true",
|
||||
help="Show PDF extraction specific options",
|
||||
dest="_help_pdf",
|
||||
)
|
||||
parser.add_argument(
|
||||
'--help-advanced',
|
||||
action='store_true',
|
||||
help='Show advanced/rare options',
|
||||
dest='_help_advanced'
|
||||
"--help-advanced",
|
||||
action="store_true",
|
||||
help="Show advanced/rare options",
|
||||
dest="_help_advanced",
|
||||
)
|
||||
parser.add_argument(
|
||||
'--help-all',
|
||||
action='store_true',
|
||||
help='Show all available options (120+ flags)',
|
||||
dest='_help_all'
|
||||
"--help-all",
|
||||
action="store_true",
|
||||
help="Show all available options (120+ flags)",
|
||||
dest="_help_all",
|
||||
)
|
||||
|
||||
def register(self, subparsers):
|
||||
@@ -104,16 +105,14 @@ Presets: -p quick (1-2min) | -p standard (5-10min) | -p comprehensive (20-60min)
|
||||
Returns:
|
||||
Configured ArgumentParser for this subcommand
|
||||
"""
|
||||
|
||||
# Custom formatter that preserves line breaks
|
||||
class NoWrapFormatter(argparse.RawDescriptionHelpFormatter):
|
||||
def _split_lines(self, text, width):
|
||||
return text.splitlines()
|
||||
|
||||
parser = subparsers.add_parser(
|
||||
self.name,
|
||||
help=self.help,
|
||||
description=self.description,
|
||||
formatter_class=NoWrapFormatter
|
||||
self.name, help=self.help, description=self.description, formatter_class=NoWrapFormatter
|
||||
)
|
||||
self.add_arguments(parser)
|
||||
return parser
|
||||
|
||||
@@ -7,6 +7,7 @@ consistency with the standalone enhance_skill_local module.
|
||||
from .base import SubcommandParser
|
||||
from skill_seekers.cli.arguments.enhance import add_enhance_arguments
|
||||
|
||||
|
||||
class EnhanceParser(SubcommandParser):
|
||||
"""Parser for enhance subcommand."""
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
from .base import SubcommandParser
|
||||
|
||||
|
||||
class EnhanceStatusParser(SubcommandParser):
|
||||
"""Parser for enhance-status subcommand."""
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
from .base import SubcommandParser
|
||||
|
||||
|
||||
class EstimateParser(SubcommandParser):
|
||||
"""Parser for estimate subcommand."""
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ consistency with the standalone github_scraper module.
|
||||
from .base import SubcommandParser
|
||||
from skill_seekers.cli.arguments.github import add_github_arguments
|
||||
|
||||
|
||||
class GitHubParser(SubcommandParser):
|
||||
"""Parser for github subcommand."""
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
from .base import SubcommandParser
|
||||
|
||||
|
||||
class InstallAgentParser(SubcommandParser):
|
||||
"""Parser for install-agent subcommand."""
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
from .base import SubcommandParser
|
||||
|
||||
|
||||
class InstallParser(SubcommandParser):
|
||||
"""Parser for install subcommand."""
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
from .base import SubcommandParser
|
||||
|
||||
|
||||
class MultilangParser(SubcommandParser):
|
||||
"""Parser for multilang subcommand."""
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ consistency with the standalone package_skill module.
|
||||
from .base import SubcommandParser
|
||||
from skill_seekers.cli.arguments.package import add_package_arguments
|
||||
|
||||
|
||||
class PackageParser(SubcommandParser):
|
||||
"""Parser for package subcommand."""
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ consistency with the standalone pdf_scraper module.
|
||||
from .base import SubcommandParser
|
||||
from skill_seekers.cli.arguments.pdf import add_pdf_arguments
|
||||
|
||||
|
||||
class PDFParser(SubcommandParser):
|
||||
"""Parser for pdf subcommand."""
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
from .base import SubcommandParser
|
||||
|
||||
|
||||
class QualityParser(SubcommandParser):
|
||||
"""Parser for quality subcommand."""
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
from .base import SubcommandParser
|
||||
|
||||
|
||||
class ResumeParser(SubcommandParser):
|
||||
"""Parser for resume subcommand."""
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ consistency with the standalone doc_scraper module.
|
||||
from .base import SubcommandParser
|
||||
from skill_seekers.cli.arguments.scrape import add_scrape_arguments
|
||||
|
||||
|
||||
class ScrapeParser(SubcommandParser):
|
||||
"""Parser for scrape subcommand."""
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
from .base import SubcommandParser
|
||||
|
||||
|
||||
class StreamParser(SubcommandParser):
|
||||
"""Parser for stream subcommand."""
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
from .base import SubcommandParser
|
||||
|
||||
|
||||
class TestExamplesParser(SubcommandParser):
|
||||
"""Parser for extract-test-examples subcommand."""
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ consistency with the standalone unified_scraper module.
|
||||
from .base import SubcommandParser
|
||||
from skill_seekers.cli.arguments.unified import add_unified_arguments
|
||||
|
||||
|
||||
class UnifiedParser(SubcommandParser):
|
||||
"""Parser for unified subcommand."""
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
from .base import SubcommandParser
|
||||
|
||||
|
||||
class UpdateParser(SubcommandParser):
|
||||
"""Parser for update subcommand."""
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ consistency with the standalone upload_skill module.
|
||||
from .base import SubcommandParser
|
||||
from skill_seekers.cli.arguments.upload import add_upload_arguments
|
||||
|
||||
|
||||
class UploadParser(SubcommandParser):
|
||||
"""Parser for upload subcommand."""
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ from dataclasses import dataclass, field
|
||||
|
||||
import argparse
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class AnalysisPreset:
|
||||
"""Definition of an analysis preset.
|
||||
@@ -29,12 +30,14 @@ class AnalysisPreset:
|
||||
features: Dict of feature flags (feature_name -> enabled)
|
||||
estimated_time: Human-readable time estimate
|
||||
"""
|
||||
|
||||
name: str
|
||||
description: str
|
||||
depth: str
|
||||
features: dict[str, bool] = field(default_factory=dict)
|
||||
estimated_time: str = ""
|
||||
|
||||
|
||||
# Preset definitions
|
||||
ANALYZE_PRESETS = {
|
||||
"quick": AnalysisPreset(
|
||||
@@ -49,9 +52,8 @@ ANALYZE_PRESETS = {
|
||||
"how_to_guides": False,
|
||||
"config_patterns": False,
|
||||
},
|
||||
estimated_time="1-2 minutes"
|
||||
estimated_time="1-2 minutes",
|
||||
),
|
||||
|
||||
"standard": AnalysisPreset(
|
||||
name="Standard",
|
||||
description="Balanced analysis with core features (recommended)",
|
||||
@@ -64,9 +66,8 @@ ANALYZE_PRESETS = {
|
||||
"how_to_guides": False,
|
||||
"config_patterns": True,
|
||||
},
|
||||
estimated_time="5-10 minutes"
|
||||
estimated_time="5-10 minutes",
|
||||
),
|
||||
|
||||
"comprehensive": AnalysisPreset(
|
||||
name="Comprehensive",
|
||||
description="Full analysis with all features",
|
||||
@@ -79,10 +80,11 @@ ANALYZE_PRESETS = {
|
||||
"how_to_guides": True,
|
||||
"config_patterns": True,
|
||||
},
|
||||
estimated_time="20-60 minutes"
|
||||
estimated_time="20-60 minutes",
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def apply_analyze_preset(args: argparse.Namespace, preset_name: str) -> None:
|
||||
"""Apply an analysis preset to the args namespace.
|
||||
|
||||
@@ -113,6 +115,7 @@ def apply_analyze_preset(args: argparse.Namespace, preset_name: str) -> None:
|
||||
skip_attr = f"skip_{feature}"
|
||||
setattr(args, skip_attr, not enabled)
|
||||
|
||||
|
||||
def get_preset_help_text(preset_name: str) -> str:
|
||||
"""Get formatted help text for a preset.
|
||||
|
||||
@@ -129,6 +132,7 @@ def get_preset_help_text(preset_name: str) -> str:
|
||||
f" Depth: {preset.depth}"
|
||||
)
|
||||
|
||||
|
||||
def show_preset_list() -> None:
|
||||
"""Print the list of available presets to stdout.
|
||||
|
||||
@@ -161,6 +165,7 @@ def show_preset_list() -> None:
|
||||
print(" skill-seekers analyze --directory <dir> --preset comprehensive --enhance-level 2")
|
||||
print()
|
||||
|
||||
|
||||
def resolve_enhance_level(args: argparse.Namespace) -> int:
|
||||
"""Determine the enhance level based on user arguments.
|
||||
|
||||
@@ -186,6 +191,7 @@ def resolve_enhance_level(args: argparse.Namespace) -> int:
|
||||
# Default is no enhancement
|
||||
return 0
|
||||
|
||||
|
||||
def apply_preset_with_warnings(args: argparse.Namespace) -> str:
|
||||
"""Apply preset with deprecation warnings for legacy flags.
|
||||
|
||||
@@ -240,6 +246,7 @@ def apply_preset_with_warnings(args: argparse.Namespace) -> str:
|
||||
|
||||
return preset_name
|
||||
|
||||
|
||||
def print_deprecation_warning(old_flag: str, new_flag: str) -> None:
|
||||
"""Print a deprecation warning for legacy flags.
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ from dataclasses import dataclass, field
|
||||
|
||||
import argparse
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class GitHubPreset:
|
||||
"""Definition of a GitHub preset.
|
||||
@@ -23,12 +24,14 @@ class GitHubPreset:
|
||||
features: Dict of feature flags (feature_name -> enabled)
|
||||
estimated_time: Human-readable time estimate
|
||||
"""
|
||||
|
||||
name: str
|
||||
description: str
|
||||
max_issues: int
|
||||
features: dict[str, bool] = field(default_factory=dict)
|
||||
estimated_time: str = ""
|
||||
|
||||
|
||||
# Preset definitions
|
||||
GITHUB_PRESETS = {
|
||||
"quick": GitHubPreset(
|
||||
@@ -40,9 +43,8 @@ GITHUB_PRESETS = {
|
||||
"include_changelog": True,
|
||||
"include_releases": False,
|
||||
},
|
||||
estimated_time="1-3 minutes"
|
||||
estimated_time="1-3 minutes",
|
||||
),
|
||||
|
||||
"standard": GitHubPreset(
|
||||
name="Standard",
|
||||
description="Balanced scraping with issues and releases (recommended)",
|
||||
@@ -52,9 +54,8 @@ GITHUB_PRESETS = {
|
||||
"include_changelog": True,
|
||||
"include_releases": True,
|
||||
},
|
||||
estimated_time="5-15 minutes"
|
||||
estimated_time="5-15 minutes",
|
||||
),
|
||||
|
||||
"comprehensive": GitHubPreset(
|
||||
name="Comprehensive",
|
||||
description="Comprehensive scraping with all available data",
|
||||
@@ -64,10 +65,11 @@ GITHUB_PRESETS = {
|
||||
"include_changelog": True,
|
||||
"include_releases": True,
|
||||
},
|
||||
estimated_time="20-60 minutes"
|
||||
estimated_time="20-60 minutes",
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def apply_github_preset(args: argparse.Namespace, preset_name: str) -> None:
|
||||
"""Apply a GitHub preset to the args namespace.
|
||||
|
||||
@@ -90,6 +92,7 @@ def apply_github_preset(args: argparse.Namespace, preset_name: str) -> None:
|
||||
if not hasattr(args, skip_attr) or not getattr(args, skip_attr):
|
||||
setattr(args, skip_attr, not enabled)
|
||||
|
||||
|
||||
def show_github_preset_list() -> None:
|
||||
"""Print the list of available GitHub presets to stdout."""
|
||||
print("\nAvailable GitHub Presets")
|
||||
|
||||
@@ -6,6 +6,7 @@ between speed and comprehensiveness.
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass
|
||||
class AnalysisPreset:
|
||||
"""Analysis preset configuration.
|
||||
@@ -22,6 +23,7 @@ class AnalysisPreset:
|
||||
estimated_time: str
|
||||
icon: str
|
||||
|
||||
|
||||
# Preset definitions
|
||||
PRESETS = {
|
||||
"quick": AnalysisPreset(
|
||||
@@ -77,6 +79,7 @@ PRESETS = {
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
class PresetManager:
|
||||
"""Manages analysis presets and applies them to CLI arguments."""
|
||||
|
||||
@@ -164,6 +167,7 @@ class PresetManager:
|
||||
"""
|
||||
return "standard"
|
||||
|
||||
|
||||
# Public API
|
||||
__all__ = [
|
||||
"AnalysisPreset",
|
||||
|
||||
@@ -12,6 +12,7 @@ from dataclasses import dataclass, field
|
||||
|
||||
import argparse
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ScrapePreset:
|
||||
"""Definition of a scrape preset.
|
||||
@@ -25,6 +26,7 @@ class ScrapePreset:
|
||||
workers: Number of parallel workers
|
||||
estimated_time: Human-readable time estimate
|
||||
"""
|
||||
|
||||
name: str
|
||||
description: str
|
||||
rate_limit: float
|
||||
@@ -33,6 +35,7 @@ class ScrapePreset:
|
||||
workers: int = 1
|
||||
estimated_time: str = ""
|
||||
|
||||
|
||||
# Preset definitions
|
||||
SCRAPE_PRESETS = {
|
||||
"quick": ScrapePreset(
|
||||
@@ -45,9 +48,8 @@ SCRAPE_PRESETS = {
|
||||
},
|
||||
async_mode=True,
|
||||
workers=5,
|
||||
estimated_time="2-5 minutes"
|
||||
estimated_time="2-5 minutes",
|
||||
),
|
||||
|
||||
"standard": ScrapePreset(
|
||||
name="Standard",
|
||||
description="Balanced scraping with good coverage (recommended)",
|
||||
@@ -58,9 +60,8 @@ SCRAPE_PRESETS = {
|
||||
},
|
||||
async_mode=True,
|
||||
workers=3,
|
||||
estimated_time="10-30 minutes"
|
||||
estimated_time="10-30 minutes",
|
||||
),
|
||||
|
||||
"comprehensive": ScrapePreset(
|
||||
name="Comprehensive",
|
||||
description="Comprehensive scraping with all features",
|
||||
@@ -71,10 +72,11 @@ SCRAPE_PRESETS = {
|
||||
},
|
||||
async_mode=True,
|
||||
workers=2,
|
||||
estimated_time="1-3 hours"
|
||||
estimated_time="1-3 hours",
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def apply_scrape_preset(args: argparse.Namespace, preset_name: str) -> None:
|
||||
"""Apply a scrape preset to the args namespace.
|
||||
|
||||
@@ -100,9 +102,12 @@ def apply_scrape_preset(args: argparse.Namespace, preset_name: str) -> None:
|
||||
|
||||
# Apply feature flags
|
||||
for feature, enabled in preset.features.items():
|
||||
if feature == "rag_chunking" and (not hasattr(args, 'chunk_for_rag') or not args.chunk_for_rag):
|
||||
if feature == "rag_chunking" and (
|
||||
not hasattr(args, "chunk_for_rag") or not args.chunk_for_rag
|
||||
):
|
||||
args.chunk_for_rag = enabled
|
||||
|
||||
|
||||
def show_scrape_preset_list() -> None:
|
||||
"""Print the list of available scrape presets to stdout."""
|
||||
print("\nAvailable Scrape Presets")
|
||||
|
||||
@@ -13,6 +13,7 @@ import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass
|
||||
class SourceInfo:
|
||||
"""Information about a detected source.
|
||||
@@ -23,18 +24,20 @@ class SourceInfo:
|
||||
suggested_name: Auto-suggested name for the skill
|
||||
raw_input: Original user input
|
||||
"""
|
||||
|
||||
type: str
|
||||
parsed: dict[str, Any]
|
||||
suggested_name: str
|
||||
raw_input: str
|
||||
|
||||
|
||||
class SourceDetector:
|
||||
"""Detects source type from user input and extracts relevant information."""
|
||||
|
||||
# GitHub repo patterns
|
||||
GITHUB_REPO_PATTERN = re.compile(r'^([a-zA-Z0-9_.-]+)/([a-zA-Z0-9_.-]+)$')
|
||||
GITHUB_REPO_PATTERN = re.compile(r"^([a-zA-Z0-9_.-]+)/([a-zA-Z0-9_.-]+)$")
|
||||
GITHUB_URL_PATTERN = re.compile(
|
||||
r'(?:https?://)?(?:www\.)?github\.com/([a-zA-Z0-9_.-]+)/([a-zA-Z0-9_.-]+)(?:\.git)?'
|
||||
r"(?:https?://)?(?:www\.)?github\.com/([a-zA-Z0-9_.-]+)/([a-zA-Z0-9_.-]+)(?:\.git)?"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@@ -51,10 +54,10 @@ class SourceDetector:
|
||||
ValueError: If source type cannot be determined
|
||||
"""
|
||||
# 1. File extension detection
|
||||
if source.endswith('.json'):
|
||||
if source.endswith(".json"):
|
||||
return cls._detect_config(source)
|
||||
|
||||
if source.endswith('.pdf'):
|
||||
if source.endswith(".pdf"):
|
||||
return cls._detect_pdf(source)
|
||||
|
||||
# 2. Directory detection
|
||||
@@ -67,12 +70,12 @@ class SourceDetector:
|
||||
return github_info
|
||||
|
||||
# 4. URL detection
|
||||
if source.startswith('http://') or source.startswith('https://'):
|
||||
if source.startswith("http://") or source.startswith("https://"):
|
||||
return cls._detect_web(source)
|
||||
|
||||
# 5. Domain inference (add https://)
|
||||
if '.' in source and not source.startswith('/'):
|
||||
return cls._detect_web(f'https://{source}')
|
||||
if "." in source and not source.startswith("/"):
|
||||
return cls._detect_web(f"https://{source}")
|
||||
|
||||
# 6. Error - cannot determine
|
||||
raise ValueError(
|
||||
@@ -90,10 +93,7 @@ class SourceDetector:
|
||||
"""Detect config file source."""
|
||||
name = os.path.splitext(os.path.basename(source))[0]
|
||||
return SourceInfo(
|
||||
type='config',
|
||||
parsed={'config_path': source},
|
||||
suggested_name=name,
|
||||
raw_input=source
|
||||
type="config", parsed={"config_path": source}, suggested_name=name, raw_input=source
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@@ -101,10 +101,7 @@ class SourceDetector:
|
||||
"""Detect PDF file source."""
|
||||
name = os.path.splitext(os.path.basename(source))[0]
|
||||
return SourceInfo(
|
||||
type='pdf',
|
||||
parsed={'file_path': source},
|
||||
suggested_name=name,
|
||||
raw_input=source
|
||||
type="pdf", parsed={"file_path": source}, suggested_name=name, raw_input=source
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@@ -115,10 +112,7 @@ class SourceDetector:
|
||||
name = os.path.basename(directory)
|
||||
|
||||
return SourceInfo(
|
||||
type='local',
|
||||
parsed={'directory': directory},
|
||||
suggested_name=name,
|
||||
raw_input=source
|
||||
type="local", parsed={"directory": directory}, suggested_name=name, raw_input=source
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@@ -135,10 +129,10 @@ class SourceDetector:
|
||||
if match:
|
||||
owner, repo = match.groups()
|
||||
return SourceInfo(
|
||||
type='github',
|
||||
parsed={'repo': f'{owner}/{repo}'},
|
||||
type="github",
|
||||
parsed={"repo": f"{owner}/{repo}"},
|
||||
suggested_name=repo,
|
||||
raw_input=source
|
||||
raw_input=source,
|
||||
)
|
||||
|
||||
# Try GitHub URL pattern
|
||||
@@ -146,13 +140,13 @@ class SourceDetector:
|
||||
if match:
|
||||
owner, repo = match.groups()
|
||||
# Clean up repo name (remove .git suffix if present)
|
||||
if repo.endswith('.git'):
|
||||
if repo.endswith(".git"):
|
||||
repo = repo[:-4]
|
||||
return SourceInfo(
|
||||
type='github',
|
||||
parsed={'repo': f'{owner}/{repo}'},
|
||||
type="github",
|
||||
parsed={"repo": f"{owner}/{repo}"},
|
||||
suggested_name=repo,
|
||||
raw_input=source
|
||||
raw_input=source,
|
||||
)
|
||||
|
||||
return None
|
||||
@@ -167,15 +161,10 @@ class SourceDetector:
|
||||
# Clean up domain for name suggestion
|
||||
# docs.react.dev -> react
|
||||
# reactjs.org -> react
|
||||
name = domain.replace('www.', '').replace('docs.', '')
|
||||
name = name.split('.')[0] # Take first part before TLD
|
||||
name = domain.replace("www.", "").replace("docs.", "")
|
||||
name = name.split(".")[0] # Take first part before TLD
|
||||
|
||||
return SourceInfo(
|
||||
type='web',
|
||||
parsed={'url': source},
|
||||
suggested_name=name,
|
||||
raw_input=source
|
||||
)
|
||||
return SourceInfo(type="web", parsed={"url": source}, suggested_name=name, raw_input=source)
|
||||
|
||||
@classmethod
|
||||
def validate_source(cls, source_info: SourceInfo) -> None:
|
||||
@@ -187,22 +176,22 @@ class SourceDetector:
|
||||
Raises:
|
||||
ValueError: If source is not accessible
|
||||
"""
|
||||
if source_info.type == 'local':
|
||||
directory = source_info.parsed['directory']
|
||||
if source_info.type == "local":
|
||||
directory = source_info.parsed["directory"]
|
||||
if not os.path.exists(directory):
|
||||
raise ValueError(f"Directory does not exist: {directory}")
|
||||
if not os.path.isdir(directory):
|
||||
raise ValueError(f"Path is not a directory: {directory}")
|
||||
|
||||
elif source_info.type == 'pdf':
|
||||
file_path = source_info.parsed['file_path']
|
||||
elif source_info.type == "pdf":
|
||||
file_path = source_info.parsed["file_path"]
|
||||
if not os.path.exists(file_path):
|
||||
raise ValueError(f"PDF file does not exist: {file_path}")
|
||||
if not os.path.isfile(file_path):
|
||||
raise ValueError(f"Path is not a file: {file_path}")
|
||||
|
||||
elif source_info.type == 'config':
|
||||
config_path = source_info.parsed['config_path']
|
||||
elif source_info.type == "config":
|
||||
config_path = source_info.parsed["config_path"]
|
||||
if not os.path.exists(config_path):
|
||||
raise ValueError(f"Config file does not exist: {config_path}")
|
||||
if not os.path.isfile(config_path):
|
||||
|
||||
@@ -13,15 +13,14 @@ import pytest
|
||||
import subprocess
|
||||
import argparse
|
||||
|
||||
|
||||
class TestParserSync:
|
||||
"""E2E tests for parser synchronization (Issue #285)."""
|
||||
|
||||
def test_scrape_interactive_flag_works(self):
|
||||
"""Test that --interactive flag (previously missing) now works."""
|
||||
result = subprocess.run(
|
||||
["skill-seekers", "scrape", "--interactive", "--help"],
|
||||
capture_output=True,
|
||||
text=True
|
||||
["skill-seekers", "scrape", "--interactive", "--help"], capture_output=True, text=True
|
||||
)
|
||||
assert result.returncode == 0, "Command should execute successfully"
|
||||
assert "--interactive" in result.stdout, "Help should show --interactive flag"
|
||||
@@ -30,9 +29,7 @@ class TestParserSync:
|
||||
def test_scrape_chunk_for_rag_flag_works(self):
|
||||
"""Test that --chunk-for-rag flag (previously missing) now works."""
|
||||
result = subprocess.run(
|
||||
["skill-seekers", "scrape", "--help"],
|
||||
capture_output=True,
|
||||
text=True
|
||||
["skill-seekers", "scrape", "--help"], capture_output=True, text=True
|
||||
)
|
||||
assert "--chunk-for-rag" in result.stdout, "Help should show --chunk-for-rag flag"
|
||||
assert "--chunk-size" in result.stdout, "Help should show --chunk-size flag"
|
||||
@@ -41,9 +38,7 @@ class TestParserSync:
|
||||
def test_scrape_verbose_flag_works(self):
|
||||
"""Test that --verbose flag (previously missing) now works."""
|
||||
result = subprocess.run(
|
||||
["skill-seekers", "scrape", "--help"],
|
||||
capture_output=True,
|
||||
text=True
|
||||
["skill-seekers", "scrape", "--help"], capture_output=True, text=True
|
||||
)
|
||||
assert "--verbose" in result.stdout, "Help should show --verbose flag"
|
||||
assert "-v" in result.stdout, "Help should show short form -v"
|
||||
@@ -51,18 +46,14 @@ class TestParserSync:
|
||||
def test_scrape_url_flag_works(self):
|
||||
"""Test that --url flag (previously missing) now works."""
|
||||
result = subprocess.run(
|
||||
["skill-seekers", "scrape", "--help"],
|
||||
capture_output=True,
|
||||
text=True
|
||||
["skill-seekers", "scrape", "--help"], capture_output=True, text=True
|
||||
)
|
||||
assert "--url URL" in result.stdout, "Help should show --url flag"
|
||||
|
||||
def test_github_all_flags_present(self):
|
||||
"""Test that github command has all expected flags."""
|
||||
result = subprocess.run(
|
||||
["skill-seekers", "github", "--help"],
|
||||
capture_output=True,
|
||||
text=True
|
||||
["skill-seekers", "github", "--help"], capture_output=True, text=True
|
||||
)
|
||||
# Key github flags that should be present
|
||||
expected_flags = [
|
||||
@@ -74,15 +65,14 @@ class TestParserSync:
|
||||
for flag in expected_flags:
|
||||
assert flag in result.stdout, f"Help should show {flag} flag"
|
||||
|
||||
|
||||
class TestPresetSystem:
|
||||
"""E2E tests for preset system (Issue #268)."""
|
||||
|
||||
def test_analyze_preset_flag_exists(self):
|
||||
"""Test that analyze command has --preset flag."""
|
||||
result = subprocess.run(
|
||||
["skill-seekers", "analyze", "--help"],
|
||||
capture_output=True,
|
||||
text=True
|
||||
["skill-seekers", "analyze", "--help"], capture_output=True, text=True
|
||||
)
|
||||
assert "--preset" in result.stdout, "Help should show --preset flag"
|
||||
assert "quick" in result.stdout, "Help should mention 'quick' preset"
|
||||
@@ -92,18 +82,14 @@ class TestPresetSystem:
|
||||
def test_analyze_preset_list_flag_exists(self):
|
||||
"""Test that analyze command has --preset-list flag."""
|
||||
result = subprocess.run(
|
||||
["skill-seekers", "analyze", "--help"],
|
||||
capture_output=True,
|
||||
text=True
|
||||
["skill-seekers", "analyze", "--help"], capture_output=True, text=True
|
||||
)
|
||||
assert "--preset-list" in result.stdout, "Help should show --preset-list flag"
|
||||
|
||||
def test_preset_list_shows_presets(self):
|
||||
"""Test that --preset-list shows all available presets."""
|
||||
result = subprocess.run(
|
||||
["skill-seekers", "analyze", "--preset-list"],
|
||||
capture_output=True,
|
||||
text=True
|
||||
["skill-seekers", "analyze", "--preset-list"], capture_output=True, text=True
|
||||
)
|
||||
assert result.returncode == 0, "Command should execute successfully"
|
||||
assert "Available presets" in result.stdout, "Should show preset list header"
|
||||
@@ -118,7 +104,7 @@ class TestPresetSystem:
|
||||
result = subprocess.run(
|
||||
["skill-seekers", "analyze", "--directory", ".", "--quick"],
|
||||
capture_output=True,
|
||||
text=True
|
||||
text=True,
|
||||
)
|
||||
# Note: Deprecation warnings go to stderr
|
||||
output = result.stdout + result.stderr
|
||||
@@ -131,22 +117,19 @@ class TestPresetSystem:
|
||||
result = subprocess.run(
|
||||
["skill-seekers", "analyze", "--directory", ".", "--comprehensive"],
|
||||
capture_output=True,
|
||||
text=True
|
||||
text=True,
|
||||
)
|
||||
output = result.stdout + result.stderr
|
||||
assert "DEPRECATED" in output, "Should show deprecation warning"
|
||||
assert "--preset comprehensive" in output, "Should suggest alternative"
|
||||
|
||||
|
||||
class TestBackwardCompatibility:
|
||||
"""E2E tests for backward compatibility."""
|
||||
|
||||
def test_old_scrape_command_still_works(self):
|
||||
"""Test that old scrape command invocations still work."""
|
||||
result = subprocess.run(
|
||||
["skill-seekers-scrape", "--help"],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
result = subprocess.run(["skill-seekers-scrape", "--help"], capture_output=True, text=True)
|
||||
assert result.returncode == 0, "Old command should still work"
|
||||
assert "documentation" in result.stdout.lower(), "Help should mention documentation"
|
||||
|
||||
@@ -154,16 +137,12 @@ class TestBackwardCompatibility:
|
||||
"""Test that unified CLI and standalone have identical arguments."""
|
||||
# Get help from unified CLI
|
||||
unified_result = subprocess.run(
|
||||
["skill-seekers", "scrape", "--help"],
|
||||
capture_output=True,
|
||||
text=True
|
||||
["skill-seekers", "scrape", "--help"], capture_output=True, text=True
|
||||
)
|
||||
|
||||
# Get help from standalone
|
||||
standalone_result = subprocess.run(
|
||||
["skill-seekers-scrape", "--help"],
|
||||
capture_output=True,
|
||||
text=True
|
||||
["skill-seekers-scrape", "--help"], capture_output=True, text=True
|
||||
)
|
||||
|
||||
# Both should have the same key flags
|
||||
@@ -180,6 +159,7 @@ class TestBackwardCompatibility:
|
||||
assert flag in unified_result.stdout, f"Unified should have {flag}"
|
||||
assert flag in standalone_result.stdout, f"Standalone should have {flag}"
|
||||
|
||||
|
||||
class TestProgrammaticAPI:
|
||||
"""Test that the shared argument functions work programmatically."""
|
||||
|
||||
@@ -221,16 +201,13 @@ class TestProgrammaticAPI:
|
||||
# Note: enhance_level is not part of AnalysisPreset anymore.
|
||||
# It's controlled separately via --enhance-level flag (default 2)
|
||||
|
||||
|
||||
class TestIntegration:
|
||||
"""Integration tests for the complete flow."""
|
||||
|
||||
def test_unified_cli_subcommands_registered(self):
|
||||
"""Test that all subcommands are properly registered."""
|
||||
result = subprocess.run(
|
||||
["skill-seekers", "--help"],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
result = subprocess.run(["skill-seekers", "--help"], capture_output=True, text=True)
|
||||
|
||||
# All major commands should be listed
|
||||
expected_commands = [
|
||||
@@ -250,9 +227,7 @@ class TestIntegration:
|
||||
def test_scrape_help_detailed(self):
|
||||
"""Test that scrape help shows all argument details."""
|
||||
result = subprocess.run(
|
||||
["skill-seekers", "scrape", "--help"],
|
||||
capture_output=True,
|
||||
text=True
|
||||
["skill-seekers", "scrape", "--help"], capture_output=True, text=True
|
||||
)
|
||||
|
||||
# Check for argument categories
|
||||
@@ -263,13 +238,14 @@ class TestIntegration:
|
||||
def test_analyze_help_shows_presets(self):
|
||||
"""Test that analyze help prominently shows preset information."""
|
||||
result = subprocess.run(
|
||||
["skill-seekers", "analyze", "--help"],
|
||||
capture_output=True,
|
||||
text=True
|
||||
["skill-seekers", "analyze", "--help"], capture_output=True, text=True
|
||||
)
|
||||
|
||||
assert "--preset" in result.stdout, "Should show --preset flag"
|
||||
assert "DEFAULT" in result.stdout or "default" in result.stdout, "Should indicate default preset"
|
||||
assert "DEFAULT" in result.stdout or "default" in result.stdout, (
|
||||
"Should indicate default preset"
|
||||
)
|
||||
|
||||
|
||||
class TestE2EWorkflow:
|
||||
"""End-to-end workflow tests."""
|
||||
@@ -279,15 +255,18 @@ class TestE2EWorkflow:
|
||||
"""Test scraping with previously missing arguments (dry run)."""
|
||||
result = subprocess.run(
|
||||
[
|
||||
"skill-seekers", "scrape",
|
||||
"--url", "https://example.com",
|
||||
"--interactive", "false", # Would fail if arg didn't exist
|
||||
"skill-seekers",
|
||||
"scrape",
|
||||
"--url",
|
||||
"https://example.com",
|
||||
"--interactive",
|
||||
"false", # Would fail if arg didn't exist
|
||||
"--verbose", # Would fail if arg didn't exist
|
||||
"--dry-run",
|
||||
],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=10
|
||||
timeout=10,
|
||||
)
|
||||
|
||||
# Dry run should complete without errors
|
||||
@@ -314,5 +293,6 @@ class TestE2EWorkflow:
|
||||
assert "--preset" in result.stdout, "Should have --preset flag"
|
||||
assert "unrecognized arguments" not in result.stderr.lower()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pytest.main([__file__, "-v", "-s"])
|
||||
|
||||
@@ -19,6 +19,7 @@ from skill_seekers.cli.arguments.create import (
|
||||
add_create_arguments,
|
||||
)
|
||||
|
||||
|
||||
class TestUniversalArguments:
|
||||
"""Test universal argument definitions."""
|
||||
|
||||
@@ -29,25 +30,34 @@ class TestUniversalArguments:
|
||||
def test_universal_argument_names(self):
|
||||
"""Universal arguments should have expected names."""
|
||||
expected_names = {
|
||||
'name', 'description', 'output',
|
||||
'enhance_level', 'api_key', # Phase 1: consolidated from enhance + enhance_local
|
||||
'dry_run', 'verbose', 'quiet',
|
||||
'chunk_for_rag', 'chunk_size', 'chunk_overlap', # Phase 2: RAG args from common.py
|
||||
'preset', 'config'
|
||||
"name",
|
||||
"description",
|
||||
"output",
|
||||
"enhance_level",
|
||||
"api_key", # Phase 1: consolidated from enhance + enhance_local
|
||||
"dry_run",
|
||||
"verbose",
|
||||
"quiet",
|
||||
"chunk_for_rag",
|
||||
"chunk_size",
|
||||
"chunk_overlap", # Phase 2: RAG args from common.py
|
||||
"preset",
|
||||
"config",
|
||||
}
|
||||
assert set(UNIVERSAL_ARGUMENTS.keys()) == expected_names
|
||||
|
||||
def test_all_universal_have_flags(self):
|
||||
"""All universal arguments should have flags."""
|
||||
for arg_name, arg_def in UNIVERSAL_ARGUMENTS.items():
|
||||
assert 'flags' in arg_def
|
||||
assert len(arg_def['flags']) > 0
|
||||
assert "flags" in arg_def
|
||||
assert len(arg_def["flags"]) > 0
|
||||
|
||||
def test_all_universal_have_kwargs(self):
|
||||
"""All universal arguments should have kwargs."""
|
||||
for arg_name, arg_def in UNIVERSAL_ARGUMENTS.items():
|
||||
assert 'kwargs' in arg_def
|
||||
assert 'help' in arg_def['kwargs']
|
||||
assert "kwargs" in arg_def
|
||||
assert "help" in arg_def["kwargs"]
|
||||
|
||||
|
||||
class TestSourceSpecificArguments:
|
||||
"""Test source-specific argument definitions."""
|
||||
@@ -55,29 +65,29 @@ class TestSourceSpecificArguments:
|
||||
def test_web_arguments_exist(self):
|
||||
"""Web-specific arguments should be defined."""
|
||||
assert len(WEB_ARGUMENTS) > 0
|
||||
assert 'max_pages' in WEB_ARGUMENTS
|
||||
assert 'rate_limit' in WEB_ARGUMENTS
|
||||
assert 'workers' in WEB_ARGUMENTS
|
||||
assert "max_pages" in WEB_ARGUMENTS
|
||||
assert "rate_limit" in WEB_ARGUMENTS
|
||||
assert "workers" in WEB_ARGUMENTS
|
||||
|
||||
def test_github_arguments_exist(self):
|
||||
"""GitHub-specific arguments should be defined."""
|
||||
assert len(GITHUB_ARGUMENTS) > 0
|
||||
assert 'repo' in GITHUB_ARGUMENTS
|
||||
assert 'token' in GITHUB_ARGUMENTS
|
||||
assert 'max_issues' in GITHUB_ARGUMENTS
|
||||
assert "repo" in GITHUB_ARGUMENTS
|
||||
assert "token" in GITHUB_ARGUMENTS
|
||||
assert "max_issues" in GITHUB_ARGUMENTS
|
||||
|
||||
def test_local_arguments_exist(self):
|
||||
"""Local-specific arguments should be defined."""
|
||||
assert len(LOCAL_ARGUMENTS) > 0
|
||||
assert 'directory' in LOCAL_ARGUMENTS
|
||||
assert 'languages' in LOCAL_ARGUMENTS
|
||||
assert 'skip_patterns' in LOCAL_ARGUMENTS
|
||||
assert "directory" in LOCAL_ARGUMENTS
|
||||
assert "languages" in LOCAL_ARGUMENTS
|
||||
assert "skip_patterns" in LOCAL_ARGUMENTS
|
||||
|
||||
def test_pdf_arguments_exist(self):
|
||||
"""PDF-specific arguments should be defined."""
|
||||
assert len(PDF_ARGUMENTS) > 0
|
||||
assert 'pdf' in PDF_ARGUMENTS
|
||||
assert 'ocr' in PDF_ARGUMENTS
|
||||
assert "pdf" in PDF_ARGUMENTS
|
||||
assert "ocr" in PDF_ARGUMENTS
|
||||
|
||||
def test_no_duplicate_flags_across_sources(self):
|
||||
"""Source-specific arguments should not have duplicate flags."""
|
||||
@@ -86,21 +96,25 @@ class TestSourceSpecificArguments:
|
||||
|
||||
for source_args in [WEB_ARGUMENTS, GITHUB_ARGUMENTS, LOCAL_ARGUMENTS, PDF_ARGUMENTS]:
|
||||
for arg_name, arg_def in source_args.items():
|
||||
flags = arg_def['flags']
|
||||
flags = arg_def["flags"]
|
||||
for flag in flags:
|
||||
# Check if this flag already exists in source-specific args
|
||||
if flag not in [f for arg in UNIVERSAL_ARGUMENTS.values() for f in arg['flags']]:
|
||||
if flag not in [
|
||||
f for arg in UNIVERSAL_ARGUMENTS.values() for f in arg["flags"]
|
||||
]:
|
||||
assert flag not in all_flags, f"Duplicate flag: {flag}"
|
||||
all_flags.add(flag)
|
||||
|
||||
|
||||
class TestAdvancedArguments:
|
||||
"""Test advanced/rare argument definitions."""
|
||||
|
||||
def test_advanced_arguments_exist(self):
|
||||
"""Advanced arguments should be defined."""
|
||||
assert len(ADVANCED_ARGUMENTS) > 0
|
||||
assert 'no_rate_limit' in ADVANCED_ARGUMENTS
|
||||
assert 'interactive_enhancement' in ADVANCED_ARGUMENTS
|
||||
assert "no_rate_limit" in ADVANCED_ARGUMENTS
|
||||
assert "interactive_enhancement" in ADVANCED_ARGUMENTS
|
||||
|
||||
|
||||
class TestArgumentHelpers:
|
||||
"""Test helper functions."""
|
||||
@@ -110,106 +124,108 @@ class TestArgumentHelpers:
|
||||
names = get_universal_argument_names()
|
||||
assert isinstance(names, set)
|
||||
assert len(names) == 13
|
||||
assert 'name' in names
|
||||
assert 'enhance_level' in names # Phase 1: consolidated flag
|
||||
assert "name" in names
|
||||
assert "enhance_level" in names # Phase 1: consolidated flag
|
||||
|
||||
def test_get_source_specific_web(self):
|
||||
"""Should return web-specific arguments."""
|
||||
args = get_source_specific_arguments('web')
|
||||
args = get_source_specific_arguments("web")
|
||||
assert args == WEB_ARGUMENTS
|
||||
|
||||
def test_get_source_specific_github(self):
|
||||
"""Should return github-specific arguments."""
|
||||
args = get_source_specific_arguments('github')
|
||||
args = get_source_specific_arguments("github")
|
||||
assert args == GITHUB_ARGUMENTS
|
||||
|
||||
def test_get_source_specific_local(self):
|
||||
"""Should return local-specific arguments."""
|
||||
args = get_source_specific_arguments('local')
|
||||
args = get_source_specific_arguments("local")
|
||||
assert args == LOCAL_ARGUMENTS
|
||||
|
||||
def test_get_source_specific_pdf(self):
|
||||
"""Should return pdf-specific arguments."""
|
||||
args = get_source_specific_arguments('pdf')
|
||||
args = get_source_specific_arguments("pdf")
|
||||
assert args == PDF_ARGUMENTS
|
||||
|
||||
def test_get_source_specific_config(self):
|
||||
"""Config should return empty dict (no extra args)."""
|
||||
args = get_source_specific_arguments('config')
|
||||
args = get_source_specific_arguments("config")
|
||||
assert args == {}
|
||||
|
||||
def test_get_source_specific_unknown(self):
|
||||
"""Unknown source should return empty dict."""
|
||||
args = get_source_specific_arguments('unknown')
|
||||
args = get_source_specific_arguments("unknown")
|
||||
assert args == {}
|
||||
|
||||
|
||||
class TestCompatibleArguments:
|
||||
"""Test compatible argument detection."""
|
||||
|
||||
def test_web_compatible_arguments(self):
|
||||
"""Web source should include universal + web + advanced."""
|
||||
compatible = get_compatible_arguments('web')
|
||||
compatible = get_compatible_arguments("web")
|
||||
|
||||
# Should include universal arguments
|
||||
assert 'name' in compatible
|
||||
assert 'enhance_level' in compatible # Phase 1: consolidated flag
|
||||
assert "name" in compatible
|
||||
assert "enhance_level" in compatible # Phase 1: consolidated flag
|
||||
|
||||
# Should include web-specific arguments
|
||||
assert 'max_pages' in compatible
|
||||
assert 'rate_limit' in compatible
|
||||
assert "max_pages" in compatible
|
||||
assert "rate_limit" in compatible
|
||||
|
||||
# Should include advanced arguments
|
||||
assert 'no_rate_limit' in compatible
|
||||
assert "no_rate_limit" in compatible
|
||||
|
||||
def test_github_compatible_arguments(self):
|
||||
"""GitHub source should include universal + github + advanced."""
|
||||
compatible = get_compatible_arguments('github')
|
||||
compatible = get_compatible_arguments("github")
|
||||
|
||||
# Should include universal arguments
|
||||
assert 'name' in compatible
|
||||
assert "name" in compatible
|
||||
|
||||
# Should include github-specific arguments
|
||||
assert 'repo' in compatible
|
||||
assert 'token' in compatible
|
||||
assert "repo" in compatible
|
||||
assert "token" in compatible
|
||||
|
||||
# Should include advanced arguments
|
||||
assert 'interactive_enhancement' in compatible
|
||||
assert "interactive_enhancement" in compatible
|
||||
|
||||
def test_local_compatible_arguments(self):
|
||||
"""Local source should include universal + local + advanced."""
|
||||
compatible = get_compatible_arguments('local')
|
||||
compatible = get_compatible_arguments("local")
|
||||
|
||||
# Should include universal arguments
|
||||
assert 'description' in compatible
|
||||
assert "description" in compatible
|
||||
|
||||
# Should include local-specific arguments
|
||||
assert 'directory' in compatible
|
||||
assert 'languages' in compatible
|
||||
assert "directory" in compatible
|
||||
assert "languages" in compatible
|
||||
|
||||
def test_pdf_compatible_arguments(self):
|
||||
"""PDF source should include universal + pdf + advanced."""
|
||||
compatible = get_compatible_arguments('pdf')
|
||||
compatible = get_compatible_arguments("pdf")
|
||||
|
||||
# Should include universal arguments
|
||||
assert 'output' in compatible
|
||||
assert "output" in compatible
|
||||
|
||||
# Should include pdf-specific arguments
|
||||
assert 'pdf' in compatible
|
||||
assert 'ocr' in compatible
|
||||
assert "pdf" in compatible
|
||||
assert "ocr" in compatible
|
||||
|
||||
def test_config_compatible_arguments(self):
|
||||
"""Config source should include universal + advanced only."""
|
||||
compatible = get_compatible_arguments('config')
|
||||
compatible = get_compatible_arguments("config")
|
||||
|
||||
# Should include universal arguments
|
||||
assert 'config' in compatible
|
||||
assert "config" in compatible
|
||||
|
||||
# Should include advanced arguments
|
||||
assert 'no_preserve_code_blocks' in compatible
|
||||
assert "no_preserve_code_blocks" in compatible
|
||||
|
||||
# Should not include source-specific arguments
|
||||
assert 'repo' not in compatible
|
||||
assert 'directory' not in compatible
|
||||
assert "repo" not in compatible
|
||||
assert "directory" not in compatible
|
||||
|
||||
|
||||
class TestAddCreateArguments:
|
||||
"""Test add_create_arguments function."""
|
||||
@@ -217,16 +233,17 @@ class TestAddCreateArguments:
|
||||
def test_default_mode_adds_universal_only(self):
|
||||
"""Default mode should add only universal arguments + source positional."""
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
add_create_arguments(parser, mode='default')
|
||||
add_create_arguments(parser, mode="default")
|
||||
|
||||
# Parse to get all arguments
|
||||
args = vars(parser.parse_args([]))
|
||||
|
||||
# Should have universal arguments
|
||||
assert 'name' in args
|
||||
assert 'enhance_level' in args
|
||||
assert 'chunk_for_rag' in args
|
||||
assert "name" in args
|
||||
assert "enhance_level" in args
|
||||
assert "chunk_for_rag" in args
|
||||
|
||||
# Should not have source-specific arguments (they're not added in default mode)
|
||||
# Note: argparse won't error on unknown args, but they won't be in namespace
|
||||
@@ -234,62 +251,60 @@ class TestAddCreateArguments:
|
||||
def test_web_mode_adds_web_arguments(self):
|
||||
"""Web mode should add universal + web arguments."""
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
add_create_arguments(parser, mode='web')
|
||||
add_create_arguments(parser, mode="web")
|
||||
|
||||
args = vars(parser.parse_args([]))
|
||||
|
||||
# Should have universal arguments
|
||||
assert 'name' in args
|
||||
assert "name" in args
|
||||
|
||||
# Should have web-specific arguments
|
||||
assert 'max_pages' in args
|
||||
assert 'rate_limit' in args
|
||||
assert "max_pages" in args
|
||||
assert "rate_limit" in args
|
||||
|
||||
def test_all_mode_adds_all_arguments(self):
|
||||
"""All mode should add every argument."""
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
add_create_arguments(parser, mode='all')
|
||||
add_create_arguments(parser, mode="all")
|
||||
|
||||
args = vars(parser.parse_args([]))
|
||||
|
||||
# Should have universal arguments
|
||||
assert 'name' in args
|
||||
assert "name" in args
|
||||
|
||||
# Should have all source-specific arguments
|
||||
assert 'max_pages' in args # web
|
||||
assert 'repo' in args # github
|
||||
assert 'directory' in args # local
|
||||
assert 'pdf' in args # pdf
|
||||
assert "max_pages" in args # web
|
||||
assert "repo" in args # github
|
||||
assert "directory" in args # local
|
||||
assert "pdf" in args # pdf
|
||||
|
||||
# Should have advanced arguments
|
||||
assert 'no_rate_limit' in args
|
||||
assert "no_rate_limit" in args
|
||||
|
||||
def test_positional_source_argument_always_added(self):
|
||||
"""Source positional argument should always be added."""
|
||||
import argparse
|
||||
for mode in ['default', 'web', 'github', 'local', 'pdf', 'all']:
|
||||
|
||||
for mode in ["default", "web", "github", "local", "pdf", "all"]:
|
||||
parser = argparse.ArgumentParser()
|
||||
add_create_arguments(parser, mode=mode)
|
||||
|
||||
# Should accept source as positional
|
||||
args = parser.parse_args(['some_source'])
|
||||
assert args.source == 'some_source'
|
||||
args = parser.parse_args(["some_source"])
|
||||
assert args.source == "some_source"
|
||||
|
||||
|
||||
class TestNoDuplicates:
|
||||
"""Test that there are no duplicate arguments across tiers."""
|
||||
|
||||
def test_no_duplicates_between_universal_and_web(self):
|
||||
"""Universal and web args should not overlap."""
|
||||
universal_flags = {
|
||||
flag for arg in UNIVERSAL_ARGUMENTS.values()
|
||||
for flag in arg['flags']
|
||||
}
|
||||
web_flags = {
|
||||
flag for arg in WEB_ARGUMENTS.values()
|
||||
for flag in arg['flags']
|
||||
}
|
||||
universal_flags = {flag for arg in UNIVERSAL_ARGUMENTS.values() for flag in arg["flags"]}
|
||||
web_flags = {flag for arg in WEB_ARGUMENTS.values() for flag in arg["flags"]}
|
||||
|
||||
# Allow some overlap since we intentionally include common args
|
||||
# in multiple places, but check that they're properly defined
|
||||
@@ -299,10 +314,10 @@ class TestNoDuplicates:
|
||||
|
||||
def test_no_duplicates_between_source_specific_args(self):
|
||||
"""Different source-specific arg groups should not overlap."""
|
||||
web_flags = {flag for arg in WEB_ARGUMENTS.values() for flag in arg['flags']}
|
||||
github_flags = {flag for arg in GITHUB_ARGUMENTS.values() for flag in arg['flags']}
|
||||
local_flags = {flag for arg in LOCAL_ARGUMENTS.values() for flag in arg['flags']}
|
||||
pdf_flags = {flag for arg in PDF_ARGUMENTS.values() for flag in arg['flags']}
|
||||
web_flags = {flag for arg in WEB_ARGUMENTS.values() for flag in arg["flags"]}
|
||||
github_flags = {flag for arg in GITHUB_ARGUMENTS.values() for flag in arg["flags"]}
|
||||
local_flags = {flag for arg in LOCAL_ARGUMENTS.values() for flag in arg["flags"]}
|
||||
pdf_flags = {flag for arg in PDF_ARGUMENTS.values() for flag in arg["flags"]}
|
||||
|
||||
# No overlap between different source types
|
||||
assert len(web_flags & github_flags) == 0
|
||||
@@ -312,6 +327,7 @@ class TestNoDuplicates:
|
||||
assert len(github_flags & pdf_flags) == 0
|
||||
assert len(local_flags & pdf_flags) == 0
|
||||
|
||||
|
||||
class TestArgumentQuality:
|
||||
"""Test argument definition quality."""
|
||||
|
||||
@@ -327,8 +343,8 @@ class TestArgumentQuality:
|
||||
}
|
||||
|
||||
for arg_name, arg_def in all_args.items():
|
||||
assert 'help' in arg_def['kwargs'], f"{arg_name} missing help text"
|
||||
assert len(arg_def['kwargs']['help']) > 0, f"{arg_name} has empty help text"
|
||||
assert "help" in arg_def["kwargs"], f"{arg_name} missing help text"
|
||||
assert len(arg_def["kwargs"]["help"]) > 0, f"{arg_name} has empty help text"
|
||||
|
||||
def test_boolean_arguments_use_store_true(self):
|
||||
"""Boolean flags should use store_true action."""
|
||||
@@ -342,13 +358,25 @@ class TestArgumentQuality:
|
||||
}
|
||||
|
||||
boolean_args = [
|
||||
'dry_run', 'verbose', 'quiet',
|
||||
'chunk_for_rag', 'skip_scrape', 'resume', 'fresh', 'async_mode',
|
||||
'no_issues', 'no_changelog', 'no_releases', 'scrape_only',
|
||||
'skip_patterns', 'skip_test_examples', 'ocr', 'no_rate_limit'
|
||||
"dry_run",
|
||||
"verbose",
|
||||
"quiet",
|
||||
"chunk_for_rag",
|
||||
"skip_scrape",
|
||||
"resume",
|
||||
"fresh",
|
||||
"async_mode",
|
||||
"no_issues",
|
||||
"no_changelog",
|
||||
"no_releases",
|
||||
"scrape_only",
|
||||
"skip_patterns",
|
||||
"skip_test_examples",
|
||||
"ocr",
|
||||
"no_rate_limit",
|
||||
]
|
||||
|
||||
for arg_name in boolean_args:
|
||||
if arg_name in all_args:
|
||||
action = all_args[arg_name]['kwargs'].get('action')
|
||||
assert action == 'store_true', f"{arg_name} should use store_true"
|
||||
action = all_args[arg_name]["kwargs"].get("action")
|
||||
assert action == "store_true", f"{arg_name} should use store_true"
|
||||
|
||||
@@ -6,21 +6,21 @@ and routes to the correct scrapers without actually scraping.
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
class TestCreateCommandBasic:
|
||||
"""Basic integration tests for create command (dry-run mode)."""
|
||||
|
||||
def test_create_command_help(self):
|
||||
"""Test that create command help works."""
|
||||
import subprocess
|
||||
|
||||
result = subprocess.run(
|
||||
['skill-seekers', 'create', '--help'],
|
||||
capture_output=True,
|
||||
text=True
|
||||
["skill-seekers", "create", "--help"], capture_output=True, text=True
|
||||
)
|
||||
assert result.returncode == 0
|
||||
assert 'Auto-detects source type' in result.stdout
|
||||
assert 'auto-detected' in result.stdout
|
||||
assert '--help-web' in result.stdout
|
||||
assert "Auto-detects source type" in result.stdout
|
||||
assert "auto-detected" in result.stdout
|
||||
assert "--help-web" in result.stdout
|
||||
|
||||
def test_create_detects_web_url(self):
|
||||
"""Test that web URLs are detected and routed correctly."""
|
||||
@@ -31,11 +31,12 @@ class TestCreateCommandBasic:
|
||||
def test_create_detects_github_repo(self):
|
||||
"""Test that GitHub repos are detected."""
|
||||
import subprocess
|
||||
|
||||
result = subprocess.run(
|
||||
['skill-seekers', 'create', 'facebook/react', '--help'],
|
||||
["skill-seekers", "create", "facebook/react", "--help"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=10
|
||||
timeout=10,
|
||||
)
|
||||
# Just verify help works - actual scraping would need API token
|
||||
assert result.returncode in [0, 2] # 0 for success, 2 for argparse help
|
||||
@@ -49,10 +50,10 @@ class TestCreateCommandBasic:
|
||||
test_dir.mkdir()
|
||||
|
||||
result = subprocess.run(
|
||||
['skill-seekers', 'create', str(test_dir), '--help'],
|
||||
["skill-seekers", "create", str(test_dir), "--help"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=10
|
||||
timeout=10,
|
||||
)
|
||||
# Verify help works
|
||||
assert result.returncode in [0, 2]
|
||||
@@ -66,10 +67,10 @@ class TestCreateCommandBasic:
|
||||
pdf_file.touch()
|
||||
|
||||
result = subprocess.run(
|
||||
['skill-seekers', 'create', str(pdf_file), '--help'],
|
||||
["skill-seekers", "create", str(pdf_file), "--help"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=10
|
||||
timeout=10,
|
||||
)
|
||||
# Verify help works
|
||||
assert result.returncode in [0, 2]
|
||||
@@ -81,17 +82,14 @@ class TestCreateCommandBasic:
|
||||
|
||||
# Create a minimal config file
|
||||
config_file = tmp_path / "test.json"
|
||||
config_data = {
|
||||
"name": "test",
|
||||
"base_url": "https://example.com/"
|
||||
}
|
||||
config_data = {"name": "test", "base_url": "https://example.com/"}
|
||||
config_file.write_text(json.dumps(config_data))
|
||||
|
||||
result = subprocess.run(
|
||||
['skill-seekers', 'create', str(config_file), '--help'],
|
||||
["skill-seekers", "create", str(config_file), "--help"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=10
|
||||
timeout=10,
|
||||
)
|
||||
# Verify help works
|
||||
assert result.returncode in [0, 2]
|
||||
@@ -105,20 +103,19 @@ class TestCreateCommandBasic:
|
||||
def test_create_supports_universal_flags(self):
|
||||
"""Test that universal flags are accepted."""
|
||||
import subprocess
|
||||
|
||||
result = subprocess.run(
|
||||
['skill-seekers', 'create', '--help'],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=10
|
||||
["skill-seekers", "create", "--help"], capture_output=True, text=True, timeout=10
|
||||
)
|
||||
assert result.returncode == 0
|
||||
|
||||
# Check that universal flags are present
|
||||
assert '--name' in result.stdout
|
||||
assert '--enhance' in result.stdout
|
||||
assert '--chunk-for-rag' in result.stdout
|
||||
assert '--preset' in result.stdout
|
||||
assert '--dry-run' in result.stdout
|
||||
assert "--name" in result.stdout
|
||||
assert "--enhance" in result.stdout
|
||||
assert "--chunk-for-rag" in result.stdout
|
||||
assert "--preset" in result.stdout
|
||||
assert "--dry-run" in result.stdout
|
||||
|
||||
|
||||
class TestBackwardCompatibility:
|
||||
"""Test that old commands still work."""
|
||||
@@ -126,53 +123,45 @@ class TestBackwardCompatibility:
|
||||
def test_scrape_command_still_works(self):
|
||||
"""Old scrape command should still function."""
|
||||
import subprocess
|
||||
|
||||
result = subprocess.run(
|
||||
['skill-seekers', 'scrape', '--help'],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=10
|
||||
["skill-seekers", "scrape", "--help"], capture_output=True, text=True, timeout=10
|
||||
)
|
||||
assert result.returncode == 0
|
||||
assert 'scrape' in result.stdout.lower()
|
||||
assert "scrape" in result.stdout.lower()
|
||||
|
||||
def test_github_command_still_works(self):
|
||||
"""Old github command should still function."""
|
||||
import subprocess
|
||||
|
||||
result = subprocess.run(
|
||||
['skill-seekers', 'github', '--help'],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=10
|
||||
["skill-seekers", "github", "--help"], capture_output=True, text=True, timeout=10
|
||||
)
|
||||
assert result.returncode == 0
|
||||
assert 'github' in result.stdout.lower()
|
||||
assert "github" in result.stdout.lower()
|
||||
|
||||
def test_analyze_command_still_works(self):
|
||||
"""Old analyze command should still function."""
|
||||
import subprocess
|
||||
|
||||
result = subprocess.run(
|
||||
['skill-seekers', 'analyze', '--help'],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=10
|
||||
["skill-seekers", "analyze", "--help"], capture_output=True, text=True, timeout=10
|
||||
)
|
||||
assert result.returncode == 0
|
||||
assert 'analyze' in result.stdout.lower()
|
||||
assert "analyze" in result.stdout.lower()
|
||||
|
||||
def test_main_help_shows_all_commands(self):
|
||||
"""Main help should show both old and new commands."""
|
||||
import subprocess
|
||||
|
||||
result = subprocess.run(
|
||||
['skill-seekers', '--help'],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=10
|
||||
["skill-seekers", "--help"], capture_output=True, text=True, timeout=10
|
||||
)
|
||||
assert result.returncode == 0
|
||||
# Should show create command
|
||||
assert 'create' in result.stdout
|
||||
assert "create" in result.stdout
|
||||
|
||||
# Should still show old commands
|
||||
assert 'scrape' in result.stdout
|
||||
assert 'github' in result.stdout
|
||||
assert 'analyze' in result.stdout
|
||||
assert "scrape" in result.stdout
|
||||
assert "github" in result.stdout
|
||||
assert "analyze" in result.stdout
|
||||
|
||||
@@ -167,7 +167,8 @@ class TestIssue219Problem2CLIFlags(unittest.TestCase):
|
||||
"test/test",
|
||||
"--name",
|
||||
"test",
|
||||
"--enhance-level", "2",
|
||||
"--enhance-level",
|
||||
"2",
|
||||
]
|
||||
|
||||
with (
|
||||
|
||||
@@ -7,6 +7,7 @@ the same arguments as the standalone scraper modules. This prevents the
|
||||
|
||||
import argparse
|
||||
|
||||
|
||||
class TestScrapeParserSync:
|
||||
"""Ensure scrape_parser has all arguments from doc_scraper."""
|
||||
|
||||
@@ -17,12 +18,12 @@ class TestScrapeParserSync:
|
||||
|
||||
# Get source arguments from doc_scraper
|
||||
source_parser = setup_argument_parser()
|
||||
source_count = len([a for a in source_parser._actions if a.dest != 'help'])
|
||||
source_count = len([a for a in source_parser._actions if a.dest != "help"])
|
||||
|
||||
# Get target arguments from unified CLI parser
|
||||
target_parser = argparse.ArgumentParser()
|
||||
ScrapeParser().add_arguments(target_parser)
|
||||
target_count = len([a for a in target_parser._actions if a.dest != 'help'])
|
||||
target_count = len([a for a in target_parser._actions if a.dest != "help"])
|
||||
|
||||
assert source_count == target_count, (
|
||||
f"Argument count mismatch: doc_scraper has {source_count}, "
|
||||
@@ -36,12 +37,12 @@ class TestScrapeParserSync:
|
||||
|
||||
# Get source arguments from doc_scraper
|
||||
source_parser = setup_argument_parser()
|
||||
source_dests = {a.dest for a in source_parser._actions if a.dest != 'help'}
|
||||
source_dests = {a.dest for a in source_parser._actions if a.dest != "help"}
|
||||
|
||||
# Get target arguments from unified CLI parser
|
||||
target_parser = argparse.ArgumentParser()
|
||||
ScrapeParser().add_arguments(target_parser)
|
||||
target_dests = {a.dest for a in target_parser._actions if a.dest != 'help'}
|
||||
target_dests = {a.dest for a in target_parser._actions if a.dest != "help"}
|
||||
|
||||
# Check for missing arguments
|
||||
missing = source_dests - target_dests
|
||||
@@ -64,27 +65,28 @@ class TestScrapeParserSync:
|
||||
break
|
||||
|
||||
assert subparsers_action is not None, "No subparsers found"
|
||||
assert 'scrape' in subparsers_action.choices, "scrape subparser not found"
|
||||
assert "scrape" in subparsers_action.choices, "scrape subparser not found"
|
||||
|
||||
scrape_parser = subparsers_action.choices['scrape']
|
||||
arg_dests = {a.dest for a in scrape_parser._actions if a.dest != 'help'}
|
||||
scrape_parser = subparsers_action.choices["scrape"]
|
||||
arg_dests = {a.dest for a in scrape_parser._actions if a.dest != "help"}
|
||||
|
||||
# Check key arguments that were missing in Issue #285
|
||||
required_args = [
|
||||
'interactive',
|
||||
'url',
|
||||
'verbose',
|
||||
'quiet',
|
||||
'resume',
|
||||
'fresh',
|
||||
'rate_limit',
|
||||
'no_rate_limit',
|
||||
'chunk_for_rag',
|
||||
"interactive",
|
||||
"url",
|
||||
"verbose",
|
||||
"quiet",
|
||||
"resume",
|
||||
"fresh",
|
||||
"rate_limit",
|
||||
"no_rate_limit",
|
||||
"chunk_for_rag",
|
||||
]
|
||||
|
||||
for arg in required_args:
|
||||
assert arg in arg_dests, f"Required argument '{arg}' missing from scrape parser"
|
||||
|
||||
|
||||
class TestGitHubParserSync:
|
||||
"""Ensure github_parser has all arguments from github_scraper."""
|
||||
|
||||
@@ -95,12 +97,12 @@ class TestGitHubParserSync:
|
||||
|
||||
# Get source arguments from github_scraper
|
||||
source_parser = setup_argument_parser()
|
||||
source_count = len([a for a in source_parser._actions if a.dest != 'help'])
|
||||
source_count = len([a for a in source_parser._actions if a.dest != "help"])
|
||||
|
||||
# Get target arguments from unified CLI parser
|
||||
target_parser = argparse.ArgumentParser()
|
||||
GitHubParser().add_arguments(target_parser)
|
||||
target_count = len([a for a in target_parser._actions if a.dest != 'help'])
|
||||
target_count = len([a for a in target_parser._actions if a.dest != "help"])
|
||||
|
||||
assert source_count == target_count, (
|
||||
f"Argument count mismatch: github_scraper has {source_count}, "
|
||||
@@ -114,12 +116,12 @@ class TestGitHubParserSync:
|
||||
|
||||
# Get source arguments from github_scraper
|
||||
source_parser = setup_argument_parser()
|
||||
source_dests = {a.dest for a in source_parser._actions if a.dest != 'help'}
|
||||
source_dests = {a.dest for a in source_parser._actions if a.dest != "help"}
|
||||
|
||||
# Get target arguments from unified CLI parser
|
||||
target_parser = argparse.ArgumentParser()
|
||||
GitHubParser().add_arguments(target_parser)
|
||||
target_dests = {a.dest for a in target_parser._actions if a.dest != 'help'}
|
||||
target_dests = {a.dest for a in target_parser._actions if a.dest != "help"}
|
||||
|
||||
# Check for missing arguments
|
||||
missing = source_dests - target_dests
|
||||
@@ -128,6 +130,7 @@ class TestGitHubParserSync:
|
||||
assert not missing, f"github_parser missing arguments: {missing}"
|
||||
assert not extra, f"github_parser has extra arguments not in github_scraper: {extra}"
|
||||
|
||||
|
||||
class TestUnifiedCLI:
|
||||
"""Test the unified CLI main parser."""
|
||||
|
||||
@@ -154,7 +157,7 @@ class TestUnifiedCLI:
|
||||
assert subparsers_action is not None, "No subparsers found"
|
||||
|
||||
# Check expected subcommands
|
||||
expected_commands = ['scrape', 'github']
|
||||
expected_commands = ["scrape", "github"]
|
||||
for cmd in expected_commands:
|
||||
assert cmd in subparsers_action.choices, f"Subcommand '{cmd}' not found"
|
||||
|
||||
@@ -166,7 +169,7 @@ class TestUnifiedCLI:
|
||||
|
||||
# This should not raise an exception
|
||||
try:
|
||||
parser.parse_args(['scrape', '--help'])
|
||||
parser.parse_args(["scrape", "--help"])
|
||||
except SystemExit as e:
|
||||
# --help causes SystemExit(0) which is expected
|
||||
assert e.code == 0
|
||||
@@ -179,7 +182,7 @@ class TestUnifiedCLI:
|
||||
|
||||
# This should not raise an exception
|
||||
try:
|
||||
parser.parse_args(['github', '--help'])
|
||||
parser.parse_args(["github", "--help"])
|
||||
except SystemExit as e:
|
||||
# --help causes SystemExit(0) which is expected
|
||||
assert e.code == 0
|
||||
|
||||
@@ -13,47 +13,49 @@ import pytest
|
||||
|
||||
from skill_seekers.cli.source_detector import SourceDetector, SourceInfo
|
||||
|
||||
|
||||
class TestWebDetection:
|
||||
"""Test web URL detection."""
|
||||
|
||||
def test_detect_full_https_url(self):
|
||||
"""Full HTTPS URL should be detected as web."""
|
||||
info = SourceDetector.detect("https://docs.react.dev/")
|
||||
assert info.type == 'web'
|
||||
assert info.parsed['url'] == "https://docs.react.dev/"
|
||||
assert info.suggested_name == 'react'
|
||||
assert info.type == "web"
|
||||
assert info.parsed["url"] == "https://docs.react.dev/"
|
||||
assert info.suggested_name == "react"
|
||||
|
||||
def test_detect_full_http_url(self):
|
||||
"""Full HTTP URL should be detected as web."""
|
||||
info = SourceDetector.detect("http://example.com/docs")
|
||||
assert info.type == 'web'
|
||||
assert info.parsed['url'] == "http://example.com/docs"
|
||||
assert info.type == "web"
|
||||
assert info.parsed["url"] == "http://example.com/docs"
|
||||
|
||||
def test_detect_domain_only(self):
|
||||
"""Domain without protocol should add https:// and detect as web."""
|
||||
info = SourceDetector.detect("docs.react.dev")
|
||||
assert info.type == 'web'
|
||||
assert info.parsed['url'] == "https://docs.react.dev"
|
||||
assert info.suggested_name == 'react'
|
||||
assert info.type == "web"
|
||||
assert info.parsed["url"] == "https://docs.react.dev"
|
||||
assert info.suggested_name == "react"
|
||||
|
||||
def test_detect_complex_url(self):
|
||||
"""Complex URL with path should be detected as web."""
|
||||
info = SourceDetector.detect("https://docs.python.org/3/library/")
|
||||
assert info.type == 'web'
|
||||
assert info.parsed['url'] == "https://docs.python.org/3/library/"
|
||||
assert info.suggested_name == 'python'
|
||||
assert info.type == "web"
|
||||
assert info.parsed["url"] == "https://docs.python.org/3/library/"
|
||||
assert info.suggested_name == "python"
|
||||
|
||||
def test_suggested_name_removes_www(self):
|
||||
"""Should remove www. prefix from suggested name."""
|
||||
info = SourceDetector.detect("https://www.example.com/")
|
||||
assert info.type == 'web'
|
||||
assert info.suggested_name == 'example'
|
||||
assert info.type == "web"
|
||||
assert info.suggested_name == "example"
|
||||
|
||||
def test_suggested_name_removes_docs(self):
|
||||
"""Should remove docs. prefix from suggested name."""
|
||||
info = SourceDetector.detect("https://docs.vue.org/")
|
||||
assert info.type == 'web'
|
||||
assert info.suggested_name == 'vue'
|
||||
assert info.type == "web"
|
||||
assert info.suggested_name == "vue"
|
||||
|
||||
|
||||
class TestGitHubDetection:
|
||||
"""Test GitHub repository detection."""
|
||||
@@ -61,37 +63,38 @@ class TestGitHubDetection:
|
||||
def test_detect_owner_repo_format(self):
|
||||
"""owner/repo format should be detected as GitHub."""
|
||||
info = SourceDetector.detect("facebook/react")
|
||||
assert info.type == 'github'
|
||||
assert info.parsed['repo'] == "facebook/react"
|
||||
assert info.suggested_name == 'react'
|
||||
assert info.type == "github"
|
||||
assert info.parsed["repo"] == "facebook/react"
|
||||
assert info.suggested_name == "react"
|
||||
|
||||
def test_detect_github_https_url(self):
|
||||
"""Full GitHub HTTPS URL should be detected."""
|
||||
info = SourceDetector.detect("https://github.com/facebook/react")
|
||||
assert info.type == 'github'
|
||||
assert info.parsed['repo'] == "facebook/react"
|
||||
assert info.suggested_name == 'react'
|
||||
assert info.type == "github"
|
||||
assert info.parsed["repo"] == "facebook/react"
|
||||
assert info.suggested_name == "react"
|
||||
|
||||
def test_detect_github_url_with_git_suffix(self):
|
||||
"""GitHub URL with .git should strip suffix."""
|
||||
info = SourceDetector.detect("https://github.com/facebook/react.git")
|
||||
assert info.type == 'github'
|
||||
assert info.parsed['repo'] == "facebook/react"
|
||||
assert info.suggested_name == 'react'
|
||||
assert info.type == "github"
|
||||
assert info.parsed["repo"] == "facebook/react"
|
||||
assert info.suggested_name == "react"
|
||||
|
||||
def test_detect_github_url_without_protocol(self):
|
||||
"""GitHub URL without protocol should be detected."""
|
||||
info = SourceDetector.detect("github.com/vuejs/vue")
|
||||
assert info.type == 'github'
|
||||
assert info.parsed['repo'] == "vuejs/vue"
|
||||
assert info.suggested_name == 'vue'
|
||||
assert info.type == "github"
|
||||
assert info.parsed["repo"] == "vuejs/vue"
|
||||
assert info.suggested_name == "vue"
|
||||
|
||||
def test_owner_repo_with_dots_and_dashes(self):
|
||||
"""Repo names with dots and dashes should work."""
|
||||
info = SourceDetector.detect("microsoft/vscode-python")
|
||||
assert info.type == 'github'
|
||||
assert info.parsed['repo'] == "microsoft/vscode-python"
|
||||
assert info.suggested_name == 'vscode-python'
|
||||
assert info.type == "github"
|
||||
assert info.parsed["repo"] == "microsoft/vscode-python"
|
||||
assert info.suggested_name == "vscode-python"
|
||||
|
||||
|
||||
class TestLocalDetection:
|
||||
"""Test local directory detection."""
|
||||
@@ -107,9 +110,9 @@ class TestLocalDetection:
|
||||
try:
|
||||
os.chdir(tmp_path)
|
||||
info = SourceDetector.detect("./my_project")
|
||||
assert info.type == 'local'
|
||||
assert 'my_project' in info.parsed['directory']
|
||||
assert info.suggested_name == 'my_project'
|
||||
assert info.type == "local"
|
||||
assert "my_project" in info.parsed["directory"]
|
||||
assert info.suggested_name == "my_project"
|
||||
finally:
|
||||
os.chdir(original_cwd)
|
||||
|
||||
@@ -120,16 +123,17 @@ class TestLocalDetection:
|
||||
test_dir.mkdir()
|
||||
|
||||
info = SourceDetector.detect(str(test_dir))
|
||||
assert info.type == 'local'
|
||||
assert info.parsed['directory'] == str(test_dir.resolve())
|
||||
assert info.suggested_name == 'test_repo'
|
||||
assert info.type == "local"
|
||||
assert info.parsed["directory"] == str(test_dir.resolve())
|
||||
assert info.suggested_name == "test_repo"
|
||||
|
||||
def test_detect_current_directory(self):
|
||||
"""Current directory (.) should be detected."""
|
||||
cwd = os.getcwd()
|
||||
info = SourceDetector.detect(".")
|
||||
assert info.type == 'local'
|
||||
assert info.parsed['directory'] == cwd
|
||||
assert info.type == "local"
|
||||
assert info.parsed["directory"] == cwd
|
||||
|
||||
|
||||
class TestPDFDetection:
|
||||
"""Test PDF file detection."""
|
||||
@@ -137,22 +141,23 @@ class TestPDFDetection:
|
||||
def test_detect_pdf_extension(self):
|
||||
"""File with .pdf extension should be detected."""
|
||||
info = SourceDetector.detect("tutorial.pdf")
|
||||
assert info.type == 'pdf'
|
||||
assert info.parsed['file_path'] == "tutorial.pdf"
|
||||
assert info.suggested_name == 'tutorial'
|
||||
assert info.type == "pdf"
|
||||
assert info.parsed["file_path"] == "tutorial.pdf"
|
||||
assert info.suggested_name == "tutorial"
|
||||
|
||||
def test_detect_pdf_with_path(self):
|
||||
"""PDF file with path should be detected."""
|
||||
info = SourceDetector.detect("/path/to/guide.pdf")
|
||||
assert info.type == 'pdf'
|
||||
assert info.parsed['file_path'] == "/path/to/guide.pdf"
|
||||
assert info.suggested_name == 'guide'
|
||||
assert info.type == "pdf"
|
||||
assert info.parsed["file_path"] == "/path/to/guide.pdf"
|
||||
assert info.suggested_name == "guide"
|
||||
|
||||
def test_suggested_name_removes_pdf_extension(self):
|
||||
"""Suggested name should not include .pdf extension."""
|
||||
info = SourceDetector.detect("my-awesome-guide.pdf")
|
||||
assert info.type == 'pdf'
|
||||
assert info.suggested_name == 'my-awesome-guide'
|
||||
assert info.type == "pdf"
|
||||
assert info.suggested_name == "my-awesome-guide"
|
||||
|
||||
|
||||
class TestConfigDetection:
|
||||
"""Test config file detection."""
|
||||
@@ -160,16 +165,17 @@ class TestConfigDetection:
|
||||
def test_detect_json_extension(self):
|
||||
"""File with .json extension should be detected as config."""
|
||||
info = SourceDetector.detect("react.json")
|
||||
assert info.type == 'config'
|
||||
assert info.parsed['config_path'] == "react.json"
|
||||
assert info.suggested_name == 'react'
|
||||
assert info.type == "config"
|
||||
assert info.parsed["config_path"] == "react.json"
|
||||
assert info.suggested_name == "react"
|
||||
|
||||
def test_detect_config_with_path(self):
|
||||
"""Config file with path should be detected."""
|
||||
info = SourceDetector.detect("configs/django.json")
|
||||
assert info.type == 'config'
|
||||
assert info.parsed['config_path'] == "configs/django.json"
|
||||
assert info.suggested_name == 'django'
|
||||
assert info.type == "config"
|
||||
assert info.parsed["config_path"] == "configs/django.json"
|
||||
assert info.suggested_name == "django"
|
||||
|
||||
|
||||
class TestValidation:
|
||||
"""Test source validation."""
|
||||
@@ -191,10 +197,10 @@ class TestValidation:
|
||||
# First try to detect it (will succeed since it looks like a path)
|
||||
with pytest.raises(ValueError, match="Directory does not exist"):
|
||||
info = SourceInfo(
|
||||
type='local',
|
||||
parsed={'directory': nonexistent},
|
||||
suggested_name='test',
|
||||
raw_input=nonexistent
|
||||
type="local",
|
||||
parsed={"directory": nonexistent},
|
||||
suggested_name="test",
|
||||
raw_input=nonexistent,
|
||||
)
|
||||
SourceDetector.validate_source(info)
|
||||
|
||||
@@ -211,10 +217,10 @@ class TestValidation:
|
||||
"""Validation should fail for nonexistent PDF."""
|
||||
with pytest.raises(ValueError, match="PDF file does not exist"):
|
||||
info = SourceInfo(
|
||||
type='pdf',
|
||||
parsed={'file_path': '/tmp/nonexistent.pdf'},
|
||||
suggested_name='test',
|
||||
raw_input='/tmp/nonexistent.pdf'
|
||||
type="pdf",
|
||||
parsed={"file_path": "/tmp/nonexistent.pdf"},
|
||||
suggested_name="test",
|
||||
raw_input="/tmp/nonexistent.pdf",
|
||||
)
|
||||
SourceDetector.validate_source(info)
|
||||
|
||||
@@ -231,13 +237,14 @@ class TestValidation:
|
||||
"""Validation should fail for nonexistent config."""
|
||||
with pytest.raises(ValueError, match="Config file does not exist"):
|
||||
info = SourceInfo(
|
||||
type='config',
|
||||
parsed={'config_path': '/tmp/nonexistent.json'},
|
||||
suggested_name='test',
|
||||
raw_input='/tmp/nonexistent.json'
|
||||
type="config",
|
||||
parsed={"config_path": "/tmp/nonexistent.json"},
|
||||
suggested_name="test",
|
||||
raw_input="/tmp/nonexistent.json",
|
||||
)
|
||||
SourceDetector.validate_source(info)
|
||||
|
||||
|
||||
class TestAmbiguousCases:
|
||||
"""Test handling of ambiguous inputs."""
|
||||
|
||||
@@ -255,8 +262,8 @@ class TestAmbiguousCases:
|
||||
"""GitHub URL should be detected as github, not web."""
|
||||
# Even though this is a URL, it should be detected as GitHub
|
||||
info = SourceDetector.detect("https://github.com/owner/repo")
|
||||
assert info.type == 'github'
|
||||
assert info.parsed['repo'] == "owner/repo"
|
||||
assert info.type == "github"
|
||||
assert info.parsed["repo"] == "owner/repo"
|
||||
|
||||
def test_directory_takes_precedence_over_domain(self, tmp_path):
|
||||
"""Existing directory should be detected even if it looks like domain."""
|
||||
@@ -266,7 +273,8 @@ class TestAmbiguousCases:
|
||||
|
||||
info = SourceDetector.detect(str(dir_like_domain))
|
||||
# Should detect as local directory, not web
|
||||
assert info.type == 'local'
|
||||
assert info.type == "local"
|
||||
|
||||
|
||||
class TestRawInputPreservation:
|
||||
"""Test that raw_input is preserved correctly."""
|
||||
@@ -292,6 +300,7 @@ class TestRawInputPreservation:
|
||||
info = SourceDetector.detect(original)
|
||||
assert info.raw_input == original
|
||||
|
||||
|
||||
class TestEdgeCases:
|
||||
"""Test edge cases and corner cases."""
|
||||
|
||||
@@ -300,19 +309,19 @@ class TestEdgeCases:
|
||||
info1 = SourceDetector.detect("https://docs.react.dev/")
|
||||
info2 = SourceDetector.detect("https://docs.react.dev")
|
||||
|
||||
assert info1.type == 'web'
|
||||
assert info2.type == 'web'
|
||||
assert info1.type == "web"
|
||||
assert info2.type == "web"
|
||||
|
||||
def test_uppercase_in_github_repo(self):
|
||||
"""GitHub repos with uppercase should be detected."""
|
||||
info = SourceDetector.detect("Microsoft/TypeScript")
|
||||
assert info.type == 'github'
|
||||
assert info.parsed['repo'] == "Microsoft/TypeScript"
|
||||
assert info.type == "github"
|
||||
assert info.parsed["repo"] == "Microsoft/TypeScript"
|
||||
|
||||
def test_numbers_in_repo_name(self):
|
||||
"""GitHub repos with numbers should be detected."""
|
||||
info = SourceDetector.detect("python/cpython3.11")
|
||||
assert info.type == 'github'
|
||||
assert info.type == "github"
|
||||
|
||||
def test_nested_directory_path(self, tmp_path):
|
||||
"""Nested directory paths should work."""
|
||||
@@ -320,5 +329,5 @@ class TestEdgeCases:
|
||||
nested.mkdir(parents=True)
|
||||
|
||||
info = SourceDetector.detect(str(nested))
|
||||
assert info.type == 'local'
|
||||
assert info.suggested_name == 'c'
|
||||
assert info.type == "local"
|
||||
assert info.suggested_name == "c"
|
||||
|
||||
Reference in New Issue
Block a user