Sorting and Metrics¶
How command-line flags resolve into a DisplayOptions, how files are ordered, and how lines of code, sizes, and modification times are measured and formatted.
Flags¶
recursivist.flags
¶
Command-line flag resolution for file sorting and annotation.
Recursivist exposes three families of file-annotation flags:
- Sorting-only —
--sort-by-similaritygroups similarly named files together but adds no annotation of its own. - Combined —
--sort-by-loc,--sort-by-size,--sort-by-mtimeand--sort-by-git-statuseach sort files by a metric and annotate every file with that metric. - Display-only —
--loc,--size,--mtimeand--git-statusannotate files with a metric without influencing the ordering.
When several of these flags are combined, they are resolved strictly by their
left-to-right order on the command line rather than by any fixed internal precedence.
The rules, implemented by resolve_flags, are:
- Only the first sorting flag (sorting-only or combined) is honored; every later sorting flag is discarded completely — it contributes neither ordering nor annotation.
- Display-only flags always annotate. Their annotations appear in the exact order the flags were given.
- When the winning sort is a combined numeric metric (LOC, size or mtime), that metric's annotation is shown first, ahead of any display-only ones.
- When the winning sort is a combined Git-status flag, its badge trails at the very end, after every display-only annotation.
The resolution is expressed as a DisplayOptions,
the single value the renderers and exporters consult to decide how to sort and what to
annotate.
FlagSpec
dataclass
¶
Static description of a single order-sensitive flag.
Attributes:
| Name | Type | Description |
|---|---|---|
id |
str
|
Stable identifier used as a dictionary key by the CLI layer. |
mode |
str
|
One of |
metric |
str
|
The metric the flag relates to (e.g. |
Source code in recursivist/flags.py
DisplayOptions
dataclass
¶
Resolved sorting and annotation directives for a single run.
This is the value produced by resolve_flags and
threaded through the renderers and exporters. It keeps two concerns separate: how
files are ordered (sort_key) and what is annotated, and in what order
(metrics plus show_git_status).
Attributes:
| Name | Type | Description |
|---|---|---|
sort_key |
str | None
|
The single metric files are ordered by — one of |
metrics |
tuple[str, ...]
|
The numeric metrics (subset of |
show_git_status |
bool
|
Whether to append the Git-status badge to each file. The badge always trails the numeric-metric parenthetical. |
Source code in recursivist/flags.py
without_remote_unsupported
¶
without_remote_unsupported() -> DisplayOptions
Return a copy with annotations that don't apply to a hosted repo.
A GitHub checkout has no meaningful per-file Git status or modification time — every file effectively shares the tip commit's status and timestamp — so the Git-status badge and the modification-time metric are dropped, and a sort keyed on either falls back to the default ordering. The lines-of-code and size metrics are retained, since those are computed from the file contents themselves.
Returns:
| Type | Description |
|---|---|
DisplayOptions
|
A |
DisplayOptions
|
modification time removed from both the sort key and the annotation set. |
Source code in recursivist/flags.py
resolve_flags
¶
resolve_flags(events: Sequence[tuple[str, str]]) -> DisplayOptions
Resolve an ordered sequence of flag events into
DisplayOptions.
Each event is a (mode, metric) pair drawn from the registry, in the
left-to-right order the flags appeared on the command line. The resolution rules are
described in the module docstring.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
events
|
Sequence[tuple[str, str]]
|
The flag events, ordered by command-line position. |
required |
Returns:
| Type | Description |
|---|---|
DisplayOptions
|
The resolved |
Source code in recursivist/flags.py
resolve_display_options
¶
resolve_display_options(*, sort_loc: bool = False, sort_size: bool = False, sort_mtime: bool = False, sort_similarity: bool = False, sort_git: bool = False, disp_loc: bool = False, disp_size: bool = False, disp_mtime: bool = False, disp_git: bool = False, order: Sequence[str] = ()) -> DisplayOptions
Resolve the raw per-flag booleans into
DisplayOptions.
The boolean arguments say which flags are active and order says in what order they were given; the CLI parser supplies both. order only arranges the active set — a flag listed in it but not active is ignored, and an active flag missing from it is placed after the listed ones, in registry order, so resolution is deterministic.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
sort_loc
|
bool
|
Whether |
False
|
sort_size
|
bool
|
Whether |
False
|
sort_mtime
|
bool
|
Whether |
False
|
sort_similarity
|
bool
|
Whether |
False
|
sort_git
|
bool
|
Whether |
False
|
disp_loc
|
bool
|
Whether |
False
|
disp_size
|
bool
|
Whether |
False
|
disp_mtime
|
bool
|
Whether |
False
|
disp_git
|
bool
|
Whether |
False
|
order
|
Sequence[str]
|
The ids of the flags (see |
()
|
Returns:
| Type | Description |
|---|---|
DisplayOptions
|
The resolved |
Raises:
| Type | Description |
|---|---|
ValueError
|
If order contains an id that is not in the registry. |
Source code in recursivist/flags.py
Sorting¶
recursivist.sorting
¶
File ordering.
Sorts a directory's files by extension/name, by a numeric metric (LOC, size, mtime), or
groups them by name similarity. Operates on FileEntry
sort_files_by_similarity
¶
Order files so that similarly named files sit next to each other.
Unlike the LOC/size/mtime metrics, name similarity is relational: it depends on
how a file's name compares to the others rather than on any single measured value,
so it cannot be expressed as a sorted key. Instead this builds a greedy
nearest-neighbour chain:
- Entries are seeded in case-insensitive name order and the alphabetically-first name becomes the start of the chain. Using a fixed, name-derived anchor makes the result deterministic and stable across runs (the same directory always yields the same order).
- Repeatedly, the not-yet-placed entry whose name is most similar to the most
recently placed name is appended. Similarity is the
difflib.SequenceMatcherratio computed case-insensitively on the full filename (extension included), somain.py/main.jsandtest_api.py/test_api.jsnaturally cluster. - Ratio ties are broken by case-insensitive name order: because the candidates are kept alphabetically sorted and the best match is only replaced on a strictly greater ratio, the alphabetically-first of any tied group wins.
This is a heuristic (locally greedy) ordering rather than a globally optimal
grouping, which is the appropriate trade-off for a directory listing: each directory
holds relatively few files, so the O(n^2) pairwise comparisons are cheap, and
the result reliably places obvious name-siblings adjacent to one another.
Only the name is used, so the metric fields do not affect the result.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
files
|
Sequence[FileEntry]
|
The |
required |
Returns:
| Type | Description |
|---|---|
list[FileEntry]
|
Reordered list of |
list[FileEntry]
|
files adjacent. |
Source code in recursivist/sorting.py
sort_files_by_type
¶
sort_files_by_type(files: Sequence[FileEntry], sort_key: str | None = None, git_markers: Mapping[str, str] | None = None) -> list[FileEntry]
Order files by a single resolved sort key.
Exactly one ordering is applied, chosen by sort_key:
None: the default — by extension, then case-insensitive name."loc"/"size"/"mtime": by that metric, largest/newest first, and by case-insensitive name among files with equal values."git_status": grouped by Git status (modified, added, deleted, untracked, then clean), and by case-insensitive name within each group. Requires git_markers."similarity": by name similarity, viasort_files_by_similarity.
Every ordering ends in a name tie-breaker, so the result is independent of the
order of files (which the scanner takes from os.listdir).
This mirrors the resolution in recursivist.flags, where only
the first sorting flag on the command line takes effect, so there is never more than
one active metric to combine.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
files
|
Sequence[FileEntry]
|
The |
required |
sort_key
|
str | None
|
The single metric to order by, or |
None
|
git_markers
|
Mapping[str, str] | None
|
|
None
|
Returns:
| Type | Description |
|---|---|
list[FileEntry]
|
Sorted list of |
Source code in recursivist/sorting.py
Metrics¶
recursivist.metrics
¶
File statistics and metric formatting.
Lines-of-code counting, file size and modification-time retrieval, and the helpers that format those metrics into the annotation suffixes shown next to files and directories. Uses only the standard library and the shared data model.
count_lines_of_code
¶
Count the number of lines in a text file.
The encoding is inferred from the first 4 KiB: UTF-16 files are recognized by their byte-order mark or by a regular pattern of null bytes, while files containing null bytes that are not UTF-16 are treated as binary and skipped. Everything else is read as UTF-8. Undecodable bytes are replaced rather than rejected, which never changes the line count, so the file is opened once and read in a single pass.
Only regular files are read. Named pipes, sockets and devices are opened without blocking and then skipped, because reading one can wait forever or never end.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
file_path
|
str
|
Path to the file. |
required |
Returns:
| Type | Description |
|---|---|
int
|
The number of lines, or |
int
|
or cannot be read. |
Source code in recursivist/metrics.py
get_file_size
¶
Return the size of a file in bytes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
file_path
|
str
|
Path to the file whose size should be retrieved. |
required |
Returns:
| Type | Description |
|---|---|
int
|
Size of the file in bytes, or |
int
|
permission error or the path no longer exists). |
Source code in recursivist/metrics.py
format_size
¶
Format a byte count as a human-readable size string.
Scales the value to bytes, KB, MB, or GB and formats it with one decimal place for
every unit above bytes. The unit is chosen after rounding, so a value that rounds up
to 1024 moves to the next unit (1048575 is "1.0 MB", not "1024.0 KB").
GB is the largest unit, so it is never promoted.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
size_in_bytes
|
int
|
Size in bytes. |
required |
Returns:
| Type | Description |
|---|---|
str
|
A human-readable size string (e.g. |
Source code in recursivist/metrics.py
get_file_mtime
¶
Return a file's modification time in seconds since the epoch.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
file_path
|
str
|
Path to the file. |
required |
Returns:
| Type | Description |
|---|---|
float
|
The modification time as a float, or |
Source code in recursivist/metrics.py
format_timestamp
¶
Format a Unix timestamp as a human-readable, recency-aware string.
The representation becomes coarser as the timestamp gets older:
- Today:
"Today HH:MM" - Yesterday:
"Yesterday HH:MM" - Within the last week: abbreviated weekday and time (e.g.
"Mon 14:30") - Earlier this year: abbreviated month and day (e.g.
"Mar 15") - Older:
"YYYY-MM-DD"
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
timestamp
|
float
|
Seconds since the epoch. |
required |
Returns:
| Type | Description |
|---|---|
str
|
The formatted date/time string, or |
str
|
outside the representable range. |
Source code in recursivist/metrics.py
format_metrics
¶
Build the parenthetical metrics annotation for a file or directory.
Includes exactly the metrics named in metrics, in that order — e.g.
metrics=("size", "loc") yields "(4.2 KB, 120 lines)". The metric names are
those defined in recursivist.flags: "loc", "size" and
"mtime".
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
loc
|
int
|
Lines-of-code count. |
0
|
size
|
int
|
Size in bytes. |
0
|
mtime
|
float
|
Modification time (seconds since epoch). |
0.0
|
metrics
|
Sequence[str]
|
The metrics to include, in display order. |
()
|
Returns:
| Type | Description |
|---|---|
str
|
The annotation string including the surrounding parentheses, or an empty string |
str
|
when metrics is empty. |
Source code in recursivist/metrics.py
format_metrics_suffix
¶
format_metrics_suffix(loc: int = 0, size: int = 0, mtime: float = 0.0, metrics: Sequence[str] = ()) -> str
Like format_metrics but prefixed with a
single space.
Convenient for appending directly after a file or directory name. Returns an empty string (no leading space) when metrics is empty.
Source code in recursivist/metrics.py
recorded_dir_metrics
¶
recorded_dir_metrics(directory: Directory, metrics: Sequence[str] = ()) -> list[str]
Return the entries of metrics that directory holds a total for.
A directory carries a total only for the metrics its scan collected, and none at all when it was not traversed. The order of metrics is preserved.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
directory
|
Directory
|
The directory whose totals are consulted. |
required |
metrics
|
Sequence[str]
|
The metrics to display, in order. |
()
|
Returns:
| Type | Description |
|---|---|
list[str]
|
The displayable subset of metrics, in the same order. |
Source code in recursivist/metrics.py
format_dir_metrics
¶
format_dir_metrics(directory: Directory, metrics: Sequence[str] = ()) -> str
Return the space-prefixed metrics suffix for a directory.
Wraps format_metrics_suffix, reading
the totals from directory and keeping only the requested metrics that it actually
holds a total for (see
recorded_dir_metrics) — while
preserving the requested display order.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
directory
|
Directory
|
The directory whose totals are formatted. |
required |
metrics
|
Sequence[str]
|
The metrics to display, in order. |
()
|
Returns:
| Type | Description |
|---|---|
str
|
The metrics suffix (with a leading space) or an empty string. |