add psychonaut journal import functionality
This commit is contained in:
parent
6064e8e1d9
commit
7fd19193f5
4 changed files with 329 additions and 30 deletions
128
Cargo.lock
generated
128
Cargo.lock
generated
|
@ -17,6 +17,56 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"colorchoice",
|
||||
"is_terminal_polyfill",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-query"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
|
||||
dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "3.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"once_cell",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.4.0"
|
||||
|
@ -77,6 +127,52 @@ dependencies = [
|
|||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eccb054f56cbd38340b380d4a8e69ef1f02f1af43db2f0cc817a4774d80ae071"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "efd9466fac8543255d3b1fcad4762c5e116ffe808c8a3043d4263cd4fd4862a2"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.5.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.7"
|
||||
|
@ -180,6 +276,12 @@ dependencies = [
|
|||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.15"
|
||||
|
@ -226,9 +328,10 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
|||
|
||||
[[package]]
|
||||
name = "meowlog"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"clap",
|
||||
"inquire",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
@ -246,7 +349,7 @@ dependencies = [
|
|||
"libc",
|
||||
"log",
|
||||
"wasi",
|
||||
"windows-sys",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -449,6 +552,12 @@ dependencies = [
|
|||
"sqlite3-src",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "strum"
|
||||
version = "0.27.1"
|
||||
|
@ -510,6 +619,12 @@ version = "0.1.14"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
|
@ -620,6 +735,15 @@ dependencies = [
|
|||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.59.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.48.5"
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
name = "meowlog"
|
||||
description = "CLI tool to log the hypothetical ingestion of substances"
|
||||
authors = ["xqtc"]
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
repository = "https://git.heroin.trade/xqtc/meowlog"
|
||||
|
@ -16,3 +16,4 @@ serde_json = "1.0"
|
|||
strum = { version = "0.27.1", features = ["derive"] }
|
||||
strum_macros = "0.27.1"
|
||||
sqlite = "0.37.0"
|
||||
clap = { version = "4.5.37", features = ["derive"] }
|
||||
|
|
138
src/import.rs
Normal file
138
src/import.rs
Normal file
|
@ -0,0 +1,138 @@
|
|||
use chrono::{TimeZone, Utc};
|
||||
use serde_json::Value;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::error::Error;
|
||||
use std::fs::File;
|
||||
use std::io::BufReader;
|
||||
|
||||
use crate::{IngestionEntry, Unit, db};
|
||||
|
||||
pub fn import_psychonaut_journal(journal_path: &str) -> Result<(), Box<dyn Error>> {
|
||||
println!("Importing journal from: {}", journal_path);
|
||||
|
||||
let substances = crate::util::read_substances_from_file()?;
|
||||
|
||||
let mut substance_map: HashMap<String, String> = HashMap::new();
|
||||
let mut aliases_map: HashMap<String, String> = HashMap::new();
|
||||
|
||||
for (name, data) in &substances {
|
||||
substance_map.insert(name.to_lowercase(), name.clone());
|
||||
|
||||
if let Some(pretty_name) = data["pretty_name"].as_str() {
|
||||
substance_map.insert(pretty_name.to_lowercase(), name.clone());
|
||||
}
|
||||
|
||||
if let Some(aliases) = data["aliases"].as_array() {
|
||||
for alias in aliases {
|
||||
if let Some(alias_str) = alias.as_str() {
|
||||
aliases_map.insert(alias_str.to_lowercase(), name.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let file = File::open(journal_path)?;
|
||||
let reader = BufReader::new(file);
|
||||
let journal: Value = serde_json::from_reader(reader)?;
|
||||
|
||||
let conn = db::init_db_conn()?;
|
||||
db::create_db()?;
|
||||
|
||||
let mut unknown_substances: HashSet<String> = HashSet::new();
|
||||
|
||||
let mut total_entries = 0;
|
||||
let mut imported_entries = 0;
|
||||
|
||||
if let Some(experiences) = journal["experiences"].as_array() {
|
||||
for experience in experiences {
|
||||
if let Some(ingestions) = experience["ingestions"].as_array() {
|
||||
for ingestion in ingestions {
|
||||
total_entries += 1;
|
||||
|
||||
let substance_name = ingestion["substanceName"].as_str().unwrap_or("Unknown");
|
||||
let administration_route =
|
||||
ingestion["administrationRoute"].as_str().unwrap_or("ORAL");
|
||||
let dose = ingestion["dose"].as_f64().unwrap_or(0.0);
|
||||
let units = ingestion["units"].as_str().unwrap_or("mg");
|
||||
|
||||
let time_ms = ingestion["time"].as_i64().unwrap_or(0);
|
||||
let ingestion_time = Utc
|
||||
.timestamp_millis_opt(time_ms)
|
||||
.single()
|
||||
.unwrap_or_else(Utc::now)
|
||||
.with_timezone(&chrono::Local);
|
||||
|
||||
let lowercase_name = substance_name.to_lowercase();
|
||||
let mapped_name = if let Some(name) = substance_map.get(&lowercase_name) {
|
||||
name.clone()
|
||||
} else if let Some(name) = aliases_map.get(&lowercase_name) {
|
||||
name.clone()
|
||||
} else {
|
||||
unknown_substances.insert(substance_name.to_string());
|
||||
substance_name.to_string()
|
||||
};
|
||||
|
||||
let route = match administration_route {
|
||||
"ORAL" => "Oral",
|
||||
"INSUFFLATED" => "Insufflated",
|
||||
"SMOKED" => "Smoked",
|
||||
"SUBLINGUAL" => "Sublingual",
|
||||
"BUCCAL" => "Buccal",
|
||||
"RECTAL" => "Rectal",
|
||||
"INTRAVENOUS" => "Intravenous",
|
||||
"INTRAMUSCULAR" => "Intramuscular",
|
||||
"SUBCUTANEOUS" => "Subcutaneous",
|
||||
"TRANSDERMAL" => "Transdermal",
|
||||
_ => "Oral", // Default to oral if unknown
|
||||
}
|
||||
.to_string();
|
||||
|
||||
let unit = match units.to_lowercase().as_str() {
|
||||
"µg" | "ug" => Unit::Ug,
|
||||
"mg" => Unit::Mg,
|
||||
"g" => Unit::G,
|
||||
"ml" => Unit::Ml,
|
||||
_ => {
|
||||
println!(
|
||||
"Warning: Unknown unit '{}' for {}, defaulting to mg",
|
||||
units, substance_name
|
||||
);
|
||||
Unit::Mg // Default to mg if unknown
|
||||
}
|
||||
};
|
||||
|
||||
let entry = IngestionEntry {
|
||||
substance: mapped_name,
|
||||
route,
|
||||
amount: dose,
|
||||
unit,
|
||||
ingestion_time,
|
||||
created_at: Some(chrono::Local::now()),
|
||||
};
|
||||
|
||||
match db::save_ingestion_to_db(&conn, &entry) {
|
||||
Ok(_) => {
|
||||
imported_entries += 1;
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Error saving entry for {}: {}", substance_name, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println!("Import completed!");
|
||||
println!("Imported {}/{} entries", imported_entries, total_entries);
|
||||
|
||||
if !unknown_substances.is_empty() {
|
||||
println!("\nWARNING: The following substances couldn't be mapped to the drugs database:");
|
||||
for substance in unknown_substances {
|
||||
println!(" - {}", substance);
|
||||
}
|
||||
println!("These substances were imported with their original names.");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
90
src/main.rs
90
src/main.rs
|
@ -1,4 +1,5 @@
|
|||
use chrono::{DateTime, Local};
|
||||
use clap::Parser;
|
||||
use db::create_db;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::error::Error;
|
||||
|
@ -6,32 +7,48 @@ use std::process::exit;
|
|||
use strum::EnumIter;
|
||||
use strum::IntoEnumIterator;
|
||||
use strum_macros::Display;
|
||||
use util::gather_ingestion_data;
|
||||
use util::initialize_app_directory;
|
||||
|
||||
mod util;
|
||||
use util::gather_ingestion_data;
|
||||
use util::initialize_app_directory;
|
||||
use util::read_substances_from_file;
|
||||
|
||||
mod db;
|
||||
use db::init_db_conn;
|
||||
|
||||
mod import;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
#[command(propagate_version = true)]
|
||||
#[command(arg_required_else_help = false)]
|
||||
struct CliArgs {
|
||||
#[arg(short, long, default_value_t = false)]
|
||||
import_psychonaut_journal: bool,
|
||||
|
||||
#[arg(short = 'j', long, required_if_eq("import_psychonaut_journal", "true"))]
|
||||
journal_path: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, EnumIter)]
|
||||
pub enum Command {
|
||||
pub enum MeowlogCommand {
|
||||
AddIngestion,
|
||||
EditIngestion,
|
||||
ListIngestions,
|
||||
DeleteIngestion,
|
||||
ShowSubstanceInfo,
|
||||
Quit,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Command {
|
||||
impl std::fmt::Display for MeowlogCommand {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let command_str = match self {
|
||||
Command::AddIngestion => "Add",
|
||||
Command::EditIngestion => "Edit",
|
||||
Command::ListIngestions => "List",
|
||||
Command::DeleteIngestion => "Delete",
|
||||
Command::Quit => "Quit",
|
||||
MeowlogCommand::AddIngestion => "Add",
|
||||
MeowlogCommand::EditIngestion => "Edit",
|
||||
MeowlogCommand::ListIngestions => "List",
|
||||
MeowlogCommand::DeleteIngestion => "Delete",
|
||||
MeowlogCommand::ShowSubstanceInfo => "Show substance info",
|
||||
MeowlogCommand::Quit => "Quit",
|
||||
};
|
||||
write!(f, "{}", command_str)
|
||||
}
|
||||
|
@ -57,31 +74,49 @@ pub struct IngestionEntry {
|
|||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
let substances = match read_substances_from_file() {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
eprintln!("Error loading substances file: {}", e);
|
||||
return Err(e);
|
||||
let cli_args = CliArgs::parse();
|
||||
match cli_args.import_psychonaut_journal {
|
||||
true => {
|
||||
let _ = initialize_app_directory()?;
|
||||
let _ = create_db();
|
||||
|
||||
if let Some(p) = &cli_args.journal_path {
|
||||
import::import_psychonaut_journal(p)?;
|
||||
}
|
||||
|
||||
println!(
|
||||
"Import completed. Run the program without --import-psychonaut-journal to use the CLI."
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
false => {
|
||||
let substances = match read_substances_from_file() {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
eprintln!("Error loading substances file: {}", e);
|
||||
return Err(e);
|
||||
}
|
||||
};
|
||||
|
||||
let _ = initialize_app_directory()?;
|
||||
let _ = create_db();
|
||||
let _ = initialize_app_directory()?;
|
||||
let _ = create_db();
|
||||
|
||||
println!("Successfully loaded {} substances", substances.len());
|
||||
println!("Successfully loaded {} substances", substances.len());
|
||||
|
||||
loop {
|
||||
handle_commands(substances.clone())?;
|
||||
loop {
|
||||
handle_commands(substances.clone())?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_commands(
|
||||
substances: std::collections::HashMap<String, serde_json::Value>,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let command =
|
||||
inquire::Select::new("What do you want to do?", Command::iter().collect()).prompt()?;
|
||||
let command = inquire::Select::new("What do you want to do?", MeowlogCommand::iter().collect())
|
||||
.prompt()?;
|
||||
match command {
|
||||
Command::AddIngestion => {
|
||||
MeowlogCommand::AddIngestion => {
|
||||
let ingestion = gather_ingestion_data(substances.clone())?;
|
||||
|
||||
println!("\nRecorded Information:");
|
||||
|
@ -121,14 +156,15 @@ fn handle_commands(
|
|||
println!("Entry saved successfully");
|
||||
};
|
||||
}
|
||||
Command::ListIngestions => {
|
||||
MeowlogCommand::ListIngestions => {
|
||||
util::list_ingestions()?;
|
||||
}
|
||||
Command::DeleteIngestion => {
|
||||
MeowlogCommand::DeleteIngestion => {
|
||||
util::delete_ingestion_entries(&init_db_conn()?)?;
|
||||
}
|
||||
Command::EditIngestion => util::edit_ingestion_entry(&init_db_conn()?)?,
|
||||
Command::Quit => exit(0),
|
||||
MeowlogCommand::EditIngestion => util::edit_ingestion_entry(&init_db_conn()?)?,
|
||||
MeowlogCommand::ShowSubstanceInfo => {}
|
||||
MeowlogCommand::Quit => exit(0),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue