ruplacer: find and replace text in source files
Introduction #
Today I’d like to talk about a command-line tool I’ve been working on.
It’s called ruplacer and as the name suggest, it’s rually cool and written in Rust.
Basically, it finds and replaces text in source files. Here’s a screenshot of ruplacer in action:
Some nice features:
- Skips files listed in
.gitignore
, as well as binary files - Runs in dry run mode by default (use
--go
to actually write the changes to the filesystem) - Defaults to searching the current working directory, although an other path maybe specified after the pattern and the replacement.
- Uses Rust regular expressions, which means you can capture groups in the pattern and use them in the replacement. For instance:
# Replaces dates looking like DD/MM/YYYY to YYYY-MM-DD
$ ruplacer '(\d{2})/(\d{2})/(\d{4})' '$3-$1-$2'
- Can be run with
--no-regex
if the pattern is just a substring and should not be used as a regular expression - Last, but not least there’s also a
--subvert
mode, which allows you to perform replacements on a variety of case styles:
$ ruplacer --subvert foo_bar spam_eggs
Patching src/foo.txt
-- foo_bar, FooBar, and FOO_BAR!
++ spam_eggs, SpamEggs, and SPAM_EGGS!
How it works #
Here’s how it works:
First, we build a structopt struct for the command-line arguments parsing. Depending on the presence of the --subvert
or --no-rexeg
flags, we build a Query, which can be of several types: Substring
, Regex
or Subvert
.
Then we leverage the ignore crate to walk through every file in the source directory while skipping files listed in .gitignore
. By the way, the ignore crates comes directly from ripgrep, an awesome alternative to grep
also written in Rust.
Along the way, we build a FilePatcher from the source file and the query. The FilePatcher goes through every line of the file and then sends it along with the query to a LinePatcher.
The LinePatcher runs the code corresponding to the query type and returns a new string, using the Inflector crate to perform case string conversions if required.
Finally, if the string has changed, the FilePatcher builds a Replacement struct and pretty-prints it to the user. While doing so, it also keeps a record of the modified contents of the file. Finally, if not in dry-run mode, it overwrites the file with the new contents.
And that’s pretty much it :)
Why I’m sharing this #
The idea of ruplacer started almost a decade ago when a colleague of mine showed me a shell function called replacer
(thanks, Cédric!) It was basically a mixture of calls to find
, sed
and diff
. You can still find it online.
Because I wanted better cross-platform support, a dry-run mode and a colorful output, I rewrote it in Python a few years ago. Along the way, the features, command line syntax and the style of the output changed quite a lot, but I’ve been using it regularly for all this time.
Finally, after hearing about ripgrep and fd, I decided to give Rust a go, and that’s how ruplacer, the third incarnation of this tool, was born. This makes me confident it’s good enough for you to try.
If you have cargo
installed, you can get ruplacer by running cargo install ruplacer
. Otherwise, you will find the source code and pre-compiled binaries on GitHub.
Cheers!
Thanks for reading this far :)
I'd love to hear what you have to say, so please feel free to leave a comment below, or read the contact page for more ways to get in touch with me.
Note that to get notified when new articles are published, you can either:
- Subscribe to the RSS feed
- Follow me on Mastodon
- Follow me on dev.to (mosts of my posts are mirrored there)
- Or send me an email to subscribe to my newsletter
Cheers!