diff --git a/Cargo.lock b/Cargo.lock index 05e0c49..6d9a740 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -84,6 +84,12 @@ version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "cc" version = "1.2.1" @@ -289,6 +295,7 @@ version = "0.1.0" dependencies = [ "chrono", "psychonaut_journal_types", + "rand", "serde", ] @@ -353,6 +360,15 @@ version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + [[package]] name = "prettytable-rs" version = "0.10.0" @@ -406,6 +422,36 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + [[package]] name = "redox_users" version = "0.4.6" @@ -706,3 +752,24 @@ name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/journal/Cargo.toml b/journal/Cargo.toml index 9253ed1..f7969ec 100644 --- a/journal/Cargo.toml +++ b/journal/Cargo.toml @@ -6,4 +6,5 @@ edition = "2021" [dependencies] serde = { version = "1.0.215", features = ["std", "derive", "serde_derive"] } chrono = { version = "0.4.38", features = ["serde"] } -psychonaut_journal_types = { path = "../psychonaut_journal_types" } \ No newline at end of file +psychonaut_journal_types = { path = "../psychonaut_journal_types" } +rand = "0.8.5" diff --git a/journal/src/helpers.rs b/journal/src/helpers.rs index ac0ba5b..f6ab8bf 100644 --- a/journal/src/helpers.rs +++ b/journal/src/helpers.rs @@ -1,4 +1,4 @@ -use crate::types::{CustomUnitDose, StandardDose}; +use crate::types::doses::{CustomUnitDose, StandardDose}; pub fn calulate_custom_unit_dose( dose: &CustomUnitDose, diff --git a/journal/src/journal.rs b/journal/src/journal.rs new file mode 100644 index 0000000..b77ea4c --- /dev/null +++ b/journal/src/journal.rs @@ -0,0 +1,162 @@ +use chrono::{DateTime, TimeZone, Utc}; +use rand::Rng; + +use crate::types::{ + doses::{CustomUnitDose, Dose, StandardDose, UnknownDose}, + Consumer, CustomUnit, Estimation, Experience, ExportFormat, Ingestion, +}; + +pub type JournalType = Box; + +pub trait Journal { + fn get_custom_unit(&self, id: i64) -> Option<&CustomUnit>; + fn get_experience(&self, id: i64) -> Option<&Experience>; + fn get_experience_ingestions(&self, id: i64) -> Option<&Vec>; + + fn first_experience_by_title(&self, title: &str) -> Option<&Experience>; + fn maybe_custom_unit(&self, ingestion: &Ingestion) -> Option<&CustomUnit>; + + fn import_psychonaut(&mut self, data: psychonaut_journal_types::ExportData); +} + +pub type InMemJournal = ExportFormat; + +impl Journal for InMemJournal { + fn get_custom_unit(&self, id: i64) -> Option<&CustomUnit> { + self.custom_units.get(&id) + } + + fn get_experience(&self, id: i64) -> Option<&Experience> { + self.experiences.get(&id) + } + + fn get_experience_ingestions(&self, id: i64) -> Option<&Vec> { + self.ingestions.get(&id) + } + + fn maybe_custom_unit(&self, ingestion: &Ingestion) -> Option<&CustomUnit> { + match &ingestion.dose { + Dose::CustomUnit(dose) => self.get_custom_unit(dose.custom_unit_id), + _ => None, + } + } + + fn first_experience_by_title(&self, title: &str) -> Option<&Experience> { + self.experiences + .values() + .find(|&experience| &experience.title == title) + } + + fn import_psychonaut(&mut self, data: psychonaut_journal_types::ExportData) { + fn from_unix_millis(time: u64) -> DateTime { + Utc.timestamp_millis_opt(time as i64).unwrap() + } + + let mut rng = rand::thread_rng(); + + for custom_unit in data.custom_units.into_iter() { + let estimation = if custom_unit.is_estimate { + if let Some(standard_deviation) = custom_unit.estimate_standard_deviation { + Estimation::StandardDeviation(standard_deviation) + } else { + Estimation::Estimate + } + } else { + Estimation::Precise + }; + + let dose = CustomUnitDose { + dose: custom_unit.dose, + unit: custom_unit.unit, + original_unit: custom_unit.original_unit, + custom_unit_id: custom_unit.id, + estimation, + }; + + self.custom_units.insert( + custom_unit.id, + CustomUnit { + id: custom_unit.id, + name: custom_unit.name, + substance_name: custom_unit.substance_name, + + administration_route: custom_unit.administration_route, + dose, + + creation_time: from_unix_millis(custom_unit.creation_time), + is_archived: custom_unit.is_archived, + }, + ); + } + + for experience in data.experiences.into_iter() { + let experience_id = rng.gen::(); + + let mut ingestions: Vec = Vec::new(); + for ingestion in experience.ingestions.into_iter() { + let estimation = if !ingestion.is_estimate { + Estimation::Precise + } else if let Some(standard_deviation) = ingestion.estimate_standard_deviation { + Estimation::StandardDeviation(standard_deviation) + } else { + Estimation::Estimate + }; + + let dose = if let Some(dose) = ingestion.dose { + if let Some(custom_unit_id) = ingestion.custom_unit_id { + let custom_unit = self + .custom_units + .get(&custom_unit_id) + .expect("custom unit not found"); + + Dose::CustomUnit(CustomUnitDose { + dose, + unit: custom_unit.dose.unit.clone(), + original_unit: custom_unit.dose.original_unit.clone(), + estimation, + custom_unit_id, + }) + } else { + Dose::Standard(StandardDose { + dose, + unit: ingestion.unit, + estimation, + contains_unknown: false, + }) + } + } else { + Dose::Unknown(UnknownDose { + unit: ingestion.unit, + }) + }; + + ingestions.push(Ingestion { + substance_name: ingestion.substance_name, + ingestion_time: from_unix_millis(ingestion.ingestion_time), + creation_time: from_unix_millis(ingestion.creation_time), + dose, + roa: ingestion.roa, + consumer: match ingestion.consumer_name { + Some(name) => Consumer::Named(name), + None => Consumer::Default, + }, + notes: ingestion.notes, + stomach_fullness: ingestion.stomach_fullness, + }) + } + + self.ingestions.insert(experience_id, ingestions); + + self.experiences.insert( + experience_id, + Experience { + id: experience_id, + title: experience.title, + text: experience.text, + creation_time: from_unix_millis(experience.creation_time), + modified_time: from_unix_millis(experience.modified_time), + }, + ); + } + } +} diff --git a/journal/src/lib.rs b/journal/src/lib.rs index 52af0f9..48e324e 100644 --- a/journal/src/lib.rs +++ b/journal/src/lib.rs @@ -1,2 +1,3 @@ pub mod helpers; +pub mod journal; pub mod types; diff --git a/journal/src/types.rs b/journal/src/types.rs deleted file mode 100644 index fdee166..0000000 --- a/journal/src/types.rs +++ /dev/null @@ -1,345 +0,0 @@ -use chrono::{DateTime, TimeZone, Utc}; -use std::collections::HashMap; -use std::fmt::{Debug, Display}; -use std::ops::{Add, Mul}; -use std::str::FromStr; - -pub type AdministrationRoute = psychonaut_journal_types::AdministrationRoute; - -#[derive(PartialEq, Debug, Copy, Clone)] -pub enum Estimation { - Precise, - Estimate, - StandardDeviation(f64), -} - -impl Estimation { - pub fn is_precise(&self) -> bool { - matches!(self, Estimation::Precise) - } - pub fn is_estimate(&self) -> bool { - !matches!(self, Estimation::Precise) - } - pub fn is_standard_deviation(&self) -> bool { - matches!(self, Estimation::StandardDeviation(_)) - } -} - -impl Add for Estimation { - type Output = Self; - - fn add(self, rhs: Self) -> Self::Output { - match (self, rhs) { - (Estimation::StandardDeviation(x), Estimation::StandardDeviation(y)) => { - Estimation::StandardDeviation(x + y) - } - (Estimation::StandardDeviation(x), _) | (_, Estimation::StandardDeviation(x)) => { - Estimation::StandardDeviation(x) - } - (Estimation::Estimate, _) | (_, Estimation::Estimate) => Estimation::Estimate, - (Estimation::Precise, Estimation::Precise) => Estimation::Precise, - } - } -} - -impl Mul for Estimation { - type Output = Self; - - fn mul(self, rhs: Self) -> Self::Output { - match (self, rhs) { - (Estimation::StandardDeviation(x), Estimation::StandardDeviation(y)) => { - Estimation::StandardDeviation(x * y) - } - (Estimation::StandardDeviation(x), _) | (_, Estimation::StandardDeviation(x)) => { - Estimation::StandardDeviation(x) - } - (Estimation::Estimate, _) | (_, Estimation::Estimate) => Estimation::Estimate, - (Estimation::Precise, Estimation::Precise) => Estimation::Precise, - } - } -} - -#[derive(Debug, Clone)] -pub struct UnknownDose { - pub unit: String, -} - -#[derive(Debug, Clone)] -pub struct StandardDose { - pub dose: f64, - pub unit: String, - pub contains_unknown: bool, - pub estimation: Estimation, -} - -impl Add for StandardDose { - type Output = Self; - - fn add(self, rhs: Self) -> Self::Output { - StandardDose { - dose: self.dose + rhs.dose, - unit: self.unit, - contains_unknown: self.contains_unknown || rhs.contains_unknown, - estimation: self.estimation + rhs.estimation, - } - } -} - -impl Mul for StandardDose { - type Output = Self; - - fn mul(self, rhs: Self) -> Self::Output { - StandardDose { - dose: self.dose * rhs.dose, - unit: self.unit, - contains_unknown: self.contains_unknown || rhs.contains_unknown, - estimation: { - let estimation = self.estimation + rhs.estimation; - if let Estimation::StandardDeviation(deviation) = estimation { - Estimation::StandardDeviation(deviation * rhs.dose) - } else { - estimation - } - }, - } - } -} - -impl From for StandardDose { - fn from(value: CustomUnitDose) -> StandardDose { - StandardDose { - dose: value.dose, - unit: value.original_unit, - contains_unknown: false, - estimation: value.estimation, - } - } -} - -#[derive(Debug, Clone)] -pub struct CustomUnitDose { - pub dose: f64, - pub unit: String, - pub original_unit: String, - pub estimation: Estimation, - pub custom_unit_id: i64, -} - -#[derive(Debug, Clone)] -pub enum Dose { - Unknown(UnknownDose), - Standard(StandardDose), - CustomUnit(CustomUnitDose), -} - -#[derive(PartialEq, Debug, Clone)] -pub enum Consumer { - Default, - Named(String), -} - -impl FromStr for Consumer { - type Err = core::convert::Infallible; - - fn from_str(s: &str) -> Result { - if s == "default" { - Ok(Consumer::Default) - } else { - Ok(Consumer::Named(s.to_string())) - } - } -} - -impl Display for Consumer { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Consumer::Default => f.write_str("default"), - Consumer::Named(name) => f.write_str(name), - } - } -} - -#[derive(Debug, Clone)] -pub struct Ingestion { - pub substance_name: String, - pub ingestion_time: DateTime, - pub creation_time: DateTime, - pub dose: Dose, - pub roa: AdministrationRoute, - pub consumer: Consumer, - pub notes: String, - pub stomach_fullness: Option, -} - -#[derive(Debug, Clone)] -pub struct CustomSubstance { - pub name: String, - pub description: String, - pub units: String, -} - -#[derive(Debug, Clone)] -pub struct Experience { - pub title: String, - pub text: String, - pub creation_time: DateTime, - pub modified_time: DateTime, - pub ingestions: Vec, -} - -#[derive(Debug, Clone)] -pub struct CustomUnit { - pub id: i64, - pub name: String, - pub substance_name: String, - - pub administration_route: AdministrationRoute, - pub dose: CustomUnitDose, - - pub creation_time: DateTime, - pub is_archived: bool, -} - -#[derive(Debug, Default, Clone)] -pub struct Journal { - pub experiences: Vec, - //pub substance_colours: HashMap, - //pub custom_substances: HashMap, - pub custom_units: HashMap, -} - -impl Journal { - pub fn get_custom_unit(&self, id: i64) -> Option { - self.custom_units.get(&id).cloned() - } - - pub fn maybe_custom_unit(&self, ingestion: &Ingestion) -> Option { - match &ingestion.dose { - Dose::CustomUnit(dose) => self.get_custom_unit(dose.custom_unit_id), - _ => None, - } - } - - pub fn first_experience_by_title(&self, title: &String) -> Option { - for experience in self.experiences.iter() { - if &experience.title == title { - return Some(experience.clone()); - } - } - - None - } - - pub fn import(data: psychonaut_journal_types::ExportData) -> Self { - fn from_unix_millis(time: u64) -> DateTime { - Utc.timestamp_millis_opt(time as i64).unwrap() - } - - let mut journal = Journal::default(); - - for custom_unit in data.custom_units.into_iter() { - journal.custom_units.insert( - custom_unit.id, - CustomUnit { - id: custom_unit.id, - name: custom_unit.name, - substance_name: custom_unit.substance_name, - - administration_route: custom_unit.administration_route, - dose: CustomUnitDose { - dose: custom_unit.dose, - unit: custom_unit.unit, - original_unit: custom_unit.original_unit, - custom_unit_id: custom_unit.id, - estimation: if custom_unit.is_estimate { - if let Some(standard_deviation) = - custom_unit.estimate_standard_deviation - { - Estimation::StandardDeviation(standard_deviation) - } else { - Estimation::Estimate - } - } else { - Estimation::Precise - }, - }, - - creation_time: from_unix_millis(custom_unit.creation_time), - is_archived: custom_unit.is_archived, - }, - ); - } - - for experience in data.experiences.into_iter() { - let mut ingestions: Vec = Vec::new(); - - for ingestion in experience.ingestions.into_iter() { - fn ingestion_estimation( - ingestion: &psychonaut_journal_types::Ingestion, - ) -> Estimation { - if !ingestion.is_estimate { - Estimation::Precise - } else if let Some(standard_deviation) = ingestion.estimate_standard_deviation { - Estimation::StandardDeviation(standard_deviation) - } else { - Estimation::Estimate - } - } - - let estimation = ingestion_estimation(&ingestion); - - ingestions.push(Ingestion { - substance_name: ingestion.substance_name, - ingestion_time: from_unix_millis(ingestion.ingestion_time), - creation_time: from_unix_millis(ingestion.creation_time), - dose: { - if let Some(dose) = ingestion.dose { - if let Some(custom_unit_id) = ingestion.custom_unit_id { - let custom_unit = journal - .custom_units - .get(&custom_unit_id) - .expect("custom unit not found"); - - Dose::CustomUnit(CustomUnitDose { - dose, - unit: custom_unit.dose.unit.clone(), - original_unit: custom_unit.dose.original_unit.clone(), - estimation, - custom_unit_id, - }) - } else { - Dose::Standard(StandardDose { - dose, - unit: ingestion.unit, - estimation, - contains_unknown: false, - }) - } - } else { - Dose::Unknown(UnknownDose { - unit: ingestion.unit, - }) - } - }, - roa: ingestion.roa, - consumer: match ingestion.consumer_name { - Some(name) => Consumer::Named(name), - None => Consumer::Default, - }, - notes: ingestion.notes, - stomach_fullness: ingestion.stomach_fullness, - }) - } - - journal.experiences.push(Experience { - title: experience.title, - text: experience.text, - creation_time: from_unix_millis(experience.creation_time), - modified_time: from_unix_millis(experience.modified_time), - ingestions, - }); - } - - journal - } -} diff --git a/journal/src/types/consumer.rs b/journal/src/types/consumer.rs new file mode 100644 index 0000000..d71d541 --- /dev/null +++ b/journal/src/types/consumer.rs @@ -0,0 +1,28 @@ +use std::{fmt::Display, str::FromStr}; + +#[derive(PartialEq, Debug, Clone)] +pub enum Consumer { + Default, + Named(String), +} + +impl FromStr for Consumer { + type Err = core::convert::Infallible; + + fn from_str(s: &str) -> Result { + if s == "default" { + Ok(Consumer::Default) + } else { + Ok(Consumer::Named(s.to_string())) + } + } +} + +impl Display for Consumer { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Consumer::Default => f.write_str("default"), + Consumer::Named(name) => f.write_str(name), + } + } +} diff --git a/journal/src/types/custom_substance.rs b/journal/src/types/custom_substance.rs new file mode 100644 index 0000000..4e7f541 --- /dev/null +++ b/journal/src/types/custom_substance.rs @@ -0,0 +1,6 @@ +#[derive(Debug, Clone)] +pub struct CustomSubstance { + pub name: String, + pub description: String, + pub units: String, +} diff --git a/journal/src/types/custom_unit.rs b/journal/src/types/custom_unit.rs new file mode 100644 index 0000000..f399f50 --- /dev/null +++ b/journal/src/types/custom_unit.rs @@ -0,0 +1,15 @@ +use crate::types::{doses::CustomUnitDose, AdministrationRoute}; +use chrono::{DateTime, Utc}; + +#[derive(Debug, Clone)] +pub struct CustomUnit { + pub id: i64, + pub name: String, + pub substance_name: String, + + pub administration_route: AdministrationRoute, + pub dose: CustomUnitDose, + + pub creation_time: DateTime, + pub is_archived: bool, +} diff --git a/journal/src/types/doses.rs b/journal/src/types/doses.rs new file mode 100644 index 0000000..1eac045 --- /dev/null +++ b/journal/src/types/doses.rs @@ -0,0 +1,76 @@ +use std::ops::{Add, Mul}; + +use crate::types::Estimation; + +#[derive(Debug, Clone)] +pub enum Dose { + Unknown(UnknownDose), + Standard(StandardDose), + CustomUnit(CustomUnitDose), +} + +#[derive(Debug, Clone)] +pub struct UnknownDose { + pub unit: String, +} + +#[derive(Debug, Clone)] +pub struct StandardDose { + pub dose: f64, + pub unit: String, + pub contains_unknown: bool, + pub estimation: Estimation, +} + +#[derive(Debug, Clone)] +pub struct CustomUnitDose { + pub dose: f64, + pub unit: String, + pub original_unit: String, + pub estimation: Estimation, + pub custom_unit_id: i64, +} + +impl Add for StandardDose { + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output { + StandardDose { + dose: self.dose + rhs.dose, + unit: self.unit, + contains_unknown: self.contains_unknown || rhs.contains_unknown, + estimation: self.estimation + rhs.estimation, + } + } +} + +impl Mul for StandardDose { + type Output = Self; + + fn mul(self, rhs: Self) -> Self::Output { + StandardDose { + dose: self.dose * rhs.dose, + unit: self.unit, + contains_unknown: self.contains_unknown || rhs.contains_unknown, + estimation: { + let estimation = self.estimation + rhs.estimation; + if let Estimation::StandardDeviation(deviation) = estimation { + Estimation::StandardDeviation(deviation * rhs.dose) + } else { + estimation + } + }, + } + } +} + +impl From for StandardDose { + fn from(value: CustomUnitDose) -> StandardDose { + StandardDose { + dose: value.dose, + unit: value.original_unit, + contains_unknown: false, + estimation: value.estimation, + } + } +} diff --git a/journal/src/types/estimate.rs b/journal/src/types/estimate.rs new file mode 100644 index 0000000..d0d502a --- /dev/null +++ b/journal/src/types/estimate.rs @@ -0,0 +1,54 @@ +use std::ops::{Add, Mul}; + +#[derive(PartialEq, Debug, Copy, Clone)] +pub enum Estimation { + Precise, + Estimate, + StandardDeviation(f64), +} + +impl Estimation { + pub fn is_precise(&self) -> bool { + matches!(self, Estimation::Precise) + } + pub fn is_estimate(&self) -> bool { + !matches!(self, Estimation::Precise) + } + pub fn is_standard_deviation(&self) -> bool { + matches!(self, Estimation::StandardDeviation(_)) + } +} + +impl Add for Estimation { + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output { + match (self, rhs) { + (Estimation::StandardDeviation(x), Estimation::StandardDeviation(y)) => { + Estimation::StandardDeviation(x + y) + } + (Estimation::StandardDeviation(x), _) | (_, Estimation::StandardDeviation(x)) => { + Estimation::StandardDeviation(x) + } + (Estimation::Estimate, _) | (_, Estimation::Estimate) => Estimation::Estimate, + (Estimation::Precise, Estimation::Precise) => Estimation::Precise, + } + } +} + +impl Mul for Estimation { + type Output = Self; + + fn mul(self, rhs: Self) -> Self::Output { + match (self, rhs) { + (Estimation::StandardDeviation(x), Estimation::StandardDeviation(y)) => { + Estimation::StandardDeviation(x * y) + } + (Estimation::StandardDeviation(x), _) | (_, Estimation::StandardDeviation(x)) => { + Estimation::StandardDeviation(x) + } + (Estimation::Estimate, _) | (_, Estimation::Estimate) => Estimation::Estimate, + (Estimation::Precise, Estimation::Precise) => Estimation::Precise, + } + } +} diff --git a/journal/src/types/experience.rs b/journal/src/types/experience.rs new file mode 100644 index 0000000..482fd56 --- /dev/null +++ b/journal/src/types/experience.rs @@ -0,0 +1,10 @@ +use chrono::{DateTime, Utc}; + +#[derive(Debug, Clone)] +pub struct Experience { + pub id: i64, + pub title: String, + pub text: String, + pub creation_time: DateTime, + pub modified_time: DateTime, +} diff --git a/journal/src/types/export.rs b/journal/src/types/export.rs new file mode 100644 index 0000000..c57749c --- /dev/null +++ b/journal/src/types/export.rs @@ -0,0 +1,10 @@ +use std::collections::HashMap; + +use crate::types::{CustomUnit, Experience, Ingestion}; + +#[derive(Debug, Default, Clone)] +pub struct ExportFormat { + pub ingestions: HashMap>, + pub experiences: HashMap, + pub custom_units: HashMap, +} diff --git a/journal/src/types/ingestion.rs b/journal/src/types/ingestion.rs new file mode 100644 index 0000000..446eddb --- /dev/null +++ b/journal/src/types/ingestion.rs @@ -0,0 +1,15 @@ +use chrono::{DateTime, Utc}; + +use crate::types::{doses::Dose, AdministrationRoute, Consumer}; + +#[derive(Debug, Clone)] +pub struct Ingestion { + pub substance_name: String, + pub ingestion_time: DateTime, + pub creation_time: DateTime, + pub dose: Dose, + pub roa: AdministrationRoute, + pub consumer: Consumer, + pub notes: String, + pub stomach_fullness: Option, +} diff --git a/journal/src/types/mod.rs b/journal/src/types/mod.rs new file mode 100644 index 0000000..1d356cf --- /dev/null +++ b/journal/src/types/mod.rs @@ -0,0 +1,24 @@ +mod estimate; +pub use estimate::Estimation; + +pub mod doses; + +mod consumer; +pub use consumer::Consumer; + +mod ingestion; +pub use ingestion::Ingestion; + +mod custom_substance; +pub use custom_substance::CustomSubstance; + +mod experience; +pub use experience::Experience; + +mod custom_unit; +pub use custom_unit::CustomUnit; + +mod export; +pub use export::ExportFormat; + +pub type AdministrationRoute = psychonaut_journal_types::AdministrationRoute; diff --git a/journal_cli/src/args.rs b/journal_cli/src/args.rs index 294ff98..72ef10b 100644 --- a/journal_cli/src/args.rs +++ b/journal_cli/src/args.rs @@ -1,14 +1,30 @@ use clap::{Parser, Subcommand}; #[derive(Debug, Clone, Subcommand)] -#[clap(rename_all = "camelCase")] +#[command(rename_all = "camelCase")] pub enum Commands { PrintExperience(crate::commands::print_experience::PrintExperienceArgs), } -#[derive(Debug, Parser)] -#[clap()] -pub struct Args { - #[clap(subcommand)] - pub command: Commands, +// TODO: add env vars back to this, +// clashes with multiple=false when one set as env and other as arg +#[derive(Debug, Clone, clap::Args)] +#[group(required = false, multiple = false)] +pub struct JournalFileArgs { + // journal file is writable + // #[arg(group = "journal_file_args", global = true, long)] + // pub journal_file: Option, + + // export file is read-only + #[arg(group = "journal_file", global = true, long)] + pub psychonaut_export_file: Option, +} + +#[derive(Debug, Parser)] +pub struct Args { + #[command(subcommand)] + pub command: Commands, + + #[command(flatten)] + pub journal_file_args: JournalFileArgs, } diff --git a/journal_cli/src/commands/print_experience.rs b/journal_cli/src/commands/print_experience.rs index ee4ca4e..9cc0e4b 100644 --- a/journal_cli/src/commands/print_experience.rs +++ b/journal_cli/src/commands/print_experience.rs @@ -9,12 +9,14 @@ use crate::utils::load_journal; #[derive(Debug, Clone, clap::Args)] pub struct PrintExperienceArgs { + #[arg(requires = "journal_file")] pub experience_title: String, - #[clap(long, env = "EXPORT_FILE")] - pub export_file: String, - #[clap(long)] + + pub _x: (), + + #[arg(long)] pub substance_filter: Option>, - #[clap(long, value_delimiter = ',')] + #[arg(long, value_delimiter = ',')] pub consumer_filter: Option>, } @@ -37,10 +39,17 @@ pub fn parse_consumer_filter(consumer_filter: Option>) -> Option Result<(), Box> { - let journal = load_journal(&args.export_file).expect("could not load export data"); + let journal = load_journal( + &global_args + .journal_file_args + .psychonaut_export_file + .clone() + .unwrap(), + ) + .expect("could not load export data"); let experience = journal .first_experience_by_title(&args.experience_title) @@ -48,14 +57,14 @@ pub fn print_experience( //println!("{:#?}", &experience); - println!("{}", format_experience_title(&experience)); + println!("{}", format_experience_title(experience)); let substance_filter = args.substance_filter.clone(); let consumer_filter = parse_consumer_filter(args.consumer_filter.clone()); print_ingestion_log( &journal, - &experience, + experience, substance_filter.as_ref(), consumer_filter.as_ref(), ); diff --git a/journal_cli/src/display.rs b/journal_cli/src/display.rs index 44f5f46..be3a2cf 100644 --- a/journal_cli/src/display.rs +++ b/journal_cli/src/display.rs @@ -1,14 +1,21 @@ -use journal::types::{Consumer, Experience, Journal}; +use journal::{ + journal::JournalType, + types::{Consumer, Experience}, +}; use crate::formatting::{format_ingestion_dose, format_ingestion_roa, format_ingestion_time}; pub fn print_ingestion_log( - journal: &Journal, + journal: &JournalType, experience: &Experience, substance_filter: Option<&Vec>, consumer_filter: Option<&Vec>, ) { - for ingestion in experience.ingestions.iter() { + for ingestion in journal + .get_experience_ingestions(experience.id) + .expect("could not find ingestions for experience") + .iter() + { if let Some(substance_filter) = substance_filter { if !substance_filter.contains(&ingestion.substance_name) { continue; @@ -22,14 +29,13 @@ pub fn print_ingestion_log( } let custom_unit = journal.maybe_custom_unit(ingestion); - - //println!("{:#?} {:#?}", &ingestion, &custom_unit); + // println!("{:#?} {:#?}", &ingestion, &custom_unit); println!( "{}|{}|{}|{}|{}", ingestion.substance_name, - format_ingestion_dose(&ingestion.dose, custom_unit.as_ref().map(|f| &f.dose)), - format_ingestion_roa(ingestion, custom_unit.as_ref()), + format_ingestion_dose(&ingestion.dose, custom_unit.map(|f| &f.dose)), + format_ingestion_roa(ingestion, custom_unit), ingestion.consumer, format_ingestion_time(ingestion) ) diff --git a/journal_cli/src/formatting.rs b/journal_cli/src/formatting.rs index f8532a4..a46522a 100644 --- a/journal_cli/src/formatting.rs +++ b/journal_cli/src/formatting.rs @@ -1,6 +1,9 @@ use journal::{ helpers::calulate_custom_unit_dose, - types::{CustomUnit, CustomUnitDose, Dose, Estimation, Experience, Ingestion, StandardDose}, + types::{ + doses::{CustomUnitDose, Dose, StandardDose}, + CustomUnit, Estimation, Experience, Ingestion, + }, }; pub fn format_experience_title(experience: &Experience) -> String { diff --git a/journal_cli/src/utils.rs b/journal_cli/src/utils.rs index f06aa38..5def5ca 100644 --- a/journal_cli/src/utils.rs +++ b/journal_cli/src/utils.rs @@ -1,13 +1,14 @@ -use journal::types::Journal; +use journal::journal::{InMemJournal, Journal, JournalType}; use psychonaut_journal_types::ExportData; use std::fs::File; -pub fn load_journal(filename: &String) -> Result> { +pub fn load_journal(filename: &String) -> Result> { let file = File::open(filename)?; let export_data: ExportData = serde_json::from_reader(file)?; - let journal = Journal::import(export_data); + let mut journal = InMemJournal::default(); + journal.import_psychonaut(export_data); - Ok(journal) + Ok(Box::new(journal)) }