ExLedger (ex_ledger v0.6.1)
Main module for ExLedger - a ledger-cli format parser and processor.
Provides utility functions for formatting dates and amounts in ledger format.
Summary
Functions
Calculates balances per account.
Calculates balances grouped by time period.
Balances postings by filling in a single missing amount.
Builds a budget report for periodic transactions.
Builds a new transaction entry based on a payee pattern.
Checks that all accounts used in transactions are declared.
Checks that all commodities used in transactions are declared.
Checks whether a ledger file parses successfully.
Checks a ledger file and returns an error tuple on failure.
Checks that all payees used in transactions are declared.
Checks whether a ledger string parses successfully.
Checks that all tags used in transactions are declared.
Expands include directives and returns the resolved ledger content.
Extracts account declarations from ledger content.
Extracts declared commodities from ledger content.
Extracts declared payees from ledger content.
Extracts declared tags from ledger content.
Returns the earliest regular transaction by date.
Forecasts balances after applying periodic budgets.
Formats account postings into a register report.
Formats an amount into ledger format with currency symbol and 2 decimal places.
Formats a numeric value for a specific currency.
Formats balance assertion failures as a human-readable error message.
Formats balances as a report string.
Formats a budget report table.
Formats a Date struct into ledger register format: YY-Mon-DD
Parses ledger content and returns formatted transactions.
Formats select rows into a tab-separated output.
Formats stats into a report string.
Formats a timeclock report.
Formats transactions into ledger-compatible output.
Returns postings for an account with running balance.
Returns the latest regular transaction by date.
Lists all account names, including declared accounts.
Lists all commodities referenced in postings.
Lists all payees in the transactions.
Lists all tags referenced in postings.
Parses an account declaration line.
Parses an amount string into a structured amount map.
Parses a date string into a Date struct.
Parses a ledger file.
Parses a ledger string with support for include directives and account declarations.
Parses a note line and returns the note tuple.
Parses a posting line.
Parses timeclock entries from input.
Parses a single transaction from a string.
Builds a register view of postings with running balances.
Resolves an account name or alias to its canonical name.
Resolves all account aliases inside transactions.
Selects postings matching a query string.
Builds summary statistics for the ledger.
Builds a timeclock report grouped by account.
Validates balance assertions in transactions.
Validates that a transaction is balanced.
Functions
Calculates balances per account.
Examples
iex> transactions = [%{postings: [
...> %{account: "Assets:Cash", amount: %{value: 10.0, currency: "$"}},
...> %{account: "Expenses:Food", amount: %{value: -10.0, currency: "$"}}
...> ]}]
iex> [first | _] = ExLedger.balance(transactions)["Assets:Cash"]
iex> first.amount
10.0
balance_by_period(transactions, group_by \\ "none", start_date \\ nil, end_date \\ nil, account_filter \\ nil)
Calculates balances grouped by time period.
Examples
iex> transactions = [%{date: ~D[2024-01-01], payee: "Open", postings: [%{account: "Assets:Cash", amount: %{value: 10.0, currency: "$"}}]}]
iex> result = ExLedger.balance_by_period(transactions, "monthly")
iex> Map.has_key?(result, "balances")
true
Balances postings by filling in a single missing amount.
Examples
iex> postings = [
...> %{account: "Assets:Cash", amount: %{value: 10.0, currency: "$"}},
...> %{account: "Equity:Opening", amount: nil}
...> ]
iex> [_filled, missing] = ExLedger.balance_postings(postings)
iex> missing.amount.value
-10.0
Builds a budget report for periodic transactions.
Examples
iex> transactions = [
...> %{kind: :periodic, period: "monthly", postings: [%{account: "Expenses:Rent", amount: %{value: 100.0, currency: "$"}}]},
...> %{kind: :regular, date: ~D[2024-01-10], postings: [%{account: "Expenses:Rent", amount: %{value: 40.0, currency: "$"}}]}
...> ]
iex> rows = ExLedger.budget_report(transactions, ~D[2024-01-15])
iex> Enum.any?(rows, &(&1.account == "Expenses:Rent"))
true
Builds a new transaction entry based on a payee pattern.
Examples
iex> transactions = [
...> %{date: ~D[2024-01-01], payee: "Coffee", postings: [
...> %{account: "Expenses:Food", amount: %{value: 4.0, currency: "$"}},
...> %{account: "Assets:Cash", amount: %{value: -4.0, currency: "$"}}
...> ]}
...> ]
iex> {:ok, output} = ExLedger.build_xact(transactions, ~D[2024-01-15], "Coffee")
iex> String.contains?(output, "Coffee")
true
Checks that all accounts used in transactions are declared.
Examples
iex> transactions = [%{postings: [%{account: "Assets:Cash", amount: %{value: 1.0, currency: "$"}}]}]
iex> ExLedger.check_accounts(transactions, %{"Assets:Cash" => :asset})
:ok
Checks that all commodities used in transactions are declared.
Examples
iex> transactions = [%{postings: [%{account: "Assets:Cash", amount: %{value: 1.0, currency: "$"}}]}]
iex> ExLedger.check_commodities(transactions, MapSet.new(["$"]))
:ok
Checks whether a ledger file parses successfully.
Examples
iex> is_boolean(ExLedger.check_file("path/to/file.ledger"))
true
Checks a ledger file and returns an error tuple on failure.
Examples
iex> result = ExLedger.check_file_with_error("path/to/file.ledger")
iex> match?({:ok, :valid}, result) or match?({:error, _}, result)
true
Checks that all payees used in transactions are declared.
Examples
iex> transactions = [%{payee: "Store", postings: []}]
iex> ExLedger.check_payees(transactions, MapSet.new(["Store"]))
:ok
Checks whether a ledger string parses successfully.
Examples
iex> input = "2024/01/01 Opening
Assets:Cash $10.00
Equity:Opening"
iex> ExLedger.check_string(input, ".")
true
Checks that all tags used in transactions are declared.
Examples
iex> transactions = [%{postings: [%{account: "Expenses:Food", amount: %{value: 5.0, currency: "$"}, tags: ["Food"]}]}]
iex> contents = "; :Food:"
iex> ExLedger.check_tags(transactions, contents, MapSet.new(["Food"]))
:ok
Expands include directives and returns the resolved ledger content.
Examples
iex> input = "2024/01/01 Opening
Assets:Cash $10.00
Equity:Opening"
iex> {:ok, expanded} = ExLedger.expand_includes(input, ".")
iex> expanded == input
true
Extracts account declarations from ledger content.
Examples
iex> input = "account Assets:Checking ; type:asset"
iex> ExLedger.extract_account_declarations(input)
%{"Assets:Checking" => :asset}
Extracts declared commodities from ledger content.
Examples
iex> input = "commodity $"
iex> ExLedger.extract_commodity_declarations(input) |> MapSet.member?("$")
true
Extracts declared payees from ledger content.
Examples
iex> input = "payee Coffee Shoppayee Grocery "
iex> ExLedger.extract_payee_declarations(input) |> MapSet.member?("Grocery")
true
Extracts declared tags from ledger content.
Examples
iex> input = "tag Travel"
iex> ExLedger.extract_tag_declarations(input) |> MapSet.member?("Travel")
true
Returns the earliest regular transaction by date.
Examples
iex> transactions = [%{date: ~D[2024-01-01], payee: "Open", postings: []}]
iex> ExLedger.first_transaction(transactions).payee
"Open"
Forecasts balances after applying periodic budgets.
Examples
iex> transactions = [
...> %{kind: :regular, date: ~D[2024-01-01], postings: [%{account: "Assets:Cash", amount: %{value: 10.0, currency: "$"}}]},
...> %{kind: :periodic, period: "monthly", postings: [%{account: "Assets:Cash", amount: %{value: 5.0, currency: "$"}}]}
...> ]
iex> [first | _] = ExLedger.forecast_balance(transactions, 2)["Assets:Cash"]
iex> first.amount
20.0
Formats account postings into a register report.
Examples
iex> postings = [%{date: ~D[2024-01-01], description: "Open", account: "Assets:Cash", amount: %{value: 10.0, currency: "$"}, balance: %{value: 10.0, currency: "$"}}]
iex> String.contains?(ExLedger.format_account_register(postings, "Assets:Cash"), "Assets:Cash")
true
Formats an amount into ledger format with currency symbol and 2 decimal places.
Examples
iex> ExLedger.format_amount(4.50)
" $4.50"
iex> ExLedger.format_amount(-4.50)
" -$4.50"
iex> ExLedger.format_amount(20.00)
" $20.00"
Formats a numeric value for a specific currency.
Examples
iex> ExLedger.format_amount_for_currency(10.5, "$")
"$10.50"
Formats balance assertion failures as a human-readable error message.
Examples
iex> failure = %{account: "Assets:Cash", expected: %{value: 100.0, currency: "CHF"}, actual: %{value: 50.0, currency: "CHF"}, transaction_date: ~D[2024-01-15], transaction_payee: "Check", source_file: nil, source_line: nil}
iex> String.contains?(ExLedger.format_assertion_failures([failure]), "Assets:Cash")
true
Formats balances as a report string.
Examples
iex> balances = %{"Assets:Checking" => [%{amount: -23.0, currency: "$"}], "Expenses:Utilities" => [%{amount: 23.0, currency: "$"}]}
iex> String.contains?(ExLedger.format_balance(balances), "Assets:Checking")
true
Formats a budget report table.
Examples
iex> rows = [%{account: "Expenses:Rent", currency: "$", actual: 40.0, budget: 100.0, remaining: 60.0}]
iex> String.contains?(ExLedger.format_budget_report(rows), "Expenses:Rent")
true
Formats a Date struct into ledger register format: YY-Mon-DD
Examples
iex> ExLedger.format_date(~D[2009-10-29])
"09-Oct-29"
iex> ExLedger.format_date(~D[2009-11-01])
"09-Nov-01"
Parses ledger content and returns formatted transactions.
Examples
iex> input = """
...> 2024/01/01 Opening
...> Assets:Cash $10.00
...> Equity:Opening
...> """
iex> ExLedger.format_ledger(input)
{:ok, "2024/01/01 Opening
Assets:Cash $10.00
Equity:Opening $-10.00"}
Formats select rows into a tab-separated output.
Examples
iex> ExLedger.format_select(["account"], [%{"account" => "Assets:Cash"}])
"Assets:Cash"
Formats stats into a report string.
Examples
iex> transactions = [%{date: ~D[2024-01-01], payee: "Store", postings: [%{account: "Assets:Cash", amount: %{value: -5.0, currency: "$"}}]}]
iex> stats = ExLedger.stats(transactions)
iex> is_binary(ExLedger.format_stats(stats))
true
Formats a timeclock report.
Examples
iex> report = %{"Work" => 1.5}
iex> String.contains?(ExLedger.format_timeclock_report(report), "Work")
true
Formats transactions into ledger-compatible output.
Examples
iex> input = "2024/01/01 Opening
Assets:Cash $10.00
Equity:Opening"
iex> {:ok, transactions} = ExLedger.parse_ledger(input)
iex> String.contains?(ExLedger.format_transactions(transactions), "Opening")
true
Returns postings for an account with running balance.
Examples
iex> transactions = [
...> %{date: ~D[2024-01-01], payee: "Open", postings: [
...> %{account: "Assets:Cash", amount: %{value: 10.0, currency: "$"}}
...> ]}
...> ]
iex> ExLedger.get_account_postings(transactions, "Assets:Cash") |> length()
1
Returns the latest regular transaction by date.
Examples
iex> transactions = [%{date: ~D[2024-01-01], payee: "Open", postings: []}]
iex> ExLedger.last_transaction(transactions).payee
"Open"
Lists all account names, including declared accounts.
Examples
iex> transactions = [%{postings: [%{account: "Assets:Cash", amount: %{value: 1.0, currency: "$"}}]}]
iex> ExLedger.list_accounts(transactions, %{"Assets:Bank" => :asset})
["Assets:Bank", "Assets:Cash"]
Lists all commodities referenced in postings.
Examples
iex> transactions = [%{postings: [%{account: "Assets:Cash", amount: %{value: 1.0, currency: "$"}}]}]
iex> ExLedger.list_commodities(transactions)
["$"]
Lists all payees in the transactions.
Examples
iex> transactions = [%{payee: "Store", postings: []}, %{payee: "Cafe", postings: []}]
iex> ExLedger.list_payees(transactions)
["Cafe", "Store"]
Lists all tags referenced in postings.
Examples
iex> transactions = [%{postings: [%{account: "Expenses:Food", amount: %{value: 5.0, currency: "$"}, tags: ["Food"]}]}]
iex> ExLedger.list_tags(transactions)
["Food"]
Parses an account declaration line.
Examples
iex> ExLedger.parse_account_declaration("account Assets:Checking ; type:asset")
{:ok, %{name: "Assets:Checking", type: :asset}}
Parses an amount string into a structured amount map.
Examples
iex> {:ok, amount} = ExLedger.parse_amount("$10.00")
iex> amount.value
10.0
Parses a date string into a Date struct.
Examples
iex> ExLedger.parse_date("2024/01/01")
{:ok, ~D[2024-01-01]}
@spec parse_file( String.t(), keyword() ) :: {:ok, ExLedger.LedgerParser.parse_result()} | {:error, term()}
Parses a ledger file.
Options
All options are passed to parse_ledger/2. Additionally:
:base_dirdefaults to the directory containing the file:source_filedefaults to the filename
Examples
{:ok, result} = ExLedger.parse_file("journal.ledger")
{:ok, result} = ExLedger.parse_file("journal.ledger", strict: true)
Parses a ledger string with support for include directives and account declarations.
Returns {:ok, result} where result is a map containing:
:transactions- List of parsed transactions:accounts- Map of account names to their types:prices- List of price directives (P DATE SYMBOL PRICE):commodities- Map of commodity symbols to their full definitions
Options
:base_dir- Base directory for resolving relative include paths (default: "."):source_file- Source file name for error reporting (default: nil)
Examples
iex> input = "2024/01/01 Opening
Assets:Cash $10.00
Equity:Opening"
iex> {:ok, result} = ExLedger.parse_ledger(input)
iex> length(result.transactions)
1
iex> input = "2024/01/01 Opening
Assets:Cash $10.00
Equity:Opening"
iex> {:ok, result} = ExLedger.parse_ledger(input, source_file: "journal.ledger")
iex> hd(result.transactions).source_file
"journal.ledger"
Parses a note line and returns the note tuple.
Examples
iex> ExLedger.parse_note(";:Food:")
{:ok, {:tag, "Food"}}
Parses a posting line.
Examples
iex> {:ok, posting} = ExLedger.parse_posting("Assets:Cash $10.00")
iex> posting.account
"Assets:Cash"
Parses timeclock entries from input.
Examples
iex> input = "i 2024/01/01 09:00:00 Assets:Work ClientO 2024/01/01 10:00:00 "
iex> ExLedger.parse_timeclock_entries(input) |> length()
1
Parses a single transaction from a string.
Examples
iex> input = "2024/01/01 Opening
Assets:Cash $10.00
Equity:Opening"
iex> {:ok, transaction} = ExLedger.parse_transaction(input)
iex> transaction.payee
"Opening"
Builds a register view of postings with running balances.
Examples
iex> transactions = [%{date: ~D[2024-01-01], payee: "Open", postings: [%{account: "Assets:Cash", amount: %{value: 10.0, currency: "$"}}]}]
iex> ExLedger.register(transactions) |> length()
1
Resolves an account name or alias to its canonical name.
Examples
iex> accounts = %{"Assets:Checking" => :asset, "checking" => "Assets:Checking"}
iex> ExLedger.resolve_account_name("checking", accounts)
"Assets:Checking"
Resolves all account aliases inside transactions.
Examples
iex> transactions = [%{postings: [%{account: "checking", amount: %{value: -10.0, currency: "$"}}]}]
iex> accounts = %{"Assets:Checking" => :asset, "checking" => "Assets:Checking"}
iex> ExLedger.resolve_transaction_aliases(transactions, accounts)
[%{postings: [%{account: "Assets:Checking", amount: %{value: -10.0, currency: "$"}}]}]
Selects postings matching a query string.
Examples
iex> transactions = [%{date: ~D[2024-01-01], payee: "Store", postings: [%{account: "Assets:Cash", amount: %{value: -5.0, currency: "$"}, tags: []}]}]
iex> ExLedger.select(transactions, "account from posts where account=~/Assets/")
{:ok, ["account"], [%{"account" => "Assets:Cash"}]}
Builds summary statistics for the ledger.
Examples
iex> transactions = [%{date: ~D[2024-01-01], payee: "Store", postings: [%{account: "Assets:Cash", amount: %{value: -5.0, currency: "$"}}]}]
iex> ExLedger.stats(transactions).unique_accounts
1
Builds a timeclock report grouped by account.
Examples
iex> entries = [%{account: "Work", duration_seconds: 3600}]
iex> ExLedger.timeclock_report(entries)["Work"]
1.0
Validates balance assertions in transactions.
Balance assertions use the syntax AMOUNT = ASSERTION_AMOUNT to verify
that the running balance of an account equals a specific value after the
posting is applied.
Options
:skip_assertions- iftrue, skips validation and returns:ok
Examples
iex> input = """
...> 2024/01/01 Opening
...> Assets:Cash 100 CHF
...> Equity:Opening -100 CHF
...>
...> 2024/01/15 Balance Check
...> Assets:Cash 0 = 100 CHF
...> Equity:Adjustments 0
...> """
iex> {:ok, transactions, _} = ExLedger.parse_ledger(input)
iex> ExLedger.validate_balance_assertions(transactions)
:ok
Validates that a transaction is balanced.
Examples
iex> transaction = %{postings: [
...> %{account: "Assets:Cash", amount: %{value: 10.0, currency: "$"}},
...> %{account: "Equity:Opening", amount: %{value: -10.0, currency: "$"}}
...> ]}
iex> ExLedger.validate_transaction(transaction)
:ok