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:
| A | Makefile | | | 24 | ++++++++++++++++++++++++ |
| A | README.md | | | 39 | +++++++++++++++++++++++++++++++++++++++ |
| A | config.mk | | | 4 | ++++ |
| A | getquote | | | 132 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | getquote.1 | | | 81 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | getquote.5 | | | 42 | ++++++++++++++++++++++++++++++++++++++++++ |
| A | getquoteaction | | | 80 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | getquoteaction.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