Use system tzinfo instead of compiling it into the binary
This commit is contained in:
parent
36aa6abcb9
commit
d7df47eba4
6 changed files with 46 additions and 207 deletions
175
Cargo.lock
generated
175
Cargo.lock
generated
|
|
@ -73,12 +73,6 @@ version = "1.4.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
|
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cfg-if"
|
|
||||||
version = "1.0.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "chrono"
|
name = "chrono"
|
||||||
version = "0.4.19"
|
version = "0.4.19"
|
||||||
|
|
@ -92,28 +86,6 @@ dependencies = [
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "chrono-tz"
|
|
||||||
version = "0.6.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "64c01c1c607d25c71bbaa67c113d6c6b36c434744b4fd66691d711b5b1bc0c8b"
|
|
||||||
dependencies = [
|
|
||||||
"chrono",
|
|
||||||
"chrono-tz-build",
|
|
||||||
"phf",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "chrono-tz-build"
|
|
||||||
version = "0.0.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "db058d493fb2f65f41861bfed7e3fe6335264a9f0f92710cab5bdf01fef09069"
|
|
||||||
dependencies = [
|
|
||||||
"parse-zoneinfo",
|
|
||||||
"phf",
|
|
||||||
"phf_codegen",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "2.33.3"
|
version = "2.33.3"
|
||||||
|
|
@ -153,17 +125,6 @@ dependencies = [
|
||||||
"typenum",
|
"typenum",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "getrandom"
|
|
||||||
version = "0.2.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
"libc",
|
|
||||||
"wasi",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "heck"
|
name = "heck"
|
||||||
version = "0.3.3"
|
version = "0.3.3"
|
||||||
|
|
@ -225,15 +186,6 @@ version = "0.2.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
|
checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "parse-zoneinfo"
|
|
||||||
version = "0.3.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c705f256449c60da65e11ff6626e0c16a0a0b96aaa348de61376b249bc340f41"
|
|
||||||
dependencies = [
|
|
||||||
"regex",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pest"
|
name = "pest"
|
||||||
version = "2.1.3"
|
version = "2.1.3"
|
||||||
|
|
@ -277,51 +229,6 @@ dependencies = [
|
||||||
"sha-1",
|
"sha-1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "phf"
|
|
||||||
version = "0.10.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b9fc3db1018c4b59d7d582a739436478b6035138b6aecbce989fc91c3e98409f"
|
|
||||||
dependencies = [
|
|
||||||
"phf_shared",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "phf_codegen"
|
|
||||||
version = "0.10.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd"
|
|
||||||
dependencies = [
|
|
||||||
"phf_generator",
|
|
||||||
"phf_shared",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "phf_generator"
|
|
||||||
version = "0.10.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6"
|
|
||||||
dependencies = [
|
|
||||||
"phf_shared",
|
|
||||||
"rand",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "phf_shared"
|
|
||||||
version = "0.10.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096"
|
|
||||||
dependencies = [
|
|
||||||
"siphasher",
|
|
||||||
"uncased",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ppv-lite86"
|
|
||||||
version = "0.2.15"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro-error"
|
name = "proc-macro-error"
|
||||||
version = "1.0.4"
|
version = "1.0.4"
|
||||||
|
|
@ -364,61 +271,6 @@ dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rand"
|
|
||||||
version = "0.8.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8"
|
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
"rand_chacha",
|
|
||||||
"rand_core",
|
|
||||||
"rand_hc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rand_chacha"
|
|
||||||
version = "0.3.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
|
||||||
dependencies = [
|
|
||||||
"ppv-lite86",
|
|
||||||
"rand_core",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rand_core"
|
|
||||||
version = "0.6.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
|
|
||||||
dependencies = [
|
|
||||||
"getrandom",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rand_hc"
|
|
||||||
version = "0.3.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7"
|
|
||||||
dependencies = [
|
|
||||||
"rand_core",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "regex"
|
|
||||||
version = "1.5.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
|
|
||||||
dependencies = [
|
|
||||||
"regex-syntax",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "regex-syntax"
|
|
||||||
version = "0.6.25"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sha-1"
|
name = "sha-1"
|
||||||
version = "0.8.2"
|
version = "0.8.2"
|
||||||
|
|
@ -431,12 +283,6 @@ dependencies = [
|
||||||
"opaque-debug",
|
"opaque-debug",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "siphasher"
|
|
||||||
version = "0.3.7"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "533494a8f9b724d33625ab53c6c4800f7cc445895924a8ef649222dcb76e938b"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strsim"
|
name = "strsim"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
|
|
@ -524,11 +370,11 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"chrono",
|
"chrono",
|
||||||
"chrono-tz",
|
|
||||||
"pest",
|
"pest",
|
||||||
"pest_derive",
|
"pest_derive",
|
||||||
"structopt",
|
"structopt",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
"tzfile",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -537,21 +383,22 @@ version = "1.14.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec"
|
checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tzfile"
|
||||||
|
version = "0.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f59c22c42a2537e4c7ad21a4007273bbc5bebed7f36bc93730a5780e22a4592e"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
|
"chrono",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ucd-trie"
|
name = "ucd-trie"
|
||||||
version = "0.1.3"
|
version = "0.1.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
|
checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "uncased"
|
|
||||||
version = "0.9.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5baeed7327e25054889b9bd4f975f32e5f4c5d434042d59ab6cd4142c0a76ed0"
|
|
||||||
dependencies = [
|
|
||||||
"version_check",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-segmentation"
|
name = "unicode-segmentation"
|
||||||
version = "1.8.0"
|
version = "1.8.0"
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,8 @@ edition = "2018"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.45"
|
anyhow = "1.0.45"
|
||||||
chrono = "0.4.19"
|
chrono = "0.4.19"
|
||||||
chrono-tz = "0.6.0"
|
|
||||||
pest = "2.1.3"
|
pest = "2.1.3"
|
||||||
pest_derive = "2.1.0"
|
pest_derive = "2.1.0"
|
||||||
structopt = "0.3.25"
|
structopt = "0.3.25"
|
||||||
thiserror = "1.0.30"
|
thiserror = "1.0.30"
|
||||||
|
tzfile = "0.1.3"
|
||||||
|
|
|
||||||
58
src/files.rs
58
src/files.rs
|
|
@ -2,7 +2,8 @@ use std::collections::HashMap;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::{fs, io, result};
|
use std::{fs, io, result};
|
||||||
|
|
||||||
use chrono_tz::Tz;
|
use chrono::{DateTime, Utc};
|
||||||
|
use tzfile::Tz;
|
||||||
|
|
||||||
use self::commands::File;
|
use self::commands::File;
|
||||||
|
|
||||||
|
|
@ -13,7 +14,7 @@ mod parse;
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Files {
|
pub struct Files {
|
||||||
files: HashMap<PathBuf, File>,
|
files: HashMap<PathBuf, File>,
|
||||||
timezone: Option<Tz>,
|
timezone: Tz,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
|
@ -25,9 +26,9 @@ pub enum Error {
|
||||||
#[error("{file1} has time zone {tz1} but {file2} has time zone {tz2}")]
|
#[error("{file1} has time zone {tz1} but {file2} has time zone {tz2}")]
|
||||||
TzConflict {
|
TzConflict {
|
||||||
file1: PathBuf,
|
file1: PathBuf,
|
||||||
tz1: Tz,
|
tz1: String,
|
||||||
file2: PathBuf,
|
file2: PathBuf,
|
||||||
tz2: Tz,
|
tz2: String,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -35,20 +36,15 @@ pub type Result<T> = result::Result<T, Error>;
|
||||||
|
|
||||||
impl Files {
|
impl Files {
|
||||||
pub fn load(path: &Path) -> Result<Self> {
|
pub fn load(path: &Path) -> Result<Self> {
|
||||||
let mut new = Self {
|
let mut files = HashMap::new();
|
||||||
files: HashMap::new(),
|
Self::load_file(&mut files, path)?;
|
||||||
timezone: None,
|
let timezone = Self::determine_timezone(&files)?;
|
||||||
};
|
Ok(Self { files, timezone })
|
||||||
|
|
||||||
new.load_file(path)?;
|
|
||||||
new.determine_timezone()?;
|
|
||||||
|
|
||||||
Ok(new)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_file(&mut self, path: &Path) -> Result<()> {
|
fn load_file(files: &mut HashMap<PathBuf, File>, path: &Path) -> Result<()> {
|
||||||
let canon_path = path.canonicalize()?;
|
let canon_path = path.canonicalize()?;
|
||||||
if self.files.contains_key(&canon_path) {
|
if files.contains_key(&canon_path) {
|
||||||
// We've already loaded this exact file.
|
// We've already loaded this exact file.
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
@ -57,39 +53,43 @@ impl Files {
|
||||||
let file = parse::parse(path, &content)?;
|
let file = parse::parse(path, &content)?;
|
||||||
let includes = file.includes.clone();
|
let includes = file.includes.clone();
|
||||||
|
|
||||||
self.files.insert(canon_path, file);
|
files.insert(canon_path, file);
|
||||||
|
|
||||||
for include in includes {
|
for include in includes {
|
||||||
self.load_file(&include)?;
|
Self::load_file(files, &include)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn determine_timezone(&mut self) -> Result<()> {
|
fn determine_timezone(files: &HashMap<PathBuf, File>) -> Result<Tz> {
|
||||||
let mut found: Option<(PathBuf, Tz)> = None;
|
let mut found: Option<(PathBuf, String)> = None;
|
||||||
|
|
||||||
for file in self.files.values() {
|
for file in files.values() {
|
||||||
if let Some(file_tz) = file.timezone {
|
if let Some(file_tz) = &file.timezone {
|
||||||
if let Some((found_name, found_tz)) = &found {
|
if let Some((found_name, found_tz)) = &found {
|
||||||
if *found_tz != file_tz {
|
if found_tz != file_tz {
|
||||||
return Err(Error::TzConflict {
|
return Err(Error::TzConflict {
|
||||||
file1: found_name.clone(),
|
file1: found_name.clone(),
|
||||||
tz1: *found_tz,
|
tz1: found_tz.clone(),
|
||||||
file2: file.name.clone(),
|
file2: file.name.clone(),
|
||||||
tz2: file_tz,
|
tz2: file_tz.clone(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
found = Some((file.name.clone(), file_tz));
|
found = Some((file.name.clone(), file_tz.clone()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some((_, tz)) = found {
|
Ok(if let Some((_, tz)) = found {
|
||||||
self.timezone = Some(tz);
|
Tz::named(&tz)?
|
||||||
}
|
} else {
|
||||||
|
Tz::local()?
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
pub fn now(&self) -> DateTime<&Tz> {
|
||||||
|
Utc::now().with_timezone(&&self.timezone)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use chrono::NaiveDate;
|
use chrono::NaiveDate;
|
||||||
use chrono_tz::Tz;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Time {
|
pub struct Time {
|
||||||
|
|
@ -331,6 +330,6 @@ pub enum Command {
|
||||||
pub struct File {
|
pub struct File {
|
||||||
pub name: PathBuf,
|
pub name: PathBuf,
|
||||||
pub includes: Vec<PathBuf>,
|
pub includes: Vec<PathBuf>,
|
||||||
pub timezone: Option<Tz>,
|
pub timezone: Option<String>,
|
||||||
pub commands: Vec<Command>,
|
pub commands: Vec<Command>,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ use std::path::{Path, PathBuf};
|
||||||
use std::result;
|
use std::result;
|
||||||
|
|
||||||
use chrono::NaiveDate;
|
use chrono::NaiveDate;
|
||||||
use chrono_tz::Tz;
|
|
||||||
use pest::error::ErrorVariant;
|
use pest::error::ErrorVariant;
|
||||||
use pest::iterators::Pair;
|
use pest::iterators::Pair;
|
||||||
use pest::prec_climber::{Assoc, Operator, PrecClimber};
|
use pest::prec_climber::{Assoc, Operator, PrecClimber};
|
||||||
|
|
@ -38,15 +37,9 @@ fn parse_include(p: Pair<Rule>) -> String {
|
||||||
p.into_inner().next().unwrap().as_str().to_string()
|
p.into_inner().next().unwrap().as_str().to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_timezone(p: Pair<Rule>) -> Result<Tz> {
|
fn parse_timezone(p: Pair<Rule>) -> String {
|
||||||
assert_eq!(p.as_rule(), Rule::timezone);
|
assert_eq!(p.as_rule(), Rule::timezone);
|
||||||
let span = p.as_span();
|
p.into_inner().next().unwrap().as_str().trim().to_string()
|
||||||
p.into_inner()
|
|
||||||
.next()
|
|
||||||
.unwrap()
|
|
||||||
.as_str()
|
|
||||||
.parse()
|
|
||||||
.map_err(|_| error(span, "invalid timezone"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_number(p: Pair<Rule>) -> i32 {
|
fn parse_number(p: Pair<Rule>) -> i32 {
|
||||||
|
|
@ -720,7 +713,7 @@ fn parse_command(p: Pair<Rule>, file: &mut File) -> Result<()> {
|
||||||
file.includes.push(parent.join(parse_include(p)));
|
file.includes.push(parent.join(parse_include(p)));
|
||||||
}
|
}
|
||||||
Rule::timezone => match file.timezone {
|
Rule::timezone => match file.timezone {
|
||||||
None => file.timezone = Some(parse_timezone(p)?),
|
None => file.timezone = Some(parse_timezone(p)),
|
||||||
Some(_) => fail(p.as_span(), "cannot set timezone multiple times")?,
|
Some(_) => fail(p.as_span(), "cannot set timezone multiple times")?,
|
||||||
},
|
},
|
||||||
Rule::task => file.commands.push(Command::Task(parse_task(p)?)),
|
Rule::task => file.commands.push(Command::Task(parse_task(p)?)),
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,6 @@ pub struct Opt {
|
||||||
fn main() -> anyhow::Result<()> {
|
fn main() -> anyhow::Result<()> {
|
||||||
let opt = Opt::from_args();
|
let opt = Opt::from_args();
|
||||||
let files = Files::load(&opt.file)?;
|
let files = Files::load(&opt.file)?;
|
||||||
println!("{:#?}", files);
|
println!("{}", files.now().format("%F %T %Z"));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue