Skip to content

markusz/intern

Repository files navigation

intern

CI codecov Release License: MIT


Because your real interns have better things to do than align your ppt boxes.

intern is a linter for PowerPoint files. Point it at a .pptx and it tells you exactly what's wrong - misaligned boxes, inconsistent fonts, sloppy text, duplicate titles. It can automatically fix alignment, font-size, and whitespace issues.

Existing tools are proprietary Office add-ins or AI-powered web uploads. intern is the first open-source, rule-based CLI linter for PowerPoint - configurable, scriptable, and CI-friendly.

Read the documentation →


$ intern check deck.pptx

┌───────┬──────────────────────┬─────────┬────────────────┬──────┬───────────────────────────────────┬─────────────────────────────────────────┐
│ Slide ┆ Rule                 ┆ Type    ┆ Position       ┆ Id   ┆ Text                              ┆ Message                                 │
╞═══════╪══════════════════════╪═════════╪════════════════╪══════╪═══════════════════════════════════╪═════════════════════════════════════════╡
│ -     ┆ FONT_SIZE_VARIETY    ┆ -       ┆ -              ┆ -    ┆ -                                 ┆ 8 distinct body font sizes (limit: 3)   │
│       ┆                      ┆         ┆                ┆      ┆                                   ┆                                         │
│ 2     ┆ BULLET_LENGTH        ┆ Body    ┆ (40px, 132px)  ┆ 5    ┆ Our goals for the next quarter... ┆ bullet is 26 words (20-word limit)      │
│ 2     ┆ RIGHT_MARGIN         ┆ Body    ┆ (40px, 132px)  ┆ 5    ┆ Our goals for the next quarter... ┆ right edge at 905.4px (typical 927.0px) │
│       ┆                      ┆         ┆                ┆      ┆                                   ┆                                         │
│ 4     ┆ TITLE_TRAILING_PUNCT ┆ Title   ┆ (28px, 15px)   ┆ 12   ┆ Project Status.                   ┆ title ends with '.' - remove it         │
│       ┆                      ┆         ┆                ┆      ┆                                   ┆                                         │
│ 8     ┆ DUPLICATE_TITLE      ┆ Title   ┆ (28px, 15px)   ┆ 44   ┆ Overview                          ┆ same title as slide 6                   │
└───────┴──────────────────────┴─────────┴────────────────┴──────┴───────────────────────────────────┴─────────────────────────────────────────┘
5 violation(s) (5 error, 0 warning)

Installation

Pick whichever fits your setup - all three give you the same intern binary.

Homebrew - macOS & Linux

brew install markusz/intern/intern

Prebuilt binary

No toolchain required. Find your target in the table, then run the install commands.

Platform Target
macOS (Apple Silicon) aarch64-apple-darwin
macOS (Intel) x86_64-apple-darwin
Linux (x86-64) x86_64-unknown-linux-gnu
Windows (x86-64) x86_64-pc-windows-msvc

macOS / Linux - set TARGET to your row, then run:

TARGET=aarch64-apple-darwin   # ← replace with your target from the table
curl -L https://github.com/markusz/intern/releases/latest/download/intern-$TARGET.tar.gz | tar xz
sudo mv intern /usr/local/bin/

Windows - download intern-x86_64-pc-windows-msvc.zip, unzip, and add intern.exe to your PATH.

Build from source

Requires Rust.

cargo install --path intern

Usage

intern deck.pptx                    # check (the default action)
intern check slides/                # check every .pptx in a folder
intern fix deck.pptx                # auto-fix violations in place
intern ignore deck.pptx -s 3 -r RULE [-e 42]  # suppress a violation in speaker notes

That's it. No configuration required to get started - but for ongoing use, most teams keep an .intern.toml with their preferred thresholds and rule set (see Config file below).

Options

Flag Default Description
--rules RULE_ID,... all Run only the specified rules
--disable RULE_ID,... none Skip specific rules
--threshold <px> 2 Alignment tolerance in pixels
--slide <n> all Analyze only slide n
--output table|text|json table Output format
--group-by slide|rule slide Group violations
--config <path> auto Load settings from a specific config file

Use in CI

intern check deck.pptx --output json > violations.json

Exit code is 0 when clean (or only warnings) and 1 when an error-severity violation is found - standard for shell scripting and CI pipelines.

Config file

Settings can live in a TOML file. intern loads the first one it finds:

  1. the path passed to --config <file>
  2. ./.intern.toml in the current directory (project config)
  3. ~/.config/intern.toml (user config, honours $XDG_CONFIG_HOME)

Files are not merged - the highest-precedence file wins as a whole, and CLI flags override individual settings on top of it.

threshold_px = 2

disable = ["ALL_CAPS"]                  # turn rules off in bulk
# only  = ["TITLE_Y", "TITLE_X_WIDTH"]  # if set, ONLY these rules run

[output]
format = "table"
group_by = "rule"

[rules.TITLE_LENGTH]
max_words = 8

[rules.ALL_CAPS]
severity = "warning"   # report it, but don't fail CI

[rules.SLIDE_COUNT]
enabled = true         # SLIDE_COUNT is off by default; enable it explicitly
max_slides = 40

Each rule is configured in its own [rules.<RULE_ID>] table; disable and only are blunt top-level lists. See the documentation for the full reference.

Suppressing violations

intern ignore - write the directive for you

The quickest way to suppress a violation is intern ignore:

intern ignore deck.pptx -s <slide> -r <rule>           # whole slide
intern ignore deck.pptx -s <slide> -r <rule> -e <id>   # one element

This writes an intern: disable line into that slide's speaker notes and backs the file up to deck.pptx.bak. The slide number and element id come straight from the violation table output.

Manual speaker-note directives

You can also edit the notes by hand. To skip an entire slide:

intern: disable                        # skip every rule on this slide
intern: disable TITLE_Y, DUPLICATE_TITLE  # skip only these rules

To suppress a rule for one element only (use the id shown in the Id column):

intern: disable(42) EMPTY_TEXTBOX
intern: disable(42)                    # suppress every rule for that element

The slide is dropped before those rules run, so it skews no baselines (like the median title position) either.


Rules

29 rules across four categories. For each rule: what it does, why it matters, defaults, and examples - see RULES.md.

Rules marked off require enabled = true in [rules.<RULE_ID>] or --rules <ID> to run. Any on-by-default rule can be suppressed with --disable.

Alignment

Rule Default What it catches
LEFT_MARGIN on Slide's leftmost unit is off the typical left margin
RIGHT_MARGIN on Slide's rightmost unit right edge is off the typical right margin
BOTTOM_MARGIN on Content extends deeper than the typical bottom margin
TITLE_MARGIN on Gap between title and nearest content unit differs from the typical gap
CLOSE_X on Two units have X positions within threshold - likely misaligned
CLOSE_Y on Two units have Y positions within threshold - likely misaligned
TITLE_Y on Title top-edge inconsistent across slides
TITLE_X_WIDTH on Title left-edge or width inconsistent across slides
TEXT_ELEMENT_OVERLAP on Two text-bearing elements on the same slide have overlapping rects
ELEMENT_OVERFLOW on Element extends outside the slide bounds

Typography

Rule Default What it catches
TITLE_FONT_SIZE on Title font size differs from the majority
FONT_SIZE_VARIETY on Too many distinct body font sizes across the deck
BODY_FONT_FAMILY on Body font family differs from the majority across slides
FONT_VARIETY on Too many distinct font families across the deck
COLOR_VARIETY on Too many distinct text colors across the deck
BODY_TEXT_COLOR off Body text color differs from the majority across slides

Text quality

Rule Default What it catches
DOUBLE_SPACE on Paragraph contains two or more consecutive spaces
LEADING_SPACE on Paragraph starts with whitespace
REPEATED_WORD on Two consecutive identical words ("the the")
BULLET_CAPITALIZATION on Bullets have inconsistent first-letter capitalization
BULLET_PUNCTUATION on Bullet ending punctuation is inconsistent across the deck
BULLET_LENGTH on Bullet is too long
ALL_CAPS off Paragraph text is ALL CAPS

Structure

Rule Default What it catches
TITLE_LENGTH on Title is too long
TITLE_TRAILING_PUNCT on Title ends with . , : or ;
DUPLICATE_TITLE on Title text is duplicated on another slide
EMPTY_TEXTBOX on Text box has no text content
TITLE_PRESENT off Slide has no title element
SLIDE_COUNT off Deck has too many slides

Embed in your own tooling

intern-core is the engine without the CLI - use it to build custom tooling, reporting pipelines, or editor integrations.

[dependencies]
intern-core = { git = "https://github.com/markusz/intern" }
use intern_core::{
    model::EMU_PER_PX,
    reader::read_presentation,
    rules::{all_rules, Limits, RuleContext},
};

let pres = read_presentation("deck.pptx")?;
let ctx = RuleContext {
    threshold: 2 * EMU_PER_PX,
    slide_width: pres.slide_width,
    slide_height: pres.slide_height,
};
let limits = Limits { slide_count: 30, ..Limits::default() };
let violations: Vec<_> = all_rules(&limits)
    .iter()
    .flat_map(|r| r.check(&pres.slides, &ctx))
    .collect();

About

A PowerPoint linter - Because your human interns have better things to do than format your slides

Resources

License

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages