Add recursive dependency downloading and directory scanning
- Implement recursive downloading of nested dependencies from ZON files - Enhance CLI to support downloading from directories containing `build.zig.zon` files - Update README with new usage instructions and command options - Refactor dependency processing to handle both single files and directories
This commit is contained in:
22
README.md
22
README.md
@ -7,6 +7,8 @@ A Python utility for working with Zig package manager files and Zig Object Notat
|
|||||||
- Parse ZON files into Python dictionaries
|
- Parse ZON files into Python dictionaries
|
||||||
- Convert ZON files to JSON
|
- Convert ZON files to JSON
|
||||||
- Download and extract dependencies from ZON files
|
- Download and extract dependencies from ZON files
|
||||||
|
- Recursively download nested dependencies
|
||||||
|
- Scan directories for `build.zig.zon` files
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
@ -59,13 +61,27 @@ The package provides a command-line interface with the following commands:
|
|||||||
|
|
||||||
#### Download Dependencies
|
#### Download Dependencies
|
||||||
|
|
||||||
Download and extract dependencies from a ZON file:
|
Download dependencies from a ZON file or directory:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
uv run zig-fetch download examples/test.zon
|
# Download dependencies from a single ZON file
|
||||||
|
zig-fetch download examples/test.zon
|
||||||
|
|
||||||
|
# Download dependencies from a directory (finds all build.zig.zon files)
|
||||||
|
zig-fetch download lib/
|
||||||
|
|
||||||
|
# Download dependencies recursively (finds dependencies of dependencies)
|
||||||
|
zig-fetch -v download examples/test.zon --recursive
|
||||||
|
|
||||||
|
# Combine directory scanning with recursive downloading
|
||||||
|
zig-fetch -v download lib/ --recursive
|
||||||
```
|
```
|
||||||
|
|
||||||
This will download all dependencies specified in the ZON file to `~/.cache/zig/p` and extract them to directories named after their hash values.
|
Options:
|
||||||
|
- `--recursive`, `-r`: Recursively process dependencies of dependencies
|
||||||
|
- `--verbose`, `-v`: Enable verbose logging (on the parent command)
|
||||||
|
|
||||||
|
This will download all dependencies to `~/.cache/zig/p` and extract them to directories named after their hash values.
|
||||||
|
|
||||||
#### Convert ZON to JSON
|
#### Convert ZON to JSON
|
||||||
|
|
||||||
|
|||||||
@ -88,15 +88,28 @@ def cli(ctx: click.Context, verbose: bool):
|
|||||||
|
|
||||||
@cli.command()
|
@cli.command()
|
||||||
@click.argument("zon_file", type=click.Path(exists=True, readable=True, path_type=Path))
|
@click.argument("zon_file", type=click.Path(exists=True, readable=True, path_type=Path))
|
||||||
|
@click.option(
|
||||||
|
"--recursive",
|
||||||
|
"-r",
|
||||||
|
is_flag=True,
|
||||||
|
help="Recursively process dependencies from downloaded artifacts or scan directories",
|
||||||
|
)
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
def download(ctx: click.Context, zon_file: Path):
|
def download(ctx: click.Context, zon_file: Path, recursive: bool):
|
||||||
"""
|
"""
|
||||||
Download dependencies from a ZON file.
|
Download dependencies from a ZON file or directory.
|
||||||
|
|
||||||
ZON_FILE: Path to the ZON file
|
If ZON_FILE is a directory, all build.zig.zon files will be processed.
|
||||||
|
If --recursive is specified, all dependencies of dependencies will also be processed.
|
||||||
|
|
||||||
|
ZON_FILE: Path to the ZON file or directory to process
|
||||||
"""
|
"""
|
||||||
logger.info(f"Processing dependencies from {zon_file}")
|
logger.info(f"Processing dependencies from {zon_file}")
|
||||||
dependencies = process_dependencies(str(zon_file))
|
|
||||||
|
if zon_file.is_dir():
|
||||||
|
logger.info(f"{zon_file} is a directory, searching for build.zig.zon files")
|
||||||
|
|
||||||
|
dependencies = process_dependencies(str(zon_file), recursive=recursive)
|
||||||
|
|
||||||
if dependencies:
|
if dependencies:
|
||||||
logger.info(f"Successfully processed {len(dependencies)} dependencies:")
|
logger.info(f"Successfully processed {len(dependencies)} dependencies:")
|
||||||
|
|||||||
@ -5,10 +5,12 @@ This module handles downloading and extracting dependencies specified in ZON fil
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import shutil
|
import shutil
|
||||||
|
import click
|
||||||
|
import sys
|
||||||
import tarfile
|
import tarfile
|
||||||
import tempfile
|
import tempfile
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict, Any, Optional
|
from typing import Dict, Any, Optional, Set, List
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
@ -133,18 +135,85 @@ def process_dependency(
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def process_dependencies(zon_file_path: str) -> Dict[str, Path]:
|
def find_build_zig_zon_files(directory: Path) -> List[Path]:
|
||||||
|
"""
|
||||||
|
Find all build.zig.zon files in a directory and its subdirectories.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
directory: Directory to search in
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of paths to build.zig.zon files
|
||||||
|
"""
|
||||||
|
logger.debug(f"Searching for build.zig.zon files in {directory}")
|
||||||
|
|
||||||
|
zon_files = []
|
||||||
|
for zon_file in directory.glob("**/build.zig.zon"):
|
||||||
|
logger.debug(f"Found build.zig.zon file: {zon_file}")
|
||||||
|
zon_files.append(zon_file)
|
||||||
|
|
||||||
|
return zon_files
|
||||||
|
|
||||||
|
|
||||||
|
def process_dependencies(
|
||||||
|
zon_file_path: str, recursive: bool = False
|
||||||
|
) -> Dict[str, Path]:
|
||||||
"""
|
"""
|
||||||
Process all dependencies from a ZON file.
|
Process all dependencies from a ZON file.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
zon_file_path: Path to the ZON file
|
zon_file_path: Path to the ZON file or directory
|
||||||
|
recursive: Whether to process dependencies recursively
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Dictionary mapping dependency names to their extracted paths
|
Dictionary mapping dependency names to their extracted paths
|
||||||
"""
|
"""
|
||||||
|
# Convert to Path object
|
||||||
|
zon_file = Path(zon_file_path)
|
||||||
|
|
||||||
|
# If the path is a directory, find all build.zig.zon files
|
||||||
|
if zon_file.is_dir():
|
||||||
|
logger.info(f"Processing directory: {zon_file}")
|
||||||
|
all_zon_files = find_build_zig_zon_files(zon_file)
|
||||||
|
|
||||||
|
if not all_zon_files:
|
||||||
|
logger.warning(f"No build.zig.zon files found in {zon_file}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
# Process all found ZON files
|
||||||
|
result = {}
|
||||||
|
for zon_path in all_zon_files:
|
||||||
|
logger.info(f"Processing {zon_path}")
|
||||||
|
deps = process_dependencies_from_file(str(zon_path), recursive)
|
||||||
|
result.update(deps)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
# Otherwise, process the single ZON file
|
||||||
|
return process_dependencies_from_file(zon_file_path, recursive)
|
||||||
|
|
||||||
|
|
||||||
|
def process_dependencies_from_file(
|
||||||
|
zon_file_path: str, recursive: bool = False
|
||||||
|
) -> Dict[str, Path]:
|
||||||
|
"""
|
||||||
|
Process all dependencies from a single ZON file.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
zon_file_path: Path to the ZON file
|
||||||
|
recursive: Whether to process dependencies recursively
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dictionary mapping dependency names to their extracted paths
|
||||||
|
"""
|
||||||
|
logger.info(f"Processing dependencies from file: {zon_file_path}")
|
||||||
|
|
||||||
# Parse the ZON file
|
# Parse the ZON file
|
||||||
|
try:
|
||||||
zon_data = parse_zon_file(zon_file_path)
|
zon_data = parse_zon_file(zon_file_path)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error parsing {zon_file_path}: {e}")
|
||||||
|
return {}
|
||||||
|
|
||||||
# Get the dependencies section
|
# Get the dependencies section
|
||||||
dependencies = zon_data.get("dependencies", {})
|
dependencies = zon_data.get("dependencies", {})
|
||||||
@ -157,23 +226,79 @@ def process_dependencies(zon_file_path: str) -> Dict[str, Path]:
|
|||||||
|
|
||||||
# Process each dependency
|
# Process each dependency
|
||||||
result = {}
|
result = {}
|
||||||
|
processed_paths = set()
|
||||||
|
|
||||||
for name, dep_info in dependencies.items():
|
for name, dep_info in dependencies.items():
|
||||||
path = process_dependency(name, dep_info, cache_dir)
|
path = process_dependency(name, dep_info, cache_dir)
|
||||||
if path:
|
if path:
|
||||||
result[name] = path
|
result[name] = path
|
||||||
|
processed_paths.add(path)
|
||||||
|
|
||||||
|
# Recursively process dependencies if requested
|
||||||
|
if recursive and path.exists():
|
||||||
|
process_nested_dependencies(path, result, processed_paths)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def main(zon_file_path: str) -> None:
|
def process_nested_dependencies(
|
||||||
|
dep_path: Path, result: Dict[str, Path], processed_paths: Set[Path]
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Process nested dependencies from a dependency directory.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
dep_path: Path to the dependency directory
|
||||||
|
result: Dictionary to update with new dependencies
|
||||||
|
processed_paths: Set of paths that have already been processed
|
||||||
|
"""
|
||||||
|
# Find all build.zig.zon files in the dependency directory
|
||||||
|
zon_files = find_build_zig_zon_files(dep_path)
|
||||||
|
|
||||||
|
if not zon_files:
|
||||||
|
logger.debug(f"No nested build.zig.zon files found in {dep_path}")
|
||||||
|
return
|
||||||
|
|
||||||
|
for zon_file in zon_files:
|
||||||
|
logger.info(f"Processing nested dependency file: {zon_file}")
|
||||||
|
|
||||||
|
# Parse the ZON file
|
||||||
|
try:
|
||||||
|
zon_data = parse_zon_file(str(zon_file))
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error parsing {zon_file}: {e}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Get the dependencies section
|
||||||
|
dependencies = zon_data.get("dependencies", {})
|
||||||
|
if not dependencies:
|
||||||
|
logger.debug(f"No dependencies found in {zon_file}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Get the cache directory
|
||||||
|
cache_dir = get_cache_dir()
|
||||||
|
|
||||||
|
# Process each dependency
|
||||||
|
for name, dep_info in dependencies.items():
|
||||||
|
path = process_dependency(name, dep_info, cache_dir)
|
||||||
|
if path and path not in processed_paths:
|
||||||
|
result[name] = path
|
||||||
|
processed_paths.add(path)
|
||||||
|
|
||||||
|
# Recursively process this dependency's dependencies
|
||||||
|
process_nested_dependencies(path, result, processed_paths)
|
||||||
|
|
||||||
|
|
||||||
|
def main(zon_file_path: str, recursive: bool = False) -> None:
|
||||||
"""
|
"""
|
||||||
Main entry point for the dependency downloader.
|
Main entry point for the dependency downloader.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
zon_file_path: Path to the ZON file
|
zon_file_path: Path to the ZON file or directory
|
||||||
|
recursive: Whether to process dependencies recursively
|
||||||
"""
|
"""
|
||||||
logger.info(f"Processing dependencies from {zon_file_path}")
|
logger.info(f"Processing dependencies from {zon_file_path}")
|
||||||
dependencies = process_dependencies(zon_file_path)
|
dependencies = process_dependencies(zon_file_path, recursive=recursive)
|
||||||
|
|
||||||
if dependencies:
|
if dependencies:
|
||||||
logger.info(f"Successfully processed {len(dependencies)} dependencies:")
|
logger.info(f"Successfully processed {len(dependencies)} dependencies:")
|
||||||
@ -183,11 +308,25 @@ def main(zon_file_path: str) -> None:
|
|||||||
logger.warning("No dependencies were processed")
|
logger.warning("No dependencies were processed")
|
||||||
|
|
||||||
|
|
||||||
|
@click.command()
|
||||||
|
@click.argument("zon_file", type=click.Path(exists=True, readable=True))
|
||||||
|
@click.option(
|
||||||
|
"--recursive",
|
||||||
|
"-r",
|
||||||
|
is_flag=True,
|
||||||
|
help="Recursively process dependencies from downloaded artifacts",
|
||||||
|
)
|
||||||
|
@click.option("--verbose", "-v", is_flag=True, help="Enable verbose logging")
|
||||||
|
def cli(zon_file, recursive, verbose):
|
||||||
|
"""Download dependencies from a ZON file."""
|
||||||
|
# Set up logging
|
||||||
|
log_level = "DEBUG" if verbose else "INFO"
|
||||||
|
logger.remove()
|
||||||
|
logger.add(sys.stderr, level=log_level)
|
||||||
|
|
||||||
|
# Run the main function
|
||||||
|
main(zon_file, recursive=recursive)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import sys
|
cli() # pylint: disable=no-value-for-parameter
|
||||||
|
|
||||||
if len(sys.argv) != 2:
|
|
||||||
print(f"Usage: {sys.argv[0]} <zon_file_path>")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
main(sys.argv[1])
|
|
||||||
|
|||||||
Reference in New Issue
Block a user