Compare commits
21 Commits
f9974e79a7
...
ef057eca66
Author | SHA1 | Date |
---|---|---|
Savanni D'Gerinel | ef057eca66 | |
Savanni D'Gerinel | c70e1d943d | |
Savanni D'Gerinel | 7711f68993 | |
Savanni D'Gerinel | f13b3effd6 | |
Savanni D'Gerinel | 4cdd2b6b0f | |
Savanni D'Gerinel | 6c831567eb | |
Savanni D'Gerinel | 0afe0c1b88 | |
Savanni D'Gerinel | e0f3cdb50a | |
Savanni D'Gerinel | efac7e43eb | |
Savanni D'Gerinel | 37c60e4346 | |
Savanni D'Gerinel | 2084061526 | |
Savanni D'Gerinel | 79422b5c7a | |
Savanni D'Gerinel | 3f2feee4dd | |
Savanni D'Gerinel | 5443015868 | |
Savanni D'Gerinel | 49b1865818 | |
Savanni D'Gerinel | 10849687e3 | |
Savanni D'Gerinel | d441e19479 | |
Savanni D'Gerinel | 5496e9ce10 | |
Savanni D'Gerinel | 7b6b7ec011 | |
Savanni D'Gerinel | e657320b28 | |
Savanni D'Gerinel | bdcd7ee18e |
|
@ -65,15 +65,6 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ansi_term"
|
|
||||||
version = "0.12.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
|
|
||||||
dependencies = [
|
|
||||||
"winapi",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstream"
|
name = "anstream"
|
||||||
version = "0.6.4"
|
version = "0.6.4"
|
||||||
|
@ -137,17 +128,6 @@ dependencies = [
|
||||||
"num-traits",
|
"num-traits",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "atty"
|
|
||||||
version = "0.2.14"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
|
||||||
dependencies = [
|
|
||||||
"hermit-abi 0.1.19",
|
|
||||||
"libc",
|
|
||||||
"winapi",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
version = "0.1.8"
|
version = "0.1.8"
|
||||||
|
@ -370,21 +350,6 @@ dependencies = [
|
||||||
"phf_codegen 0.11.2",
|
"phf_codegen 0.11.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "clap"
|
|
||||||
version = "2.34.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
|
|
||||||
dependencies = [
|
|
||||||
"ansi_term",
|
|
||||||
"atty",
|
|
||||||
"bitflags 1.3.2",
|
|
||||||
"strsim 0.8.0",
|
|
||||||
"textwrap",
|
|
||||||
"unicode-width",
|
|
||||||
"vec_map",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.4.6"
|
version = "4.4.6"
|
||||||
|
@ -404,7 +369,7 @@ dependencies = [
|
||||||
"anstream",
|
"anstream",
|
||||||
"anstyle",
|
"anstyle",
|
||||||
"clap_lex",
|
"clap_lex",
|
||||||
"strsim 0.10.0",
|
"strsim",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -857,7 +822,7 @@ dependencies = [
|
||||||
"build_html",
|
"build_html",
|
||||||
"bytes",
|
"bytes",
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap 4.4.6",
|
"clap",
|
||||||
"cookie",
|
"cookie",
|
||||||
"cool_asserts",
|
"cool_asserts",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
|
@ -1642,15 +1607,6 @@ dependencies = [
|
||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hermit-abi"
|
|
||||||
version = "0.1.19"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
|
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hermit-abi"
|
name = "hermit-abi"
|
||||||
version = "0.3.3"
|
version = "0.3.3"
|
||||||
|
@ -1969,20 +1925,11 @@ version = "0.4.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
|
checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"hermit-abi 0.3.3",
|
"hermit-abi",
|
||||||
"rustix",
|
"rustix",
|
||||||
"windows-sys",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "itertools"
|
|
||||||
version = "0.10.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
|
|
||||||
dependencies = [
|
|
||||||
"either",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itertools"
|
name = "itertools"
|
||||||
version = "0.11.0"
|
version = "0.11.0"
|
||||||
|
@ -2025,21 +1972,6 @@ dependencies = [
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "jsonwebtoken"
|
|
||||||
version = "5.0.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8d438ea707d465c230305963b67f8357a1d56fcfad9434797d7cb1c46c2e41df"
|
|
||||||
dependencies = [
|
|
||||||
"base64 0.9.3",
|
|
||||||
"chrono",
|
|
||||||
"ring",
|
|
||||||
"serde 1.0.188",
|
|
||||||
"serde_derive",
|
|
||||||
"serde_json",
|
|
||||||
"untrusted",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kifu-core"
|
name = "kifu-core"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
@ -2150,12 +2082,6 @@ dependencies = [
|
||||||
"vcpkg",
|
"vcpkg",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "linked-hash-map"
|
|
||||||
version = "0.5.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "linux-raw-sys"
|
name = "linux-raw-sys"
|
||||||
version = "0.4.8"
|
version = "0.4.8"
|
||||||
|
@ -2465,7 +2391,7 @@ version = "1.16.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
|
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"hermit-abi 0.3.3",
|
"hermit-abi",
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -2528,23 +2454,6 @@ dependencies = [
|
||||||
"vcpkg",
|
"vcpkg",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "orizentic"
|
|
||||||
version = "1.0.1"
|
|
||||||
dependencies = [
|
|
||||||
"chrono",
|
|
||||||
"clap 2.34.0",
|
|
||||||
"itertools 0.10.5",
|
|
||||||
"jsonwebtoken",
|
|
||||||
"serde 1.0.188",
|
|
||||||
"serde_derive",
|
|
||||||
"serde_json",
|
|
||||||
"thiserror",
|
|
||||||
"uuid 0.8.2",
|
|
||||||
"version_check 0.1.5",
|
|
||||||
"yaml-rust",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pango"
|
name = "pango"
|
||||||
version = "0.17.10"
|
version = "0.17.10"
|
||||||
|
@ -3210,18 +3119,6 @@ dependencies = [
|
||||||
"winreg",
|
"winreg",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ring"
|
|
||||||
version = "0.13.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "2c4db68a2e35f3497146b7e4563df7d4773a2433230c5e4b448328e31740458a"
|
|
||||||
dependencies = [
|
|
||||||
"cc",
|
|
||||||
"lazy_static",
|
|
||||||
"libc",
|
|
||||||
"untrusted",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rsa"
|
name = "rsa"
|
||||||
version = "0.9.2"
|
version = "0.9.2"
|
||||||
|
@ -3575,7 +3472,7 @@ version = "0.2.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6b7b278788e7be4d0d29c0f39497a0eef3fba6bbc8e70d8bf7fde46edeaa9e85"
|
checksum = "6b7b278788e7be4d0d29c0f39497a0eef3fba6bbc8e70d8bf7fde46edeaa9e85"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itertools 0.11.0",
|
"itertools",
|
||||||
"nom",
|
"nom",
|
||||||
"unicode_categories",
|
"unicode_categories",
|
||||||
]
|
]
|
||||||
|
@ -3785,12 +3682,6 @@ dependencies = [
|
||||||
"unicode-normalization",
|
"unicode-normalization",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "strsim"
|
|
||||||
version = "0.8.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strsim"
|
name = "strsim"
|
||||||
version = "0.10.0"
|
version = "0.10.0"
|
||||||
|
@ -3897,15 +3788,6 @@ dependencies = [
|
||||||
"winapi-util",
|
"winapi-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "textwrap"
|
|
||||||
version = "0.11.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
|
|
||||||
dependencies = [
|
|
||||||
"unicode-width",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "1.0.49"
|
version = "1.0.49"
|
||||||
|
@ -4324,12 +4206,6 @@ version = "1.10.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
|
checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "unicode-width"
|
|
||||||
version = "0.1.11"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode_categories"
|
name = "unicode_categories"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
|
@ -4345,12 +4221,6 @@ dependencies = [
|
||||||
"traitobject",
|
"traitobject",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "untrusted"
|
|
||||||
version = "0.6.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "55cd1f4b4e96b46aeb8d4855db4a7a9bd96eeeb5c6a1ab54593328761642ce2f"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "url"
|
name = "url"
|
||||||
version = "1.7.2"
|
version = "1.7.2"
|
||||||
|
@ -4420,12 +4290,6 @@ version = "0.2.15"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "vec_map"
|
|
||||||
version = "0.8.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "version-compare"
|
name = "version-compare"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
|
@ -4718,15 +4582,6 @@ dependencies = [
|
||||||
"windows-sys",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "yaml-rust"
|
|
||||||
version = "0.4.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
|
|
||||||
dependencies = [
|
|
||||||
"linked-hash-map",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zeroize"
|
name = "zeroize"
|
||||||
version = "1.6.0"
|
version = "1.6.0"
|
||||||
|
|
|
@ -17,7 +17,6 @@ members = [
|
||||||
"kifu/core",
|
"kifu/core",
|
||||||
"kifu/gtk",
|
"kifu/gtk",
|
||||||
"memorycache",
|
"memorycache",
|
||||||
"orizentic",
|
|
||||||
"screenplay",
|
"screenplay",
|
||||||
"sgf",
|
"sgf",
|
||||||
"nom-training",
|
"nom-training",
|
||||||
|
|
2
build.sh
2
build.sh
|
@ -20,7 +20,6 @@ RUST_ALL_TARGETS=(
|
||||||
"kifu-core"
|
"kifu-core"
|
||||||
"kifu-gtk"
|
"kifu-gtk"
|
||||||
"memorycache"
|
"memorycache"
|
||||||
"orizentic"
|
|
||||||
"screenplay"
|
"screenplay"
|
||||||
"sgf"
|
"sgf"
|
||||||
)
|
)
|
||||||
|
@ -39,6 +38,7 @@ build_dist() {
|
||||||
|
|
||||||
for target in $TARGETS; do
|
for target in $TARGETS; do
|
||||||
if [ -f $target/dist.sh ]; then
|
if [ -f $target/dist.sh ]; then
|
||||||
|
build_rust_targets release ${TARGETS[*]}
|
||||||
cd $target && ./dist.sh
|
cd $target && ./dist.sh
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
|
@ -14,6 +14,9 @@ case $CMD in
|
||||||
build)
|
build)
|
||||||
$CARGO build $MODULE $PARAMS
|
$CARGO build $MODULE $PARAMS
|
||||||
;;
|
;;
|
||||||
|
lint)
|
||||||
|
$CARGO clippy $MODULE $PARAMS -- -Dwarnings
|
||||||
|
;;
|
||||||
test)
|
test)
|
||||||
$CARGO test $MODULE $PARAMS
|
$CARGO test $MODULE $PARAMS
|
||||||
;;
|
;;
|
||||||
|
@ -21,16 +24,18 @@ case $CMD in
|
||||||
$CARGO run $MODULE $PARAMS
|
$CARGO run $MODULE $PARAMS
|
||||||
;;
|
;;
|
||||||
release)
|
release)
|
||||||
|
$CARGO clippy $MODULE $PARAMS -- -Dwarnings
|
||||||
$CARGO build --release $MODULE $PARAMS
|
$CARGO build --release $MODULE $PARAMS
|
||||||
|
$CARGO test --release $MODULE $PARAMS
|
||||||
;;
|
;;
|
||||||
clean)
|
clean)
|
||||||
$CARGO clean $MODULE
|
$CARGO clean $MODULE
|
||||||
;;
|
;;
|
||||||
"")
|
"")
|
||||||
echo "No command specified. Use build | test | run | release | clean"
|
echo "No command specified. Use build | lint | test | run | release | clean"
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
echo "$CMD is unknown. Use build | test | run | release | clean"
|
echo "$CMD is unknown. Use build | lint | test | run | release | clean"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,6 @@ name = "changeset"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "GPL-3.0-only"
|
license = "GPL-3.0-only"
|
||||||
license-file = "../COPYING"
|
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ pub enum Change<Key: Eq + Hash, Value> {
|
||||||
NewRecord(Value),
|
NewRecord(Value),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct Changeset<Key: Clone + Eq + Hash, Value> {
|
pub struct Changeset<Key: Clone + Eq + Hash, Value> {
|
||||||
delete: HashSet<Key>,
|
delete: HashSet<Key>,
|
||||||
update: HashMap<Key, Value>,
|
update: HashMap<Key, Value>,
|
||||||
|
@ -34,14 +34,6 @@ pub struct Changeset<Key: Clone + Eq + Hash, Value> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Key: Clone + Constructable + Eq + Hash, Value> Changeset<Key, Value> {
|
impl<Key: Clone + Constructable + Eq + Hash, Value> Changeset<Key, Value> {
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
delete: HashSet::new(),
|
|
||||||
update: HashMap::new(),
|
|
||||||
new: HashMap::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add(&mut self, r: Value) -> Key {
|
pub fn add(&mut self, r: Value) -> Key {
|
||||||
let k = Key::new();
|
let k = Key::new();
|
||||||
self.new.insert(k.clone(), r);
|
self.new.insert(k.clone(), r);
|
||||||
|
@ -90,7 +82,7 @@ impl<Key: Clone + Eq + Hash, Value> From<Changeset<Key, Value>> for Vec<Change<K
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(k, v)| Change::UpdateRecord((k, v))),
|
.map(|(k, v)| Change::UpdateRecord((k, v))),
|
||||||
)
|
)
|
||||||
.chain(new.into_iter().map(|(_, v)| Change::NewRecord(v)))
|
.chain(new.into_values().map(|v| Change::NewRecord(v)))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -100,7 +92,7 @@ mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Hash)]
|
#[derive(Clone, PartialEq, Eq, Hash, Default)]
|
||||||
struct Id(Uuid);
|
struct Id(Uuid);
|
||||||
impl Constructable for Id {
|
impl Constructable for Id {
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
|
@ -110,7 +102,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn it_generates_a_new_record() {
|
fn it_generates_a_new_record() {
|
||||||
let mut set: Changeset<Id, String> = Changeset::new();
|
let mut set: Changeset<Id, String> = Changeset::default();
|
||||||
set.add("efgh".to_string());
|
set.add("efgh".to_string());
|
||||||
let changes = Vec::from(set.clone());
|
let changes = Vec::from(set.clone());
|
||||||
assert_eq!(changes.len(), 1);
|
assert_eq!(changes.len(), 1);
|
||||||
|
@ -125,7 +117,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn it_generates_a_delete_record() {
|
fn it_generates_a_delete_record() {
|
||||||
let mut set: Changeset<Id, String> = Changeset::new();
|
let mut set: Changeset<Id, String> = Changeset::default();
|
||||||
let id1 = Id::new();
|
let id1 = Id::new();
|
||||||
set.delete(id1.clone());
|
set.delete(id1.clone());
|
||||||
let changes = Vec::from(set.clone());
|
let changes = Vec::from(set.clone());
|
||||||
|
@ -142,7 +134,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn update_unrelated_records() {
|
fn update_unrelated_records() {
|
||||||
let mut set: Changeset<Id, String> = Changeset::new();
|
let mut set: Changeset<Id, String> = Changeset::default();
|
||||||
let id1 = Id::new();
|
let id1 = Id::new();
|
||||||
let id2 = Id::new();
|
let id2 = Id::new();
|
||||||
set.update(id1.clone(), "abcd".to_owned());
|
set.update(id1.clone(), "abcd".to_owned());
|
||||||
|
@ -155,7 +147,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn delete_cancels_new() {
|
fn delete_cancels_new() {
|
||||||
let mut set: Changeset<Id, String> = Changeset::new();
|
let mut set: Changeset<Id, String> = Changeset::default();
|
||||||
let key = set.add("efgh".to_string());
|
let key = set.add("efgh".to_string());
|
||||||
set.delete(key);
|
set.delete(key);
|
||||||
let changes = Vec::from(set);
|
let changes = Vec::from(set);
|
||||||
|
@ -164,7 +156,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn delete_cancels_update() {
|
fn delete_cancels_update() {
|
||||||
let mut set: Changeset<Id, String> = Changeset::new();
|
let mut set: Changeset<Id, String> = Changeset::default();
|
||||||
let id = Id::new();
|
let id = Id::new();
|
||||||
set.update(id.clone(), "efgh".to_owned());
|
set.update(id.clone(), "efgh".to_owned());
|
||||||
set.delete(id.clone());
|
set.delete(id.clone());
|
||||||
|
@ -175,7 +167,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn update_atop_new_is_new() {
|
fn update_atop_new_is_new() {
|
||||||
let mut set: Changeset<Id, String> = Changeset::new();
|
let mut set: Changeset<Id, String> = Changeset::default();
|
||||||
let key = set.add("efgh".to_owned());
|
let key = set.add("efgh".to_owned());
|
||||||
set.update(key, "wxyz".to_owned());
|
set.update(key, "wxyz".to_owned());
|
||||||
let changes = Vec::from(set);
|
let changes = Vec::from(set);
|
||||||
|
@ -185,7 +177,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn updates_get_squashed() {
|
fn updates_get_squashed() {
|
||||||
let mut set: Changeset<Id, String> = Changeset::new();
|
let mut set: Changeset<Id, String> = Changeset::default();
|
||||||
let id1 = Id::new();
|
let id1 = Id::new();
|
||||||
let id2 = Id::new();
|
let id2 = Id::new();
|
||||||
set.update(id1.clone(), "efgh".to_owned());
|
set.update(id1.clone(), "efgh".to_owned());
|
||||||
|
|
|
@ -33,12 +33,12 @@ fn main() {
|
||||||
|
|
||||||
let filename = args
|
let filename = args
|
||||||
.next()
|
.next()
|
||||||
.map(|p| PathBuf::from(p))
|
.map(PathBuf::from)
|
||||||
.expect("A filename is required");
|
.expect("A filename is required");
|
||||||
let size = args
|
let size = args
|
||||||
.next()
|
.next()
|
||||||
.and_then(|s| s.parse::<usize>().ok())
|
.and_then(|s| s.parse::<usize>().ok())
|
||||||
.unwrap_or(3);
|
.unwrap_or(3);
|
||||||
let map: hex_map::Map<MapVal> = hex_map::Map::new_hexagonal(size);
|
let map: hex_map::Map<MapVal> = hex_map::Map::new_hexagonal(size);
|
||||||
hex_map::write_file(filename, map);
|
hex_map::write_file(filename, map).expect("to write file");
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,10 +10,9 @@ Luminescent Dreams Tools is distributed in the hope that it will be useful, but
|
||||||
You should have received a copy of the GNU General Public License along with Lumeto. If not, see <https://www.gnu.org/licenses/>.
|
You should have received a copy of the GNU General Public License along with Lumeto. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/// Ĉi-tiu modulo enhavas la elementojn por kub-koordinato.
|
/// This module contains the elements of cube coordinates.
|
||||||
///
|
///
|
||||||
/// This code is based on https://www.redblobgames.com/grids/hexagons/
|
/// This code is based on https://www.redblobgames.com/grids/hexagons/
|
||||||
use crate::Error;
|
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
/// An address within the hex coordinate system
|
/// An address within the hex coordinate system
|
||||||
|
@ -62,7 +61,7 @@ impl AxialAddr {
|
||||||
pub fn is_adjacent(&self, dest: &AxialAddr) -> bool {
|
pub fn is_adjacent(&self, dest: &AxialAddr) -> bool {
|
||||||
dest.adjacencies()
|
dest.adjacencies()
|
||||||
.collect::<Vec<AxialAddr>>()
|
.collect::<Vec<AxialAddr>>()
|
||||||
.contains(&self)
|
.contains(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Measure the distance to a destination
|
/// Measure the distance to a destination
|
||||||
|
@ -79,7 +78,7 @@ impl AxialAddr {
|
||||||
|
|
||||||
positions.push(item);
|
positions.push(item);
|
||||||
|
|
||||||
while positions.len() > 0 {
|
while !positions.is_empty() {
|
||||||
let elem = positions.remove(0);
|
let elem = positions.remove(0);
|
||||||
for adj in elem.adjacencies() {
|
for adj in elem.adjacencies() {
|
||||||
if self.distance(&adj) <= distance && !results.contains(&adj) {
|
if self.distance(&adj) <= distance && !results.contains(&adj) {
|
||||||
|
|
|
@ -14,7 +14,6 @@ use crate::{hex::AxialAddr, Error};
|
||||||
use nom::{
|
use nom::{
|
||||||
bytes::complete::tag,
|
bytes::complete::tag,
|
||||||
character::complete::alphanumeric1,
|
character::complete::alphanumeric1,
|
||||||
error::ParseError,
|
|
||||||
multi::many1,
|
multi::many1,
|
||||||
sequence::{delimited, separated_pair},
|
sequence::{delimited, separated_pair},
|
||||||
Finish, IResult, Parser,
|
Finish, IResult, Parser,
|
||||||
|
@ -81,7 +80,7 @@ pub fn parse_data<'a, A: Default + From<String>>(
|
||||||
}
|
}
|
||||||
|
|
||||||
let cells = data
|
let cells = data
|
||||||
.map(|line| parse_line::<A>(&line).unwrap())
|
.map(|line| parse_line::<A>(line).unwrap())
|
||||||
.collect::<Vec<(AxialAddr, A)>>();
|
.collect::<Vec<(AxialAddr, A)>>();
|
||||||
let cells = cells.into_iter().collect::<HashMap<AxialAddr, A>>();
|
let cells = cells.into_iter().collect::<HashMap<AxialAddr, A>>();
|
||||||
Map { cells }
|
Map { cells }
|
||||||
|
|
|
@ -9,9 +9,9 @@ Lumeto is distributed in the hope that it will be useful, but WITHOUT ANY WARRAN
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License along with Lumeto. If not, see <https://www.gnu.org/licenses/>.
|
You should have received a copy of the GNU General Public License along with Lumeto. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
use thiserror;
|
use thiserror::Error;
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error("IO error on reading or writing: {0}")]
|
#[error("IO error on reading or writing: {0}")]
|
||||||
IO(std::io::Error),
|
IO(std::io::Error),
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
name = "cyberpunk-splash"
|
name = "cyberpunk-splash"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
license = "GPL-3.0-only"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
|
|
@ -2,8 +2,8 @@ use cairo::{
|
||||||
Context, FontSlant, FontWeight, Format, ImageSurface, LineCap, LinearGradient, Pattern,
|
Context, FontSlant, FontWeight, Format, ImageSurface, LineCap, LinearGradient, Pattern,
|
||||||
TextExtents,
|
TextExtents,
|
||||||
};
|
};
|
||||||
use glib::{GString, Object};
|
use glib::Object;
|
||||||
use gtk::{gdk::Key, prelude::*, subclass::prelude::*, EventControllerKey};
|
use gtk::{prelude::*, subclass::prelude::*, EventControllerKey};
|
||||||
use std::{
|
use std::{
|
||||||
cell::RefCell,
|
cell::RefCell,
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
|
@ -14,12 +14,6 @@ use std::{
|
||||||
const WIDTH: i32 = 1600;
|
const WIDTH: i32 = 1600;
|
||||||
const HEIGHT: i32 = 600;
|
const HEIGHT: i32 = 600;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
|
||||||
enum Event {
|
|
||||||
Frames(u8),
|
|
||||||
Time(Duration),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub enum State {
|
pub enum State {
|
||||||
Running {
|
Running {
|
||||||
|
@ -50,7 +44,7 @@ impl State {
|
||||||
*self = Self::Running {
|
*self = Self::Running {
|
||||||
last_update: Instant::now(),
|
last_update: Instant::now(),
|
||||||
deadline: Instant::now() + *time_remaining,
|
deadline: Instant::now() + *time_remaining,
|
||||||
timeout: timeout.clone(),
|
timeout: *timeout,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -62,7 +56,7 @@ impl State {
|
||||||
{
|
{
|
||||||
*self = Self::Paused {
|
*self = Self::Paused {
|
||||||
time_remaining: *deadline - Instant::now(),
|
time_remaining: *deadline - Instant::now(),
|
||||||
timeout: timeout.clone(),
|
timeout: *timeout,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -108,13 +102,13 @@ impl TimeoutAnimation {
|
||||||
fn tick(&mut self, frames_elapsed: u8) {
|
fn tick(&mut self, frames_elapsed: u8) {
|
||||||
let step_size = 1. / (self.duration * 60.);
|
let step_size = 1. / (self.duration * 60.);
|
||||||
if self.ascending {
|
if self.ascending {
|
||||||
self.intensity = self.intensity + step_size * frames_elapsed as f64;
|
self.intensity += step_size * frames_elapsed as f64;
|
||||||
if self.intensity > 1. {
|
if self.intensity > 1. {
|
||||||
self.intensity = 1.0;
|
self.intensity = 1.0;
|
||||||
self.ascending = false;
|
self.ascending = false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.intensity = self.intensity - step_size * frames_elapsed as f64;
|
self.intensity -= step_size * frames_elapsed as f64;
|
||||||
if self.intensity < 0. {
|
if self.intensity < 0. {
|
||||||
self.intensity = 0.0;
|
self.intensity = 0.0;
|
||||||
self.ascending = true;
|
self.ascending = true;
|
||||||
|
@ -148,7 +142,6 @@ impl SplashPrivate {
|
||||||
*self.height.borrow(),
|
*self.height.borrow(),
|
||||||
2.,
|
2.,
|
||||||
8.,
|
8.,
|
||||||
8.,
|
|
||||||
(0.7, 0., 1.),
|
(0.7, 0., 1.),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -333,7 +326,7 @@ impl Splash {
|
||||||
let _ = context.set_source(&*background);
|
let _ = context.set_source(&*background);
|
||||||
let _ = context.paint();
|
let _ = context.paint();
|
||||||
|
|
||||||
let state = s.imp().state.borrow().clone();
|
let state = *s.imp().state.borrow();
|
||||||
|
|
||||||
let time = match state {
|
let time = match state {
|
||||||
State::Running { deadline, .. } => deadline - Instant::now(),
|
State::Running { deadline, .. } => deadline - Instant::now(),
|
||||||
|
@ -359,7 +352,7 @@ impl Splash {
|
||||||
|
|
||||||
let mut saved_extents = s.imp().time_extents.borrow_mut();
|
let mut saved_extents = s.imp().time_extents.borrow_mut();
|
||||||
if saved_extents.is_none() {
|
if saved_extents.is_none() {
|
||||||
*saved_extents = Some(time_extents.clone());
|
*saved_extents = Some(time_extents);
|
||||||
}
|
}
|
||||||
|
|
||||||
let time_baseline_x = center_x - time_extents.width() / 2.;
|
let time_baseline_x = center_x - time_extents.width() / 2.;
|
||||||
|
@ -372,8 +365,8 @@ impl Splash {
|
||||||
time_baseline_y,
|
time_baseline_y,
|
||||||
);
|
);
|
||||||
let (running, timeout_animation) = match state {
|
let (running, timeout_animation) = match state {
|
||||||
State::Running { timeout, .. } => (true, timeout.clone()),
|
State::Running { timeout, .. } => (true, timeout),
|
||||||
State::Paused { timeout, .. } => (false, timeout.clone()),
|
State::Paused { timeout, .. } => (false, timeout),
|
||||||
};
|
};
|
||||||
match timeout_animation {
|
match timeout_animation {
|
||||||
Some(ref animation) => {
|
Some(ref animation) => {
|
||||||
|
@ -395,8 +388,7 @@ impl Splash {
|
||||||
let _ = context.show_text(&time);
|
let _ = context.show_text(&time);
|
||||||
};
|
};
|
||||||
|
|
||||||
match *s.imp().time_extents.borrow() {
|
if let Some(extents) = *s.imp().time_extents.borrow() {
|
||||||
Some(extents) => {
|
|
||||||
context.set_source_rgb(0.7, 0.0, 1.0);
|
context.set_source_rgb(0.7, 0.0, 1.0);
|
||||||
let time_meter = SlashMeter {
|
let time_meter = SlashMeter {
|
||||||
orientation: gtk::Orientation::Horizontal,
|
orientation: gtk::Orientation::Horizontal,
|
||||||
|
@ -407,9 +399,7 @@ impl Splash {
|
||||||
height: 60.,
|
height: 60.,
|
||||||
length: 100.,
|
length: 100.,
|
||||||
};
|
};
|
||||||
time_meter.draw(&context);
|
time_meter.draw(context);
|
||||||
}
|
|
||||||
None => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -544,7 +534,7 @@ impl SlashMeter {
|
||||||
gtk::Orientation::Horizontal => {
|
gtk::Orientation::Horizontal => {
|
||||||
let angle: f64 = 0.8;
|
let angle: f64 = 0.8;
|
||||||
let run = self.height / angle.tan();
|
let run = self.height / angle.tan();
|
||||||
let width = self.length as f64 / (self.count as f64 * 2.);
|
let width = self.length / (self.count as f64 * 2.);
|
||||||
|
|
||||||
for c in 0..self.count {
|
for c in 0..self.count {
|
||||||
context.set_line_width(1.);
|
context.set_line_width(1.);
|
||||||
|
@ -579,10 +569,6 @@ trait Pen {
|
||||||
struct GlowPen {
|
struct GlowPen {
|
||||||
blur_context: Context,
|
blur_context: Context,
|
||||||
draw_context: Context,
|
draw_context: Context,
|
||||||
|
|
||||||
line_width: f64,
|
|
||||||
blur_line_width: f64,
|
|
||||||
blur_size: f64,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GlowPen {
|
impl GlowPen {
|
||||||
|
@ -591,7 +577,6 @@ impl GlowPen {
|
||||||
height: i32,
|
height: i32,
|
||||||
line_width: f64,
|
line_width: f64,
|
||||||
blur_line_width: f64,
|
blur_line_width: f64,
|
||||||
blur_size: f64,
|
|
||||||
color: (f64, f64, f64),
|
color: (f64, f64, f64),
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let blur_context =
|
let blur_context =
|
||||||
|
@ -611,9 +596,6 @@ impl GlowPen {
|
||||||
Self {
|
Self {
|
||||||
blur_context,
|
blur_context,
|
||||||
draw_context,
|
draw_context,
|
||||||
line_width,
|
|
||||||
blur_line_width,
|
|
||||||
blur_size,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -630,8 +612,10 @@ impl Pen for GlowPen {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stroke(&self) {
|
fn stroke(&self) {
|
||||||
self.blur_context.stroke();
|
self.blur_context.stroke().expect("to draw the blur line");
|
||||||
self.draw_context.stroke();
|
self.draw_context
|
||||||
|
.stroke()
|
||||||
|
.expect("to draw the regular line");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finish(self) -> Pattern {
|
fn finish(self) -> Pattern {
|
||||||
|
@ -681,7 +665,7 @@ fn main() {
|
||||||
let countdown = match options.lookup::<String>("countdown") {
|
let countdown = match options.lookup::<String>("countdown") {
|
||||||
Ok(Some(countdown_str)) => {
|
Ok(Some(countdown_str)) => {
|
||||||
let parts = countdown_str.split(':').collect::<Vec<&str>>();
|
let parts = countdown_str.split(':').collect::<Vec<&str>>();
|
||||||
let duration = match parts.len() {
|
match parts.len() {
|
||||||
2 => {
|
2 => {
|
||||||
let minutes = parts[0].parse::<u64>().unwrap();
|
let minutes = parts[0].parse::<u64>().unwrap();
|
||||||
let seconds = parts[1].parse::<u64>().unwrap();
|
let seconds = parts[1].parse::<u64>().unwrap();
|
||||||
|
@ -692,8 +676,7 @@ fn main() {
|
||||||
Duration::from_secs(seconds)
|
Duration::from_secs(seconds)
|
||||||
}
|
}
|
||||||
_ => Duration::from_secs(300),
|
_ => Duration::from_secs(300),
|
||||||
};
|
}
|
||||||
duration
|
|
||||||
}
|
}
|
||||||
_ => Duration::from_secs(300),
|
_ => Duration::from_secs(300),
|
||||||
};
|
};
|
||||||
|
@ -725,7 +708,7 @@ fn main() {
|
||||||
let window = gtk::ApplicationWindow::new(app);
|
let window = gtk::ApplicationWindow::new(app);
|
||||||
window.present();
|
window.present();
|
||||||
|
|
||||||
let splash = Splash::new(title.read().unwrap().clone(), state.read().unwrap().clone());
|
let splash = Splash::new(title.read().unwrap().clone(), *state.read().unwrap());
|
||||||
|
|
||||||
window.set_child(Some(&splash));
|
window.set_child(Some(&splash));
|
||||||
|
|
||||||
|
@ -763,7 +746,7 @@ fn main() {
|
||||||
loop {
|
loop {
|
||||||
std::thread::sleep(Duration::from_millis(1000 / 60));
|
std::thread::sleep(Duration::from_millis(1000 / 60));
|
||||||
state.write().unwrap().run(Instant::now());
|
state.write().unwrap().run(Instant::now());
|
||||||
let _ = gtk_tx.send(state.read().unwrap().clone());
|
let _ = gtk_tx.send(*state.read().unwrap());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
set -x
|
|
||||||
|
VERSION=`cat Cargo.toml | grep "^version =" | sed -r 's/^version = "(.+)"$/\1/'`
|
||||||
|
|
||||||
mkdir -p dist
|
mkdir -p dist
|
||||||
cp dashboard.desktop dist
|
cp dashboard.desktop dist
|
||||||
cp ../target/release/dashboard dist
|
cp ../target/release/dashboard dist
|
||||||
strip dist/dashboard
|
strip dist/dashboard
|
||||||
tar -czf dashboard.tgz dist/
|
tar -czf dashboard-${VERSION}.tgz dist/
|
||||||
|
|
||||||
|
|
|
@ -40,16 +40,16 @@ impl ApplicationWindow {
|
||||||
.vexpand(true)
|
.vexpand(true)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let date_label = Date::new();
|
let date_label = Date::default();
|
||||||
layout.append(&date_label);
|
layout.append(&date_label);
|
||||||
|
|
||||||
let events = Events::new();
|
let events = Events::default();
|
||||||
layout.append(&events);
|
layout.append(&events);
|
||||||
|
|
||||||
let transit_card = TransitCard::new();
|
let transit_card = TransitCard::default();
|
||||||
layout.append(&transit_card);
|
layout.append(&transit_card);
|
||||||
|
|
||||||
let transit_clock = TransitClock::new();
|
let transit_clock = TransitClock::default();
|
||||||
layout.append(&transit_clock);
|
layout.append(&transit_clock);
|
||||||
|
|
||||||
window.set_content(Some(&layout));
|
window.set_content(Some(&layout));
|
||||||
|
|
|
@ -35,8 +35,8 @@ glib::wrapper! {
|
||||||
pub struct Date(ObjectSubclass<DatePrivate>) @extends gtk::Box, gtk::Widget;
|
pub struct Date(ObjectSubclass<DatePrivate>) @extends gtk::Box, gtk::Widget;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Date {
|
impl Default for Date {
|
||||||
pub fn new() -> Self {
|
fn default() -> Self {
|
||||||
let s: Self = Object::builder().build();
|
let s: Self = Object::builder().build();
|
||||||
s.set_margin_bottom(8);
|
s.set_margin_bottom(8);
|
||||||
s.set_margin_top(8);
|
s.set_margin_top(8);
|
||||||
|
@ -48,7 +48,9 @@ impl Date {
|
||||||
s.redraw();
|
s.redraw();
|
||||||
s
|
s
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Date {
|
||||||
pub fn update_date(&self, date: IFC) {
|
pub fn update_date(&self, date: IFC) {
|
||||||
*self.imp().date.borrow_mut() = date;
|
*self.imp().date.borrow_mut() = date;
|
||||||
self.redraw();
|
self.redraw();
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
components::Date,
|
components::Date,
|
||||||
solstices::{self, YearlyEvents},
|
solstices::{self, YearlyEvents},
|
||||||
soluna_client::SunMoon,
|
|
||||||
};
|
};
|
||||||
use chrono::TimeZone;
|
|
||||||
use glib::Object;
|
use glib::Object;
|
||||||
use gtk::{prelude::*, subclass::prelude::*, IconLookupFlags};
|
use gtk::{prelude::*, subclass::prelude::*};
|
||||||
use ifc::IFC;
|
use ifc::IFC;
|
||||||
|
|
||||||
|
/*
|
||||||
#[derive(PartialEq)]
|
#[derive(PartialEq)]
|
||||||
pub enum UpcomingEvent {
|
pub enum UpcomingEvent {
|
||||||
SpringEquinox,
|
SpringEquinox,
|
||||||
|
@ -15,25 +14,15 @@ pub enum UpcomingEvent {
|
||||||
AutumnEquinox,
|
AutumnEquinox,
|
||||||
WinterSolstice,
|
WinterSolstice,
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
pub struct EventsPrivate {
|
pub struct EventsPrivate {
|
||||||
spring_equinox: Date,
|
spring_equinox: Date,
|
||||||
summer_solstice: Date,
|
summer_solstice: Date,
|
||||||
autumn_equinox: Date,
|
autumn_equinox: Date,
|
||||||
winter_solstice: Date,
|
winter_solstice: Date,
|
||||||
next: UpcomingEvent,
|
// next: UpcomingEvent,
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for EventsPrivate {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
spring_equinox: Date::new(),
|
|
||||||
summer_solstice: Date::new(),
|
|
||||||
autumn_equinox: Date::new(),
|
|
||||||
winter_solstice: Date::new(),
|
|
||||||
next: UpcomingEvent::SpringEquinox,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[glib::object_subclass]
|
#[glib::object_subclass]
|
||||||
|
@ -51,8 +40,8 @@ glib::wrapper! {
|
||||||
pub struct Events(ObjectSubclass<EventsPrivate>) @extends gtk::Widget, gtk::Box, @implements gtk::Orientable;
|
pub struct Events(ObjectSubclass<EventsPrivate>) @extends gtk::Widget, gtk::Box, @implements gtk::Orientable;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Events {
|
impl Default for Events {
|
||||||
pub fn new() -> Self {
|
fn default() -> Self {
|
||||||
let s: Self = Object::builder().build();
|
let s: Self = Object::builder().build();
|
||||||
s.set_orientation(gtk::Orientation::Horizontal);
|
s.set_orientation(gtk::Orientation::Horizontal);
|
||||||
s.set_spacing(8);
|
s.set_spacing(8);
|
||||||
|
@ -64,7 +53,9 @@ impl Events {
|
||||||
|
|
||||||
s
|
s
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Events {
|
||||||
pub fn set_events(&self, events: YearlyEvents, next_event: solstices::Event) {
|
pub fn set_events(&self, events: YearlyEvents, next_event: solstices::Event) {
|
||||||
self.imp()
|
self.imp()
|
||||||
.spring_equinox
|
.spring_equinox
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
use crate::soluna_client::SunMoon;
|
|
||||||
use glib::Object;
|
use glib::Object;
|
||||||
use gtk::{prelude::*, subclass::prelude::*, IconLookupFlags};
|
use gtk::{prelude::*, subclass::prelude::*};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct LabelPrivate {
|
pub struct LabelPrivate {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::{components::Label, soluna_client::SunMoon};
|
use crate::{components::Label, soluna_client::SunMoon};
|
||||||
use glib::Object;
|
use glib::Object;
|
||||||
use gtk::{prelude::*, subclass::prelude::*, IconLookupFlags};
|
use gtk::{prelude::*, subclass::prelude::*};
|
||||||
|
|
||||||
pub struct TransitCardPrivate {
|
pub struct TransitCardPrivate {
|
||||||
sunrise: Label,
|
sunrise: Label,
|
||||||
|
@ -35,8 +35,8 @@ glib::wrapper! {
|
||||||
pub struct TransitCard(ObjectSubclass<TransitCardPrivate>) @extends gtk::Grid, gtk::Widget;
|
pub struct TransitCard(ObjectSubclass<TransitCardPrivate>) @extends gtk::Grid, gtk::Widget;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TransitCard {
|
impl Default for TransitCard {
|
||||||
pub fn new() -> Self {
|
fn default() -> Self {
|
||||||
let s: Self = Object::builder().build();
|
let s: Self = Object::builder().build();
|
||||||
s.add_css_class("card");
|
s.add_css_class("card");
|
||||||
s.set_column_homogeneous(true);
|
s.set_column_homogeneous(true);
|
||||||
|
@ -48,7 +48,9 @@ impl TransitCard {
|
||||||
|
|
||||||
s
|
s
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TransitCard {
|
||||||
pub fn update_transit(&self, transit_info: &SunMoon) {
|
pub fn update_transit(&self, transit_info: &SunMoon) {
|
||||||
self.imp()
|
self.imp()
|
||||||
.sunrise
|
.sunrise
|
||||||
|
|
|
@ -7,18 +7,11 @@ use glib::Object;
|
||||||
use gtk::{prelude::*, subclass::prelude::*};
|
use gtk::{prelude::*, subclass::prelude::*};
|
||||||
use std::{cell::RefCell, f64::consts::PI, rc::Rc};
|
use std::{cell::RefCell, f64::consts::PI, rc::Rc};
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
pub struct TransitClockPrivate {
|
pub struct TransitClockPrivate {
|
||||||
info: Rc<RefCell<Option<SunMoon>>>,
|
info: Rc<RefCell<Option<SunMoon>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for TransitClockPrivate {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
info: Rc::new(RefCell::new(None)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[glib::object_subclass]
|
#[glib::object_subclass]
|
||||||
impl ObjectSubclass for TransitClockPrivate {
|
impl ObjectSubclass for TransitClockPrivate {
|
||||||
const NAME: &'static str = "TransitClock";
|
const NAME: &'static str = "TransitClock";
|
||||||
|
@ -34,8 +27,8 @@ glib::wrapper! {
|
||||||
pub struct TransitClock(ObjectSubclass<TransitClockPrivate>) @extends gtk::DrawingArea, gtk::Widget;
|
pub struct TransitClock(ObjectSubclass<TransitClockPrivate>) @extends gtk::DrawingArea, gtk::Widget;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TransitClock {
|
impl Default for TransitClock {
|
||||||
pub fn new() -> Self {
|
fn default() -> Self {
|
||||||
let s: Self = Object::builder().build();
|
let s: Self = Object::builder().build();
|
||||||
s.set_width_request(500);
|
s.set_width_request(500);
|
||||||
s.set_height_request(500);
|
s.set_height_request(500);
|
||||||
|
@ -100,7 +93,9 @@ impl TransitClock {
|
||||||
|
|
||||||
s
|
s
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TransitClock {
|
||||||
pub fn update_transit(&self, transit_info: SunMoon) {
|
pub fn update_transit(&self, transit_info: SunMoon) {
|
||||||
*self.imp().info.borrow_mut() = Some(transit_info);
|
*self.imp().info.borrow_mut() = Some(transit_info);
|
||||||
self.queue_draw();
|
self.queue_draw();
|
||||||
|
|
|
@ -90,7 +90,7 @@ pub fn main() {
|
||||||
tx: Arc::new(RwLock::new(None)),
|
tx: Arc::new(RwLock::new(None)),
|
||||||
};
|
};
|
||||||
|
|
||||||
let _ = runtime.spawn({
|
runtime.spawn({
|
||||||
let core = core.clone();
|
let core = core.clone();
|
||||||
async move {
|
async move {
|
||||||
let soluna_client = SolunaClient::new();
|
let soluna_client = SolunaClient::new();
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
use chrono;
|
|
||||||
use chrono::prelude::*;
|
use chrono::prelude::*;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use serde_derive::{Deserialize, Serialize};
|
use serde_derive::{Deserialize, Serialize};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
// http://astropixels.com/ephemeris/soleq2001.html
|
// http://astropixels.com/ephemeris/soleq2001.html
|
||||||
const SOLSTICE_TEXT: &'static str = "
|
const SOLSTICE_TEXT: &str = "
|
||||||
2001 Mar 20 13:31 Jun 21 07:38 Sep 22 23:05 Dec 21 19:22
|
2001 Mar 20 13:31 Jun 21 07:38 Sep 22 23:05 Dec 21 19:22
|
||||||
2002 Mar 20 19:16 Jun 21 13:25 Sep 23 04:56 Dec 22 01:15
|
2002 Mar 20 19:16 Jun 21 13:25 Sep 23 04:56 Dec 22 01:15
|
||||||
2003 Mar 21 01:00 Jun 21 19:11 Sep 23 10:47 Dec 22 07:04
|
2003 Mar 21 01:00 Jun 21 19:11 Sep 23 10:47 Dec 22 07:04
|
||||||
|
@ -91,12 +90,14 @@ impl Event {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_time<'a>(
|
fn parse_time<'a>(
|
||||||
jaro: &str,
|
year: &str,
|
||||||
iter: impl Iterator<Item = &'a str>,
|
iter: impl Iterator<Item = &'a str>,
|
||||||
) -> chrono::DateTime<chrono::Utc> {
|
) -> chrono::DateTime<chrono::Utc> {
|
||||||
let partoj = iter.collect::<Vec<&str>>();
|
let parts = iter.collect::<Vec<&str>>();
|
||||||
let p = format!("{} {} {} {}", jaro, partoj[0], partoj[1], partoj[2]);
|
let p = format!("{} {} {} {}", year, parts[0], parts[1], parts[2]);
|
||||||
chrono::Utc.datetime_from_str(&p, "%Y %b %d %H:%M").unwrap()
|
NaiveDateTime::parse_from_str(&p, "%Y %b %d %H:%M")
|
||||||
|
.unwrap()
|
||||||
|
.and_utc()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_line(year: &str, rest: &[&str]) -> YearlyEvents {
|
fn parse_line(year: &str, rest: &[&str]) -> YearlyEvents {
|
||||||
|
@ -118,7 +119,7 @@ fn parse_events() -> Vec<Option<YearlyEvents>> {
|
||||||
.lines()
|
.lines()
|
||||||
.map(|line| {
|
.map(|line| {
|
||||||
match line
|
match line
|
||||||
.split(" ")
|
.split(' ')
|
||||||
.filter(|elem| !elem.is_empty())
|
.filter(|elem| !elem.is_empty())
|
||||||
.collect::<Vec<&str>>()
|
.collect::<Vec<&str>>()
|
||||||
.as_slice()
|
.as_slice()
|
||||||
|
@ -134,7 +135,7 @@ pub struct Solstices(HashMap<i32, YearlyEvents>);
|
||||||
|
|
||||||
impl Solstices {
|
impl Solstices {
|
||||||
pub fn yearly_events(&self, year: i32) -> Option<YearlyEvents> {
|
pub fn yearly_events(&self, year: i32) -> Option<YearlyEvents> {
|
||||||
self.0.get(&year).map(|c| c.clone())
|
self.0.get(&year).copied()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn next_event(&self, date: chrono::DateTime<chrono::Utc>) -> Option<Event> {
|
pub fn next_event(&self, date: chrono::DateTime<chrono::Utc>) -> Option<Event> {
|
||||||
|
@ -142,17 +143,17 @@ impl Solstices {
|
||||||
match year_events {
|
match year_events {
|
||||||
Some(year_events) => {
|
Some(year_events) => {
|
||||||
if date <= year_events.spring_equinox {
|
if date <= year_events.spring_equinox {
|
||||||
Some(Event::SpringEquinox(year_events.spring_equinox.clone()))
|
Some(Event::SpringEquinox(year_events.spring_equinox))
|
||||||
} else if date <= year_events.summer_solstice {
|
} else if date <= year_events.summer_solstice {
|
||||||
Some(Event::SummerSolstice(year_events.summer_solstice.clone()))
|
Some(Event::SummerSolstice(year_events.summer_solstice))
|
||||||
} else if date <= year_events.autumn_equinox {
|
} else if date <= year_events.autumn_equinox {
|
||||||
Some(Event::AutumnEquinox(year_events.autumn_equinox.clone()))
|
Some(Event::AutumnEquinox(year_events.autumn_equinox))
|
||||||
} else if date <= year_events.winter_solstice {
|
} else if date <= year_events.winter_solstice {
|
||||||
Some(Event::WinterSolstice(year_events.winter_solstice.clone()))
|
Some(Event::WinterSolstice(year_events.winter_solstice))
|
||||||
} else {
|
} else {
|
||||||
self.0
|
self.0
|
||||||
.get(&(date.year() + 1))
|
.get(&(date.year() + 1))
|
||||||
.map(|_| Event::SpringEquinox(year_events.spring_equinox.clone()))
|
.map(|_| Event::SpringEquinox(year_events.spring_equinox))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => None,
|
None => None,
|
||||||
|
@ -165,7 +166,7 @@ impl From<Vec<Option<YearlyEvents>>> for Solstices {
|
||||||
Solstices(event_list.iter().fold(HashMap::new(), |mut m, record| {
|
Solstices(event_list.iter().fold(HashMap::new(), |mut m, record| {
|
||||||
match record {
|
match record {
|
||||||
Some(record) => {
|
Some(record) => {
|
||||||
m.insert(record.year, record.clone());
|
m.insert(record.year, *record);
|
||||||
}
|
}
|
||||||
None => (),
|
None => (),
|
||||||
}
|
}
|
||||||
|
@ -177,3 +178,24 @@ impl From<Vec<Option<YearlyEvents>>> for Solstices {
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
pub static ref EVENTS: Solstices = Solstices::from(parse_events());
|
pub static ref EVENTS: Solstices = Solstices::from(parse_events());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use chrono::{NaiveDate, NaiveDateTime};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_can_parse_a_solstice_time() {
|
||||||
|
let p = "2001 Mar 20 13:31".to_owned();
|
||||||
|
let parsed_date = NaiveDateTime::parse_from_str(&p, "%Y %b %d %H:%M")
|
||||||
|
.unwrap()
|
||||||
|
.and_utc();
|
||||||
|
assert_eq!(
|
||||||
|
parsed_date,
|
||||||
|
NaiveDate::from_ymd_opt(2001, 03, 20)
|
||||||
|
.unwrap()
|
||||||
|
.and_hms_opt(13, 31, 0)
|
||||||
|
.unwrap()
|
||||||
|
.and_utc()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
use chrono::{DateTime, Duration, Local, NaiveTime, Offset, TimeZone, Timelike, Utc};
|
use chrono::{DateTime, Duration, Local, NaiveTime, Offset, TimeZone, Timelike, Utc};
|
||||||
use geo_types::{Latitude, Longitude};
|
use geo_types::{Latitude, Longitude};
|
||||||
use memorycache::MemoryCache;
|
use memorycache::MemoryCache;
|
||||||
use reqwest;
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
const ENDPOINT: &str = "https://api.solunar.org/solunar";
|
const ENDPOINT: &str = "https://api.solunar.org/solunar";
|
||||||
|
@ -26,8 +25,8 @@ impl SunMoon {
|
||||||
|
|
||||||
let sunrise = parse_time(val.sunrise).unwrap();
|
let sunrise = parse_time(val.sunrise).unwrap();
|
||||||
let sunset = parse_time(val.sunset).unwrap();
|
let sunset = parse_time(val.sunset).unwrap();
|
||||||
let moonrise = val.moonrise.and_then(|v| parse_time(v));
|
let moonrise = val.moonrise.and_then(parse_time);
|
||||||
let moonset = val.moonset.and_then(|v| parse_time(v));
|
let moonset = val.moonset.and_then(parse_time);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
sunrise,
|
sunrise,
|
||||||
|
@ -82,7 +81,7 @@ impl SolunaClient {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
client: reqwest::Client::new(),
|
client: reqwest::Client::new(),
|
||||||
memory_cache: MemoryCache::new(),
|
memory_cache: MemoryCache::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,7 +109,7 @@ impl SolunaClient {
|
||||||
.get(reqwest::header::EXPIRES)
|
.get(reqwest::header::EXPIRES)
|
||||||
.and_then(|header| header.to_str().ok())
|
.and_then(|header| header.to_str().ok())
|
||||||
.and_then(|expiration| DateTime::parse_from_rfc2822(expiration).ok())
|
.and_then(|expiration| DateTime::parse_from_rfc2822(expiration).ok())
|
||||||
.map(|dt_local| DateTime::<Utc>::from(dt_local))
|
.map(DateTime::<Utc>::from)
|
||||||
.unwrap_or(
|
.unwrap_or(
|
||||||
Local::now()
|
Local::now()
|
||||||
.with_hour(0)
|
.with_hour(0)
|
||||||
|
|
|
@ -10,7 +10,6 @@ Luminescent Dreams Tools is distributed in the hope that it will be useful, but
|
||||||
You should have received a copy of the GNU General Public License along with Lumeto. If not, see <https://www.gnu.org/licenses/>.
|
You should have received a copy of the GNU General Public License along with Lumeto. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use date_time_tz::DateTimeTz;
|
|
||||||
use types::{Recordable, Timestamp};
|
use types::{Recordable, Timestamp};
|
||||||
|
|
||||||
/// This trait is used for constructing queries for searching the database.
|
/// This trait is used for constructing queries for searching the database.
|
||||||
|
|
|
@ -33,19 +33,13 @@ use std::{fmt, str::FromStr};
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub struct DateTimeTz(pub chrono::DateTime<chrono_tz::Tz>);
|
pub struct DateTimeTz(pub chrono::DateTime<chrono_tz::Tz>);
|
||||||
|
|
||||||
impl DateTimeTz {
|
impl fmt::Display for DateTimeTz {
|
||||||
pub fn map<F>(&self, f: F) -> DateTimeTz
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||||
where
|
|
||||||
F: FnOnce(chrono::DateTime<chrono_tz::Tz>) -> chrono::DateTime<chrono_tz::Tz>,
|
|
||||||
{
|
|
||||||
DateTimeTz(f(self.0))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_string(&self) -> String {
|
|
||||||
if self.0.timezone() == UTC {
|
if self.0.timezone() == UTC {
|
||||||
self.0.to_rfc3339_opts(SecondsFormat::Secs, true)
|
write!(f, "{}", self.0.to_rfc3339_opts(SecondsFormat::Secs, true))
|
||||||
} else {
|
} else {
|
||||||
format!(
|
write!(
|
||||||
|
f,
|
||||||
"{} {}",
|
"{} {}",
|
||||||
self.0
|
self.0
|
||||||
.with_timezone(&chrono_tz::Etc::UTC)
|
.with_timezone(&chrono_tz::Etc::UTC)
|
||||||
|
@ -56,11 +50,20 @@ impl DateTimeTz {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl DateTimeTz {
|
||||||
|
pub fn map<F>(&self, f: F) -> DateTimeTz
|
||||||
|
where
|
||||||
|
F: FnOnce(chrono::DateTime<chrono_tz::Tz>) -> chrono::DateTime<chrono_tz::Tz>,
|
||||||
|
{
|
||||||
|
DateTimeTz(f(self.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl std::str::FromStr for DateTimeTz {
|
impl std::str::FromStr for DateTimeTz {
|
||||||
type Err = chrono::ParseError;
|
type Err = chrono::ParseError;
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
let v: Vec<&str> = s.split_terminator(" ").collect();
|
let v: Vec<&str> = s.split_terminator(' ').collect();
|
||||||
if v.len() == 2 {
|
if v.len() == 2 {
|
||||||
let tz = v[1].parse::<chrono_tz::Tz>().unwrap();
|
let tz = v[1].parse::<chrono_tz::Tz>().unwrap();
|
||||||
chrono::DateTime::parse_from_rfc3339(v[0]).map(|ts| DateTimeTz(ts.with_timezone(&tz)))
|
chrono::DateTime::parse_from_rfc3339(v[0]).map(|ts| DateTimeTz(ts.with_timezone(&tz)))
|
||||||
|
@ -86,9 +89,9 @@ impl<'de> Visitor<'de> for DateTimeTzVisitor {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn visit_str<E: de::Error>(self, s: &str) -> Result<Self::Value, E> {
|
fn visit_str<E: de::Error>(self, s: &str) -> Result<Self::Value, E> {
|
||||||
DateTimeTz::from_str(s).or(Err(E::custom(format!(
|
DateTimeTz::from_str(s).or(Err(E::custom(
|
||||||
"string is not a parsable datetime representation"
|
"string is not a parsable datetime representation".to_owned(),
|
||||||
))))
|
)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,28 +120,43 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn it_creates_timestamp_with_z() {
|
fn it_creates_timestamp_with_z() {
|
||||||
let t = DateTimeTz(UTC.ymd(2019, 5, 15).and_hms(12, 0, 0));
|
let t = DateTimeTz(UTC.with_ymd_and_hms(2019, 5, 15, 12, 0, 0).unwrap());
|
||||||
assert_eq!(t.to_string(), "2019-05-15T12:00:00Z");
|
assert_eq!(t.to_string(), "2019-05-15T12:00:00Z");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn it_parses_utc_rfc3339_z() {
|
fn it_parses_utc_rfc3339_z() {
|
||||||
let t = DateTimeTz::from_str("2019-05-15T12:00:00Z").unwrap();
|
let t = DateTimeTz::from_str("2019-05-15T12:00:00Z").unwrap();
|
||||||
assert_eq!(t, DateTimeTz(UTC.ymd(2019, 5, 15).and_hms(12, 0, 0)));
|
assert_eq!(
|
||||||
|
t,
|
||||||
|
DateTimeTz(UTC.with_ymd_and_hms(2019, 5, 15, 12, 0, 0).unwrap())
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn it_parses_rfc3339_with_offset() {
|
fn it_parses_rfc3339_with_offset() {
|
||||||
let t = DateTimeTz::from_str("2019-05-15T12:00:00-06:00").unwrap();
|
let t = DateTimeTz::from_str("2019-05-15T12:00:00-06:00").unwrap();
|
||||||
assert_eq!(t, DateTimeTz(UTC.ymd(2019, 5, 15).and_hms(18, 0, 0)));
|
assert_eq!(
|
||||||
|
t,
|
||||||
|
DateTimeTz(UTC.with_ymd_and_hms(2019, 5, 15, 18, 0, 0).unwrap())
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn it_parses_rfc3339_with_tz() {
|
fn it_parses_rfc3339_with_tz() {
|
||||||
let t = DateTimeTz::from_str("2019-06-15T19:00:00Z US/Arizona").unwrap();
|
let t = DateTimeTz::from_str("2019-06-15T19:00:00Z US/Arizona").unwrap();
|
||||||
assert_eq!(t, DateTimeTz(UTC.ymd(2019, 6, 15).and_hms(19, 0, 0)));
|
assert_eq!(
|
||||||
assert_eq!(t, DateTimeTz(Arizona.ymd(2019, 6, 15).and_hms(12, 0, 0)));
|
t,
|
||||||
assert_eq!(t, DateTimeTz(Central.ymd(2019, 6, 15).and_hms(14, 0, 0)));
|
DateTimeTz(UTC.with_ymd_and_hms(2019, 6, 15, 19, 0, 0).unwrap())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
t,
|
||||||
|
DateTimeTz(Arizona.with_ymd_and_hms(2019, 6, 15, 12, 0, 0).unwrap())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
t,
|
||||||
|
DateTimeTz(Central.with_ymd_and_hms(2019, 6, 15, 14, 0, 0).unwrap())
|
||||||
|
);
|
||||||
assert_eq!(t.to_string(), "2019-06-15T19:00:00Z US/Arizona");
|
assert_eq!(t.to_string(), "2019-06-15T19:00:00Z US/Arizona");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -172,6 +190,9 @@ mod test {
|
||||||
fn it_json_parses() {
|
fn it_json_parses() {
|
||||||
let t =
|
let t =
|
||||||
serde_json::from_str::<DateTimeTz>("\"2019-06-15T19:00:00Z America/Phoenix\"").unwrap();
|
serde_json::from_str::<DateTimeTz>("\"2019-06-15T19:00:00Z America/Phoenix\"").unwrap();
|
||||||
assert_eq!(t, DateTimeTz(Phoenix.ymd(2019, 6, 15).and_hms(12, 0, 0)));
|
assert_eq!(
|
||||||
|
t,
|
||||||
|
DateTimeTz(Phoenix.with_ymd_and_hms(2019, 6, 15, 12, 0, 0).unwrap())
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,7 +47,7 @@ where
|
||||||
.read(true)
|
.read(true)
|
||||||
.append(true)
|
.append(true)
|
||||||
.create(true)
|
.create(true)
|
||||||
.open(&path)
|
.open(path)
|
||||||
.map_err(EmseriesReadError::IOError)?;
|
.map_err(EmseriesReadError::IOError)?;
|
||||||
|
|
||||||
let records = Series::load_file(&f)?;
|
let records = Series::load_file(&f)?;
|
||||||
|
@ -88,8 +88,8 @@ where
|
||||||
/// Put a new record into the database. A unique id will be assigned to the record and
|
/// Put a new record into the database. A unique id will be assigned to the record and
|
||||||
/// returned.
|
/// returned.
|
||||||
pub fn put(&mut self, entry: T) -> Result<UniqueId, EmseriesWriteError> {
|
pub fn put(&mut self, entry: T) -> Result<UniqueId, EmseriesWriteError> {
|
||||||
let uuid = UniqueId::new();
|
let uuid = UniqueId::default();
|
||||||
self.update(uuid.clone(), entry).and_then(|_| Ok(uuid))
|
self.update(uuid.clone(), entry).map(|_| uuid)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update an existing record. The `UniqueId` of the record passed into this function must match
|
/// Update an existing record. The `UniqueId` of the record passed into this function must match
|
||||||
|
@ -138,7 +138,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get all of the records in the database.
|
/// Get all of the records in the database.
|
||||||
pub fn records<'s>(&'s self) -> impl Iterator<Item = (&'s UniqueId, &'s T)> + 's {
|
pub fn records(&self) -> impl Iterator<Item = (&UniqueId, &T)> {
|
||||||
self.records.iter()
|
self.records.iter()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -166,7 +166,7 @@ where
|
||||||
|
|
||||||
/// Get an exact record from the database based on unique id.
|
/// Get an exact record from the database based on unique id.
|
||||||
pub fn get(&self, uuid: &UniqueId) -> Option<T> {
|
pub fn get(&self, uuid: &UniqueId) -> Option<T> {
|
||||||
self.records.get(uuid).map(|v| v.clone())
|
self.records.get(uuid).cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -55,8 +55,8 @@ impl str::FromStr for Timestamp {
|
||||||
type Err = chrono::ParseError;
|
type Err = chrono::ParseError;
|
||||||
fn from_str(line: &str) -> Result<Self, Self::Err> {
|
fn from_str(line: &str) -> Result<Self, Self::Err> {
|
||||||
DateTimeTz::from_str(line)
|
DateTimeTz::from_str(line)
|
||||||
.map(|dtz| Timestamp::DateTime(dtz))
|
.map(Timestamp::DateTime)
|
||||||
.or(NaiveDate::from_str(line).map(|d| Timestamp::Date(d)))
|
.or(NaiveDate::from_str(line).map(Timestamp::Date))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,8 +70,8 @@ impl Ord for Timestamp {
|
||||||
fn cmp(&self, other: &Timestamp) -> Ordering {
|
fn cmp(&self, other: &Timestamp) -> Ordering {
|
||||||
match (self, other) {
|
match (self, other) {
|
||||||
(Timestamp::DateTime(dt1), Timestamp::DateTime(dt2)) => dt1.cmp(dt2),
|
(Timestamp::DateTime(dt1), Timestamp::DateTime(dt2)) => dt1.cmp(dt2),
|
||||||
(Timestamp::DateTime(dt1), Timestamp::Date(dt2)) => dt1.0.date().naive_utc().cmp(&dt2),
|
(Timestamp::DateTime(dt1), Timestamp::Date(dt2)) => dt1.0.date_naive().cmp(dt2),
|
||||||
(Timestamp::Date(dt1), Timestamp::DateTime(dt2)) => dt1.cmp(&dt2.0.date().naive_utc()),
|
(Timestamp::Date(dt1), Timestamp::DateTime(dt2)) => dt1.cmp(&dt2.0.date_naive()),
|
||||||
(Timestamp::Date(dt1), Timestamp::Date(dt2)) => dt1.cmp(dt2),
|
(Timestamp::Date(dt1), Timestamp::Date(dt2)) => dt1.cmp(dt2),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -105,11 +105,9 @@ pub trait Recordable {
|
||||||
#[derive(Clone, Debug, Eq, Hash, PartialEq, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Eq, Hash, PartialEq, Deserialize, Serialize)]
|
||||||
pub struct UniqueId(Uuid);
|
pub struct UniqueId(Uuid);
|
||||||
|
|
||||||
impl UniqueId {
|
impl Default for UniqueId {
|
||||||
/// Create a new V4 UUID (this is the most common type in use these days).
|
fn default() -> Self {
|
||||||
pub fn new() -> UniqueId {
|
Self(Uuid::new_v4())
|
||||||
let id = Uuid::new_v4();
|
|
||||||
UniqueId(id)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,14 +118,14 @@ impl str::FromStr for UniqueId {
|
||||||
fn from_str(val: &str) -> Result<Self, Self::Err> {
|
fn from_str(val: &str) -> Result<Self, Self::Err> {
|
||||||
Uuid::parse_str(val)
|
Uuid::parse_str(val)
|
||||||
.map(UniqueId)
|
.map(UniqueId)
|
||||||
.map_err(|err| EmseriesReadError::UUIDParseError(err))
|
.map_err(EmseriesReadError::UUIDParseError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for UniqueId {
|
impl fmt::Display for UniqueId {
|
||||||
/// Convert to a hyphenated string
|
/// Convert to a hyphenated string
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||||
write!(f, "{}", self.0.to_hyphenated().to_string())
|
write!(f, "{}", self.0.to_hyphenated())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -146,7 +144,7 @@ where
|
||||||
type Err = EmseriesReadError;
|
type Err = EmseriesReadError;
|
||||||
|
|
||||||
fn from_str(line: &str) -> Result<Self, Self::Err> {
|
fn from_str(line: &str) -> Result<Self, Self::Err> {
|
||||||
serde_json::from_str(&line).map_err(|err| EmseriesReadError::JSONParseError(err))
|
serde_json::from_str(line).map_err(EmseriesReadError::JSONParseError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,7 +182,9 @@ mod test {
|
||||||
fn timestamp_parses_datetimetz_without_timezone() {
|
fn timestamp_parses_datetimetz_without_timezone() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
"2003-11-10T06:00:00Z".parse::<Timestamp>().unwrap(),
|
"2003-11-10T06:00:00Z".parse::<Timestamp>().unwrap(),
|
||||||
Timestamp::DateTime(DateTimeTz(UTC.ymd(2003, 11, 10).and_hms(6, 0, 0))),
|
Timestamp::DateTime(DateTimeTz(
|
||||||
|
UTC.with_ymd_and_hms(2003, 11, 10, 6, 0, 0).unwrap()
|
||||||
|
)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -210,7 +210,9 @@ mod test {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
rec.data,
|
rec.data,
|
||||||
Some(WeightRecord {
|
Some(WeightRecord {
|
||||||
date: Timestamp::DateTime(DateTimeTz(UTC.ymd(2003, 11, 10).and_hms(6, 0, 0))),
|
date: Timestamp::DateTime(DateTimeTz(
|
||||||
|
UTC.with_ymd_and_hms(2003, 11, 10, 6, 0, 0).unwrap()
|
||||||
|
)),
|
||||||
weight: Weight(77.79109 * KG),
|
weight: Weight(77.79109 * KG),
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
@ -219,7 +221,9 @@ mod test {
|
||||||
#[test]
|
#[test]
|
||||||
fn serialization_output() {
|
fn serialization_output() {
|
||||||
let rec = WeightRecord {
|
let rec = WeightRecord {
|
||||||
date: Timestamp::DateTime(DateTimeTz(UTC.ymd(2003, 11, 10).and_hms(6, 0, 0))),
|
date: Timestamp::DateTime(DateTimeTz(
|
||||||
|
UTC.with_ymd_and_hms(2003, 11, 10, 6, 0, 0).unwrap(),
|
||||||
|
)),
|
||||||
weight: Weight(77.0 * KG),
|
weight: Weight(77.0 * KG),
|
||||||
};
|
};
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -228,7 +232,12 @@ mod test {
|
||||||
);
|
);
|
||||||
|
|
||||||
let rec2 = WeightRecord {
|
let rec2 = WeightRecord {
|
||||||
date: Timestamp::DateTime(Central.ymd(2003, 11, 10).and_hms(0, 0, 0).into()),
|
date: Timestamp::DateTime(
|
||||||
|
Central
|
||||||
|
.with_ymd_and_hms(2003, 11, 10, 0, 0, 0)
|
||||||
|
.unwrap()
|
||||||
|
.into(),
|
||||||
|
),
|
||||||
weight: Weight(77.0 * KG),
|
weight: Weight(77.0 * KG),
|
||||||
};
|
};
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -239,22 +248,28 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn two_datetimes_can_be_compared() {
|
fn two_datetimes_can_be_compared() {
|
||||||
let time1 = Timestamp::DateTime(DateTimeTz(UTC.ymd(2003, 11, 10).and_hms(6, 0, 0)));
|
let time1 = Timestamp::DateTime(DateTimeTz(
|
||||||
let time2 = Timestamp::DateTime(DateTimeTz(UTC.ymd(2003, 11, 11).and_hms(6, 0, 0)));
|
UTC.with_ymd_and_hms(2003, 11, 10, 6, 0, 0).unwrap(),
|
||||||
|
));
|
||||||
|
let time2 = Timestamp::DateTime(DateTimeTz(
|
||||||
|
UTC.with_ymd_and_hms(2003, 11, 11, 6, 0, 0).unwrap(),
|
||||||
|
));
|
||||||
assert!(time1 < time2);
|
assert!(time1 < time2);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn two_dates_can_be_compared() {
|
fn two_dates_can_be_compared() {
|
||||||
let time1 = Timestamp::Date(NaiveDate::from_ymd(2003, 11, 10));
|
let time1 = Timestamp::Date(NaiveDate::from_ymd_opt(2003, 11, 10).unwrap());
|
||||||
let time2 = Timestamp::Date(NaiveDate::from_ymd(2003, 11, 11));
|
let time2 = Timestamp::Date(NaiveDate::from_ymd_opt(2003, 11, 11).unwrap());
|
||||||
assert!(time1 < time2);
|
assert!(time1 < time2);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn datetime_and_date_can_be_compared() {
|
fn datetime_and_date_can_be_compared() {
|
||||||
let time1 = Timestamp::DateTime(DateTimeTz(UTC.ymd(2003, 11, 10).and_hms(6, 0, 0)));
|
let time1 = Timestamp::DateTime(DateTimeTz(
|
||||||
let time2 = Timestamp::Date(NaiveDate::from_ymd(2003, 11, 11));
|
UTC.with_ymd_and_hms(2003, 11, 10, 6, 0, 0).unwrap(),
|
||||||
|
));
|
||||||
|
let time2 = Timestamp::Date(NaiveDate::from_ymd_opt(2003, 11, 11).unwrap());
|
||||||
assert!(time1 < time2)
|
assert!(time1 < time2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ extern crate emseries;
|
||||||
mod test {
|
mod test {
|
||||||
use chrono::prelude::*;
|
use chrono::prelude::*;
|
||||||
use chrono_tz::Etc::UTC;
|
use chrono_tz::Etc::UTC;
|
||||||
use dimensioned::si::{Kilogram, Meter, Second, KG, M, S};
|
use dimensioned::si::{Kilogram, Meter, Second, M, S};
|
||||||
|
|
||||||
use emseries::*;
|
use emseries::*;
|
||||||
|
|
||||||
|
@ -52,31 +52,31 @@ mod test {
|
||||||
fn mk_trips() -> [BikeTrip; 5] {
|
fn mk_trips() -> [BikeTrip; 5] {
|
||||||
[
|
[
|
||||||
BikeTrip {
|
BikeTrip {
|
||||||
datetime: DateTimeTz(UTC.ymd(2011, 10, 29).and_hms(0, 0, 0)),
|
datetime: DateTimeTz(UTC.with_ymd_and_hms(2011, 10, 29, 0, 0, 0).unwrap()),
|
||||||
distance: Distance(58741.055 * M),
|
distance: Distance(58741.055 * M),
|
||||||
duration: Duration(11040.0 * S),
|
duration: Duration(11040.0 * S),
|
||||||
comments: String::from("long time ago"),
|
comments: String::from("long time ago"),
|
||||||
},
|
},
|
||||||
BikeTrip {
|
BikeTrip {
|
||||||
datetime: DateTimeTz(UTC.ymd(2011, 10, 31).and_hms(0, 0, 0)),
|
datetime: DateTimeTz(UTC.with_ymd_and_hms(2011, 10, 31, 0, 0, 0).unwrap()),
|
||||||
distance: Distance(17702.0 * M),
|
distance: Distance(17702.0 * M),
|
||||||
duration: Duration(2880.0 * S),
|
duration: Duration(2880.0 * S),
|
||||||
comments: String::from("day 2"),
|
comments: String::from("day 2"),
|
||||||
},
|
},
|
||||||
BikeTrip {
|
BikeTrip {
|
||||||
datetime: DateTimeTz(UTC.ymd(2011, 11, 02).and_hms(0, 0, 0)),
|
datetime: DateTimeTz(UTC.with_ymd_and_hms(2011, 11, 02, 0, 0, 0).unwrap()),
|
||||||
distance: Distance(41842.945 * M),
|
distance: Distance(41842.945 * M),
|
||||||
duration: Duration(7020.0 * S),
|
duration: Duration(7020.0 * S),
|
||||||
comments: String::from("Do Some Distance!"),
|
comments: String::from("Do Some Distance!"),
|
||||||
},
|
},
|
||||||
BikeTrip {
|
BikeTrip {
|
||||||
datetime: DateTimeTz(UTC.ymd(2011, 11, 04).and_hms(0, 0, 0)),
|
datetime: DateTimeTz(UTC.with_ymd_and_hms(2011, 11, 04, 0, 0, 0).unwrap()),
|
||||||
distance: Distance(34600.895 * M),
|
distance: Distance(34600.895 * M),
|
||||||
duration: Duration(5580.0 * S),
|
duration: Duration(5580.0 * S),
|
||||||
comments: String::from("I did a lot of distance back then"),
|
comments: String::from("I did a lot of distance back then"),
|
||||||
},
|
},
|
||||||
BikeTrip {
|
BikeTrip {
|
||||||
datetime: DateTimeTz(UTC.ymd(2011, 11, 05).and_hms(0, 0, 0)),
|
datetime: DateTimeTz(UTC.with_ymd_and_hms(2011, 11, 05, 0, 0, 0).unwrap()),
|
||||||
distance: Distance(6437.376 * M),
|
distance: Distance(6437.376 * M),
|
||||||
duration: Duration(960.0 * S),
|
duration: Duration(960.0 * S),
|
||||||
comments: String::from("day 5"),
|
comments: String::from("day 5"),
|
||||||
|
@ -122,7 +122,7 @@ mod test {
|
||||||
Some(tr) => {
|
Some(tr) => {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
tr.timestamp(),
|
tr.timestamp(),
|
||||||
DateTimeTz(UTC.ymd(2011, 10, 29).and_hms(0, 0, 0)).into()
|
DateTimeTz(UTC.with_ymd_and_hms(2011, 10, 29, 0, 0, 0).unwrap()).into()
|
||||||
);
|
);
|
||||||
assert_eq!(tr.duration, Duration(11040.0 * S));
|
assert_eq!(tr.duration, Duration(11040.0 * S));
|
||||||
assert_eq!(tr.comments, String::from("long time ago"));
|
assert_eq!(tr.comments, String::from("long time ago"));
|
||||||
|
@ -145,7 +145,7 @@ mod test {
|
||||||
|
|
||||||
let v: Vec<(&UniqueId, &BikeTrip)> = ts
|
let v: Vec<(&UniqueId, &BikeTrip)> = ts
|
||||||
.search(exact_time(
|
.search(exact_time(
|
||||||
DateTimeTz(UTC.ymd(2011, 10, 31).and_hms(0, 0, 0)).into(),
|
DateTimeTz(UTC.with_ymd_and_hms(2011, 10, 31, 0, 0, 0).unwrap()).into(),
|
||||||
))
|
))
|
||||||
.collect();
|
.collect();
|
||||||
assert_eq!(v.len(), 1);
|
assert_eq!(v.len(), 1);
|
||||||
|
@ -166,9 +166,9 @@ mod test {
|
||||||
|
|
||||||
let v: Vec<(&UniqueId, &BikeTrip)> = ts.search_sorted(
|
let v: Vec<(&UniqueId, &BikeTrip)> = ts.search_sorted(
|
||||||
time_range(
|
time_range(
|
||||||
DateTimeTz(UTC.ymd(2011, 10, 31).and_hms(0, 0, 0)).into(),
|
DateTimeTz(UTC.with_ymd_and_hms(2011, 10, 31, 0, 0, 0).unwrap()).into(),
|
||||||
true,
|
true,
|
||||||
DateTimeTz(UTC.ymd(2011, 11, 04).and_hms(0, 0, 0)).into(),
|
DateTimeTz(UTC.with_ymd_and_hms(2011, 11, 04, 0, 0, 0).unwrap()).into(),
|
||||||
true,
|
true,
|
||||||
),
|
),
|
||||||
|l, r| l.1.timestamp().cmp(&r.1.timestamp()),
|
|l, r| l.1.timestamp().cmp(&r.1.timestamp()),
|
||||||
|
@ -199,9 +199,9 @@ mod test {
|
||||||
.expect("expect the time series to open correctly");
|
.expect("expect the time series to open correctly");
|
||||||
let v: Vec<(&UniqueId, &BikeTrip)> = ts.search_sorted(
|
let v: Vec<(&UniqueId, &BikeTrip)> = ts.search_sorted(
|
||||||
time_range(
|
time_range(
|
||||||
DateTimeTz(UTC.ymd(2011, 10, 31).and_hms(0, 0, 0)).into(),
|
DateTimeTz(UTC.with_ymd_and_hms(2011, 10, 31, 0, 0, 0).unwrap()).into(),
|
||||||
true,
|
true,
|
||||||
DateTimeTz(UTC.ymd(2011, 11, 04).and_hms(0, 0, 0)).into(),
|
DateTimeTz(UTC.with_ymd_and_hms(2011, 11, 04, 0, 0, 0).unwrap()).into(),
|
||||||
true,
|
true,
|
||||||
),
|
),
|
||||||
|l, r| l.1.timestamp().cmp(&r.1.timestamp()),
|
|l, r| l.1.timestamp().cmp(&r.1.timestamp()),
|
||||||
|
@ -233,9 +233,9 @@ mod test {
|
||||||
.expect("expect the time series to open correctly");
|
.expect("expect the time series to open correctly");
|
||||||
let v: Vec<(&UniqueId, &BikeTrip)> = ts.search_sorted(
|
let v: Vec<(&UniqueId, &BikeTrip)> = ts.search_sorted(
|
||||||
time_range(
|
time_range(
|
||||||
DateTimeTz(UTC.ymd(2011, 10, 31).and_hms(0, 0, 0)).into(),
|
DateTimeTz(UTC.with_ymd_and_hms(2011, 10, 31, 0, 0, 0).unwrap()).into(),
|
||||||
true,
|
true,
|
||||||
DateTimeTz(UTC.ymd(2011, 11, 04).and_hms(0, 0, 0)).into(),
|
DateTimeTz(UTC.with_ymd_and_hms(2011, 11, 04, 0, 0, 0).unwrap()).into(),
|
||||||
true,
|
true,
|
||||||
),
|
),
|
||||||
|l, r| l.1.timestamp().cmp(&r.1.timestamp()),
|
|l, r| l.1.timestamp().cmp(&r.1.timestamp()),
|
||||||
|
@ -252,9 +252,9 @@ mod test {
|
||||||
.expect("expect the time series to open correctly");
|
.expect("expect the time series to open correctly");
|
||||||
let v: Vec<(&UniqueId, &BikeTrip)> = ts.search_sorted(
|
let v: Vec<(&UniqueId, &BikeTrip)> = ts.search_sorted(
|
||||||
time_range(
|
time_range(
|
||||||
DateTimeTz(UTC.ymd(2011, 10, 31).and_hms(0, 0, 0)).into(),
|
DateTimeTz(UTC.with_ymd_and_hms(2011, 10, 31, 0, 0, 0).unwrap()).into(),
|
||||||
true,
|
true,
|
||||||
DateTimeTz(UTC.ymd(2011, 11, 05).and_hms(0, 0, 0)).into(),
|
DateTimeTz(UTC.with_ymd_and_hms(2011, 11, 05, 0, 0, 0).unwrap()).into(),
|
||||||
true,
|
true,
|
||||||
),
|
),
|
||||||
|l, r| l.1.timestamp().cmp(&r.1.timestamp()),
|
|l, r| l.1.timestamp().cmp(&r.1.timestamp()),
|
||||||
|
@ -294,7 +294,7 @@ mod test {
|
||||||
Some(trip) => {
|
Some(trip) => {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
trip.datetime,
|
trip.datetime,
|
||||||
DateTimeTz(UTC.ymd(2011, 11, 02).and_hms(0, 0, 0))
|
DateTimeTz(UTC.with_ymd_and_hms(2011, 11, 02, 0, 0, 0).unwrap())
|
||||||
);
|
);
|
||||||
assert_eq!(trip.distance, Distance(50000.0 * M));
|
assert_eq!(trip.distance, Distance(50000.0 * M));
|
||||||
assert_eq!(trip.duration, Duration(7020.0 * S));
|
assert_eq!(trip.duration, Duration(7020.0 * S));
|
||||||
|
@ -335,13 +335,13 @@ mod test {
|
||||||
|
|
||||||
let trips: Vec<(&UniqueId, &BikeTrip)> = ts
|
let trips: Vec<(&UniqueId, &BikeTrip)> = ts
|
||||||
.search(exact_time(
|
.search(exact_time(
|
||||||
DateTimeTz(UTC.ymd(2011, 11, 02).and_hms(0, 0, 0)).into(),
|
DateTimeTz(UTC.with_ymd_and_hms(2011, 11, 02, 0, 0, 0).unwrap()).into(),
|
||||||
))
|
))
|
||||||
.collect();
|
.collect();
|
||||||
assert_eq!(trips.len(), 1);
|
assert_eq!(trips.len(), 1);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
trips[0].1.datetime,
|
trips[0].1.datetime,
|
||||||
DateTimeTz(UTC.ymd(2011, 11, 02).and_hms(0, 0, 0))
|
DateTimeTz(UTC.with_ymd_and_hms(2011, 11, 02, 0, 0, 0).unwrap())
|
||||||
);
|
);
|
||||||
assert_eq!(trips[0].1.distance, Distance(50000.0 * M));
|
assert_eq!(trips[0].1.distance, Distance(50000.0 * M));
|
||||||
assert_eq!(trips[0].1.duration, Duration(7020.0 * S));
|
assert_eq!(trips[0].1.duration, Duration(7020.0 * S));
|
||||||
|
@ -361,7 +361,6 @@ mod test {
|
||||||
let trip_id = ts.put(trips[0].clone()).expect("expect a successful put");
|
let trip_id = ts.put(trips[0].clone()).expect("expect a successful put");
|
||||||
ts.put(trips[1].clone()).expect("expect a successful put");
|
ts.put(trips[1].clone()).expect("expect a successful put");
|
||||||
ts.put(trips[2].clone()).expect("expect a successful put");
|
ts.put(trips[2].clone()).expect("expect a successful put");
|
||||||
|
|
||||||
ts.delete(&trip_id).expect("successful delete");
|
ts.delete(&trip_id).expect("successful delete");
|
||||||
|
|
||||||
let recs: Vec<(&UniqueId, &BikeTrip)> = ts.records().collect();
|
let recs: Vec<(&UniqueId, &BikeTrip)> = ts.records().collect();
|
||||||
|
|
|
@ -26,11 +26,7 @@ pub async fn main() {
|
||||||
Commands::AddUser { username } => {
|
Commands::AddUser { username } => {
|
||||||
match authdb.add_user(Username::from(username.clone())).await {
|
match authdb.add_user(Username::from(username.clone())).await {
|
||||||
Ok(token) => {
|
Ok(token) => {
|
||||||
println!(
|
println!("User {} created. Auth token: {}", username, *token);
|
||||||
"User {} created. Auth token: {}",
|
|
||||||
username,
|
|
||||||
token.to_string()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
println!("Could not create user {}", username);
|
println!("Could not create user {}", username);
|
||||||
|
@ -38,7 +34,7 @@ pub async fn main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Commands::DeleteUser { username } => {}
|
Commands::DeleteUser { .. } => {}
|
||||||
Commands::ListUsers => {}
|
Commands::ListUsers => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
use build_html::Html;
|
use build_html::Html;
|
||||||
use bytes::Buf;
|
use bytes::Buf;
|
||||||
use cookie::time::error::Format;
|
|
||||||
use file_service::WriteFileError;
|
use file_service::WriteFileError;
|
||||||
use futures_util::StreamExt;
|
use futures_util::StreamExt;
|
||||||
use http::{Error, StatusCode};
|
use http::{Error, StatusCode};
|
||||||
|
@ -80,25 +79,22 @@ pub async fn handle_auth(
|
||||||
form: HashMap<String, String>,
|
form: HashMap<String, String>,
|
||||||
) -> Result<http::Response<String>, Error> {
|
) -> Result<http::Response<String>, Error> {
|
||||||
match form.get("token") {
|
match form.get("token") {
|
||||||
Some(token) => match app
|
Some(token) => match app.authenticate(AuthToken::from(token.clone())).await {
|
||||||
.authenticate(AuthToken::from(AuthToken::from(token.clone())))
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(Some(session_token)) => Response::builder()
|
Ok(Some(session_token)) => Response::builder()
|
||||||
.header("location", "/")
|
.header("location", "/")
|
||||||
.header(
|
.header(
|
||||||
"set-cookie",
|
"set-cookie",
|
||||||
format!(
|
format!(
|
||||||
"session={}; Secure; HttpOnly; SameSite=Strict",
|
"session={}; Secure; HttpOnly; SameSite=Strict",
|
||||||
session_token.to_string()
|
*session_token
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.status(StatusCode::SEE_OTHER)
|
.status(StatusCode::SEE_OTHER)
|
||||||
.body("".to_owned()),
|
.body("".to_owned()),
|
||||||
Ok(None) => render_auth_page(Some(format!("no user found"))),
|
Ok(None) => render_auth_page(Some("no user found".to_owned())),
|
||||||
Err(_) => render_auth_page(Some(format!("invalid auth token"))),
|
Err(_) => render_auth_page(Some("invalid auth token".to_owned())),
|
||||||
},
|
},
|
||||||
None => render_auth_page(Some(format!("no token available"))),
|
None => render_auth_page(Some("no token available".to_owned())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,7 @@ impl Html for Form {
|
||||||
fn to_html_string(&self) -> String {
|
fn to_html_string(&self) -> String {
|
||||||
let encoding = match self.encoding {
|
let encoding = match self.encoding {
|
||||||
Some(ref encoding) => format!("enctype=\"{encoding}\"", encoding = encoding),
|
Some(ref encoding) => format!("enctype=\"{encoding}\"", encoding = encoding),
|
||||||
None => format!(""),
|
None => "".to_owned(),
|
||||||
};
|
};
|
||||||
format!(
|
format!(
|
||||||
"<form action=\"{path}\" method=\"{method}\" {encoding}>\n{elements}\n</form>\n",
|
"<form action=\"{path}\" method=\"{method}\" {encoding}>\n{elements}\n</form>\n",
|
||||||
|
@ -108,10 +108,12 @@ impl Input {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
pub fn with_content(mut self, val: &str) -> Self {
|
pub fn with_content(mut self, val: &str) -> Self {
|
||||||
self.content = Some(val.to_owned());
|
self.content = Some(val.to_owned());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
#[macro_use]
|
|
||||||
extern crate log;
|
extern crate log;
|
||||||
|
|
||||||
use cookie::Cookie;
|
use cookie::Cookie;
|
||||||
|
@ -78,10 +77,7 @@ fn parse_cookies(cookie_str: &str) -> Result<HashMap<String, String>, cookie::Pa
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_session_token(cookies: HashMap<String, String>) -> Option<SessionToken> {
|
fn get_session_token(cookies: HashMap<String, String>) -> Option<SessionToken> {
|
||||||
cookies
|
cookies.get("session").cloned().map(SessionToken::from)
|
||||||
.get("session")
|
|
||||||
.cloned()
|
|
||||||
.and_then(|session| Some(SessionToken::from(session)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn maybe_with_session() -> impl Filter<Extract = (Option<SessionToken>,), Error = Rejection> + Copy
|
fn maybe_with_session() -> impl Filter<Extract = (Option<SessionToken>,), Error = Rejection> + Copy
|
||||||
|
|
|
@ -97,7 +97,7 @@ impl TryFrom<&Path> for PathResolver {
|
||||||
.ok_or(PathError::InvalidPath)?,
|
.ok_or(PathError::InvalidPath)?,
|
||||||
id: path
|
id: path
|
||||||
.file_stem()
|
.file_stem()
|
||||||
.and_then(|s| s.to_str().map(|s| FileId::from(s)))
|
.and_then(|s| s.to_str().map(FileId::from))
|
||||||
.ok_or(PathError::InvalidPath)?,
|
.ok_or(PathError::InvalidPath)?,
|
||||||
extension: path
|
extension: path
|
||||||
.extension()
|
.extension()
|
||||||
|
@ -146,7 +146,7 @@ impl FileHandle {
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut md_file = std::fs::File::create(path.metadata_path())?;
|
let mut md_file = std::fs::File::create(path.metadata_path())?;
|
||||||
md_file.write(&serde_json::to_vec(&info)?)?;
|
let _ = md_file.write(&serde_json::to_vec(&info)?)?;
|
||||||
|
|
||||||
Ok(Self { id, path, info })
|
Ok(Self { id, path, info })
|
||||||
}
|
}
|
||||||
|
@ -168,7 +168,7 @@ impl FileHandle {
|
||||||
self.info.hash = self.hash_content(&content).as_string();
|
self.info.hash = self.hash_content(&content).as_string();
|
||||||
|
|
||||||
let mut md_file = std::fs::File::create(self.path.metadata_path())?;
|
let mut md_file = std::fs::File::create(self.path.metadata_path())?;
|
||||||
md_file.write(&serde_json::to_vec(&self.info)?)?;
|
let _ = md_file.write(&serde_json::to_vec(&self.info)?)?;
|
||||||
|
|
||||||
self.write_thumbnail()?;
|
self.write_thumbnail()?;
|
||||||
|
|
||||||
|
@ -188,9 +188,9 @@ impl FileHandle {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_thumbnail(&self) -> Result<(), WriteFileError> {
|
fn write_thumbnail(&self) -> Result<(), WriteFileError> {
|
||||||
let img = image::open(&self.path.file_path())?;
|
let img = image::open(self.path.file_path())?;
|
||||||
let tn = img.resize(640, 640, FilterType::Nearest);
|
let tn = img.resize(640, 640, FilterType::Nearest);
|
||||||
tn.save(&self.path.thumbnail_path())?;
|
tn.save(self.path.thumbnail_path())?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -203,7 +203,7 @@ impl FileHandle {
|
||||||
|
|
||||||
fn load_content(path: &Path) -> Result<Vec<u8>, ReadFileError> {
|
fn load_content(path: &Path) -> Result<Vec<u8>, ReadFileError> {
|
||||||
let mut buf = Vec::new();
|
let mut buf = Vec::new();
|
||||||
let mut file = std::fs::File::open(&path)?;
|
let mut file = std::fs::File::open(path)?;
|
||||||
file.read_to_end(&mut buf)?;
|
file.read_to_end(&mut buf)?;
|
||||||
Ok(buf)
|
Ok(buf)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,6 @@ use crate::FileId;
|
||||||
use super::{ReadFileError, WriteFileError};
|
use super::{ReadFileError, WriteFileError};
|
||||||
use chrono::prelude::*;
|
use chrono::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json;
|
|
||||||
use std::{
|
use std::{
|
||||||
io::{Read, Write},
|
io::{Read, Write},
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
|
@ -33,7 +32,7 @@ impl FileInfo {
|
||||||
pub fn save(&self, path: PathBuf) -> Result<(), WriteFileError> {
|
pub fn save(&self, path: PathBuf) -> Result<(), WriteFileError> {
|
||||||
let ser = serde_json::to_string(self).unwrap();
|
let ser = serde_json::to_string(self).unwrap();
|
||||||
let mut file = std::fs::File::create(path)?;
|
let mut file = std::fs::File::create(path)?;
|
||||||
file.write(ser.as_bytes())?;
|
let _ = file.write(ser.as_bytes())?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,12 +3,10 @@ use serde::{Deserialize, Serialize};
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
use sqlx::{
|
use sqlx::{
|
||||||
sqlite::{SqlitePool, SqliteRow},
|
sqlite::{SqlitePool, SqliteRow},
|
||||||
Executor, Row,
|
Row,
|
||||||
};
|
};
|
||||||
use std::collections::HashSet;
|
use std::{collections::HashSet, ops::Deref, path::PathBuf};
|
||||||
use std::{ops::Deref, path::PathBuf, sync::Arc};
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use tokio::sync::RwLock;
|
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
mod filehandle;
|
mod filehandle;
|
||||||
|
@ -288,7 +286,7 @@ impl AuthDB {
|
||||||
return Err(AuthError::DuplicateAuthToken);
|
return Err(AuthError::DuplicateAuthToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
if results.len() == 0 {
|
if results.is_empty() {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -322,7 +320,7 @@ impl AuthDB {
|
||||||
return Err(AuthError::DuplicateSessionToken);
|
return Err(AuthError::DuplicateSessionToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
if rows.len() == 0 {
|
if rows.is_empty() {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -348,7 +346,7 @@ impl Store {
|
||||||
let path_ = path.unwrap().path();
|
let path_ = path.unwrap().path();
|
||||||
if path_.extension().and_then(|s| s.to_str()) == Some("json") {
|
if path_.extension().and_then(|s| s.to_str()) == Some("json") {
|
||||||
let stem = path_.file_stem().and_then(|s| s.to_str()).unwrap();
|
let stem = path_.file_stem().and_then(|s| s.to_str()).unwrap();
|
||||||
Some(FileId::from(FileId::from(stem)))
|
Some(FileId::from(stem))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,6 @@ name = "flow"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "GPL-3.0-only"
|
license = "GPL-3.0-only"
|
||||||
license-file = "../COPYING"
|
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,6 @@ edition = "2018"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
description = "An ergonomics wrapper around Fluent-RS"
|
description = "An ergonomics wrapper around Fluent-RS"
|
||||||
license = "GPL-3.0-only"
|
license = "GPL-3.0-only"
|
||||||
license-file = "../COPYING"
|
|
||||||
homepage = "https://github.com/luminescent-dreams/fluent-ergonomics"
|
homepage = "https://github.com/luminescent-dreams/fluent-ergonomics"
|
||||||
repository = "https://github.com/luminescent-dreams/fluent-ergonomics"
|
repository = "https://github.com/luminescent-dreams/fluent-ergonomics"
|
||||||
categories = ["internationalization"]
|
categories = ["internationalization"]
|
||||||
|
|
|
@ -171,14 +171,14 @@ impl FluentErgo {
|
||||||
match entry {
|
match entry {
|
||||||
Entry::Occupied(mut e) => {
|
Entry::Occupied(mut e) => {
|
||||||
let bundle = e.get_mut();
|
let bundle = e.get_mut();
|
||||||
bundle.add_resource(res).map_err(|err| Error::from(err))
|
bundle.add_resource(res).map_err(Error::from)
|
||||||
}
|
}
|
||||||
Entry::Vacant(e) => {
|
Entry::Vacant(e) => {
|
||||||
let mut bundle: FluentBundle<
|
let mut bundle: FluentBundle<
|
||||||
FluentResource,
|
FluentResource,
|
||||||
intl_memoizer::concurrent::IntlLangMemoizer,
|
intl_memoizer::concurrent::IntlLangMemoizer,
|
||||||
> = FluentBundle::new_concurrent(vec![lang]);
|
> = FluentBundle::new_concurrent(vec![lang]);
|
||||||
bundle.add_resource(res).map_err(|err| Error::from(err))?;
|
bundle.add_resource(res).map_err(Error::from)?;
|
||||||
e.insert(bundle);
|
e.insert(bundle);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -248,16 +248,10 @@ impl FluentErgo {
|
||||||
///
|
///
|
||||||
pub fn tr(&self, msgid: &str, args: Option<&FluentArgs>) -> Result<String, Error> {
|
pub fn tr(&self, msgid: &str, args: Option<&FluentArgs>) -> Result<String, Error> {
|
||||||
let bundles = self.bundles.read().unwrap();
|
let bundles = self.bundles.read().unwrap();
|
||||||
let result: Option<String> = self
|
let result: Option<String> = self.languages.iter().find_map(|lang| {
|
||||||
.languages
|
|
||||||
.iter()
|
|
||||||
.map(|lang| {
|
|
||||||
let bundle = bundles.get(lang)?;
|
let bundle = bundles.get(lang)?;
|
||||||
self.tr_(bundle, msgid, args)
|
self.tr_(bundle, msgid, args)
|
||||||
})
|
});
|
||||||
.filter(|v| v.is_some())
|
|
||||||
.map(|v| v.unwrap())
|
|
||||||
.next();
|
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Some(r) => Ok(r),
|
Some(r) => Ok(r),
|
||||||
|
@ -276,8 +270,8 @@ impl FluentErgo {
|
||||||
let res = match pattern {
|
let res = match pattern {
|
||||||
None => None,
|
None => None,
|
||||||
Some(p) => {
|
Some(p) => {
|
||||||
let res = bundle.format_pattern(&p, args, &mut errors);
|
let res = bundle.format_pattern(p, args, &mut errors);
|
||||||
if errors.len() > 0 {
|
if !errors.is_empty() {
|
||||||
println!("Errors in formatting: {:?}", errors)
|
println!("Errors in formatting: {:?}", errors)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ pub struct Latitude(f32);
|
||||||
|
|
||||||
impl From<f32> for Latitude {
|
impl From<f32> for Latitude {
|
||||||
fn from(val: f32) -> Self {
|
fn from(val: f32) -> Self {
|
||||||
if val > 90.0 || val < -90.0 {
|
if !(-90.0..=90.0).contains(&val) {
|
||||||
panic!("Latitude is outside of range");
|
panic!("Latitude is outside of range");
|
||||||
}
|
}
|
||||||
Self(val)
|
Self(val)
|
||||||
|
@ -23,7 +23,7 @@ pub struct Longitude(f32);
|
||||||
|
|
||||||
impl From<f32> for Longitude {
|
impl From<f32> for Longitude {
|
||||||
fn from(val: f32) -> Self {
|
fn from(val: f32) -> Self {
|
||||||
if val > 180.0 || val < -180.0 {
|
if !(-180.0..=180.0).contains(&val) {
|
||||||
panic!("Longitude is outside fo range");
|
panic!("Longitude is outside fo range");
|
||||||
}
|
}
|
||||||
Self(val)
|
Self(val)
|
||||||
|
|
|
@ -45,7 +45,7 @@ impl ApplicationWindow {
|
||||||
]
|
]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|name| {
|
.map(|name| {
|
||||||
let playlist = PlaylistCard::new();
|
let playlist = PlaylistCard::default();
|
||||||
playlist.set_name(name);
|
playlist.set_name(name);
|
||||||
playlist
|
playlist
|
||||||
})
|
})
|
||||||
|
|
|
@ -31,8 +31,8 @@ glib::wrapper! {
|
||||||
pub struct PlaylistCard(ObjectSubclass<PlaylistCardPrivate>) @extends gtk::Box, gtk::Widget, @implements gtk::Orientable;
|
pub struct PlaylistCard(ObjectSubclass<PlaylistCardPrivate>) @extends gtk::Box, gtk::Widget, @implements gtk::Orientable;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlaylistCard {
|
impl Default for PlaylistCard {
|
||||||
pub fn new() -> Self {
|
fn default() -> Self {
|
||||||
let s: Self = Object::builder().build();
|
let s: Self = Object::builder().build();
|
||||||
s.set_orientation(gtk::Orientation::Vertical);
|
s.set_orientation(gtk::Orientation::Vertical);
|
||||||
s.add_css_class("playlist-card");
|
s.add_css_class("playlist-card");
|
||||||
|
@ -43,7 +43,9 @@ impl PlaylistCard {
|
||||||
|
|
||||||
s
|
s
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PlaylistCard {
|
||||||
pub fn set_name(&self, s: &str) {
|
pub fn set_name(&self, s: &str) {
|
||||||
self.imp().name.set_text(s);
|
self.imp().name.set_text(s);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
use gtk::prelude::*;
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
gtk::init();
|
let _ = gtk::init();
|
||||||
for name in gtk::IconTheme::new().icon_names() {
|
for name in gtk::IconTheme::new().icon_names() {
|
||||||
println!("{}", name);
|
println!("{}", name);
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,10 +10,9 @@ Luminescent Dreams Tools is distributed in the hope that it will be useful, but
|
||||||
You should have received a copy of the GNU General Public License along with Lumeto. If not, see <https://www.gnu.org/licenses/>.
|
You should have received a copy of the GNU General Public License along with Lumeto. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use cairo::Context;
|
|
||||||
use coordinates::{hex_map::parse_data, AxialAddr};
|
use coordinates::{hex_map::parse_data, AxialAddr};
|
||||||
use gio::resources_lookup_data;
|
use gio::resources_lookup_data;
|
||||||
use glib::{subclass::InitializingObject, Object};
|
use glib::Object;
|
||||||
use gtk::{gio, prelude::*, subclass::prelude::*, Application, DrawingArea};
|
use gtk::{gio, prelude::*, subclass::prelude::*, Application, DrawingArea};
|
||||||
use image::io::Reader as ImageReader;
|
use image::io::Reader as ImageReader;
|
||||||
use std::{cell::RefCell, io::Cursor, rc::Rc};
|
use std::{cell::RefCell, io::Cursor, rc::Rc};
|
||||||
|
@ -23,7 +22,7 @@ mod palette_entry;
|
||||||
mod tile;
|
mod tile;
|
||||||
mod utilities;
|
mod utilities;
|
||||||
|
|
||||||
const APP_ID: &'static str = "com.luminescent-dreams.hex-grid";
|
const APP_ID: &str = "com.luminescent-dreams.hex-grid";
|
||||||
const HEX_RADIUS: f64 = 50.;
|
const HEX_RADIUS: f64 = 50.;
|
||||||
const MAP_RADIUS: usize = 3;
|
const MAP_RADIUS: usize = 3;
|
||||||
const DRAWING_ORIGIN: (f64, f64) = (1024. / 2., 768. / 2.);
|
const DRAWING_ORIGIN: (f64, f64) = (1024. / 2., 768. / 2.);
|
||||||
|
@ -178,14 +177,14 @@ impl ObjectImpl for HexGridWindowPrivate {
|
||||||
let norm_x = x - DRAWING_ORIGIN.0;
|
let norm_x = x - DRAWING_ORIGIN.0;
|
||||||
let norm_y = y - DRAWING_ORIGIN.1;
|
let norm_y = y - DRAWING_ORIGIN.1;
|
||||||
let q = (2. / 3. * norm_x) / HEX_RADIUS;
|
let q = (2. / 3. * norm_x) / HEX_RADIUS;
|
||||||
let r = (-1. / 3. * norm_x + (3. as f64).sqrt() / 3. * norm_y) / HEX_RADIUS;
|
let r = (-1. / 3. * norm_x + (3_f64).sqrt() / 3. * norm_y) / HEX_RADIUS;
|
||||||
|
|
||||||
let (q, r) = axial_round(q, r);
|
let (q, r) = axial_round(q, r);
|
||||||
let coordinate = AxialAddr::new(q, r);
|
let coordinate = AxialAddr::new(q, r);
|
||||||
canvas_address.set_value(&format!("{:.0} {:.0}", x, y));
|
canvas_address.set_value(&format!("{:.0} {:.0}", x, y));
|
||||||
|
|
||||||
if coordinate.distance(&AxialAddr::origin()) > MAP_RADIUS {
|
if coordinate.distance(&AxialAddr::origin()) > MAP_RADIUS {
|
||||||
hex_address.set_value(&format!("-----"));
|
hex_address.set_value("-----");
|
||||||
*c.borrow_mut() = None;
|
*c.borrow_mut() = None;
|
||||||
} else {
|
} else {
|
||||||
hex_address.set_value(&format!("{:.0} {:.0}", coordinate.q(), coordinate.r()));
|
hex_address.set_value(&format!("{:.0} {:.0}", coordinate.q(), coordinate.r()));
|
||||||
|
@ -209,10 +208,10 @@ impl ObjectImpl for HexGridWindowPrivate {
|
||||||
DRAWING_ORIGIN.0 + HEX_RADIUS * (3. / 2. * (coordinate.q() as f64));
|
DRAWING_ORIGIN.0 + HEX_RADIUS * (3. / 2. * (coordinate.q() as f64));
|
||||||
let center_y = DRAWING_ORIGIN.1
|
let center_y = DRAWING_ORIGIN.1
|
||||||
+ HEX_RADIUS
|
+ HEX_RADIUS
|
||||||
* ((3. as f64).sqrt() / 2. * (coordinate.q() as f64)
|
* ((3_f64).sqrt() / 2. * (coordinate.q() as f64)
|
||||||
+ (3. as f64).sqrt() * (coordinate.r() as f64));
|
+ (3_f64).sqrt() * (coordinate.r() as f64));
|
||||||
let translate_x = center_x - HEX_RADIUS;
|
let translate_x = center_x - HEX_RADIUS;
|
||||||
let translate_y = center_y - (3. as f64).sqrt() * HEX_RADIUS / 2.;
|
let translate_y = center_y - (3_f64).sqrt() * HEX_RADIUS / 2.;
|
||||||
|
|
||||||
let tile = match hex_map.get(&coordinate).unwrap() {
|
let tile = match hex_map.get(&coordinate).unwrap() {
|
||||||
tile::Terrain::Mountain => &mountain,
|
tile::Terrain::Mountain => &mountain,
|
||||||
|
@ -249,10 +248,11 @@ impl HexGridWindow {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
fn draw_hexagon(context: &Context, center_x: f64, center_y: f64, radius: f64) {
|
fn draw_hexagon(context: &Context, center_x: f64, center_y: f64, radius: f64) {
|
||||||
let ul_x = center_x - radius;
|
let ul_x = center_x - radius;
|
||||||
let ul_y = center_y - (3. as f64).sqrt() * radius / 2.;
|
let ul_y = center_y - (3_f64).sqrt() * radius / 2.;
|
||||||
let points: Vec<(f64, f64)> = utilities::hexagon(radius * 2., (3. as f64).sqrt() * radius);
|
let points: Vec<(f64, f64)> = utilities::hexagon(radius * 2., (3_f64).sqrt() * radius);
|
||||||
context.new_path();
|
context.new_path();
|
||||||
context.move_to(ul_x + points[0].0, ul_y + points[0].1);
|
context.move_to(ul_x + points[0].0, ul_y + points[0].1);
|
||||||
context.line_to(ul_x + points[1].0, ul_y + points[1].1);
|
context.line_to(ul_x + points[1].0, ul_y + points[1].1);
|
||||||
|
@ -262,6 +262,7 @@ fn draw_hexagon(context: &Context, center_x: f64, center_y: f64, radius: f64) {
|
||||||
context.line_to(ul_x + points[5].0, ul_y + points[5].1);
|
context.line_to(ul_x + points[5].0, ul_y + points[5].1);
|
||||||
context.close_path();
|
context.close_path();
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
fn axial_round(q_f64: f64, r_f64: f64) -> (i32, i32) {
|
fn axial_round(q_f64: f64, r_f64: f64) -> (i32, i32) {
|
||||||
let s_f64 = -q_f64 - r_f64;
|
let s_f64 = -q_f64 - r_f64;
|
||||||
|
|
|
@ -27,20 +27,20 @@ pub fn hexagon(width: f64, height: f64) -> Vec<(f64, f64)> {
|
||||||
(center_x + radius, center_y),
|
(center_x + radius, center_y),
|
||||||
(
|
(
|
||||||
center_x + radius / 2.,
|
center_x + radius / 2.,
|
||||||
center_y + (3. as f64).sqrt() * radius / 2.,
|
center_y + (3_f64).sqrt() * radius / 2.,
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
center_x - radius / 2.,
|
center_x - radius / 2.,
|
||||||
center_y + (3. as f64).sqrt() * radius / 2.,
|
center_y + (3_f64).sqrt() * radius / 2.,
|
||||||
),
|
),
|
||||||
(center_x - radius, center_y),
|
(center_x - radius, center_y),
|
||||||
(
|
(
|
||||||
center_x - radius / 2.,
|
center_x - radius / 2.,
|
||||||
center_y - (3. as f64).sqrt() * radius / 2.,
|
center_y - (3_f64).sqrt() * radius / 2.,
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
center_x + radius / 2.,
|
center_x + radius / 2.,
|
||||||
center_y - (3. as f64).sqrt() * radius / 2.,
|
center_y - (3_f64).sqrt() * radius / 2.,
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,6 @@ edition = "2018"
|
||||||
keywords = ["date", "time", "calendar"]
|
keywords = ["date", "time", "calendar"]
|
||||||
categories = ["date-and-time"]
|
categories = ["date-and-time"]
|
||||||
license = "GPL-3.0-only"
|
license = "GPL-3.0-only"
|
||||||
license-file = "../COPYING"
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
chrono = { version = "0.4" }
|
chrono = { version = "0.4" }
|
||||||
|
|
|
@ -141,9 +141,7 @@ impl IFC {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ymd(year: i32, month: u8, day: u8) -> Result<Self, Error> {
|
pub fn ymd(year: i32, month: u8, day: u8) -> Result<Self, Error> {
|
||||||
if month < 1 || month > 13 {
|
if !(1..=13).contains(&month) || !(1..=28).contains(&day) {
|
||||||
Err(Error::InvalidDate)
|
|
||||||
} else if day < 1 || day > 28 {
|
|
||||||
Err(Error::InvalidDate)
|
Err(Error::InvalidDate)
|
||||||
} else {
|
} else {
|
||||||
Ok(Self::Day(Day { year, month, day }))
|
Ok(Self::Day(Day { year, month, day }))
|
||||||
|
@ -248,12 +246,12 @@ impl From<chrono::NaiveDate> for IFC {
|
||||||
if is_leap_year(date.year())
|
if is_leap_year(date.year())
|
||||||
&& date > NaiveDate::from_ymd_opt(date.year(), 6, 17).unwrap()
|
&& date > NaiveDate::from_ymd_opt(date.year(), 6, 17).unwrap()
|
||||||
{
|
{
|
||||||
days = days - 1;
|
days -= 1;
|
||||||
}
|
}
|
||||||
let mut month: u8 = (days / 28).try_into().unwrap();
|
let mut month: u8 = (days / 28).try_into().unwrap();
|
||||||
let mut day: u8 = (days % 28).try_into().unwrap();
|
let mut day: u8 = (days % 28).try_into().unwrap();
|
||||||
if day == 0 {
|
if day == 0 {
|
||||||
month = month - 1;
|
month -= 1;
|
||||||
day = 28;
|
day = 28;
|
||||||
}
|
}
|
||||||
Self::Day(Day {
|
Self::Day(Day {
|
||||||
|
|
|
@ -120,7 +120,7 @@ impl CoreApp {
|
||||||
app_state.game = Some(GameState {
|
app_state.game = Some(GameState {
|
||||||
white_player,
|
white_player,
|
||||||
black_player,
|
black_player,
|
||||||
..GameState::new()
|
..GameState::default()
|
||||||
});
|
});
|
||||||
let game_state = app_state.game.as_ref().unwrap();
|
let game_state = app_state.game.as_ref().unwrap();
|
||||||
CoreResponse::PlayingFieldView(playing_field(game_state))
|
CoreResponse::PlayingFieldView(playing_field(game_state))
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::{BoardError, Color, Size};
|
use crate::{BoardError, Color, Size};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct Board {
|
pub struct Board {
|
||||||
pub size: Size,
|
pub size: Size,
|
||||||
pub groups: Vec<Group>,
|
pub groups: Vec<Group>,
|
||||||
|
@ -14,7 +14,7 @@ impl std::fmt::Display for Board {
|
||||||
for c in 0..19 {
|
for c in 0..19 {
|
||||||
write!(f, "{:2}", c)?;
|
write!(f, "{:2}", c)?;
|
||||||
}
|
}
|
||||||
writeln!(f, "")?;
|
writeln!(f)?;
|
||||||
|
|
||||||
for row in 0..self.size.height {
|
for row in 0..self.size.height {
|
||||||
write!(f, " {:2}", row)?;
|
write!(f, " {:2}", row)?;
|
||||||
|
@ -25,7 +25,7 @@ impl std::fmt::Display for Board {
|
||||||
Some(Color::White) => write!(f, " O")?,
|
Some(Color::White) => write!(f, " O")?,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
writeln!(f, "")?;
|
writeln!(f)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -38,16 +38,16 @@ impl PartialEq for Board {
|
||||||
}
|
}
|
||||||
|
|
||||||
for group in self.groups.iter() {
|
for group in self.groups.iter() {
|
||||||
if !other.groups.contains(&group) {
|
if !other.groups.contains(group) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for group in other.groups.iter() {
|
for group in other.groups.iter() {
|
||||||
if !self.groups.contains(&group) {
|
if !self.groups.contains(group) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,7 +79,7 @@ pub struct Coordinate {
|
||||||
|
|
||||||
impl Board {
|
impl Board {
|
||||||
pub fn place_stone(mut self, coordinate: Coordinate, color: Color) -> Result<Self, BoardError> {
|
pub fn place_stone(mut self, coordinate: Coordinate, color: Color) -> Result<Self, BoardError> {
|
||||||
if let Some(_) = self.stone(&coordinate) {
|
if self.stone(&coordinate).is_some() {
|
||||||
return Err(BoardError::InvalidPosition);
|
return Err(BoardError::InvalidPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,7 +92,7 @@ impl Board {
|
||||||
acc.union(&set).cloned().collect()
|
acc.union(&set).cloned().collect()
|
||||||
});
|
});
|
||||||
|
|
||||||
friendly_group.insert(coordinate.clone());
|
friendly_group.insert(coordinate);
|
||||||
|
|
||||||
self.groups
|
self.groups
|
||||||
.retain(|g| g.coordinates.is_disjoint(&friendly_group));
|
.retain(|g| g.coordinates.is_disjoint(&friendly_group));
|
||||||
|
@ -138,7 +138,7 @@ impl Board {
|
||||||
let mut grps: Vec<Group> = Vec::new();
|
let mut grps: Vec<Group> = Vec::new();
|
||||||
|
|
||||||
adjacent_spaces.for_each(|coord| match self.group(&coord) {
|
adjacent_spaces.for_each(|coord| match self.group(&coord) {
|
||||||
None => return,
|
None => {}
|
||||||
Some(adj) => {
|
Some(adj) => {
|
||||||
if group.color == adj.color {
|
if group.color == adj.color {
|
||||||
return;
|
return;
|
||||||
|
@ -157,15 +157,14 @@ impl Board {
|
||||||
group
|
group
|
||||||
.coordinates
|
.coordinates
|
||||||
.iter()
|
.iter()
|
||||||
.map(|c| self.adjacencies(c))
|
.flat_map(|c| self.adjacencies(c))
|
||||||
.flatten()
|
|
||||||
.collect::<HashSet<Coordinate>>()
|
.collect::<HashSet<Coordinate>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn liberties(&self, group: &Group) -> usize {
|
pub fn liberties(&self, group: &Group) -> usize {
|
||||||
self.group_halo(group)
|
self.group_halo(group)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|c| self.stone(&c) == None)
|
.filter(|c| self.stone(c).is_none())
|
||||||
.count()
|
.count()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
use std::{ffi::OsStr, io::Read, os::unix::ffi::OsStrExt, path::PathBuf};
|
use std::{io::Read, path::PathBuf};
|
||||||
|
|
||||||
use sgf::{go, parse_sgf, Game};
|
use sgf::{go, parse_sgf, Game};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
|
/*
|
||||||
#[error("Database permission denied")]
|
#[error("Database permission denied")]
|
||||||
PermissionDenied,
|
PermissionDenied,
|
||||||
|
*/
|
||||||
#[error("An IO error occurred: {0}")]
|
#[error("An IO error occurred: {0}")]
|
||||||
IOError(std::io::Error),
|
IOError(std::io::Error),
|
||||||
}
|
}
|
||||||
|
@ -19,7 +21,6 @@ impl From<std::io::Error> for Error {
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Database {
|
pub struct Database {
|
||||||
path: PathBuf,
|
|
||||||
games: Vec<go::Game>,
|
games: Vec<go::Game>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,11 +57,7 @@ impl Database {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Database { path, games })
|
Ok(Database { games })
|
||||||
}
|
|
||||||
|
|
||||||
pub fn len(&self) -> usize {
|
|
||||||
self.games.len()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn all_games(&self) -> impl Iterator<Item = &go::Game> {
|
pub fn all_games(&self) -> impl Iterator<Item = &go::Game> {
|
||||||
|
@ -86,14 +83,13 @@ mod test {
|
||||||
let db =
|
let db =
|
||||||
Database::open_path(PathBuf::from("fixtures/five_games/")).expect("database to open");
|
Database::open_path(PathBuf::from("fixtures/five_games/")).expect("database to open");
|
||||||
assert_eq!(db.all_games().count(), 5);
|
assert_eq!(db.all_games().count(), 5);
|
||||||
for game in db.all_games() {}
|
|
||||||
|
|
||||||
assert_matches!(db.all_games().find(|g| g.info.black_player == Some("Steve".to_owned())),
|
assert_matches!(db.all_games().find(|g| g.info.black_player == Some("Steve".to_owned())),
|
||||||
Some(game) => {
|
Some(game) => {
|
||||||
assert_eq!(game.info.black_player, Some("Steve".to_owned()));
|
assert_eq!(game.info.black_player, Some("Steve".to_owned()));
|
||||||
assert_eq!(game.info.white_player, Some("Savanni".to_owned()));
|
assert_eq!(game.info.white_player, Some("Savanni".to_owned()));
|
||||||
assert_eq!(game.info.date, vec![Date::Date(chrono::NaiveDate::from_ymd_opt(2023, 4, 19).unwrap())]);
|
assert_eq!(game.info.date, vec![Date::Date(chrono::NaiveDate::from_ymd_opt(2023, 4, 19).unwrap())]);
|
||||||
assert_eq!(game.info.komi, Some(6.5));
|
// assert_eq!(game.info.komi, Some(6.5));
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
#[macro_use]
|
|
||||||
extern crate config_derive;
|
extern crate config_derive;
|
||||||
|
|
||||||
mod api;
|
mod api;
|
||||||
|
@ -10,11 +9,6 @@ pub use api::{
|
||||||
mod board;
|
mod board;
|
||||||
pub use board::*;
|
pub use board::*;
|
||||||
|
|
||||||
/*
|
|
||||||
mod config;
|
|
||||||
pub use config::*;
|
|
||||||
*/
|
|
||||||
|
|
||||||
mod database;
|
mod database;
|
||||||
|
|
||||||
mod types;
|
mod types;
|
||||||
|
|
|
@ -77,21 +77,18 @@ pub struct AppState {
|
||||||
impl AppState {
|
impl AppState {
|
||||||
pub fn new(database_path: DatabasePath) -> Self {
|
pub fn new(database_path: DatabasePath) -> Self {
|
||||||
Self {
|
Self {
|
||||||
game: Some(GameState::new()),
|
game: Some(GameState::default()),
|
||||||
database: Database::open_path(database_path.to_path_buf()).unwrap(),
|
database: Database::open_path(database_path.to_path_buf()).unwrap(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn place_stone(&mut self, req: PlayStoneRequest) {
|
pub fn place_stone(&mut self, req: PlayStoneRequest) {
|
||||||
match self.game {
|
if let Some(ref mut game) = self.game {
|
||||||
Some(ref mut game) => {
|
|
||||||
let _ = game.place_stone(Coordinate {
|
let _ = game.place_stone(Coordinate {
|
||||||
column: req.column,
|
column: req.column,
|
||||||
row: req.row,
|
row: req.row,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
None => {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -142,9 +139,9 @@ pub struct GameState {
|
||||||
pub black_clock: Duration,
|
pub black_clock: Duration,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GameState {
|
impl Default for GameState {
|
||||||
pub fn new() -> GameState {
|
fn default() -> Self {
|
||||||
GameState {
|
Self {
|
||||||
board: Board::new(),
|
board: Board::new(),
|
||||||
past_positions: vec![],
|
past_positions: vec![],
|
||||||
conversation: vec![],
|
conversation: vec![],
|
||||||
|
@ -161,7 +158,9 @@ impl GameState {
|
||||||
black_clock: Duration::from_secs(600),
|
black_clock: Duration::from_secs(600),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GameState {
|
||||||
fn place_stone(&mut self, coordinate: Coordinate) -> Result<(), BoardError> {
|
fn place_stone(&mut self, coordinate: Coordinate) -> Result<(), BoardError> {
|
||||||
let board = self.board.clone();
|
let board = self.board.clone();
|
||||||
let new_board = board.place_stone(coordinate, self.current_player)?;
|
let new_board = board.place_stone(coordinate, self.current_player)?;
|
||||||
|
@ -186,7 +185,7 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn current_player_changes_after_move() {
|
fn current_player_changes_after_move() {
|
||||||
let mut state = GameState::new();
|
let mut state = GameState::default();
|
||||||
assert_eq!(state.current_player, Color::Black);
|
assert_eq!(state.current_player, Color::Black);
|
||||||
state.place_stone(Coordinate { column: 9, row: 9 }).unwrap();
|
state.place_stone(Coordinate { column: 9, row: 9 }).unwrap();
|
||||||
assert_eq!(state.current_player, Color::White);
|
assert_eq!(state.current_player, Color::White);
|
||||||
|
@ -194,7 +193,7 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn current_player_remains_the_same_after_self_capture() {
|
fn current_player_remains_the_same_after_self_capture() {
|
||||||
let mut state = GameState::new();
|
let mut state = GameState::default();
|
||||||
state.board = Board::from_coordinates(
|
state.board = Board::from_coordinates(
|
||||||
vec![
|
vec![
|
||||||
(Coordinate { column: 17, row: 0 }, Color::White),
|
(Coordinate { column: 17, row: 0 }, Color::White),
|
||||||
|
@ -215,7 +214,7 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ko_rules_are_enforced() {
|
fn ko_rules_are_enforced() {
|
||||||
let mut state = GameState::new();
|
let mut state = GameState::default();
|
||||||
state.board = Board::from_coordinates(
|
state.board = Board::from_coordinates(
|
||||||
vec![
|
vec![
|
||||||
(Coordinate { column: 7, row: 9 }, Color::White),
|
(Coordinate { column: 7, row: 9 }, Color::White),
|
||||||
|
|
|
@ -76,7 +76,7 @@ impl BoardElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn addr(&self, column: u8, row: u8) -> usize {
|
fn addr(&self, column: u8, row: u8) -> usize {
|
||||||
((row as usize) * (self.size.width as usize) + (column as usize)) as usize
|
(row as usize) * (self.size.width as usize) + (column as usize)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,5 +28,5 @@ where
|
||||||
let result = f();
|
let result = f();
|
||||||
let end = std::time::Instant::now();
|
let end = std::time::Instant::now();
|
||||||
println!("[Trace: {}] {:?}", trace_name, end - start);
|
println!("[Trace: {}] {:?}", trace_name, end - start);
|
||||||
return result;
|
result
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,8 +34,8 @@ fn handle_response(api: CoreApi, app_window: &AppWindow, message: CoreResponse)
|
||||||
app_window.set_content(&field);
|
app_window.set_content(&field);
|
||||||
*playing_field = Some(field);
|
*playing_field = Some(field);
|
||||||
})
|
})
|
||||||
} else {
|
} else if let Some(field) = playing_field.as_ref() {
|
||||||
playing_field.as_ref().map(|field| field.update_view(view));
|
field.update_view(view)
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
CoreResponse::UpdatedConfigurationView(view) => perftrace("UpdatedConfiguration", || {
|
CoreResponse::UpdatedConfigurationView(view) => perftrace("UpdatedConfiguration", || {
|
||||||
|
@ -56,13 +56,13 @@ fn main() {
|
||||||
);
|
);
|
||||||
|
|
||||||
let config_path = std::env::var("CONFIG")
|
let config_path = std::env::var("CONFIG")
|
||||||
.and_then(|config| Ok(std::path::PathBuf::from(config)))
|
.map(std::path::PathBuf::from)
|
||||||
.or({
|
.or({
|
||||||
std::env::var("HOME").and_then(|base| {
|
std::env::var("HOME").map(|base| {
|
||||||
let mut config_path = std::path::PathBuf::from(base);
|
let mut config_path = std::path::PathBuf::from(base);
|
||||||
config_path.push(".config");
|
config_path.push(".config");
|
||||||
config_path.push("kifu");
|
config_path.push("kifu");
|
||||||
Ok(config_path)
|
config_path
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.expect("no config path could be found");
|
.expect("no config path could be found");
|
||||||
|
@ -87,7 +87,7 @@ fn main() {
|
||||||
let (gtk_tx, gtk_rx) =
|
let (gtk_tx, gtk_rx) =
|
||||||
gtk::glib::MainContext::channel::<CoreResponse>(gtk::glib::PRIORITY_DEFAULT);
|
gtk::glib::MainContext::channel::<CoreResponse>(gtk::glib::PRIORITY_DEFAULT);
|
||||||
|
|
||||||
let app_window = AppWindow::new(&app);
|
let app_window = AppWindow::new(app);
|
||||||
|
|
||||||
let api = CoreApi {
|
let api = CoreApi {
|
||||||
gtk_tx,
|
gtk_tx,
|
||||||
|
@ -123,5 +123,5 @@ fn main() {
|
||||||
println!("running the gtk loop");
|
println!("running the gtk loop");
|
||||||
app.run();
|
app.run();
|
||||||
|
|
||||||
let _ = runtime.block_on(async { core_handle.await });
|
let _ = runtime.block_on(core_handle);
|
||||||
}
|
}
|
||||||
|
|
|
@ -89,7 +89,7 @@ impl ObjectImpl for BoardPrivate {
|
||||||
|
|
||||||
match background {
|
match background {
|
||||||
Ok(Some(ref background)) => {
|
Ok(Some(ref background)) => {
|
||||||
context.set_source_pixbuf(&background, 0., 0.);
|
context.set_source_pixbuf(background, 0., 0.);
|
||||||
context.paint().expect("paint should succeed");
|
context.paint().expect("paint should succeed");
|
||||||
}
|
}
|
||||||
Ok(None) | Err(_) => context.set_source_rgb(0.7, 0.7, 0.7),
|
Ok(None) | Err(_) => context.set_source_rgb(0.7, 0.7, 0.7),
|
||||||
|
@ -140,11 +140,8 @@ impl ObjectImpl for BoardPrivate {
|
||||||
|
|
||||||
(0..19).for_each(|col| {
|
(0..19).for_each(|col| {
|
||||||
(0..19).for_each(|row| {
|
(0..19).for_each(|row| {
|
||||||
match board.stone(row, col) {
|
if let IntersectionElement::Filled(stone) = board.stone(row, col) {
|
||||||
IntersectionElement::Filled(stone) => {
|
pen.stone(context, row, col, stone.color, stone.liberties);
|
||||||
pen.stone(&context, row, col, stone.color, stone.liberties);
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
@ -152,15 +149,18 @@ impl ObjectImpl for BoardPrivate {
|
||||||
let cursor = cursor_location.borrow();
|
let cursor = cursor_location.borrow();
|
||||||
match *cursor {
|
match *cursor {
|
||||||
None => {}
|
None => {}
|
||||||
Some(ref cursor) => match board.stone(cursor.row, cursor.column) {
|
Some(ref cursor) => {
|
||||||
IntersectionElement::Empty(_) => pen.ghost_stone(
|
if let IntersectionElement::Empty(_) =
|
||||||
|
board.stone(cursor.row, cursor.column)
|
||||||
|
{
|
||||||
|
pen.ghost_stone(
|
||||||
context,
|
context,
|
||||||
cursor.row,
|
cursor.row,
|
||||||
cursor.column,
|
cursor.column,
|
||||||
*current_player.borrow(),
|
*current_player.borrow(),
|
||||||
),
|
)
|
||||||
_ => {}
|
}
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
let render_end = std::time::Instant::now();
|
let render_end = std::time::Instant::now();
|
||||||
println!("board rendering time: {:?}", render_end - render_start);
|
println!("board rendering time: {:?}", render_end - render_start);
|
||||||
|
@ -208,16 +208,17 @@ impl ObjectImpl for BoardPrivate {
|
||||||
let cursor = cursor.borrow();
|
let cursor = cursor.borrow();
|
||||||
match *cursor {
|
match *cursor {
|
||||||
None => {}
|
None => {}
|
||||||
Some(ref cursor) => match board.stone(cursor.row, cursor.column) {
|
Some(ref cursor) => {
|
||||||
IntersectionElement::Empty(request) => {
|
if let IntersectionElement::Empty(request) =
|
||||||
|
board.stone(cursor.row, cursor.column)
|
||||||
|
{
|
||||||
println!("need to send request: {:?}", request);
|
println!("need to send request: {:?}", request);
|
||||||
api.borrow()
|
api.borrow()
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.expect("API must exist")
|
.expect("API must exist")
|
||||||
.dispatch(request);
|
.dispatch(request);
|
||||||
}
|
}
|
||||||
_ => {}
|
}
|
||||||
},
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,7 @@ impl Chat {
|
||||||
element.messages.into_iter().for_each(|msg| {
|
element.messages.into_iter().for_each(|msg| {
|
||||||
s.imp().chat_history.append(
|
s.imp().chat_history.append(
|
||||||
>k::Label::builder()
|
>k::Label::builder()
|
||||||
.label(&msg)
|
.label(msg)
|
||||||
.halign(gtk::Align::Start)
|
.halign(gtk::Align::Start)
|
||||||
.build(),
|
.build(),
|
||||||
)
|
)
|
||||||
|
|
|
@ -26,8 +26,8 @@ glib::wrapper! {
|
||||||
pub struct GamePreview(ObjectSubclass<GamePreviewPrivate>) @extends gtk::Box, gtk::Widget, @implements gtk::Orientable;
|
pub struct GamePreview(ObjectSubclass<GamePreviewPrivate>) @extends gtk::Box, gtk::Widget, @implements gtk::Orientable;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GamePreview {
|
impl Default for GamePreview {
|
||||||
pub fn new() -> GamePreview {
|
fn default() -> Self {
|
||||||
let s: Self = Object::builder().build();
|
let s: Self = Object::builder().build();
|
||||||
s.set_orientation(gtk::Orientation::Horizontal);
|
s.set_orientation(gtk::Orientation::Horizontal);
|
||||||
s.set_homogeneous(true);
|
s.set_homogeneous(true);
|
||||||
|
@ -41,7 +41,9 @@ impl GamePreview {
|
||||||
|
|
||||||
s
|
s
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GamePreview {
|
||||||
pub fn set_game(&self, element: GamePreviewElement) {
|
pub fn set_game(&self, element: GamePreviewElement) {
|
||||||
self.imp().black_player.set_text(&element.black_player);
|
self.imp().black_player.set_text(&element.black_player);
|
||||||
self.imp().white_player.set_text(&element.white_player);
|
self.imp().white_player.set_text(&element.white_player);
|
||||||
|
|
|
@ -137,7 +137,7 @@ impl Home {
|
||||||
.build();
|
.build();
|
||||||
s.append(&new_game_button);
|
s.append(&new_game_button);
|
||||||
|
|
||||||
let library = Library::new();
|
let library = Library::default();
|
||||||
let library_view = gtk::ScrolledWindow::builder()
|
let library_view = gtk::ScrolledWindow::builder()
|
||||||
.hscrollbar_policy(gtk::PolicyType::Never)
|
.hscrollbar_policy(gtk::PolicyType::Never)
|
||||||
.min_content_width(360)
|
.min_content_width(360)
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
use crate::ui::GamePreview;
|
|
||||||
use adw::{prelude::*, subclass::prelude::*};
|
use adw::{prelude::*, subclass::prelude::*};
|
||||||
use glib::Object;
|
use glib::Object;
|
||||||
use gtk::{glib, prelude::*, subclass::prelude::*};
|
use gtk::glib;
|
||||||
use kifu_core::ui::GamePreviewElement;
|
use kifu_core::ui::GamePreviewElement;
|
||||||
use std::{cell::RefCell, rc::Rc};
|
use std::{cell::RefCell, rc::Rc};
|
||||||
|
|
||||||
|
@ -92,7 +91,7 @@ impl Default for LibraryPrivate {
|
||||||
.set_child(Some(
|
.set_child(Some(
|
||||||
>k::Label::builder()
|
>k::Label::builder()
|
||||||
.halign(gtk::Align::Start)
|
.halign(gtk::Align::Start)
|
||||||
.ellipsize(pango::EllipsizeMode::End)
|
.ellipsize(gtk::pango::EllipsizeMode::End)
|
||||||
.build(),
|
.build(),
|
||||||
))
|
))
|
||||||
});
|
});
|
||||||
|
@ -100,10 +99,9 @@ impl Default for LibraryPrivate {
|
||||||
let list_item = list_item.downcast_ref::<gtk::ListItem>().unwrap();
|
let list_item = list_item.downcast_ref::<gtk::ListItem>().unwrap();
|
||||||
let game = list_item.item().and_downcast::<GameObject>().unwrap();
|
let game = list_item.item().and_downcast::<GameObject>().unwrap();
|
||||||
let preview = list_item.child().and_downcast::<gtk::Label>().unwrap();
|
let preview = list_item.child().and_downcast::<gtk::Label>().unwrap();
|
||||||
match game.game() {
|
if let Some(game) = game.game() {
|
||||||
Some(game) => preview.set_text(&bind(game)),
|
preview.set_text(&bind(game))
|
||||||
None => (),
|
}
|
||||||
};
|
|
||||||
});
|
});
|
||||||
factory
|
factory
|
||||||
}
|
}
|
||||||
|
@ -148,18 +146,20 @@ glib::wrapper! {
|
||||||
pub struct Library(ObjectSubclass<LibraryPrivate>) @extends adw::Bin, gtk::Widget;
|
pub struct Library(ObjectSubclass<LibraryPrivate>) @extends adw::Bin, gtk::Widget;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Library {
|
impl Default for Library {
|
||||||
pub fn new() -> Self {
|
fn default() -> Self {
|
||||||
let s: Self = Object::builder().build();
|
let s: Self = Object::builder().build();
|
||||||
|
|
||||||
s.set_child(Some(&s.imp().list_view));
|
s.set_child(Some(&s.imp().list_view));
|
||||||
s
|
s
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Library {
|
||||||
pub fn set_games(&self, games: Vec<GamePreviewElement>) {
|
pub fn set_games(&self, games: Vec<GamePreviewElement>) {
|
||||||
let games = games
|
let games = games
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|g| GameObject::new(g))
|
.map(GameObject::new)
|
||||||
.collect::<Vec<GameObject>>();
|
.collect::<Vec<GameObject>>();
|
||||||
self.imp().model.extend_from_slice(&games);
|
self.imp().model.extend_from_slice(&games);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use adw::prelude::*;
|
use adw::prelude::*;
|
||||||
use gio::resources_lookup_data;
|
use gio::resources_lookup_data;
|
||||||
use glib::IsA;
|
use glib::IsA;
|
||||||
use gtk::{prelude::*, STYLE_PROVIDER_PRIORITY_USER};
|
use gtk::STYLE_PROVIDER_PRIORITY_USER;
|
||||||
|
|
||||||
mod chat;
|
mod chat;
|
||||||
pub use chat::Chat;
|
pub use chat::Chat;
|
||||||
|
|
|
@ -50,11 +50,9 @@ impl PlayingField {
|
||||||
let chat = Chat::new(view.chat);
|
let chat = Chat::new(view.chat);
|
||||||
|
|
||||||
*s.imp().board.borrow_mut() = Some(Board::new(api));
|
*s.imp().board.borrow_mut() = Some(Board::new(api));
|
||||||
s.imp()
|
if let Some(board) = s.imp().board.borrow().as_ref() {
|
||||||
.board
|
s.attach(board, 1, 1, 1, 2)
|
||||||
.borrow()
|
}
|
||||||
.as_ref()
|
|
||||||
.map(|board| s.attach(board, 1, 1, 1, 2));
|
|
||||||
s.attach(&player_card_black, 2, 1, 1, 1);
|
s.attach(&player_card_black, 2, 1, 1, 1);
|
||||||
s.attach(&player_card_white, 3, 1, 1, 1);
|
s.attach(&player_card_white, 3, 1, 1, 1);
|
||||||
s.attach(&chat, 2, 2, 2, 1);
|
s.attach(&chat, 2, 2, 2, 1);
|
||||||
|
@ -63,20 +61,20 @@ impl PlayingField {
|
||||||
*s.imp().player_card_black.borrow_mut() = Some(player_card_black);
|
*s.imp().player_card_black.borrow_mut() = Some(player_card_black);
|
||||||
*s.imp().chat.borrow_mut() = Some(chat);
|
*s.imp().chat.borrow_mut() = Some(chat);
|
||||||
|
|
||||||
s.imp().board.borrow().as_ref().map(|board| {
|
if let Some(board) = s.imp().board.borrow().as_ref() {
|
||||||
board.set_board(view.board);
|
board.set_board(view.board);
|
||||||
board.set_current_player(view.current_player);
|
board.set_current_player(view.current_player);
|
||||||
});
|
};
|
||||||
|
|
||||||
s
|
s
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_view(&self, view: PlayingFieldView) {
|
pub fn update_view(&self, view: PlayingFieldView) {
|
||||||
perftrace("update_view", || {
|
perftrace("update_view", || {
|
||||||
self.imp().board.borrow().as_ref().map(|board| {
|
if let Some(board) = self.imp().board.borrow().as_ref() {
|
||||||
board.set_board(view.board);
|
board.set_board(view.board);
|
||||||
board.set_current_player(view.current_player);
|
board.set_current_player(view.current_player);
|
||||||
});
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,11 +13,13 @@ pub struct MemoryCacheRecord<T> {
|
||||||
|
|
||||||
pub struct MemoryCache<T>(Arc<RwLock<HashMap<String, MemoryCacheRecord<T>>>>);
|
pub struct MemoryCache<T>(Arc<RwLock<HashMap<String, MemoryCacheRecord<T>>>>);
|
||||||
|
|
||||||
impl<T: Clone> MemoryCache<T> {
|
impl<T: Clone> Default for MemoryCache<T> {
|
||||||
pub fn new() -> MemoryCache<T> {
|
fn default() -> Self {
|
||||||
MemoryCache(Arc::new(RwLock::new(HashMap::new())))
|
Self(Arc::new(RwLock::new(HashMap::new())))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Clone> MemoryCache<T> {
|
||||||
pub async fn find(&self, key: &str, f: impl Future<Output = (DateTime<Utc>, T)>) -> T {
|
pub async fn find(&self, key: &str, f: impl Future<Output = (DateTime<Utc>, T)>) -> T {
|
||||||
let val = {
|
let val = {
|
||||||
let cache = self.0.read().unwrap();
|
let cache = self.0.read().unwrap();
|
||||||
|
@ -53,7 +55,7 @@ mod tests {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn it_runs_the_requestor_when_the_value_does_not_exist() {
|
async fn it_runs_the_requestor_when_the_value_does_not_exist() {
|
||||||
let cache = MemoryCache::new();
|
let cache = MemoryCache::default();
|
||||||
let value = cache
|
let value = cache
|
||||||
.find("my_key", async { (Utc::now(), Value(15)) })
|
.find("my_key", async { (Utc::now(), Value(15)) })
|
||||||
.await;
|
.await;
|
||||||
|
@ -63,7 +65,7 @@ mod tests {
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn it_runs_the_requestor_when_the_value_is_old() {
|
async fn it_runs_the_requestor_when_the_value_is_old() {
|
||||||
let run = Arc::new(RwLock::new(false));
|
let run = Arc::new(RwLock::new(false));
|
||||||
let cache = MemoryCache::new();
|
let cache = MemoryCache::default();
|
||||||
let _ = cache
|
let _ = cache
|
||||||
.find("my_key", async {
|
.find("my_key", async {
|
||||||
(Utc::now() - Duration::seconds(10), Value(15))
|
(Utc::now() - Duration::seconds(10), Value(15))
|
||||||
|
@ -82,7 +84,7 @@ mod tests {
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn it_returns_the_cached_value_when_the_value_is_new() {
|
async fn it_returns_the_cached_value_when_the_value_is_new() {
|
||||||
let run = Arc::new(RwLock::new(false));
|
let run = Arc::new(RwLock::new(false));
|
||||||
let cache = MemoryCache::new();
|
let cache = MemoryCache::default();
|
||||||
let _ = cache
|
let _ = cache
|
||||||
.find("my_key", async {
|
.find("my_key", async {
|
||||||
(Utc::now() + Duration::seconds(10), Value(15))
|
(Utc::now() + Duration::seconds(10), Value(15))
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use chrono::{Datelike, NaiveDate};
|
use chrono::{Datelike, NaiveDate};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::num::ParseIntError;
|
use std::{fmt, num::ParseIntError};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use typeshare::typeshare;
|
use typeshare::typeshare;
|
||||||
|
|
||||||
|
@ -11,9 +11,6 @@ pub enum Error {
|
||||||
|
|
||||||
#[error("Invalid date")]
|
#[error("Invalid date")]
|
||||||
InvalidDate,
|
InvalidDate,
|
||||||
|
|
||||||
#[error("unsupported date format")]
|
|
||||||
Unsupported,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, PartialOrd, Deserialize, Serialize)]
|
#[derive(Clone, Debug, PartialEq, PartialOrd, Deserialize, Serialize)]
|
||||||
|
@ -24,12 +21,12 @@ pub enum Date {
|
||||||
Date(chrono::NaiveDate),
|
Date(chrono::NaiveDate),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Date {
|
impl fmt::Display for Date {
|
||||||
pub fn to_string(&self) -> String {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Date::Year(y) => format!("{}", y),
|
Date::Year(y) => write!(f, "{}", y),
|
||||||
Date::YearMonth(y, m) => format!("{}-{}", y, m),
|
Date::YearMonth(y, m) => write!(f, "{}-{}", y, m),
|
||||||
Date::Date(date) => format!("{}-{}-{}", date.year(), date.month(), date.day()),
|
Date::Date(date) => write!(f, "{}-{}-{}", date.year(), date.month(), date.day()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -71,13 +68,13 @@ impl TryFrom<&str> for Date {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
fn parse_numbers(s: &str) -> Result<Vec<i32>, Error> {
|
fn parse_numbers(s: &str) -> Result<Vec<i32>, Error> {
|
||||||
s.split("-")
|
s.split('-')
|
||||||
.map(|s| s.parse::<i32>().map_err(|err| Error::ParseNumberError(err)))
|
.map(|s| s.parse::<i32>().map_err(Error::ParseNumberError))
|
||||||
.collect::<Result<Vec<i32>, Error>>()
|
.collect::<Result<Vec<i32>, Error>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_date_field(s: &str) -> Result<Vec<Date>, Error> {
|
pub fn parse_date_field(s: &str) -> Result<Vec<Date>, Error> {
|
||||||
let date_elements = s.split(",");
|
let date_elements = s.split(',');
|
||||||
let mut dates = Vec::new();
|
let mut dates = Vec::new();
|
||||||
|
|
||||||
let mut most_recent: Option<Date> = None;
|
let mut most_recent: Option<Date> = None;
|
||||||
|
@ -96,9 +93,9 @@ pub fn parse_date_field(s: &str) -> Result<Vec<Date>, Error> {
|
||||||
None => Date::Year(*v1),
|
None => Date::Year(*v1),
|
||||||
},
|
},
|
||||||
[v1, v2] => Date::YearMonth(*v1, *v2 as u32),
|
[v1, v2] => Date::YearMonth(*v1, *v2 as u32),
|
||||||
[v1, v2, v3, ..] => Date::Date(
|
[v1, v2, v3, ..] => {
|
||||||
NaiveDate::from_ymd_opt(v1.clone(), v2.clone() as u32, v3.clone() as u32).unwrap(),
|
Date::Date(NaiveDate::from_ymd_opt(*v1, *v2 as u32, *v3 as u32).unwrap())
|
||||||
),
|
}
|
||||||
};
|
};
|
||||||
dates.push(new_date.clone());
|
dates.push(new_date.clone());
|
||||||
most_recent = Some(new_date);
|
most_recent = Some(new_date);
|
||||||
|
|
|
@ -95,6 +95,7 @@ impl Deref for Game {
|
||||||
impl TryFrom<Tree> for Game {
|
impl TryFrom<Tree> for Game {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
|
|
||||||
|
#[allow(clippy::field_reassign_with_default)]
|
||||||
fn try_from(tree: Tree) -> Result<Self, Self::Error> {
|
fn try_from(tree: Tree) -> Result<Self, Self::Error> {
|
||||||
let board_size = match tree.root.find_prop("SZ") {
|
let board_size = match tree.root.find_prop("SZ") {
|
||||||
Some(prop) => Size::try_from(prop.values[0].as_str())?,
|
Some(prop) => Size::try_from(prop.values[0].as_str())?,
|
||||||
|
@ -131,7 +132,7 @@ impl TryFrom<Tree> for Game {
|
||||||
.root
|
.root
|
||||||
.find_prop("TM")
|
.find_prop("TM")
|
||||||
.and_then(|prop| prop.values[0].parse::<u64>().ok())
|
.and_then(|prop| prop.values[0].parse::<u64>().ok())
|
||||||
.and_then(|seconds| Some(std::time::Duration::from_secs(seconds)));
|
.map(std::time::Duration::from_secs);
|
||||||
|
|
||||||
info.date = tree
|
info.date = tree
|
||||||
.root
|
.root
|
||||||
|
@ -182,7 +183,7 @@ pub enum Rank {
|
||||||
impl TryFrom<&str> for Rank {
|
impl TryFrom<&str> for Rank {
|
||||||
type Error = String;
|
type Error = String;
|
||||||
fn try_from(r: &str) -> Result<Rank, Self::Error> {
|
fn try_from(r: &str) -> Result<Rank, Self::Error> {
|
||||||
let parts = r.split(" ").map(|s| s.to_owned()).collect::<Vec<String>>();
|
let parts = r.split(' ').map(|s| s.to_owned()).collect::<Vec<String>>();
|
||||||
let cnt = parts[0].parse::<u8>().map_err(|err| format!("{:?}", err))?;
|
let cnt = parts[0].parse::<u8>().map_err(|err| format!("{:?}", err))?;
|
||||||
match parts[1].to_ascii_lowercase().as_str() {
|
match parts[1].to_ascii_lowercase().as_str() {
|
||||||
"kyu" => Ok(Rank::Kyu(cnt)),
|
"kyu" => Ok(Rank::Kyu(cnt)),
|
||||||
|
@ -250,7 +251,7 @@ impl TryFrom<&str> for GameResult {
|
||||||
} else if s == "Void" {
|
} else if s == "Void" {
|
||||||
Ok(GameResult::Annulled)
|
Ok(GameResult::Annulled)
|
||||||
} else {
|
} else {
|
||||||
let parts = s.split("+").collect::<Vec<&str>>();
|
let parts = s.split('+').collect::<Vec<&str>>();
|
||||||
let res = match parts[0].to_ascii_lowercase().as_str() {
|
let res = match parts[0].to_ascii_lowercase().as_str() {
|
||||||
"b" => GameResult::Black,
|
"b" => GameResult::Black,
|
||||||
"w" => GameResult::White,
|
"w" => GameResult::White,
|
||||||
|
|
|
@ -51,7 +51,7 @@ impl From<nom::error::Error<&str>> for ParseError {
|
||||||
fn from(err: nom::error::Error<&str>) -> Self {
|
fn from(err: nom::error::Error<&str>) -> Self {
|
||||||
Self::NomError(nom::error::Error {
|
Self::NomError(nom::error::Error {
|
||||||
input: err.input.to_owned(),
|
input: err.input.to_owned(),
|
||||||
code: err.code.clone(),
|
code: err.code,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ use crate::Error;
|
||||||
use nom::{
|
use nom::{
|
||||||
branch::alt,
|
branch::alt,
|
||||||
bytes::complete::{escaped_transform, tag},
|
bytes::complete::{escaped_transform, tag},
|
||||||
character::complete::{alpha1, digit1, multispace0, multispace1, none_of},
|
character::complete::{alpha1, multispace0, multispace1, none_of},
|
||||||
combinator::{opt, value},
|
combinator::{opt, value},
|
||||||
multi::{many0, many1, separated_list1},
|
multi::{many0, many1, separated_list1},
|
||||||
IResult,
|
IResult,
|
||||||
|
@ -101,7 +101,7 @@ impl Node {
|
||||||
.cloned()
|
.cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn next<'a>(&'a self) -> Option<&'a Node> {
|
pub fn next(&self) -> Option<&Node> {
|
||||||
self.next.get(0)
|
self.next.get(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -209,21 +209,6 @@ fn parse_propval_text<'a, E: nom::error::ParseError<&'a str>>(
|
||||||
Ok((input, value.map(|v| v.to_owned())))
|
Ok((input, value.map(|v| v.to_owned())))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_size<'a, E: nom::error::ParseError<&'a str>>(
|
|
||||||
input: &'a str,
|
|
||||||
) -> IResult<&'a str, Size, E> {
|
|
||||||
let (input, dimensions) = separated_list1(tag(":"), digit1)(input)?;
|
|
||||||
let (width, height) = match dimensions.as_slice() {
|
|
||||||
[width] => (width.parse::<i32>().unwrap(), width.parse::<i32>().unwrap()),
|
|
||||||
[width, height] => (
|
|
||||||
width.parse::<i32>().unwrap(),
|
|
||||||
height.parse::<i32>().unwrap(),
|
|
||||||
),
|
|
||||||
_ => (19, 19),
|
|
||||||
};
|
|
||||||
Ok((input, Size { width, height }))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
Loading…
Reference in New Issue