Rendering¶
Turning a scanned structure into terminal output: the tree view, the side-by-side comparison, and the colors and icons both use.
Tree Rendering¶
recursivist.tree
¶
Terminal tree rendering.
Builds and prints a rich tree from a scanned structure, with extension colors,
optional metric annotations, and Git status markers. The structure comes from
get_directory_structure, which applies
all filtering.
build_tree
¶
build_tree(structure: Directory, tree: Tree, color_map: dict[str, str], spec: DisplayOptions, icon_style: str = 'emoji') -> None
Populate a rich tree from a scanned directory structure.
Recursively adds each file and subdirectory of structure to tree, with filenames
colored by extension. Each file is labeled with its stored path, which already
holds the full path when the scan requested one. Files are ordered by
spec.sort_key via
recursivist.sorting.sort_files_by_type.
A subtree that hit the depth limit is simply left unexpanded; its folder icon still
shows whether anything was cut off.
The resolved spec controls the annotations appended to each entry:
spec.metrics: the ordered lines-of-code, size, and modification-time metrics to append (in the exact order requested).spec.show_git_status: append a colored marker to each file —[U]untracked (grey),[M]modified (yellow),[A]added (green),[D]deleted (red). The marker always trails the metric parenthetical, and deleted files no longer on disk are also struck through.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
structure
|
Directory
|
Directory to render. |
required |
tree
|
Tree
|
|
required |
color_map
|
dict[str, str]
|
Mapping of lowercase file extension to hex color. |
required |
spec
|
DisplayOptions
|
Resolved sorting and annotation directives. |
required |
icon_style
|
str
|
Icon style to use, either |
'emoji'
|
Source code in recursivist/tree.py
display_tree
¶
display_tree(structure: Directory, extensions: set[str], root_name: str, spec: DisplayOptions | None = None, icon_style: str = 'emoji') -> None
Render a scanned directory structure as a tree in the terminal.
Builds a color map from extensions, populates a rich tree from structure
with build_tree, and prints it. structure is
rendered exactly as given: exclusions, depth limits, full paths, metrics, and Git
status are all determined by the scan that produced it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
structure
|
Directory
|
Scanned directory structure to render, as returned by
|
required |
extensions
|
set[str]
|
Set of file extensions found in structure, as returned alongside it by the scan. |
required |
root_name
|
str
|
Display name for the root node (e.g. the directory's basename, or a repository name for a GitHub input). |
required |
spec
|
DisplayOptions | None
|
Resolved sorting and annotation directives. Defaults to a plain
|
None
|
icon_style
|
str
|
Icon style to use, either |
'emoji'
|
Source code in recursivist/tree.py
Compare¶
recursivist.compare
¶
Side-by-side directory comparison.
Builds the structures for two directories with identical filtering and renders them next to each other, highlighting entries unique to either side. Supports the same filtering and metric options as the single-tree renderer, with terminal output for interactive use and HTML export for sharing.
build_comparison_tree
¶
build_comparison_tree(structure: Directory, other_structure: Directory, tree: Tree, spec: DisplayOptions, icon_style: str = 'emoji', identity_spec: DisplayOptions | None = None, *, this_is_remote: bool = False, other_is_remote: bool = False) -> None
Populate a rich tree, highlighting differences against another tree.
Recursively adds the entries of structure to tree, comparing each against
other_structure: items present in both are shown normally, items unique to
structure are highlighted in green, and items unique to other_structure are
highlighted in red. File names are rendered without file-type-specific colors so the
green/red difference highlighting stands out. Files are ordered by spec.sort_key
and metric annotations are appended in spec.metrics order.
When spec.show_git_status is set, each file is followed by a plain Git-status
badge — [U] untracked, [M] modified, [A] added, [D] deleted — read
from the git_markers stored on structure (and on other_structure for
entries unique to it). The badge is not color-coded; it trails the metric
parenthetical, and deleted files are struck through.
Two identically named files count as the same entry only when their displayed
annotations also match (see _comparison_identity), so a differing metric or Git
status marks them as unique to their side. identity_spec controls which
annotations that match considers: it defaults to spec, but a caller comparing a
local directory against a hosted repository passes
spec.without_remote_unsupported() so that annotations a remote side cannot
provide (modification time, Git status) are excluded from the identity — those are
still displayed per spec, but they do not split otherwise-matching files across
the two sides.
The traversal is shared with the HTML export (see _ComparisonWalker), so both
views always agree on ordering, badges and highlighting.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
structure
|
Directory
|
The directory being rendered. |
required |
other_structure
|
Directory
|
The directory being compared against. |
required |
tree
|
Tree
|
|
required |
spec
|
DisplayOptions
|
Resolved sorting and annotation directives. |
required |
icon_style
|
str
|
Icon style to use, either |
'emoji'
|
identity_spec
|
DisplayOptions | None
|
Directives governing which annotations contribute to cross-side file identity. Defaults to spec. |
None
|
this_is_remote
|
bool
|
Whether the primary structure originates from a hosted repository. |
False
|
other_is_remote
|
bool
|
Whether the compared structure originates from a hosted repository. |
False
|
Source code in recursivist/compare.py
display_comparison
¶
display_comparison(dir1: str, dir2: str, exclude_dirs: list[str] | None = None, ignore_file: str | None = None, exclude_extensions: set[str] | None = None, exclude_patterns: list[str] | None = None, include_patterns: list[str] | None = None, use_regex: bool = False, max_depth: int = 0, show_full_path: bool = False, spec: DisplayOptions | None = None, icon_style: str = 'emoji', *, targets: _Targets | None = None) -> None
Render two directory trees side by side in the terminal.
Scans both directories with identical options and prints them as two labeled, color-highlighted panels: entries unique to dir1 and dir2 are highlighted in contrasting colors, shared entries are shown normally, and a legend explains the scheme.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dir1
|
str
|
Path to the first directory. |
required |
dir2
|
str
|
Path to the second directory. |
required |
exclude_dirs
|
list[str] | None
|
Directory names to skip entirely. |
None
|
ignore_file
|
str | None
|
Name of an ignore file to honor (e.g. |
None
|
exclude_extensions
|
set[str] | None
|
File extensions to exclude. Normalized to a lowercase, dot-prefixed form before scanning. |
None
|
exclude_patterns
|
list[str] | None
|
Glob or regex patterns to exclude. |
None
|
include_patterns
|
list[str] | None
|
Glob or regex patterns to include. When given, only files whose names match one are kept, and a match overrides ignore-file rules for that file. They do not override exclude_dirs, exclude_extensions, or exclude_patterns. |
None
|
use_regex
|
bool
|
Whether to treat the patterns as regular expressions instead of glob patterns. |
False
|
max_depth
|
int
|
Maximum depth to display, or |
0
|
show_full_path
|
bool
|
Whether to display absolute paths instead of bare filenames. |
False
|
spec
|
DisplayOptions | None
|
Resolved sorting and annotation directives. Defaults to a plain
|
None
|
icon_style
|
str
|
Icon style to use, either |
'emoji'
|
targets
|
_Targets | None
|
The |
None
|
Source code in recursivist/compare.py
799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 | |
export_comparison
¶
export_comparison(dir1: str, dir2: str, format_type: str, output_path: str, exclude_dirs: list[str] | None = None, ignore_file: str | None = None, exclude_extensions: set[str] | None = None, exclude_patterns: list[str] | None = None, include_patterns: list[str] | None = None, use_regex: bool = False, max_depth: int = 0, show_full_path: bool = False, spec: DisplayOptions | None = None, icon_style: str = 'emoji', *, targets: _Targets | None = None) -> None
Export a side-by-side directory comparison to an HTML file.
Scans both directories with identical options and writes a standalone, responsive HTML document containing the highlighted comparison, a legend, and a summary of the settings used. Only HTML output is supported.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dir1
|
str
|
Path to the first directory. |
required |
dir2
|
str
|
Path to the second directory. |
required |
format_type
|
str
|
Export format. Only |
required |
output_path
|
str
|
Path the HTML file is written to. |
required |
exclude_dirs
|
list[str] | None
|
Directory names to skip entirely. |
None
|
ignore_file
|
str | None
|
Name of an ignore file to honor (e.g. |
None
|
exclude_extensions
|
set[str] | None
|
File extensions to exclude. Normalized to a lowercase, dot-prefixed form before scanning. |
None
|
exclude_patterns
|
list[str] | None
|
Glob or regex patterns to exclude. |
None
|
include_patterns
|
list[str] | None
|
Glob or regex patterns to include. When given, only files whose names match one are kept, and a match overrides ignore-file rules for that file. They do not override exclude_dirs, exclude_extensions, or exclude_patterns. |
None
|
use_regex
|
bool
|
Whether to treat the patterns as regular expressions instead of glob patterns. |
False
|
max_depth
|
int
|
Maximum depth to include, or |
0
|
show_full_path
|
bool
|
Whether to write absolute paths instead of bare filenames. |
False
|
spec
|
DisplayOptions | None
|
Resolved sorting and annotation directives. Defaults to a plain
|
None
|
icon_style
|
str
|
Icon style to use, either |
'emoji'
|
targets
|
_Targets | None
|
The |
None
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If format_type is not |
Source code in recursivist/compare.py
1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 | |
Colors¶
recursivist.colors
¶
Deterministic color assignment for file extensions.
Derives a hex color for each file extension from a hash of the extension, then nudges it
away from the colors already assigned so distinct extensions stay visually separable.
build_color_map colors a whole set of extensions
at once, in sorted order, so a given set always produces the same mapping. Also provides
WCAG 2.1 contrast helpers used by renderers that draw onto a known background (such as
the HTML exporter) to guarantee legible text. Pure standard library.
WCAG_AA_NORMAL_TEXT
module-attribute
¶
WCAG 2.1 level AA minimum contrast ratio for normal-sized body text.
WCAG_AAA_NORMAL_TEXT
module-attribute
¶
WCAG 2.1 level AAA minimum contrast ratio for normal-sized body text.
color_distance
¶
Calculate the perceptual distance between two RGB colors.
Uses a weighted Euclidean distance formula that approximates human color perception by emphasising the green channel over red and blue.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
color1
|
tuple[int, int, int]
|
First color as an |
required |
color2
|
tuple[int, int, int]
|
Second color as an |
required |
Returns:
| Type | Description |
|---|---|
float
|
A non-negative float representing the perceptual distance; |
float
|
colors are identical and larger values indicate greater visual difference. |
Source code in recursivist/colors.py
hex_to_rgb
¶
Convert a CSS hex color string to an (r, g, b) tuple.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
hex_color
|
str
|
Six-digit hex color string, optionally prefixed with |
required |
Returns:
| Type | Description |
|---|---|
tuple[int, int, int]
|
A three-tuple of integers |
Source code in recursivist/colors.py
rgb_to_hex
¶
Convert an (r, g, b) tuple to a CSS hex color string.
Each component is an integer in the range 0-255. The result is lowercase,
six digits long, and prefixed with '#'.
Source code in recursivist/colors.py
relative_luminance
¶
Calculate the WCAG relative luminance of an sRGB color.
Implements the definition given in WCAG 2.1: each channel is normalised to
0-1, linearised to remove the sRGB transfer function, and then combined with
the standard luminance coefficients.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
color
|
tuple[int, int, int]
|
Color as an |
required |
Returns:
| Type | Description |
|---|---|
float
|
The relative luminance, from |
Source code in recursivist/colors.py
contrast_ratio
¶
Calculate the WCAG contrast ratio between two colors.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
color1
|
tuple[int, int, int]
|
First color as an |
required |
color2
|
tuple[int, int, int]
|
Second color as an |
required |
Returns:
| Type | Description |
|---|---|
float
|
The contrast ratio, from |
float
|
against white). WCAG 2.1 requires at least |
float
|
AA and |
Source code in recursivist/colors.py
ensure_contrast
cached
¶
ensure_contrast(hex_color: str, background: str = '#ffffff', min_ratio: float = WCAG_AA_NORMAL_TEXT) -> str
Adjust a color until it meets a WCAG contrast ratio against background.
The hue is preserved so extensions stay recognisable and mutually distinguishable; only brightness (and, if brightness alone is not enough, saturation) is changed. Colors that already meet min_ratio are returned unchanged, so this is a no-op for compliant input.
Colors are darkened against light backgrounds and lightened against dark ones, whichever direction can reach the required ratio.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
hex_color
|
str
|
Foreground color as a hex string, with or without a leading |
required |
background
|
str
|
Background color the text is drawn on, as a hex string. |
'#ffffff'
|
min_ratio
|
float
|
Minimum acceptable contrast ratio. Defaults to |
WCAG_AA_NORMAL_TEXT
|
Returns:
| Type | Description |
|---|---|
str
|
A CSS hex color string that meets min_ratio against background, or (if no |
str
|
adjustment of this hue can reach the ratio) the closest achievable color. |
Source code in recursivist/colors.py
build_color_map
¶
Assign a visually distinct color to every extension in extensions.
Extensions are colored in sorted order, starting from an empty set of assigned colors, so the result is a pure function of the set of extensions: it depends neither on the order extensions is iterated in nor on any colors generated earlier. The same set therefore always yields the same mapping, across runs and across renderers.
Each color starts from a hash of its extension and is nudged away from the colors of the extensions sorted before it. An extension's color can consequently differ between two sets that contain different extensions.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
extensions
|
Iterable[str]
|
File extensions to color, each as it should be keyed in the result
(e.g. |
required |
Returns:
| Type | Description |
|---|---|
dict[str, str]
|
A mapping of each extension to a CSS hex color string. An empty extension maps |
dict[str, str]
|
to white. |
Source code in recursivist/colors.py
Icons¶
recursivist.icons
¶
Nerd Font icon mappings for files and directories.
This module provides icon lookup utilities based on Nerd Font glyph codes. Icons are resolved in priority order:
- Exact filename match (e.g.,
Dockerfile,package.json) - File extension match (e.g.,
.py,.ts) - Named folder match (e.g.,
node_modules,.git) - Generic fallback icons for unknown files and directories
get_icon
¶
Return the icon for a file or directory in the requested style.
With the "emoji" style, a single generic file emoji is returned for files and
an open or closed folder emoji for directories. With the "nerd" style, a Nerd
Font glyph is resolved in priority order.
For files:
- Exact filename match in
EXACT_MATCH_ICONS(case-insensitive). - File-extension match in
EXTENSION_ICONS. - The
DEFAULT_NERD_FILEfallback.
For directories, FOLDER_ICONS is consulted first (so well-known folders keep
their distinctive glyph regardless of their contents), falling back to the open or
closed generic folder glyph depending on is_empty.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
filename
|
str
|
Name of the file or directory (basename only, not a full path). Matched case-insensitively. |
required |
is_dir
|
bool
|
When |
False
|
style
|
str
|
Icon style to use, either |
'emoji'
|
is_empty
|
bool
|
Only meaningful when is_dir is set. When |
False
|
Returns:
| Type | Description |
|---|---|
str
|
A single Unicode character containing the matching glyph. |