diff --git a/journal/src/journal.rs b/journal/src/journal.rs index e4c79d8..e670ad3 100644 --- a/journal/src/journal.rs +++ b/journal/src/journal.rs @@ -1,4 +1,6 @@ -use crate::types::{AdministrationRoute, Consumer, CustomUnit, Ingestion, Session, Unit}; +use crate::types::{ + AdministrationRoute, Consumer, CustomUnit, Ingestion, Session, Substance, Unit, +}; pub type JournalType = Box; @@ -13,13 +15,15 @@ pub trait Journal { route_filter: Option<&Vec>, consumer_filter: Option<&Vec>, ) -> Option>; + fn get_substance(&self, name: &String) -> 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 set_custom_unit(&mut self, unit_id: i32, unit: CustomUnit); + fn set_session(&mut self, session_id: i32, session: Session); + fn set_session_ingestions(&mut self, session_id: i32, ingestions: Vec); + fn set_substance(&mut self, name: &String, substance: Substance); fn first_session_by_title(&self, title: &str) -> Option; - fn resolve_unit(&self, unit: &Unit) -> Unit; + fn resolve_unit(&self, ingestion: &Ingestion) -> Option; fn save(&self) -> Result<(), Box>; } diff --git a/journal/src/journal/json_journal.rs b/journal/src/journal/json_journal.rs index 8bec872..4494f69 100644 --- a/journal/src/journal/json_journal.rs +++ b/journal/src/journal/json_journal.rs @@ -1,12 +1,15 @@ use std::{collections::HashMap, fs::File}; -use crate::types::{AdministrationRoute, Consumer, CustomUnit, Ingestion, Session, Unit}; +use crate::types::{ + AdministrationRoute, Consumer, CustomUnit, Ingestion, Session, Substance, Unit, +}; use super::{Journal, JournalType}; #[derive(Debug, Default, Clone)] #[derive(serde::Serialize, serde::Deserialize)] pub struct JSONJournalData { + pub substances: HashMap, pub ingestions: HashMap>, pub sessions: HashMap, pub custom_units: HashMap, @@ -20,11 +23,7 @@ pub struct JSONJournal { impl JSONJournal { pub fn create(filename: &String) -> Result> { - let data: JSONJournalData = JSONJournalData { - ingestions: HashMap::new(), - sessions: HashMap::new(), - custom_units: HashMap::new(), - }; + let data: JSONJournalData = JSONJournalData::default(); let journal = JSONJournal { data, @@ -109,18 +108,22 @@ impl Journal for JSONJournal { } } - 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 get_substance(&self, name: &String) -> Option { + self.data.substances.get(name).cloned() + } + + fn resolve_unit(&self, ingestion: &Ingestion) -> Option { + match ingestion.custom_unit_id { + None => match self.get_substance(&ingestion.substance_name) { + Some(substance) => Some(Unit::Simple(substance.unit)), + None => None, + }, + Some(custom_unit_id) => match self.get_custom_unit(custom_unit_id) { + Some(custom_unit) => Some(Unit::Custom { + id: custom_unit_id, + unit: Some(custom_unit), + }), + None => None, }, } } @@ -145,6 +148,10 @@ impl Journal for JSONJournal { self.data.ingestions.insert(session_id, ingestions); } + fn set_substance(&mut self, name: &String, substance: Substance) { + self.data.substances.insert(name.clone(), substance); + } + fn save(&self) -> Result<(), Box> { self.save() } diff --git a/journal/src/types/consumer.rs b/journal/src/types/consumer.rs index e54016d..17e7ac8 100644 --- a/journal/src/types/consumer.rs +++ b/journal/src/types/consumer.rs @@ -2,6 +2,7 @@ use std::fmt::Display; #[derive(PartialEq, Debug, Clone)] #[derive(serde::Serialize, serde::Deserialize)] +#[serde(untagged)] pub enum Consumer { Default, Named(String), diff --git a/journal/src/types/ingestion.rs b/journal/src/types/ingestion.rs index 168f4ee..710dcc6 100644 --- a/journal/src/types/ingestion.rs +++ b/journal/src/types/ingestion.rs @@ -9,7 +9,7 @@ pub struct Ingestion { pub ingestion_time: DateTime, pub creation_time: DateTime, pub dose: Dose, - pub unit: Unit, + pub custom_unit_id: Option, pub roa: AdministrationRoute, pub consumer: Consumer, pub notes: String, @@ -37,10 +37,8 @@ impl From for Ingestion { Dose::new_unknown() } }, - unit: match ingestion.custom_unit_id { - Some(id) => Unit::Custom { id, unit: None }, - None => Unit::Simple(ingestion.unit), - }, + + custom_unit_id: ingestion.custom_unit_id, roa: ingestion.roa, @@ -84,7 +82,7 @@ mod tests { ingestion_time: from_unix_millis(0), creation_time: from_unix_millis(0), dose: Dose::new_precise(10.0), - unit: Unit::Simple("mg".to_string()), + custom_unit_id: None, roa: AdministrationRoute::Oral, consumer: Consumer::Default, notes: "".to_string(), diff --git a/journal/src/types/substance.rs b/journal/src/types/substance.rs index 42ccf05..31e33bc 100644 --- a/journal/src/types/substance.rs +++ b/journal/src/types/substance.rs @@ -1,6 +1,7 @@ #[derive(Debug, Clone)] +#[derive(serde::Serialize, serde::Deserialize)] pub struct Substance { pub name: String, pub description: String, - pub units: String, + pub unit: String, } diff --git a/journal_cli/src/args.rs b/journal_cli/src/args.rs index b0b3879..d5a18d8 100644 --- a/journal_cli/src/args.rs +++ b/journal_cli/src/args.rs @@ -5,12 +5,6 @@ use clap_complete::Shell; pub struct Args { #[command(subcommand)] pub command: Commands, - - #[command(flatten)] - pub journal_location: MaybeJournalLocation, - - #[arg(long = "generate", value_enum)] - pub autocomplete_shell_kind: Option, } #[derive(Debug, Clone, clap::Subcommand)] @@ -19,7 +13,11 @@ pub struct Args { pub enum Commands { Journal(commands::journal::JournalSubcommand), PrintSession(commands::PrintSessionArgs), - GenerateShell, + GenerateShellAutocompletions(GenerateShellAutocompletionslArgs), +} +#[derive(Debug, Clone, clap::Args)] +pub struct GenerateShellAutocompletionslArgs { + pub kind: Shell, } // We need two copies of this struct, one for the root command @@ -28,22 +26,6 @@ pub enum Commands { #[group(id = "journal_location")] pub struct JournalLocation { // journal file is writable - #[arg(long, group = "journal_location", value_hint = clap::ValueHint::FilePath)] + #[arg(long, group = "journal_location", value_hint = clap::ValueHint::FilePath, env = "JOURNAL_FILE")] 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 index 5e92df9..c901330 100644 --- a/journal_cli/src/commands/journal/import_psychonaut.rs +++ b/journal_cli/src/commands/journal/import_psychonaut.rs @@ -1,8 +1,8 @@ -use std::fs::File; +use std::{collections::HashMap, fs::File, path::Path}; use journal::{ - journal::JSONJournal, - types::{CustomUnit, Ingestion, Session}, + journal::{JSONJournal, Journal}, + types::{CustomUnit, Ingestion, Session, Substance}, }; use rand::Rng; @@ -16,6 +16,23 @@ pub struct JournalImportPsychonautArgs { #[arg(long, value_hint = clap::ValueHint::FilePath)] pub export_file: String, + + #[arg(long)] + pub create: bool, + #[arg(long)] + pub force_create: bool, + + #[arg(long, value_hint = clap::ValueHint::FilePath)] + pub mappings_file: Option, + + #[arg(long)] + pub infer_missing_substances: bool, +} + +#[derive(Debug, Default, Clone)] +#[derive(serde::Serialize, serde::Deserialize)] +struct Mappings { + pub substance_names: HashMap, } pub fn journal_import_psychonaut( @@ -23,34 +40,115 @@ pub fn journal_import_psychonaut( ) -> Result<(), Box> { let journal_file_location = &args.journal_location.journal_file.clone(); - let mut journal = JSONJournal::load(journal_file_location)?; + let mut journal: Box = if args.create { + if Path::new(&journal_file_location).exists() && !args.force_create { + panic!("journal already exists, try --force-create if you want to recreate") + } + JSONJournal::create(journal_file_location)? + } else { + 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 mappings: Mappings = match args.mappings_file.clone() { + Some(filename) => { + let mappings_file = File::open(filename)?; + let mappings_data: Mappings = serde_json::from_reader(mappings_file)?; + mappings_data + } + None => Mappings::default(), + }; + 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::(); + for custom_substance in export_data.custom_substances.into_iter() { + match journal.get_substance(&custom_substance.name) { + Some(substance) => { + if substance.unit != custom_substance.units { + panic!( + "mismatch on units for {}: {} != {} ", + custom_substance.name, substance.unit, custom_substance.units + ) + } + } + None => { + if args.infer_missing_substances { + journal.set_substance( + &custom_substance.name, + Substance { + name: custom_substance.name.clone(), + description: custom_substance.description.clone(), + unit: custom_substance.units, + }, + ); + } + } + } + } - let mut ingestions: Vec = Vec::new(); - for ingestion in session.ingestions.iter() { - ingestions.push(Ingestion::from(ingestion.clone())) + for experience in export_data.experiences.iter() { + for ingestion in experience.ingestions.iter() { + let substance_name = mappings + .substance_names + .get(&ingestion.substance_name) + .unwrap_or(&ingestion.substance_name); + + let ingestion_unit = match ingestion.custom_unit_id { + Some(id) => journal.get_custom_unit(id).unwrap().original_unit, + None => ingestion.unit.clone(), + }; + + match journal.get_substance(substance_name) { + Some(substance) => { + if substance.unit != ingestion_unit { + panic!( + "mismatch on units for {substance_name}: {} != {ingestion_unit} ", + substance.unit + ) + } + } + None => { + journal.set_substance( + substance_name, + Substance { + name: substance_name.clone(), + description: "".to_string(), + unit: ingestion_unit, + }, + ); + } + } + } + } + + for experience in export_data.experiences.into_iter() { + if journal.first_session_by_title(&experience.title).is_some() { + println!("Skipping {} as is already in journal", experience.title); + continue; } - journal.set_session_ingestions(session_id, ingestions); + let session_id = rng.gen::(); journal.set_session( session_id, Session { id: session_id, - ..Session::from(session) + ..Session::from(experience.clone()) }, ); + + let mut ingestions: Vec = Vec::new(); + for ingestion in experience.ingestions.iter() { + ingestions.push(Ingestion::from(ingestion.clone())) + } + + journal.set_session_ingestions(session_id, ingestions); } journal.save()?; diff --git a/journal_cli/src/commands/journal/new.rs b/journal_cli/src/commands/journal/new.rs index 491b254..945f270 100644 --- a/journal_cli/src/commands/journal/new.rs +++ b/journal_cli/src/commands/journal/new.rs @@ -18,7 +18,7 @@ pub fn journal_new(args: &JournalNewArgs) -> Result<(), Box>, #[arg(long, value_delimiter = ',')] @@ -31,11 +36,8 @@ pub fn parse_consumer_filter(consumer_filter: Option>) -> Option Result<(), Box> { - let journal = JSONJournal::load(global_args.journal_location.journal_file.as_ref().unwrap())?; +pub fn print_session(args: &PrintSessionArgs) -> Result<(), Box> { + let journal = JSONJournal::load(&args.journal_location.journal_file)?; let session = journal .first_session_by_title(&args.session_title) .expect("could not find session"); diff --git a/journal_cli/src/display.rs b/journal_cli/src/display.rs index d61ad70..f2d3b04 100644 --- a/journal_cli/src/display.rs +++ b/journal_cli/src/display.rs @@ -28,7 +28,7 @@ pub fn print_ingestion_log( } } - let unit = journal.resolve_unit(&ingestion.unit); + let unit = journal.resolve_unit(&ingestion).unwrap(); // println!("{:#?} {:#?}", &ingestion, &custom_unit); println!( diff --git a/journal_cli/src/main.rs b/journal_cli/src/main.rs index d02fb48..6e5550b 100644 --- a/journal_cli/src/main.rs +++ b/journal_cli/src/main.rs @@ -17,10 +17,10 @@ fn main() -> Result<(), Box> { let command = args.command.to_owned(); - //println!("{:#?}", args); + eprintln!("{:#?}", args); match command { - Commands::PrintSession(subommand_args) => commands::print_session(&args, &subommand_args)?, + Commands::PrintSession(subommand_args) => commands::print_session(&subommand_args)?, Commands::Journal(command) => match command.subcommand { commands::journal::JournalSubcommands::New(subommand_args) => { commands::journal::new::journal_new(&subommand_args)? @@ -29,8 +29,8 @@ fn main() -> Result<(), Box> { commands::journal::import_psychonaut::journal_import_psychonaut(&subommand_args)? } }, - Commands::GenerateShell => { - print_completions(args.autocomplete_shell_kind.unwrap(), &mut Args::command()) + Commands::GenerateShellAutocompletions(subommand_args) => { + print_completions(subommand_args.kind, &mut Args::command()) } } diff --git a/loadShellCompletions.sh b/loadShellCompletions.sh index 2e77cb1..b4a8f08 100644 --- a/loadShellCompletions.sh +++ b/loadShellCompletions.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash FILE="$(mktemp)" -cargo run --bin journal_cli -- --generate bash generateShell >"${FILE}" +cargo run --bin journal_cli -- generateShellAutocompletions zsh >"${FILE}" # shellcheck disable=SC1090 source "${FILE}"