Skip to main content

srcloc: line counting for source trees

srcloc counts how a tree splits into doc, comment, config, testdata, test and code lines, here on chatmail's filtermail relay:

$ 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



$ srcloc -v
doc   comment   config   test   code   SUM   file
---   -------   ------   ----   ----   ---   -----------------------------
  .         .        .    337      .   337   tests/test_kinds.py
  .         .        .    335      .   335   tests/test_cli.py
  .        23        .      .    231   254   srcloc/kinds.py
  .         9        .      .    233   242   srcloc/cli.py
  .        20        .      .    191   211   srcloc/views.py
189         .        .      .      .   189   README.md
  .        14        .      .    165   179   srcloc/langs.py
  .        21        .      .    134   155   srcloc/collect.py
  .         .        .    132      .   132   tests/test_collect.py
  .         .        .    109      .   109   tests/test_tables.py
  .        24       60      .      .    84   cliff.toml
  .        16        .      .     64    80   srcloc/tables.py
  .         .        .     57      .    57   tests/conftest.py
  .        22       24      .      .    46   .github/workflows/release.yml
  .         .       37      .      .    37   pyproject.toml
  .         1       15      .      .    16   .github/workflows/ci.yml
  .         1        .      .      .     1   srcloc/__init__.py
  .         .        .      .      .     .   CHANGELOG.md

language   NUMFILES   doc   comment   config   test   code   SUM
--------   --------   ---   -------   ------   ----   ----   ----
python           12     .       104        .    970   1018   2092
markdown          2   189         .        .      .      .    189
toml              2     .        24       97      .      .    121
yaml              2     .        23       39      .      .     62
unknown           1     .         .        .      .      .      .
--------   --------   ---   -------   ------   ----   ----   ----
total            19   189       151      136    970   1018   2464




$ 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


$ srcloc --comment -v
comment    file
--------   -----------------------------
24 [18%]   cliff.toml
23 [17%]   srcloc/kinds.py
22 [16%]   .github/workflows/release.yml
21 [15%]   srcloc/collect.py
16 [12%]   srcloc/tables.py
13 [ 9%]   srcloc/langs.py
13 [ 9%]   srcloc/views.py
 3 [ 2%]   srcloc/cli.py
 1 [ 1%]   .github/workflows/ci.yml
 1 [ 1%]   srcloc/__init__.py

language    comment
--------   ----------
python      90 [ 66%]
toml        24 [ 18%]
yaml        23 [ 17%]
--------   ----------
total      137 [100%]

Install

uv tool install srcloc

Bash completion: add eval "$(srcloc --completion)" to ~/.bashrc.

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.

  • -v adds files: per path the language and the counted lines per category.

  • -vv adds unknown: 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, Rust tests.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, testdata or fixtures directory, 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; .svtest files 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 .plist and friends) and svg: /* */ and <!-- --> block comments, plain lines count as config.

  • Prose -- markdown, rst, txt, html, man (.1 through .9): doc lines only.

  • Data -- eml, pgp (.asc/.pem): pure testdata lines.

  • Media -- png, jpg, gif, webp, ico, font (.woff/.ttf and 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; -vv lists 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 -U999999 when 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

srcloc-0.1.0.tar.gz (40.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

srcloc-0.1.0-py3-none-any.whl (31.4 kB view details)

Uploaded Python 3

File details

Details for the file srcloc-0.1.0.tar.gz.

File metadata

  • Download URL: srcloc-0.1.0.tar.gz
  • Upload date:
  • Size: 40.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for srcloc-0.1.0.tar.gz
Algorithm Hash digest
SHA256 ce54cebd83522e62e401d3c755f0801ddeccdde5823764d02c7d56583bb83e2f
MD5 649441ec2f53d12c372783e273d7ae8a
BLAKE2b-256 9f5859fdb53dd7c1e5628480ef655f1824afd7ca8db7643953379307d111724b

See more details on using hashes here.

Provenance

The following attestation bundles were made for srcloc-0.1.0.tar.gz:

Publisher: release.yml on hpk42/srcloc

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file srcloc-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: srcloc-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 31.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for srcloc-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 bf33a20235b67c32c6ce21291213003a2800afce2f3933cb84520deccde429b5
MD5 e6a5e9f7be7b4c72abae043468b70f79
BLAKE2b-256 c52cfcf3ba8bdcc44f7bff52be64744055fceeb25ac5943137c397a9ddf7deb1

See more details on using hashes here.

Provenance

The following attestation bundles were made for srcloc-0.1.0-py3-none-any.whl:

Publisher: release.yml on hpk42/srcloc

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.2.0

2 files

This release

0.1.0 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page