aboutsummaryrefslogtreecommitdiff
path: root/curr
blob: 15ed195e5485ea7df7eebfde1f1962bcc011ddcc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
#!/usr/bin/env kscript
@file:DependsOnMaven("com.beust:klaxon:5.5")

import com.beust.klaxon.Klaxon
import java.net.URL
import kotlin.system.exitProcess

fun path(key: String) = "https://currencyapi.net/api/v1/rates?key=$key&base=USD"

data class CurrencyApi(
    var valid: Boolean = false,
    var base: String = "",
    var rates: Map<String, Double> = mapOf()
) {
    fun fromBase(amount: Double, currencyCode: String): Double? = rate(currencyCode)?.let { amount * it }
    fun toBase(amount: Double, currencyCode: String): Double? = rate(currencyCode)?.let { amount / it }

    fun convert(from: String, amount: Double, to: String): Double? =
        toBase(amount, from)?.let { fromBase(it, to) }


    fun rate(currencyCode: String): Double? =
        currencyCode.toUpperCase().let { return@let if (currencyCode == base) 1.0 else rates[currencyCode] }

}

val apiKey: String? = System.getenv("CURRENCY_API_KEY")

fun E(text: String): Int {
    System.err.println("curr: $text")
    return 1
}

fun E_UNKNOWN_USAGE(): Int =
    E("Unknown usage. See curr --help")

fun E_NO_API_KEY(): Int =
    E("Missing currency api key. Get one at https://currencyapi.net/ and set in \$CURRENCY_API_KEY")

fun E_APIERR(): Int =
    E("Failed to parse currency api response. This might be a rate limit.")

fun E_INVALID_AMOUNT(): Int =
    E("Invalid amount. See curr --help")

fun E_INVALID_CODE(code: String): Int =
    E("Invalid code: $code. See curr --codes")

fun getApi(key: String): CurrencyApi? = URL(path(key)).openStream().use { Klaxon().parse<CurrencyApi>(it) }

fun main(): Int {
    val args = args.toMutableList()
    if (args.size == 1 && (args[0] == "--help" || args[0] == "-h")) {
        System.err.println(
            """
                Usage: curr [-p/--plain] <from> <to> <amount>
                Usage: curr --codes
                Usage: curr --help
                
                --plain -p      Plain output mode.
                --codes -c      Display currency code list.
                --help -h       Display this help message.
                
                Set your https://currencyapi.net/ api key in ${'$'}CURRENCY_API_KEY
            """.trimIndent()
        )
        return 0
    }
    if (args.size == 1 && (args[0] == "--codes" || args[0] == "-c")) {
        val api = getApi(apiKey ?: return E_NO_API_KEY()) ?: return E_APIERR()
        println((api.rates.keys + api.base).joinToString("\n"))
        return 0
    }
    var isPlain = args.removeAll { it == "-p" || it == "--plain" }
    if (args.size == 3) {
        val amount: Double = try {
            args[2].toDouble()
        } catch (e: NumberFormatException) {
            return E_INVALID_AMOUNT()
        }
        val api = getApi(apiKey ?: return E_NO_API_KEY()) ?: return E_APIERR()
        if (api.rate(args[0]) == null) return E_INVALID_CODE(args[0])
        if (api.rate(args[1]) == null) return E_INVALID_CODE(args[1])
        val converted = api.convert(args[0], amount, args[1])
        if (isPlain) {
            println(converted)
        } else {
            println("$amount ${args[0]} to ${args[1]}: $converted")
        }
        return 0
    }
    return E_UNKNOWN_USAGE()
}

exitProcess(main())