Skip to main content

hmh — read-only project analyzer

Split any project into its spectrum.
Lines of code, languages, sizes and structure — without ever touching your files.

PyPI Python License CI


hmh is a read-only project analyzer for Python. Point it at any folder and it tells you exactly what is inside: how many lines, in how many languages, how big everything is, and which files carry the most weight.

It reads. It never writes.

pip install hmh
hmh .
hmh  ·  read-only project analysis
path      /home/you/projects/myapp
scanned   412 files (388 source · 24 binary)
size      5.4 MB
lines     71,338  =  code 54,120  ·  comments 9,004  ·  blank 8,214
comments  0.166 per code line
duration  312 ms

LANGUAGE              FILES        CODE   COMMENTS      BLANK   SHARE
Python                  142     28,410      5,201      4,118   52.5%
TypeScript               96     14,902      2,140      2,310   27.5%
JavaScript               71      6,744      1,003      1,020   12.5%
CSS                      34      2,860        412        488    5.3%
YAML                     22        680        188        144    1.3%
Markdown                 23          42         60         34    0.1%

Why hmh?

Most code counters are useful but invasive — they beg you to add config, install a daemon, or grant write access. hmh has exactly one hard rule:

hmh NEVER modifies the source project.

Every file is opened read-only. Nothing is deleted, moved, renamed, rewritten, or "cleaned up". There is no cache directory, no lock file, no temp copy, no sidecar metadata. Run it twice and the directory's hashes, sizes and mtimes are byte-for-byte identical. A dedicated test enforces this.

That makes it safe to run on:

  • production boxes and CI runners where you only have read access,
  • mounted or read-only volumes,
  • repositories mid-rebase, mid-git stash, or with uncommitted work,
  • anything you would rather not let a tool "tidy up".

Features

  • 65+ languages detected by extension, well-known filenames (Dockerfile, Makefile, CMakeLists.txt, Rakefile…) and #! shebangs.
  • Real comment counting — per-language line comments, block comments and multi-line strings are tracked across lines, so # inside a Python string is not mistaken for a comment.
  • Four numbers that matter: code, comments, blank and total lines — per file and per language.
  • Language share by code lines and by bytes, so you can see where the weight really is.
  • Structure: file counts, binary detection, largest and densest files.
  • .gitignore aware — honours your existing rules (including ! negation), and can be switched off entirely with --no-gitignore.
  • Zero runtime dependencies — Python standard library only.
  • Blazing fast — single-pass walk with os.scandir, no subprocesses, no git calls.
  • Typed — ships py.typed, full annotations, no # type: ignore smoke.

Install

pip install hmh

Requires Python 3.9+.

Command line

hmh .                                  # summary table for the current directory
hmh ~/projects/myapp --top 10          # only the 10 biggest languages
hmh . --files 5                        # also list the 5 densest source files
hmh . --format markdown -o REPORT.md   # embeddable table for docs
hmh . --format json | jq '.totals'     # machine-readable
hmh . --format csv > stats.csv         # spreadsheet-ready
hmh --languages                        # every language hmh knows
Option Meaning
--format {table,json,csv,markdown} Output format (default table)
--top N Show only the N largest languages
--sort {code,lines,files,bytes,name} Ordering of the language table
--files N Also list the N densest source files
--exclude PATTERN Extra gitignore-style pattern to skip (repeatable)
--include-hidden Also walk dot-directories (.github, .venv, …)
--no-gitignore Ignore .gitignore and scan everything
--follow-symlinks Traverse symlinks instead of skipping them
--max-file-mb N Skip line-counting above this size (default 20)
-o, --out FILE Write the report to a file
-q, --quiet Print nothing; only the exit code

Exit codes: 0 success, 2 bad path or unreadable project.

Python API

import hmh

report = hmh.analyze("~/projects/myapp")

print(report.code_lines)        # 54120
print(len(report.languages))    # 6
print(report.size_human)        # '5.4 MB'

for lang in report.top(5):
    print(f"{lang.name:<15} {lang.code:>7,}  {lang.share_of(report.code_lines):5.1f}%")

Report is a plain dataclass — inspect it, serialize it, or render it:

report.to_dict()                                  # nested dict, ready for JSON
report.languages_by_code                          # sorted
report.languages_by_bytes
report.densest_files                              # biggest source files by code lines
report.largest_files                              # biggest files by bytes
report.comments_per_code                          # density signal

hmh.format_report(report, "markdown")            # same renderer the CLI uses

Analyse several projects at once:

for report in hmh.analyze_many(["./api", "./web", "./infra"]):
    print(report.root, report.code_lines)

Need the raw numbers without a walk? hmh.languages.count_lines(text, "Python") returns (code, comment, blank) for any string.

How it detects languages

hmh resolves a file's language in this order:

  1. Known filename — Dockerfile, Makefile, CMakeLists.txt, Rakefile, Gemfile, BUILD.
  2. Extension — .py, .tsx, .rs, .go, .vue, .tf, .proto, .nix, …
  3. Shebang — #!/usr/bin/env python3 → Python, even without an extension.

Anything else is unknown. Files containing a NUL byte in the first 8 KB are treated as binary: they are counted by size, never parsed.

How it counts lines

Counting is a small state machine that walks each line character by character, tracking:

  • whether we are inside a block comment (/* … */, <!-- … -->, {- … -}),
  • whether we are inside a multi-line string (""" … """ in Python),
  • whether a line comment (#, //, --, %, ;) starts after real code.

That last point is why int x; // trailing counts as code, not as a comment — and why a docstring counts as code, not as documentation. Languages with no comment syntax (JSON, CSV, Markdown) count every non-blank line as code, matching how cloc and tokei behave.

What is skipped

By default hmh never walks into dependency and build directories, because they are reproducible noise rather than your project:

.git  .hg  .svn  node_modules  __pycache__  .venv  venv  .tox  .nox
.mypy_cache  .pytest_cache  .ruff_cache  .next  .nuxt  target  build
dist  vendor  coverage  .gradle  .idea  .vscode  …

Add --include-hidden to walk dot-directories, or --no-gitignore to scan absolutely everything. Explicit --exclude patterns always win.

.env* files are never reported on, so secrets do not end up in a shared report.

Comparisons

hmh cloc tokei scc
Runtime deps none (Python) Perl binary Rust binary Go binary
Read-only guarantee enforced + tested ✅ ✅ ✅
.gitignore support built-in ❌ partial partial
JSON / CSV / Markdown out all four JSON JSON JSON
Install pip install hmh package manager cargo install binary download

Development

git clone https://github.com/mrhamidrezam/hmh
cd hmh
python -m venv .venv && .venv\Scripts\activate      # or: source .venv/bin/activate
pip install -e ".[dev]"
pytest

The test suite is fast and touches no network: language detection, the comment state machine, the .gitignore matcher, the analyzer, every formatter, and the CLI end-to-end.

A source-integrity test hashes every file before and after analysis and asserts nothing changed.

License

MIT — see LICENSE.

Author

Hamidreza Moghaddam Kohi — Full Stack Developer · AI · Automation Website: https://hamidrezamoghaddam.ir/ · Email: info@hamidrezamoghaddam.ir

Metadata

Release files for hmh 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for hmh 0.1.0
File Size Uploaded Provenance
hmh-0.1.0.tar.gz 25.1 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for hmh 0.1.0
File Interpreter ABI Platform
hmh-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 47.6 kB

Release files / hmh-0.1.0.tar.gz

Download URL hmh-0.1.0.tar.gz
Size 25.1 kB
Tags Source
SHA-256 checksum
How to use checksums
775b3f9d55356f1587c84127f777adfac92a6e7da0c82fda58fb0c79e6010357
BLAKE2b-256 checksum
How to use checksums
f86733296b6e3bcc0ad379d2e098009e8b73787d3d5255773b44818f0fd9f255
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.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 Oct 18, 2026.

Transparency log

Release files / hmh-0.1.0-py3-none-any.whl

Download URL hmh-0.1.0-py3-none-any.whl
Size 22.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
e8700f0fa2ab2e4fc453828dd9c88e7c0ae00d2887afd2d4a9a62f6dbed57ecc
BLAKE2b-256 checksum
How to use checksums
439c15f6450f8836c7714d49ea18fb8179a8a9b7e48fa0893e25e1346f64041e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.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 Oct 18, 2026.

Transparency log

Security for hmh 0.1.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.

Select from 2 files

Attestation for hmh-0.1.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 Oct 18, 2026.

Transparency log

A 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.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files Full provenance

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