From 841e561973b700d72c0e7f7d16604268907c79d8 Mon Sep 17 00:00:00 2001 From: chaos Date: Sat, 23 Nov 2024 21:43:28 +0000 Subject: [PATCH] update --- .envrc | 1 + .rustfmt.toml | 3 +- Cargo.lock | 38 +++-- journal/Cargo.toml | 1 + journal/src/journal.rs | 132 ++------------- journal/src/journal/json_journal.rs | 151 ++++++++++++++++++ journal/src/lib.rs | 2 + journal/src/types/consumer.rs | 1 + journal/src/types/custom_unit.rs | 3 +- journal/src/types/dose.rs | 1 + journal/src/types/export.rs | 10 -- journal/src/types/ingestion.rs | 1 + journal/src/types/mod.rs | 11 +- .../src/types/{experience.rs => session.rs} | 19 +-- .../{custom_substance.rs => substance.rs} | 2 +- journal/src/types/unit.rs | 3 +- journal_cli/Cargo.toml | 6 +- journal_cli/src/args.rs | 67 +++++--- .../src/commands/journal/import_psychonaut.rs | 59 +++++++ journal_cli/src/commands/journal/mod.rs | 16 ++ journal_cli/src/commands/journal/new.rs | 27 ++++ journal_cli/src/commands/mod.rs | 5 +- .../{print_experience.rs => print_session.rs} | 37 ++--- journal_cli/src/display.rs | 8 +- journal_cli/src/formatting.rs | 12 +- journal_cli/src/main.rs | 29 ++-- journal_cli/src/utils.rs | 14 -- loadShellCompletions.sh | 5 + psychonaut_journal_types/src/lib.rs | 6 +- 29 files changed, 418 insertions(+), 252 deletions(-) create mode 100644 .envrc create mode 100644 journal/src/journal/json_journal.rs delete mode 100644 journal/src/types/export.rs rename journal/src/types/{experience.rs => session.rs} (70%) rename journal/src/types/{custom_substance.rs => substance.rs} (75%) create mode 100644 journal_cli/src/commands/journal/import_psychonaut.rs create mode 100644 journal_cli/src/commands/journal/mod.rs create mode 100644 journal_cli/src/commands/journal/new.rs rename journal_cli/src/commands/{print_experience.rs => print_session.rs} (60%) delete mode 100644 journal_cli/src/utils.rs create mode 100644 loadShellCompletions.sh diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..98ee51d --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +dotenv_if_exists ".dev.env" diff --git a/.rustfmt.toml b/.rustfmt.toml index 9b52fc0..2502d27 100644 --- a/.rustfmt.toml +++ b/.rustfmt.toml @@ -1,4 +1,5 @@ hard_tabs = true use_field_init_shorthand = true unstable_features = true -imports_granularity = "Crate" \ No newline at end of file +imports_granularity = "Crate" +merge_derives = false \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index f1a908a..b507c2a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -142,6 +142,15 @@ dependencies = [ "strsim", ] +[[package]] +name = "clap_complete" +version = "4.5.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9647a559c112175f17cf724dc72d3645680a883c58481332779192b0d8e7a01" +dependencies = [ + "clap", +] + [[package]] name = "clap_derive" version = "4.5.18" @@ -297,6 +306,22 @@ dependencies = [ "psychonaut_journal_types", "rand", "serde", + "serde_json", +] + +[[package]] +name = "journal_cli" +version = "0.1.0" +dependencies = [ + "chrono", + "clap", + "clap_complete", + "journal", + "prettytable-rs", + "psychonaut_journal_types", + "rand", + "serde", + "serde_json", ] [[package]] @@ -389,19 +414,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "psychonaut_journal_cli" -version = "0.1.0" -dependencies = [ - "chrono", - "clap", - "journal", - "prettytable-rs", - "psychonaut_journal_types", - "serde", - "serde_json", -] - [[package]] name = "psychonaut_journal_types" version = "0.1.0" diff --git a/journal/Cargo.toml b/journal/Cargo.toml index f7969ec..cde16f8 100644 --- a/journal/Cargo.toml +++ b/journal/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" [dependencies] serde = { version = "1.0.215", features = ["std", "derive", "serde_derive"] } +serde_json = "1.0.132" chrono = { version = "0.4.38", features = ["serde"] } psychonaut_journal_types = { path = "../psychonaut_journal_types" } rand = "0.8.5" diff --git a/journal/src/journal.rs b/journal/src/journal.rs index 31ffd88..e4c79d8 100644 --- a/journal/src/journal.rs +++ b/journal/src/journal.rs @@ -1,132 +1,28 @@ -use rand::Rng; - -use crate::types::{ - AdministrationRoute, Consumer, CustomUnit, Experience, ExportFormat, Ingestion, Unit, -}; +use crate::types::{AdministrationRoute, Consumer, CustomUnit, Ingestion, Session, Unit}; pub type JournalType = Box; pub trait Journal { - fn get_custom_unit(&self, id: i64) -> Option; - fn get_experience(&self, id: i64) -> Option; - fn get_experience_ingestions(&self, id: i64) -> Option>; - fn get_experience_ingestions_by( + fn get_custom_unit(&self, id: i32) -> Option; + fn get_session(&self, id: i32) -> Option; + fn get_session_ingestions(&self, id: i32) -> Option>; + fn get_session_ingestions_by( &self, - id: i64, + id: i32, substance_filter: Option<&Vec>, route_filter: Option<&Vec>, consumer_filter: Option<&Vec>, ) -> Option>; - fn first_experience_by_title(&self, title: &str) -> Option; + fn set_custom_unit(&mut self, id: i32, unit: CustomUnit); + fn set_session(&mut self, id: i32, session: Session); + fn set_session_ingestions(&mut self, id: i32, ingestions: Vec); + + fn first_session_by_title(&self, title: &str) -> Option; fn resolve_unit(&self, unit: &Unit) -> Unit; - fn import_psychonaut(&mut self, data: psychonaut_journal_types::ExportData); + fn save(&self) -> Result<(), Box>; } -pub type InMemJournal = ExportFormat; - -impl Journal for InMemJournal { - fn get_custom_unit(&self, id: i64) -> Option { - self.custom_units.get(&id).cloned() - } - - fn get_experience(&self, id: i64) -> Option { - self.experiences.get(&id).cloned() - } - - fn get_experience_ingestions(&self, id: i64) -> Option> { - self.ingestions.get(&id).cloned() - } - - fn get_experience_ingestions_by( - &self, - id: i64, - substance_filter: Option<&Vec>, - route_filter: Option<&Vec>, - consumer_filter: Option<&Vec>, - ) -> Option> { - if let Some(ingestions) = self.get_experience_ingestions(id) { - let ingestions = ingestions - .clone() - .into_iter() - .filter(|ingestion| { - if let Some(substance_filter) = substance_filter { - if !substance_filter.contains(&ingestion.substance_name) { - return false; - } - } - - if let Some(route_filter) = route_filter { - if !route_filter.contains(&ingestion.roa) { - return false; - } - } - - if let Some(consumer_filter) = consumer_filter { - if !consumer_filter.contains(&ingestion.consumer) { - return false; - } - } - - true - }) - .collect::>(); - - Some(ingestions) - } else { - None - } - } - - fn resolve_unit(&self, unit: &Unit) -> Unit { - match unit { - Unit::Simple(_) => unit.clone(), - Unit::Custom { id, unit } => match unit { - Some(unit) => Unit::Custom { - id: *id, - unit: Some(unit.clone()), - }, - None => Unit::Custom { - id: *id, - unit: self.get_custom_unit(*id), - }, - }, - } - } - - fn first_experience_by_title(&self, title: &str) -> Option { - self.experiences - .values() - .find(|&experience| experience.title == title) - .cloned() - } - - fn import_psychonaut(&mut self, data: psychonaut_journal_types::ExportData) { - let mut rng = rand::thread_rng(); - - for custom_unit in data.custom_units.into_iter() { - self.custom_units - .insert(custom_unit.id, CustomUnit::from(custom_unit)); - } - - for experience in data.experiences.into_iter() { - let experience_id = rng.gen::(); - - let mut ingestions: Vec = Vec::new(); - for ingestion in experience.ingestions.iter() { - ingestions.push(Ingestion::from(ingestion.clone())) - } - - self.ingestions.insert(experience_id, ingestions); - - self.experiences.insert( - experience_id, - Experience { - id: experience_id, - ..Experience::from(experience) - }, - ); - } - } -} +mod json_journal; +pub use json_journal::JSONJournal; diff --git a/journal/src/journal/json_journal.rs b/journal/src/journal/json_journal.rs new file mode 100644 index 0000000..8bec872 --- /dev/null +++ b/journal/src/journal/json_journal.rs @@ -0,0 +1,151 @@ +use std::{collections::HashMap, fs::File}; + +use crate::types::{AdministrationRoute, Consumer, CustomUnit, Ingestion, Session, Unit}; + +use super::{Journal, JournalType}; + +#[derive(Debug, Default, Clone)] +#[derive(serde::Serialize, serde::Deserialize)] +pub struct JSONJournalData { + pub ingestions: HashMap>, + pub sessions: HashMap, + pub custom_units: HashMap, +} + +#[derive(Debug, Default)] +pub struct JSONJournal { + data: JSONJournalData, + filename: String, +} + +impl JSONJournal { + pub fn create(filename: &String) -> Result> { + let data: JSONJournalData = JSONJournalData { + ingestions: HashMap::new(), + sessions: HashMap::new(), + custom_units: HashMap::new(), + }; + + let journal = JSONJournal { + data, + filename: filename.clone(), + }; + + journal.save()?; + + Ok(Box::new(journal)) + } + + pub fn load(filename: &String) -> Result> { + let file = File::open(filename)?; + + let data: JSONJournalData = serde_json::from_reader(file)?; + let journal = JSONJournal { + data, + filename: filename.clone(), + }; + + Ok(Box::new(journal)) + } + + pub fn save(&self) -> Result<(), Box> { + let file = File::create(self.filename.clone())?; + serde_json::to_writer_pretty(&file, &self.data)?; + + Ok(()) + } +} + +impl Journal for JSONJournal { + fn get_custom_unit(&self, id: i32) -> Option { + self.data.custom_units.get(&id).cloned() + } + + fn get_session(&self, id: i32) -> Option { + self.data.sessions.get(&id).cloned() + } + + fn get_session_ingestions(&self, id: i32) -> Option> { + self.data.ingestions.get(&id).cloned() + } + + fn get_session_ingestions_by( + &self, + id: i32, + substance_filter: Option<&Vec>, + route_filter: Option<&Vec>, + consumer_filter: Option<&Vec>, + ) -> Option> { + if let Some(ingestions) = self.get_session_ingestions(id) { + let ingestions = ingestions + .clone() + .into_iter() + .filter(|ingestion| { + if let Some(substance_filter) = substance_filter { + if !substance_filter.contains(&ingestion.substance_name) { + return false; + } + } + + if let Some(route_filter) = route_filter { + if !route_filter.contains(&ingestion.roa) { + return false; + } + } + + if let Some(consumer_filter) = consumer_filter { + if !consumer_filter.contains(&ingestion.consumer) { + return false; + } + } + + true + }) + .collect::>(); + + Some(ingestions) + } else { + None + } + } + + fn resolve_unit(&self, unit: &Unit) -> Unit { + match unit { + Unit::Simple(_) => unit.clone(), + Unit::Custom { id, unit } => match unit { + Some(unit) => Unit::Custom { + id: *id, + unit: Some(unit.clone()), + }, + None => Unit::Custom { + id: *id, + unit: self.get_custom_unit(*id), + }, + }, + } + } + + fn first_session_by_title(&self, title: &str) -> Option { + self.data + .sessions + .values() + .find(|&session| session.title == title) + .cloned() + } + + fn set_custom_unit(&mut self, unit_id: i32, unit: CustomUnit) { + self.data.custom_units.insert(unit_id, unit); + } + + fn set_session(&mut self, session_id: i32, session: Session) { + self.data.sessions.insert(session_id, session); + } + + fn set_session_ingestions(&mut self, session_id: i32, ingestions: Vec) { + self.data.ingestions.insert(session_id, ingestions); + } + + fn save(&self) -> Result<(), Box> { + self.save() + } +} diff --git a/journal/src/lib.rs b/journal/src/lib.rs index f158672..225befb 100644 --- a/journal/src/lib.rs +++ b/journal/src/lib.rs @@ -1,2 +1,4 @@ pub mod journal; pub mod types; + +pub use journal::JSONJournal; diff --git a/journal/src/types/consumer.rs b/journal/src/types/consumer.rs index d719c9d..e54016d 100644 --- a/journal/src/types/consumer.rs +++ b/journal/src/types/consumer.rs @@ -1,6 +1,7 @@ use std::fmt::Display; #[derive(PartialEq, Debug, Clone)] +#[derive(serde::Serialize, serde::Deserialize)] pub enum Consumer { Default, Named(String), diff --git a/journal/src/types/custom_unit.rs b/journal/src/types/custom_unit.rs index 601d09f..8d4fa22 100644 --- a/journal/src/types/custom_unit.rs +++ b/journal/src/types/custom_unit.rs @@ -2,8 +2,9 @@ use super::{from_unix_millis, AdministrationRoute, Dose}; use chrono::{DateTime, Utc}; #[derive(Default, PartialEq, Debug, Clone)] +#[derive(serde::Serialize, serde::Deserialize)] pub struct CustomUnit { - pub id: i64, + pub id: i32, pub name: String, pub substance_name: String, diff --git a/journal/src/types/dose.rs b/journal/src/types/dose.rs index 2155fd1..9a8fe77 100644 --- a/journal/src/types/dose.rs +++ b/journal/src/types/dose.rs @@ -1,4 +1,5 @@ #[derive(Default, PartialEq, Debug, Clone)] +#[derive(serde::Serialize, serde::Deserialize)] pub struct Dose { pub value: Option, pub is_estimate: bool, diff --git a/journal/src/types/export.rs b/journal/src/types/export.rs deleted file mode 100644 index 00b636f..0000000 --- a/journal/src/types/export.rs +++ /dev/null @@ -1,10 +0,0 @@ -use std::collections::HashMap; - -use super::{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 index 5f5047b..168f4ee 100644 --- a/journal/src/types/ingestion.rs +++ b/journal/src/types/ingestion.rs @@ -3,6 +3,7 @@ use chrono::{DateTime, Utc}; use super::{dose::Dose, from_unix_millis, AdministrationRoute, Consumer, Unit}; #[derive(PartialEq, Default, Debug, Clone)] +#[derive(serde::Serialize, serde::Deserialize)] pub struct Ingestion { pub substance_name: String, pub ingestion_time: DateTime, diff --git a/journal/src/types/mod.rs b/journal/src/types/mod.rs index 21734f9..bf0b123 100644 --- a/journal/src/types/mod.rs +++ b/journal/src/types/mod.rs @@ -15,18 +15,15 @@ pub use consumer::Consumer; mod ingestion; pub use ingestion::Ingestion; -mod custom_substance; -pub use custom_substance::CustomSubstance; +mod substance; +pub use substance::Substance; -mod experience; -pub use experience::Experience; +mod session; +pub use session::Session; mod custom_unit; pub use custom_unit::CustomUnit; -mod export; -pub use export::ExportFormat; - pub type AdministrationRoute = psychonaut_journal_types::AdministrationRoute; pub(crate) fn from_unix_millis(time: u64) -> DateTime { diff --git a/journal/src/types/experience.rs b/journal/src/types/session.rs similarity index 70% rename from journal/src/types/experience.rs rename to journal/src/types/session.rs index 2aae759..f399558 100644 --- a/journal/src/types/experience.rs +++ b/journal/src/types/session.rs @@ -3,22 +3,21 @@ use chrono::{DateTime, Utc}; use super::from_unix_millis; #[derive(PartialEq, Default, Debug, Clone)] -pub struct Experience { - pub id: i64, +#[derive(serde::Serialize, serde::Deserialize)] +pub struct Session { + pub id: i32, pub title: String, pub text: String, pub creation_time: DateTime, - pub modified_time: DateTime, } -impl From for Experience { +impl From for Session { fn from(experience: psychonaut_journal_types::Experience) -> Self { - Experience { + Session { id: 0, title: experience.title, text: experience.text, creation_time: from_unix_millis(experience.creation_time), - modified_time: from_unix_millis(experience.modified_time), } } } @@ -27,25 +26,23 @@ impl From for Experience { mod tests { use crate::types::from_unix_millis; - use super::Experience; + use super::Session; use psychonaut_journal_types::Experience as PsychonautExperience; #[test] fn psychonaut_journal_conversion() { assert_eq!( - Experience::from(PsychonautExperience { + Session::from(PsychonautExperience { title: "Experience".to_string(), text: "Some Text".to_string(), creation_time: 0, - modified_time: 0, ingestions: vec![] }), - Experience { + Session { id: 0, title: "Experience".to_string(), text: "Some Text".to_string(), creation_time: from_unix_millis(0), - modified_time: from_unix_millis(0) } ); } diff --git a/journal/src/types/custom_substance.rs b/journal/src/types/substance.rs similarity index 75% rename from journal/src/types/custom_substance.rs rename to journal/src/types/substance.rs index 4e7f541..42ccf05 100644 --- a/journal/src/types/custom_substance.rs +++ b/journal/src/types/substance.rs @@ -1,5 +1,5 @@ #[derive(Debug, Clone)] -pub struct CustomSubstance { +pub struct Substance { pub name: String, pub description: String, pub units: String, diff --git a/journal/src/types/unit.rs b/journal/src/types/unit.rs index 4573969..02b5df3 100644 --- a/journal/src/types/unit.rs +++ b/journal/src/types/unit.rs @@ -1,9 +1,10 @@ use super::CustomUnit; #[derive(PartialEq, Debug, Clone)] +#[derive(serde::Serialize, serde::Deserialize)] pub enum Unit { Simple(String), - Custom { id: i64, unit: Option }, + Custom { id: i32, unit: Option }, } impl Default for Unit { diff --git a/journal_cli/Cargo.toml b/journal_cli/Cargo.toml index d8b4a51..68a5cd0 100644 --- a/journal_cli/Cargo.toml +++ b/journal_cli/Cargo.toml @@ -1,10 +1,10 @@ [package] -name = "psychonaut_journal_cli" +name = "journal_cli" version = "0.1.0" edition = "2021" [[bin]] -name = "journal-cli" +name = "journal_cli" path = "src/main.rs" [dependencies] @@ -17,6 +17,8 @@ clap = { version = "4.5.21", features = ["derive", "env"] } prettytable-rs = "0.10.0" serde = { version = "1.0.215", features = ["std", "derive", "serde_derive"] } serde_json = "1.0.132" +clap_complete = "4.5.38" +rand = "0.8.5" #serde_with = "3.11.0" #string-error = "0.1.0" #termcolor = "1.4.1" diff --git a/journal_cli/src/args.rs b/journal_cli/src/args.rs index 6db9436..b0b3879 100644 --- a/journal_cli/src/args.rs +++ b/journal_cli/src/args.rs @@ -1,32 +1,49 @@ -use clap::{Parser, Subcommand}; +use crate::commands; +use clap_complete::Shell; -#[derive(Debug, Clone, Subcommand)] -#[command(rename_all = "camelCase")] -#[command(flatten_help = false)] -pub enum Commands { - PrintExperience(crate::commands::print_experience::PrintExperienceArgs), -} - -// 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(id = "journal_location", multiple = false)] -pub struct JournalLocationArgs { - // journal file is writable - #[arg(group = "journal_location", global = true, long)] - pub journal_file: Option, - - // export file is read-only - #[arg(group = "journal_location", global = true, long)] - pub psychonaut_export_file: Option, -} - -#[derive(Debug, Parser)] -#[command(flatten_help = false)] +#[derive(Debug, clap::Parser)] pub struct Args { #[command(subcommand)] pub command: Commands, #[command(flatten)] - pub journal_location: JournalLocationArgs, + pub journal_location: MaybeJournalLocation, + + #[arg(long = "generate", value_enum)] + pub autocomplete_shell_kind: Option, +} + +#[derive(Debug, Clone, clap::Subcommand)] +#[command(rename_all = "camelCase")] +#[command(flatten_help = false)] +pub enum Commands { + Journal(commands::journal::JournalSubcommand), + PrintSession(commands::PrintSessionArgs), + GenerateShell, +} + +// We need two copies of this struct, one for the root command +// another for subcommands which require the required parts +#[derive(Debug, Clone, clap::Args)] +#[group(id = "journal_location")] +pub struct JournalLocation { + // journal file is writable + #[arg(long, group = "journal_location", value_hint = clap::ValueHint::FilePath)] + pub journal_file: String, +} + +#[derive(Debug, Clone, clap::Args)] +#[group(id = "journal_location")] +pub struct MaybeJournalLocation { + // journal file is writable + #[arg(long, group = "journal_location", global = true, value_hint = clap::ValueHint::FilePath)] + pub journal_file: Option, +} + +impl From for JournalLocation { + fn from(value: MaybeJournalLocation) -> Self { + JournalLocation { + journal_file: value.journal_file.unwrap(), + } + } } diff --git a/journal_cli/src/commands/journal/import_psychonaut.rs b/journal_cli/src/commands/journal/import_psychonaut.rs new file mode 100644 index 0000000..5e92df9 --- /dev/null +++ b/journal_cli/src/commands/journal/import_psychonaut.rs @@ -0,0 +1,59 @@ +use std::fs::File; + +use journal::{ + journal::JSONJournal, + types::{CustomUnit, Ingestion, Session}, +}; +use rand::Rng; + +use crate::args::JournalLocation; + +#[derive(Debug, Clone, clap::Args)] +#[group(requires = "journal_location")] +pub struct JournalImportPsychonautArgs { + #[command(flatten)] + pub journal_location: JournalLocation, + + #[arg(long, value_hint = clap::ValueHint::FilePath)] + pub export_file: String, +} + +pub fn journal_import_psychonaut( + args: &JournalImportPsychonautArgs, +) -> Result<(), Box> { + let journal_file_location = &args.journal_location.journal_file.clone(); + + let mut journal = JSONJournal::load(journal_file_location)?; + + let export_file = File::open(args.export_file.clone())?; + let export_data: psychonaut_journal_types::ExportData = serde_json::from_reader(export_file)?; + + let mut rng = rand::thread_rng(); + + for custom_unit in export_data.custom_units.into_iter() { + journal.set_custom_unit(custom_unit.id, CustomUnit::from(custom_unit)); + } + + for session in export_data.experiences.into_iter() { + let session_id = rng.gen::(); + + let mut ingestions: Vec = Vec::new(); + for ingestion in session.ingestions.iter() { + ingestions.push(Ingestion::from(ingestion.clone())) + } + + journal.set_session_ingestions(session_id, ingestions); + + journal.set_session( + session_id, + Session { + id: session_id, + ..Session::from(session) + }, + ); + } + + journal.save()?; + + Ok(()) +} diff --git a/journal_cli/src/commands/journal/mod.rs b/journal_cli/src/commands/journal/mod.rs new file mode 100644 index 0000000..31f6fc4 --- /dev/null +++ b/journal_cli/src/commands/journal/mod.rs @@ -0,0 +1,16 @@ +#[derive(Debug, Clone, clap::Args)] +#[command(rename_all = "camelCase")] +pub struct JournalSubcommand { + #[command(subcommand)] + pub subcommand: JournalSubcommands, +} + +#[derive(Debug, Clone, clap::Subcommand)] +#[command(rename_all = "camelCase")] +pub enum JournalSubcommands { + New(new::JournalNewArgs), + ImportPsychonaut(import_psychonaut::JournalImportPsychonautArgs), +} + +pub mod import_psychonaut; +pub mod new; diff --git a/journal_cli/src/commands/journal/new.rs b/journal_cli/src/commands/journal/new.rs new file mode 100644 index 0000000..491b254 --- /dev/null +++ b/journal_cli/src/commands/journal/new.rs @@ -0,0 +1,27 @@ +use std::path::Path; + +use journal::journal::JSONJournal; + +use crate::args::JournalLocation; + +#[derive(Debug, Clone, clap::Args)] +#[group(requires = "journal_location")] +pub struct JournalNewArgs { + #[command(flatten)] + pub journal_location: JournalLocation, + + #[arg(long)] + pub force: bool, +} + +pub fn journal_new(args: &JournalNewArgs) -> Result<(), Box> { + let file_location = &args.journal_location.journal_file.clone(); + + if Path::new(&file_location).exists() && !args.force { + panic!("file exists") + } + + JSONJournal::create(file_location)?; + + Ok(()) +} diff --git a/journal_cli/src/commands/mod.rs b/journal_cli/src/commands/mod.rs index d27ed5e..c37d04c 100644 --- a/journal_cli/src/commands/mod.rs +++ b/journal_cli/src/commands/mod.rs @@ -1 +1,4 @@ -pub mod print_experience; +pub mod journal; + +mod print_session; +pub use print_session::{print_session, PrintSessionArgs}; diff --git a/journal_cli/src/commands/print_experience.rs b/journal_cli/src/commands/print_session.rs similarity index 60% rename from journal_cli/src/commands/print_experience.rs rename to journal_cli/src/commands/print_session.rs index e90c995..c1ab3bd 100644 --- a/journal_cli/src/commands/print_experience.rs +++ b/journal_cli/src/commands/print_session.rs @@ -1,14 +1,11 @@ -use journal::types::Consumer; +use journal::{journal::JSONJournal, types::Consumer}; -use crate::{ - args::Args, display::print_ingestion_log, formatting::format_experience_title, - utils::load_journal, -}; +use crate::{args::Args, display::print_ingestion_log, formatting::format_session_title}; #[derive(Debug, Clone, clap::Args)] #[group(requires = "journal_location")] -pub struct PrintExperienceArgs { - pub experience_title: String, +pub struct PrintSessionArgs { + pub session_title: String, #[arg(long)] pub substance_filter: Option>, @@ -34,33 +31,25 @@ pub fn parse_consumer_filter(consumer_filter: Option>) -> Option Result<(), Box> { - let journal = load_journal( - &global_args - .journal_location - .psychonaut_export_file - .clone() - .unwrap(), - ) - .expect("could not load export data"); + let journal = JSONJournal::load(global_args.journal_location.journal_file.as_ref().unwrap())?; + let session = journal + .first_session_by_title(&args.session_title) + .expect("could not find session"); - let experience = journal - .first_experience_by_title(&args.experience_title) - .expect("could not find experience"); + //println!("{:#?}", &session); - //println!("{:#?}", &experience); - - println!("{}", format_experience_title(&experience)); + println!("{}", format_session_title(&session)); let substance_filter = args.substance_filter.clone(); let consumer_filter = parse_consumer_filter(args.consumer_filter.clone()); print_ingestion_log( &journal, - &experience, + &session, substance_filter.as_ref(), consumer_filter.as_ref(), ); diff --git a/journal_cli/src/display.rs b/journal_cli/src/display.rs index 13bd95e..d61ad70 100644 --- a/journal_cli/src/display.rs +++ b/journal_cli/src/display.rs @@ -1,19 +1,19 @@ use journal::{ journal::JournalType, - types::{format_dose, Consumer, Experience}, + types::{format_dose, Consumer, Session}, }; use crate::formatting::{format_ingestion_roa, format_ingestion_time}; pub fn print_ingestion_log( journal: &JournalType, - experience: &Experience, + session: &Session, substance_filter: Option<&Vec>, consumer_filter: Option<&Vec>, ) { for ingestion in journal - .get_experience_ingestions(experience.id) - .expect("could not find ingestions for experience") + .get_session_ingestions(session.id) + .expect("could not find ingestions for session") .iter() { if let Some(substance_filter) = substance_filter { diff --git a/journal_cli/src/formatting.rs b/journal_cli/src/formatting.rs index c61573b..b813252 100644 --- a/journal_cli/src/formatting.rs +++ b/journal_cli/src/formatting.rs @@ -1,7 +1,7 @@ -use journal::types::{Experience, Ingestion, Unit}; +use journal::types::{Ingestion, Session, Unit}; -pub fn format_experience_title(experience: &Experience) -> String { - format!("{}: {}", experience.title, experience.creation_time) +pub fn format_session_title(session: &Session) -> String { + format!("{}: {}", session.title, session.creation_time) } pub fn format_ingestion_time(ingestion: &Ingestion) -> String { @@ -24,11 +24,11 @@ mod tests { use super::*; #[test] - fn format_experience_title() { - let result = super::format_experience_title(&Experience { + fn format_session_title() { + let result = super::format_session_title(&Session { title: "Title".to_string(), creation_time: Utc.timestamp_millis_opt(0).unwrap(), - ..Experience::default() + ..Session::default() }); assert_eq!(result, "Title: 1970-01-01 00:00:00 UTC"); } diff --git a/journal_cli/src/main.rs b/journal_cli/src/main.rs index c0a7ff9..d02fb48 100644 --- a/journal_cli/src/main.rs +++ b/journal_cli/src/main.rs @@ -1,25 +1,36 @@ -use clap::Parser; +use clap::{CommandFactory, Parser}; +use clap_complete::{generate, Generator}; pub mod args; pub mod commands; pub mod display; pub mod formatting; -pub mod utils; -use commands::print_experience::print_experience; +use args::{Args, Commands}; + +fn print_completions(gen: G, cmd: &mut clap::Command) { + generate(gen, cmd, cmd.get_name().to_string(), &mut std::io::stdout()); +} fn main() -> Result<(), Box> { - let args = args::Args::parse(); + let args = Args::parse(); let command = args.command.to_owned(); - println!("{:#?}", args); + //println!("{:#?}", args); match command { - args::Commands::PrintExperience(print_experience_args) => { - println!("{:#?}", print_experience_args); - - print_experience(&args, &print_experience_args)? + Commands::PrintSession(subommand_args) => commands::print_session(&args, &subommand_args)?, + Commands::Journal(command) => match command.subcommand { + commands::journal::JournalSubcommands::New(subommand_args) => { + commands::journal::new::journal_new(&subommand_args)? + } + commands::journal::JournalSubcommands::ImportPsychonaut(subommand_args) => { + commands::journal::import_psychonaut::journal_import_psychonaut(&subommand_args)? + } + }, + Commands::GenerateShell => { + print_completions(args.autocomplete_shell_kind.unwrap(), &mut Args::command()) } } diff --git a/journal_cli/src/utils.rs b/journal_cli/src/utils.rs deleted file mode 100644 index 5def5ca..0000000 --- a/journal_cli/src/utils.rs +++ /dev/null @@ -1,14 +0,0 @@ -use journal::journal::{InMemJournal, Journal, JournalType}; -use psychonaut_journal_types::ExportData; -use std::fs::File; - -pub fn load_journal(filename: &String) -> Result> { - let file = File::open(filename)?; - - let export_data: ExportData = serde_json::from_reader(file)?; - - let mut journal = InMemJournal::default(); - journal.import_psychonaut(export_data); - - Ok(Box::new(journal)) -} diff --git a/loadShellCompletions.sh b/loadShellCompletions.sh new file mode 100644 index 0000000..2e77cb1 --- /dev/null +++ b/loadShellCompletions.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +FILE="$(mktemp)" +cargo run --bin journal_cli -- --generate bash generateShell >"${FILE}" +# shellcheck disable=SC1090 +source "${FILE}" diff --git a/psychonaut_journal_types/src/lib.rs b/psychonaut_journal_types/src/lib.rs index 26d976c..ec0b897 100644 --- a/psychonaut_journal_types/src/lib.rs +++ b/psychonaut_journal_types/src/lib.rs @@ -56,7 +56,7 @@ pub struct Ingestion { pub is_estimate: bool, #[serde(rename = "estimatedDoseStandardDeviation")] pub estimate_standard_deviation: Option, - pub custom_unit_id: Option, + pub custom_unit_id: Option, #[serde(rename = "administrationRoute")] pub roa: AdministrationRoute, pub consumer_name: Option, @@ -79,8 +79,6 @@ pub struct Experience { pub text: String, #[serde(rename = "creationDate")] pub creation_time: u64, - #[serde(rename = "sortDate")] - pub modified_time: u64, pub ingestions: Vec, } @@ -94,7 +92,7 @@ pub struct SubstanceCompanion { #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "camelCase")] pub struct CustomUnit { - pub id: i64, + pub id: i32, pub substance_name: String, pub name: String, #[serde(rename = "creationDate")]