hmh 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,6 @@
1
+ * text=auto eol=lf
2
+ *.py text eol=lf
3
+ *.md text eol=lf
4
+ *.toml text eol=lf
5
+ *.yml text eol=lf
6
+ *.bat text eol=crlf
@@ -0,0 +1,52 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ${{ matrix.os }}
12
+ strategy:
13
+ fail-fast: false
14
+ matrix:
15
+ os: [ubuntu-latest, windows-latest, macos-latest]
16
+ python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
17
+ exclude:
18
+ # 3.9 images are retired on macOS runners
19
+ - os: macos-latest
20
+ python-version: "3.9"
21
+
22
+ steps:
23
+ - uses: actions/checkout@v4
24
+
25
+ - uses: actions/setup-python@v5
26
+ with:
27
+ python-version: ${{ matrix.python-version }}
28
+
29
+ - name: Install
30
+ run: |
31
+ python -m pip install --upgrade pip
32
+ python -m pip install -e ".[dev]"
33
+
34
+ - name: Run tests
35
+ run: python -m pytest -q
36
+
37
+ build:
38
+ runs-on: ubuntu-latest
39
+ steps:
40
+ - uses: actions/checkout@v4
41
+
42
+ - uses: actions/setup-python@v5
43
+ with:
44
+ python-version: "3.12"
45
+
46
+ - name: Build
47
+ run: |
48
+ python -m pip install --upgrade pip build twine
49
+ python -m build
50
+
51
+ - name: Check metadata
52
+ run: twine check --strict dist/*
@@ -0,0 +1,53 @@
1
+ name: Publish
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+ push:
7
+ tags: ["v*"]
8
+ workflow_dispatch:
9
+
10
+ permissions:
11
+ contents: read
12
+
13
+ jobs:
14
+ build:
15
+ runs-on: ubuntu-latest
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - uses: actions/setup-python@v5
20
+ with:
21
+ python-version: "3.12"
22
+
23
+ - name: Build distributions
24
+ run: |
25
+ python -m pip install --upgrade pip build
26
+ python -m build
27
+
28
+ - uses: actions/upload-artifact@v4
29
+ with:
30
+ name: dist
31
+ path: dist/
32
+
33
+ publish:
34
+ needs: build
35
+ runs-on: ubuntu-latest
36
+
37
+ # NOTE: no `environment:` block on purpose. Configure the Trusted Publisher
38
+ # on PyPI with Environment = "(Any)" so the OIDC token is accepted whether
39
+ # or not a GitHub environment is attached to this job.
40
+
41
+ permissions:
42
+ id-token: write
43
+
44
+ steps:
45
+ - uses: actions/download-artifact@v4
46
+ with:
47
+ name: dist
48
+ path: dist/
49
+
50
+ - name: Publish to PyPI (Trusted Publishing)
51
+ uses: pypa/gh-action-pypi-publish@release/v1
52
+ with:
53
+ skip-existing: true
hmh-0.1.0/.gitignore ADDED
@@ -0,0 +1,22 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ .eggs/
6
+ build/
7
+ dist/
8
+ .venv/
9
+ venv/
10
+ env/
11
+
12
+ # Tooling
13
+ .pytest_cache/
14
+ .ruff_cache/
15
+ .coverage
16
+ htmlcov/
17
+ .mypy_cache/
18
+ .tox/
19
+
20
+ # Reports generated while experimenting
21
+ out/
22
+ REPORT.md
hmh-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Hamidreza
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
hmh-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,280 @@
1
+ Metadata-Version: 2.5
2
+ Name: hmh
3
+ Version: 0.1.0
4
+ Summary: Read-only project analyzer: lines of code, languages, sizes and structure — without ever touching your files.
5
+ Project-URL: Homepage, https://github.com/mrhamidrezam/hmh
6
+ Project-URL: Repository, https://github.com/mrhamidrezam/hmh
7
+ Project-URL: Issues, https://github.com/mrhamidrezam/hmh/issues
8
+ Author-email: Hamidreza <h4midrezam@gmail.com>
9
+ License: MIT License
10
+
11
+ Copyright (c) 2026 Hamidreza
12
+
13
+ Permission is hereby granted, free of charge, to any person obtaining a copy
14
+ of this software and associated documentation files (the "Software"), to deal
15
+ in the Software without restriction, including without limitation the rights
16
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17
+ copies of the Software, and to permit persons to whom the Software is
18
+ furnished to do so, subject to the following conditions:
19
+
20
+ The above copyright notice and this permission notice shall be included in all
21
+ copies or substantial portions of the Software.
22
+
23
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29
+ SOFTWARE.
30
+ License-File: LICENSE
31
+ Keywords: analyzer,cloc,codebase,language-detection,loc,read-only,report,repository,sloc,statistics
32
+ Classifier: Development Status :: 4 - Beta
33
+ Classifier: Environment :: Console
34
+ Classifier: Intended Audience :: Developers
35
+ Classifier: Intended Audience :: Information Technology
36
+ Classifier: License :: OSI Approved :: MIT License
37
+ Classifier: Operating System :: OS Independent
38
+ Classifier: Programming Language :: Python :: 3
39
+ Classifier: Programming Language :: Python :: 3.9
40
+ Classifier: Programming Language :: Python :: 3.10
41
+ Classifier: Programming Language :: Python :: 3.11
42
+ Classifier: Programming Language :: Python :: 3.12
43
+ Classifier: Programming Language :: Python :: 3.13
44
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
45
+ Classifier: Topic :: Software Development :: Quality Assurance
46
+ Classifier: Topic :: Utilities
47
+ Classifier: Typing :: Typed
48
+ Requires-Python: >=3.9
49
+ Provides-Extra: dev
50
+ Requires-Dist: build>=1.2; extra == 'dev'
51
+ Requires-Dist: pytest>=7; extra == 'dev'
52
+ Requires-Dist: twine>=5; extra == 'dev'
53
+ Description-Content-Type: text/markdown
54
+
55
+ <p align="center">
56
+ <img src="https://raw.githubusercontent.com/mrhamidrezam/hmh/main/assets/banner.svg" alt="hmh — read-only project analyzer" width="100%" />
57
+ </p>
58
+
59
+ <p align="center">
60
+ <strong>Split any project into its spectrum.</strong><br/>
61
+ Lines of code, languages, sizes and structure — <em>without ever touching your files</em>.
62
+ </p>
63
+
64
+ <p align="center">
65
+ <a href="https://pypi.org/project/hmh/"><img alt="PyPI" src="https://img.shields.io/pypi/v/hmh?color=4c1&label=PyPI"></a>
66
+ <a href="https://pypi.org/project/hmh/"><img alt="Python" src="https://img.shields.io/pypi/pyversions/hmh"></a>
67
+ <a href="LICENSE"><img alt="License" src="https://img.shields.io/badge/license-MIT-blue"></a>
68
+ <a href="https://github.com/mrhamidrezam/hmh/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/mrhamidrezam/hmh/actions/workflows/ci.yml/badge.svg"></a>
69
+ </p>
70
+
71
+ ---
72
+
73
+ `hmh` is a **read-only project analyzer** for Python. Point it at any folder and it tells you
74
+ exactly what is inside: how many lines, in how many languages, how big everything is, and which
75
+ files carry the most weight.
76
+
77
+ It reads. It never writes.
78
+
79
+ ```bash
80
+ pip install hmh
81
+ hmh .
82
+ ```
83
+
84
+ ```text
85
+ hmh · read-only project analysis
86
+ path /home/you/projects/myapp
87
+ scanned 412 files (388 source · 24 binary)
88
+ size 5.4 MB
89
+ lines 71,338 = code 54,120 · comments 9,004 · blank 8,214
90
+ comments 0.166 per code line
91
+ duration 312 ms
92
+
93
+ LANGUAGE FILES CODE COMMENTS BLANK SHARE
94
+ Python 142 28,410 5,201 4,118 52.5%
95
+ TypeScript 96 14,902 2,140 2,310 27.5%
96
+ JavaScript 71 6,744 1,003 1,020 12.5%
97
+ CSS 34 2,860 412 488 5.3%
98
+ YAML 22 680 188 144 1.3%
99
+ Markdown 23 42 60 34 0.1%
100
+ ```
101
+
102
+ ## Why `hmh`?
103
+
104
+ Most code counters are useful but invasive — they beg you to add config, install a daemon, or
105
+ grant write access. `hmh` has exactly one hard rule:
106
+
107
+ > **`hmh` NEVER modifies the source project.**
108
+
109
+ Every file is opened read-only. Nothing is deleted, moved, renamed, rewritten, or "cleaned up".
110
+ There is no cache directory, no lock file, no temp copy, no sidecar metadata. Run it twice and the
111
+ directory's hashes, sizes and mtimes are byte-for-byte identical. A dedicated test enforces this.
112
+
113
+ That makes it safe to run on:
114
+
115
+ - production boxes and CI runners where you only have read access,
116
+ - mounted or read-only volumes,
117
+ - repositories mid-rebase, mid-`git stash`, or with uncommitted work,
118
+ - anything you would rather not let a tool "tidy up".
119
+
120
+ ## Features
121
+
122
+ - **65+ languages** detected by extension, well-known filenames (`Dockerfile`, `Makefile`,
123
+ `CMakeLists.txt`, `Rakefile`…) and `#!` shebangs.
124
+ - **Real comment counting** — per-language line comments, block comments and multi-line strings
125
+ are tracked across lines, so `#` inside a Python string is not mistaken for a comment.
126
+ - **Four numbers that matter**: code, comments, blank and total lines — per file and per language.
127
+ - **Language share** by code lines and by bytes, so you can see where the weight really is.
128
+ - **Structure**: file counts, binary detection, largest and densest files.
129
+ - **`.gitignore` aware** — honours your existing rules (including `!` negation), and can be
130
+ switched off entirely with `--no-gitignore`.
131
+ - **Zero runtime dependencies** — Python standard library only.
132
+ - **Blazing fast** — single-pass walk with `os.scandir`, no subprocesses, no git calls.
133
+ - **Typed** — ships `py.typed`, full annotations, no `# type: ignore` smoke.
134
+
135
+ ## Install
136
+
137
+ ```bash
138
+ pip install hmh
139
+ ```
140
+
141
+ Requires Python 3.9+.
142
+
143
+ ## Command line
144
+
145
+ ```bash
146
+ hmh . # summary table for the current directory
147
+ hmh ~/projects/myapp --top 10 # only the 10 biggest languages
148
+ hmh . --files 5 # also list the 5 densest source files
149
+ hmh . --format markdown -o REPORT.md # embeddable table for docs
150
+ hmh . --format json | jq '.totals' # machine-readable
151
+ hmh . --format csv > stats.csv # spreadsheet-ready
152
+ hmh --languages # every language hmh knows
153
+ ```
154
+
155
+ | Option | Meaning |
156
+ | --- | --- |
157
+ | `--format {table,json,csv,markdown}` | Output format (default `table`) |
158
+ | `--top N` | Show only the N largest languages |
159
+ | `--sort {code,lines,files,bytes,name}` | Ordering of the language table |
160
+ | `--files N` | Also list the N densest source files |
161
+ | `--exclude PATTERN` | Extra gitignore-style pattern to skip (repeatable) |
162
+ | `--include-hidden` | Also walk dot-directories (`.github`, `.venv`, …) |
163
+ | `--no-gitignore` | Ignore `.gitignore` and scan everything |
164
+ | `--follow-symlinks` | Traverse symlinks instead of skipping them |
165
+ | `--max-file-mb N` | Skip line-counting above this size (default 20) |
166
+ | `-o, --out FILE` | Write the report to a file |
167
+ | `-q, --quiet` | Print nothing; only the exit code |
168
+
169
+ Exit codes: `0` success, `2` bad path or unreadable project.
170
+
171
+ ## Python API
172
+
173
+ ```python
174
+ import hmh
175
+
176
+ report = hmh.analyze("~/projects/myapp")
177
+
178
+ print(report.code_lines) # 54120
179
+ print(len(report.languages)) # 6
180
+ print(report.size_human) # '5.4 MB'
181
+
182
+ for lang in report.top(5):
183
+ print(f"{lang.name:<15} {lang.code:>7,} {lang.share_of(report.code_lines):5.1f}%")
184
+ ```
185
+
186
+ `Report` is a plain dataclass — inspect it, serialize it, or render it:
187
+
188
+ ```python
189
+ report.to_dict() # nested dict, ready for JSON
190
+ report.languages_by_code # sorted
191
+ report.languages_by_bytes
192
+ report.densest_files # biggest source files by code lines
193
+ report.largest_files # biggest files by bytes
194
+ report.comments_per_code # density signal
195
+
196
+ hmh.format_report(report, "markdown") # same renderer the CLI uses
197
+ ```
198
+
199
+ Analyse several projects at once:
200
+
201
+ ```python
202
+ for report in hmh.analyze_many(["./api", "./web", "./infra"]):
203
+ print(report.root, report.code_lines)
204
+ ```
205
+
206
+ Need the raw numbers without a walk? `hmh.languages.count_lines(text, "Python")` returns
207
+ `(code, comment, blank)` for any string.
208
+
209
+ ## How it detects languages
210
+
211
+ `hmh` resolves a file's language in this order:
212
+
213
+ 1. **Known filename** — `Dockerfile`, `Makefile`, `CMakeLists.txt`, `Rakefile`, `Gemfile`, `BUILD`.
214
+ 2. **Extension** — `.py`, `.tsx`, `.rs`, `.go`, `.vue`, `.tf`, `.proto`, `.nix`, …
215
+ 3. **Shebang** — `#!/usr/bin/env python3` → Python, even without an extension.
216
+
217
+ Anything else is `unknown`. Files containing a NUL byte in the first 8 KB are treated as binary:
218
+ they are counted by size, never parsed.
219
+
220
+ ## How it counts lines
221
+
222
+ Counting is a small state machine that walks each line character by character, tracking:
223
+
224
+ - whether we are inside a **block comment** (`/* … */`, `<!-- … -->`, `{- … -}`),
225
+ - whether we are inside a **multi-line string** (`""" … """` in Python),
226
+ - whether a **line comment** (`#`, `//`, `--`, `%`, `;`) starts after real code.
227
+
228
+ That last point is why `int x; // trailing` counts as **code**, not as a comment — and why a
229
+ docstring counts as code, not as documentation. Languages with no comment syntax (JSON, CSV,
230
+ Markdown) count every non-blank line as code, matching how `cloc` and `tokei` behave.
231
+
232
+ ## What is skipped
233
+
234
+ By default `hmh` never walks into dependency and build directories, because they are reproducible
235
+ noise rather than your project:
236
+
237
+ ```text
238
+ .git .hg .svn node_modules __pycache__ .venv venv .tox .nox
239
+ .mypy_cache .pytest_cache .ruff_cache .next .nuxt target build
240
+ dist vendor coverage .gradle .idea .vscode …
241
+ ```
242
+
243
+ Add `--include-hidden` to walk dot-directories, or `--no-gitignore` to scan absolutely
244
+ everything. Explicit `--exclude` patterns always win.
245
+
246
+ > `.env*` files are never reported on, so secrets do not end up in a shared report.
247
+
248
+ ## Comparisons
249
+
250
+ | | `hmh` | `cloc` | `tokei` | `scc` |
251
+ | --- | --- | --- | --- | --- |
252
+ | Runtime deps | **none** (Python) | Perl binary | Rust binary | Go binary |
253
+ | Read-only guarantee | **enforced + tested** | ✅ | ✅ | ✅ |
254
+ | `.gitignore` support | built-in | ❌ | partial | partial |
255
+ | JSON / CSV / Markdown out | **all four** | JSON | JSON | JSON |
256
+ | Install | `pip install hmh` | package manager | `cargo install` | binary download |
257
+
258
+ ## Development
259
+
260
+ ```bash
261
+ git clone https://github.com/mrhamidrezam/hmh
262
+ cd hmh
263
+ python -m venv .venv && .venv\Scripts\activate # or: source .venv/bin/activate
264
+ pip install -e ".[dev]"
265
+ pytest
266
+ ```
267
+
268
+ The test suite is fast and **touches no network**: language detection, the comment state machine,
269
+ the `.gitignore` matcher, the analyzer, every formatter, and the CLI end-to-end.
270
+
271
+ A source-integrity test hashes every file before and after analysis and asserts nothing changed.
272
+
273
+ ## License
274
+
275
+ MIT — see [LICENSE](LICENSE).
276
+
277
+ ## Author
278
+
279
+ **Hamidreza Moghaddam Kohi** — Full Stack Developer · AI · Automation
280
+ Website: <https://hamidrezamoghaddam.ir/> · Email: <info@hamidrezamoghaddam.ir>
hmh-0.1.0/README.md ADDED
@@ -0,0 +1,226 @@
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/mrhamidrezam/hmh/main/assets/banner.svg" alt="hmh — read-only project analyzer" width="100%" />
3
+ </p>
4
+
5
+ <p align="center">
6
+ <strong>Split any project into its spectrum.</strong><br/>
7
+ Lines of code, languages, sizes and structure — <em>without ever touching your files</em>.
8
+ </p>
9
+
10
+ <p align="center">
11
+ <a href="https://pypi.org/project/hmh/"><img alt="PyPI" src="https://img.shields.io/pypi/v/hmh?color=4c1&label=PyPI"></a>
12
+ <a href="https://pypi.org/project/hmh/"><img alt="Python" src="https://img.shields.io/pypi/pyversions/hmh"></a>
13
+ <a href="LICENSE"><img alt="License" src="https://img.shields.io/badge/license-MIT-blue"></a>
14
+ <a href="https://github.com/mrhamidrezam/hmh/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/mrhamidrezam/hmh/actions/workflows/ci.yml/badge.svg"></a>
15
+ </p>
16
+
17
+ ---
18
+
19
+ `hmh` is a **read-only project analyzer** for Python. Point it at any folder and it tells you
20
+ exactly what is inside: how many lines, in how many languages, how big everything is, and which
21
+ files carry the most weight.
22
+
23
+ It reads. It never writes.
24
+
25
+ ```bash
26
+ pip install hmh
27
+ hmh .
28
+ ```
29
+
30
+ ```text
31
+ hmh · read-only project analysis
32
+ path /home/you/projects/myapp
33
+ scanned 412 files (388 source · 24 binary)
34
+ size 5.4 MB
35
+ lines 71,338 = code 54,120 · comments 9,004 · blank 8,214
36
+ comments 0.166 per code line
37
+ duration 312 ms
38
+
39
+ LANGUAGE FILES CODE COMMENTS BLANK SHARE
40
+ Python 142 28,410 5,201 4,118 52.5%
41
+ TypeScript 96 14,902 2,140 2,310 27.5%
42
+ JavaScript 71 6,744 1,003 1,020 12.5%
43
+ CSS 34 2,860 412 488 5.3%
44
+ YAML 22 680 188 144 1.3%
45
+ Markdown 23 42 60 34 0.1%
46
+ ```
47
+
48
+ ## Why `hmh`?
49
+
50
+ Most code counters are useful but invasive — they beg you to add config, install a daemon, or
51
+ grant write access. `hmh` has exactly one hard rule:
52
+
53
+ > **`hmh` NEVER modifies the source project.**
54
+
55
+ Every file is opened read-only. Nothing is deleted, moved, renamed, rewritten, or "cleaned up".
56
+ There is no cache directory, no lock file, no temp copy, no sidecar metadata. Run it twice and the
57
+ directory's hashes, sizes and mtimes are byte-for-byte identical. A dedicated test enforces this.
58
+
59
+ That makes it safe to run on:
60
+
61
+ - production boxes and CI runners where you only have read access,
62
+ - mounted or read-only volumes,
63
+ - repositories mid-rebase, mid-`git stash`, or with uncommitted work,
64
+ - anything you would rather not let a tool "tidy up".
65
+
66
+ ## Features
67
+
68
+ - **65+ languages** detected by extension, well-known filenames (`Dockerfile`, `Makefile`,
69
+ `CMakeLists.txt`, `Rakefile`…) and `#!` shebangs.
70
+ - **Real comment counting** — per-language line comments, block comments and multi-line strings
71
+ are tracked across lines, so `#` inside a Python string is not mistaken for a comment.
72
+ - **Four numbers that matter**: code, comments, blank and total lines — per file and per language.
73
+ - **Language share** by code lines and by bytes, so you can see where the weight really is.
74
+ - **Structure**: file counts, binary detection, largest and densest files.
75
+ - **`.gitignore` aware** — honours your existing rules (including `!` negation), and can be
76
+ switched off entirely with `--no-gitignore`.
77
+ - **Zero runtime dependencies** — Python standard library only.
78
+ - **Blazing fast** — single-pass walk with `os.scandir`, no subprocesses, no git calls.
79
+ - **Typed** — ships `py.typed`, full annotations, no `# type: ignore` smoke.
80
+
81
+ ## Install
82
+
83
+ ```bash
84
+ pip install hmh
85
+ ```
86
+
87
+ Requires Python 3.9+.
88
+
89
+ ## Command line
90
+
91
+ ```bash
92
+ hmh . # summary table for the current directory
93
+ hmh ~/projects/myapp --top 10 # only the 10 biggest languages
94
+ hmh . --files 5 # also list the 5 densest source files
95
+ hmh . --format markdown -o REPORT.md # embeddable table for docs
96
+ hmh . --format json | jq '.totals' # machine-readable
97
+ hmh . --format csv > stats.csv # spreadsheet-ready
98
+ hmh --languages # every language hmh knows
99
+ ```
100
+
101
+ | Option | Meaning |
102
+ | --- | --- |
103
+ | `--format {table,json,csv,markdown}` | Output format (default `table`) |
104
+ | `--top N` | Show only the N largest languages |
105
+ | `--sort {code,lines,files,bytes,name}` | Ordering of the language table |
106
+ | `--files N` | Also list the N densest source files |
107
+ | `--exclude PATTERN` | Extra gitignore-style pattern to skip (repeatable) |
108
+ | `--include-hidden` | Also walk dot-directories (`.github`, `.venv`, …) |
109
+ | `--no-gitignore` | Ignore `.gitignore` and scan everything |
110
+ | `--follow-symlinks` | Traverse symlinks instead of skipping them |
111
+ | `--max-file-mb N` | Skip line-counting above this size (default 20) |
112
+ | `-o, --out FILE` | Write the report to a file |
113
+ | `-q, --quiet` | Print nothing; only the exit code |
114
+
115
+ Exit codes: `0` success, `2` bad path or unreadable project.
116
+
117
+ ## Python API
118
+
119
+ ```python
120
+ import hmh
121
+
122
+ report = hmh.analyze("~/projects/myapp")
123
+
124
+ print(report.code_lines) # 54120
125
+ print(len(report.languages)) # 6
126
+ print(report.size_human) # '5.4 MB'
127
+
128
+ for lang in report.top(5):
129
+ print(f"{lang.name:<15} {lang.code:>7,} {lang.share_of(report.code_lines):5.1f}%")
130
+ ```
131
+
132
+ `Report` is a plain dataclass — inspect it, serialize it, or render it:
133
+
134
+ ```python
135
+ report.to_dict() # nested dict, ready for JSON
136
+ report.languages_by_code # sorted
137
+ report.languages_by_bytes
138
+ report.densest_files # biggest source files by code lines
139
+ report.largest_files # biggest files by bytes
140
+ report.comments_per_code # density signal
141
+
142
+ hmh.format_report(report, "markdown") # same renderer the CLI uses
143
+ ```
144
+
145
+ Analyse several projects at once:
146
+
147
+ ```python
148
+ for report in hmh.analyze_many(["./api", "./web", "./infra"]):
149
+ print(report.root, report.code_lines)
150
+ ```
151
+
152
+ Need the raw numbers without a walk? `hmh.languages.count_lines(text, "Python")` returns
153
+ `(code, comment, blank)` for any string.
154
+
155
+ ## How it detects languages
156
+
157
+ `hmh` resolves a file's language in this order:
158
+
159
+ 1. **Known filename** — `Dockerfile`, `Makefile`, `CMakeLists.txt`, `Rakefile`, `Gemfile`, `BUILD`.
160
+ 2. **Extension** — `.py`, `.tsx`, `.rs`, `.go`, `.vue`, `.tf`, `.proto`, `.nix`, …
161
+ 3. **Shebang** — `#!/usr/bin/env python3` → Python, even without an extension.
162
+
163
+ Anything else is `unknown`. Files containing a NUL byte in the first 8 KB are treated as binary:
164
+ they are counted by size, never parsed.
165
+
166
+ ## How it counts lines
167
+
168
+ Counting is a small state machine that walks each line character by character, tracking:
169
+
170
+ - whether we are inside a **block comment** (`/* … */`, `<!-- … -->`, `{- … -}`),
171
+ - whether we are inside a **multi-line string** (`""" … """` in Python),
172
+ - whether a **line comment** (`#`, `//`, `--`, `%`, `;`) starts after real code.
173
+
174
+ That last point is why `int x; // trailing` counts as **code**, not as a comment — and why a
175
+ docstring counts as code, not as documentation. Languages with no comment syntax (JSON, CSV,
176
+ Markdown) count every non-blank line as code, matching how `cloc` and `tokei` behave.
177
+
178
+ ## What is skipped
179
+
180
+ By default `hmh` never walks into dependency and build directories, because they are reproducible
181
+ noise rather than your project:
182
+
183
+ ```text
184
+ .git .hg .svn node_modules __pycache__ .venv venv .tox .nox
185
+ .mypy_cache .pytest_cache .ruff_cache .next .nuxt target build
186
+ dist vendor coverage .gradle .idea .vscode …
187
+ ```
188
+
189
+ Add `--include-hidden` to walk dot-directories, or `--no-gitignore` to scan absolutely
190
+ everything. Explicit `--exclude` patterns always win.
191
+
192
+ > `.env*` files are never reported on, so secrets do not end up in a shared report.
193
+
194
+ ## Comparisons
195
+
196
+ | | `hmh` | `cloc` | `tokei` | `scc` |
197
+ | --- | --- | --- | --- | --- |
198
+ | Runtime deps | **none** (Python) | Perl binary | Rust binary | Go binary |
199
+ | Read-only guarantee | **enforced + tested** | ✅ | ✅ | ✅ |
200
+ | `.gitignore` support | built-in | ❌ | partial | partial |
201
+ | JSON / CSV / Markdown out | **all four** | JSON | JSON | JSON |
202
+ | Install | `pip install hmh` | package manager | `cargo install` | binary download |
203
+
204
+ ## Development
205
+
206
+ ```bash
207
+ git clone https://github.com/mrhamidrezam/hmh
208
+ cd hmh
209
+ python -m venv .venv && .venv\Scripts\activate # or: source .venv/bin/activate
210
+ pip install -e ".[dev]"
211
+ pytest
212
+ ```
213
+
214
+ The test suite is fast and **touches no network**: language detection, the comment state machine,
215
+ the `.gitignore` matcher, the analyzer, every formatter, and the CLI end-to-end.
216
+
217
+ A source-integrity test hashes every file before and after analysis and asserts nothing changed.
218
+
219
+ ## License
220
+
221
+ MIT — see [LICENSE](LICENSE).
222
+
223
+ ## Author
224
+
225
+ **Hamidreza Moghaddam Kohi** — Full Stack Developer · AI · Automation
226
+ Website: <https://hamidrezamoghaddam.ir/> · Email: <info@hamidrezamoghaddam.ir>