Skip to content

Instantly share code, notes, and snippets.

@tobiashochguertel
Last active May 21, 2026 07:03
Show Gist options
  • Select an option

  • Save tobiashochguertel/0baa3a91e425f8881d4bc94258ff46cb to your computer and use it in GitHub Desktop.

Select an option

Save tobiashochguertel/0baa3a91e425f8881d4bc94258ff46cb to your computer and use it in GitHub Desktop.
Generate config file path variants with customizable discovery matrix (dot-prefix, extensions, subdirectories)
#!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.12"
# dependencies = [
# "typer>=0.15",
# "pydantic>=2.0",
# ]
# ///
"""
path-discovery.py
Utility to generate all possible file path variants for config discovery.
Usage:
./path-discovery.py resource-catalog
./path-discovery.py resource-catalog --dot-prefixed false
./path-discovery.py resource-catalog --extensions yaml json
./path-discovery.py resource-catalog --directories "" config .config
./path-discovery.py resource-catalog --import | python3 # print import snippet
"""
from __future__ import annotations
from pathlib import Path
from typing import Annotated
import typer
from pydantic import BaseModel
class DiscoveryConfig(BaseModel):
"""Configuration for file path discovery.
Controls which filename variants are generated by discover_paths().
All fields have defaults — override only what you need.
"""
search_cwd: bool = True
dot_prefixed: bool = True
dot_directory: bool = True
extensions: list[str] = ["yaml", "yml"]
directories: list[str] = ["config"]
def discover_paths(name: str, config: DiscoveryConfig | None = None) -> list[Path]:
"""Generate all possible file paths for a given config filename.
Combines the name with every extension, dot-prefix, and directory
variant defined in the config to produce a flat list of candidate paths.
Args:
name: Base filename without extension (e.g., "resource-catalog").
config: Optional overrides. Defaults to yaml/yml, dot-prefixed,
and .config/ + cwd directories.
Returns:
List of Path objects, unique and in a predictable order.
"""
if config is None:
config = DiscoveryConfig()
seen: set[str] = set()
paths: list[Path] = []
dirs: list[str] = list(config.directories)
if config.search_cwd:
dirs.insert(0, "")
expanded_dirs: list[str] = []
for d in dirs:
expanded_dirs.append(d)
if d and config.dot_directory and not d.startswith("."):
expanded_dirs.append(f".{d}")
for ext in config.extensions:
for directory in expanded_dirs:
dir_prefix = f"{directory}/" if directory else ""
candidates: list[str] = [f"{dir_prefix}{name}.{ext}"]
if config.dot_prefixed and not directory:
candidates.append(f".{name}.{ext}")
for c in candidates:
if c not in seen:
seen.add(c)
paths.append(Path(c))
return paths
app = typer.Typer(
name="path-discovery",
help="Generate file-path variants for config discovery.",
no_args_is_help=True,
pretty_exceptions_enable=False,
)
@app.command()
def main(
name: Annotated[str, typer.Argument(help="Base filename (e.g., resource-catalog)")],
search_cwd: Annotated[bool | None, typer.Option("--search-cwd", help="Include current working directory")] = None,
dot_prefixed: Annotated[bool | None, typer.Option("--dot-prefixed", help="Prefix filename with . in cwd")] = None,
dot_directory: Annotated[bool | None, typer.Option("--dot-directory", help="Auto-prefix directories with .")] = None,
extensions: Annotated[list[str] | None, typer.Option("--extension", "-e", help="File extensions to include")] = None,
directories: Annotated[list[str] | None, typer.Option("--directory", "-d", help="Subdirectories to search (e.g., config)")] = None,
) -> None:
"""Print all path variants for a given filename."""
config_kwargs = {}
if search_cwd is not None:
config_kwargs["search_cwd"] = search_cwd
if dot_prefixed is not None:
config_kwargs["dot_prefixed"] = dot_prefixed
if dot_directory is not None:
config_kwargs["dot_directory"] = dot_directory
if extensions is not None:
config_kwargs["extensions"] = extensions
if directories is not None:
config_kwargs["directories"] = directories
config = DiscoveryConfig(**config_kwargs) if config_kwargs else None
paths = discover_paths(name, config)
for p in paths:
print(p)
if __name__ == "__main__":
app()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment