count-lines
Count lines of code across multiple languages with a comment-aware breakdown.
Supports Python, JavaScript (incl. JSX), TypeScript (incl. TSX), and Rust. Scans local folders or shallow-clones public git repositories on the fly.
Supported languages
| Language | Extensions |
|---|---|
| Python | .py |
| JavaScript | .js, .mjs, .cjs |
| JSX | .jsx |
| TypeScript | .ts |
| TSX | .tsx |
| Rust | .rs |
JSX files are parsed with the JavaScript grammar (the modern JS grammar handles JSX natively).
Installation
uv tool install python-count-lines
Run pcl -v to check the installed version.
Usage
pcl # current directory
pcl path/to/repo # a specific folder
pcl src/app.py # a single file
pcl github.com/psf/requests # a public repo (shorthand)
pcl https://github.com/psf/requests # full URL also works
Output
count-lines
Path: /home/me/projects/mixed-app
Folders 12
Source files 47
typescript 28
rust 12
python 7
Total lines 9,820
code 6,540
doc lines 1,180
comments 520
blank 1,580
Top 5 largest files
backend/src/lib.rs 1,420
web/src/components/App.tsx 984
backend/src/handlers.rs 910
web/src/utils/parser.ts 802
scripts/migrate.py 610
When --exclude is used, the headline rows (folders, files, total) carry a dim
delta showing how much was filtered out:
Folders 1 | -50% of 2
Source files 7 | -36% of 11
Total lines 427 | -26% of 580
Remote repositories
If the target looks like a git URL or a shorthand, pcl performs a shallow clone
(--depth 1 --filter=blob:none) into a temp directory, runs the scan, and cleans up.
git must be on PATH.
| Form | Example |
|---|---|
| HTTPS | https://github.com/psf/requests.git |
| SSH | git@github.com:psf/requests.git |
ssh:// |
ssh://git@github.com/psf/requests.git |
git:// |
git://github.com/psf/requests |
| Shorthand | github.com/psf/requests |
Shorthand is also recognised for gitlab.com, bitbucket.org, and codeberg.org.
Counting rules
Each supported source file is parsed once via tree-sitter; every line is classified into exactly one bucket:
| Bucket | What it is |
|---|---|
| blank | whitespace-only line |
| comment | line inside a non-doc comment (e.g. # in Python, // and /* */ in JS/TS/Rust) |
| doc | Python module/class/function docstrings, JSDoc /** */, Rust ///, //!, /** */, /*! */ |
| code | everything else |
Resolution priority on overlap: comment > doc > blank > code. So a blank line
inside a multi-line docstring counts as doc (it's part of the doc content),
while a trailing # ... on a code line stays code.
Tree-sitter parses each file; comment-only and string-literal nodes are mapped
to the right bucket per language. Strings containing // or # are never
mistaken for comments. On parse errors tree-sitter's error recovery still
surfaces well-formed regions.
Excludes
--exclude accepts one or more fnmatch
patterns. They combine as a logical OR — a path is excluded if it matches any pattern.
Each pattern is tested two ways:
- against the full path relative to the scan root (e.g.
src/migrations/*) - against every individual path component, including the filename
(e.g.
testsmatches anytests/folder;*_test.pymatches any matching file)
Hidden directories (starting with .) and __pycache__ are skipped by default.
Examples
# Folder names — match at any depth
pcl . --exclude tests
pcl . --exclude tests docs
# Anchor a folder pattern to a specific path
pcl . --exclude "src/migrations/*"
# Filename patterns
pcl . --exclude "test_*.py" # test_foo.py
pcl . --exclude "*_test.py" # fetch_orders_test.py
pcl . --exclude "*test*.py" # anything with 'test' in the name
# Combine freely — folders, paths, and filenames at once
pcl . --exclude tests docs "src/migrations/*" "*_test.py" "test_*.py"
Tips
- Quote glob patterns (
"*_test.py") so the shell doesn't expand them against your current directory beforepclsees them. --excludeis greedy (consumes all following words). Put the target before it, or separate with--:pcl /repo --exclude tests "*_test.py" # target first ✓ pcl --exclude tests "*_test.py" -- /repo # -- terminator ✓ pcl --exclude tests "*_test.py" /repo # /repo eaten ✗
Flags
| Flag | Description |
|---|---|
target |
folder, file, or git URL/shorthand. Defaults to . |
--exclude PATTERN [PATTERN ...] |
fnmatch patterns to skip |
--lang NAME [NAME ...] |
limit counting to the named languages (default: all supported) |
--strip-comments |
exclude comment-only lines from the headline LOC total |
-v, --version |
print the installed version and exit |
--strip-comments only changes the headline number; the breakdown is always shown.
It composes with everything else:
pcl github.com/psf/requests --exclude tests docs "*_test.py" --strip-comments
License
MIT
Metadata
Release files for python-count-lines 0.1.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| python_count_lines-0.1.3.tar.gz | 46.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| python_count_lines-0.1.3-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 61.5 kB
Release files / python_count_lines-0.1.3.tar.gz
| Download URL | python_count_lines-0.1.3.tar.gz |
|---|---|
| Size | 46.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
27da263b70afe4910bac06ed16eb26f66fbf186baf305ab8d789ece198b9f70e
|
|
BLAKE2b-256 checksum How to use checksums |
e38e1a75c2c8922ea2ccef4fe3d1273bdaf998a7e815a045b7b98ef40b70fe2b
|
| 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 Aug 7, 2026.
Transparency logRelease files / python_count_lines-0.1.3-py3-none-any.whl
| Download URL | python_count_lines-0.1.3-py3-none-any.whl |
|---|---|
| Size | 14.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
2a5061febf033fd1583d38c94a687f329a3c829d616eb5ea58fc4a89ac430c42
|
|
BLAKE2b-256 checksum How to use checksums |
0146fb7abd166b6ece387555356075dfb5864284fc61af6e5005e50a67bbff0c
|
| 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 Aug 7, 2026.
Transparency log