How book-to-skill Resolves Input Files and Handles Glob Patterns

book-to-skill resolves input files by expanding tildes, detecting glob patterns, filtering by supported extensions, and deduplicating results while preserving deterministic order.

The book-to-skill repository provides a robust document extraction pipeline that accepts diverse input specifications—from single files to complex glob expressions. Understanding how it resolves these inputs ensures you can efficiently batch-process PDFs, EPUBs, and other supported formats without manual file enumeration.

The Core Resolution Pipeline

The heart of input resolution resides in book_to_skill/utils.py, specifically within the resolve_input_files() function. This utility implements an eight-step pipeline that transforms raw command-line arguments into a validated, ordered list of absolute Path objects.

Step 1: Tilde Expansion

Before any pattern matching occurs, the function expands user home directories. Each raw path passes through os.path.expanduser(), converting leading tildes (~) into absolute home directory paths.

path_str = os.path.expanduser(raw_path)

This operation respects the HOME environment variable on POSIX systems and USERPROFILE on Windows, ensuring cross-platform consistency whether you specify ~/books/*.epub or ~user/document.pdf.

Step 2: Glob Pattern Detection

The resolver distinguishes between literal paths and glob patterns by checking two conditions: the path must not exist as a file or directory, and it must contain wildcard characters (*, ?, or [).

if not Path(path_str).exists() and any(char in path_str for char in '*?['):
    # Treat as glob pattern

This heuristic prevents accidental glob expansion of legitimate filenames while capturing intentional patterns like **/*.md or documents/lecture?.pdf.

Step 3: Pattern Matching and Extension Filtering

When a glob pattern is detected, resolve_input_files() invokes glob.glob(path_str, recursive=True) to collect matches. Each result undergoes validation against SUPPORTED_EXTENSIONS—a constant defined in the configuration layer that includes PDF, EPUB, DOCX, RTF, TXT, HTML, and Calibre e-book formats.

Only files matching these extensions proceed; directories and unsupported binary files are silently discarded during this phase.

Step 4: Deterministic Sorting

To guarantee reproducible execution across runs, matched files undergo case-insensitive lexical sorting:

expanded.sort(key=lambda x: str(x).lower())

This deterministic ordering proves essential for downstream agents that may cache results or rely on consistent file sequencing.

Step 5: Directory Recursion

When the input resolves to an existing directory, the function switches to os.walk() traversal. The walker recursively descends the directory tree, collecting all supported files (filtered by the same SUPPORTED_EXTENSIONS criteria) and applying identical case-insensitive sorting logic.

Step 6: Plain File Preservation

Arguments that are neither globs nor directories are appended verbatim to the results list, even if they do not exist on the filesystem. This deferral strategy allows callers to handle validation errors later with specific context, rather than failing silently during the resolution phase.

Step 7: Deduplication with Order Preservation

After processing all arguments, the function eliminates duplicates using a set-based tracking mechanism while maintaining insertion order. This ensures that explicitly named files retain priority over glob-discovered duplicates, and the final list contains only unique absolute paths.

Supported File Extensions

The resolution logic strictly filters inputs against SUPPORTED_EXTENSIONS, which typically includes:

  • Document formats: .pdf, .docx, .rtf
  • E-book formats: .epub, .mobi, .azw3
  • Markup and text: .html, .htm, .txt, .md

This filtering occurs in book_to_skill/utils.py lines 75-81 and prevents the extractor from attempting to parse binary assets or unsupported file types.

Practical Usage Examples

Command-Line Interface

The CLI transparently passes arguments to resolve_input_files(), supporting mixed input types:


# Single file with tilde expansion

book-to-skill ~/documents/thesis.pdf

# Recursive glob for all EPUBs in home directory

book-to-skill "~/books/**/*.epub"

# Mixed inputs: explicit file, glob pattern, and directory

book-to-skill chapter1.pdf "~/downloads/*.docx" ./reference_materials/

Python API

Import resolve_input_files directly for programmatic control:

from book_to_skill.utils import resolve_input_files

raw_inputs = [
    "~/ebooks/*.epub",
    "./technical_docs/",
    "specific_article.pdf"
]

resolved_paths = resolve_input_files(raw_inputs)

for path in resolved_paths:
    print(f"Processing: {path}")
    # Each path is an absolute Path object ready for extraction

The function returns a list[Path] where each element is an absolute, deduplicated, and extension-filtered file location.

Error Handling and Edge Cases

The resolver exhibits specific behaviors for problematic inputs:

  • Unresolvable tildes: Paths like ~nonexistentuser/file.txt remain unchanged (tilde unexpanded) so downstream validators can raise clear ExtractionError messages
  • Empty glob matches: Patterns matching zero files result in no additions to the results list, not runtime exceptions
  • Permission errors: During directory walks, permission-denied directories are skipped rather than crashing the traversal

These behaviors are validated by the test suite in tests/test_tilde_expansion.py and tests/test_book_to_skill.py, which verify correct handling of HOME/USERPROFILE overrides, glob filtering accuracy, and duplicate removal semantics.

Summary

  • resolve_input_files() in book_to_skill/utils.py orchestrates all input resolution through an eight-step pipeline
  • Tilde expansion occurs first via os.path.expanduser(), supporting cross-platform home directory resolution
  • Glob detection relies on wildcard character presence combined with path non-existence checks
  • Extension filtering enforces the SUPPORTED_EXTENSIONS whitelist during both glob and directory traversal phases
  • Deterministic sorting uses case-insensitive string comparison to ensure reproducible file ordering
  • Deduplication preserves insertion order while eliminating redundant paths across mixed input types

Frequently Asked Questions

How does book-to-skill handle the tilde (~) character in file paths?

book-to-skill expands tildes using os.path.expanduser() before pattern matching, converting ~/documents/file.pdf into an absolute path based on the HOME or USERPROFILE environment variable. If the username portion is invalid (e.g., ~nonexistentuser), the path remains unexpanded for later error reporting.

What glob syntax does book-to-shelf support?

The tool uses Python's standard glob.glob() with recursive=True, supporting * for wildcards, ? for single characters, [seq] for character classes, and **/ for recursive directory traversal. Patterns like ~/books/**/*.epub correctly match EPUB files at any depth within the books directory.

Why are my unsupported files being ignored during resolution?

resolve_input_files() filters all candidates against SUPPORTED_EXTENSIONS, which includes only document and e-book formats like PDF, EPUB, DOCX, and HTML. Files with extensions such as .jpg, .zip, or .exe are silently excluded during both glob expansion and directory walking to prevent extractor failures.

What happens if I provide a path that does not exist?

Non-existent paths that do not contain glob wildcards are appended to the results list verbatim as absolute paths. The function does not raise errors during resolution; instead, it allows downstream components like extract_single_file() to detect the missing file and raise an appropriate ExtractionError with specific context.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →