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 convertCore 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
Pentiment.Span- Span types (ByteandPosition)Pentiment.Spannable- Protocol for custom span typesPentiment.Label- Labeled spans for annotationsPentiment.Source- Source text representationPentiment.Diagnostic- Protocol for diagnostic typesPentiment.Report- Default diagnostic struct with builder APIPentiment.Formatter.Renderer- Rich multi-line formatterPentiment.Formatter.Compact- Single-line formatterPentiment.Elixir- Helpers for Elixir AST integration
Summary
Functions
Formats a diagnostic for display.
Formats multiple diagnostics for display.
Formats a diagnostic as a single line (compact format).
Types
@type format_options() :: [ colors: boolean(), context_lines: non_neg_integer(), formatter: module() ]
Functions
@spec format( Pentiment.Diagnostic.t(), Pentiment.Source.t() | map() | String.t(), format_options() ) :: String.t()
Formats a diagnostic for display.
Arguments
diagnostic- Any struct implementingPentiment.Diagnosticsources- Source content, can be:- A
Pentiment.Sourcestruct - A map of source names to content strings
- A map of source names to
Pentiment.Sourcestructs - A file path string (will be read from disk)
- A
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)
@spec format_all( [Pentiment.Diagnostic.t()], Pentiment.Source.t() | map() | String.t(), format_options() ) :: String.t()
Formats multiple diagnostics for display.
Arguments
diagnostics- List of structs implementingPentiment.Diagnosticsources- Source content (same formats asformat/3)opts- Formatting options (same asformat/3)
Examples
errors = [error1, error2, error3]
IO.puts(Pentiment.format_all(errors, sources))
@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)"