Requires N:M mapping.

This mapping has 1 multi-map group: several pairings are equally correct there. The code view shows one arbitrary valid pairing, not the only one.

This mapping has 5 all-to-all groups: every node on one side corresponds to every node on the other, and none is deleted or inserted. Each member carries an all N:M badge; selecting one highlights all of its counterparts, in the tree and in the code view alike.

Before

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5use crate::style_ser;
6use color_eyre::Result;
7use curl::easy::Easy;
8use eyre::{eyre, Report, WrapErr};
9use nu_ansi_term::{Color, Style};
10use rink_core::output::fmt::FmtToken;
11use rink_core::parsing::datetime;
12use rink_core::Context;
13use rink_core::{loader::gnu_units, CURRENCY_FILE, DATES_FILE, DEFAULT_FILE};
14use serde_derive::{Deserialize, Serialize};
15use std::env;
16use std::ffi::OsString;
17use std::io::{ErrorKind, Read, Seek, SeekFrom, Write};
18use std::path::Path;
19use std::path::PathBuf;
20use std::time::Duration;
21use std::{
22 collections::HashMap,
23 fs::{read_to_string, File},
24};
25use ubyte::ByteUnit;
26
27fn file_to_string(mut file: File) -> Result<String> {
28 let mut string = String::new();
29 let _ = file.read_to_string(&mut string)?;
30 Ok(string)
31}
32
33pub fn config_path(name: &str) -> Result<PathBuf> {
34 let mut path = dirs::config_dir().ok_or_else(|| eyre!("Could not find config directory"))?;
35 path.push("rink");
36 path.push(name);
37 Ok(path)
38}
39
40#[derive(Serialize, Deserialize, Clone)]
41#[serde(default, deny_unknown_fields)]
42pub struct Config {
…lines 43–54 unchanged (12 lines)
55pub struct Rink {
56 /// Which prompt to render when run interactively.
57 pub prompt: String,
58 /// Use multi-line output for lists.
59 pub long_output: bool,
60}
61
62#[derive(Serialize, Deserialize, Clone)]
63#[serde(default, deny_unknown_fields)]
64pub struct Currency {
65 /// Set to false to disable currency loading entirely.
66 pub enabled: bool,
67 /// Set to false to only reuse the existing cached currency data.
68 pub fetch_on_startup: bool,
69 /// Which web endpoint should be used to download currency data?
70 pub endpoint: String,
71 /// How long to cache for.
72 #[serde(with = "humantime_serde")]
73 pub cache_duration: Duration,
74 /// How long to wait before timing out requests.
…lines 75–173 unchanged (99 lines)
174 }
175}
176
177impl Default for Currency {
178 fn default() -> Self {
179 Currency {
180 enabled: true,
181 fetch_on_startup: true,
182 endpoint: "https://rinkcalc.app/data/currency.json".to_owned(),
183 cache_duration: Duration::from_secs(60 * 60), // 1 hour
184 timeout: Duration::from_secs(2),
185 }
186 }
187}
188
189impl Default for Rink {
190 fn default() -> Self {
191 Rink {
192 prompt: "> ".to_owned(),
193 long_output: false,
194 }
…lines 195–225 unchanged (31 lines)
226 theme.unwrap_or(&self.default_theme)
227 } else {
228 &self.disabled_theme
229 }
230 }
231}
232
233fn read_from_search_path(
234 filename: &str,
235 paths: &[PathBuf],
236 default: Option<&'static str>,
237) -> Result<Vec<String>> {
238 let result: Vec<String> = paths
239 .iter()
240 .filter_map(|path| {
241 let mut buf = PathBuf::from(path);
242 buf.push(filename);
243 read_to_string(buf).ok()
…lines 244–256 unchanged (13 lines)
257 ))
258 } else {
259 Ok(result)
260 }
261}
262
263pub fn force_refresh_currency(config: &Currency) -> Result<String> {
264 println!("Fetching...");
265 let start = std::time::Instant::now();
266 let mut path = dirs::cache_dir().ok_or_else(|| eyre!("Could not find cache directory"))?;
267 path.push("rink");
268 path.push("currency.json");
269 let file = download_to_file(&path, &config.endpoint, config.timeout)
270 .wrap_err("Fetching currency data failed")?;
271 let delta = std::time::Instant::now() - start;
272 let metadata = file
273 .metadata()
274 .wrap_err("Fetched currency file, but failed to read file metadata")?;
275 let len = metadata.len();
276 Ok(format!(
277 "Fetched {len} byte currency file after {}ms",
278 delta.as_millis()
279 ))
280}
281
282fn load_live_currency(config: &Currency) -> Result<String> {
283 let duration = if config.fetch_on_startup {
284 Some(config.cache_duration)
285 } else {
286 None
287 };
288 let file = cached("currency.json", &config.endpoint, duration, config.timeout)?;
289 let contents = file_to_string(file)?;
290 Ok(contents)
291}
292
293fn try_load_currency(config: &Currency, ctx: &mut Context, search_path: &[PathBuf]) -> Result<()> {
294 let base = read_from_search_path("currency.units", search_path, CURRENCY_FILE)?
295 .into_iter()
296 .collect::<Vec<_>>()
297 .join("\n");
298 let live_defs = load_live_currency(config)?;
299 ctx.load_currency(&live_defs, &base.as_str())
300 .map_err(|err| eyre!("{err}"))?;
301 Ok(())
302}
303
304pub fn read_config(override_path: Option<&str>) -> Result<Config> {
305 let path = if let Some(path) = override_path {
306 PathBuf::from(path)
307 } else {
308 config_path("config.toml")?
309 };
310 match read_to_string(path) {
311 // Hard fail if the file has invalid TOML.
312 Ok(result) => toml::from_str(&result).wrap_err("While parsing config.toml"),
313 Err(err) if err.kind() == ErrorKind::NotFound => {
314 if let Some(override_path) = override_path {
…lines 315–357 unchanged (43 lines)
358 ctx.load(rink_core::ast::Defs { defs: unit_defs })
359 .map_err(|err| eyre!(err))?;
360 ctx.load_dates(datetime::parse_datefile(&dates));
361
362 // Load currency data.
363 if config.currency.enabled {
364 match try_load_currency(&config.currency, &mut ctx, &search_path) {
365 Ok(()) => (),
366 Err(err) => {
367 println!("{:?}", err.wrap_err("Failed to load currency data"));
368 }
369 }
370 }
371
372 Ok(ctx)
373}
374
375fn read_if_current(file: File, expiration: Option<Duration>) -> Result<File> {
376 use std::time::SystemTime;
377
378 let stats = file.metadata()?;
379 let mtime = stats.modified()?;
380 let now = SystemTime::now();
381 let elapsed = now.duration_since(mtime)?;
382 if let Some(expiration) = expiration {
383 if elapsed > expiration {
384 return Err(eyre!("File is out of date"));
385 }
386 }
387 Ok(file)
388}
389
390pub fn download_to_file(path: &Path, url: &str, timeout: Duration) -> Result<File> {
391 use std::fs::create_dir_all;
392
393 create_dir_all(path.parent().unwrap())?;
394
395 // Given a filename like `foo.json`, names the temp file something
396 // similar, like `foo.ABCDEF.json`.
397 let mut prefix = path.file_stem().unwrap().to_owned();
398 prefix.push(".");
399 let mut suffix = OsString::from(".");
400 suffix.push(path.extension().unwrap());
401
402 // The temp file should be created in the same directory as its
403 // final home, as the default temporary file directory might not be
404 // the same filesystem.
405 let mut temp_file = tempfile::Builder::new()
406 .prefix(&prefix)
407 .suffix(&suffix)
408 .tempfile_in(path.parent().unwrap())?;
409
410 let mut easy = Easy::new();
411 easy.url(url)?;
412 easy.useragent(&format!(
413 "rink-cli {} <{}>",
414 env!("CARGO_PKG_VERSION"),
415 env!("CARGO_PKG_REPOSITORY")
416 ))?;
417 easy.timeout(timeout)?;
418
419 let mut write_handle = temp_file.as_file_mut().try_clone()?;
420 easy.write_function(move |data| {
421 write_handle
422 .write(data)
423 .map_err(|_| curl::easy::WriteError::Pause)
424 })?;
425 easy.perform()?;
426
427 let status = easy.response_code()?;
428 if status != 200 {
429 return Err(eyre!(
430 "Received status {} while downloading {}",
431 status,
432 url
433 ));
434 }
435
436 temp_file.as_file_mut().sync_all()?;
437 temp_file.as_file_mut().seek(SeekFrom::Start(0))?;
438
439 temp_file
440 .persist(path)
441 .wrap_err("Failed to write to cache dir")
442}
443
444fn cached(
445 filename: &str,
446 url: &str,
447 expiration: Option<Duration>,
448 timeout: Duration,
449) -> Result<File> {
450 let mut path = dirs::cache_dir().ok_or_else(|| eyre!("Could not find cache directory"))?;
451 path.push("rink");
452 path.push(filename);
453
454 // 1. Return file if it exists and is up to date.
455 // 2. Try to download a new version of the file and return it.
456 // 3. If that fails, return the stale file if it exists.
457
458 if let Ok(file) = File::open(&path) {
459 if let Ok(result) = read_if_current(file, expiration) {
460 return Ok(result);
461 }
462 }
463
464 let err = match download_to_file(&path, url, timeout) {
465 Ok(result) => return Ok(result),
466 Err(err) => err,
467 };
468
469 if let Ok(file) = File::open(&path) {
470 // Indicate error even though we're returning success.
471 println!(
472 "{:?}",
473 Report::wrap_err(
474 err,
475 format!("Failed to refresh {}, using stale version", url)
476 )
477 );
478 Ok(file)
479 } else {
480 Err(err).wrap_err_with(|| format!("Failed to fetch {}", url))
481 }
482}
483

After

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5use crate::currency::load_cached_currency_if_current;
6use crate::style_ser;
7use color_eyre::Result;
8use eyre::{eyre, WrapErr};
9use nu_ansi_term::{Color, Style};
10use rink_core::loader::gnu_units;
11use rink_core::output::fmt::FmtToken;
12use rink_core::parsing::datetime;
13use rink_core::Context;
14use rink_core::{DATES_FILE, DEFAULT_FILE};
15use serde_derive::{Deserialize, Serialize};
16use std::collections::HashMap;
17use std::env;
18use std::fs::read_to_string;
19use std::io::ErrorKind;
20use std::path::PathBuf;
21use std::time::Duration;
22use ubyte::ByteUnit;
23
24pub fn config_toml_path() -> Result<PathBuf> {
25 let mut path = dirs::config_dir().ok_or_else(|| eyre!("Could not find config directory"))?;
26 path.push("rink");
27 path.push("config.toml");
28 Ok(path)
29}
30
31#[derive(Serialize, Deserialize, Clone)]
32#[serde(default, deny_unknown_fields)]
33pub struct Config {
…lines 34–46 unchanged (13 lines)
47 /// Which prompt to render when run interactively.
48 pub prompt: String,
49 /// Use multi-line output for lists.
50 pub long_output: bool,
51}
52
53#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
54#[serde(rename_all = "lowercase")]
55pub enum CurrencyBehavior {
56 Prompt,
57 Always,
58}
59
60#[derive(Serialize, Deserialize, Clone)]
61#[serde(default, deny_unknown_fields)]
62pub struct Currency {
63 /// Set behavior for currency fetching, whether to prompt.
64 pub behavior: CurrencyBehavior,
65 /// Set to false to disable currency loading entirely.
66 pub enabled: bool,
67 /// Set to false to only reuse the existing cached currency data.
68 #[serde(alias = "fetch_on_startup")]
69 pub cache_expiration_enabled: bool,
70 /// Which web endpoint should be used to download currency data?
71 pub endpoint: String,
72 /// How long to cache for.
73 #[serde(with = "humantime_serde")]
74 pub cache_duration: Duration,
75 /// How long to wait before timing out requests.
…lines 76–174 unchanged (99 lines)
175 }
176}
177
178impl Default for Currency {
179 fn default() -> Self {
180 Currency {
181 behavior: CurrencyBehavior::Prompt,
182 enabled: true,
183 cache_expiration_enabled: true,
184 endpoint: "https://rinkcalc.app/data/currency.json".to_owned(),
185 cache_duration: Duration::from_secs(60 * 60), // 1 hour
186 timeout: Duration::from_secs(15),
187 }
188 }
189}
190
191impl Currency {
192 pub fn expiration(&self) -> Option<Duration> {
193 if self.cache_expiration_enabled {
194 Some(self.cache_duration)
195 } else {
196 None
197 }
198 }
199}
200
201impl Default for Rink {
202 fn default() -> Self {
203 Rink {
204 prompt: "> ".to_owned(),
205 long_output: false,
…lines 206–238 unchanged (33 lines)
239 } else {
240 &self.disabled_theme
241 }
242 }
243}
244
245pub(crate) fn read_from_search_path(
246 filename: &str,
247 paths: &[PathBuf],
248 default: Option<&'static str>,
249) -> Result<Vec<String>> {
250 let result: Vec<String> = paths
251 .iter()
252 .filter_map(|path| {
253 let mut buf = PathBuf::from(path);
254 buf.push(filename);
255 read_to_string(buf).ok()
…lines 256–267 unchanged (12 lines)
268 .join("")
269 ))
270 } else {
271 Ok(result)
272 }
273}
274
275pub fn read_config(override_path: Option<&str>) -> Result<Config> {
276 let path = if let Some(path) = override_path {
277 PathBuf::from(path)
278 } else {
279 config_toml_path()?
280 };
281 match read_to_string(path) {
282 // Hard fail if the file has invalid TOML.
283 Ok(result) => toml::from_str(&result).wrap_err("While parsing config.toml"),
284 Err(err) if err.kind() == ErrorKind::NotFound => {
285 if let Some(override_path) = override_path {
…lines 286–328 unchanged (43 lines)
329 ctx.load(rink_core::ast::Defs { defs: unit_defs })
330 .map_err(|err| eyre!(err))?;
331 ctx.load_dates(datetime::parse_datefile(&dates));
332
333 // Load currency data.
334 if config.currency.enabled {
335 let res = load_cached_currency_if_current(&config.currency, &mut ctx, &search_path);
336 if let Err(err) = res {
337 println!("{:?}", err.wrap_err("Failed to load currency data"));
338 }
339 }
340
341 Ok(ctx)
342}
343

Before

source_file
line_comment
// "//"
line_comment
// "//"
line_comment
// "//"
use_declaration
use "use"
scoped_identifier
crate "crate"
:: "::"
identifier "style_ser"
; ";"
use_declaration
use "use"
scoped_identifier
identifier "color_eyre"
:: "::"
identifier "Result"
; ";"
use_declaration
use "use"
scoped_identifier
scoped_identifier
identifier "curl"
:: "::"
identifier "easy"
:: "::"
identifier "Easy"
; ";"
use_declaration
use "use"
scoped_use_list
identifier "eyre"
:: "::"
use_list
{ "{"
identifier "eyre"
, ","
identifier "Report"
, ","
identifier "WrapErr"
} "}"
; ";"
use_declaration
use "use"
scoped_use_list
identifier "nu_ansi_term"
:: "::"
use_list
{ "{"
identifier "Color"
, ","
identifier "Style"
} "}"
; ";"
use_declaration
use "use"
scoped_identifier
scoped_identifier
scoped_identifier
identifier "rink_core"
:: "::"
identifier "output"
:: "::"
identifier "fmt"
:: "::"
identifier "FmtToken"
; ";"
use_declaration
use "use"
scoped_identifier
scoped_identifier
identifier "rink_core"
:: "::"
identifier "parsing"
:: "::"
identifier "datetime"
; ";"
use_declaration
use "use"
scoped_identifier
identifier "rink_core"
:: "::"
identifier "Context"
; ";"
use_declaration
use "use"
scoped_use_list
identifier "rink_core"
:: "::"
use_list
{ "{"
scoped_identifier
identifier "loader"
:: "::"
identifier "gnu_units"
, ","
identifier "CURRENCY_FILE"
, ","
identifier "DATES_FILE"
, ","
identifier "DEFAULT_FILE"
} "}"
; ";"
use_declaration
use "use"
scoped_use_list
identifier "serde_derive"
:: "::"
use_list
{ "{"
identifier "Deserialize"
, ","
identifier "Serialize"
} "}"
; ";"
use_declaration
use "use"
scoped_identifier
identifier "std"
:: "::"
identifier "env"
; ";"
use_declaration
use "use"
scoped_identifier
scoped_identifier
identifier "std"
:: "::"
identifier "ffi"
:: "::"
identifier "OsString"
; ";"
use_declaration
use "use"
scoped_use_list
scoped_identifier any 1:2
identifier "std"
:: "::"
identifier "io"
:: "::"
use_list
{ "{"
identifier "ErrorKind"
, ","
identifier "Read"
, ","
identifier "Seek"
, ","
identifier "SeekFrom"
, ","
identifier "Write"
} "}"
; ";"
use_declaration
use "use"
scoped_identifier
scoped_identifier
identifier "std"
:: "::"
identifier "path"
:: "::"
identifier "Path"
; ";"
use_declaration
use "use"
scoped_identifier
scoped_identifier
identifier "std"
:: "::"
identifier "path"
:: "::"
identifier "PathBuf"
; ";"
use_declaration
use "use"
scoped_identifier
scoped_identifier
identifier "std"
:: "::"
identifier "time"
:: "::"
identifier "Duration"
; ";"
use_declaration all 1:2
use "use" all 1:2
scoped_use_list
identifier "std" all 1:2
:: "::" all 1:2
use_list
{ "{"
scoped_identifier
identifier "collections"
:: "::"
identifier "HashMap"
, ","
scoped_use_list
identifier "fs"
:: "::"
use_list
{ "{"
identifier "read_to_string"
, ","
identifier "File"
} "}"
, ","
} "}"
; ";" all 1:2
use_declaration
use "use"
scoped_identifier
identifier "ubyte"
:: "::"
identifier "ByteUnit"
; ";"
function_item
fn "fn"
identifier "file_to_string"
parameters
( "("
parameter
mutable_specifier "mut"
identifier "file"
: ":"
type_identifier "File"
) ")"
-> "->"
generic_type
type_identifier "Result"
type_arguments
< "<"
type_identifier "String"
> ">"
block
{ "{"
let_declaration
let "let"
mutable_specifier "mut"
identifier "string"
= "="
call_expression
scoped_identifier
identifier "String"
:: "::"
identifier "new"
arguments
( "("
) ")"
; ";"
let_declaration
let "let"
_ "_"
= "="
try_expression
call_expression
field_expression
identifier "file"
. "."
field_identifier "read_to_string"
arguments
( "("
reference_expression
& "&"
mutable_specifier "mut"
identifier "string"
) ")"
? "?"
; ";"
call_expression
identifier "Ok"
arguments
( "("
identifier "string"
) ")"
} "}"
function_item
visibility_modifier
pub "pub"
fn "fn"
identifier "config_path"
parameters
( "("
parameter
identifier "name"
: ":"
reference_type
& "&"
primitive_type "str"
) ")"
-> "->"
generic_type
type_identifier "Result"
type_arguments
< "<"
type_identifier "PathBuf"
> ">"
block
{ "{"
let_declaration (+37 nodes collapsed)
expression_statement
call_expression
field_expression
identifier "path"
. "."
field_identifier "push"
arguments
( "("
string_literal
" "\""
string_content "rink"
" "\""
) ")"
; ";"
expression_statement
call_expression
field_expression
identifier "path"
. "."
field_identifier "push"
arguments
( "("
identifier "name"
) ")"
; ";"
call_expression
identifier "Ok"
arguments
( "("
identifier "path"
) ")"
} "}"
attribute_item
# "#"
[ "["
attribute
identifier "derive"
token_tree
( "("
identifier "Serialize"
, ","
identifier "Deserialize"
, ","
identifier "Clone"
) ")"
] "]"
attribute_item
# "#"
[ "["
attribute
identifier "serde"
token_tree
( "("
default "default"
, ","
identifier "deny_unknown_fields"
) ")"
] "]"
struct_item (+62 nodes collapsed)
attribute_item
# "#"
[ "["
attribute
identifier "derive"
token_tree
( "("
identifier "Serialize"
, ","
identifier "Deserialize"
, ","
identifier "Clone"
) ")"
] "]"
attribute_item
# "#"
[ "["
attribute
identifier "serde"
token_tree
( "("
default "default"
, ","
identifier "deny_unknown_fields"
) ")"
] "]"
struct_item (+32 nodes collapsed)
attribute_item
# "#"
[ "["
attribute
identifier "derive"
token_tree
( "("
identifier "Serialize"
, ","
identifier "Deserialize"
, ","
identifier "Clone"
) ")"
] "]"
attribute_item
# "#"
[ "["
attribute
identifier "serde"
token_tree
( "("
default "default"
, ","
identifier "deny_unknown_fields"
) ")"
] "]"
struct_item
visibility_modifier
pub "pub"
struct "struct"
type_identifier "Currency"
field_declaration_list
{ "{"
line_comment
// "//"
outer_doc_comment_marker
/ "/"
doc_comment " Set to false to disable currency loading entirely.\n"
field_declaration
visibility_modifier
pub "pub"
field_identifier "enabled"
: ":"
primitive_type "bool"
, ","
line_comment
// "//"
outer_doc_comment_marker
/ "/"
doc_comment " Set to false to only reuse the existing cached currency dat"...
field_declaration
visibility_modifier
pub "pub"
field_identifier "fetch_on_startup"
: ":"
primitive_type "bool"
, ","
line_comment
// "//"
outer_doc_comment_marker
/ "/"
doc_comment " Which web endpoint should be used to download currency data"...
field_declaration
visibility_modifier
pub "pub"
field_identifier "endpoint"
: ":"
type_identifier "String"
, ","
line_comment
// "//"
outer_doc_comment_marker
/ "/"
doc_comment " How long to cache for.\n"
attribute_item
# "#"
[ "["
attribute
identifier "serde"
token_tree
( "("
identifier "with"
= "="
string_literal
" "\""
string_content "humantime_serde"
" "\""
) ")"
] "]"
field_declaration
visibility_modifier
pub "pub"
field_identifier "cache_duration"
: ":"
type_identifier "Duration"
, ","
line_comment
// "//"
outer_doc_comment_marker
/ "/"
doc_comment " How long to wait before timing out requests.\n"
attribute_item
# "#"
[ "["
attribute
identifier "serde"
token_tree
( "("
identifier "with"
= "="
string_literal
" "\""
string_content "humantime_serde"
" "\""
) ")"
] "]"
field_declaration
visibility_modifier
pub "pub"
field_identifier "timeout"
: ":"
type_identifier "Duration"
, ","
} "}"
attribute_item
# "#"
[ "["
attribute
identifier "derive"
token_tree
( "("
identifier "Serialize"
, ","
identifier "Deserialize"
, ","
identifier "Clone"
) ")"
] "]"
attribute_item
# "#"
[ "["
attribute
identifier "serde"
token_tree
( "("
default "default"
, ","
identifier "deny_unknown_fields"
) ")"
] "]"
struct_item (+37 nodes collapsed)
attribute_item
# "#"
[ "["
attribute
identifier "derive"
token_tree
( "("
identifier "Serialize"
, ","
identifier "Deserialize"
, ","
identifier "Clone"
) ")"
] "]"
attribute_item
# "#"
[ "["
attribute
identifier "serde"
token_tree
( "("
default "default"
, ","
identifier "deny_unknown_fields"
) ")"
] "]"
struct_item (+71 nodes collapsed)
attribute_item
# "#"
[ "["
attribute
identifier "derive"
token_tree
( "("
identifier "Serialize"
, ","
identifier "Deserialize"
, ","
identifier "Default"
, ","
identifier "Clone"
) ")"
] "]"
attribute_item
# "#"
[ "["
attribute
identifier "serde"
token_tree
( "("
default "default"
, ","
identifier "deny_unknown_fields"
) ")"
] "]"
struct_item (+228 nodes collapsed)
impl_item (+192 nodes collapsed)
impl_item (+313 nodes collapsed)
impl_item
impl "impl"
type_identifier "Default"
for "for"
type_identifier "Currency"
declaration_list
{ "{"
function_item
fn "fn"
identifier "default"
parameters
( "("
) ")"
-> "->"
type_identifier "Self"
block
{ "{"
struct_expression
type_identifier "Currency"
field_initializer_list
{ "{"
field_initializer
field_identifier "enabled"
: ":"
boolean_literal
true "true"
, ","
field_initializer
field_identifier "fetch_on_startup"
: ":"
boolean_literal
true "true"
, ","
field_initializer
field_identifier "endpoint"
: ":"
call_expression
field_expression
string_literal
" "\""
string_content "https://rinkcalc.app/data/currency.json"
" "\""
. "."
field_identifier "to_owned"
arguments
( "("
) ")"
, ","
field_initializer
field_identifier "cache_duration"
: ":"
call_expression
scoped_identifier
identifier "Duration"
:: "::"
identifier "from_secs"
arguments
( "("
binary_expression
integer_literal "60"
* "*"
integer_literal "60"
) ")"
, ","
line_comment
// "//"
field_initializer
field_identifier "timeout"
: ":"
call_expression
scoped_identifier
identifier "Duration"
:: "::"
identifier "from_secs"
arguments
( "("
integer_literal "2"
) ")"
, ","
} "}"
} "}"
} "}"
impl_item (+45 nodes collapsed)
impl_item (+44 nodes collapsed)
impl_item (+62 nodes collapsed)
impl_item (+138 nodes collapsed)
function_item
fn "fn"
identifier "read_from_search_path"
parameters (+35 nodes collapsed)
-> "->"
generic_type
type_identifier "Result"
type_arguments
< "<"
generic_type
type_identifier "Vec"
type_arguments
< "<"
type_identifier "String"
> ">"
> ">"
block (+196 nodes collapsed)
function_item
visibility_modifier
pub "pub"
fn "fn"
identifier "force_refresh_currency"
parameters
( "("
parameter
identifier "config"
: ":"
reference_type
& "&"
type_identifier "Currency"
) ")"
-> "->"
generic_type
type_identifier "Result"
type_arguments
< "<"
type_identifier "String"
> ">"
block
{ "{"
expression_statement
macro_invocation
identifier "println"
! "!"
token_tree
( "("
string_literal
" "\""
string_content "Fetching..."
" "\""
) ")"
; ";"
let_declaration
let "let"
identifier "start"
= "="
call_expression
scoped_identifier
scoped_identifier
scoped_identifier
identifier "std"
:: "::"
identifier "time"
:: "::"
identifier "Instant"
:: "::"
identifier "now"
arguments
( "("
) ")"
; ";"
let_declaration
let "let"
mutable_specifier "mut"
identifier "path"
= "="
try_expression
call_expression
field_expression
call_expression
scoped_identifier
identifier "dirs"
:: "::"
identifier "cache_dir"
arguments
( "("
) ")"
. "."
field_identifier "ok_or_else"
arguments
( "("
closure_expression
closure_parameters
| "|"
| "|"
macro_invocation
identifier "eyre"
! "!"
token_tree
( "("
string_literal
" "\""
string_content "Could not find cache directory"
" "\""
) ")"
) ")"
? "?"
; ";"
expression_statement
call_expression
field_expression
identifier "path"
. "."
field_identifier "push"
arguments
( "("
string_literal
" "\""
string_content "rink"
" "\""
) ")"
; ";"
expression_statement
call_expression
field_expression
identifier "path"
. "."
field_identifier "push"
arguments
( "("
string_literal
" "\""
string_content "currency.json"
" "\""
) ")"
; ";"
let_declaration
let "let"
identifier "file"
= "="
try_expression
call_expression
field_expression
call_expression
identifier "download_to_file"
arguments
( "("
reference_expression
& "&"
identifier "path"
, ","
reference_expression
& "&"
field_expression
identifier "config"
. "."
field_identifier "endpoint"
, ","
field_expression
identifier "config"
. "."
field_identifier "timeout"
) ")"
. "."
field_identifier "wrap_err"
arguments
( "("
string_literal
" "\""
string_content "Fetching currency data failed"
" "\""
) ")"
? "?"
; ";"
let_declaration
let "let"
identifier "delta"
= "="
binary_expression
call_expression
scoped_identifier
scoped_identifier
scoped_identifier
identifier "std"
:: "::"
identifier "time"
:: "::"
identifier "Instant"
:: "::"
identifier "now"
arguments
( "("
) ")"
- "-"
identifier "start"
; ";"
let_declaration
let "let"
identifier "metadata"
= "="
try_expression
call_expression
field_expression
call_expression
field_expression
identifier "file"
. "."
field_identifier "metadata"
arguments
( "("
) ")"
. "."
field_identifier "wrap_err"
arguments
( "("
string_literal
" "\""
string_content "Fetched currency file, but failed to read file metadata"
" "\""
) ")"
? "?"
; ";"
let_declaration
let "let"
identifier "len"
= "="
call_expression
field_expression
identifier "metadata"
. "."
field_identifier "len"
arguments
( "("
) ")"
; ";"
call_expression
identifier "Ok"
arguments
( "("
macro_invocation
identifier "format"
! "!"
token_tree
( "("
string_literal
" "\""
string_content "Fetched {len} byte currency file after {}ms"
" "\""
, ","
identifier "delta"
. "."
identifier "as_millis"
token_tree
( "("
) ")"
) ")"
) ")"
} "}"
function_item
fn "fn"
identifier "load_live_currency"
parameters
( "("
parameter
identifier "config"
: ":"
reference_type
& "&"
type_identifier "Currency"
) ")"
-> "->"
generic_type
type_identifier "Result"
type_arguments
< "<"
type_identifier "String"
> ">"
block
{ "{"
let_declaration
let "let"
identifier "duration"
= "="
if_expression
if "if"
field_expression
identifier "config"
. "."
field_identifier "fetch_on_startup"
block
{ "{"
call_expression
identifier "Some"
arguments
( "("
field_expression
identifier "config"
. "."
field_identifier "cache_duration"
) ")"
} "}"
else_clause
else "else"
block
{ "{"
identifier "None"
} "}"
; ";"
let_declaration
let "let"
identifier "file"
= "="
try_expression
call_expression
identifier "cached"
arguments
( "("
string_literal
" "\""
string_content "currency.json"
" "\""
, ","
reference_expression
& "&"
field_expression
identifier "config"
. "."
field_identifier "endpoint"
, ","
identifier "duration"
, ","
field_expression
identifier "config"
. "."
field_identifier "timeout"
) ")"
? "?"
; ";"
let_declaration
let "let"
identifier "contents"
= "="
try_expression
call_expression
identifier "file_to_string"
arguments
( "("
identifier "file"
) ")"
? "?"
; ";"
call_expression
identifier "Ok"
arguments
( "("
identifier "contents"
) ")"
} "}"
function_item
fn "fn"
identifier "try_load_currency"
parameters
( "("
parameter
identifier "config"
: ":"
reference_type
& "&"
type_identifier "Currency"
, ","
parameter
identifier "ctx"
: ":"
reference_type
& "&"
mutable_specifier "mut"
type_identifier "Context"
, ","
parameter
identifier "search_path"
: ":"
reference_type
& "&"
array_type
[ "["
type_identifier "PathBuf"
] "]"
) ")"
-> "->"
generic_type
type_identifier "Result"
type_arguments
< "<"
unit_type
( "("
) ")"
> ">"
block
{ "{"
let_declaration
let "let"
identifier "base"
= "="
call_expression
field_expression
call_expression
generic_function
field_expression
call_expression
field_expression
try_expression
call_expression
identifier "read_from_search_path"
arguments
( "("
string_literal
" "\""
string_content "currency.units"
" "\""
, ","
identifier "search_path"
, ","
identifier "CURRENCY_FILE"
) ")"
? "?"
. "."
field_identifier "into_iter"
arguments
( "("
) ")"
. "."
field_identifier "collect"
:: "::"
type_arguments
< "<"
generic_type
type_identifier "Vec"
type_arguments
< "<"
type_identifier "_"
> ">"
> ">"
arguments
( "("
) ")"
. "."
field_identifier "join"
arguments
( "("
string_literal
" "\""
escape_sequence "\\n"
" "\""
) ")"
; ";"
let_declaration
let "let"
identifier "live_defs"
= "="
try_expression
call_expression
identifier "load_live_currency"
arguments
( "("
identifier "config"
) ")"
? "?"
; ";"
expression_statement
try_expression
call_expression
field_expression
call_expression
field_expression
identifier "ctx"
. "."
field_identifier "load_currency"
arguments
( "("
reference_expression
& "&"
identifier "live_defs"
, ","
reference_expression
& "&"
call_expression
field_expression
identifier "base"
. "."
field_identifier "as_str"
arguments
( "("
) ")"
) ")"
. "."
field_identifier "map_err"
arguments
( "("
closure_expression
closure_parameters
| "|"
identifier "err"
| "|"
macro_invocation
identifier "eyre"
! "!"
token_tree
( "("
string_literal
" "\""
string_content "{err}"
" "\""
) ")"
) ")"
? "?"
; ";"
call_expression
identifier "Ok"
arguments
( "("
unit_expression
( "("
) ")"
) ")"
} "}"
function_item
visibility_modifier
pub "pub"
fn "fn"
identifier "read_config"
parameters
( "("
parameter
identifier "override_path"
: ":"
generic_type
type_identifier "Option"
type_arguments
< "<"
reference_type
& "&"
primitive_type "str"
> ">"
) ")"
-> "->"
generic_type
type_identifier "Result"
type_arguments
< "<"
type_identifier "Config"
> ">"
block
{ "{"
let_declaration
let "let"
identifier "path"
= "="
if_expression
if "if"
let_condition
let "let"
tuple_struct_pattern
identifier "Some"
( "("
identifier "path"
) ")"
= "="
identifier "override_path"
block
{ "{"
call_expression
scoped_identifier
identifier "PathBuf"
:: "::"
identifier "from"
arguments
( "("
identifier "path"
) ")"
} "}"
else_clause
else "else"
block
{ "{"
try_expression
call_expression
identifier "config_path"
arguments
( "("
string_literal
" "\""
string_content "config.toml"
" "\""
) ")"
? "?"
} "}"
; ";"
expression_statement (+173 nodes collapsed)
} "}"
line_comment
// "//"
outer_doc_comment_marker
/ "/"
doc_comment " Creates a context by searching standard directories\n"
function_item
visibility_modifier
pub "pub"
fn "fn"
identifier "load"
parameters
( "("
parameter
identifier "config"
: ":"
reference_type
& "&"
type_identifier "Config"
) ")"
-> "->"
generic_type
type_identifier "Result"
type_arguments
< "<"
type_identifier "Context"
> ">"
block
{ "{"
let_declaration (+22 nodes collapsed)
expression_statement (+49 nodes collapsed)
expression_statement (+42 nodes collapsed)
line_comment
// "//"
let_declaration (+22 nodes collapsed)
let_declaration (+58 nodes collapsed)
line_comment
// "//"
let_declaration (+43 nodes collapsed)
let_declaration
let "let"
mutable_specifier "mut"
identifier "ctx"
= "="
call_expression
scoped_identifier
identifier "Context"
:: "::"
identifier "new"
arguments
( "("
) ")"
; ";"
expression_statement
assignment_expression
field_expression
identifier "ctx"
. "."
field_identifier "save_previous_result"
= "="
boolean_literal
true "true"
; ";"
expression_statement (+46 nodes collapsed)
expression_statement (+21 nodes collapsed)
line_comment
// "//"
expression_statement
if_expression
if "if"
field_expression
field_expression
identifier "config"
. "."
field_identifier "currency"
. "."
field_identifier "enabled"
block
{ "{"
expression_statement
match_expression
match "match"
call_expression
identifier "try_load_currency"
arguments
( "("
reference_expression
& "&"
field_expression
identifier "config"
. "."
field_identifier "currency"
, ","
reference_expression
& "&"
mutable_specifier "mut"
identifier "ctx"
, ","
reference_expression
& "&"
identifier "search_path"
) ")"
match_block
{ "{"
match_arm
match_pattern
tuple_struct_pattern
identifier "Ok"
( "("
tuple_pattern
( "("
) ")"
) ")"
=> "=>"
unit_expression
( "("
) ")"
, ","
match_arm
match_pattern
tuple_struct_pattern
identifier "Err"
( "("
identifier "err"
) ")"
=> "=>"
block (+26 nodes collapsed)
} "}"
} "}"
call_expression
identifier "Ok"
arguments
( "("
identifier "ctx"
) ")"
} "}"
function_item
fn "fn"
identifier "read_if_current"
parameters
( "("
parameter
identifier "file"
: ":"
type_identifier "File"
, ","
parameter
identifier "expiration"
: ":"
generic_type
type_identifier "Option"
type_arguments
< "<"
type_identifier "Duration"
> ">"
) ")"
-> "->"
generic_type
type_identifier "Result"
type_arguments
< "<"
type_identifier "File"
> ">"
block
{ "{"
use_declaration
use "use"
scoped_identifier
scoped_identifier
identifier "std"
:: "::"
identifier "time"
:: "::"
identifier "SystemTime"
; ";"
let_declaration
let "let"
identifier "stats"
= "="
try_expression
call_expression
field_expression
identifier "file"
. "."
field_identifier "metadata"
arguments
( "("
) ")"
? "?"
; ";"
let_declaration
let "let"
identifier "mtime"
= "="
try_expression
call_expression
field_expression
identifier "stats"
. "."
field_identifier "modified"
arguments
( "("
) ")"
? "?"
; ";"
let_declaration
let "let"
identifier "now"
= "="
call_expression
scoped_identifier
identifier "SystemTime"
:: "::"
identifier "now"
arguments
( "("
) ")"
; ";"
let_declaration
let "let"
identifier "elapsed"
= "="
try_expression
call_expression
field_expression
identifier "now"
. "."
field_identifier "duration_since"
arguments
( "("
identifier "mtime"
) ")"
? "?"
; ";"
expression_statement
if_expression
if "if"
let_condition
let "let"
tuple_struct_pattern
identifier "Some"
( "("
identifier "expiration"
) ")"
= "="
identifier "expiration"
block
{ "{"
expression_statement
if_expression
if "if"
binary_expression
identifier "elapsed"
> ">"
identifier "expiration"
block
{ "{"
expression_statement
return_expression
return "return"
call_expression
identifier "Err"
arguments
( "("
macro_invocation
identifier "eyre"
! "!"
token_tree
( "("
string_literal
" "\""
string_content "File is out of date"
" "\""
) ")"
) ")"
; ";"
} "}"
} "}"
call_expression
identifier "Ok"
arguments
( "("
identifier "file"
) ")"
} "}"
function_item
visibility_modifier
pub "pub"
fn "fn"
identifier "download_to_file"
parameters
( "("
parameter
identifier "path"
: ":"
reference_type
& "&"
type_identifier "Path"
, ","
parameter
identifier "url"
: ":"
reference_type
& "&"
primitive_type "str"
, ","
parameter
identifier "timeout"
: ":"
type_identifier "Duration"
) ")"
-> "->"
generic_type
type_identifier "Result"
type_arguments
< "<"
type_identifier "File"
> ">"
block
{ "{"
use_declaration
use "use"
scoped_identifier
scoped_identifier
identifier "std"
:: "::"
identifier "fs"
:: "::"
identifier "create_dir_all"
; ";"
expression_statement
try_expression
call_expression
identifier "create_dir_all"
arguments
( "("
call_expression
field_expression
call_expression
field_expression
identifier "path"
. "."
field_identifier "parent"
arguments
( "("
) ")"
. "."
field_identifier "unwrap"
arguments
( "("
) ")"
) ")"
? "?"
; ";"
line_comment
// "//"
line_comment
// "//"
let_declaration
let "let"
mutable_specifier "mut"
identifier "prefix"
= "="
call_expression
field_expression
call_expression
field_expression
call_expression
field_expression
identifier "path"
. "."
field_identifier "file_stem"
arguments
( "("
) ")"
. "."
field_identifier "unwrap"
arguments
( "("
) ")"
. "."
field_identifier "to_owned"
arguments
( "("
) ")"
; ";"
expression_statement
call_expression
field_expression
identifier "prefix"
. "."
field_identifier "push"
arguments
( "("
string_literal
" "\""
string_content "."
" "\""
) ")"
; ";"
let_declaration
let "let"
mutable_specifier "mut"
identifier "suffix"
= "="
call_expression
scoped_identifier
identifier "OsString"
:: "::"
identifier "from"
arguments
( "("
string_literal
" "\""
string_content "."
" "\""
) ")"
; ";"
expression_statement
call_expression
field_expression
identifier "suffix"
. "."
field_identifier "push"
arguments
( "("
call_expression
field_expression
call_expression
field_expression
identifier "path"
. "."
field_identifier "extension"
arguments
( "("
) ")"
. "."
field_identifier "unwrap"
arguments
( "("
) ")"
) ")"
; ";"
line_comment
// "//"
line_comment
// "//"
line_comment
// "//"
let_declaration
let "let"
mutable_specifier "mut"
identifier "temp_file"
= "="
try_expression
call_expression
field_expression
call_expression
field_expression
call_expression
field_expression
call_expression
scoped_identifier
scoped_identifier
identifier "tempfile"
:: "::"
identifier "Builder"
:: "::"
identifier "new"
arguments
( "("
) ")"
. "."
field_identifier "prefix"
arguments
( "("
reference_expression
& "&"
identifier "prefix"
) ")"
. "."
field_identifier "suffix"
arguments
( "("
reference_expression
& "&"
identifier "suffix"
) ")"
. "."
field_identifier "tempfile_in"
arguments
( "("
call_expression
field_expression
call_expression
field_expression
identifier "path"
. "."
field_identifier "parent"
arguments
( "("
) ")"
. "."
field_identifier "unwrap"
arguments
( "("
) ")"
) ")"
? "?"
; ";"
let_declaration
let "let"
mutable_specifier "mut"
identifier "easy"
= "="
call_expression
scoped_identifier
identifier "Easy"
:: "::"
identifier "new"
arguments
( "("
) ")"
; ";"
expression_statement
try_expression
call_expression
field_expression
identifier "easy"
. "."
field_identifier "url"
arguments
( "("
identifier "url"
) ")"
? "?"
; ";"
expression_statement
try_expression
call_expression
field_expression
identifier "easy"
. "."
field_identifier "useragent"
arguments
( "("
reference_expression
& "&"
macro_invocation
identifier "format"
! "!"
token_tree
( "("
string_literal
" "\""
string_content "rink-cli {} <{}>"
" "\""
, ","
identifier "env"
! "!"
token_tree
( "("
string_literal
" "\""
string_content "CARGO_PKG_VERSION"
" "\""
) ")"
, ","
identifier "env"
! "!"
token_tree
( "("
string_literal
" "\""
string_content "CARGO_PKG_REPOSITORY"
" "\""
) ")"
) ")"
) ")"
? "?"
; ";"
expression_statement
try_expression
call_expression
field_expression
identifier "easy"
. "."
field_identifier "timeout"
arguments
( "("
identifier "timeout"
) ")"
? "?"
; ";"
let_declaration
let "let"
mutable_specifier "mut"
identifier "write_handle"
= "="
try_expression
call_expression
field_expression
call_expression
field_expression
identifier "temp_file"
. "."
field_identifier "as_file_mut"
arguments
( "("
) ")"
. "."
field_identifier "try_clone"
arguments
( "("
) ")"
? "?"
; ";"
expression_statement
try_expression
call_expression
field_expression
identifier "easy"
. "."
field_identifier "write_function"
arguments
( "("
closure_expression
move "move"
closure_parameters
| "|"
identifier "data"
| "|"
block
{ "{"
call_expression
field_expression
call_expression
field_expression
identifier "write_handle"
. "."
field_identifier "write"
arguments
( "("
identifier "data"
) ")"
. "."
field_identifier "map_err"
arguments
( "("
closure_expression
closure_parameters
| "|"
_ "_"
| "|"
scoped_identifier
scoped_identifier
scoped_identifier
identifier "curl"
:: "::"
identifier "easy"
:: "::"
identifier "WriteError"
:: "::"
identifier "Pause"
) ")"
} "}"
) ")"
? "?"
; ";"
expression_statement
try_expression
call_expression
field_expression
identifier "easy"
. "."
field_identifier "perform"
arguments
( "("
) ")"
? "?"
; ";"
let_declaration
let "let"
identifier "status"
= "="
try_expression
call_expression
field_expression
identifier "easy"
. "."
field_identifier "response_code"
arguments
( "("
) ")"
? "?"
; ";"
expression_statement
if_expression
if "if"
binary_expression
identifier "status"
!= "!="
integer_literal "200"
block
{ "{"
expression_statement
return_expression
return "return"
call_expression
identifier "Err"
arguments
( "("
macro_invocation
identifier "eyre"
! "!"
token_tree
( "("
string_literal
" "\""
string_content "Received status {} while downloading {}"
" "\""
, ","
identifier "status"
, ","
identifier "url"
) ")"
) ")"
; ";"
} "}"
expression_statement
try_expression
call_expression
field_expression
call_expression
field_expression
identifier "temp_file"
. "."
field_identifier "as_file_mut"
arguments
( "("
) ")"
. "."
field_identifier "sync_all"
arguments
( "("
) ")"
? "?"
; ";"
expression_statement
try_expression
call_expression
field_expression
call_expression
field_expression
identifier "temp_file"
. "."
field_identifier "as_file_mut"
arguments
( "("
) ")"
. "."
field_identifier "seek"
arguments
( "("
call_expression
scoped_identifier
identifier "SeekFrom"
:: "::"
identifier "Start"
arguments
( "("
integer_literal "0"
) ")"
) ")"
? "?"
; ";"
call_expression
field_expression
call_expression
field_expression
identifier "temp_file"
. "."
field_identifier "persist"
arguments
( "("
identifier "path"
) ")"
. "."
field_identifier "wrap_err"
arguments
( "("
string_literal
" "\""
string_content "Failed to write to cache dir"
" "\""
) ")"
} "}"
function_item
fn "fn"
identifier "cached"
parameters
( "("
parameter
identifier "filename"
: ":"
reference_type
& "&"
primitive_type "str"
, ","
parameter
identifier "url"
: ":"
reference_type
& "&"
primitive_type "str"
, ","
parameter
identifier "expiration"
: ":"
generic_type
type_identifier "Option"
type_arguments
< "<"
type_identifier "Duration"
> ">"
, ","
parameter
identifier "timeout"
: ":"
type_identifier "Duration"
, ","
) ")"
-> "->"
generic_type
type_identifier "Result"
type_arguments
< "<"
type_identifier "File"
> ">"
block
{ "{"
let_declaration
let "let"
mutable_specifier "mut"
identifier "path"
= "="
try_expression
call_expression
field_expression
call_expression
scoped_identifier
identifier "dirs"
:: "::"
identifier "cache_dir"
arguments
( "("
) ")"
. "."
field_identifier "ok_or_else"
arguments
( "("
closure_expression
closure_parameters
| "|"
| "|"
macro_invocation
identifier "eyre"
! "!"
token_tree
( "("
string_literal
" "\""
string_content "Could not find cache directory"
" "\""
) ")"
) ")"
? "?"
; ";"
expression_statement
call_expression
field_expression
identifier "path"
. "."
field_identifier "push"
arguments
( "("
string_literal
" "\""
string_content "rink"
" "\""
) ")"
; ";"
expression_statement
call_expression
field_expression
identifier "path"
. "."
field_identifier "push"
arguments
( "("
identifier "filename"
) ")"
; ";"
line_comment
// "//"
line_comment
// "//"
line_comment
// "//"
expression_statement
if_expression
if "if"
let_condition
let "let"
tuple_struct_pattern
identifier "Ok"
( "("
identifier "file"
) ")"
= "="
call_expression
scoped_identifier
identifier "File"
:: "::"
identifier "open"
arguments
( "("
reference_expression
& "&"
identifier "path"
) ")"
block
{ "{"
expression_statement
if_expression
if "if"
let_condition
let "let"
tuple_struct_pattern
identifier "Ok"
( "("
identifier "result"
) ")"
= "="
call_expression
identifier "read_if_current"
arguments
( "("
identifier "file"
, ","
identifier "expiration"
) ")"
block
{ "{"
expression_statement
return_expression
return "return"
call_expression
identifier "Ok"
arguments
( "("
identifier "result"
) ")"
; ";"
} "}"
} "}"
let_declaration
let "let"
identifier "err"
= "="
match_expression
match "match"
call_expression
identifier "download_to_file"
arguments
( "("
reference_expression
& "&"
identifier "path"
, ","
identifier "url"
, ","
identifier "timeout"
) ")"
match_block
{ "{"
match_arm
match_pattern
tuple_struct_pattern
identifier "Ok"
( "("
identifier "result"
) ")"
=> "=>"
return_expression
return "return"
call_expression
identifier "Ok"
arguments
( "("
identifier "result"
) ")"
, ","
match_arm
match_pattern
tuple_struct_pattern
identifier "Err"
( "("
identifier "err"
) ")"
=> "=>"
identifier "err"
, ","
} "}"
; ";"
expression_statement
if_expression
if "if"
let_condition
let "let"
tuple_struct_pattern
identifier "Ok"
( "("
identifier "file"
) ")"
= "="
call_expression
scoped_identifier
identifier "File"
:: "::"
identifier "open"
arguments
( "("
reference_expression
& "&"
identifier "path"
) ")"
block
{ "{"
line_comment
// "//"
expression_statement
macro_invocation
identifier "println"
! "!"
token_tree
( "("
string_literal
" "\""
string_content "{:?}"
" "\""
, ","
identifier "Report"
:: "::"
identifier "wrap_err"
token_tree
( "("
identifier "err"
, ","
identifier "format"
! "!"
token_tree
( "("
string_literal
" "\""
string_content "Failed to refresh {}, using stale version"
" "\""
, ","
identifier "url"
) ")"
) ")"
) ")"
; ";"
call_expression
identifier "Ok"
arguments
( "("
identifier "file"
) ")"
} "}"
else_clause
else "else"
block
{ "{"
call_expression
field_expression
call_expression
identifier "Err"
arguments
( "("
identifier "err"
) ")"
. "."
field_identifier "wrap_err_with"
arguments
( "("
closure_expression
closure_parameters
| "|"
| "|"
macro_invocation
identifier "format"
! "!"
token_tree
( "("
string_literal
" "\""
string_content "Failed to fetch {}"
" "\""
, ","
identifier "url"
) ")"
) ")"
} "}"
} "}"

After

source_file
line_comment
// "//"
line_comment
// "//"
line_comment
// "//"
use_declaration
use "use"
scoped_identifier
scoped_identifier
crate "crate"
:: "::"
identifier "currency"
:: "::"
identifier "load_cached_currency_if_current"
; ";"
use_declaration
use "use"
scoped_identifier
crate "crate"
:: "::"
identifier "style_ser"
; ";"
use_declaration
use "use"
scoped_identifier
identifier "color_eyre"
:: "::"
identifier "Result"
; ";"
use_declaration
use "use"
scoped_use_list
identifier "eyre"
:: "::"
use_list
{ "{"
identifier "eyre"
, ","
identifier "WrapErr"
} "}"
; ";"
use_declaration
use "use"
scoped_use_list
identifier "nu_ansi_term"
:: "::"
use_list
{ "{"
identifier "Color"
, ","
identifier "Style"
} "}"
; ";"
use_declaration
use "use"
scoped_identifier
scoped_identifier
identifier "rink_core"
:: "::"
identifier "loader"
:: "::"
identifier "gnu_units"
; ";"
use_declaration
use "use"
scoped_identifier
scoped_identifier
scoped_identifier
identifier "rink_core"
:: "::"
identifier "output"
:: "::"
identifier "fmt"
:: "::"
identifier "FmtToken"
; ";"
use_declaration
use "use"
scoped_identifier
scoped_identifier
identifier "rink_core"
:: "::"
identifier "parsing"
:: "::"
identifier "datetime"
; ";"
use_declaration
use "use"
scoped_identifier
identifier "rink_core"
:: "::"
identifier "Context"
; ";"
use_declaration
use "use"
scoped_use_list
identifier "rink_core"
:: "::"
use_list
{ "{"
identifier "DATES_FILE"
, ","
identifier "DEFAULT_FILE"
} "}"
; ";"
use_declaration
use "use"
scoped_use_list
identifier "serde_derive"
:: "::"
use_list
{ "{"
identifier "Deserialize"
, ","
identifier "Serialize"
} "}"
; ";"
use_declaration all 1:2
use "use" all 1:2
scoped_identifier
scoped_identifier
identifier "std" all 1:2
:: "::" all 1:2
identifier "collections"
:: "::"
identifier "HashMap"
; ";" all 1:2
use_declaration
use "use"
scoped_identifier
identifier "std"
:: "::"
identifier "env"
; ";"
use_declaration all 1:2
use "use" all 1:2
scoped_identifier
scoped_identifier
identifier "std" all 1:2
:: "::" all 1:2
identifier "fs"
:: "::"
identifier "read_to_string"
; ";" all 1:2
use_declaration
use "use"
scoped_identifier any 1:2
scoped_identifier any 1:2
identifier "std"
:: "::"
identifier "io"
:: "::"
identifier "ErrorKind"
; ";"
use_declaration
use "use"
scoped_identifier
scoped_identifier
identifier "std"
:: "::"
identifier "path"
:: "::"
identifier "PathBuf"
; ";"
use_declaration
use "use"
scoped_identifier
scoped_identifier
identifier "std"
:: "::"
identifier "time"
:: "::"
identifier "Duration"
; ";"
use_declaration
use "use"
scoped_identifier
identifier "ubyte"
:: "::"
identifier "ByteUnit"
; ";"
function_item
visibility_modifier
pub "pub"
fn "fn"
identifier "config_toml_path"
parameters
( "("
) ")"
-> "->"
generic_type
type_identifier "Result"
type_arguments
< "<"
type_identifier "PathBuf"
> ">"
block
{ "{"
let_declaration (+37 nodes collapsed)
expression_statement
call_expression
field_expression
identifier "path"
. "."
field_identifier "push"
arguments
( "("
string_literal
" "\""
string_content "rink"
" "\""
) ")"
; ";"
expression_statement
call_expression
field_expression
identifier "path"
. "."
field_identifier "push"
arguments
( "("
string_literal
" "\""
string_content "config.toml"
" "\""
) ")"
; ";"
call_expression
identifier "Ok"
arguments
( "("
identifier "path"
) ")"
} "}"
attribute_item
# "#"
[ "["
attribute
identifier "derive"
token_tree
( "("
identifier "Serialize"
, ","
identifier "Deserialize"
, ","
identifier "Clone"
) ")"
] "]"
attribute_item
# "#"
[ "["
attribute
identifier "serde"
token_tree
( "("
default "default"
, ","
identifier "deny_unknown_fields"
) ")"
] "]"
struct_item (+62 nodes collapsed)
attribute_item
# "#"
[ "["
attribute
identifier "derive"
token_tree
( "("
identifier "Serialize"
, ","
identifier "Deserialize"
, ","
identifier "Clone"
) ")"
] "]"
attribute_item
# "#"
[ "["
attribute
identifier "serde"
token_tree
( "("
default "default"
, ","
identifier "deny_unknown_fields"
) ")"
] "]"
struct_item (+32 nodes collapsed)
attribute_item
# "#"
[ "["
attribute
identifier "derive"
token_tree
( "("
identifier "Serialize"
, ","
identifier "Deserialize"
, ","
identifier "Clone"
, ","
identifier "PartialEq"
, ","
identifier "Eq"
) ")"
] "]"
attribute_item
# "#"
[ "["
attribute
identifier "serde"
token_tree
( "("
identifier "rename_all"
= "="
string_literal
" "\""
string_content "lowercase"
" "\""
) ")"
] "]"
enum_item
visibility_modifier
pub "pub"
enum "enum"
type_identifier "CurrencyBehavior"
enum_variant_list
{ "{"
enum_variant
identifier "Prompt"
, ","
enum_variant
identifier "Always"
, ","
} "}"
attribute_item
# "#"
[ "["
attribute
identifier "derive"
token_tree
( "("
identifier "Serialize"
, ","
identifier "Deserialize"
, ","
identifier "Clone"
) ")"
] "]"
attribute_item
# "#"
[ "["
attribute
identifier "serde"
token_tree
( "("
default "default"
, ","
identifier "deny_unknown_fields"
) ")"
] "]"
struct_item
visibility_modifier
pub "pub"
struct "struct"
type_identifier "Currency"
field_declaration_list
{ "{"
line_comment
// "//"
outer_doc_comment_marker
/ "/"
doc_comment " Set behavior for currency fetching, whether to prompt.\n"
field_declaration
visibility_modifier
pub "pub"
field_identifier "behavior"
: ":"
type_identifier "CurrencyBehavior"
, ","
line_comment
// "//"
outer_doc_comment_marker
/ "/"
doc_comment " Set to false to disable currency loading entirely.\n"
field_declaration
visibility_modifier
pub "pub"
field_identifier "enabled"
: ":"
primitive_type "bool"
, ","
line_comment
// "//"
outer_doc_comment_marker
/ "/"
doc_comment " Set to false to only reuse the existing cached currency dat"...
attribute_item
# "#"
[ "["
attribute
identifier "serde"
token_tree
( "("
identifier "alias"
= "="
string_literal
" "\""
string_content "fetch_on_startup"
" "\""
) ")"
] "]"
field_declaration
visibility_modifier
pub "pub"
field_identifier "cache_expiration_enabled"
: ":"
primitive_type "bool"
, ","
line_comment
// "//"
outer_doc_comment_marker
/ "/"
doc_comment " Which web endpoint should be used to download currency data"...
field_declaration
visibility_modifier
pub "pub"
field_identifier "endpoint"
: ":"
type_identifier "String"
, ","
line_comment
// "//"
outer_doc_comment_marker
/ "/"
doc_comment " How long to cache for.\n"
attribute_item
# "#"
[ "["
attribute
identifier "serde"
token_tree
( "("
identifier "with"
= "="
string_literal
" "\""
string_content "humantime_serde"
" "\""
) ")"
] "]"
field_declaration
visibility_modifier
pub "pub"
field_identifier "cache_duration"
: ":"
type_identifier "Duration"
, ","
line_comment
// "//"
outer_doc_comment_marker
/ "/"
doc_comment " How long to wait before timing out requests.\n"
attribute_item
# "#"
[ "["
attribute
identifier "serde"
token_tree
( "("
identifier "with"
= "="
string_literal
" "\""
string_content "humantime_serde"
" "\""
) ")"
] "]"
field_declaration
visibility_modifier
pub "pub"
field_identifier "timeout"
: ":"
type_identifier "Duration"
, ","
} "}"
attribute_item
# "#"
[ "["
attribute
identifier "derive"
token_tree
( "("
identifier "Serialize"
, ","
identifier "Deserialize"
, ","
identifier "Clone"
) ")"
] "]"
attribute_item
# "#"
[ "["
attribute
identifier "serde"
token_tree
( "("
default "default"
, ","
identifier "deny_unknown_fields"
) ")"
] "]"
struct_item (+37 nodes collapsed)
attribute_item
# "#"
[ "["
attribute
identifier "derive"
token_tree
( "("
identifier "Serialize"
, ","
identifier "Deserialize"
, ","
identifier "Clone"
) ")"
] "]"
attribute_item
# "#"
[ "["
attribute
identifier "serde"
token_tree
( "("
default "default"
, ","
identifier "deny_unknown_fields"
) ")"
] "]"
struct_item (+71 nodes collapsed)
attribute_item
# "#"
[ "["
attribute
identifier "derive"
token_tree
( "("
identifier "Serialize"
, ","
identifier "Deserialize"
, ","
identifier "Default"
, ","
identifier "Clone"
) ")"
] "]"
attribute_item
# "#"
[ "["
attribute
identifier "serde"
token_tree
( "("
default "default"
, ","
identifier "deny_unknown_fields"
) ")"
] "]"
struct_item (+228 nodes collapsed)
impl_item (+192 nodes collapsed)
impl_item (+313 nodes collapsed)
impl_item
impl "impl"
type_identifier "Default"
for "for"
type_identifier "Currency"
declaration_list
{ "{"
function_item
fn "fn"
identifier "default"
parameters
( "("
) ")"
-> "->"
type_identifier "Self"
block
{ "{"
struct_expression
type_identifier "Currency"
field_initializer_list
{ "{"
field_initializer
field_identifier "behavior"
: ":"
scoped_identifier
identifier "CurrencyBehavior"
:: "::"
identifier "Prompt"
, ","
field_initializer
field_identifier "enabled"
: ":"
boolean_literal
true "true"
, ","
field_initializer
field_identifier "cache_expiration_enabled"
: ":"
boolean_literal
true "true"
, ","
field_initializer
field_identifier "endpoint"
: ":"
call_expression
field_expression
string_literal
" "\""
string_content "https://rinkcalc.app/data/currency.json"
" "\""
. "."
field_identifier "to_owned"
arguments
( "("
) ")"
, ","
field_initializer
field_identifier "cache_duration"
: ":"
call_expression
scoped_identifier
identifier "Duration"
:: "::"
identifier "from_secs"
arguments
( "("
binary_expression
integer_literal "60"
* "*"
integer_literal "60"
) ")"
, ","
line_comment
// "//"
field_initializer
field_identifier "timeout"
: ":"
call_expression
scoped_identifier
identifier "Duration"
:: "::"
identifier "from_secs"
arguments
( "("
integer_literal "15"
) ")"
, ","
} "}"
} "}"
} "}"
impl_item
impl "impl"
type_identifier "Currency"
declaration_list
{ "{"
function_item
visibility_modifier
pub "pub"
fn "fn"
identifier "expiration"
parameters
( "("
self_parameter
& "&"
self "self"
) ")"
-> "->"
generic_type
type_identifier "Option"
type_arguments
< "<"
type_identifier "Duration"
> ">"
block
{ "{"
expression_statement
if_expression
if "if"
field_expression
self "self"
. "."
field_identifier "cache_expiration_enabled"
block
{ "{"
call_expression
identifier "Some"
arguments
( "("
field_expression
self "self"
. "."
field_identifier "cache_duration"
) ")"
} "}"
else_clause
else "else"
block
{ "{"
identifier "None"
} "}"
} "}"
} "}"
impl_item (+45 nodes collapsed)
impl_item (+44 nodes collapsed)
impl_item (+62 nodes collapsed)
impl_item (+138 nodes collapsed)
function_item
visibility_modifier
pub "pub"
( "("
crate "crate"
) ")"
fn "fn"
identifier "read_from_search_path"
parameters (+35 nodes collapsed)
-> "->"
generic_type
type_identifier "Result"
type_arguments
< "<"
generic_type
type_identifier "Vec"
type_arguments
< "<"
type_identifier "String"
> ">"
> ">"
block (+196 nodes collapsed)
function_item
visibility_modifier
pub "pub"
fn "fn"
identifier "read_config"
parameters
( "("
parameter
identifier "override_path"
: ":"
generic_type
type_identifier "Option"
type_arguments
< "<"
reference_type
& "&"
primitive_type "str"
> ">"
) ")"
-> "->"
generic_type
type_identifier "Result"
type_arguments
< "<"
type_identifier "Config"
> ">"
block
{ "{"
let_declaration
let "let"
identifier "path"
= "="
if_expression
if "if"
let_condition
let "let"
tuple_struct_pattern
identifier "Some"
( "("
identifier "path"
) ")"
= "="
identifier "override_path"
block
{ "{"
call_expression
scoped_identifier
identifier "PathBuf"
:: "::"
identifier "from"
arguments
( "("
identifier "path"
) ")"
} "}"
else_clause
else "else"
block
{ "{"
try_expression
call_expression
identifier "config_toml_path"
arguments
( "("
) ")"
? "?"
} "}"
; ";"
expression_statement (+173 nodes collapsed)
} "}"
line_comment
// "//"
outer_doc_comment_marker
/ "/"
doc_comment " Creates a context by searching standard directories\n"
function_item
visibility_modifier
pub "pub"
fn "fn"
identifier "load"
parameters
( "("
parameter
identifier "config"
: ":"
reference_type
& "&"
type_identifier "Config"
) ")"
-> "->"
generic_type
type_identifier "Result"
type_arguments
< "<"
type_identifier "Context"
> ">"
block
{ "{"
let_declaration (+22 nodes collapsed)
expression_statement (+49 nodes collapsed)
expression_statement (+42 nodes collapsed)
line_comment
// "//"
let_declaration (+22 nodes collapsed)
let_declaration (+58 nodes collapsed)
line_comment
// "//"
let_declaration (+43 nodes collapsed)
let_declaration
let "let"
mutable_specifier "mut"
identifier "ctx"
= "="
call_expression
scoped_identifier
identifier "Context"
:: "::"
identifier "new"
arguments
( "("
) ")"
; ";"
expression_statement
assignment_expression
field_expression
identifier "ctx"
. "."
field_identifier "save_previous_result"
= "="
boolean_literal
true "true"
; ";"
expression_statement (+46 nodes collapsed)
expression_statement (+21 nodes collapsed)
line_comment
// "//"
expression_statement
if_expression
if "if"
field_expression
field_expression
identifier "config"
. "."
field_identifier "currency"
. "."
field_identifier "enabled"
block
{ "{"
let_declaration
let "let"
identifier "res"
= "="
call_expression
identifier "load_cached_currency_if_current"
arguments
( "("
reference_expression
& "&"
field_expression
identifier "config"
. "."
field_identifier "currency"
, ","
reference_expression
& "&"
mutable_specifier "mut"
identifier "ctx"
, ","
reference_expression
& "&"
identifier "search_path"
) ")"
; ";"
expression_statement
if_expression
if "if"
let_condition
let "let"
tuple_struct_pattern
identifier "Err"
( "("
identifier "err"
) ")"
= "="
identifier "res"
block (+26 nodes collapsed)
} "}"
call_expression
identifier "Ok"
arguments
( "("
identifier "ctx"
) ")"
} "}"