Compare commits

..

No commits in common. "ef057eca665d8c77e26b2ca038c8722b7b196894" and "f9974e79a7528940a95b04d45cb01e9670215db9" have entirely different histories.

65 changed files with 580 additions and 408 deletions

155
Cargo.lock generated
View File

@ -65,6 +65,15 @@ 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"
@ -128,6 +137,17 @@ 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"
@ -350,6 +370,21 @@ 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"
@ -369,7 +404,7 @@ dependencies = [
"anstream", "anstream",
"anstyle", "anstyle",
"clap_lex", "clap_lex",
"strsim", "strsim 0.10.0",
] ]
[[package]] [[package]]
@ -822,7 +857,7 @@ dependencies = [
"build_html", "build_html",
"bytes", "bytes",
"chrono", "chrono",
"clap", "clap 4.4.6",
"cookie", "cookie",
"cool_asserts", "cool_asserts",
"futures-util", "futures-util",
@ -1607,6 +1642,15 @@ 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"
@ -1925,11 +1969,20 @@ 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", "hermit-abi 0.3.3",
"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"
@ -1972,6 +2025,21 @@ 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"
@ -2082,6 +2150,12 @@ 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"
@ -2391,7 +2465,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", "hermit-abi 0.3.3",
"libc", "libc",
] ]
@ -2454,6 +2528,23 @@ 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"
@ -3119,6 +3210,18 @@ 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"
@ -3472,7 +3575,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", "itertools 0.11.0",
"nom", "nom",
"unicode_categories", "unicode_categories",
] ]
@ -3682,6 +3785,12 @@ 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"
@ -3788,6 +3897,15 @@ 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"
@ -4206,6 +4324,12 @@ 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"
@ -4221,6 +4345,12 @@ 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"
@ -4290,6 +4420,12 @@ 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"
@ -4582,6 +4718,15 @@ 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"

View File

@ -17,6 +17,7 @@ members = [
"kifu/core", "kifu/core",
"kifu/gtk", "kifu/gtk",
"memorycache", "memorycache",
"orizentic",
"screenplay", "screenplay",
"sgf", "sgf",
"nom-training", "nom-training",

View File

@ -20,6 +20,7 @@ RUST_ALL_TARGETS=(
"kifu-core" "kifu-core"
"kifu-gtk" "kifu-gtk"
"memorycache" "memorycache"
"orizentic"
"screenplay" "screenplay"
"sgf" "sgf"
) )
@ -38,7 +39,6 @@ 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

View File

@ -14,9 +14,6 @@ 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
;; ;;
@ -24,18 +21,16 @@ 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 | lint | test | run | release | clean" echo "No command specified. Use build | test | run | release | clean"
;; ;;
*) *)
echo "$CMD is unknown. Use build | lint | test | run | release | clean" echo "$CMD is unknown. Use build | test | run | release | clean"
;; ;;
esac esac

View File

@ -3,6 +3,7 @@ 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

View File

@ -26,7 +26,7 @@ pub enum Change<Key: Eq + Hash, Value> {
NewRecord(Value), NewRecord(Value),
} }
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug)]
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,6 +34,14 @@ 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);
@ -82,7 +90,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_values().map(|v| Change::NewRecord(v))) .chain(new.into_iter().map(|(_, v)| Change::NewRecord(v)))
.collect() .collect()
} }
} }
@ -92,7 +100,7 @@ mod tests {
use super::*; use super::*;
use uuid::Uuid; use uuid::Uuid;
#[derive(Clone, PartialEq, Eq, Hash, Default)] #[derive(Clone, PartialEq, Eq, Hash)]
struct Id(Uuid); struct Id(Uuid);
impl Constructable for Id { impl Constructable for Id {
fn new() -> Self { fn new() -> Self {
@ -102,7 +110,7 @@ mod tests {
#[test] #[test]
fn it_generates_a_new_record() { fn it_generates_a_new_record() {
let mut set: Changeset<Id, String> = Changeset::default(); let mut set: Changeset<Id, String> = Changeset::new();
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);
@ -117,7 +125,7 @@ mod tests {
#[test] #[test]
fn it_generates_a_delete_record() { fn it_generates_a_delete_record() {
let mut set: Changeset<Id, String> = Changeset::default(); let mut set: Changeset<Id, String> = Changeset::new();
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());
@ -134,7 +142,7 @@ mod tests {
#[test] #[test]
fn update_unrelated_records() { fn update_unrelated_records() {
let mut set: Changeset<Id, String> = Changeset::default(); let mut set: Changeset<Id, String> = Changeset::new();
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());
@ -147,7 +155,7 @@ mod tests {
#[test] #[test]
fn delete_cancels_new() { fn delete_cancels_new() {
let mut set: Changeset<Id, String> = Changeset::default(); let mut set: Changeset<Id, String> = Changeset::new();
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);
@ -156,7 +164,7 @@ mod tests {
#[test] #[test]
fn delete_cancels_update() { fn delete_cancels_update() {
let mut set: Changeset<Id, String> = Changeset::default(); let mut set: Changeset<Id, String> = Changeset::new();
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());
@ -167,7 +175,7 @@ mod tests {
#[test] #[test]
fn update_atop_new_is_new() { fn update_atop_new_is_new() {
let mut set: Changeset<Id, String> = Changeset::default(); let mut set: Changeset<Id, String> = Changeset::new();
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);
@ -177,7 +185,7 @@ mod tests {
#[test] #[test]
fn updates_get_squashed() { fn updates_get_squashed() {
let mut set: Changeset<Id, String> = Changeset::default(); let mut set: Changeset<Id, String> = Changeset::new();
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());

View File

@ -33,12 +33,12 @@ fn main() {
let filename = args let filename = args
.next() .next()
.map(PathBuf::from) .map(|p| PathBuf::from(p))
.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).expect("to write file"); hex_map::write_file(filename, map);
} }

View File

@ -10,9 +10,10 @@ 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/>.
*/ */
/// This module contains the elements of cube coordinates. /// Ĉi-tiu modulo enhavas la elementojn por kub-koordinato.
/// ///
/// 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
@ -61,7 +62,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
@ -78,7 +79,7 @@ impl AxialAddr {
positions.push(item); positions.push(item);
while !positions.is_empty() { while positions.len() > 0 {
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) {

View File

@ -14,6 +14,7 @@ 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,
@ -80,7 +81,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 }

View File

@ -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::Error; use thiserror;
#[derive(Debug, Error)] #[derive(Debug, thiserror::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),

View File

@ -2,7 +2,6 @@
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

View File

@ -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::Object; use glib::{GString, Object};
use gtk::{prelude::*, subclass::prelude::*, EventControllerKey}; use gtk::{gdk::Key, prelude::*, subclass::prelude::*, EventControllerKey};
use std::{ use std::{
cell::RefCell, cell::RefCell,
rc::Rc, rc::Rc,
@ -14,6 +14,12 @@ 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 {
@ -44,7 +50,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, timeout: timeout.clone(),
}; };
} }
} }
@ -56,7 +62,7 @@ impl State {
{ {
*self = Self::Paused { *self = Self::Paused {
time_remaining: *deadline - Instant::now(), time_remaining: *deadline - Instant::now(),
timeout: *timeout, timeout: timeout.clone(),
} }
} }
} }
@ -102,13 +108,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 += step_size * frames_elapsed as f64; self.intensity = 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 -= step_size * frames_elapsed as f64; self.intensity = 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;
@ -142,6 +148,7 @@ impl SplashPrivate {
*self.height.borrow(), *self.height.borrow(),
2., 2.,
8., 8.,
8.,
(0.7, 0., 1.), (0.7, 0., 1.),
); );
@ -326,7 +333,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(); let state = s.imp().state.borrow().clone();
let time = match state { let time = match state {
State::Running { deadline, .. } => deadline - Instant::now(), State::Running { deadline, .. } => deadline - Instant::now(),
@ -352,7 +359,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); *saved_extents = Some(time_extents.clone());
} }
let time_baseline_x = center_x - time_extents.width() / 2.; let time_baseline_x = center_x - time_extents.width() / 2.;
@ -365,8 +372,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), State::Running { timeout, .. } => (true, timeout.clone()),
State::Paused { timeout, .. } => (false, timeout), State::Paused { timeout, .. } => (false, timeout.clone()),
}; };
match timeout_animation { match timeout_animation {
Some(ref animation) => { Some(ref animation) => {
@ -388,7 +395,8 @@ impl Splash {
let _ = context.show_text(&time); let _ = context.show_text(&time);
}; };
if let Some(extents) = *s.imp().time_extents.borrow() { match *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,
@ -399,7 +407,9 @@ impl Splash {
height: 60., height: 60.,
length: 100., length: 100.,
}; };
time_meter.draw(context); time_meter.draw(&context);
}
None => {}
} }
} }
}); });
@ -534,7 +544,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 / (self.count as f64 * 2.); let width = self.length as f64 / (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.);
@ -569,6 +579,10 @@ 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 {
@ -577,6 +591,7 @@ 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 =
@ -596,6 +611,9 @@ impl GlowPen {
Self { Self {
blur_context, blur_context,
draw_context, draw_context,
line_width,
blur_line_width,
blur_size,
} }
} }
} }
@ -612,10 +630,8 @@ impl Pen for GlowPen {
} }
fn stroke(&self) { fn stroke(&self) {
self.blur_context.stroke().expect("to draw the blur line"); self.blur_context.stroke();
self.draw_context self.draw_context.stroke();
.stroke()
.expect("to draw the regular line");
} }
fn finish(self) -> Pattern { fn finish(self) -> Pattern {
@ -665,7 +681,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>>();
match parts.len() { let duration = 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();
@ -676,7 +692,8 @@ 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),
}; };
@ -708,7 +725,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()); let splash = Splash::new(title.read().unwrap().clone(), state.read().unwrap().clone());
window.set_child(Some(&splash)); window.set_child(Some(&splash));
@ -746,7 +763,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()); let _ = gtk_tx.send(state.read().unwrap().clone());
} }
} }
}); });

View File

@ -1,12 +1,11 @@
#!/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-${VERSION}.tgz dist/ tar -czf dashboard.tgz dist/

View File

@ -40,16 +40,16 @@ impl ApplicationWindow {
.vexpand(true) .vexpand(true)
.build(); .build();
let date_label = Date::default(); let date_label = Date::new();
layout.append(&date_label); layout.append(&date_label);
let events = Events::default(); let events = Events::new();
layout.append(&events); layout.append(&events);
let transit_card = TransitCard::default(); let transit_card = TransitCard::new();
layout.append(&transit_card); layout.append(&transit_card);
let transit_clock = TransitClock::default(); let transit_clock = TransitClock::new();
layout.append(&transit_clock); layout.append(&transit_clock);
window.set_content(Some(&layout)); window.set_content(Some(&layout));

View File

@ -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 Default for Date { impl Date {
fn default() -> Self { pub fn new() -> 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,9 +48,7 @@ impl Default for 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();

View File

@ -1,12 +1,13 @@
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::*}; use gtk::{prelude::*, subclass::prelude::*, IconLookupFlags};
use ifc::IFC; use ifc::IFC;
/*
#[derive(PartialEq)] #[derive(PartialEq)]
pub enum UpcomingEvent { pub enum UpcomingEvent {
SpringEquinox, SpringEquinox,
@ -14,15 +15,25 @@ 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]
@ -40,8 +51,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 Default for Events { impl Events {
fn default() -> Self { pub fn new() -> 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);
@ -53,9 +64,7 @@ impl Default for 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

View File

@ -1,5 +1,6 @@
use crate::soluna_client::SunMoon;
use glib::Object; use glib::Object;
use gtk::{prelude::*, subclass::prelude::*}; use gtk::{prelude::*, subclass::prelude::*, IconLookupFlags};
#[derive(Default)] #[derive(Default)]
pub struct LabelPrivate { pub struct LabelPrivate {

View File

@ -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::*}; use gtk::{prelude::*, subclass::prelude::*, IconLookupFlags};
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 Default for TransitCard { impl TransitCard {
fn default() -> Self { pub fn new() -> 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,9 +48,7 @@ impl Default for 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

View File

@ -7,11 +7,18 @@ 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";
@ -27,8 +34,8 @@ glib::wrapper! {
pub struct TransitClock(ObjectSubclass<TransitClockPrivate>) @extends gtk::DrawingArea, gtk::Widget; pub struct TransitClock(ObjectSubclass<TransitClockPrivate>) @extends gtk::DrawingArea, gtk::Widget;
} }
impl Default for TransitClock { impl TransitClock {
fn default() -> Self { pub fn new() -> 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);
@ -93,9 +100,7 @@ impl Default for 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();

View File

@ -90,7 +90,7 @@ pub fn main() {
tx: Arc::new(RwLock::new(None)), tx: Arc::new(RwLock::new(None)),
}; };
runtime.spawn({ let _ = runtime.spawn({
let core = core.clone(); let core = core.clone();
async move { async move {
let soluna_client = SolunaClient::new(); let soluna_client = SolunaClient::new();

View File

@ -1,10 +1,11 @@
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: &str = " const SOLSTICE_TEXT: &'static 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
@ -90,14 +91,12 @@ impl Event {
} }
fn parse_time<'a>( fn parse_time<'a>(
year: &str, jaro: &str,
iter: impl Iterator<Item = &'a str>, iter: impl Iterator<Item = &'a str>,
) -> chrono::DateTime<chrono::Utc> { ) -> chrono::DateTime<chrono::Utc> {
let parts = iter.collect::<Vec<&str>>(); let partoj = iter.collect::<Vec<&str>>();
let p = format!("{} {} {} {}", year, parts[0], parts[1], parts[2]); let p = format!("{} {} {} {}", jaro, partoj[0], partoj[1], partoj[2]);
NaiveDateTime::parse_from_str(&p, "%Y %b %d %H:%M") chrono::Utc.datetime_from_str(&p, "%Y %b %d %H:%M").unwrap()
.unwrap()
.and_utc()
} }
fn parse_line(year: &str, rest: &[&str]) -> YearlyEvents { fn parse_line(year: &str, rest: &[&str]) -> YearlyEvents {
@ -119,7 +118,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()
@ -135,7 +134,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).copied() self.0.get(&year).map(|c| c.clone())
} }
pub fn next_event(&self, date: chrono::DateTime<chrono::Utc>) -> Option<Event> { pub fn next_event(&self, date: chrono::DateTime<chrono::Utc>) -> Option<Event> {
@ -143,17 +142,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)) Some(Event::SpringEquinox(year_events.spring_equinox.clone()))
} else if date <= year_events.summer_solstice { } else if date <= year_events.summer_solstice {
Some(Event::SummerSolstice(year_events.summer_solstice)) Some(Event::SummerSolstice(year_events.summer_solstice.clone()))
} else if date <= year_events.autumn_equinox { } else if date <= year_events.autumn_equinox {
Some(Event::AutumnEquinox(year_events.autumn_equinox)) Some(Event::AutumnEquinox(year_events.autumn_equinox.clone()))
} else if date <= year_events.winter_solstice { } else if date <= year_events.winter_solstice {
Some(Event::WinterSolstice(year_events.winter_solstice)) Some(Event::WinterSolstice(year_events.winter_solstice.clone()))
} else { } else {
self.0 self.0
.get(&(date.year() + 1)) .get(&(date.year() + 1))
.map(|_| Event::SpringEquinox(year_events.spring_equinox)) .map(|_| Event::SpringEquinox(year_events.spring_equinox.clone()))
} }
} }
None => None, None => None,
@ -166,7 +165,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); m.insert(record.year, record.clone());
} }
None => (), None => (),
} }
@ -178,24 +177,3 @@ 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()
);
}
}

View File

@ -4,6 +4,7 @@
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";
@ -25,8 +26,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(parse_time); let moonrise = val.moonrise.and_then(|v| parse_time(v));
let moonset = val.moonset.and_then(parse_time); let moonset = val.moonset.and_then(|v| parse_time(v));
Self { Self {
sunrise, sunrise,
@ -81,7 +82,7 @@ impl SolunaClient {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
client: reqwest::Client::new(), client: reqwest::Client::new(),
memory_cache: MemoryCache::default(), memory_cache: MemoryCache::new(),
} }
} }
@ -109,7 +110,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(DateTime::<Utc>::from) .map(|dt_local| DateTime::<Utc>::from(dt_local))
.unwrap_or( .unwrap_or(
Local::now() Local::now()
.with_hour(0) .with_hour(0)

View File

@ -10,6 +10,7 @@ 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.

View File

@ -33,13 +33,19 @@ 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 fmt::Display for DateTimeTz { impl DateTimeTz {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { 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))
}
pub fn to_string(&self) -> String {
if self.0.timezone() == UTC { if self.0.timezone() == UTC {
write!(f, "{}", self.0.to_rfc3339_opts(SecondsFormat::Secs, true)) self.0.to_rfc3339_opts(SecondsFormat::Secs, true)
} else { } else {
write!( format!(
f,
"{} {}", "{} {}",
self.0 self.0
.with_timezone(&chrono_tz::Etc::UTC) .with_timezone(&chrono_tz::Etc::UTC)
@ -50,20 +56,11 @@ impl fmt::Display for 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)))
@ -89,9 +86,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( DateTimeTz::from_str(s).or(Err(E::custom(format!(
"string is not a parsable datetime representation".to_owned(), "string is not a parsable datetime representation"
))) ))))
} }
} }
@ -120,43 +117,28 @@ mod test {
#[test] #[test]
fn it_creates_timestamp_with_z() { fn it_creates_timestamp_with_z() {
let t = DateTimeTz(UTC.with_ymd_and_hms(2019, 5, 15, 12, 0, 0).unwrap()); let t = DateTimeTz(UTC.ymd(2019, 5, 15).and_hms(12, 0, 0));
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!( assert_eq!(t, DateTimeTz(UTC.ymd(2019, 5, 15).and_hms(12, 0, 0)));
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!( assert_eq!(t, DateTimeTz(UTC.ymd(2019, 5, 15).and_hms(18, 0, 0)));
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!( assert_eq!(t, DateTimeTz(UTC.ymd(2019, 6, 15).and_hms(19, 0, 0)));
t, assert_eq!(t, DateTimeTz(Arizona.ymd(2019, 6, 15).and_hms(12, 0, 0)));
DateTimeTz(UTC.with_ymd_and_hms(2019, 6, 15, 19, 0, 0).unwrap()) assert_eq!(t, DateTimeTz(Central.ymd(2019, 6, 15).and_hms(14, 0, 0)));
);
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");
} }
@ -190,9 +172,6 @@ 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!( assert_eq!(t, DateTimeTz(Phoenix.ymd(2019, 6, 15).and_hms(12, 0, 0)));
t,
DateTimeTz(Phoenix.with_ymd_and_hms(2019, 6, 15, 12, 0, 0).unwrap())
);
} }
} }

View File

@ -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::default(); let uuid = UniqueId::new();
self.update(uuid.clone(), entry).map(|_| uuid) self.update(uuid.clone(), entry).and_then(|_| Ok(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(&self) -> impl Iterator<Item = (&UniqueId, &T)> { pub fn records<'s>(&'s self) -> impl Iterator<Item = (&'s UniqueId, &'s T)> + 's {
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).cloned() self.records.get(uuid).map(|v| v.clone())
} }
/* /*

View File

@ -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(Timestamp::DateTime) .map(|dtz| Timestamp::DateTime(dtz))
.or(NaiveDate::from_str(line).map(Timestamp::Date)) .or(NaiveDate::from_str(line).map(|d| Timestamp::Date(d)))
} }
} }
@ -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().cmp(dt2), (Timestamp::DateTime(dt1), Timestamp::Date(dt2)) => dt1.0.date().naive_utc().cmp(&dt2),
(Timestamp::Date(dt1), Timestamp::DateTime(dt2)) => dt1.cmp(&dt2.0.date_naive()), (Timestamp::Date(dt1), Timestamp::DateTime(dt2)) => dt1.cmp(&dt2.0.date().naive_utc()),
(Timestamp::Date(dt1), Timestamp::Date(dt2)) => dt1.cmp(dt2), (Timestamp::Date(dt1), Timestamp::Date(dt2)) => dt1.cmp(dt2),
} }
} }
@ -105,9 +105,11 @@ 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 Default for UniqueId { impl UniqueId {
fn default() -> Self { /// Create a new V4 UUID (this is the most common type in use these days).
Self(Uuid::new_v4()) pub fn new() -> UniqueId {
let id = Uuid::new_v4();
UniqueId(id)
} }
} }
@ -118,14 +120,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(EmseriesReadError::UUIDParseError) .map_err(|err| EmseriesReadError::UUIDParseError(err))
} }
} }
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()) write!(f, "{}", self.0.to_hyphenated().to_string())
} }
} }
@ -144,7 +146,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(EmseriesReadError::JSONParseError) serde_json::from_str(&line).map_err(|err| EmseriesReadError::JSONParseError(err))
} }
} }
@ -182,9 +184,7 @@ 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( Timestamp::DateTime(DateTimeTz(UTC.ymd(2003, 11, 10).and_hms(6, 0, 0))),
UTC.with_ymd_and_hms(2003, 11, 10, 6, 0, 0).unwrap()
)),
); );
} }
@ -210,9 +210,7 @@ mod test {
assert_eq!( assert_eq!(
rec.data, rec.data,
Some(WeightRecord { Some(WeightRecord {
date: Timestamp::DateTime(DateTimeTz( date: Timestamp::DateTime(DateTimeTz(UTC.ymd(2003, 11, 10).and_hms(6, 0, 0))),
UTC.with_ymd_and_hms(2003, 11, 10, 6, 0, 0).unwrap()
)),
weight: Weight(77.79109 * KG), weight: Weight(77.79109 * KG),
}) })
); );
@ -221,9 +219,7 @@ mod test {
#[test] #[test]
fn serialization_output() { fn serialization_output() {
let rec = WeightRecord { let rec = WeightRecord {
date: Timestamp::DateTime(DateTimeTz( date: Timestamp::DateTime(DateTimeTz(UTC.ymd(2003, 11, 10).and_hms(6, 0, 0))),
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!(
@ -232,12 +228,7 @@ mod test {
); );
let rec2 = WeightRecord { let rec2 = WeightRecord {
date: Timestamp::DateTime( date: Timestamp::DateTime(Central.ymd(2003, 11, 10).and_hms(0, 0, 0).into()),
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!(
@ -248,28 +239,22 @@ mod test {
#[test] #[test]
fn two_datetimes_can_be_compared() { fn two_datetimes_can_be_compared() {
let time1 = Timestamp::DateTime(DateTimeTz( let time1 = Timestamp::DateTime(DateTimeTz(UTC.ymd(2003, 11, 10).and_hms(6, 0, 0)));
UTC.with_ymd_and_hms(2003, 11, 10, 6, 0, 0).unwrap(), let time2 = Timestamp::DateTime(DateTimeTz(UTC.ymd(2003, 11, 11).and_hms(6, 0, 0)));
));
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_opt(2003, 11, 10).unwrap()); let time1 = Timestamp::Date(NaiveDate::from_ymd(2003, 11, 10));
let time2 = Timestamp::Date(NaiveDate::from_ymd_opt(2003, 11, 11).unwrap()); let time2 = Timestamp::Date(NaiveDate::from_ymd(2003, 11, 11));
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( let time1 = Timestamp::DateTime(DateTimeTz(UTC.ymd(2003, 11, 10).and_hms(6, 0, 0)));
UTC.with_ymd_and_hms(2003, 11, 10, 6, 0, 0).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)
} }
} }

View File

@ -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, M, S}; use dimensioned::si::{Kilogram, Meter, Second, KG, 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.with_ymd_and_hms(2011, 10, 29, 0, 0, 0).unwrap()), datetime: DateTimeTz(UTC.ymd(2011, 10, 29).and_hms(0, 0, 0)),
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.with_ymd_and_hms(2011, 10, 31, 0, 0, 0).unwrap()), datetime: DateTimeTz(UTC.ymd(2011, 10, 31).and_hms(0, 0, 0)),
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.with_ymd_and_hms(2011, 11, 02, 0, 0, 0).unwrap()), datetime: DateTimeTz(UTC.ymd(2011, 11, 02).and_hms(0, 0, 0)),
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.with_ymd_and_hms(2011, 11, 04, 0, 0, 0).unwrap()), datetime: DateTimeTz(UTC.ymd(2011, 11, 04).and_hms(0, 0, 0)),
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.with_ymd_and_hms(2011, 11, 05, 0, 0, 0).unwrap()), datetime: DateTimeTz(UTC.ymd(2011, 11, 05).and_hms(0, 0, 0)),
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.with_ymd_and_hms(2011, 10, 29, 0, 0, 0).unwrap()).into() DateTimeTz(UTC.ymd(2011, 10, 29).and_hms(0, 0, 0)).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.with_ymd_and_hms(2011, 10, 31, 0, 0, 0).unwrap()).into(), DateTimeTz(UTC.ymd(2011, 10, 31).and_hms(0, 0, 0)).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.with_ymd_and_hms(2011, 10, 31, 0, 0, 0).unwrap()).into(), DateTimeTz(UTC.ymd(2011, 10, 31).and_hms(0, 0, 0)).into(),
true, true,
DateTimeTz(UTC.with_ymd_and_hms(2011, 11, 04, 0, 0, 0).unwrap()).into(), DateTimeTz(UTC.ymd(2011, 11, 04).and_hms(0, 0, 0)).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.with_ymd_and_hms(2011, 10, 31, 0, 0, 0).unwrap()).into(), DateTimeTz(UTC.ymd(2011, 10, 31).and_hms(0, 0, 0)).into(),
true, true,
DateTimeTz(UTC.with_ymd_and_hms(2011, 11, 04, 0, 0, 0).unwrap()).into(), DateTimeTz(UTC.ymd(2011, 11, 04).and_hms(0, 0, 0)).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.with_ymd_and_hms(2011, 10, 31, 0, 0, 0).unwrap()).into(), DateTimeTz(UTC.ymd(2011, 10, 31).and_hms(0, 0, 0)).into(),
true, true,
DateTimeTz(UTC.with_ymd_and_hms(2011, 11, 04, 0, 0, 0).unwrap()).into(), DateTimeTz(UTC.ymd(2011, 11, 04).and_hms(0, 0, 0)).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.with_ymd_and_hms(2011, 10, 31, 0, 0, 0).unwrap()).into(), DateTimeTz(UTC.ymd(2011, 10, 31).and_hms(0, 0, 0)).into(),
true, true,
DateTimeTz(UTC.with_ymd_and_hms(2011, 11, 05, 0, 0, 0).unwrap()).into(), DateTimeTz(UTC.ymd(2011, 11, 05).and_hms(0, 0, 0)).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.with_ymd_and_hms(2011, 11, 02, 0, 0, 0).unwrap()) DateTimeTz(UTC.ymd(2011, 11, 02).and_hms(0, 0, 0))
); );
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.with_ymd_and_hms(2011, 11, 02, 0, 0, 0).unwrap()).into(), DateTimeTz(UTC.ymd(2011, 11, 02).and_hms(0, 0, 0)).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.with_ymd_and_hms(2011, 11, 02, 0, 0, 0).unwrap()) DateTimeTz(UTC.ymd(2011, 11, 02).and_hms(0, 0, 0))
); );
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,6 +361,7 @@ 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();

View File

@ -26,7 +26,11 @@ 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!("User {} created. Auth token: {}", username, *token); println!(
"User {} created. Auth token: {}",
username,
token.to_string()
);
} }
Err(err) => { Err(err) => {
println!("Could not create user {}", username); println!("Could not create user {}", username);
@ -34,7 +38,7 @@ pub async fn main() {
} }
} }
} }
Commands::DeleteUser { .. } => {} Commands::DeleteUser { username } => {}
Commands::ListUsers => {} Commands::ListUsers => {}
} }
} }

View File

@ -1,5 +1,6 @@
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};
@ -79,22 +80,25 @@ 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.authenticate(AuthToken::from(token.clone())).await { Some(token) => match app
.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 session_token.to_string()
), ),
) )
.status(StatusCode::SEE_OTHER) .status(StatusCode::SEE_OTHER)
.body("".to_owned()), .body("".to_owned()),
Ok(None) => render_auth_page(Some("no user found".to_owned())), Ok(None) => render_auth_page(Some(format!("no user found"))),
Err(_) => render_auth_page(Some("invalid auth token".to_owned())), Err(_) => render_auth_page(Some(format!("invalid auth token"))),
}, },
None => render_auth_page(Some("no token available".to_owned())), None => render_auth_page(Some(format!("no token available"))),
} }
} }

View File

@ -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 => "".to_owned(), None => format!(""),
}; };
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,12 +108,10 @@ 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)]

View File

@ -1,3 +1,4 @@
#[macro_use]
extern crate log; extern crate log;
use cookie::Cookie; use cookie::Cookie;
@ -77,7 +78,10 @@ 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.get("session").cloned().map(SessionToken::from) cookies
.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

View File

@ -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(FileId::from)) .and_then(|s| s.to_str().map(|s| FileId::from(s)))
.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())?;
let _ = md_file.write(&serde_json::to_vec(&info)?)?; 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())?;
let _ = md_file.write(&serde_json::to_vec(&self.info)?)?; 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)
} }

View File

@ -3,6 +3,7 @@ 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,
@ -32,7 +33,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)?;
let _ = file.write(ser.as_bytes())?; file.write(ser.as_bytes())?;
Ok(()) Ok(())
} }
} }

View File

@ -3,10 +3,12 @@ use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256}; use sha2::{Digest, Sha256};
use sqlx::{ use sqlx::{
sqlite::{SqlitePool, SqliteRow}, sqlite::{SqlitePool, SqliteRow},
Row, Executor, Row,
}; };
use std::{collections::HashSet, ops::Deref, path::PathBuf}; use std::collections::HashSet;
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;
@ -286,7 +288,7 @@ impl AuthDB {
return Err(AuthError::DuplicateAuthToken); return Err(AuthError::DuplicateAuthToken);
} }
if results.is_empty() { if results.len() == 0 {
return Ok(None); return Ok(None);
} }
@ -320,7 +322,7 @@ impl AuthDB {
return Err(AuthError::DuplicateSessionToken); return Err(AuthError::DuplicateSessionToken);
} }
if rows.is_empty() { if rows.len() == 0 {
return Ok(None); return Ok(None);
} }
@ -346,7 +348,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(stem)) Some(FileId::from(FileId::from(stem)))
} else { } else {
None None
} }

View File

@ -3,6 +3,7 @@ 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

View File

@ -5,6 +5,7 @@ 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"]

View File

@ -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(Error::from) bundle.add_resource(res).map_err(|err| Error::from(err))
} }
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(Error::from)?; bundle.add_resource(res).map_err(|err| Error::from(err))?;
e.insert(bundle); e.insert(bundle);
Ok(()) Ok(())
} }
@ -248,10 +248,16 @@ 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.languages.iter().find_map(|lang| { let result: Option<String> = self
.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),
@ -270,8 +276,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.is_empty() { if errors.len() > 0 {
println!("Errors in formatting: {:?}", errors) println!("Errors in formatting: {:?}", errors)
} }

View File

@ -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 !(-90.0..=90.0).contains(&val) { if val > 90.0 || val < -90.0 {
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 !(-180.0..=180.0).contains(&val) { if val > 180.0 || val < -180.0 {
panic!("Longitude is outside fo range"); panic!("Longitude is outside fo range");
} }
Self(val) Self(val)

View File

@ -45,7 +45,7 @@ impl ApplicationWindow {
] ]
.into_iter() .into_iter()
.map(|name| { .map(|name| {
let playlist = PlaylistCard::default(); let playlist = PlaylistCard::new();
playlist.set_name(name); playlist.set_name(name);
playlist playlist
}) })

View File

@ -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 Default for PlaylistCard { impl PlaylistCard {
fn default() -> Self { pub fn new() -> 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,9 +43,7 @@ impl Default for 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);
} }

View File

@ -1,5 +1,7 @@
use gtk::prelude::*;
fn main() { fn main() {
let _ = gtk::init(); gtk::init();
for name in gtk::IconTheme::new().icon_names() { for name in gtk::IconTheme::new().icon_names() {
println!("{}", name); println!("{}", name);
} }

View File

@ -10,9 +10,10 @@ 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::Object; use glib::{subclass::InitializingObject, 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};
@ -22,7 +23,7 @@ mod palette_entry;
mod tile; mod tile;
mod utilities; mod utilities;
const APP_ID: &str = "com.luminescent-dreams.hex-grid"; const APP_ID: &'static 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.);
@ -177,14 +178,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_f64).sqrt() / 3. * norm_y) / HEX_RADIUS; let r = (-1. / 3. * norm_x + (3. as 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("-----"); hex_address.set_value(&format!("-----"));
*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()));
@ -208,10 +209,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_f64).sqrt() / 2. * (coordinate.q() as f64) * ((3. as f64).sqrt() / 2. * (coordinate.q() as f64)
+ (3_f64).sqrt() * (coordinate.r() as f64)); + (3. as 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_f64).sqrt() * HEX_RADIUS / 2.; let translate_y = center_y - (3. as 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,
@ -248,11 +249,10 @@ 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_f64).sqrt() * radius / 2.; let ul_y = center_y - (3. as f64).sqrt() * radius / 2.;
let points: Vec<(f64, f64)> = utilities::hexagon(radius * 2., (3_f64).sqrt() * radius); let points: Vec<(f64, f64)> = utilities::hexagon(radius * 2., (3. as 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,7 +262,6 @@ 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;

View File

@ -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_f64).sqrt() * radius / 2., center_y + (3. as f64).sqrt() * radius / 2.,
), ),
( (
center_x - radius / 2., center_x - radius / 2.,
center_y + (3_f64).sqrt() * radius / 2., center_y + (3. as 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_f64).sqrt() * radius / 2., center_y - (3. as f64).sqrt() * radius / 2.,
), ),
( (
center_x + radius / 2., center_x + radius / 2.,
center_y - (3_f64).sqrt() * radius / 2., center_y - (3. as f64).sqrt() * radius / 2.,
), ),
] ]
} }

View File

@ -7,6 +7,7 @@ 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" }

View File

@ -141,7 +141,9 @@ 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 !(1..=13).contains(&month) || !(1..=28).contains(&day) { if month < 1 || month > 13 {
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 }))
@ -246,12 +248,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 -= 1; days = 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 -= 1; month = month - 1;
day = 28; day = 28;
} }
Self::Day(Day { Self::Day(Day {

View File

@ -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::default() ..GameState::new()
}); });
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))

View File

@ -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, Default)] #[derive(Clone, Debug)]
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;
} }
} }
true return 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 self.stone(&coordinate).is_some() { if let Some(_) = self.stone(&coordinate) {
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); friendly_group.insert(coordinate.clone());
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 => {} None => return,
Some(adj) => { Some(adj) => {
if group.color == adj.color { if group.color == adj.color {
return; return;
@ -157,14 +157,15 @@ impl Board {
group group
.coordinates .coordinates
.iter() .iter()
.flat_map(|c| self.adjacencies(c)) .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).is_none()) .filter(|c| self.stone(&c) == None)
.count() .count()
} }

View File

@ -1,14 +1,12 @@
use std::{io::Read, path::PathBuf}; use std::{ffi::OsStr, io::Read, os::unix::ffi::OsStrExt, 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),
} }
@ -21,6 +19,7 @@ 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>,
} }
@ -57,7 +56,11 @@ impl Database {
} }
} }
Ok(Database { games }) Ok(Database { path, 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> {
@ -83,13 +86,14 @@ 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));
} }
); );
} }

View File

@ -1,3 +1,4 @@
#[macro_use]
extern crate config_derive; extern crate config_derive;
mod api; mod api;
@ -9,6 +10,11 @@ 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;

View File

@ -77,18 +77,21 @@ 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::default()), game: Some(GameState::new()),
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) {
if let Some(ref mut game) = self.game { match 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 => {}
}
} }
} }
@ -139,9 +142,9 @@ pub struct GameState {
pub black_clock: Duration, pub black_clock: Duration,
} }
impl Default for GameState { impl GameState {
fn default() -> Self { pub fn new() -> GameState {
Self { GameState {
board: Board::new(), board: Board::new(),
past_positions: vec![], past_positions: vec![],
conversation: vec![], conversation: vec![],
@ -158,9 +161,7 @@ impl Default for 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)?;
@ -185,7 +186,7 @@ mod test {
#[test] #[test]
fn current_player_changes_after_move() { fn current_player_changes_after_move() {
let mut state = GameState::default(); let mut state = GameState::new();
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);
@ -193,7 +194,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::default(); let mut state = GameState::new();
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),
@ -214,7 +215,7 @@ mod test {
#[test] #[test]
fn ko_rules_are_enforced() { fn ko_rules_are_enforced() {
let mut state = GameState::default(); let mut state = GameState::new();
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),

View File

@ -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) ((row as usize) * (self.size.width as usize) + (column as usize)) as usize
} }
} }

View File

@ -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);
result return result;
} }

View File

@ -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 if let Some(field) = playing_field.as_ref() { } else {
field.update_view(view) playing_field.as_ref().map(|field| 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")
.map(std::path::PathBuf::from) .and_then(|config| Ok(std::path::PathBuf::from(config)))
.or({ .or({
std::env::var("HOME").map(|base| { std::env::var("HOME").and_then(|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");
config_path Ok(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(core_handle); let _ = runtime.block_on(async { core_handle.await });
} }

View File

@ -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,8 +140,11 @@ impl ObjectImpl for BoardPrivate {
(0..19).for_each(|col| { (0..19).for_each(|col| {
(0..19).for_each(|row| { (0..19).for_each(|row| {
if let IntersectionElement::Filled(stone) = board.stone(row, col) { match board.stone(row, col) {
pen.stone(context, row, col, stone.color, stone.liberties); IntersectionElement::Filled(stone) => {
pen.stone(&context, row, col, stone.color, stone.liberties);
}
_ => {}
}; };
}) })
}); });
@ -149,18 +152,15 @@ impl ObjectImpl for BoardPrivate {
let cursor = cursor_location.borrow(); let cursor = cursor_location.borrow();
match *cursor { match *cursor {
None => {} None => {}
Some(ref cursor) => { Some(ref cursor) => match board.stone(cursor.row, cursor.column) {
if let IntersectionElement::Empty(_) = IntersectionElement::Empty(_) => pen.ghost_stone(
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,17 +208,16 @@ impl ObjectImpl for BoardPrivate {
let cursor = cursor.borrow(); let cursor = cursor.borrow();
match *cursor { match *cursor {
None => {} None => {}
Some(ref cursor) => { Some(ref cursor) => match board.stone(cursor.row, cursor.column) {
if let IntersectionElement::Empty(request) = 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);
} }
} _ => {}
},
} }
}); });
} }

View File

@ -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(
&gtk::Label::builder() &gtk::Label::builder()
.label(msg) .label(&msg)
.halign(gtk::Align::Start) .halign(gtk::Align::Start)
.build(), .build(),
) )

View File

@ -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 Default for GamePreview { impl GamePreview {
fn default() -> Self { pub fn new() -> GamePreview {
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,9 +41,7 @@ impl Default for 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);

View File

@ -137,7 +137,7 @@ impl Home {
.build(); .build();
s.append(&new_game_button); s.append(&new_game_button);
let library = Library::default(); let library = Library::new();
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)

View File

@ -1,6 +1,7 @@
use crate::ui::GamePreview;
use adw::{prelude::*, subclass::prelude::*}; use adw::{prelude::*, subclass::prelude::*};
use glib::Object; use glib::Object;
use gtk::glib; use gtk::{glib, prelude::*, subclass::prelude::*};
use kifu_core::ui::GamePreviewElement; use kifu_core::ui::GamePreviewElement;
use std::{cell::RefCell, rc::Rc}; use std::{cell::RefCell, rc::Rc};
@ -91,7 +92,7 @@ impl Default for LibraryPrivate {
.set_child(Some( .set_child(Some(
&gtk::Label::builder() &gtk::Label::builder()
.halign(gtk::Align::Start) .halign(gtk::Align::Start)
.ellipsize(gtk::pango::EllipsizeMode::End) .ellipsize(pango::EllipsizeMode::End)
.build(), .build(),
)) ))
}); });
@ -99,9 +100,10 @@ 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();
if let Some(game) = game.game() { match game.game() {
preview.set_text(&bind(game)) Some(game) => preview.set_text(&bind(game)),
} None => (),
};
}); });
factory factory
} }
@ -146,20 +148,18 @@ glib::wrapper! {
pub struct Library(ObjectSubclass<LibraryPrivate>) @extends adw::Bin, gtk::Widget; pub struct Library(ObjectSubclass<LibraryPrivate>) @extends adw::Bin, gtk::Widget;
} }
impl Default for Library { impl Library {
fn default() -> Self { pub fn new() -> 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(GameObject::new) .map(|g| GameObject::new(g))
.collect::<Vec<GameObject>>(); .collect::<Vec<GameObject>>();
self.imp().model.extend_from_slice(&games); self.imp().model.extend_from_slice(&games);
} }

View File

@ -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::STYLE_PROVIDER_PRIORITY_USER; use gtk::{prelude::*, STYLE_PROVIDER_PRIORITY_USER};
mod chat; mod chat;
pub use chat::Chat; pub use chat::Chat;

View File

@ -50,9 +50,11 @@ 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));
if let Some(board) = s.imp().board.borrow().as_ref() { s.imp()
s.attach(board, 1, 1, 1, 2) .board
} .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);
@ -61,20 +63,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);
if let Some(board) = s.imp().board.borrow().as_ref() { s.imp().board.borrow().as_ref().map(|board| {
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", || {
if let Some(board) = self.imp().board.borrow().as_ref() { self.imp().board.borrow().as_ref().map(|board| {
board.set_board(view.board); board.set_board(view.board);
board.set_current_player(view.current_player); board.set_current_player(view.current_player);
} });
}) })
} }
} }

View File

@ -13,13 +13,11 @@ 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> Default for MemoryCache<T> { impl<T: Clone> MemoryCache<T> {
fn default() -> Self { pub fn new() -> MemoryCache<T> {
Self(Arc::new(RwLock::new(HashMap::new()))) MemoryCache(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();
@ -55,7 +53,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::default(); let cache = MemoryCache::new();
let value = cache let value = cache
.find("my_key", async { (Utc::now(), Value(15)) }) .find("my_key", async { (Utc::now(), Value(15)) })
.await; .await;
@ -65,7 +63,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::default(); let cache = MemoryCache::new();
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))
@ -84,7 +82,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::default(); let cache = MemoryCache::new();
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))

View File

@ -1,6 +1,6 @@
use chrono::{Datelike, NaiveDate}; use chrono::{Datelike, NaiveDate};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{fmt, num::ParseIntError}; use std::num::ParseIntError;
use thiserror::Error; use thiserror::Error;
use typeshare::typeshare; use typeshare::typeshare;
@ -11,6 +11,9 @@ 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)]
@ -21,12 +24,12 @@ pub enum Date {
Date(chrono::NaiveDate), Date(chrono::NaiveDate),
} }
impl fmt::Display for Date { impl Date {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { pub fn to_string(&self) -> String {
match self { match self {
Date::Year(y) => write!(f, "{}", y), Date::Year(y) => format!("{}", y),
Date::YearMonth(y, m) => write!(f, "{}-{}", y, m), Date::YearMonth(y, m) => format!("{}-{}", y, m),
Date::Date(date) => write!(f, "{}-{}-{}", date.year(), date.month(), date.day()), Date::Date(date) => format!("{}-{}-{}", date.year(), date.month(), date.day()),
} }
} }
} }
@ -68,13 +71,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(Error::ParseNumberError)) .map(|s| s.parse::<i32>().map_err(|err| Error::ParseNumberError(err)))
.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;
@ -93,9 +96,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, ..] => { [v1, v2, v3, ..] => Date::Date(
Date::Date(NaiveDate::from_ymd_opt(*v1, *v2 as u32, *v3 as u32).unwrap()) NaiveDate::from_ymd_opt(v1.clone(), v2.clone() as u32, v3.clone() as u32).unwrap(),
} ),
}; };
dates.push(new_date.clone()); dates.push(new_date.clone());
most_recent = Some(new_date); most_recent = Some(new_date);

View File

@ -95,7 +95,6 @@ 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())?,
@ -132,7 +131,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())
.map(std::time::Duration::from_secs); .and_then(|seconds| Some(std::time::Duration::from_secs(seconds)));
info.date = tree info.date = tree
.root .root
@ -183,7 +182,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)),
@ -251,7 +250,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,

View File

@ -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, code: err.code.clone(),
}) })
} }
} }

View File

@ -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, multispace0, multispace1, none_of}, character::complete::{alpha1, digit1, 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(&self) -> Option<&Node> { pub fn next<'a>(&'a self) -> Option<&'a Node> {
self.next.get(0) self.next.get(0)
} }
} }
@ -209,6 +209,21 @@ 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::*;