API usability improvements (#184)

* Added helper functions `load_definitions()` and `load_currency()` on
Context to deduplicate code every frontend had to write out
* Added ToSpans impl for `Result<QueryReply, QueryError>` to avoid a
pointless looking match statement
* Added more examples to the API docs
* Fleshed out the API docs a bit more
This commit is contained in:
Tiffany Bennett 2024-06-02 15:59:35 -07:00 committed by GitHub
parent df8b961edf
commit db3f8d060b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 495 additions and 98 deletions

3
Cargo.lock generated
View file

@ -1756,7 +1756,6 @@ dependencies = [
"rustyline",
"serde",
"serde_derive",
"serde_json",
"similar-asserts",
"tempfile",
"tiny_http",
@ -1796,7 +1795,6 @@ dependencies = [
"rink-sandbox",
"serde",
"serde_derive",
"serde_json",
"tokio",
"toml 0.8.12",
"ubyte",
@ -1813,7 +1811,6 @@ dependencies = [
"serde",
"serde-wasm-bindgen",
"serde_derive",
"serde_json",
"wasm-bindgen",
"wasm-bindgen-test",
"wee_alloc",

View file

@ -3,6 +3,6 @@ members = ["core", "rink-js", "sandbox", "cli", "irc"]
default-members = ["cli"]
[profile.release]
strip = true
strip = "debuginfo"
opt-level = "z"
lto = true

View file

@ -19,7 +19,6 @@ clap = "4.5"
dirs = "4"
curl = "0.4.46"
chrono = { version = "0.4.19", default-features = false }
serde_json = "1"
toml = "0.5"
serde_derive = "1"
serde = { version = "1", default-features = false }
@ -35,6 +34,7 @@ ubyte = { version = "0.10.3", features = ["serde"] }
[dependencies.rink-core]
version = "0.8"
path = "../core"
features = [ "serde_json" ]
[dependencies.rink-sandbox]
version = "0.6"

View file

@ -10,7 +10,7 @@ use nu_ansi_term::{Color, Style};
use rink_core::output::fmt::FmtToken;
use rink_core::parsing::datetime;
use rink_core::Context;
use rink_core::{ast, loader::gnu_units, CURRENCY_FILE, DATES_FILE, DEFAULT_FILE};
use rink_core::{loader::gnu_units, CURRENCY_FILE, DATES_FILE, DEFAULT_FILE};
use serde_derive::{Deserialize, Serialize};
use std::env;
use std::ffi::OsString;
@ -279,7 +279,7 @@ pub(crate) fn force_refresh_currency(config: &Currency) -> Result<String> {
))
}
fn load_live_currency(config: &Currency) -> Result<ast::Defs> {
fn load_live_currency(config: &Currency) -> Result<String> {
let duration = if config.fetch_on_startup {
Some(config.cache_duration)
} else {
@ -287,7 +287,7 @@ fn load_live_currency(config: &Currency) -> Result<ast::Defs> {
};
let file = cached("currency.json", &config.endpoint, duration, config.timeout)?;
let contents = file_to_string(file)?;
serde_json::from_str(&contents).wrap_err("Invalid JSON")
Ok(contents)
}
fn try_load_currency(config: &Currency, ctx: &mut Context, search_path: &[PathBuf]) -> Result<()> {
@ -295,15 +295,9 @@ fn try_load_currency(config: &Currency, ctx: &mut Context, search_path: &[PathBu
.into_iter()
.next()
.unwrap();
let mut base_defs = gnu_units::parse_str(&base);
let mut live_defs = load_live_currency(config)?;
let mut defs = vec![];
defs.append(&mut base_defs.defs);
defs.append(&mut live_defs.defs);
ctx.load(ast::Defs { defs }).map_err(|err| eyre!(err))?;
let live_defs = load_live_currency(config)?;
ctx.load_currency(&live_defs, &base)
.map_err(|err| eyre!("{err}"))?;
Ok(())
}
@ -506,7 +500,9 @@ mod tests {
Duration::from_millis(5),
);
let result = result.expect_err("this should always fail");
assert_eq!(result.to_string(), "[28] Timeout was reached (Operation timed out after 5 milliseconds with 0 bytes received)");
let result = result.to_string();
assert!(result.starts_with("[28] Timeout was reached (Operation timed out after "));
assert!(result.ends_with(" milliseconds with 0 bytes received)"));
thread_handle.join().unwrap();
drop(server);
}
@ -547,7 +543,7 @@ mod tests {
let thread_handle = std::thread::spawn(move || {
let request = server2.recv().expect("the request should not fail");
assert_eq!(request.url(), "/data/currency.json");
let mut data = b"{}".to_owned();
let mut data = include_bytes!("../../core/tests/currency.snapshot.json").to_owned();
let cursor = std::io::Cursor::new(&mut data);
request
.respond(Response::new(StatusCode(200), vec![], cursor, None, None))
@ -563,7 +559,10 @@ mod tests {
result
.read_to_string(&mut string)
.expect("the file should exist");
assert_eq!(string, "{}");
assert_eq!(
string,
include_str!("../../core/tests/currency.snapshot.json")
);
thread_handle.join().unwrap();
drop(server);
}
@ -584,7 +583,7 @@ mod tests {
let thread_handle = std::thread::spawn(move || {
let request = server2.recv().expect("the request should not fail");
assert_eq!(request.url(), "/data/currency.json");
let mut data = b"{}".to_owned();
let mut data = include_bytes!("../../core/tests/currency.snapshot.json").to_owned();
let cursor = std::io::Cursor::new(&mut data);
request
.respond(Response::new(StatusCode(200), vec![], cursor, None, None))
@ -592,7 +591,7 @@ mod tests {
});
let result = super::force_refresh_currency(&config);
let result = result.expect("this should succeed");
assert!(result.starts_with("Fetched 2 byte currency file after "));
assert!(result.starts_with("Fetched 6599 byte currency file after "));
thread_handle.join().unwrap();
drop(server);
}

View file

@ -12,6 +12,7 @@ edition = "2018"
[features]
default = ["chrono-humanize"]
bundle-files = []
serde_json = ["dep:serde_json"]
[dependencies]
num-bigint = { version = "0.4", features = ["serde"] }
@ -22,6 +23,7 @@ strsim = "0.10.0"
chrono-tz = { version = "0.5.2", default-features = false }
chrono-humanize = { version = "0.1.2", optional = true }
serde = { version = "1", features = ["rc"], default-features = false }
serde_json = { version = "1", optional = true }
serde_derive = "1"
indexmap = "2"

View file

@ -2,6 +2,8 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
//! Abstract syntax tree for rink's query language.
use crate::output::Digits;
use crate::types::Numeric;
use chrono_tz::Tz;

View file

@ -2,6 +2,9 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
//! Provides direct access to commands that can be run in rink, like
//! [search()] and [factorize()].
mod factorize;
mod search;

View file

@ -3,27 +3,50 @@
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
use crate::{
loader::gnu_units,
output::{QueryError, QueryReply},
parsing::text_query,
Context,
};
/// The default `definitions.units` file that contains all of the base
/// units, units, prefixes, quantities, and substances.
///
/// This will be Some if the `bundle-files` feature is enabled,
/// otherwise it will be None.
#[cfg(feature = "bundle-files")]
pub static DEFAULT_FILE: Option<&'static str> = Some(include_str!("../definitions.units"));
#[cfg(not(feature = "bundle-files"))]
pub static DEFAULT_FILE: Option<&'static str> = None;
/// The default `datepatterns.txt` file that contains patterns that rink
/// uses for parsing datetimes.
///
/// This will be Some if the `bundle-files` feature is enabled,
/// otherwise it will be None.
#[cfg(feature = "bundle-files")]
pub static DATES_FILE: Option<&'static str> = Some(include_str!("../datepatterns.txt"));
#[cfg(not(feature = "bundle-files"))]
pub static DATES_FILE: Option<&'static str> = None;
/// The default `currenty.units` file that contains currency information
/// that changes rarely. It's used together with live currency data
/// to add currency support to rink.
///
/// This will be Some if the `bundle-files` feature is enabled,
/// otherwise it will be None.
#[cfg(feature = "bundle-files")]
pub static CURRENCY_FILE: Option<&'static str> = Some(include_str!("../currency.units"));
#[cfg(not(feature = "bundle-files"))]
pub static CURRENCY_FILE: Option<&'static str> = None;
/// Helper function that updates the `now` to the current time, parses
/// the query, evaluates it, and updates the `previous_result` field
/// that's used to return the previous query when using `ans`.
///
/// ## Panics
///
/// Panics on platforms where fetching the current time is not possible,
/// such as WASM.
pub fn eval(ctx: &mut Context, line: &str) -> Result<QueryReply, QueryError> {
ctx.update_time();
let mut iter = text_query::TokenIterator::new(line.trim()).peekable();
@ -39,7 +62,7 @@ pub fn eval(ctx: &mut Context, line: &str) -> Result<QueryReply, QueryError> {
Ok(res)
}
/// A version of eval() that converts results and errors into strings.
/// A version of eval() that converts results and errors into plain-text strings.
pub fn one_line(ctx: &mut Context, line: &str) -> Result<String, String> {
eval(ctx, line)
.as_ref()
@ -54,20 +77,16 @@ pub fn simple_context() -> Result<Context, String> {
let message = "bundle-files feature not enabled, cannot create simple context.";
let units = DEFAULT_FILE.ok_or(message.to_owned())?;
let mut iter = gnu_units::TokenIterator::new(&*units).peekable();
let units = gnu_units::parse(&mut iter);
let dates = DATES_FILE.ok_or(message.to_owned())?;
let dates = crate::parsing::datetime::parse_datefile(dates);
let mut ctx = Context::new();
ctx.load(units)?;
ctx.load_dates(dates);
ctx.load_definitions(units)?;
ctx.load_date_file(dates);
Ok(ctx)
}
// Returns `env!("CARGO_PKG_VERSION")`, a string in `x.y.z` format.
/// Returns `env!("CARGO_PKG_VERSION")`, a string in `x.y.z` format.
pub fn version() -> &'static str {
env!("CARGO_PKG_VERSION")
}

View file

@ -2,32 +2,100 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
/*! The primary interface of this library is meant to expose a very
simple command-reply model for frontends, and to allow gradual
addition of more advanced functionality. For now, only basic
functionality exists.
Using Rink as a library for uses other than simple unit conversion
tools is not currently well supported, and if you wish to do so,
please make issues for any problems you have.
There are currently a number of hardcoded `println!`s and `unwrap()`s
because most of this code was written in a day without much thought
towards making it into a library.
To use the library, check how the CLI tool does it. To get additional
features like currency and BTC you'll need to fetch those files
yourself and add them into the Context.
## Example
```rust
use rink_core::*;
let mut ctx = simple_context().unwrap();
println!("{}", one_line(&mut ctx, "kWh / year -> W").unwrap());
```
*/
//! Rink is a small language for calculations and unit conversions.
//! It is available as a CLI, a web interface, an IRC client.
//! `rink_core` is the library that the frontends use.
//!
//! The API is designed to let you start simple and then progressively
//! add more features.
//!
//! Rink is designed to be used interactively, with the user typing a
//! query and then seeing the result. It's common for this to be a
//! session, so the previous query can be referenced with `ans`, and
//! some form of history is available using up/down arrows.
//!
//! Using rink for purposes other than this is out of scope, but may be
//! possible anyway depending on what you're trying to do.
//!
//! ## Example
//!
//! Minimal implementation.
//!
//! ```rust
//! # fn main() -> Result<(), String> {
//! // Create a context. This is expensive (30+ ms), so do it once at
//! // startup and keep it around.
//! let mut ctx = rink_core::simple_context()?;
//! // `one_line` is a helper function that parses a query, evaluates
//! // it, then converts the result into a plain text string.
//! println!("{}", rink_core::one_line(&mut ctx, "kWh / year -> W")?);
//! // Prints: approx. 0.1140795 watt (power)
//! # Ok(())
//! # }
//! ```
//!
//! ## Currency fetching
//!
//! The first step to adding currency fetching is to add code to
//! download this file:
//!
//! <https://rinkcalc.app/data/currency.json>
//!
//! You can use any http library, such as `curl` or `reqwest`. The file
//! updates about once an hour. Please make sure to set an accurate
//! user-agent when fetching it.
//!
//! ```rust
//! # fn fetch_from_http(_url: &str) -> String { include_str!("../tests/currency.snapshot.json").to_owned() }
//! # fn main() -> Result<(), String> {
//! # let mut ctx = rink_core::simple_context()?;
//! let live_data: String = fetch_from_http("https://rinkcalc.app/data/currency.json");
//! // CURRENCY_FILE requires that the `bundle-features` feature is
//! // enabled. Otherwise, you'll need to install and load this file
//! // yourself.
//! let base_defs = rink_core::CURRENCY_FILE.expect("bundle-files feature to be enabled");
//! ctx.load_currency(&live_data, base_defs)?;
//!
//! println!("{}", rink_core::one_line(&mut ctx, "USD").unwrap());
//! // Definition: USD = (1 / 1.0843) EUR = approx. 922.2539 millieuro (money; EUR).
//! // Sourced from European Central Bank. Current as of 2024-05-27.
//! # Ok(())
//! # }
//! ```
//!
//! ## Markup
//!
//! To add color highlighting, or other forms of rich markup such as
//! links or superscripts, you can use [eval] instead of [one_line] and
//! then call [output::fmt::TokenFmt::to_spans] on the result. This
//! returns a tree of spans, each of which has a formatting hint
//! attached to it. See [output::fmt::Span] and [output::fmt::FmtToken].
//!
//! ```rust
//! use rink_core::output::fmt::{TokenFmt, Span, FmtToken};
//! # let mut ctx = rink_core::simple_context().unwrap();
//! let result = rink_core::eval(&mut ctx, "meter");
//! // converts both the Ok and Err cases to spans
//! let spans = result.to_spans();
//!
//! fn write_xml(out: &mut String, spans: &[Span]) {
//! for span in spans {
//! match span {
//! Span::Content {text, token: FmtToken::DocString} => {
//! out.push_str("<i>");
//! out.push_str(&text);
//! out.push_str("</i>");
//! }
//! Span::Content {text, ..} => out.push_str(&text),
//! Span::Child(child) => write_xml(out, &child.to_spans()),
//! }
//! }
//! }
//!
//! let mut out = String::new();
//! write_xml(&mut out, &spans);
//! println!("{}", out);
//! ```
// False positives, or make code harder to understand.
#![allow(clippy::cognitive_complexity)]

View file

@ -65,6 +65,11 @@ impl Context {
self.registry.datepatterns.append(&mut dates)
}
pub fn load_date_file(&mut self, file: &str) {
let dates = crate::parsing::datetime::parse_datefile(file);
self.load_dates(dates)
}
/// Given a unit name, returns its value if it exists. Supports SI
/// prefixes, plurals, bare dimensions like length, and quantities.
pub fn lookup(&self, name: &str) -> Option<Number> {
@ -196,6 +201,33 @@ impl Context {
}
}
// Takes the string definition.units file, parses it, and loads it.
pub fn load_definitions(&mut self, content: &str) -> Result<(), String> {
let defs = crate::loader::gnu_units::parse_str(&content);
self.load(defs)
}
// Takes the currency JSON and the currency.units file, parses both,
// and loads them.
//
// The latest live_data string can be obtained by making a web
// request to: https://rinkcalc.app/data/currency.json
//
// The currency.unit file exists at rink_core::CURRENCY_FILE when
// the `bundle-files` feature is enabled. Otherwise, it needs to be
// installed on the system somewhere and loaded.
#[cfg(feature = "serde_json")]
pub fn load_currency(&mut self, live_data: &str, currency_units: &str) -> Result<(), String> {
let mut base_defs = crate::loader::gnu_units::parse_str(currency_units);
let mut live_defs: Vec<crate::ast::DefEntry> =
serde_json::from_str(&live_data).map_err(|err| format!("{}", err))?;
let mut defs = vec![];
defs.append(&mut base_defs.defs);
defs.append(&mut live_defs);
self.load(crate::ast::Defs { defs })
}
/// Evaluates an expression to compute its value, *excluding* `->`
/// conversions.
pub fn eval(&self, expr: &Expr) -> Result<Value, QueryError> {

View file

@ -2,6 +2,8 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
//! [Context], [Registry], and the [definitions file parser][gnu_units]
mod context;
pub mod gnu_units;
mod load;

View file

@ -2,6 +2,8 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
//! Return types from evaluating a query, starting from [QueryReply] and [QueryError]
mod doc_string;
pub mod fmt;
mod number_parts;

View file

@ -640,3 +640,12 @@ impl<'a> TokenFmt<'a> for NotFoundError {
tokens
}
}
impl<'a> TokenFmt<'a> for Result<QueryReply, QueryError> {
fn to_spans(&'a self) -> Vec<Span<'a>> {
match self {
Ok(res) => res.to_spans(),
Err(err) => err.to_spans(),
}
}
}

View file

@ -2,6 +2,9 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
//! Parsers for [rink's query language][text_query], [molecular
//! formulas][formula::substance_from_formula], and [datetimes][datetime].
pub mod datetime;
pub mod formula;
pub mod text_query;

View file

@ -2,6 +2,8 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
//! [Value] stores all types that rink is capable of processing
mod eval;
mod substance;
mod value;

View file

@ -2,6 +2,8 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
//! Basic types that rink uses, such as [arbitrary-precision rationals][BigRat]
mod base_unit;
mod bigint;
mod bigrat;

View file

@ -0,0 +1,269 @@
[
{
"name": "BTC",
"doc": null,
"category": "currencies",
"type": "unit",
"expr": "price of bitcoin"
},
{
"name": "bitcoin",
"doc": "Properties of the global Bitcoin network. Sourced from <https://blockchain.info>. Current as of Sun, 02 Jun 2024 21:38:26 GMT",
"category": "currencies",
"type": "substance",
"symbol": null,
"properties": [
{
"name": "price",
"doc": "Current market price of 1 BTC.",
"category": "currencies",
"inputName": "bitcoin",
"input": "1",
"outputName": "bitcoin",
"output": "67710.94 USD"
},
{
"name": "hashrate",
"doc": "Current hash rate of the global network",
"category": null,
"inputName": "hashrate",
"input": "1",
"outputName": "rate",
"output": "562079779113.5204 1e9 'hash'/sec"
},
{
"name": "total",
"doc": "Total number of BTC in circulation.",
"category": null,
"inputName": "total",
"input": "1",
"outputName": "bitcoin",
"output": "1970701250000000 / 1e8"
}
]
},
{
"name": "USD",
"doc": "Sourced from European Central Bank. Current as of 2024-05-31.",
"category": "currencies",
"type": "unit",
"expr": "(1 / 1.0852) EUR"
},
{
"name": "JPY",
"doc": "Sourced from European Central Bank. Current as of 2024-05-31.",
"category": "currencies",
"type": "unit",
"expr": "(1 / 170.52) EUR"
},
{
"name": "BGN",
"doc": "Sourced from European Central Bank. Current as of 2024-05-31.",
"category": "currencies",
"type": "unit",
"expr": "(1 / 1.9558) EUR"
},
{
"name": "CZK",
"doc": "Sourced from European Central Bank. Current as of 2024-05-31.",
"category": "currencies",
"type": "unit",
"expr": "(1 / 24.709) EUR"
},
{
"name": "DKK",
"doc": "Sourced from European Central Bank. Current as of 2024-05-31.",
"category": "currencies",
"type": "unit",
"expr": "(1 / 7.4588) EUR"
},
{
"name": "GBP",
"doc": "Sourced from European Central Bank. Current as of 2024-05-31.",
"category": "currencies",
"type": "unit",
"expr": "(1 / 0.85365) EUR"
},
{
"name": "HUF",
"doc": "Sourced from European Central Bank. Current as of 2024-05-31.",
"category": "currencies",
"type": "unit",
"expr": "(1 / 388.83) EUR"
},
{
"name": "PLN",
"doc": "Sourced from European Central Bank. Current as of 2024-05-31.",
"category": "currencies",
"type": "unit",
"expr": "(1 / 4.2645) EUR"
},
{
"name": "RON",
"doc": "Sourced from European Central Bank. Current as of 2024-05-31.",
"category": "currencies",
"type": "unit",
"expr": "(1 / 4.9767) EUR"
},
{
"name": "SEK",
"doc": "Sourced from European Central Bank. Current as of 2024-05-31.",
"category": "currencies",
"type": "unit",
"expr": "(1 / 11.4210) EUR"
},
{
"name": "CHF",
"doc": "Sourced from European Central Bank. Current as of 2024-05-31.",
"category": "currencies",
"type": "unit",
"expr": "(1 / 0.9818) EUR"
},
{
"name": "ISK",
"doc": "Sourced from European Central Bank. Current as of 2024-05-31.",
"category": "currencies",
"type": "unit",
"expr": "(1 / 149.10) EUR"
},
{
"name": "NOK",
"doc": "Sourced from European Central Bank. Current as of 2024-05-31.",
"category": "currencies",
"type": "unit",
"expr": "(1 / 11.3830) EUR"
},
{
"name": "TRY",
"doc": "Sourced from European Central Bank. Current as of 2024-05-31.",
"category": "currencies",
"type": "unit",
"expr": "(1 / 34.9691) EUR"
},
{
"name": "AUD",
"doc": "Sourced from European Central Bank. Current as of 2024-05-31.",
"category": "currencies",
"type": "unit",
"expr": "(1 / 1.6315) EUR"
},
{
"name": "BRL",
"doc": "Sourced from European Central Bank. Current as of 2024-05-31.",
"category": "currencies",
"type": "unit",
"expr": "(1 / 5.6418) EUR"
},
{
"name": "CAD",
"doc": "Sourced from European Central Bank. Current as of 2024-05-31.",
"category": "currencies",
"type": "unit",
"expr": "(1 / 1.4804) EUR"
},
{
"name": "CNY",
"doc": "Sourced from European Central Bank. Current as of 2024-05-31.",
"category": "currencies",
"type": "unit",
"expr": "(1 / 7.8577) EUR"
},
{
"name": "HKD",
"doc": "Sourced from European Central Bank. Current as of 2024-05-31.",
"category": "currencies",
"type": "unit",
"expr": "(1 / 8.4838) EUR"
},
{
"name": "IDR",
"doc": "Sourced from European Central Bank. Current as of 2024-05-31.",
"category": "currencies",
"type": "unit",
"expr": "(1 / 17641.72) EUR"
},
{
"name": "ILS",
"doc": "Sourced from European Central Bank. Current as of 2024-05-31.",
"category": "currencies",
"type": "unit",
"expr": "(1 / 4.0342) EUR"
},
{
"name": "INR",
"doc": "Sourced from European Central Bank. Current as of 2024-05-31.",
"category": "currencies",
"type": "unit",
"expr": "(1 / 90.5255) EUR"
},
{
"name": "KRW",
"doc": "Sourced from European Central Bank. Current as of 2024-05-31.",
"category": "currencies",
"type": "unit",
"expr": "(1 / 1501.11) EUR"
},
{
"name": "MXN",
"doc": "Sourced from European Central Bank. Current as of 2024-05-31.",
"category": "currencies",
"type": "unit",
"expr": "(1 / 18.4387) EUR"
},
{
"name": "MYR",
"doc": "Sourced from European Central Bank. Current as of 2024-05-31.",
"category": "currencies",
"type": "unit",
"expr": "(1 / 5.1080) EUR"
},
{
"name": "NZD",
"doc": "Sourced from European Central Bank. Current as of 2024-05-31.",
"category": "currencies",
"type": "unit",
"expr": "(1 / 1.7696) EUR"
},
{
"name": "PHP",
"doc": "Sourced from European Central Bank. Current as of 2024-05-31.",
"category": "currencies",
"type": "unit",
"expr": "(1 / 63.604) EUR"
},
{
"name": "SGD",
"doc": "Sourced from European Central Bank. Current as of 2024-05-31.",
"category": "currencies",
"type": "unit",
"expr": "(1 / 1.4663) EUR"
},
{
"name": "THB",
"doc": "Sourced from European Central Bank. Current as of 2024-05-31.",
"category": "currencies",
"type": "unit",
"expr": "(1 / 39.925) EUR"
},
{
"name": "ZAR",
"doc": "Sourced from European Central Bank. Current as of 2024-05-31.",
"category": "currencies",
"type": "unit",
"expr": "(1 / 20.2927) EUR"
},
{
"name": "HRK",
"doc": "Croatian Kuna. Pinned to Euro at a fixed rate since 2023-01-01.",
"category": "currencies",
"type": "unit",
"expr": "(1 / 7.5345) EUR"
},
{
"name": "RUB",
"doc": "Fetching live data failed. Fallback value provided from xe.com on 2024-01-06.",
"category": "currencies",
"type": "unit",
"expr": "(1 / 99.477867) EUR"
}
]

View file

@ -0,0 +1,17 @@
#[test]
fn load_currency() {
let mut ctx = rink_core::simple_context().unwrap();
let live_data = include_str!("../tests/currency.snapshot.json");
let base_defs = rink_core::CURRENCY_FILE.unwrap();
ctx.load_currency(&live_data, base_defs).unwrap();
let result = rink_core::one_line(&mut ctx, "USD");
assert_eq!(
result,
Ok("Definition: USD = (1 / 1.0852) EUR = \
approx. 921.4891 millieuro (money; EUR). \
Sourced from European Central Bank. \
Current as of 2024-05-31."
.to_owned())
);
}

View file

@ -63,10 +63,7 @@ fn test(input: &str, output: &[FlatSpan<'static>]) {
let expr = text_query::parse_query(&mut iter);
CONTEXT.with(|ctx| {
let res = ctx.eval_query(&expr);
let res = match res {
Ok(ref v) => v.to_spans(),
Err(ref v) => v.to_spans(),
};
let res = res.to_spans();
let res = res.into_iter().map(FlatSpan::from).collect::<Vec<_>>();
similar_asserts::assert_eq!(res, output);
});

View file

@ -10,7 +10,7 @@ edition = "2018"
[dependencies.rink-core]
version = "0.8"
path = "../core"
features = [ "bundle-files" ]
features = [ "bundle-files", "serde_json" ]
[dependencies.rink-sandbox]
path = "../sandbox"
@ -22,7 +22,6 @@ humantime-serde = "1.0.1"
irc = "1.0.0"
serde = { version = "1", default-features = false }
serde_derive = "1"
serde_json = "1"
tokio = { version = "1", features = ["full"] }
toml = "0.8"
ubyte = { version = "0.10.3", features = ["serde"] }

View file

@ -3,7 +3,7 @@
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
use irc::client::data::Config as IrcConfig;
use rink_core::{ast, loader::gnu_units, parsing::datetime, Context};
use rink_core::{parsing::datetime, Context};
use serde_derive::{Deserialize, Serialize};
use std::{path::PathBuf, time::Duration};
use ubyte::ByteUnit;
@ -76,13 +76,7 @@ fn try_load_currency(config: &Currency, ctx: &mut Context) {
let base = rink_core::CURRENCY_FILE.unwrap();
let live = std::fs::read_to_string(&config.path).unwrap();
let mut base_defs = gnu_units::parse_str(&base);
let mut live_defs: ast::Defs = serde_json::from_str(&live).unwrap();
let mut defs = vec![];
defs.append(&mut base_defs.defs);
defs.append(&mut live_defs.defs);
ctx.load(ast::Defs { defs }).unwrap();
ctx.load_currency(&live, &base).unwrap();
}
/// Creates a context by searching standard directories
@ -94,7 +88,7 @@ pub fn load(config: &Config) -> Context {
let mut ctx = Context::new();
ctx.save_previous_result = true;
ctx.load(gnu_units::parse_str(&units)).unwrap();
ctx.load_definitions(&units).unwrap();
ctx.load_dates(datetime::parse_datefile(&dates));
// Load currency data.

View file

@ -42,10 +42,7 @@ async fn server_task(config: config::Config, index: usize) {
};
println!("[{servername}] <== {command}");
let result = rink_core::eval(&mut ctx, command);
let result = match &result {
Ok(res) => res.to_spans(),
Err(err) => err.to_spans(),
};
let result = result.to_spans();
let result = fmt::to_irc_string(&config, &result);
println!("[{servername}] ==> {result}");
let where_to = if channel == client.current_nickname() {

View file

@ -17,7 +17,7 @@ default = ["console_error_panic_hook"]
[dependencies.rink-core]
path = "../core"
version = "0.8"
features = ["bundle-files"]
features = ["bundle-files", "serde_json"]
[dependencies]
wasm-bindgen = { version = "0.2", default-features = false }
@ -26,7 +26,6 @@ wee_alloc = { version = "0.4.5", default-features = false }
chrono = { version = "0.4.13", default-features = false }
serde = { version = "1", default-features = false }
serde_derive = "1"
serde_json = "1"
serde-wasm-bindgen = "0.6"
# The `console_error_panic_hook` crate provides better debugging of panics by

View file

@ -123,22 +123,8 @@ impl Context {
#[wasm_bindgen(js_name = loadCurrency)]
pub fn load_currency(&mut self, live_defs: String) -> Result<(), JsValue> {
let mut live_defs: Vec<ast::DefEntry> =
serde_json::from_str(&live_defs).map_err(|e| e.to_string())?;
let mut base_defs = {
use rink_core::loader::gnu_units;
let defs = rink_core::CURRENCY_FILE.unwrap();
let mut iter = gnu_units::TokenIterator::new(defs).peekable();
gnu_units::parse(&mut iter)
};
let currency = {
let mut defs = vec![];
defs.append(&mut live_defs);
defs.append(&mut base_defs.defs);
ast::Defs { defs }
};
self.context.load(currency)?;
let base_defs = rink_core::CURRENCY_FILE.unwrap();
self.context.load_currency(&live_defs, base_defs)?;
Ok(())
}
@ -170,10 +156,7 @@ impl Context {
}
}
}
let spans = match value {
Ok(ref value) => value.to_spans(),
Err(ref value) => value.to_spans(),
};
let spans = value.to_spans();
let tokens = visit_tokens(&spans);
match serde_wasm_bindgen::to_value(&tokens) {