A CLI tool that injects content into marked regions in source files.
cargo install injmnix profile install github:Fovir-GitHub/injmDownload the latest binary for your platform from GitHub Releases.
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/**"]injmThis 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:
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.rsResult:
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.rsGive 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 greetingInject into multiple regions at once:
echo -n 'println!("Hello!")' | injm inject --output dest.rs --id greeting --id farewellIf --id is not specified, only regions without an ID are injected; regions with a >id are left untouched.
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.rsdest.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.
Per-region options can be appended to the injm begin marker as :option=value
tokens. They are set on the output region (the >id marker) and applied to
whatever content is injected into it. Multiple options may be combined in any
order:
// injm begin >id :offset=1 :trim=true :indent=4
// injm end:offset=N (default 0) — extend the region being replaced by N lines beyond the markers on each side, so lines just inside the markers survive injection. Useful for wrapper lines:
dest.tex
% injm begin >id :offset=1
\begin{minted}{rust}
\end{minted}
% injm endOnly the content between \begin{minted} and \end{minted} is replaced; the wrapper lines are kept. An offset that exceeds the block's own content reports an error.
:trim / :trim=true (default false) — remove leading and trailing blank lines from the injected content:
// injm begin >id :trim=true
// injm end:indent=N (default unset) — rebase the minimum indentation of the injected
content to N columns, preserving the relative indentation of the other lines.
The example below injects an 8-column-indented region at 4 columns instead:
src.rs
fn main() {
if true {
// injm begin <id
if true {
println!("Hello world");
}
// injm end
}
}dest.rs
fn main() {
// injm begin >id :indent=4
// injm end
}Becomes:
fn main() {
// injm begin >id :indent=4
if true {
println!("Hello world");
}
// injm end
}--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.rsSkip 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).
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-gitignoreCreate 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:
injmThis 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.tomlConfig values are merged with CLI flags — CLI arguments take precedence:
# Merges config's output with --exclude from CLI
injm --config injm.toml --exclude "temp/**"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 jsonAccepts positional arguments (files, globs, or directories). Falls back to current directory when no argument is given.
Preview the result without writing to the file:
cat src.txt | injm inject --output dest.rs --dry-runTo 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 --diffThese 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 --diffVerify that all output blocks (>id) contain the same content as their matching input blocks (<id):
injm check src/main.rsIf 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 --diffAccepts files, globs, or directories as arguments. Falls back to current directory when no argument is provided.
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
See CONTRIBUTING.md.
See ROADMAP.md.
MIT
- 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.