From afef2e25ff1e81fd9c5dcc513eb52959a1e8da12 Mon Sep 17 00:00:00 2001 From: mat Date: Sat, 13 Apr 2024 00:52:28 -0500 Subject: [PATCH] add numbat as calculator engine --- Cargo.lock | 467 +++++++++++++++++++++++- Cargo.toml | 1 + README | 2 +- default-config.toml | 4 + src/engines/answer.rs | 3 +- src/engines/answer/{calc.rs => fend.rs} | 0 src/engines/answer/numbat.rs | 196 ++++++++++ src/engines/mod.rs | 9 +- src/web/assets/style.css | 4 + 9 files changed, 676 insertions(+), 10 deletions(-) rename src/engines/answer/{calc.rs => fend.rs} (100%) create mode 100644 src/engines/answer/numbat.rs diff --git a/Cargo.lock b/Cargo.lock index 4b1fada..3138434 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -82,6 +82,12 @@ dependencies = [ "libc", ] +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + [[package]] name = "async-compression" version = "0.4.6" @@ -129,6 +135,19 @@ dependencies = [ "syn 2.0.52", ] +[[package]] +name = "attohttpc" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "184f5e6cce583a9db6b6f8d772a42cfce5b78e7c3ef26118cec3ce4c8c14969b" +dependencies = [ + "http 1.0.0", + "log", + "rustls 0.22.3", + "url", + "webpki-roots 0.26.1", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -227,6 +246,15 @@ version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + [[package]] name = "brotli" version = "3.4.0" @@ -316,6 +344,16 @@ dependencies = [ "uncased", ] +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -332,6 +370,15 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + [[package]] name = "crc32fast" version = "1.4.0" @@ -341,6 +388,16 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + [[package]] name = "cssparser" version = "0.31.2" @@ -375,6 +432,37 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + [[package]] name = "dtoa" version = "1.0.9" @@ -396,6 +484,12 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a68a4904193147e0a8dec3314640e6db742afd5f6e634f428a6af230d9b3591" +[[package]] +name = "either" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" + [[package]] name = "encoding_rs" version = "0.8.33" @@ -560,6 +654,16 @@ dependencies = [ "byteorder", ] +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getopts" version = "0.2.21" @@ -630,6 +734,15 @@ version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "html-escape" version = "0.2.13" @@ -774,7 +887,7 @@ dependencies = [ "futures-util", "http 0.2.12", "hyper 0.14.28", - "rustls", + "rustls 0.21.10", "tokio", "tokio-rustls", ] @@ -850,6 +963,15 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.10" @@ -871,6 +993,16 @@ version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.4.2", + "libc", +] + [[package]] name = "lock_api" version = "0.4.11" @@ -940,6 +1072,7 @@ dependencies = [ "fend-core", "futures", "html-escape", + "numbat", "once_cell", "rand", "regex", @@ -986,6 +1119,48 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" +[[package]] +name = "num-bigint" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-format" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3" +dependencies = [ + "arrayvec", + "itoa", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.18" @@ -995,6 +1170,43 @@ dependencies = [ "autocfg", ] +[[package]] +name = "numbat" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a54ccd3e33759b0171c20adfa0d8b0d2709fe131067faabcfcc1c35621a30b4b" +dependencies = [ + "chrono", + "chrono-tz", + "codespan-reporting", + "heck", + "iana-time-zone", + "itertools", + "libc", + "num-format", + "num-integer", + "num-rational", + "num-traits", + "numbat-exchange-rates", + "pretty_dtoa", + "rust-embed", + "strsim", + "thiserror", + "unicode-ident", + "unicode-width", + "walkdir", +] + +[[package]] +name = "numbat-exchange-rates" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd1e3c3e4f9f22d0d7cdcb413f01194f6506a302a9029d95deedcd1c25df7718" +dependencies = [ + "attohttpc", + "quick-xml", +] + [[package]] name = "object" version = "0.32.2" @@ -1010,6 +1222,12 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "parking_lot" version = "0.12.1" @@ -1183,6 +1401,15 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" +[[package]] +name = "pretty_dtoa" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a239bcdfda2c685fda1add3b4695c06225f50075e3cfb5b954e91545587edff2" +dependencies = [ + "ryu_floating_decimal", +] + [[package]] name = "proc-macro2" version = "1.0.79" @@ -1192,6 +1419,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "quick-xml" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33" +dependencies = [ + "memchr", +] + [[package]] name = "quote" version = "1.0.35" @@ -1240,6 +1476,17 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "redox_users" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + [[package]] name = "regex" version = "1.10.3" @@ -1293,7 +1540,7 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls", + "rustls 0.21.10", "rustls-pemfile", "serde", "serde_json", @@ -1308,7 +1555,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots", + "webpki-roots 0.25.4", "winreg", ] @@ -1327,6 +1574,41 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rust-embed" +version = "8.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb78f46d0066053d16d4ca7b898e9343bc3530f71c61d5ad84cd404ada068745" +dependencies = [ + "rust-embed-impl", + "rust-embed-utils", + "walkdir", +] + +[[package]] +name = "rust-embed-impl" +version = "8.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b91ac2a3c6c0520a3fb3dd89321177c3c692937c4eb21893378219da10c44fc8" +dependencies = [ + "proc-macro2", + "quote", + "rust-embed-utils", + "shellexpand", + "syn 2.0.52", + "walkdir", +] + +[[package]] +name = "rust-embed-utils" +version = "8.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86f69089032567ffff4eada41c573fc43ff466c7db7c5688b2e7969584345581" +dependencies = [ + "sha2", + "walkdir", +] + [[package]] name = "rustc-demangle" version = "0.1.23" @@ -1341,10 +1623,24 @@ checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" dependencies = [ "log", "ring", - "rustls-webpki", + "rustls-webpki 0.101.7", "sct", ] +[[package]] +name = "rustls" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99008d7ad0bbbea527ec27bddbc0e432c5b87d8175178cee68d2eec9c4a1813c" +dependencies = [ + "log", + "ring", + "rustls-pki-types", + "rustls-webpki 0.102.2", + "subtle", + "zeroize", +] + [[package]] name = "rustls-pemfile" version = "1.0.4" @@ -1354,6 +1650,12 @@ dependencies = [ "base64 0.21.7", ] +[[package]] +name = "rustls-pki-types" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecd36cc4259e3e4514335c4a138c6b43171a8d61d8f5c9348f9fc7529416f247" + [[package]] name = "rustls-webpki" version = "0.101.7" @@ -1364,6 +1666,17 @@ dependencies = [ "untrusted", ] +[[package]] +name = "rustls-webpki" +version = "0.102.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faaa0a62740bedb9b2ef5afa303da42764c012f743917351dc9a237ea1663610" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.14" @@ -1376,6 +1689,21 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" +[[package]] +name = "ryu_floating_decimal" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "700de91d5fd6091442d00fdd9ee790af6d4f0f480562b0f5a1e8f59e90aafe73" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -1498,6 +1826,26 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shellexpand" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da03fa3b94cc19e3ebfc88c4229c49d8f08cdbd1228870a45f0ffdf84988e14b" +dependencies = [ + "dirs", +] + [[package]] name = "siphasher" version = "0.3.11" @@ -1567,6 +1915,18 @@ dependencies = [ "quote", ] +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + [[package]] name = "syn" version = "1.0.109" @@ -1627,6 +1987,35 @@ dependencies = [ "utf-8", ] +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", +] + [[package]] name = "tinyvec" version = "1.6.0" @@ -1675,7 +2064,7 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ - "rustls", + "rustls 0.21.10", "tokio", ] @@ -1790,6 +2179,12 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + [[package]] name = "uncased" version = "0.9.10" @@ -1820,6 +2215,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-segmentation" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" + [[package]] name = "unicode-width" version = "0.1.11" @@ -1867,6 +2268,16 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.1" @@ -1964,6 +2375,46 @@ version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" +[[package]] +name = "webpki-roots" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3de34ae270483955a94f4b21bdaaeb83d508bb84a01435f393818edb0012009" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-core" version = "0.52.0" @@ -2143,3 +2594,9 @@ dependencies = [ "quote", "syn 2.0.52", ] + +[[package]] +name = "zeroize" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" diff --git a/Cargo.toml b/Cargo.toml index bdf02c7..a5abf8e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ eyre = "0.6.12" fend-core = "1.4.5" futures = "0.3.30" html-escape = "0.2.13" +numbat = "1.11.0" once_cell = "1.19.0" rand = "0.8.5" regex = "1.10.3" diff --git a/README b/README index 637ddfc..bc3586c 100644 --- a/README +++ b/README @@ -14,7 +14,7 @@ build it with `cargo b -r`, the resulting binary will be at `target/release/metasearch2`. the config.toml file is created in your current working directory on the first -run of metasearch2. alternatively, you can copy the example-config.toml in the +run of metasearch2. alternatively, you can copy the default-config.toml in the repo and rename it to config.toml. the default port is port 28019. diff --git a/default-config.toml b/default-config.toml index acfd3c6..899cbfa 100644 --- a/default-config.toml +++ b/default-config.toml @@ -5,6 +5,10 @@ google = { weight = 1.05 } bing = { weight = 1.0 } brave = { weight = 1.25 } +# calculators (give them a high weight so they're always the first thing in autocomplete) +numbat = { weight = 10 } +fend = { enabled = false, weight = 10 } + [engines.marginalia] args = { profile = "corpo", js = "default", adtech = "default" } weight = 0.15 diff --git a/src/engines/answer.rs b/src/engines/answer.rs index b4b998b..3d60649 100644 --- a/src/engines/answer.rs +++ b/src/engines/answer.rs @@ -1,6 +1,7 @@ -pub mod calc; pub mod dictionary; +pub mod fend; pub mod ip; +pub mod numbat; pub mod thesaurus; pub mod timezone; pub mod useragent; diff --git a/src/engines/answer/calc.rs b/src/engines/answer/fend.rs similarity index 100% rename from src/engines/answer/calc.rs rename to src/engines/answer/fend.rs diff --git a/src/engines/answer/numbat.rs b/src/engines/answer/numbat.rs new file mode 100644 index 0000000..4b1da02 --- /dev/null +++ b/src/engines/answer/numbat.rs @@ -0,0 +1,196 @@ +use std::collections::HashSet; + +use fend_core::SpanKind; +use numbat::{ + markup::{FormatType, FormattedString, Markup}, + pretty_print::PrettyPrint, + resolver::CodeSource, + InterpreterResult, InterpreterSettings, Statement, +}; +use once_cell::sync::Lazy; + +use crate::engines::EngineResponse; + +pub fn request(query: &str) -> EngineResponse { + let query = clean_query(query); + + let Some(NumbatResponse { + query_html, + result_html, + }) = evaluate(&query) + else { + return EngineResponse::new(); + }; + + EngineResponse::answer_html(format!( + r#"

{query_html} =

+

{result_html}

"# + )) +} + +pub fn request_autocomplete(query: &str) -> Vec { + let mut results = Vec::new(); + + let query = clean_query(query); + + if let Some(result) = evaluate_for_autocomplete(&query) { + results.push(format!("= {result}")); + } + + results +} + +fn clean_query(query: &str) -> String { + query.strip_suffix('=').unwrap_or(query).trim().to_string() +} + +#[derive(Debug)] +pub struct Span { + pub text: String, + pub kind: SpanKind, +} + +fn is_potential_request(query: &str) -> bool { + // allow these short constants, they're fine + if matches!(query.to_lowercase().as_str(), "pi" | "e" | "c") { + return true; + } + + // at least 3 characters + if query.len() < 3 { + return false; + } + + // must have numbers + if !query.chars().any(|c| c.is_numeric()) { + return false; + } + + // if it starts and ends with quotes then the person was just searching in + // quotes and didn't mean to evaluate a string + if query.starts_with('"') + && query.ends_with('"') + && query.chars().filter(|c| *c == '"').count() == 2 + { + return false; + } + + true +} + +fn interpret(query: &str) -> Option<(Statement, InterpreterResult)> { + if !is_potential_request(query) { + return None; + } + + let mut ctx = NUMBAT_CTX.clone(); + + let (statements, res) = match ctx.interpret_with_settings( + &mut InterpreterSettings { + print_fn: Box::new(move |_: &Markup| {}), + }, + query, + CodeSource::Text, + ) { + Ok(r) => r, + Err(_) => { + return None; + } + }; + + Some((statements.into_iter().last()?, res)) +} + +fn evaluate_for_autocomplete(query: &str) -> Option { + let (_statements, res) = interpret(query)?; + + let res_markup = match res { + InterpreterResult::Value(val) => val.pretty_print(), + InterpreterResult::Continue => return None, + }; + + Some(res_markup.to_string()) +} + +pub struct NumbatResponse { + pub query_html: String, + pub result_html: String, +} + +fn evaluate(query: &str) -> Option { + let (statement, res) = interpret(query)?; + + let res_markup = match res { + InterpreterResult::Value(val) => val.pretty_print(), + InterpreterResult::Continue => return None, + }; + + let statement_markup = statement.pretty_print(); + let query_html = markup_to_html(statement_markup); + let result_html = markup_to_html(res_markup); + + Some(NumbatResponse { + query_html, + result_html, + }) +} + +fn markup_to_html(markup: Markup) -> String { + let mut html = String::new(); + for FormattedString(_output_type, format_type, content) in markup.0 { + let class = match format_type { + FormatType::Value => "answer-calc-constant", + FormatType::String => "answer-calc-string", + FormatType::Identifier => "answer-calc-func", + _ => "", + }; + if class.is_empty() { + html.push_str(&html_escape::encode_safe(&content)); + } else { + html.push_str(&format!( + r#"{content}"#, + content = html_escape::encode_safe(&content) + )); + } + } + html +} + +pub static NUMBAT_CTX: Lazy = Lazy::new(|| { + let mut ctx = numbat::Context::new(numbat::module_importer::BuiltinModuleImporter {}); + let _ = ctx.interpret("use prelude", CodeSource::Internal); + let _ = ctx.interpret("use units::currencies", CodeSource::Internal); + + ctx.load_currency_module_on_demand(true); + + // a few hardcoded aliases + // (the lowercase alias code won't work for these because they have prefixes) + for (alias, canonical) in &[ + ("kb", "kB"), + ("mb", "MB"), + ("gb", "GB"), + ("tb", "TB"), + ("pb", "PB"), + ] { + let _ = ctx.interpret(&format!("unit {alias} = {canonical}"), CodeSource::Internal); + } + + // lowercase aliases (so for example usd and USD are the same unit) + + let mut unit_names = HashSet::new(); + for names in ctx.unit_names() { + unit_names.extend(names.iter().map(|name| name.to_owned())); + } + + for name in &unit_names { + // taken_unit_names.insert(alias_name); + let name_lower = name.to_lowercase(); + // add every lowercase aliases for every unit as long as that alias isn't + // already taken + if !unit_names.contains(&name_lower) { + let _ = ctx.interpret(&format!("unit {name_lower} = {name}"), CodeSource::Internal); + } + } + + ctx +}); diff --git a/src/engines/mod.rs b/src/engines/mod.rs index fba4a2e..5d6d21c 100644 --- a/src/engines/mod.rs +++ b/src/engines/mod.rs @@ -33,7 +33,8 @@ engines! { // answer Useragent = "useragent", Ip = "ip", - Calc = "calc", + Fend = "fend", + Numbat = "numbat", Wikipedia = "wikipedia", Dictionary = "dictionary", Thesaurus = "thesaurus", @@ -53,7 +54,8 @@ engine_requests! { // answer Useragent => answer::useragent::request, None, Ip => answer::ip::request, None, - Calc => answer::calc::request, None, + Fend => answer::fend::request, None, + Numbat => answer::numbat::request, None, Wikipedia => answer::wikipedia::request, parse_response, Dictionary => answer::dictionary::request, parse_response, Thesaurus => answer::thesaurus::request, parse_response, @@ -62,7 +64,8 @@ engine_requests! { engine_autocomplete_requests! { Google => search::google::request_autocomplete, parse_autocomplete_response, - Calc => answer::calc::request_autocomplete, None, + Fend => answer::fend::request_autocomplete, None, + Numbat => answer::numbat::request_autocomplete, None, } engine_postsearch_requests! { diff --git a/src/web/assets/style.css b/src/web/assets/style.css index 2bdd5ba..fce2b68 100644 --- a/src/web/assets/style.css +++ b/src/web/assets/style.css @@ -26,6 +26,7 @@ --syntax-special: #e6b673; --syntax-constant: #d2a6ff; --syntax-comment: #acb6bf8c; + --syntax-func: #ffb454; } html { @@ -237,6 +238,9 @@ h1 { .answer-calc-special { color: var(--syntax-special); } +.answer-calc-func { + color: var(--syntax-func); +} .answer-dictionary-word, .answer-thesaurus-word {