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.
- hmh-0.1.0/.gitattributes +6 -0
- hmh-0.1.0/.github/workflows/ci.yml +52 -0
- hmh-0.1.0/.github/workflows/publish.yml +53 -0
- hmh-0.1.0/.gitignore +22 -0
- hmh-0.1.0/LICENSE +21 -0
- hmh-0.1.0/PKG-INFO +280 -0
- hmh-0.1.0/README.md +226 -0
- hmh-0.1.0/assets/banner.svg +50 -0
- hmh-0.1.0/examples/quickstart.py +46 -0
- hmh-0.1.0/pyproject.toml +67 -0
- hmh-0.1.0/src/hmh/__init__.py +38 -0
- hmh-0.1.0/src/hmh/__main__.py +8 -0
- hmh-0.1.0/src/hmh/analyzer.py +257 -0
- hmh-0.1.0/src/hmh/cli.py +110 -0
- hmh-0.1.0/src/hmh/formatters.py +189 -0
- hmh-0.1.0/src/hmh/gitignore.py +136 -0
- hmh-0.1.0/src/hmh/languages.py +363 -0
- hmh-0.1.0/src/hmh/models.py +160 -0
- hmh-0.1.0/src/hmh/py.typed +0 -0
- hmh-0.1.0/tests/test_analyzer.py +148 -0
- hmh-0.1.0/tests/test_cli.py +99 -0
- hmh-0.1.0/tests/test_formatters.py +86 -0
- hmh-0.1.0/tests/test_gitignore.py +86 -0
- hmh-0.1.0/tests/test_languages.py +114 -0
- hmh-0.1.0/tests/test_module.py +31 -0
hmh-0.1.0/.gitattributes
ADDED
|
@@ -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>
|