10 releases (6 breaking)
Uses new Rust 2024
| 0.7.0 | Jul 28, 2026 |
|---|---|
| 0.6.0 | Jul 19, 2026 |
| 0.5.1 | Jul 18, 2026 |
| 0.4.0 | Jul 10, 2026 |
| 0.1.1 | Jul 5, 2026 |
#179 in Development tools
92KB
2.5K
SLoC
injm
A CLI tool that injects content into marked regions in source files.
Table of Contents
Installation
Cargo
cargo install injm
Nix
nix profile install github:Fovir-GitHub/injm
Download Binary
Download the latest binary for your platform from GitHub Releases.
Usage
Quick Start
The simplest way is to create an injm.toml in your project root and run injm without any subcommand:
input = ["src/**/*.rs"]
output = ["docs/"]
exclude = ["target/**"]
injm
This reads content from src/**/*.rs, finds all <id markers, and injects them into matching >id regions in docs/.
You can also use subcommands for one-off operations. The main one is inject:
Basic Injection
Mark a region in your source file with injm begin and injm end comments:
dest.rs
fn main() {
// injm begin
// injm end
}
Then pipe content into injm:
echo -n 'println!("Hello, world!")' | injm inject --output dest.rs
Result:
dest.rs
fn main() {
// injm begin
println!("Hello, world!");
// injm end
}
Running injm inject again will replace the content between the markers:
cat src.txt | injm inject --output dest.rs
Inject into a Specific Region
Give a region an output ID with >id, then target it with --id:
dest.rs
fn main() {
// injm begin >greeting
// injm end
// injm begin >farewell
// injm end
}
Inject into a specific region:
echo -n 'println!("Hello!")' | injm inject --output dest.rs --id greeting
Inject into multiple regions at once:
echo -n 'println!("Hello!")' | injm inject --output dest.rs --id greeting --id farewell
If --id is not specified, only regions without an ID are injected; regions with a >id are left untouched.
Sync Between Files
Instead of piping from stdin, copy content between files with --input.
Mark the source region with <id (the content to read) and the destination
region with >id (where it goes):
src.rs
fn main() {
// injm begin <hello
println!("Hello, world!");
// injm end
}
dest.rs
fn main() {
// injm begin >hello
// injm end
}
Then sync:
injm inject --input src.rs --output dest.rs
dest.rs becomes:
fn main() {
// injm begin >hello
println!("Hello, world!");
// injm end
}
A region may read from several sources by listing multiple <id markers.
If a >id in the output has no matching <id in the input, injm reports
the missing ID and exits with an error.
Multiple Files and Globs
--input and --output accept multiple values and glob patterns:
# Multiple explicit files
injm inject --input src1.rs src2.rs --output dest.rs
# Sync to multiple outputs
injm inject --input src.rs --output out1.rs out2.rs
# Glob patterns
injm inject --input "src/**/*.rs" --output "docs/"
# Multiple globs
injm inject --input "mod_a/**/*.rs" "mod_b/**/*.rs" --output dest.rs
Excluding Files
Skip files with --exclude (or -e). Accepts glob patterns matched against
absolute paths:
# Exclude a specific file
injm inject --input src/ --output dest/ --exclude src/legacy.rs
# Exclude with glob patterns
injm inject --input src/ --output dest/ --exclude "**/vendor/**" "**/*.generated.rs"
# Multiple flags
injm inject --input src/ --output dest/ -e "target/**" -e "node_modules/**"
Exclusion applies to both --input and --output files. The same patterns can also be set in injm.toml (see Project Configuration).
.gitignore Integration
By default, injm respects .gitignore rules — any file matched by .gitignore is skipped:
# Files in .gitignore are automatically excluded
injm inject --input src/ --output dest/
To include gitignored files, use --no-gitignore:
injm inject --input src/ --output dest/ --no-gitignore
Project Configuration
Create an injm.toml in your project root to define input sources, output
destinations, and exclusion patterns:
input = ["examples/*.md"]
output = ["docs/**"]
exclude = [
"target/**",
"vendor/**"
]
When injm.toml is present, you can run injm without any subcommand:
injm
This reads the config, injects content from input into matching regions in
output, and writes the result. Equivalent to:
injm inject --input "examples/*.md" --output "docs/**" --exclude "target/**" --exclude "vendor/**"
You can also point to a different config file with --config:
injm --config path/to/injm.toml
Config values are merged with CLI flags — CLI arguments take precedence:
# Merges config's output with --exclude from CLI
injm --config injm.toml --exclude "temp/**"
List Markers
Preview all marker regions across files:
injm list src/
Output:
+-------------+----------+--------+-------+
| File | ID | Type | Lines |
+-------------+----------+--------+-------+
| src/main.rs | hello | output | 6-7 |
+-------------+----------+--------+-------+
| src/main.rs | hello | input | 23-24 |
+-------------+----------+--------+-------+
| src/cli.rs | greeting | input | 1-2 |
+-------------+----------+--------+-------+
JSON output:
injm list src/ --format json
Accepts positional arguments (files, globs, or directories). Falls back to current directory when no argument is given.
Dry Run
Preview the result without writing to the file:
cat src.txt | injm inject --output dest.rs --dry-run
To see a unified diff of what would change instead of the full file, add --diff:
cat src.txt | injm inject --output dest.rs --dry-run --diff
These flags also work with the root command when using injm.toml:
# Preview all changes from config
injm --dry-run
# Show unified diff of what would change
injm --dry-run --diff
Check
Verify that all output blocks (>id) contain the same content as their matching input blocks (<id):
injm check src/main.rs
If all blocks are synchronized, injm check exits 0 and prints:
all marker blocks are synchronized
If any are out of sync, it exits non-zero and lists each mismatch:
src/main.rs:12-14: output block `hello` is out of sync
To see a unified diff of what each out-of-sync block should contain, use
--diff:
injm check src/main.rs --diff
Accepts files, globs, or directories as arguments. Falls back to current directory when no argument is provided.
Supported Languages
injm uses tree-sitter to parse source files, so markers are detected from actual comment nodes — not from string literals or other non-comment content.
Supports any language recognized by tree-sitter-language-pack, including:
- Rust, C, C++
- Python, Ruby
- JavaScript, TypeScript
- Go, Java
- And 300+ more
Contributing
See CONTRIBUTING.md.
Roadmap
See ROADMAP.md.
License
MIT
Acknowledgement
- clap-rs/clap: A full featured, fast Command Line Argument Parser for Rust.
- ignore: The ignore crate provides a fast recursive directory iterator that respects various filters such as globs, file types and .gitignore files. This crate also provides lower level direct access to gitignore and file type matchers.
- rust-lang/glob: Support for matching file paths against Unix shell style patterns.
- serde-rs/serde: Serialization framework for Rust.
- xberg-io/tree-sitter-language-pack: Comprehensive tree-sitter grammar compilation with polyglot bindings — Rust, Python, Node.js, Go, Java, Ruby, Elixir, PHP, C#, WASM, Dart, Kotlin-Android, Swift, Zig, and CLI. 306+ languages.
- zhiburt/tabled: An easy to use library for pretty print tables of Rust structs and enums.
Dependencies
~17–27MB
~383K SLoC