Packages
beancount_ex
0.6.0
An idiomatic Elixir interface to Beancount that serves as the long-term behavioral oracle for a future native Elixir General Ledger.
Current section
Files
Jump to
Current section
Files
lib/beancount/parser.ex
defmodule Beancount.Parser do
@moduledoc """
Parse Beancount `.bean` text into typed directive structs.
The parser covers the full Beancount grammar: transactions with cost and
price annotations, balance assertions with tolerance, metadata, comments,
tags, links, and all standard directives including `pad`, `include`,
`option`, `query`, `plugin`, `pushtag`, and `poptag`.
Parse failures return `{:error, %Beancount.Parser.Error{}}` with line and
column information.
"""
alias Beancount.Parser.{Error, Grammar}
@doc """
Parse a directive list or `.bean` text.
Lists pass through unchanged; binaries are parsed.
## Examples
iex> {:ok, [open]} = Beancount.Parser.parse([Beancount.open(~D[2026-01-01], "Assets:Bank", ["USD"])])
iex> open.account
"Assets:Bank"
iex> {:ok, [open]} = Beancount.Parser.parse("2026-01-01 open Assets:Bank USD\\n")
iex> open.account
"Assets:Bank"
"""
@spec parse([Beancount.directive()] | binary()) ::
{:ok, [Beancount.directive()]} | {:error, Error.t()}
def parse(directives) when is_list(directives), do: {:ok, directives}
def parse(text) when is_binary(text), do: parse_text(text)
@doc """
Parse `.bean` text into a directive list.
## Examples
iex> {:ok, [open | _]} = Beancount.Parser.parse_text("2026-01-01 open Assets:Bank USD\\n")
iex> open.account
"Assets:Bank"
"""
@spec parse_text(binary()) :: {:ok, [Beancount.directive()]} | {:error, Error.t()}
def parse_text(text) when is_binary(text), do: Grammar.parse(text)
@doc """
Read and parse a `.bean` file from disk.
## Examples
path = Path.join(System.tmp_dir!(), "parser_example.bean")
File.write!(path, "2026-01-01 commodity USD\\n")
{:ok, [%Beancount.Directives.Commodity{currency: "USD"}]} =
Beancount.Parser.parse_file(path)
"""
@spec parse_file(Path.t()) :: {:ok, [Beancount.directive()]} | {:error, term()}
def parse_file(path) do
case File.read(path) do
{:ok, text} -> parse_text(text)
{:error, reason} -> {:error, reason}
end
end
@doc """
Parse `.bean` text, raising `Beancount.Parser.Error` on failure.
## Examples
iex> Beancount.Parser.parse!("2026-01-01 open Assets:Bank USD\\n")
...> |> hd()
...> |> Map.get(:account)
"Assets:Bank"
"""
@spec parse!(binary()) :: [Beancount.directive()]
def parse!(text) when is_binary(text) do
case parse_text(text) do
{:ok, directives} -> directives
{:error, %Error{} = error} -> raise error
end
end
end