This release is a pre-release and may not be stable for production use.
ramani
Every file knows where it lives.
One comment at the top of each file says where it sits in your project, and it follows the file when it moves.
Install · Usage · Map your project · CI · Configuration · Supported files
ramani (ra-MA-ni) is Swahili for "map". That is the whole idea: ramani draws a map of your project inside the project. Each file carries one comment with its own path, kept correct as files are renamed and moved, and the same tool can print the entire project as a tree, a list, an outline, JSON or a diagram.
# ramani: src/shop/billing/pay.py
def pay(): ...
Why
Files travel without their address. They get pasted into chats, issues, code reviews and AI prompts, and the question "which file is this, again?" comes up every time. With ramani the path is part of the file, so it travels with it.
Features
- 🧭 Move-aware. Rename or move a file, run ramani again, and its comment follows. Nothing is duplicated.
- 🪶 Careful placement. The comment goes after shebangs, encoding lines, XML declarations and Markdown front matter, and never turns a docstring into a plain string.
- 🗂️ Knows 60+ file types. Python, JavaScript, Go, Rust, SQL, HTML, CSS, Markdown, Dockerfile and more, each with the right comment syntax. Add your own in one line.
- 🙈 Respects your ignores. Built-in lists for
__pycache__,node_modulesand friends, every.gitignoreit finds (nested ones too), and flags orpyproject.tomlsettings for the rest. - 🗺️ Maps your project five ways. Tree, flat paths, Markdown outline, JSON or a Mermaid diagram, and one flag copies any of them to your clipboard.
- 🛑 Asks before big runs. Pointed at a huge folder (say, a whole drive) by mistake? ramani asks before writing to more than 1000 files.
--yesskips the question. - 🔍 CI-friendly.
--checkexits with code 1 when any comment is missing or stale.--dry-runwrites nothing. - 🧹 Reversible.
--removestrips every comment ramani added.
Install
uv tool install ramani # install the command globally (recommended)
uvx ramani --dry-run # or run it once without installing anything
pipx install ramani # pipx or plain pip work too: pip install ramani
Prefer to pin it to a single project, for CI or pre-commit? Add it as a dev dependency and run it through uv run:
uv add --dev ramani
uv run ramani --check
Then, inside a project:
ramani --dry-run # see what would change
ramani # do it
Usage
Point it at a folder (default: the current one) or a single file. Paths in the comments are relative to that folder.
$ ramani
added docs/setup.md
added src/shop/cart.py
added src/shop/index.html
added src/shop/pay.py
added README.md
5 added, 1 unsupported
no comment style for: .json x1
add one with -c EXT=START[,END] or [tool.ramani.comments] in pyproject.toml
The comment uses each language's own syntax, and sits where it can't break anything:
# ramani: src/shop/cart.py
"""Cart logic."""
import json
<!-- ramani: src/shop/index.html -->
<h1>Hi</h1>
---
title: Setup
---
<!-- ramani: docs/setup.md -->
# Setup
When a file moves
Move pay.py into a new billing/ folder and run ramani again. It recognises its own comment, sees the path is out of date, and rewrites it. Untouched files stay untouched.
file first line
──────────────────── ───────────────────────────────
src/shop/pay.py # ramani: src/shop/pay.py
│
│ mv src/shop/pay.py src/shop/billing/
▼
src/shop/billing/pay.py # ramani: src/shop/pay.py ← stale
│
│ ramani
▼
src/shop/billing/pay.py # ramani: src/shop/billing/pay.py ← fixed
$ mkdir src/shop/billing && mv src/shop/pay.py src/shop/billing/
$ ramani
updated src/shop/billing/pay.py
4 unchanged, 1 unsupported, 1 updated
Big folders ask first
If a run would write to more than 1000 files, ramani stops and asks before touching anything. That keeps a stray ramani in your home folder or on a whole drive from rewriting everything under it.
$ cd /
$ ramani
ramani: more than 1000 files under /. Continue? [y/N] n
aborted, nothing was changed
-y, --yesskips the question.--max-files Nchanges the limit (default: 1000).- Without a terminal (CI, pipes) and without
--yes, ramani aborts with exit code 1 rather than waiting for an answer. --dry-run,--checkand every map format write nothing, so they never ask.
The count follows the same ignore rules as marking, and stops as soon as the limit is passed, so ramani doesn't scan a whole drive just to count.
Map your project
ramani can print the whole project, using the same ignore rules as marking, so you see exactly the files it works on. Nothing is written. Pick a format with -f (-t is shorthand for -f tree):
| Format | Flag combination | Best for |
|---|---|---|
tree |
-t, -f tree |
Reading in a terminal, pasting into chats |
paths |
-f paths |
Scripts: xargs, grep, wc -l |
markdown |
-f markdown |
Issues, pull requests and docs |
json |
-f json |
Tools, dashboards, anything that parses |
mermaid |
-f mermaid |
A diagram that GitHub and many doc sites render |
Add --copy to any of them to put the result on your clipboard.
Tree
Folders come first.
$ ramani --tree
shop/
├── docs/
│ └── setup.md
├── src/
│ └── shop/
│ ├── billing/
│ │ └── pay.py
│ ├── cart.py
│ └── index.html
├── README.md
└── data.json
Paths
One file per line, relative to the folder you pointed at. Empty folders are left out.
$ ramani -f paths
docs/setup.md
src/shop/billing/pay.py
src/shop/cart.py
src/shop/index.html
README.md
data.json
Handy in pipelines:
ramani -f paths | xargs wc -l
Markdown
A nested bullet list that renders cleanly anywhere Markdown does.
$ ramani -f markdown
- `shop/`
- `docs/`
- `setup.md`
- `src/`
- `shop/`
- `billing/`
- `pay.py`
- `cart.py`
- `index.html`
- `README.md`
- `data.json`
JSON
Nested objects with a name, a type (dir or file) and, for folders, children.
$ ramani -f json
{
"name": "shop",
"type": "dir",
"children": [
{
"name": "docs",
"type": "dir",
"children": [{ "name": "setup.md", "type": "file" }]
},
{ "name": "README.md", "type": "file" }
]
}
(Abridged here; the real output is indented one key per line.)
Mermaid
A flowchart you can paste straight into a README, an issue or a wiki. GitHub draws it as a diagram; elsewhere you will see the source.
$ ramani -f mermaid
flowchart TD
n0["shop/"]
n1["src/"]
n0 --> n1
n2["cart.py"]
n1 --> n2
n3["pay.py"]
n1 --> n3
n4["README.md"]
n0 --> n4
flowchart TD
n0["shop/"]
n1["src/"]
n0 --> n1
n2["cart.py"]
n1 --> n2
n3["pay.py"]
n1 --> n3
n4["README.md"]
n0 --> n4
Combine with other flags
ramani --copy # print the tree and copy it
ramani ./src -f markdown --copy # outline of ./src on your clipboard
ramani -f paths -d tests -e .md # paths, leaving out tests and Markdown
On Linux, the clipboard needs
xclip,xselorwl-clipboardinstalled. If none is available the map is still printed, with a note on stderr and exit code 1.
Use it in CI
--check changes nothing and exits with 1 if any comment is missing or stale, so stale paths never reach your main branch. It never asks for confirmation. A real run (without --check or --dry-run) over more than 1000 files needs --yes when there is no terminal.
GitHub Actions
- uses: astral-sh/setup-uv@v7
- run: uvx ramani --check
pre-commit
repos:
- repo: local
hooks:
- id: ramani
name: ramani
entry: uvx ramani --check
language: system
pass_filenames: false
Options
| Option | What it does |
|---|---|
PATH |
Folder or file to process (default: current folder) |
-e, --ignore-ext EXT |
Also skip this extension, e.g. -e .txt (repeatable; also ignore-ext in pyproject.toml) |
-d, --ignore-dir NAME |
Also skip this folder name or glob, e.g. -d "build*" (repeatable; also ignore-dir in pyproject.toml) |
-c, --comment EXT=START[,END] |
Comment style for an extension or file name (repeatable) |
--no-gitignore |
Ignore .gitignore files |
-n, --dry-run |
Show what would change, write nothing |
--check |
Dry run that exits 1 if anything is missing or stale |
--max-files N |
Ask before writing to more than N files (default: 1000) |
-y, --yes |
Don't ask for confirmation, even for a large number of files |
--remove |
Strip ramani comments instead of adding them |
-t, --tree |
Print the folder tree instead of marking files |
-f, --format FORMAT |
Map the project as tree, paths, markdown, json or mermaid instead of marking files |
--copy |
Copy the map to the clipboard (implies --tree unless -f is given) |
-V, --version |
Print the installed version and exit |
-v, --verbose |
Also list unchanged files |
-t, -f and --copy can't be combined with --remove or --check.
Configuration
Settings can be given as flags or once and for all in your pyproject.toml.
Skip file types and folders
Skip extensions with -e and folders with -d (a name or a glob):
ramani -e .txt -e .csv -d gen -d "build*"
or list them in pyproject.toml (the leading dot on extensions is optional):
[tool.ramani]
ignore-ext = [".txt", ".csv"]
ignore-dir = ["gen", "build*"]
Flags and pyproject.toml add to each other and to the built-in ignores, so nothing is overridden.
Teach ramani a file type
On the command line:
ramani -c .abc=// # line comment
ramani -c ".tpl=<!--,-->" # block comment: start,end
ramani -c Jenkinsfile=// # by exact file name
or in pyproject.toml:
[tool.ramani.comments]
".abc" = "//"
".tpl" = ["<!--", "-->"]
Flags beat pyproject.toml, which beats the built-in styles, so you can override those too.
Supported files
| Comment | Looks like | Files |
|---|---|---|
# |
# ramani: a/b.py |
.py .pyi .sh .rb .pl .r .jl .ex .yml .yaml .toml .cfg .tf .ps1 .nix Dockerfile Makefile .gitignore … |
// |
// ramani: a/b.ts |
.js .mjs .jsx .ts .tsx .java .kt .scala .c .h .cpp .cs .go .rs .swift .dart .scss .proto … |
-- |
-- ramani: a/b.sql |
.sql .lua .hs .elm |
<!-- --> |
<!-- ramani: a/b.html --> |
.html .htm .xml .svg .vue .svelte .md .markdown |
/* */ |
/* ramani: a/b.css */ |
.css |
{# #} |
{# ramani: a/b.j2 #} |
.j2 .jinja .jinja2 |
{/* */} |
{/* ramani: a/b.mdx */} |
.mdx |
% |
% ramani: a/b.tex |
.tex |
Formats with no comment syntax, such as .json and .csv, are left alone and listed at the end of each run so nothing is skipped silently. The full table lives in src/ramani/config.py.
How it works
For every file, ramani makes one decision:
┌───────────────────────────────┐
│ skip what must stay first: │
│ shebang · encoding line · │
│ XML declaration · # syntax= │
│ · Markdown front matter │
└───────────────┬───────────────┘
▼
is the next line a ramani comment?
│ │
no yes
│ │
▼ ▼
insert the comment is the path the same?
→ added │ │
yes no
│ │
▼ ▼
do nothing rewrite that line
→ unchanged → updated
Line endings (LF or CRLF) and UTF-8 byte-order marks are preserved. Symlinks, files that are not UTF-8, and binary files (anything containing a NUL byte) are never touched.
Development
uv sync
uv run poe test
Ramani has two small runtime dependencies: termcolor for coloured output and pyperclip for --copy. Colour appears only in a terminal; pipes, files and the clipboard get plain text, and NO_COLOR=1 turns it off.
License
MIT © Treeolive Tech Ltd. Use it, change it, ship it; just keep the copyright notice.
Metadata
Release files for ramani 0.1.0a9
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| ramani-0.1.0a9.tar.gz | 20.9 kB | Without provenance | Details for ramani-0.1.0a9.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| ramani-0.1.0a9-py3-none-any.whl | Python 3 | none | any | Details for ramani-0.1.0a9-py3-none-any.whl |
Total release size: 44.6 kB
Release files / ramani-0.1.0a9.tar.gz
| Download URL | ramani-0.1.0a9.tar.gz |
|---|---|
| Size | 20.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
02611c87dffd28698a69ffd1fc74ea7355d04c8e5f7c3f1bcce6446c5b224f89
|
|
BLAKE2b-256 checksum How to use checksums |
45ad8b313ea8f143d49270e8d56fb55be32dbbe8b85a500dd573952279ca94a2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.7 {"installer":{"name":"uv","version":"0.13.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / ramani-0.1.0a9-py3-none-any.whl
| Download URL | ramani-0.1.0a9-py3-none-any.whl |
|---|---|
| Size | 23.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b39893369aa3e67ae6a441713d768dd2999ff60a92208620e2be86d37dbcfbcc
|
|
BLAKE2b-256 checksum How to use checksums |
38f4fa4a8012b5d44206cc3dd540405a96c76259c76ff104162aaab59d56e2a5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.7 {"installer":{"name":"uv","version":"0.13.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Security for ramani 0.1.0a9
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.
No provenance found
None of the files in this release were uploaded with provenance metadata.
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.