Skip to content

dotfiles_tree

Group chezmoi-managed dotfiles into a nested tree for the "Sync dotfiles" tab.

chezmoi_managed_paths() returns a flat list of absolute file paths. This module groups them by path segment (relative to a root, normally the user's home directory) so the frontend can render a folder tree with per-folder cascading selection instead of one long flat list.

DotfileTreeNode

Bases: BaseModel

A single node in the dotfiles tree.

Leaf nodes (files) have path set; directory nodes have path=None and one or more children, keyed by path segment name.

Source code in src/personal_os_setup/frontend/dotfiles_tree.py
class DotfileTreeNode(BaseModel):
    """A single node in the dotfiles tree.

    Leaf nodes (files) have `path` set; directory nodes have `path=None` and
    one or more `children`, keyed by path segment name.
    """

    model_config = ConfigDict(frozen=True)

    name: str
    path: Path | None = None
    children: dict[str, "DotfileTreeNode"] = Field(default_factory=dict)

    @property
    def is_file(self) -> bool:
        """Whether this node represents a file rather than a directory."""
        return self.path is not None

    def all_file_paths(self) -> list[Path]:
        """Return every file path in this node's subtree.

        Returns:
            `[self.path]` if this node is a file, otherwise every file path
            found recursively under its children.
        """
        if self.path is not None:
            return [self.path]
        paths: list[Path] = []
        for child in self.children.values():
            paths.extend(child.all_file_paths())
        return paths

is_file property

Whether this node represents a file rather than a directory.

all_file_paths()

Return every file path in this node's subtree.

Returns:

Type Description
list[Path]

[self.path] if this node is a file, otherwise every file path

list[Path]

found recursively under its children.

Source code in src/personal_os_setup/frontend/dotfiles_tree.py
def all_file_paths(self) -> list[Path]:
    """Return every file path in this node's subtree.

    Returns:
        `[self.path]` if this node is a file, otherwise every file path
        found recursively under its children.
    """
    if self.path is not None:
        return [self.path]
    paths: list[Path] = []
    for child in self.children.values():
        paths.extend(child.all_file_paths())
    return paths

build_dotfiles_tree(paths, root, root_label='~')

Group flat absolute file paths into a nested tree relative to a root.

Parameters:

Name Type Description Default
paths list[Path]

Absolute file paths, e.g. from chezmoi_managed_paths().

required
root Path

The directory paths are grouped relative to (normally Path.home()).

required
root_label str

Display name for the tree's root node.

'~'

Returns:

Type Description
DotfileTreeNode

The tree's root node; its children are the top-level path segments.

DotfileTreeNode

Paths not under root (e.g. a different drive on Windows) fall back to

DotfileTreeNode

being grouped by their full parts instead of raising.

Source code in src/personal_os_setup/frontend/dotfiles_tree.py
def build_dotfiles_tree(paths: list[Path], root: Path, root_label: str = "~") -> DotfileTreeNode:
    """Group flat absolute file paths into a nested tree relative to a root.

    Args:
        paths: Absolute file paths, e.g. from `chezmoi_managed_paths()`.
        root: The directory paths are grouped relative to (normally `Path.home()`).
        root_label: Display name for the tree's root node.

    Returns:
        The tree's root node; its children are the top-level path segments.
        Paths not under `root` (e.g. a different drive on Windows) fall back to
        being grouped by their full parts instead of raising.
    """
    tree = DotfileTreeNode(name=root_label)
    for p in paths:
        try:
            parts = p.relative_to(root).parts
        except ValueError:
            parts = p.parts
        node = tree
        for i, part in enumerate(parts):
            is_last_part = i == len(parts) - 1
            if part not in node.children:
                node.children[part] = DotfileTreeNode(name=part, path=p if is_last_part else None)
            node = node.children[part]
    return tree