update
This commit is contained in:
parent
841e561973
commit
ea544ad4af
|
@ -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>>;
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>();
|
||||
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<Ingestion> = 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::<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()?;
|
||||
|
|
|
@ -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)?;
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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!(
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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}"
|
||||
|
|
Loading…
Reference in a new issue