Pentiment.Source (pentiment v0.2.0)
View SourceRepresents 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.
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
@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.
@type source_option() :: {:language, language()}
Functions
@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
Returns the raw string content of the source.
Returns nil if no content is available.
@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}
@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}
Returns true if the source has content available.
@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
@spec line_count(t()) :: non_neg_integer() | nil
Returns the total number of lines, or nil if content is not available.
@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"}]
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"]
Returns the source name (file path or display name).
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}