getquote

Getquote implementation for ledger(1)
Log | Files | Refs | README

commit a5210c970883d82bb242eb46de73f22055e36f66
Author: Jacob R. Edwards <jacob@jacobedwards.org>
Date:   Thu, 22 Jan 2026 16:26:26 -0600

Add functional getquote implementation and docs

Diffstat:
AMakefile | 24++++++++++++++++++++++++
AREADME.md | 39+++++++++++++++++++++++++++++++++++++++
Aconfig.mk | 4++++
Agetquote | 132+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Agetquote.1 | 81+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Agetquote.5 | 42++++++++++++++++++++++++++++++++++++++++++
Agetquoteaction | 80+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Agetquoteaction.1 | 45+++++++++++++++++++++++++++++++++++++++++++++
8 files changed, 447 insertions(+), 0 deletions(-)

diff --git a/Makefile b/Makefile @@ -0,0 +1,24 @@ +# getquote Makefile + +names = getquote getquoteaction +manpages = getquote.1 getquote.5 getquoteaction.1 + +install: +.for m in ${manpages} + install -o root -g bin -m 0644 $m ${MANPREFIX}/man${m:E}/$m + makewhatis -d ${MANPREFIX} ${MANPREFIX}/man${m:E}/$m +.endfor +.for n in ${names} + install -o root -g bin -m 0755 $n ${PREFIX}/bin/$n +.endfor + +uninstall: +.for n in ${names} + rm -f ${PREFIX}/bin/$n +.endfor +.for m in ${manpages} + rm -f ${MANPREFIX}/man${m:E}/$m + makewhatis -u ${MANPREFIX} ${MANPREFIX}/man${m:E}/$m +.endfor + +.PHONY: install uninstall diff --git a/README.md b/README.md @@ -0,0 +1,39 @@ +# Getquote Implementation for Ledger(1) + +This is a simple getquote implementation for ledger(1) using the +[Twelve Data](https://twelvedata.com/) [API](https://twelvedata.com/docs). + +## Requirements + +- Standard UNIX utilities +- jq utility to process JSON +- curl utility to make API requests +- /bin/sh with -o pipefail option +- Twelve Data API Key ([free](https://twelvedata.com/pricing) up + to 8 requests per minute and 800/day) + +## Installation + +Edit config.mk to match your system (getquote is installed under +the /usr/local namespace by default). + +Once configured, install with BSD make: + + $ make install + +## Configuration + +The getquote configuration directory is located under `$XDG_CONFIG_HOME`, +or `$HOME/.local/config` if it's undefined. + +An API key is expected to be set in the `TWELVE_DATA_API_KEY` +environment variable or stored in the configuration directory under +"keys/api.twelvedata.com". + +It's recommended to make a "symbols" file in the configuration +directory to define which symbols are tickers to get a quote for: +By default symbols are treated as a currency, and only by failing +to get an exchange rate will they be treated as tickers. See +getquote(5) for the syntax. + +For further instructions see the included manuals. diff --git a/config.mk b/config.mk @@ -0,0 +1,4 @@ +# getquote config.mk + +PREFIX = /usr/local +MANPREFIX = ${PREFIX}/man diff --git a/getquote b/getquote @@ -0,0 +1,132 @@ +#!/bin/sh +# +# Getquote implementation for ledger(1) +# +# Copyright (c) 2026 Jacob R. Edwards <jacob@jacobedwards.org> +# +# Permission to use, copy, modify, and distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +twelve_query() ( + domain=api.twelvedata.com + printenv TWELVE_DATA_API_KEY >/dev/null || { + # Let shell do error handling + TWELVE_DATA_API_KEY="$(cat "$rcdir"/keys/$domain)" + } + + func="${1:?function}" + shift + IFS='&' + debug curl -Ss "https://$domain/$func?$*&apikey=${TWELVE_DATA_API_KEY:?key}" | + jq -r 'if (.status == "error") then error("API Error: \(.code) (\(.message))") end' +) + +twelve_quote() { + twelve_query price symbol="$1" | + jq -r ' + if (.price == null) then + error("Unable to get price") + else + [ '"\"$1\""', .price, "USD" ] | join(" ") + end' +} + +twelve_exchange() { + to="${2:-USD}" + twelve_quote "${1:?symbol}/$to" | + awk -vfr="$1" -vto="$to" '{ $1 = fr; $NF = to; print }' +} + +quote() { + twelve_quote "$@" +} + +exchange() { + twelve_exchange "$@" +} + +err() { + echo "$@" 1>&2 + return 1 +} + +ledger_prices() ( + IFS=' +' + while read symbol price symbol2 err + do + test -n "$err" && + err 'Extra input' + expr "$price" : '^[0-9][0-9]*\(\.[0-9][0-9]*\)\{0,1\}$' >/dev/null || + err "'$price': Invalid price ${price:-(empty)}" + debug date "+%F $symbol $price $symbol2" + done +) + +fifo() { + true & + f=/tmp/fifo$$.$! + mkfifo "$f" + echo "$f" +} + +action() { + : "${1:?symbol}" + test -f "$rcdir"/symbols || + echo "$1" + debug getquoteaction "$1" < "$rcdir"/symbols +} + +getquote() ( + action "${1:?symbol}" | { + read -r symbol action + case $action in + ('') + exchange "$symbol" ${2:+"$2"} 2>/dev/null || { + test "$#" -gt 1 -a "$2" '!=' '$' -a "$2" '!=' 'USD' && + err "$2: Expected '$' or 'USD'" + quote "$symbol" + } ;; + (quote|exchange) $action "$symbol" ${2:+"$2"} + esac + } | awk -vs="$1" '{ $1 = s; print }' +) + +main() { + set -e + + case "$#${1%%[!-]*}" in + ([12]) getquote "$@" ;; + (*) err 'usage: getquote symbol [exchange_to]' ;; + esac | ledger_prices +} + +if printenv GETQUOTE_DEBUG >/dev/null +then + debug() ( + f="$(fifo)" + trap 'rm -f "$f"' 0 + echo ":! $@" 1>&2 + sed 's/^/:/' < "$f" >&2 & + "$@" | tee "$f" + ) + set -x +else + debug() { + "$@" + } +fi + +rcdir="${XDG_CONFIG_HOME:-"$HOME"/.local/config}"/getquote + +set -o pipefail +printenv GETQUOTE_INCLUDE >/dev/null || main "$@" diff --git a/getquote.1 b/getquote.1 @@ -0,0 +1,81 @@ +./" +./" Copyright (c) 2026 Jacob R. Edwards <jacob@jacobedwards.org> +./" +./" Permission to use, copy, modify, and distribute this software for any +./" purpose with or without fee is hereby granted, provided that the above +./" copyright notice and this permission notice appear in all copies. +./" +./" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIE +./" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +./" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +./" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +./" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +./" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +./" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +./" +.Dd January 22, 2026 +.Dt GETQUOTE 1 +.Os +.Sh NAME +.Nm getquote +.Nd Fetch commodity prices for ledger(1) +.Sh SYNOPSIS +.Nm +.Ar symbol +.Op Ar exchange_to +.Sh DESCRIPTION +.Nm +is a getquote implementation for +.Xr ledger 1 +using the TwelveData API. +.Pp +By default, +.Nm +first treats +.Ar symbol +as a currency and attempts to fetch the exchange rate of it and +.Ar exchange_to +(or USD if not given). If that fails, it will then treat +.Ar symbol +as a ticker and retrieve a quote. +.Pp +The default behavior can be altered using the configuration file described in +.Xr getquote 5 . +.Sh ENVIRONMENT +.Bl -tag -width Ds +.It Ev TWELVE_DATA_API_KEY +API key for Twelve Data. +.It Ev XDG_CONFIG_HOME +Location of the +.Pa getquote +configuration directory. (Defaults to +.Pa ~/.local/config ) +.Sh FILES +.Bl -tag -width Ds +.It Pa $XDG_DATA_HOME/getquote/keys/api.twelvedata.com +API key to use if +.Ev TWELVE_DATA_API_KEY +isn't set. +.El +.Bl -tag -width Ds +.It Pa $XDG_DATA_HOME/getquote/symbols +Symbols file to be processed by +.Xr getquoteaction . +.El +.Sh EXIT STATUS +.Ex -std +.Sh EXAMPLES +.Pp +Get the price of Vanguard's Total Stock Market ETF (VTI) in a format +suitable for ledger: +.Pp +.Dl $ getquote VTI +.Pp +Get the XMR/EUR exchange rate: +.Pp +.Dl $ getquote XMR EUR +.Sh SEE ALSO +.Xr ledger 1 , +.Xr getquoteaction 1 +.Sh AUTHORS +.An Jacob R. Edwards Aq Mt jacob@jacobedwards.org diff --git a/getquote.5 b/getquote.5 @@ -0,0 +1,42 @@ +.Dd January 22, 2026 +.Dt GETQUOTE 5 +.Os +.Sh NAME +.Nm getquote +.Nd getquote configuration file +.Sh DESCRIPTION +The +.Nm +configuration file defines symbol aliases and treatment for +.Xr getquote 1 . +.Pp +The file contains tab delimited symbol-value pairs, one per line. +The value can either be an action or an alias. An action is one of +quote or exchange, an alias starts with equals (`=`) followed by +the alias. Aliases must not be circular definitions. +.Pp +.Ed +.Sh FILES +.Bl -tag -width Ds +.It Pa $XDG_CONFIG_HOME/getquote/config +Configuration file for +.Xr getquote 1 . +.El +.Sh EXAMPLES +An example configuration file is shown below: +.Bd -offset indent -literal +# Quote by default +* quote + +# Use exchange action on the following symbols +BTC exchange +XMR exchange +EUR exchange + +# Define Bitcoin as an alias for BTC +Bitcoin =BTC +.Ed +.Sh SEE ALSO +.Xr getquote 1 +.Sh AUTHORS +.An Jacob R. Edwards Aq Mt jacob@jacobedwards.org diff --git a/getquoteaction b/getquoteaction @@ -0,0 +1,80 @@ +#!/usr/bin/awk -f +# +# Helper script for getquote, ledger-compatible price fetcher +# +# Copyright (c) 2026 Jacob R. Edwards <jacob@jacobedwards.org> +# +# Permission to use, copy, modify, and distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +function resolve_symbol(s, _start) { + if (!_start) + _start = s + else if (s = _start) + err(s ": Circular definition") + + if (!(s in k)) + return s + if (k[s] ~ /^=/) + return resolve_symbol(substr(k[s], 2), _start) + return s +} + +function resolve_action(s) { + if (!(s in k)) { + if ("*" in k) + s = "*" + else + return "" + } + if (k[s] !~ /^=/) + return k[s] + return "" +} + +function err(msg) { + print msg > "/dev/stderr" + exit (status = 1) +} + +BEGIN { + if (ARGC-- <= 1) + err("usage: getquoteaction symbol [file ...]") + s = ARGV[1] + delete ARGV[1] +} + +# Remove comments +{ sub("(^|[ ]+)#.*", "", $0) } + +# Remove empty lines (sometimes created by removing comments) +/^$/ { next } + +# Error on obviously invalid lines +NF != 2 { err(NR ": Must have two lines") } + +{ + if ($1 == "*" && $2 ~ /^=/) + err("error: Wildcard can only be used for actions") + if ($2 !~ /^=/ && $2 != "quote" && $2 != "exchange") + err("error: " $2 ": Only quote and exchange actions supported") + k[$1] = $2 +} + +END { + if (status) + exit status + s = resolve_symbol(s) + a = resolve_action(s) + OFS = " " + print s, a +} diff --git a/getquoteaction.1 b/getquoteaction.1 @@ -0,0 +1,45 @@ +./" +./" Copyright (c) 2026 Jacob R. Edwards <jacob@jacobedwards.org> +./" +./" Permission to use, copy, modify, and distribute this software for any +./" purpose with or without fee is hereby granted, provided that the above +./" copyright notice and this permission notice appear in all copies. +./" +./" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIE +./" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +./" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +./" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +./" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +./" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +./" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +./" +.Dd January 22, 2026 +.Dt GETQUOTEACTION 1 +.Os +.Sh NAME +.Nm getquoteaction +.Nd Process getquote(1) symbols file +.Sh SYNOPSIS +.Nm +.Ar symbol +.Op Ar +.Sh DESCRIPTION +.Nm +processes +the +.Xr getquote 1 +configuration file described in +.Xr getquote 5 . +.Sh EXIT STATUS +.Ex -std +.Sh EXAMPLES +.Pp +Get the resolved symbol and action for VTI using the +.Pa symbols +file: +.Pp +.Dl $ getquoteaction VTI < symbols +.Sh SEE ALSO +.Xr getquote 1 +.Sh AUTHORS +.An Jacob R. Edwards Aq Mt jacob@jacobedwards.org