144 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable file
		
	
	
	
	
			
		
		
	
	
			144 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable file
		
	
	
	
	
#!/usr/bin/env python3
 | 
						|
 | 
						|
"""
 | 
						|
A shell script that converts one currency to another.
 | 
						|
 | 
						|
Usage: shekel [-rh] AMOUNT FROM TO [TO ...]
 | 
						|
 | 
						|
FROM and TO are currency codes.
 | 
						|
 | 
						|
  Examples:
 | 
						|
    shekel 200 USD CAD
 | 
						|
"""
 | 
						|
 | 
						|
import argparse
 | 
						|
import sys
 | 
						|
import json
 | 
						|
from urllib import request
 | 
						|
from datetime import datetime
 | 
						|
from pathlib import Path
 | 
						|
 | 
						|
 | 
						|
API_KEY = "92a7ccb3b6038a7151fc15e99f2de83d"  # please use your own!
 | 
						|
API_URL = "http://apilayer.net/api/live?access_key={}¤cies={},{}"
 | 
						|
 | 
						|
DEFAULT_CODES = ('CAD', 'USD')
 | 
						|
CACHE_FILE = "/tmp/shekel-hist"
 | 
						|
CACHE_TTL = 3600
 | 
						|
 | 
						|
class CurrencyCache():
 | 
						|
    def __init__(self, path=CACHE_FILE, ttl=CACHE_TTL):
 | 
						|
        self.path = Path(path)
 | 
						|
        self.ttl = ttl
 | 
						|
 | 
						|
        cache = {}
 | 
						|
        if self.path.exists():
 | 
						|
            cache = json.loads(self.path.read_text())
 | 
						|
        self.cache = cache
 | 
						|
 | 
						|
    def __enter__(self):
 | 
						|
        return self
 | 
						|
 | 
						|
    def __exit__(self, *args):
 | 
						|
        self.path.write_text(json.dumps(self.cache))
 | 
						|
 | 
						|
    def clear(self):
 | 
						|
        db = self.cache['_DB']
 | 
						|
        self.cache.clear()
 | 
						|
        self.cache['_DB'] = db
 | 
						|
 | 
						|
    def store(self, from_code, to_code, rate):
 | 
						|
        self._set("-".join([from_code, to_code]), rate)
 | 
						|
        self._set("-".join([to_code, from_code]), 1 / rate)
 | 
						|
 | 
						|
    def get(self, from_code, to_code):
 | 
						|
        key = "-".join([from_code, to_code])
 | 
						|
        if key in self.cache:
 | 
						|
            return self.cache[key]['value']
 | 
						|
        return None
 | 
						|
 | 
						|
    def _set(self, key, value):
 | 
						|
        self.cache[key] = {'time': datetime.now().timestamp(), 'value': value}
 | 
						|
 | 
						|
 | 
						|
def _request_currency_rate(url=API_URL, *args):
 | 
						|
    """Fetch currency rates from remote API."""
 | 
						|
    real_url = url.format(*map(request.quote, map(str, args)))
 | 
						|
    data = json.loads(request.urlopen(real_url).read().decode('utf-8'))
 | 
						|
    if not data['success']:
 | 
						|
        raise Exception(data['error']['info'])
 | 
						|
    return data["quotes"]
 | 
						|
 | 
						|
 | 
						|
def format_currency(curr):
 | 
						|
    """Format a currency entry for the FZF prompt."""
 | 
						|
    return "{}  {}".format(curr['id'], curr['currencyName'])
 | 
						|
 | 
						|
 | 
						|
def currency_rate(from_code, to_code):
 | 
						|
    """
 | 
						|
    Retrieves the exchange rate between two currencies.
 | 
						|
 | 
						|
    :param from_code: an ISO 4217 currency code of the source currency
 | 
						|
    :param to_code: an ISO 4217 currency code of the destination currency
 | 
						|
    :returns: an exchange rate (a float)
 | 
						|
    """
 | 
						|
    if from_code == to_code:
 | 
						|
        return 1.0
 | 
						|
    elif not from_code or not to_code:
 | 
						|
        return None
 | 
						|
    rates = _request_currency_rate(API_URL, API_KEY, from_code, to_code)
 | 
						|
    try:
 | 
						|
        return (1 / rates["USD" + from_code]) * rates["USD" + to_code]
 | 
						|
    except KeyError as e:
 | 
						|
        print("Could not find currency named {}".format(from_code))
 | 
						|
        return None
 | 
						|
 | 
						|
 | 
						|
def each_currency_rate(from_code, to_codes, refresh=False):
 | 
						|
    """
 | 
						|
    Returns a cached generator for each exchange rate.
 | 
						|
 | 
						|
    :param from_code: the ISO 4217 currency code of the source currency
 | 
						|
    :param to_codes: a list of ISO 4217 currency codes to convert to
 | 
						|
    :yields: a tuple containg (TO_CODE, EXCHANGE_RATE)
 | 
						|
    """
 | 
						|
    with CurrencyCache(path=CACHE_FILE, ttl=CACHE_TTL) as cache:
 | 
						|
        if refresh:
 | 
						|
            cache.clear()
 | 
						|
        for code in to_codes:
 | 
						|
            rate = cache.get(from_code, code)
 | 
						|
            if not rate:
 | 
						|
                rate = currency_rate(from_code, code)
 | 
						|
                if rate:
 | 
						|
                    cache.store(from_code, code, rate)
 | 
						|
            if not rate:
 | 
						|
                yield (None, None)
 | 
						|
            else:
 | 
						|
                yield (code, rate)
 | 
						|
 | 
						|
 | 
						|
def main():
 | 
						|
    opts = argparse.ArgumentParser(
 | 
						|
        description="Convert one amount of currency into another, or multiple.")
 | 
						|
    opts.add_argument('amount', type=float, help="The amount to convert")
 | 
						|
    opts.add_argument('src', metavar='from', type=str,
 | 
						|
                      help="Currency to convert from")
 | 
						|
    opts.add_argument('dest', metavar='to', type=str, nargs='+',
 | 
						|
                      help="Currency(ies) to convert to")
 | 
						|
    opts.add_argument('-r', '--refresh', action='store_true',
 | 
						|
                      help="Refresh the cache")
 | 
						|
    args = opts.parse_args()
 | 
						|
 | 
						|
    try:
 | 
						|
        for code, rate in each_currency_rate(args.src, args.dest, refresh=args.refresh):
 | 
						|
            if code:
 | 
						|
                print("{:.4f} {}".format(rate * args.amount, code))
 | 
						|
    except KeyboardInterrupt:
 | 
						|
        print("Aborted!")
 | 
						|
        sys.exit(1)
 | 
						|
 | 
						|
 | 
						|
if __name__ == '__main__':
 | 
						|
    main()
 |