Skip to content

Xm798/beanfmt

Repository files navigation

Beanfmt

中文文档

A fast beancount file formatter with CJK double-width character support and smart date-based sorting.

Features

  • Column-aligned currencies and costs with CJK-aware display width
  • Thousands separator normalization (add, remove, or keep)
  • Brace spacing control for cost annotations
  • Smart date-based sorting (asc/desc) with time metadata intra-day ordering, timeless entry positioning, and directive-type sort barriers
  • Recursive formatting of included files (with glob support)
  • Multi-platform: CLI, Python library, WASM module, and VSCode extension

Installation

CLI

cargo install beanfmt

Python

pip install beanfmt

VSCode Extension

Install from the VSCode Marketplace, or from the command line:

code --install-extension cyrus-x.beanfmt

Configuration

Beanfmt supports TOML configuration files with a three-layer merge priority (low → high):

  1. Built-in defaults
  2. Global config: $XDG_CONFIG_HOME/beanfmt/config.toml (defaults to ~/.config/beanfmt/config.toml)
  3. Project config: .beanfmt.toml or beanfmt.toml (searched upward from the current directory)
  4. CLI arguments (highest priority)

Example .beanfmt.toml:

indent = 2
currency_column = 60
cost_column = 65
inline_comment_column = 0   # column to align inline comments to; 0 disables
thousands = "add"
spaces_in_braces = true
fixed_cjk_width = true
sort = "asc"    # "asc", "desc", "off", or true/false
sort_timeless = "keep"   # "begin", "end", or "keep"
sort_exclude = ["open", "close"]  # excluded types act as sort barriers

All fields are optional. Unspecified fields inherit from the next lower priority layer. Use --no-config to skip all configuration file loading. See beanfmt.toml for a full reference with comments.

Config file support varies by target:

Target Global config Project config Notes
CLI Auto Auto --no-config to disable
Python No Opt-in via config=True load_project_config() for manual loading
VSCode No (user settings serve this role) Auto (within workspace) Explicit settings override config file

Usage

CLI

# Format from stdin
cat ledger.beancount | beanfmt

# Format a file (print to stdout)
beanfmt ledger.beancount

# Format in-place
beanfmt -w ledger.beancount

# Recursively format all included files in-place
beanfmt --recursive -w ledger.beancount

# Custom alignment columns
beanfmt --currency-column 60 --cost-column 65 ledger.beancount

# Add thousands separators and sort by date
beanfmt --thousands add --sort ledger.beancount

Options

Flag Default Description
--indent <N> 4 Number of spaces for indentation
--currency-column <N> 70 Target column for currency alignment
--cost-column <N> 75 Target column for cost/price alignment
--inline-comment-column <N> 0 Target column to align inline comments (;) to; 0 disables alignment
--thousands <MODE> keep Thousands separator: add, remove, or keep
--spaces-in-braces / --no-spaces-in-braces off Add spaces inside cost braces { ... }
--fixed-cjk-width / --no-fixed-cjk-width on CJK double-width alignment
--sort [MODE] / --no-sort off Sort entries by date: asc (default if bare --sort), desc, off
--sort-timeless <POS> begin Where to place timeless entries within a day: begin, end
--sort-exclude <TYPES> (none) Comma-separated directive types to exclude from sorting; excluded directives act as sort barriers. Values: transaction, balance, open, close, price, pad, note, document, event, custom, query, commodity
--recursive off Follow and format included files
-w, --write off Write output back to file (in-place)
--no-config off Skip loading configuration files

Python

import beanfmt

# Format a string
output = beanfmt.format(source, currency_column=60, sort=True)

# Format a file
output = beanfmt.format_file("ledger.beancount")

# Format with project config (.beanfmt.toml auto-discovery)
output = beanfmt.format_file("ledger.beancount", config=True)

# Format with a specific config file
output = beanfmt.format_file("ledger.beancount", config="/path/to/.beanfmt.toml")

# Config + kwargs override (kwargs win over config file values)
output = beanfmt.format_file("ledger.beancount", config=True, indent=8)

# Load and inspect project config
opts = beanfmt.load_project_config("/path/to/project/")
opts = beanfmt.parse_config('indent = 2\ncurrency_column = 80\n')

# Reusable options
opts = beanfmt.Options(currency_column=60, thousands_separator="add")
output = beanfmt.format(source, options=opts)

# Recursive formatting — returns list of (path, content) tuples
results = beanfmt.format_recursive("ledger.beancount", config=True)

The config parameter on format_file and format_recursive accepts None (default, no config loading), True (auto-discover .beanfmt.toml from the file's directory upward), False (same as None), or a string path to a specific config file. Individual kwargs always override config file values. The config and options parameters are mutually exclusive.

WASM

import { format, format_default } from "beanfmt";

// Format with default options
const output = format_default(source);

// Format with full options
const output = format(source, 4, 70, 75, "keep", false, true, "off", "begin", undefined);

VSCode

Install the extension, then configure in settings.json:

"[beancount]": {
    "editor.defaultFormatter": "cyrus-x.beanfmt",
    "editor.formatOnSave": true
}

The extension automatically reads .beanfmt.toml or beanfmt.toml from the workspace (searching from the file's directory up to the workspace root). Explicit VSCode settings override config file values; unset settings fall back to the config file, then to built-in defaults.

Available settings:

Setting Default Description
beanfmt.indent 4 Number of spaces for indentation
beanfmt.currencyColumn 70 Currency alignment column
beanfmt.costColumn 75 Cost/price alignment column
beanfmt.thousandsSeparator "keep" "add", "remove", or "keep"
beanfmt.spacesInBraces false Spaces inside cost braces
beanfmt.fixedCJKWidth true CJK double-width alignment
beanfmt.sort "off" Sort entries by date: "asc", "desc", "off"
beanfmt.sortTimeless "keep" Timeless entry position within a day: "begin", "end", "keep"
beanfmt.sortExclude [] Directive types to exclude from sorting (act as sort barriers)

Development

Beanfmt is a Rust workspace with four build targets — library, CLI, Python (PyO3/maturin), and WASM (wasm-bindgen) — plus a VSCode extension.

Prerequisites

You only need the extra tools for the targets you actually build.

Building

Common tasks are wrapped in just recipes:

just                # List all recipes
just build          # Build the library
just build-cli      # Build the CLI binary
just build-python   # Build the Python extension (uv + maturin)
just build-wasm     # Build the WASM module
just build-vscode   # Build the VSCode extension (WASM + TypeScript)

just test           # Run all tests
just clippy         # Run clippy lints
just fmt            # Format code
just check          # fmt-check + clippy + test

Or build individual targets directly with Cargo:

cargo install --path .                 # CLI
maturin develop --features python      # Python (requires maturin)

Note: the python and wasm features are mutually exclusive.

License

MIT

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors