AtlatestRepositorysigil-ledger
sigil-ledger / tree / src / ledgerreport.sgl
1
;;; (ledger report) - hledger CLI Report Generation2
;;;3
;;; Run hledger CLI commands and parse their output for reports.4
;;; Uses `--output-format json` where available for structured data.5
;;;6
;;; ## Basic Usage7
;;;8
;;; ```scheme9
;;; (import (ledger report))10
;;;11
;;; ;; Get account balances12
;;; (hledger-balance "main.journal" "assets")13
;;;14
;;; ;; Get transaction register15
;;; (hledger-register "main.journal" "expenses" begin: "2026-01")16
;;;17
;;; ;; Income statement18
;;; (hledger-income-statement "main.journal" period: "monthly")19
;;;20
;;; ;; Run any hledger command21
;;; (hledger-command "accounts" file: "main.journal")22
;;; ```24
(define-library (ledger report)25
(import (sigil string)26
(sigil io)27
(sigil process)28
(sigil json))29
(export30
hledger-available?31
hledger-command32
hledger-balance33
hledger-register34
hledger-income-statement35
hledger-print-json)37
(begin39
;;; Check if hledger is available on PATH.40
(define (hledger-available?)41
(command-exists? "hledger"))43
(define (ensure-hledger!)44
(unless (hledger-available?)45
(error "hledger is not installed or not on PATH")))47
;;; Run an arbitrary hledger command and return its output as a string.48
;;;49
;;; The `file:` keyword specifies the journal file.50
;;; Additional arguments are passed through to hledger.51
;;;52
;;; ```scheme53
;;; (hledger-command "accounts" file: "main.journal")54
;;; ; => "assets:checking\nassets:savings\n..."55
;;;56
;;; (hledger-command "bal" file: "main.journal" args: '("assets" "--tree"))57
;;; ; => balance report as text58
;;; ```59
(define (hledger-command command (keys: (file #f) (args '())))60
(ensure-hledger!)61
(let ((cmd-args (if file62
(cons command (cons "-f" (cons file args)))63
(cons command args))))64
(apply process-output->string "hledger" cmd-args)))66
;; Internal: run a report command with parsed query/option args67
(define (run-report command file rest)68
(ensure-hledger!)69
(let* ((query-and-opts (parse-report-args rest))70
(query (car query-and-opts))71
(opts (cdr query-and-opts))72
(args (build-report-args command file query opts)))73
(apply process-output->string "hledger" args)))75
;;; Run `hledger bal` and return the output.76
;;;77
;;; ```scheme78
;;; (hledger-balance "main.journal")79
;;; (hledger-balance "main.journal" "assets")80
;;; (hledger-balance "main.journal" "expenses"81
;;; begin: "2026-01" end: "2026-04" depth: 2 tree: #t)82
;;; ```83
(define (hledger-balance file . rest)84
(run-report "bal" file rest))86
;;; Run `hledger reg` and return the output.87
;;;88
;;; ```scheme89
;;; (hledger-register "main.journal" "expenses:food")90
;;; (hledger-register "main.journal" begin: "2026-03")91
;;; ```92
(define (hledger-register file . rest)93
(run-report "reg" file rest))95
;;; Run `hledger is` (income statement) and return the output.96
;;;97
;;; ```scheme98
;;; (hledger-income-statement "main.journal")99
;;; (hledger-income-statement "main.journal" period: "monthly")100
;;; ```101
(define (hledger-income-statement file . rest)102
(run-report "is" file rest))104
;;; Run `hledger print -O json` and return parsed JSON.105
;;;106
;;; Returns the hledger JSON representation of transactions,107
;;; parsed into Sigil dicts/arrays.108
;;;109
;;; ```scheme110
;;; (define txns (hledger-print-json "main.journal"))111
;;; ```112
(define (hledger-print-json file . rest)113
(ensure-hledger!)114
(let* ((query-and-opts (parse-report-args rest))115
(query (car query-and-opts))116
(opts (cdr query-and-opts))117
(args (build-report-args "print" file query118
(cons (cons output-format: "json") opts))))119
(let ((output (apply process-output->string "hledger" args)))120
(if (string-empty? (string-trim output))121
#[]122
(json-decode output)))))124
;; Parse variadic report arguments into (query . opts) pair125
(define (parse-report-args args)126
(let loop ((rest args) (query '()) (opts '()))127
(if (null? rest)128
(cons (reverse query) (reverse opts))129
(let ((arg (car rest)))130
(cond131
((keyword? arg)132
(if (null? (cdr rest))133
(cons (reverse query) (reverse opts))134
(loop (cddr rest) query135
(cons (cons arg (cadr rest)) opts))))136
((string? arg)137
(loop (cdr rest) (cons arg query) opts))138
(else139
(loop (cdr rest) query opts)))))))141
;; Build hledger argument list from parsed report options142
(define (build-report-args command file query opts)143
(let ((base (list command "-f" file)))144
(let ((with-opts (fold-right145
(lambda (opt acc)146
(let ((key (car opt))147
(val (cdr opt)))148
(cond149
((eq? key begin:)150
(cons "-b" (cons val acc)))151
((eq? key end:)152
(cons "-e" (cons val acc)))153
((eq? key period:)154
(cons "-p" (cons val acc)))155
((eq? key depth:)156
(cons "--depth" (cons (if (number? val) (number->string val) val) acc)))157
((eq? key tree:)158
(if val (cons "--tree" acc) acc))159
((eq? key monthly:)160
(if val (cons "-M" acc) acc))161
((eq? key quarterly:)162
(if val (cons "-Q" acc) acc))163
((eq? key yearly:)164
(if val (cons "-Y" acc) acc))165
((eq? key output-format:)166
(cons "-O" (cons val acc)))167
((eq? key cost:)168
(if val (cons "-B" acc) acc))169
((eq? key market:)170
(if val (cons "-V" acc) acc))171
((eq? key exchange:)172
(cons "-X" (cons val acc)))173
(else acc))))174
'()175
opts)))176
(append base with-opts query))))))