Updated 2026-09-30 for v0.1.16: in-memory rendering, any Unicode character, safer comments across hundreds of languages, and coverage measured in CI.
Tatuagem (Portuguese for “tattoo”) stamps a big ASCII-art banner onto every source file in a directory, as a comment in each file’s own language. It’s a code signature, and a boastful one.
This was a fun collaboration between me and @DerekDickerson. The idea is stupid simple: you have a directory of source files, and you want to stamp a text banner – “CONFIDENTIAL”, “DRAFT”, “COPYRIGHT 2026 ACME CORP”, whatever – across every single file, recursively. A tattoo for your code.
Why This Exists
I kept running into the same annoyance: corporate projects that require license headers, confidentiality banners, or copyright notices at the top of every source file. You can write a bash one-liner with find and sed, sure. But then you need to handle different file types (some use # comments, some use //, some use /* */), you need to avoid double-stamping files that already have the banner, and you need to not accidentally corrupt binary files. It is the kind of task that feels like it should take five minutes and actually takes an hour of edge-case whacking.
Tatuagem handles all of this. One command.
How It Works
Rendering. Each character of your phrase is drawn with Pillow’s ImageFont/ImageDraw onto a 64×64 canvas in memory, and the result is read back as a NumPy array. Every pixel is either ink or background: ink becomes the --text character (default 1), background becomes the --backsplash character (default 0), or a repeating --pattern if you want wallpaper. Empty columns are trimmed so the glyphs sit tight together. Here is tatuagem hi --text '#' --backsplash ' ', cropped:
### ###
### ###
###
### ######## ###
############# ###
##### #### ###
#### #### ###
### #### ###
### #### ###
It ships with Arial Unicode and Poppins, and --font takes a path to any .ttf/.otf, so accents, symbols and non-Latin scripts all render.
Tattooing. With --recurse-path, Tatuagem walks the tree and works out each file’s language from its name (Makefile, Dockerfile), its extension, and for the ambiguous ones its content: a .pl with :- rules is Prolog, not Perl, and a .m with #import is Objective-C, not MATLAB. Extensionless scripts are identified by their shebang. The banner is then written in that language’s comment syntax, with a few safety rules:
- A block comment (
/* */,<!-- -->,""") is only used when the banner can’t close it early. If your art contains*/, C files get//line comments instead. If no safe form exists, the file is skipped rather than broken. - Anything that has to stay on line 1 stays there: shebangs,
<?php,<?xml ?>declarations, Python/Ruby encoding cookies, and Dockerfile# syntax=directives. - Hidden directories (
.git,.venv) are never touched, and.tatignoreexcludes paths with the same rules as.gitignore. - It’s idempotent: files that already carry a tattoo are skipped, and
--overwriteswaps the old tattoo for the new one.
Usage
pip install tatuagem
usage: tatuagem [-h] [--version] [--file FILE] [--text TEXT]
[--backsplash BACKSPLASH] [--font FONT] [--pattern PATTERN]
[--margin MARGIN] [--recurse-path RECURSE_PATH] [--overwrite]
[--dry-run] [--check] [--verbose]
[phrase]
Print a banner:
tatuagem "CONFIDENTIAL"
tatuagem "L'appel du vide" --text '@' --backsplash '!'
tatuagem "Tatuagem" --pattern '`:,.'
Stamp a codebase, previewing first:
tatuagem "COPYRIGHT 2026 ACME" --recurse-path ./src --dry-run
tatuagem "COPYRIGHT 2026 ACME" --recurse-path ./src
tatuagem "DRAFT v2.0" --font ./myfont.ttf --recurse-path ./docs --overwrite
Use pre-made art (or a multi-line license header) from a file as-is:
tatuagem --file ./license_header.txt --recurse-path ./src
Enforce it in CI. --check exits non-zero if any file is missing its tattoo:
tatuagem --file ./license_header.txt --recurse-path . --check
Does It Actually Work?
The repo keeps a corpus of hello-worlds in 451 language folders, and CI now keeps us honest in three ways:
- Language coverage. Tatuagem tattoos the whole corpus and counts a language only if the comment it wrote is valid in that language. That’s 91.9% of the 395 languages that can hold a comment at all, COBOL, Visual Basic and AppleScript included. Languages with no comment syntax, like Brainfuck, Piet and JSON, are listed but not counted, and I cut 17 joke folders that weren’t real languages. Before this pass the badge said ~67%, and it was measuring nothing: it only checked that each folder had an entry in a lookup table.
- It still compiles. A copy of the tattooed corpus is re-run through gcc, g++, node, python, ruby, perl, bash, php and swiftc: 1,812 files, none broken, with both a rendered banner and a hand-drawn one full of quotes and backslashes.
- Dogfooding. Every source file in Tatuagem carries a tattoo, and CI fails if one doesn’t.
Design Choices
-
Pillow for text rendering: I could have used a FIGlet-style ASCII font, but rasterizing real TrueType fonts means any font and any script works, and the banner actually looks good.
-
Refuse rather than corrupt: a tool that edits every file in your repo has to be boring about safety. When a language’s comment rules are unclear, Tatuagem leaves the file alone.
-
File-based text input: The
--fileflag reads the stamp from a file and uses it verbatim, which is how you get multi-line license headers or hand-made art like the one below.
Derek handled the backsplash decoration system – the border art that frames the text – while I focused on the recursive file walking and the overwrite detection. Good division of labor for a weekend project.
Built in Python. Published on PyPI. Does one thing. Does it well.
▋▄▄▄▄▂▉▋▎ ▏▎▋▉▂▄▄▄▃▌
▇█████▅▅█▇▃▉▍ ▏▌ ▌ ▍▉▃▆▆▄▆█████▄
▁███▆▇▃▇███▅▅▅▁▌▏ ▍▍ ▌▍ ▏▌▂▆▅▅███▆▄▆▆███▋
▄██▂▄████▅▊▃███▇▁▍ ▏▍▎ ▎▍▏ ▍▂▇███▁▉▆████▄▃██▁
▏▂█▇██▉▍▍▌▊▅▁▍▍▁▇█▅▊▏ ▏▍▎ ▎▍▏ ▏▊▅█▆▁▍▌▂▅▊▌▍▍▁██▇▇▉
▉██▇▃▌▌▍▍▌▋▌▏ ▏▋▇█▇▉▏ ▎▋▋▎ ▏▉▇█▆▋▏ ▏▌▋▌▍▍▍▋▄▇██▋
▏▇█▇▊▍▍▍▍▍▍▌▋▋▌▍▆███▇▊▂██▉▉▇██▇▂▌▋▋▊▌▍▍▍▍▍▍▁▇█▅
▍██▃▎ ▏▍▍▌▋▂▆▇█████▇▇████████▆▄▋▍▍▍▏ ▏▍▄██▎
▁█▇▁▌▍▍▎▏ ▏▊▇█████▆▇█████▂▎ ▏▎▍▍▋▂██▊
▏▃█▆▊▋▉▂▄▆▇▇████████████████████▇▇▆▄▂▉▋▉▇█▉
▋▃▆▇▇▇██▄▁▁▁▄▊▉█████████▇▊▋▃▁▂▂▄██▆▇▇▆▃▌
▉██▂▍▍▍▊▎ ▁████████▉ ▎▋▍▍▍▄█▇▊
▌██▇▌▍▍▎▊▏▍▊▎▃▇█████▂▎▋▍▏▋▎▍▍▌███▎
▋███▋▎▌▎▊▍▌▏▍▎▆████▅▌▎▏▌▍▋▎▌▍▂███▍
▆█▇█▄▎▊▏▏▋ ▋ █████▅▏▋ ▋▏▎▊▌▅█▇█▄
▏▄████▅▋▂▏ ▊ ▇████▅ ▊ ▎▁▋▅████▃
▌▅█▇█▇█▆▅▅▊▇████▇▉▆▅▆█▇▇██▅▎
▎▃▅▇█▇▇██████████▇▇█▇▅▃▎
▍▄▆████████████▆▃▎
▏▉▇▂▏▉▇████████▇▉▏▂▇▉▏
▎▊▃███▄ ▏▊▅████▅▊▏ ▃███▃▊▎
▏▍▊▃▆██████▅▊ ▏▉▇▇▊ ▋▅██████▆▃▊▍▏
▏▌▁▄▇██████████▆▎▊▏▎▊▅██▅▊▎▏▊▎▆███████████▄▁▌▏
▏▄████████████████▏▏▊▌ ▍██▍ ▍▊▏▎████████████████▄
▌█████████████████▊ ▃██▃ ▊█████████████████▌
▃█████████████████▄ ▏████▏ ▄█████████████████▂
▏▇██████████████████▎ ▌████▍ ▎██████████████████▆
▍███████████████████▁ ▊████▊ ▁███████████████████▏
▊███████████████████▇▏ ▁████▁ ▏▇███████▆▃▉▄████████▌
▁████████████████████▉ ▂████▂ ▉█████▄▁▊▉▁▂▆████████▊
▃████████████████████▇▏▄████▄▏▇████████████████████▁
▄█████████████████████▁▄████▄▁█████████████████████▃
▅██████████████████████▇████▇██████████████████████▄
▆██████████████████████████████████████████████████▅
▇██████████████████████████████████████████████████▅
▇████████▂████████████████████████████████▂████████▅
▇███████▆▍████████████████████████████████▎▆███████▅
▇███████▁ ▆██████████████████████████████▆ ▁███████▅
▇███████▅ ▅██████████████████████████████▅ ▄███████▇
████████▉ ▅██████████████████████████████▄ ▉████████
▄▄▄▄▄▄▄▅▂ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▂ ▂▄▄▄▄▄▄▄▃