4 releases (2 breaking)

0.3.1 Jun 10, 2020
0.3.0 Jun 2, 2020
0.2.0 May 26, 2020
0.1.0 May 25, 2020

#2847 in Parser implementations

Custom license

200KB
1.5K SLoC

md-inc - Include files in Markdown docs

Overview

Preview

  • Include external files into your markdown documents - inline!
    • Input is inserted between command block and end block
    • Overwrites anything that was previously there
  • Transform included files
    • Wrap your input in code blocks or custom text
    • Filter specific lines
    • Add line numbers or other line decorators
  • Easily configure with .md-inc.toml file.
    • Specify 1 or more files to transform.
    • Specify base path used for relative file includes.
    • Replace <!--{ and }--> tags - useful if you need to avoid conflicts or existing comments.

Example

Here is a code file, file.rs, that we want to include in our Markdown document:

fn main() {
    println!("Hello, World!");
}

The file can be included using command tags, sneakily disguised as comments so they aren't rendered in the actual document:

Look at the following rust code:
<!--{ "file.rs" | code: rust }-->
<!--{ end }-->
This will print 'Hello World' to the console.

After running md-inc, the file will be transformed into:

Look at the following rust code:
<!--{ "file.rs" | code: rust }-->
```rust
fn main() {
    println!("Hello, World!");
}
```
<!--{ end }-->
This will print 'Hello World' to the console.

Note: The surrounding ```rust and ``` lines were inserted because we piped the input into the code: rust command. More on this later!

Install

cargo install md-inc

Run

md-inc [FLAGS] [OPTIONS] [files]...

If no files are given, the files field in .md-inc.toml is used.

Configuration

.md-inc.toml can be configured by setting any of the following:

open_tag: The opening tag for a command

# <!--{ COMMAND }-->
# ^^^^^
open_tag = "<!--{" 

close_tag: The closing tag for a command

# <!--{ COMMAND }-->
#               ^^^^
close_tag = "}-->"

end_command: The name to use for the end command

# <!--{ COMMAND }-->
# <<FILE_CONTENTS>>
# <!--{ end }-->
#       ^^^
end_command = "end"

base_dir: The base directory for relative imported file paths, relative to the config file

# For the directory tree:
#   ├╼ README.md
#   ├╼ .md-inc.toml
#   ╰╼ doc
#     ├╼ file.txt
#     ╰╼ other
#       ╰╼ file2.txt
# If base_dir = "doc", then files can be named relative to doc
# <!--{ "file.txt" }-->
# ...
# <!--{ "other/file2.txt" }-->
base_dir = "doc"

files: A list of files to be transformed, relative to the config file

files = ["README.md", "doc/file.md"]

depend_dirs: A list of directories containing ".md-inc.toml" that will be visited before this one.

depend_dirs = ["doc/example1", "doc/example2"]

next_dirs: A list of directories containing ".md-inc.toml" that will be visited after this one.

next_dirs = ["doc/example1", "doc/example2"]

Note: "depend_dirs" and "next_dirs" are NOT called recursively.

out_dir: An optional output directory for generated files. If this is defined, the generated files will be written to this directory instead of overwriting the original files.

out_dir = "path/to/output"

Commands

Included files can be manipulated by piping commands together.

General Syntax:

Include file.txt:

<!--{ "file.txt }-->
<!--{ end }-->

Include file.txt inside a code block:

<!--{ "file.txt | code }-->
<!--{ end }-->

Include file.py inside a code block with python syntax highlighting:

<!--{ "file.py" | code: python }-->
<!--{ end }-->

Include only lines 4 to 10 of file.py inside a code block with python syntax highlighting:

<!--{ "file.py" | lines: 4 10 | code: python }-->
<!--{ end }-->
  • The first value should always be the filename.
  • Commands can be chained together using the pipe (|) operator. "file.txt" | code
  • Some commands may take space-separated arguments after a colon (:) character. "file.txt | lines: 4 10
  • Commands are applied to the included file from left to right.

code: [language]

  • Wraps the file in a code block (triple backticks)
  • language: the language used for syntax highlighting. If given, this will be added directly after the top backticks.

Without language:

<!--{ "doc/file.txt" | code }-->
```
FILE_CONTENTS
```
<!--{ end }-->

With language:

<!--{ "doc/file.html" | code: html }-->
```html
FILE_CONTENTS
```
<!--{ end }-->

lines: first [last]

  • Restricts the input to the given range of lines
    • (include line, if first <= line <= last)
  • first: The first line to import
  • last: The last line to import (1-based index)
    • If last is not provided, all lines will be included from first until the end of the input.

Given the file, alphabet.txt:

A
B
C
D
E

Trim leading lines

Input:

<!--{ "alphabet.txt" | lines: 4 }-->
<!--{ end }-->

This keeps the 4th line until the end of the file.

Output:

<!--{ "alphabet.txt" | lines: 4 }-->
D
E
<!--{ end }-->

Trim trailing lines

Input:

<!--{ "alphabet.txt" | lines: 1 3 }-->
<!--{ end }-->

This keeps only lines 1 to 3 Output:

<!--{ "alphabet.txt" | lines: 1 3 }-->
A
B
C
<!--{ end }-->

Trim both leading and trailing lines

Input:

<!--{ "alphabet.txt" | lines: 2 4 }-->
<!--{ end }-->

This keeps only lines 2 to 4

Output:

<!--{ "alphabet.txt" | lines: 2 4 }-->
B
C
D
<!--{ end }-->

line: list...

  • Restricts the input to the given list of line numbers (1-based index).
  • list...: A list of line numbers to included

Input:

<!--{ "alphabet.txt" | line: 3 2 1 }-->
<!--{ end }-->

Output:

<!--{ "alphabet.txt" | line: 3 2 1 }-->
C
B
A
<!--{ end }-->

line-numbers: [separator] [width]

  • Adds a line number to each line
  • [separator]: Optional separator used between the line number and the rest of the line.
    • If not provided, : is used.
  • [width]: Optional width for line numbers.
    • If not provided, the width of the longest line number is used.

With Default Arguments:

Input:

<!--{ "full_alphabet.txt" | line-numbers | lines: 8 14 }-->
<!--{ end }-->

Output:

<!--{ "full_alphabet.txt" | line-numbers | lines: 8 14 }-->
 8: H
 9: I
10: J
11: K
12: L
13: M
14: N
<!--{ end }-->

With Provided Arguments:

Input:

<!--{ "alphabet.txt" | line-numbers: " " 4 }-->
<!--{ end }-->

Output:

<!--{ "alphabet.txt" | line-numbers: " " 4 }-->
   1 A
   2 B
   3 C
   4 D
   5 E
<!--{ end }-->

wrap: text or wrap: before after

  • Inserts text before and after the input.
  • text: Text that is inserted before and after the input (no newline)
  • before: Text that is inserted before the input (no newline).
  • after: Text that is inserted after the input (no newline).

wrap-lines: text or wrap-lines: before after

  • Inserts text before and after each line of the input.
  • text: Text that is inserted before and after each line of the input.
  • before: Text that is inserted before each line of the input.
  • after: Text that is inserted after each line of the input.

match: pattern [group_num]

  • Inserts text from a file that matches the pattern.
  • pattern: A regex pattern
  • group_num: The capture group matching group_num is inserted.
    • A group_num of 0 is the whole regex pattern

For a file, hello_world.rs:

// Main
fn main() {
    println!("Hello, World!");
}
// Goodbye
fn goodbye() {
    println!("Goodbye, World!");
}

The main() function can be extracted using the match command:

Input:

<!--{ "hello_world.rs" | match: "\n(fn main[\s\S]*?\n\})" 1 | code: rust }-->
<!--{ end }-->

Output:

<!--{ "hello_world.rs" | match: "\n(fn main[\s\S]*?\n\})" 1 | code: rust }-->
```rust
fn main() {
    println!("Hello, World!");
}
```
<!--{ end }-->

Dependencies

~8.5MB
~150K SLoC