srcloc: line counting for source trees
srcloc counts how a tree splits into doc, comment, config, testdata, test and code lines.
Install
uv tool install srcloc
Examples
Comparing a git diff
chatmail/core$ git diff main.. | srcloc
category added removed net
--------------- ----- ------- ----
comment rust +100 -7 +93
testdata eml +15 -0 +15
test python +37 -0 +37
test rust +432 -16 +416
code rust +379 -133 +246
--------------- ----- ------- ----
total +963 -156 +807
One category across the files
chatmail/srcloc$ srcloc --test -v
test file
---------- ---------------------
339 [ 35%] tests/test_cli.py
337 [ 35%] tests/test_kinds.py
132 [ 14%] tests/test_collect.py
109 [ 11%] tests/test_tables.py
57 [ 6%] tests/conftest.py
---------- ---------------------
974 [100%] total
language test
-------- ----------
python 974 [100%]
-------- ----------
total 974 [100%]
Counting a source tree
chatmail/filtermail$ srcloc
language NUMFILES doc comment config testdata test code SUM
-------- -------- --- ------- ------ -------- ---- ---- ----
rust 21 . 464 . . 670 2746 3880
eml 14 . . . 513 . . 513
markdown 2 331 . . . . . 331
yaml 3 . 4 176 . . . 180
toml 2 . 41 108 . . . 149
unknown 13 . . . . . . .
-------- -------- --- ------- ------ -------- ---- ---- ----
total 55 331 509 284 513 670 2746 5053
Per-file counts, columns as percentages
chatmail/srcloc$ srcloc -v -c
doc comment config test code SUM file
---------- ---------- ---------- ---------- ----------- ---- -----------------------------
. . . 339 [ 35%] . 339 tests/test_cli.py
. . . 337 [ 35%] . 337 tests/test_kinds.py
. 23 [ 15%] . . 231 [ 23%] 254 srcloc/kinds.py
. 9 [ 6%] . . 233 [ 23%] 242 srcloc/cli.py
. 20 [ 13%] . . 195 [ 19%] 215 srcloc/views.py
191 [ 99%] . . . . 191 README.md
. 14 [ 9%] . . 165 [ 16%] 179 srcloc/langs.py
. 21 [ 14%] . . 134 [ 13%] 155 srcloc/collect.py
. . . 132 [ 14%] . 132 tests/test_collect.py
. . . 109 [ 11%] . 109 tests/test_tables.py
. 24 [ 16%] 60 [ 44%] . . 84 cliff.toml
. 16 [ 11%] . . 64 [ 6%] 80 srcloc/tables.py
. . . 57 [ 6%] . 57 tests/conftest.py
. 22 [ 15%] 24 [ 18%] . . 46 .github/workflows/release.yml
. . 37 [ 27%] . . 37 pyproject.toml
. 1 [ 1%] 15 [ 11%] . . 16 .github/workflows/ci.yml
2 [ 1%] . . . . 2 CHANGELOG.md
. 1 [ 1%] . . . 1 srcloc/__init__.py
. . . . . . tests/__init__.py
---------- ---------- ---------- ---------- ----------- ---- -----------------------------
193 [100%] 151 [100%] 136 [100%] 974 [100%] 1022 [100%] 2476 total
language NUMFILES doc comment config test code SUM
-------- -------- ---------- ---------- ---------- ---------- ----------- ----
python 13 . 104 [ 69%] . 974 [100%] 1022 [100%] 2100
markdown 2 193 [100%] . . . . 193
toml 2 . 24 [ 16%] 97 [ 71%] . . 121
yaml 2 . 23 [ 15%] 39 [ 29%] . . 62
unknown 2 . . . . . .
-------- -------- ---------- ---------- ---------- ---------- ----------- ----
total 21 193 [100%] 151 [100%] 136 [100%] 974 [100%] 1022 [100%] 2476
JSON
--json prints one JSON object instead of the tables, for jq
and other postprocessing. Zero counts are left out everywhere,
like the tables' dot cells; the category, language
and --with-empty flags filter as usual.
Counting:
{
"languages": {"python": {"files": 2, "comment": 69, "code": 681},
"markdown": {"files": 2, "doc": 126}},
"total": {"files": 4, "doc": 126, "comment": 69, "code": 681},
"unknown_files": 1
}
-
languages: per language the file count and its counted lines per category. -
total: the same summed over the languages; the unknown files are not part of it. -
unknown_files: how many files stayed uncounted, when any. -
-vaddsfiles: per path the language and the counted lines per category. -
-vvaddsunknown: the uncounted paths.
A diff carries added and removed instead,
each language to counted lines per category,
and total sums both sides over the shown categories:
{
"added": {"rust": {"comment": 100, "test": 432, "code": 379}},
"removed": {"rust": {"comment": 7, "test": 16, "code": 133}},
"total": {"added": 963, "removed": 156}
}
-v adds files: per path its added and removed line counts.
Counting rules
Every line lands in one category, the tables running from the least to the most important:
-
test: whole test files (
conftest.py,test_*.py,*.test.ts,*.spec.js,tests/directories, Rusttests.rs,*_tests.rs,test_*.rs,tests/crates) and#[cfg(test)]regions inside regular Rust modules. -
testdata: every line of a file under a
test-data,test_data,testdataorfixturesdirectory, and of the data formats eml and pgp wherever they live. -
code: every other non-empty line. A line holding code and a trailing comment counts as code.
-
config: every non-comment line of a config format. Shell scripts execute, so their lines count as code.
-
doc: every non-empty line of a prose file, the only category prose knows.
-
comment: comment-only lines outside test code. Python docstrings count here; Rust
//,///and nested/* */are recognized, comment markers inside literals are not.
Empty lines count only with --with-empty;
a zero count shows as a dim dot, and a category
without any lines drops out of the table.
The languages:
-
C-style code -- javascript (also
.jsx), typescript, c (.c/.h), cpp, java, kotlin, swift, go, qml, gradle -- read like Rust://and/* */comments and string literals are recognized, backtick strings included. -
Script formats -- shell, sql, lua, sieve, make (
.mk/.am,Makefile), docker (Dockerfile): line comments (#, in sql and lua--), plain lines count as code, they execute;.svtestfiles are sieve tests. -
Config formats -- systemd (the unit suffixes), conf (
.conf/.cf), ini (.ini/.cfg), toml, yaml, json, nix, m4 (.m4/.ac), zone, po, strings, cmake (CMakeLists.txt): line comments (json knows none), plain lines count as config. -
Markup -- css (also
.scss/.sass), xml (also.plistand friends) and svg:/* */and<!-- -->block comments, plain lines count as config. -
Prose -- markdown, rst, txt, html, man (
.1through.9): doc lines only. -
Data -- eml, pgp (
.asc/.pem): pure testdata lines. -
Media -- png, jpg, gif, webp, ico, font (
.woff/.ttfand kin), pdf, xdc: one row per type, counted as files, never read. -
unknown: everything else -- binaries and generated lock files (
Cargo.lock,package-lock.json, ...) among them -- is counted, never read;-vvlists the paths.
A .f (str.format), .j2 (jinja2) or .in
(autoconf) template suffix is stripped first,
so doveauth.service.f is a systemd unit and Makefile.in a makefile.
Fine print:
-
Outside git, ephemeral directories (
venv,target,node_modules,build,dist,__pycache__, dot-directories,*.egg-info) are skipped, also where a repository tracks them. -
Changed lines are classified with the context the diff carries; pipe
git diff -U999999when Rust test regions far from a change must be attributed exactly.
Development
uv venv venv
uv pip install -e . --python venv/bin/python
venv/bin/python -m pytest
Code must pass ruff check . and ruff format --check ..
CI and releasing
CI and releases follow the shared
chatmail/workflows standards:
every push runs the reusable py-checks flow (ruff lint and format
at a centrally pinned version, uv build, a twine metadata check
and pytest), and pushing a vX.Y.Z tag runs the same checks
and then publishes the built distributions to PyPI.
Publishing uses PyPI trusted publishing (OIDC):
the release workflow authenticates as this repository against
the pypi environment, so no long-lived token exists anywhere.
The version derives from the git tag (setuptools-git-versioning)
and the changelog from the commit messages (git-cliff, cliff.toml).
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file srcloc-0.2.0.tar.gz.
File metadata
- Download URL: srcloc-0.2.0.tar.gz
- Upload date:
- Size: 40.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d9c33ccdf6915c20d5224b2fdef2819f00d56df90e5afd63268353b1e41ad629
|
|
| MD5 |
f659e9f69bcc6f938e5a6fe13a9da672
|
|
| BLAKE2b-256 |
e3bcd5499fa108ad51595d730e06de61270b553a19ba8bc45e007928c9ef9667
|
Provenance
The following attestation bundles were made for srcloc-0.2.0.tar.gz:
Publisher:
release.yml on hpk42/srcloc
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
srcloc-0.2.0.tar.gz -
Subject digest:
d9c33ccdf6915c20d5224b2fdef2819f00d56df90e5afd63268353b1e41ad629 - Sigstore transparency entry: 2645275580
- Sigstore integration time:
-
Permalink:
hpk42/srcloc@23fde78ceddfbccb6a4070b054e526037f8d189d -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/hpk42
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@23fde78ceddfbccb6a4070b054e526037f8d189d -
Trigger Event:
push
-
Statement type:
File details
Details for the file srcloc-0.2.0-py3-none-any.whl.
File metadata
- Download URL: srcloc-0.2.0-py3-none-any.whl
- Upload date:
- Size: 31.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a4acfc4ec212c4bc0e9b46cefd0d937c50db789357ccabf51f3bd86f361f9a15
|
|
| MD5 |
b687ef7cfbdf6d418be87022be463f2a
|
|
| BLAKE2b-256 |
56ce0a8415e97a3ccf2d4ff0cd4ece8d1df56f0731244dddd2f871eac9f071da
|
Provenance
The following attestation bundles were made for srcloc-0.2.0-py3-none-any.whl:
Publisher:
release.yml on hpk42/srcloc
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
srcloc-0.2.0-py3-none-any.whl -
Subject digest:
a4acfc4ec212c4bc0e9b46cefd0d937c50db789357ccabf51f3bd86f361f9a15 - Sigstore transparency entry: 2645275647
- Sigstore integration time:
-
Permalink:
hpk42/srcloc@23fde78ceddfbccb6a4070b054e526037f8d189d -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/hpk42
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@23fde78ceddfbccb6a4070b054e526037f8d189d -
Trigger Event:
push
-
Statement type: