From 17a4728b06419e83eede3adc9f653a83d1b3f7ee Mon Sep 17 00:00:00 2001 From: Daniel Duque Date: Fri, 27 Sep 2024 03:44:17 +0200 Subject: [PATCH] feat: Push elogs to remote server --- Cargo.lock | 45 +++++++++++++++++++++++++++ Cargo.toml | 1 + src/config.rs | 28 +++++++++++++---- src/main.rs | 84 ++++++++++++++++++++++++++++++++++++++++++++++++--- 4 files changed, 148 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6b3171c..e2bf85f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -24,6 +24,7 @@ dependencies = [ "anyhow", "clap", "csv", + "dialoguer", "directories", "indent", "reqwest", @@ -223,6 +224,19 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" +[[package]] +name = "console" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "unicode-width", + "windows-sys 0.52.0", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -285,6 +299,19 @@ version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" +[[package]] +name = "dialoguer" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "658bce805d770f407bc62102fca7c2c64ceef2fbcb2b8bd19d2765ce093980de" +dependencies = [ + "console", + "shell-words", + "tempfile", + "thiserror", + "zeroize", +] + [[package]] name = "digest" version = "0.10.7" @@ -316,6 +343,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + [[package]] name = "encoding_rs" version = "0.8.34" @@ -663,6 +696,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "libc" version = "0.2.158" @@ -1173,6 +1212,12 @@ dependencies = [ "digest", ] +[[package]] +name = "shell-words" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" + [[package]] name = "shlex" version = "1.3.0" diff --git a/Cargo.toml b/Cargo.toml index a3c37f5..7f1dbea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ publish = false anyhow = "1.0.89" clap = { version = "4.5.18", features = ["derive"] } csv = "1.3.0" +dialoguer = "0.11.0" directories = "5.0.1" indent = "0.1.1" reqwest = { version = "0.12.7", features = ["blocking"] } diff --git a/src/config.rs b/src/config.rs index 68ece24..bbc4f8e 100644 --- a/src/config.rs +++ b/src/config.rs @@ -3,17 +3,33 @@ use std::path::PathBuf; #[derive(Debug, Deserialize)] pub struct Config { - elog: ElogConfig, + pub elog: ElogConfig, pub data_handler: DataHandlerConfig, pub rules: Vec, } #[derive(Debug, Deserialize)] -struct ElogConfig { - client: PathBuf, - host: String, - port: u16, - logbook: String, +pub struct ElogConfig { + pub client: PathBuf, + pub host: String, + pub port: u16, + pub logbook: Logbook, +} + +#[derive(Debug, Deserialize)] +pub enum Logbook { + DataLog, + #[serde(rename = "test")] + Test, +} + +impl std::fmt::Display for Logbook { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Logbook::DataLog => write!(f, "DataLog"), + Logbook::Test => write!(f, "test"), + } + } } #[derive(Debug, Deserialize)] diff --git a/src/main.rs b/src/main.rs index ffa6ac3..f05af26 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,14 @@ -use crate::config::Config; +use crate::config::{Config, Logbook}; use crate::data_handler::{get_final_odb, get_spill_log}; -use anyhow::{Context, Result}; +use anyhow::{ensure, Context, Result}; use clap::Parser; +use dialoguer::{theme::ColorfulTheme, Input, Select}; use elog::{loggable_records, ElogEntry}; +use std::ffi::OsStr; +use std::io::Write; use std::path::PathBuf; +use std::process::Command; +use tempfile::NamedTempFile; mod config; mod data_handler; @@ -35,6 +40,54 @@ fn main() -> Result<()> { let final_odb = get_final_odb(args.run_number, &config.data_handler) .context("failed to get the final ODB from the data handler")?; + + let parent_id: String = Input::with_theme(&ColorfulTheme::default()) + .with_prompt("Parent message ID (leave empty to create a new thread instead)") + .allow_empty(true) + .validate_with(|input: &String| -> Result<(), &str> { + if input.is_empty() { + Ok(()) + } else { + input + .parse::() + .map(|_| ()) + .map_err(|_| "message ID must be a non-negative integer") + } + }) + .interact_text() + .context("failed to read parent message ID")?; + + let mut attributes = Vec::new(); + let author: String = Input::with_theme(&ColorfulTheme::default()) + .with_prompt("Author") + .interact_text() + .context("failed to read author")?; + attributes.push(format!("Author={author}")); + if let Logbook::DataLog = config.elog.logbook { + let types = &[ + "Baseline Log", + "Pbar Log", + "Trapping Series", + "Electron Log", + "Positron Log", + ]; + let selection = Select::with_theme(&ColorfulTheme::default()) + .with_prompt("Type") + .default(0) + .items(&types[..]) + .interact() + .context("failed to read logbook")?; + attributes.push(format!("Type={}", types[selection])); + attributes.push(format!("Run={}", args.run_number)); + attributes.push(format!( + "Subject={}", + final_odb + .pointer("/Experiment/Edit on start/Comment") + .and_then(serde_json::Value::as_str) + .context("failed to get comment from ODB")? + )); + } + let spill_log = get_spill_log(args.run_number, &config.data_handler) .context("failed to get spill log from the data handler")?; @@ -50,8 +103,31 @@ fn main() -> Result<()> { elog_entry.add_record(args.run_number, &loggable, &final_odb, &config.data_handler); } - println!("{}", elog_entry.text); - println!("{:?}", elog_entry.attachments); + let mut temp_text = + NamedTempFile::new().context("failed to create temporary elog text file")?; + temp_text + .write_all(elog_entry.text.as_bytes()) + .context("failed to write to temporary elog text file")?; + + let mut cmd = Command::new(&config.elog.client); + cmd.args(["-h", &config.elog.host]) + .args(["-p", &config.elog.port.to_string()]) + .args(["-l", &config.elog.logbook.to_string()]) + .args( + elog_entry + .attachments + .iter() + .flat_map(|path| [OsStr::new("-f"), path.as_ref()]), + ) + .args(attributes.iter().flat_map(|attribute| ["-a", attribute])) + .arg("-x") + .args([OsStr::new("-m"), temp_text.path().as_ref()]); + if !parent_id.is_empty() { + cmd.args(["-r", &parent_id]); + } + + let status = cmd.status().context("failed to run the elog client")?; + ensure!(status.success(), "elog client failed with `{status}`"); Ok(()) }