Pentiment.Source (pentiment v0.2.0)

View Source

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

Types

The language of the source content, used for syntax highlighting.

t()

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

language()

@type language() :: :elixir | :erlang | nil

The language of the source content, used for syntax highlighting.

Inferred from the source name's extension by the constructors, or set explicitly via the :language option. nil means unknown; unknown sources are never highlighted.

source_option()

@type source_option() :: {:language, language()}

t()

@type t() :: %Pentiment.Source{
  content: String.t() | nil,
  language: language(),
  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, opts \\ [])

@spec from_file(Path.t(), [source_option()]) :: t()

Creates a source from a file path.

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

The language is inferred from the path's extension (.ex/.exs are Elixir, .erl/.hrl are Erlang); pass language: to override.

Examples

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

from_string(name, content, opts \\ [])

@spec from_string(String.t(), String.t(), [source_option()]) :: 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.

The language is inferred from the name's extension (.ex/.exs are Elixir, .erl/.hrl are Erlang); names without a recognized extension (like "<stdin>") get nil. Pass language: to override.

Examples

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

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.

The language is inferred from the name's extension so it stays consistent with the other constructors, but a named source has no content and is therefore never highlighted.

Examples

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