Pentiment.Source (pentiment v0.1.5)

Represents a source of text that diagnostics can reference.

Sources provide the content needed to display source code context in diagnostic output. There are three ways to create a source:

Source Types

from_file/1 - File on disk

Reads content from a file path. The source name is the file path.

source = Pentiment.Source.from_file("lib/my_app.ex")

from_string/2 - In-memory content

Creates a source from a string with a display name. Useful for code that isn't on disk (user input, test fixtures, generated code).

source = Pentiment.Source.from_string("<stdin>", user_input)
source = Pentiment.Source.from_string("generated.ex", generated_code)

named/1 - Deferred content

Creates a source with just a name, no content. Content is provided later when formatting. Useful when accumulating diagnostics and deferring file reads.

source = Pentiment.Source.named("lib/app.ex")
# Later, when formatting:
Pentiment.format(report, %{"lib/app.ex" => File.read!("lib/app.ex")})

Accessing Content

Use lines/1 to get the source as a list of lines (for rendering), and content/1 to get the raw string content.

Summary

Functions

Converts a byte offset to a line and column position.

Returns the raw string content of the source.

Creates a source from a file path.

Creates a source from a string with a display name.

Returns true if the source has content available.

Returns a specific line from the source (1-indexed).

Returns the total number of lines, or nil if content is not available.

Returns a range of lines from the source (1-indexed, inclusive).

Returns the source content as a list of lines.

Returns the source name (file path or display name).

Creates a named source without content.

Types

t()

@type t() :: %Pentiment.Source{
  content: String.t() | nil,
  lines: [String.t()] | nil,
  name: String.t()
}

Functions

byte_to_position(source, offset)

@spec byte_to_position(t(), non_neg_integer()) :: {pos_integer(), pos_integer()} | nil

Converts a byte offset to a line and column position.

Returns {line, column} where both are 1-indexed, or nil if the offset is out of bounds or no content is available.

Examples

iex> source = Pentiment.Source.from_string("test", "hello\nworld")
iex> Pentiment.Source.byte_to_position(source, 0)
{1, 1}

iex> source = Pentiment.Source.from_string("test", "hello\nworld")
iex> Pentiment.Source.byte_to_position(source, 6)
{2, 1}

iex> source = Pentiment.Source.from_string("test", "hello\nworld")
iex> Pentiment.Source.byte_to_position(source, 100)
nil

content(source)

@spec content(t()) :: String.t() | nil

Returns the raw string content of the source.

Returns nil if no content is available.

from_file(path)

@spec from_file(Path.t()) :: t()

Creates a source from a file path.

Reads the file content immediately. Raises if the file cannot be read.

Examples

iex> source = Pentiment.Source.from_file("lib/my_app.ex")
%Pentiment.Source{name: "lib/my_app.ex", content: "..."}

from_string(name, content)

@spec from_string(String.t(), String.t()) :: t()

Creates a source from a string with a display name.

Use this for content that isn't on disk, like user input or generated code.

Examples

iex> source = Pentiment.Source.from_string("<stdin>", "x = 1 + 2")
%Pentiment.Source{name: "<stdin>", content: "x = 1 + 2"}

has_content?(source)

@spec has_content?(t()) :: boolean()

Returns true if the source has content available.

line(source, line_num)

@spec line(t(), pos_integer()) :: String.t() | nil

Returns a specific line from the source (1-indexed).

Returns nil if the line doesn't exist or content is not available.

Examples

iex> source = Pentiment.Source.from_string("test", "line1\nline2\nline3")
iex> Pentiment.Source.line(source, 2)
"line2"

iex> Pentiment.Source.line(source, 100)
nil

line_count(source)

@spec line_count(t()) :: non_neg_integer() | nil

Returns the total number of lines, or nil if content is not available.

line_range(source, start_line, end_line)

@spec line_range(t(), pos_integer(), pos_integer()) :: [{pos_integer(), String.t()}]

Returns a range of lines from the source (1-indexed, inclusive).

Returns an empty list if content is not available.

Examples

iex> source = Pentiment.Source.from_string("test", "a\nb\nc\nd\ne")
iex> Pentiment.Source.line_range(source, 2, 4)
[{2, "b"}, {3, "c"}, {4, "d"}]

lines(source)

@spec lines(t()) :: [String.t()] | nil

Returns the source content as a list of lines.

Lines are 1-indexed when accessed (line 1 is at index 0). Returns nil if no content is available.

Examples

iex> source = Pentiment.Source.from_string("test", "line1\nline2\nline3")
iex> Pentiment.Source.lines(source)
["line1", "line2", "line3"]

name(source)

@spec name(t()) :: String.t()

Returns the source name (file path or display name).

named(name)

@spec named(String.t()) :: t()

Creates a named source without content.

The content must be provided later when formatting the diagnostic. This is useful when you want to defer file reads or when the content comes from an external source.

Examples

iex> source = Pentiment.Source.named("lib/app.ex")
%Pentiment.Source{name: "lib/app.ex", content: nil}