#marker #inject #code

app injm

A CLI tool that injects content into marked regions in source files

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

MIT license

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