Pentiment (pentiment v0.1.5)

Beautiful, informative compiler-style error messages for Elixir.

Pentiment provides rich diagnostic formatting with highlighted source spans, helpful suggestions, and clear error context. It's designed for:

  • Compile-time macro errors
  • DSL validation
  • Parser error reporting
  • Configuration file validation

Quick Start

# Create a diagnostic
report = Pentiment.Report.error("Type mismatch")
  |> Pentiment.Report.with_code("E0001")
  |> Pentiment.Report.with_source("lib/my_app.ex")
  |> Pentiment.Report.with_label(
    Pentiment.Label.primary(Pentiment.Span.position(15, 10), "expected `integer`, found `float`")
  )
  |> Pentiment.Report.with_help("Use `trunc/1` to convert")

# Format and display
IO.puts(Pentiment.format(report, source))

Output Example

error[E0001]: Type mismatch
   [lib/my_app.ex:15:10]
   
14    add = fn x :: integer, y :: integer ->
15      x + y + 1.5
                
                     expected `integer`, found `float`
   
   
      help: Use `trunc/1` to convert

Core Concepts

  • Span - A region in source code (byte offset or line/column)
  • Label - An annotated span with a message and priority
  • Source - The source text to display context from
  • Diagnostic - The complete error/warning with all metadata
  • Report - A ready-to-use diagnostic struct with builder API

Modules

Summary

Functions

Formats a diagnostic for display.

Formats multiple diagnostics for display.

Formats a diagnostic as a single line (compact format).

Types

format_options()

@type format_options() :: [
  colors: boolean(),
  context_lines: non_neg_integer(),
  formatter: module()
]

Functions

format(diagnostic, sources, opts \\ [])

Formats a diagnostic for display.

Arguments

Options

  • :colors - Whether to use ANSI colors (default: true)
  • :context_lines - Lines of context around labels (default: 2)
  • :formatter - Formatter module (default: Pentiment.Formatter.Renderer)

Examples

# With a Source struct
source = Pentiment.Source.from_file("lib/app.ex")
Pentiment.format(report, source)

# With a map of sources
Pentiment.format(report, %{"lib/app.ex" => File.read!("lib/app.ex")})

# With a file path (reads from disk)
Pentiment.format(report, "lib/app.ex")

# Without colors
Pentiment.format(report, source, colors: false)

format_all(diagnostics, sources, opts \\ [])

@spec format_all(
  [Pentiment.Diagnostic.t()],
  Pentiment.Source.t() | map() | String.t(),
  format_options()
) :: String.t()

Formats multiple diagnostics for display.

Arguments

Examples

errors = [error1, error2, error3]
IO.puts(Pentiment.format_all(errors, sources))

format_compact(diagnostic)

@spec format_compact(Pentiment.Diagnostic.t()) :: String.t()

Formats a diagnostic as a single line (compact format).

Useful for log output or machine-parseable formats.

Examples

report = Pentiment.Report.error("Type mismatch")
  |> Pentiment.Report.with_code("E0001")
  |> Pentiment.Report.with_source("lib/app.ex")
  |> Pentiment.Report.with_label(Pentiment.Label.primary(Pentiment.Span.position(15, 10), "here"))

Pentiment.format_compact(report)
# => "[E0001] Type mismatch (lib/app.ex:15:10)"