getquote (2946B)
1 #!/bin/sh 2 # 3 # Getquote implementation for ledger(1) 4 # 5 # Copyright (c) 2026 Jacob R. Edwards <jacob@jacobedwards.org> 6 # 7 # Permission to use, copy, modify, and distribute this software for any 8 # purpose with or without fee is hereby granted, provided that the above 9 # copyright notice and this permission notice appear in all copies. 10 # 11 # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 12 # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 13 # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 14 # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 15 # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 16 # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 17 # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 18 19 twelve_query() ( 20 domain=api.twelvedata.com 21 printenv TWELVE_DATA_API_KEY >/dev/null || { 22 # Let shell do error handling 23 TWELVE_DATA_API_KEY="$(cat "$rcdir"/keys/$domain)" 24 } 25 26 func="${1:?function}" 27 shift 28 IFS='&' 29 debug curl -Ss "https://$domain/$func?$*&apikey=${TWELVE_DATA_API_KEY:?key}" | 30 jq -r 'if (.status == "error") then error("API Error: \(.code) (\(.message))") end' 31 ) 32 33 twelve_quote() { 34 twelve_query price symbol="$1" | 35 jq -r ' 36 if (.price == null) then 37 error("Unable to get price") 38 else 39 [ '"\"$1\""', .price, "USD" ] | join(" ") 40 end' 41 } 42 43 twelve_exchange() { 44 to="${2:-USD}" 45 twelve_quote "${1:?symbol}/$to" | 46 awk -vfr="$1" -vto="$to" '{ $1 = fr; $NF = to; print }' 47 } 48 49 quote() { 50 twelve_quote "$@" 51 } 52 53 exchange() { 54 twelve_exchange "$@" 55 } 56 57 err() { 58 echo "$@" 1>&2 59 return 1 60 } 61 62 ledger_prices() ( 63 IFS=' 64 ' 65 while read symbol price symbol2 err 66 do 67 test -n "$err" && 68 err 'Extra input' 69 expr "$price" : '^[0-9][0-9]*\(\.[0-9][0-9]*\)\{0,1\}$' >/dev/null || 70 err "'$price': Invalid price ${price:-(empty)}" 71 debug date "+%F $symbol $price $symbol2" 72 done 73 ) 74 75 fifo() { 76 true & 77 f=/tmp/fifo$$.$! 78 mkfifo "$f" 79 echo "$f" 80 } 81 82 action() { 83 : "${1:?symbol}" 84 test -f "$rcdir"/symbols || 85 echo "$1" 86 debug getquoteaction "$1" < "$rcdir"/symbols 87 } 88 89 getquote() ( 90 action "${1:?symbol}" | { 91 read -r symbol action 92 case $action in 93 ('') 94 exchange "$symbol" ${2:+"$2"} 2>/dev/null || { 95 test "$#" -gt 1 -a "$2" '!=' '$' -a "$2" '!=' 'USD' && 96 err "$2: Expected '$' or 'USD'" 97 quote "$symbol" 98 } ;; 99 (quote|exchange) $action "$symbol" ${2:+"$2"} 100 esac 101 } | awk -vs="$1" '{ $1 = s; print }' 102 ) 103 104 main() { 105 set -e 106 107 case "$#${1%%[!-]*}" in 108 ([12]) getquote "$@" ;; 109 (*) err 'usage: getquote symbol [exchange_to]' ;; 110 esac | ledger_prices 111 } 112 113 if printenv GETQUOTE_DEBUG >/dev/null 114 then 115 debug() ( 116 f="$(fifo)" 117 trap 'rm -f "$f"' 0 118 echo ":! $@" 1>&2 119 sed 's/^/:/' < "$f" >&2 & 120 "$@" | tee "$f" 121 ) 122 set -x 123 else 124 debug() { 125 "$@" 126 } 127 fi 128 129 rcdir="${XDG_CONFIG_HOME:-"$HOME"/.local/config}"/getquote 130 131 set -o pipefail 132 printenv GETQUOTE_INCLUDE >/dev/null || main "$@"