squeeze-lib 0.5.0

Finders for URLs, emails, IPs, hashes, TODOs, and more in free-form text
Documentation

squeeze CI Latest release License: MIT

Extract 20 kinds of data from text, including URLs, emails, IPs, hashes, and TODOs. Like grep -o, but it already knows what a URL looks like.

squeeze pulling timestamps, URLs, IPs, a UUID, an email, a version, a hash, and a handle out of a log file

$ cat notes.md
See [the docs](https://example.com/docs), or https://en.wikipedia.org/wiki/Squeeze_(disambiguation).
Ping ops@example.com, then check <https://status.example.com>.

$ grep -oE 'https?://[^ ]+' notes.md
https://example.com/docs),
https://en.wikipedia.org/wiki/Squeeze_(disambiguation).
https://status.example.com>.

$ squeeze --url notes.md
https://example.com/docs
https://en.wikipedia.org/wiki/Squeeze_(disambiguation)
https://status.example.com
  • Handles surrounding text. Targeted parsers recognize common formats within Markdown, JSON, HTML, and prose, including links with balanced parentheses and surrounding punctuation or quotes.
  • Plays well with pipes and repositories. It reads stdin, files, globs, or whole directory trees (skipping what .gitignore ignores, like ripgrep), streams results as it finds them, and stops at the first one with -1.
  • Structured when you need it. JSON, YAML, or CSV output with the kind, line, and column of each match, or path:line:column: prefixes your editor can jump to.
  • Faster than ripgrep on our URL benchmark. On a generated 56 MiB mixed corpus, single-threaded squeeze used 18 ms of CPU versus ripgrep's 35 ms. The benchmark includes the exact command, generated data, and match counts; the tools match different URL grammars.
  • Also a Rust library. Every finder is available as a crate.

Install

Homebrew (macOS and Linux):

brew install aymericbeaumet/tap/squeeze

Cargo (all platforms, Rust 1.95+):

cargo install --locked squeeze-cli

mise (all platforms):

mise use -g github:aymericbeaumet/squeeze

Release archives: download the archive for your platform (Linux, macOS, or Windows; amd64 or arm64) from GitHub Releases and verify it against the release's SHA256SUMS before putting squeeze (squeeze.exe on Windows) on your PATH. Linux binaries are statically linked. Release archives also carry build provenance you can check with gh attestation verify <archive> --repo aymericbeaumet/squeeze.

Shell completions: squeeze --completions <shell> prints the script for bash, zsh, fish, elvish, or PowerShell; Homebrew installs them for you.

Usage

Pick one or more finders, or none to run all of them. Results contain only the matched values by default; use --with-kind to label them. Like grep, squeeze reads standard input when you give it no path (or -), and scans the files, directories, and quoted glob patterns you pass after the options. A directory such as . is walked recursively the way ripgrep walks it: hidden entries, .gitignore and .ignore rules, and binary files are skipped. Scanning is parallel by default: directories and multiple files spread over every core, and so do standard input and big files.

For example, a broad scan of a log prints the value without a kind prefix:

$ printf 'peer=192.0.2.1\n' > app.log
$ squeeze app.log
192.0.2.1

Run squeeze --with-kind app.log when you also need the finder name.

# Everything squeeze recognizes in a file, one value per line
squeeze app.log

# Every link on a web page
curl -s https://news.ycombinator.com | squeeze --url --uniq

# Everyone who committed to a repository
git log --format='%an <%ae>' | squeeze --email --sort --uniq

# IPs, request IDs, and timestamps in logs, labeled by kind
kubectl logs deploy/api | squeeze --ip --uuid --datetime --with-kind

# TODOs and FIXMEs across a repository, with locations your editor understands
squeeze --todo --fixme --with-location .

# Rust sources only
squeeze --todo --fixme --with-location 'src/**/*.rs'

# Everything squeeze can find, with metadata as JSON
squeeze --all --with-kind --output json notes.md

With several finders, results come out in the order they appear:

$ echo '2026-01-15T10:30:00Z GET /health from 10.0.4.2 took 3ms' | squeeze --datetime --ip --with-kind
datetime  2026-01-15T10:30:00Z
ip        10.0.4.2

Finders

Finder Flag Examples
CIDR --cidr 192.168.1.0/24, 2001:db8::/32
Codetags --codetag, --todo, --fixme TODO: fix this, FIXME(#42): bug
Colors --color #ff0000, rgb(255, 0, 0), hsl(0, 100%, 50%)
Datetimes --datetime 2024-01-15, 2024-01-15T10:30:00Z
Domains --domain example.com, mail.example.co.uk
Emails --email user@example.com, first.last+tag@company.co.uk
Emojis --emoji ๐Ÿ˜€, ๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ, 1๏ธโƒฃ
Env vars --env $HOME, ${PATH}, ${VAR:-default}
Handles --handle @alice, @user@example.social
Hashes --hash, --md5, --sha1, --sha256, --sha512 5d41402abc4b2a76b9719d911017c592
IPs --ip, --ipv4, --ipv6 192.168.1.1, ::1, 2001:db8::1
JSON --json {"key": "value"}, [1, 2, 3]
JWTs --jwt eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxIn0.c2ln
MACs --mac 00:1A:2B:3C:4D:5E, 001A.2B3C.4D5E
Modelines --modeline vim: set ts=4 sw=4 et:
Paths --path /etc/hosts, ./src/main.rs:42:10
Phones --phone +14155551234, (415) 555-1234
Semver --semver 1.0.0, v2.3.1-rc.1+build.42
URIs --uri, --url, --http, --https https://example.com, mailto:hi@example.com
UUIDs --uuid 550e8400-e29b-41d4-a716-446655440000

Some finders take a filter: --codetag=todo,fixme, --hash=sha256, --uri=https,ssh. --url is shorthand for the schemes people usually mean by a URL (http, https, ftp, mailto, โ€ฆ); --uri accepts any scheme.

Finders favor precision over recall. Use --strict to make --uri follow RFC 3986 to the letter.

Output options

Flag Description
-1, --first stop after the first result
--last only print the last result
--sort sort the results
--uniq deduplicate the results, keeping the first occurrence
--with-kind print the finder name (uri, email, โ€ฆ) with each result, aligned in a terminal and tab-separated otherwise
--with-location print path:line:column: before each result
--output <fmt> text (default), json, yaml, csv, or none
--no-overlap drop matches that overlap an earlier one (--precedence longest keeps the longest instead)
--copy copy the results to the clipboard
--open open each result with the default application
-j, --jobs <N> scanning threads; auto (default) uses every core for directories, stdin, and files of 8 MiB and more, output of a single input kept in order

Walking a directory:

Flag Description
--hidden also search hidden files and directories (.git is always skipped)
--no-ignore do not respect .gitignore, .ignore, and git exclude rules
--follow follow symbolic links

In the structured formats, --with-kind and --with-location both switch each result to an object with its kind, value, line, column, byte offsets, and source file. Run squeeze --help for the full list.

Integrations

vim/nvim

Press Enter in visual mode to open the first URL in the selection:

" ~/.vimrc
vnoremap <silent> <CR> :<C-U>'<,'>w !squeeze -1 --url --open<CR><CR>

Load every TODO and FIXME of a project into the quickfix list:

:cexpr system("squeeze --todo --fixme --with-location 'src/**/*'")

tmux

Press Enter in copy mode to open the first URL in the selection:

# ~/.tmux.conf
bind -T copy-mode-vi enter send -X copy-pipe-and-cancel "squeeze -1 --url --open"

fzf

Pick a URL from the scrollback of the current tmux pane and open it:

tmux capture-pane -pJ -S - | squeeze --url --uniq | fzf --tac | squeeze --url --open

Shell

List the URLs from your shell history, most recent first:

# ~/.bashrc, ~/.zshrc
urls() { fc -rl 1 | squeeze --url --uniq; }

Performance

No regex engine is involved: SIMD searches and byte classification narrow the positions where a match can start. Big files, streams, and directory trees can be scanned across available cores.

Run mise run bench-cli -- --scale 8 --runs 7 --big 2 --tree 20000 to regenerate the corpus and the comparison in the CLI benchmark. Its source data and results are written under /tmp/squeeze-bench/. The regex tools use approximate patterns and can return different matches, so compare the match counts alongside the timings.

Use as a Rust library

The finders are packaged as squeeze-lib, which the CLI builds on and which is imported as squeeze. For the 0.5.0 release, add:

[dependencies]
squeeze = { package = "squeeze-lib", version = "0.5" }
use squeeze::{email::Email, scanner::Scanner, uri::URI};

let scanner = Scanner::new(vec![Box::new(URI::default()), Box::new(Email::default())]);

let line = "Ping ops@example.com about https://example.com/status";
for m in scanner.scan_line(line) {
    let kind = scanner.finders()[m.finder_index].id();
    println!("{kind}\t{}", &line[m.range]);
}

Once the 0.5.0 crate is published, its API documentation will be on docs.rs. In a clone, run cargo doc --open -p squeeze-lib.

Development

Install mise, then run the same checks as CI:

mise trust
mise install
mise run check

See development and releases for the other tasks and the release process. Bug reports with a sample input that squeeze gets wrong are especially welcome.

License

MIT