This commit is contained in:
chaos 2024-11-23 23:43:25 +00:00
parent 841e561973
commit ea544ad4af
12 changed files with 171 additions and 78 deletions

View file

@ -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<dyn Journal>;
@ -13,13 +15,15 @@ pub trait Journal {
route_filter: Option<&Vec<AdministrationRoute>>,
consumer_filter: Option<&Vec<Consumer>>,
) -> Option<Vec<Ingestion>>;
fn get_substance(&self, name: &String) -> Option<Substance>;
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<Ingestion>);
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<Ingestion>);
fn set_substance(&mut self, name: &String, substance: Substance);
fn first_session_by_title(&self, title: &str) -> Option<Session>;
fn resolve_unit(&self, unit: &Unit) -> Unit;
fn resolve_unit(&self, ingestion: &Ingestion) -> Option<Unit>;
fn save(&self) -> Result<(), Box<dyn std::error::Error>>;
}

View file

@ -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<String, Substance>,
pub ingestions: HashMap<i32, Vec<Ingestion>>,
pub sessions: HashMap<i32, Session>,
pub custom_units: HashMap<i32, CustomUnit>,
@ -20,11 +23,7 @@ pub struct JSONJournal {
impl JSONJournal {
pub fn create(filename: &String) -> Result<JournalType, Box<dyn std::error::Error>> {
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<Substance> {
self.data.substances.get(name).cloned()
}
fn resolve_unit(&self, ingestion: &Ingestion) -> Option<Unit> {
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<dyn std::error::Error>> {
self.save()
}

View file

@ -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),

View file

@ -9,7 +9,7 @@ pub struct Ingestion {
pub ingestion_time: DateTime<Utc>,
pub creation_time: DateTime<Utc>,
pub dose: Dose,
pub unit: Unit,
pub custom_unit_id: Option<i32>,
pub roa: AdministrationRoute,
pub consumer: Consumer,
pub notes: String,
@ -37,10 +37,8 @@ impl From<psychonaut_journal_types::Ingestion> 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(),

View file

@ -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,
}

View file

@ -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<Shell>,
}
#[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<String>,
}
impl From<MaybeJournalLocation> for JournalLocation {
fn from(value: MaybeJournalLocation) -> Self {
JournalLocation {
journal_file: value.journal_file.unwrap(),
}
}
}

View file

@ -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<String>,
#[arg(long)]
pub infer_missing_substances: bool,
}
#[derive(Debug, Default, Clone)]
#[derive(serde::Serialize, serde::Deserialize)]
struct Mappings {
pub substance_names: HashMap<String, String>,
}
pub fn journal_import_psychonaut(
@ -23,34 +40,115 @@ pub fn journal_import_psychonaut(
) -> Result<(), Box<dyn std::error::Error>> {
let journal_file_location = &args.journal_location.journal_file.clone();
let mut journal = JSONJournal::load(journal_file_location)?;
let mut journal: Box<dyn Journal> = 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::<i32>();
let mut ingestions: Vec<Ingestion> = Vec::new();
for ingestion in session.ingestions.iter() {
ingestions.push(Ingestion::from(ingestion.clone()))
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,
},
);
}
}
}
}
journal.set_session_ingestions(session_id, ingestions);
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;
}
let session_id = rng.gen::<i32>();
journal.set_session(
session_id,
Session {
id: session_id,
..Session::from(session)
..Session::from(experience.clone())
},
);
let mut ingestions: Vec<Ingestion> = Vec::new();
for ingestion in experience.ingestions.iter() {
ingestions.push(Ingestion::from(ingestion.clone()))
}
journal.set_session_ingestions(session_id, ingestions);
}
journal.save()?;

View file

@ -18,7 +18,7 @@ pub fn journal_new(args: &JournalNewArgs) -> Result<(), Box<dyn std::error::Erro
let file_location = &args.journal_location.journal_file.clone();
if Path::new(&file_location).exists() && !args.force {
panic!("file exists")
panic!("journal already exists, try --force if you want to recreate")
}
JSONJournal::create(file_location)?;

View file

@ -1,12 +1,17 @@
use journal::{journal::JSONJournal, types::Consumer};
use crate::{args::Args, display::print_ingestion_log, formatting::format_session_title};
use crate::{
args::JournalLocation, display::print_ingestion_log, formatting::format_session_title,
};
#[derive(Debug, Clone, clap::Args)]
#[group(requires = "journal_location")]
pub struct PrintSessionArgs {
pub session_title: String,
#[command(flatten)]
pub journal_location: JournalLocation,
#[arg(long)]
pub substance_filter: Option<Vec<String>>,
#[arg(long, value_delimiter = ',')]
@ -31,11 +36,8 @@ pub fn parse_consumer_filter(consumer_filter: Option<Vec<String>>) -> Option<Vec
}
}
pub fn print_session(
global_args: &Args,
args: &PrintSessionArgs,
) -> Result<(), Box<dyn std::error::Error>> {
let journal = JSONJournal::load(global_args.journal_location.journal_file.as_ref().unwrap())?;
pub fn print_session(args: &PrintSessionArgs) -> Result<(), Box<dyn std::error::Error>> {
let journal = JSONJournal::load(&args.journal_location.journal_file)?;
let session = journal
.first_session_by_title(&args.session_title)
.expect("could not find session");

View file

@ -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!(

View file

@ -17,10 +17,10 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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())
}
}

View file

@ -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}"