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).
Metadata
Release files for srcloc 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| srcloc-0.2.0.tar.gz | 40.9 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| srcloc-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 72.4 kB
Release files / srcloc-0.2.0.tar.gz
| Download URL | srcloc-0.2.0.tar.gz |
|---|---|
| Size | 40.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d9c33ccdf6915c20d5224b2fdef2819f00d56df90e5afd63268353b1e41ad629
|
|
BLAKE2b-256 checksum How to use checksums |
e3bcd5499fa108ad51595d730e06de61270b553a19ba8bc45e007928c9ef9667
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 29, 2026.
Transparency logRelease files / srcloc-0.2.0-py3-none-any.whl
| Download URL | srcloc-0.2.0-py3-none-any.whl |
|---|---|
| Size | 31.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a4acfc4ec212c4bc0e9b46cefd0d937c50db789357ccabf51f3bd86f361f9a15
|
|
BLAKE2b-256 checksum How to use checksums |
56ce0a8415e97a3ccf2d4ff0cd4ece8d1df56f0731244dddd2f871eac9f071da
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 29, 2026.
Transparency logSecurity for srcloc 0.2.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for srcloc-0.2.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 29, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.