injm 1.0.0

A CLI tool that injects content into marked regions in source files.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
# injm

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

## Table of Contents

<!-- toc -->

- [Installation]#installation
  - [Cargo]#cargo
  - [Nix]#nix
  - [Download Binary]#download-binary
  - [GitHub Action]#github-action
- [Usage]#usage
  - [Quick Start]#quick-start
  - [Basic Injection]#basic-injection
  - [Inject into a Specific Region]#inject-into-a-specific-region
  - [Sync Between Files]#sync-between-files
  - [Marker Region Configuration]#marker-region-configuration
  - [Multiple Files and Globs]#multiple-files-and-globs
  - [Excluding Files]#excluding-files
  - [`.gitignore` Integration]#gitignore-integration
  - [Project Configuration]#project-configuration
  - [List Markers]#list-markers
  - [Dry Run]#dry-run
  - [Check]#check
- [Supported Languages]#supported-languages
- [Contributing]#contributing
- [Roadmap]#roadmap
- [License]#license
- [Acknowledgement]#acknowledgement

<!-- tocstop -->

## Installation

### Cargo

```bash
cargo install injm
```

### Nix

```bash
nix profile install github:FovirDev/injm
```

### Download Binary

Download the latest binary for your platform from [GitHub Releases](https://github.com/FovirDev/injm/releases/latest).

### GitHub Action

Use the [setup injm action](https://github.com/FovirDev/injm) to install `injm` in your GitHub Actions workflow:

```yaml
steps:
  - uses: FovirDev/injm@v1
    with:
      version: latest # optional, defaults to `latest`; use a specific tag like `v1.0.0` to pin
```

The action downloads the injm binary for the current platform and adds it to `PATH`, so you can use it in subsequent steps:

```yaml
steps:
  - uses: FovirDev/injm@v1

  - name: Verify marker regions are in sync
    run: injm check
```

It supports `ubuntu` (x64/arm64), `macOS` (x64/arm64) and `windows` (x64) runners.

## Usage

### Quick Start

The simplest way is to create an `injm.toml` in your project root and run `injm` without any subcommand:

```toml
input = ["src/**/*.rs"]
output = ["docs/"]
exclude = ["target/**"]
```

```bash
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`

```rust
fn main() {
    // injm begin
    // injm end
}
```

Then pipe content into `injm`:

```bash
echo -n 'println!("Hello, world!")' | injm inject --output dest.rs
```

Result:

`dest.rs`

```rust
fn main() {
    // injm begin
println!("Hello, world!");
    // injm end
}
```

Running `injm inject` again will replace the content between the markers:

```bash
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`

```rust
fn main() {
    // injm begin >greeting
    // injm end

    // injm begin >farewell
    // injm end
}
```

Inject into a specific region:

```bash
echo -n 'println!("Hello!")' | injm inject --output dest.rs --id greeting
```

Inject into multiple regions at once:

```bash
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`

```rust
fn main() {
    // injm begin <hello
    println!("Hello, world!");
    // injm end
}
```

`dest.rs`

```rust
fn main() {
    // injm begin >hello
    // injm end
}
```

Then sync:

```bash
injm inject --input src.rs --output dest.rs
```

`dest.rs` becomes:

```rust
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.

### Marker Region Configuration

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:

```rust
// 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`

```latex
% injm begin >id :offset=1
\begin{minted}{rust}
\end{minted}
% injm end
```

Only 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:

```rust
// 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`

```rust
fn main() {
    if true {
        // injm begin <id
        if true {
            println!("Hello world");
        }
        // injm end
    }
}
```

`dest.rs`

```rust
fn main() {
    // injm begin >id :indent=4
    // injm end
}
```

Becomes:

```rust
fn main() {
    // injm begin >id :indent=4
    if true {
        println!("Hello world");
    }
    // injm end
}
```

### Multiple Files and Globs

`--input` and `--output` accept multiple values and glob patterns:

```bash
# 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:

```bash
# 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](#project-configuration)).

### `.gitignore` Integration

By default, `injm` respects `.gitignore` rules — any file matched by `.gitignore` is skipped:

```bash
# Files in .gitignore are automatically excluded
injm inject --input src/ --output dest/
```

To include gitignored files, use `--no-gitignore`:

```bash
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:

```toml
input = ["examples/*.md"]
output = ["docs/**"]
exclude = [
    "target/**",
    "vendor/**"
]
```

When `injm.toml` is present, you can run `injm` **without any subcommand**:

```bash
injm
```

This reads the config, injects content from `input` into matching regions in
`output`, and writes the result. Equivalent to:

```bash
injm inject --input "examples/*.md" --output "docs/**" --exclude "target/**" --exclude "vendor/**"
```

You can also point to a different config file with `--config`:

```bash
injm --config path/to/injm.toml
```

Config values are merged with CLI flags — CLI arguments take precedence:

```bash
# Merges config's output with --exclude from CLI
injm --config injm.toml --exclude "temp/**"
```

### List Markers

Preview all marker regions across files:

```bash
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:

```bash
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:

```bash
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`:

```bash
cat src.txt | injm inject --output dest.rs --dry-run --diff
```

These flags also work with the root command when using `injm.toml`:

```bash
# 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`):

```bash
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`:

```bash
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](https://tree-sitter.github.io/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](https://github.com/kreuzberg-dev/tree-sitter-language-pack), including:

- Rust, C, C++
- Python, Ruby
- JavaScript, TypeScript
- Go, Java
- And [300+ more]https://github.com/kreuzberg-dev/tree-sitter-language-pack

## Contributing

See [CONTRIBUTING.md](CONTRIBUTING.md).

## Roadmap

See [ROADMAP.md](ROADMAP.md).

## License

MIT

## Acknowledgement

- [clap-rs/clap]https://github.com/clap-rs/clap: A full featured, fast Command Line Argument Parser for Rust.
- [ignore]https://github.com/BurntSushi/ripgrep/tree/master/crates/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]https://github.com/rust-lang/glob: Support for matching file paths against Unix shell style patterns.
- [serde-rs/serde]https://github.com/serde-rs/serde: Serialization framework for Rust.
- [xberg-io/tree-sitter-language-pack]https://github.com/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]https://github.com/zhiburt/tabled: An easy to use library for pretty print tables of Rust structs and enums.