Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use more generic types #11

Merged
merged 1 commit into from
Jan 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions src/pkglite/classify.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ def is_text_file(path: str, n: int | None = None) -> bool:
in zlib (`doc/txtvsbin.txt`).

Args:
path (str): File path.
n (int, optional): Maximal number of bytes to read. Defaults to file size.
path: File path.
n: Maximal number of bytes to read. Defaults to file size.

Returns:
bool: True if the file is text, False if binary.
True if the file is text, False if binary.
"""
ALLOW: FrozenSet[int] = frozenset([9, 10, 13] + list(range(32, 256)))
BLOCK: FrozenSet[int] = frozenset(list(range(0, 7)) + list(range(14, 32)))
Expand All @@ -33,12 +33,12 @@ def is_text_file(path: str, n: int | None = None) -> bool:

def classify_file(path: str) -> str:
"""
Classify file as 'text' or 'binary'.
Classify file as text or binary.

Args:
path (str): Path to the file to classify.
path: Path to the file to classify.

Returns:
str: 'text' if the file is detected as text, 'binary' otherwise.
`'text'` if the file is detected as text, `'binary'` otherwise.
"""
return "text" if is_text_file(path) else "binary"
6 changes: 3 additions & 3 deletions src/pkglite/main.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from pathlib import Path
from typing import List, Annotated
from typing import Annotated

import typer

Expand All @@ -19,7 +19,7 @@ def callback():

@app.command()
def pack(
input_dirs: List[Path],
input_dirs: list[Path],
output_file: Annotated[Path, typer.Option("--output-file", "-o")] = Path(
"pkglite.txt"
),
Expand Down Expand Up @@ -55,7 +55,7 @@ def unpack(

@app.command()
def use(
input_dirs: List[Path],
input_dirs: list[Path],
force: Annotated[bool, typer.Option("--force", "-f")] = False,
quiet: Annotated[bool, typer.Option("--quiet", "-q")] = False,
):
Expand Down
64 changes: 33 additions & 31 deletions src/pkglite/pack.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import os
from pathlib import Path
from typing import Callable
from collections.abc import Callable
from typing import Protocol

from pathspec import PathSpec

Expand All @@ -14,16 +15,19 @@
)


def load_ignore_matcher(directory: str) -> Callable[[str], bool]:
class PathMatcher(Protocol):
def __call__(self, path: str) -> bool: ...


def load_ignore_matcher(directory: str) -> PathMatcher:
"""
Load ignore patterns from a `.pkgliteignore` file in the directory.

Args:
directory (str): Path to the directory to pack.
directory: Path to the directory to pack.

Returns:
function: A matcher function that returns True if a path
should be ignored, False otherwise.
A matcher function that returns True if a path should be ignored.
"""
abs_dir = os.path.abspath(os.path.expanduser(directory))
ignore_path = os.path.join(abs_dir, ".pkgliteignore")
Expand All @@ -41,11 +45,11 @@ def matcher(path: str) -> bool:
Check if a path matches any ignore pattern.

Args:
path (str): Path to check against ignore patterns.
path: Path to check against ignore patterns.
Should be relative to the base directory.

Returns:
bool: True if path should be ignored, False otherwise.
True if path should be ignored, False otherwise.
"""
if os.path.isabs(path):
path = os.path.relpath(path, abs_dir)
Expand All @@ -60,10 +64,10 @@ def get_package_name(directory: str) -> str:
Derive the package name from the directory name.

Args:
directory (str): Path to the directory.
directory: Path to the directory.

Returns:
str: The base name of the directory path.
The base name of the directory path.
"""
return os.path.basename(os.path.normpath(directory))

Expand All @@ -73,12 +77,12 @@ def create_file_metadata(package_name: str, relative_path: str, file_type: str)
Create file metadata string.

Args:
package_name (str): Name of the package.
relative_path (str): Relative path of the file within the package.
file_type (str): Type of the file ('text' or 'binary').
package_name: Name of the package.
relative_path: Relative path of the file within the package.
file_type: Type of the file ('text' or 'binary').

Returns:
str: Formatted metadata string.
Formatted metadata string.
"""
return (
f"Package: {package_name}\n"
Expand All @@ -93,10 +97,10 @@ def read_text_content(file_path: str) -> str:
Read text file content and format it.

Args:
file_path (str): Path to the text file to read from.
file_path: Path to the text file to read from.

Returns:
str: Formatted text content.
Formatted text content.
"""
with open(file_path, "r", encoding="utf-8") as f:
return "".join(" " + line for line in f)
Expand All @@ -107,10 +111,10 @@ def read_binary_content(file_path: str) -> str:
Read binary file content and format it in hex format.

Args:
file_path (str): Path to the binary file to read from.
file_path: Path to the binary file to read from.

Returns:
str: Formatted binary content.
Formatted binary content.
"""
with open(file_path, "rb") as f:
content = f.read().hex()
Expand All @@ -124,11 +128,11 @@ def read_file_content(file_path: str, file_type: str) -> str:
Read and format file content based on type.

Args:
file_path (str): Path to the file to read from.
file_type (str): Type of the file ('text' or 'binary').
file_path: Path to the file to read from.
file_type: Type of the file ('text' or 'binary').

Returns:
str: Formatted file content.
Formatted file content.
"""
return (
read_text_content(file_path)
Expand All @@ -147,13 +151,13 @@ def process_single_file(
Process a single file and return its formatted content.

Args:
file_path (str): Path to the file to process.
directory (str): Base directory path.
package_name (str): Name of the package.
ignore_matcher (function): Function to check if file should be ignored.
file_path: Path to the file to process.
directory: Base directory path.
package_name: Name of the package.
ignore_matcher: Function to check if file should be ignored.

Returns:
str | None: Formatted file content if not ignored, None otherwise.
Formatted file content if not ignored, None otherwise.
"""
if ignore_matcher(file_path):
return None
Expand All @@ -173,7 +177,7 @@ def create_header() -> str:
Create the pkglite header string.

Returns:
str: Formatted header string.
Formatted header string.
"""
return (
"# Generated by py-pkglite: do not edit by hand\n"
Expand All @@ -190,11 +194,9 @@ def pack(
Pack files from one or multiple directories into a text file.

Args:
input_dirs (str or Path or list): Path or list of paths to the
directories to pack.
output_file (str or Path): Path to the output file.
Default is 'pkglite.txt'.
quiet (bool): If True, suppress output messages. Default False.
input_dirs: Path or sequence of paths to the directories to pack.
output_file: Path to the output file. Default is 'pkglite.txt'.
quiet: If True, suppress output messages.
"""
dirs = [input_dirs] if isinstance(input_dirs, (str, Path)) else input_dirs
abs_dirs = [os.path.abspath(os.path.expanduser(str(d))) for d in dirs]
Expand Down
63 changes: 31 additions & 32 deletions src/pkglite/unpack.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import os
import binascii
from typing import List, Dict, Set
from collections.abc import Sequence
from pathlib import Path
from dataclasses import dataclass

Expand All @@ -26,28 +26,28 @@ def extract_metadata_field(line: str, tag: str) -> str | None:
Extract a metadata field value from a line with a given tag.

Args:
line (str): The line to extract from.
tag (str): The tag to look for.
line: The line to extract from.
tag: The tag to look for.

Returns:
str | None: The extracted value if found, None otherwise.
The extracted value if found, None otherwise.
"""
return line.split(f"{tag}: ")[1] if line.startswith(f"{tag}: ") else None


def create_file_entry(
package_name: str, content_lines: List[str], file_format: str
) -> Dict[str, str]:
package_name: str, content_lines: list[str], file_format: str
) -> dict[str, str]:
"""
Create a file entry dictionary with the given content.

Args:
package_name (str): Name of the package.
content_lines (list): List of content lines.
file_format (str): Format of the file ('text' or 'binary').
package_name: Name of the package.
content_lines: List of content lines.
file_format: Format of the file ('text' or 'binary').

Returns:
Dict[str, str]: Dictionary containing the file entry data.
Dictionary containing the file entry data.
"""
content = (
"\n".join(content_lines) if file_format == "text" else "".join(content_lines)
Expand All @@ -60,27 +60,27 @@ def process_content_line(line: str) -> str:
Process a content line by removing the leading spaces if present.

Args:
line (str): The line to process.
line: The line to process.

Returns:
str: The processed line with leading spaces removed if present.
The processed line with leading spaces removed if present.
"""
return line[2:] if line.startswith(" ") else ""


def parse_packed_file(input_file: str) -> List[FileData]:
def parse_packed_file(input_file: str) -> Sequence[FileData]:
"""
Parse the packed text file and extract file data.

Args:
input_file (str): Path to the packed file.
input_file: Path to the packed file.

Returns:
List[FileData]: A list of FileData objects containing file information.
A sequence of FileData objects containing file information.
"""

def process_file_entry(
current: Dict[str, str], lines: List[str]
current: dict[str, str], lines: list[str]
) -> FileData | None:
"""
Process a file entry and create a FileData object.
Expand All @@ -90,7 +90,7 @@ def process_file_entry(
lines: List of content lines.

Returns:
FileData | None: FileData object if valid entry, None otherwise.
FileData object if valid entry, None otherwise.
"""
if not (current and "package" in current and "path" in current):
return None
Expand All @@ -104,9 +104,9 @@ def process_file_entry(
content=content["content"],
)

files: List[FileData] = []
current_file: Dict[str, str] = {}
content_lines: List[str] = []
files: list[FileData] = []
current_file: dict[str, str] = {}
content_lines: list[str] = []
in_content = False

with open(input_file, "r", encoding="utf-8") as f:
Expand Down Expand Up @@ -151,8 +151,8 @@ def write_text_file(file_path: Path, content: str) -> None:
Write content to a text file.

Args:
file_path (Path): Path to the file to write.
content (str): Text content to write.
file_path: Path to the file to write.
content: Text content to write.
"""
file_path.parent.mkdir(parents=True, exist_ok=True)
file_path.write_text(content, encoding="utf-8")
Expand All @@ -163,8 +163,8 @@ def write_binary_file(file_path: Path, content: str) -> None:
Write hex content to a binary file.

Args:
file_path (Path): Path to the file to write.
content (str): Hexadecimal string content to write.
file_path: Path to the file to write.
content: Hexadecimal string content to write.

Raises:
ValueError: If the content is not valid hexadecimal.
Expand All @@ -182,8 +182,8 @@ def write_file(file_data: FileData, output_directory: Path) -> None:
Write a file to the specified output directory.

Args:
file_data (FileData): FileData object containing file information
output_directory (Path): Root directory for unpacked files.
file_data: FileData object containing file information
output_directory: Root directory for unpacked files.
"""
file_path = output_directory / file_data.package / file_data.path

Expand All @@ -200,19 +200,18 @@ def unpack(
Unpack files from a text file into the specified directory.

Args:
input_file (str or Path): Path to the packed file.
output_dir (str or Path): Path to the directory to unpack files into.
Default is current directory.
quiet (bool): If True, suppress output messages. Default False.
input_file: Path to the packed file.
output_dir: Path to the directory to unpack files into.
quiet: If True, suppress output messages.
"""
input_path = Path(os.path.expanduser(str(input_file)))
output_path = Path(os.path.expanduser(str(output_dir)))

files = parse_packed_file(str(input_path))
packages: Set[str] = {file_data.package for file_data in files}
packages: set[str] = {file_data.package for file_data in files}

# Group files by package
files_by_package: Dict[str, List[FileData]] = {}
files_by_package: dict[str, list[FileData]] = {}
for file_data in files:
pkg = file_data.package
if pkg not in files_by_package:
Expand Down
Loading
Loading