From bd03b55fc1e6cc31e4ac377d32a0163e034be8d7 Mon Sep 17 00:00:00 2001 From: ReeseHatfield Date: Sat, 29 Aug 2026 10:44:02 -0400 Subject: [PATCH 1/6] refactor tag type + prevent lazy evaluation of -o --- src/cmd/export.rs | 13 ++++++++++--- src/config.rs | 24 +++++++++++++----------- src/tags.rs | 23 ++++++++++++++++------- 3 files changed, 39 insertions(+), 21 deletions(-) diff --git a/src/cmd/export.rs b/src/cmd/export.rs index 65f81a9..320c2c9 100644 --- a/src/cmd/export.rs +++ b/src/cmd/export.rs @@ -20,13 +20,20 @@ use crate::{ #[derive(Args, Debug, Clone)] pub struct ExportOptions { /// String name of the output PDF file - #[arg(long, default_value_t = get_default_output())] - pub output: String, + pub output: Option, /// Port the project runs on #[arg(long, default_value_t = 3000)] pub port: u16, } +impl ExportOptions { + pub fn output_or_default(&self) -> String { + self.output + .clone() + .unwrap_or_else(|| get_default_output()) + } +} + fn get_default_output() -> String { let default = String::from("slides.pdf"); @@ -79,7 +86,7 @@ pub fn export(opts: ExportOptions) -> Result<(), Box> { // run decktape, assuming the server has spun up by now let export_output = Command::new("npm") - .args(["exec", "decktape", "reveal", &addr, &opts.output]) + .args(["exec", "decktape", "reveal", &addr, &opts.output_or_default()]) .output()?; // send shutdown flag, should signal to run_with_shutdown to kill the process diff --git a/src/config.rs b/src/config.rs index 6d978f3..dd65422 100644 --- a/src/config.rs +++ b/src/config.rs @@ -13,7 +13,7 @@ use crate::cmd::check::OsedaCheckError; use crate::cmd::init::InitOptions; use crate::color::Color; use crate::github; -use crate::tags::Tag; +use crate::tags::{DefinedTag, Tag}; pub fn read_config_file>( path: P, @@ -143,12 +143,12 @@ pub fn create_conf(options: InitOptions) -> Result> None => prompt_for_title()?.replace(" ", "-"), }; - let tags = match options.tags { + let defined_tags = match options.tags { Some(arg_tags) => { arg_tags .iter() - .map(|arg_tag| Tag::from_str(arg_tag)) - .collect::, _>>() + .map(|arg_tag| DefinedTag::from_str(arg_tag)) + .collect::, _>>() .map_err(|_| "Invalid tag. Custom Tags may be added to the oseda-config.json after initialization".to_string())? }, None => prompt_for_tags()? @@ -163,10 +163,11 @@ pub fn create_conf(options: InitOptions) -> Result> let user_name = github::get_config_from_user_git("user.name") .ok_or("Could not get github username. Please ensure you are signed into github")?; + Ok(OsedaConfig { title: title.trim().to_owned(), author: user_name, - tags, + tags: defined_tags.into_iter().map(|t| Tag::from(t)).collect(), last_updated: get_time(), color: color.into_hex(), // start them with empty description @@ -179,8 +180,8 @@ pub fn create_conf(options: InitOptions) -> Result> /// # Returns /// * `Ok(Vec)` with selected categories /// * `Err` if the prompting went wrong somewhere -fn prompt_for_tags() -> Result, Box> { - let options: Vec = Tag::iter().collect(); +fn prompt_for_tags() -> Result, Box> { + let options: Vec = DefinedTag::iter().collect(); let selected_tags = inquire::MultiSelect::new("Select categories (type to search):", options.clone()) @@ -254,6 +255,7 @@ pub fn write_config(path: &str, conf: &OsedaConfig) -> Result<(), Box mod test { use std::path::Path; use tempfile::tempdir; + use crate::tags::DefinedTag; use super::*; @@ -285,7 +287,7 @@ mod test { let conf = OsedaConfig { title: "my-project".to_string(), author: "JaneDoe".to_string(), - tags: vec![Tag::ComputerScience], + tags: vec![Tag::from(DefinedTag::ComputerScience)], last_updated: chrono::Utc::now(), color: Color::Black.into_hex(), description: String::from("Test Description"), @@ -303,7 +305,7 @@ mod test { let conf = OsedaConfig { title: "my-project".to_string(), author: "JaneDoe".to_string(), - tags: vec![Tag::ComputerScience], + tags: vec![Tag::from(DefinedTag::ComputerScience)], last_updated: chrono::Utc::now(), color: Color::Black.into_hex(), description: String::from("Test Description"), @@ -321,7 +323,7 @@ mod test { let conf = OsedaConfig { title: "correct-name".to_string(), author: "JaneDoe".to_string(), - tags: vec![Tag::ComputerScience], + tags: vec![Tag::from(DefinedTag::ComputerScience)], last_updated: chrono::Utc::now(), color: Color::Black.into_hex(), description: String::new(), @@ -341,7 +343,7 @@ mod test { let conf = OsedaConfig { title: "oseda".to_string(), author: "JaneDoe".to_string(), - tags: vec![Tag::ComputerScience], + tags: vec![Tag::from(DefinedTag::ComputerScience)], last_updated: chrono::Utc::now(), color: Color::Black.into_hex(), description: String::from("Test Description"), diff --git a/src/tags.rs b/src/tags.rs index e95f061..86248ba 100644 --- a/src/tags.rs +++ b/src/tags.rs @@ -4,7 +4,7 @@ use strum_macros::{Display, EnumIter, EnumString}; #[derive(Serialize, Deserialize, Debug, Clone, Copy, Display, EnumIter, EnumString)] #[strum(ascii_case_insensitive)] -pub enum Tag { +pub enum DefinedTag { Aerospace, Business, ComputerScience, @@ -20,13 +20,22 @@ pub enum Tag { Politics, Psychology, Science, - // Custom(String), } -// TODO document me -// Custom tags must be added by hand to the oseda-config.json -impl Tag { - pub fn to_vec() -> Vec { - Tag::iter().collect() +#[derive(Serialize, Deserialize, Debug, Clone, Display)] +pub enum Tag { + DefinedTag(DefinedTag), + CustomTag(String) +} + +impl From for Tag { + fn from(tag: DefinedTag) -> Self { + Tag::DefinedTag(tag) + } +} + +impl DefinedTag { + pub fn to_vec() -> Vec { + DefinedTag::iter().collect() } } From 9014677d1816e766850f3b605123340a6760c7dc Mon Sep 17 00:00:00 2001 From: ReeseHatfield Date: Sun, 30 Aug 2026 07:48:44 -0400 Subject: [PATCH 2/6] error formatting and missing tag error --- src/cmd/check.rs | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/cmd/check.rs b/src/cmd/check.rs index 21fa425..ef239c7 100644 --- a/src/cmd/check.rs +++ b/src/cmd/check.rs @@ -25,6 +25,7 @@ pub enum OsedaCheckError { DirectoryNameMismatch(String), CouldNotPingLocalPresentation(String), MissingDescription(String), + MissingTags(String) } impl std::error::Error for OsedaCheckError {} @@ -33,18 +34,22 @@ impl std::error::Error for OsedaCheckError {} impl std::fmt::Display for OsedaCheckError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Self::MissingConfig(msg) => write!(f, "Missing config file {}", msg), - Self::BadConfig(msg) => write!(f, "Bad config file {}", msg), - Self::BadGitCredentials(msg) => write!(f, "Missing git credentials {}", msg), + Self::MissingConfig(msg) => write!(f, "Missing config file: {}", msg), + Self::BadConfig(msg) => write!(f, "Bad config file: {}", msg), + Self::BadGitCredentials(msg) => write!(f, "Missing git credentials: {}", msg), Self::DirectoryNameMismatch(msg) => { - write!(f, "Project name does not match directory {}", msg) - } + write!(f, "Project name does not match directory: {}", msg) + }, Self::CouldNotPingLocalPresentation(msg) => { - write!(f, "Could not ping localhost after project was ran {}", msg) - } + write!(f, "Could not ping localhost after project was ran: {}", msg) + }, Self::MissingDescription(msg) => { - write!(f, "Config file is missing description {}", msg) + write!(f, "Config file is missing description: {}", msg) + }, + Self::MissingTags(msg) => { + write!(f, "No tags detected: {}", msg) } + } } } From d56dee1e9619e6e9c7cc850d3afd1b1a2f6ae56f Mon Sep 17 00:00:00 2001 From: ReeseHatfield Date: Sun, 30 Aug 2026 07:48:58 -0400 Subject: [PATCH 3/6] refactor tag type again --- src/tags.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/tags.rs b/src/tags.rs index 86248ba..61d5e4b 100644 --- a/src/tags.rs +++ b/src/tags.rs @@ -2,6 +2,7 @@ use serde::{Deserialize, Serialize}; use strum::IntoEnumIterator; use strum_macros::{Display, EnumIter, EnumString}; + #[derive(Serialize, Deserialize, Debug, Clone, Copy, Display, EnumIter, EnumString)] #[strum(ascii_case_insensitive)] pub enum DefinedTag { @@ -22,15 +23,10 @@ pub enum DefinedTag { Science, } -#[derive(Serialize, Deserialize, Debug, Clone, Display)] -pub enum Tag { - DefinedTag(DefinedTag), - CustomTag(String) -} -impl From for Tag { +impl From for String { fn from(tag: DefinedTag) -> Self { - Tag::DefinedTag(tag) + tag.to_string() } } From 971a87489e8c21338915d8364c0e5370c3d0b0b7 Mon Sep 17 00:00:00 2001 From: ReeseHatfield Date: Sun, 30 Aug 2026 07:49:22 -0400 Subject: [PATCH 4/6] treat tag as strings post-creation --- src/config.rs | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/config.rs b/src/config.rs index dd65422..0aca675 100644 --- a/src/config.rs +++ b/src/config.rs @@ -13,7 +13,7 @@ use crate::cmd::check::OsedaCheckError; use crate::cmd::init::InitOptions; use crate::color::Color; use crate::github; -use crate::tags::{DefinedTag, Tag}; +use crate::tags::DefinedTag; pub fn read_config_file>( path: P, @@ -101,6 +101,12 @@ pub fn validate_config( )); } + if conf.tags.is_empty() { + return Err(OsedaCheckError::MissingTags( + "Please add tags to oseda-config.json".to_owned(), + )); + } + Ok(()) } @@ -109,7 +115,7 @@ pub fn validate_config( pub struct OsedaConfig { pub title: String, pub author: String, - pub tags: Vec, + pub tags: Vec, // effectively mutable. Will get updated on each deployment pub last_updated: DateTime, pub color: String, @@ -167,7 +173,7 @@ pub fn create_conf(options: InitOptions) -> Result> Ok(OsedaConfig { title: title.trim().to_owned(), author: user_name, - tags: defined_tags.into_iter().map(|t| Tag::from(t)).collect(), + tags: defined_tags.into_iter().map(|t: DefinedTag| DefinedTag::to_string(&t)).collect(), last_updated: get_time(), color: color.into_hex(), // start them with empty description @@ -287,7 +293,7 @@ mod test { let conf = OsedaConfig { title: "my-project".to_string(), author: "JaneDoe".to_string(), - tags: vec![Tag::from(DefinedTag::ComputerScience)], + tags: vec![DefinedTag::from(DefinedTag::ComputerScience).to_string()], last_updated: chrono::Utc::now(), color: Color::Black.into_hex(), description: String::from("Test Description"), @@ -305,7 +311,7 @@ mod test { let conf = OsedaConfig { title: "my-project".to_string(), author: "JaneDoe".to_string(), - tags: vec![Tag::from(DefinedTag::ComputerScience)], + tags: vec![DefinedTag::from(DefinedTag::ComputerScience).to_string()], last_updated: chrono::Utc::now(), color: Color::Black.into_hex(), description: String::from("Test Description"), @@ -323,7 +329,7 @@ mod test { let conf = OsedaConfig { title: "correct-name".to_string(), author: "JaneDoe".to_string(), - tags: vec![Tag::from(DefinedTag::ComputerScience)], + tags: vec![DefinedTag::from(DefinedTag::ComputerScience).to_string()], last_updated: chrono::Utc::now(), color: Color::Black.into_hex(), description: String::new(), @@ -343,7 +349,7 @@ mod test { let conf = OsedaConfig { title: "oseda".to_string(), author: "JaneDoe".to_string(), - tags: vec![Tag::from(DefinedTag::ComputerScience)], + tags: vec![DefinedTag::from(DefinedTag::ComputerScience).to_string()], last_updated: chrono::Utc::now(), color: Color::Black.into_hex(), description: String::from("Test Description"), From d285d83b0a300c9d4d966932a5f6076dcd0ada42 Mon Sep 17 00:00:00 2001 From: ReeseHatfield Date: Sun, 30 Aug 2026 07:52:14 -0400 Subject: [PATCH 5/6] cargo fmt --- src/cmd/check.rs | 9 ++++----- src/cmd/export.rs | 12 ++++++++---- src/config.rs | 8 +++++--- src/tags.rs | 2 -- 4 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/cmd/check.rs b/src/cmd/check.rs index ef239c7..3f2193e 100644 --- a/src/cmd/check.rs +++ b/src/cmd/check.rs @@ -25,7 +25,7 @@ pub enum OsedaCheckError { DirectoryNameMismatch(String), CouldNotPingLocalPresentation(String), MissingDescription(String), - MissingTags(String) + MissingTags(String), } impl std::error::Error for OsedaCheckError {} @@ -39,17 +39,16 @@ impl std::fmt::Display for OsedaCheckError { Self::BadGitCredentials(msg) => write!(f, "Missing git credentials: {}", msg), Self::DirectoryNameMismatch(msg) => { write!(f, "Project name does not match directory: {}", msg) - }, + } Self::CouldNotPingLocalPresentation(msg) => { write!(f, "Could not ping localhost after project was ran: {}", msg) - }, + } Self::MissingDescription(msg) => { write!(f, "Config file is missing description: {}", msg) - }, + } Self::MissingTags(msg) => { write!(f, "No tags detected: {}", msg) } - } } } diff --git a/src/cmd/export.rs b/src/cmd/export.rs index 320c2c9..08ac7fa 100644 --- a/src/cmd/export.rs +++ b/src/cmd/export.rs @@ -28,9 +28,7 @@ pub struct ExportOptions { impl ExportOptions { pub fn output_or_default(&self) -> String { - self.output - .clone() - .unwrap_or_else(|| get_default_output()) + self.output.clone().unwrap_or_else(|| get_default_output()) } } @@ -86,7 +84,13 @@ pub fn export(opts: ExportOptions) -> Result<(), Box> { // run decktape, assuming the server has spun up by now let export_output = Command::new("npm") - .args(["exec", "decktape", "reveal", &addr, &opts.output_or_default()]) + .args([ + "exec", + "decktape", + "reveal", + &addr, + &opts.output_or_default(), + ]) .output()?; // send shutdown flag, should signal to run_with_shutdown to kill the process diff --git a/src/config.rs b/src/config.rs index 0aca675..e82ad1e 100644 --- a/src/config.rs +++ b/src/config.rs @@ -169,11 +169,13 @@ pub fn create_conf(options: InitOptions) -> Result> let user_name = github::get_config_from_user_git("user.name") .ok_or("Could not get github username. Please ensure you are signed into github")?; - Ok(OsedaConfig { title: title.trim().to_owned(), author: user_name, - tags: defined_tags.into_iter().map(|t: DefinedTag| DefinedTag::to_string(&t)).collect(), + tags: defined_tags + .into_iter() + .map(|t: DefinedTag| DefinedTag::to_string(&t)) + .collect(), last_updated: get_time(), color: color.into_hex(), // start them with empty description @@ -259,9 +261,9 @@ pub fn write_config(path: &str, conf: &OsedaConfig) -> Result<(), Box #[cfg(test)] mod test { + use crate::tags::DefinedTag; use std::path::Path; use tempfile::tempdir; - use crate::tags::DefinedTag; use super::*; diff --git a/src/tags.rs b/src/tags.rs index 61d5e4b..003ddf0 100644 --- a/src/tags.rs +++ b/src/tags.rs @@ -2,7 +2,6 @@ use serde::{Deserialize, Serialize}; use strum::IntoEnumIterator; use strum_macros::{Display, EnumIter, EnumString}; - #[derive(Serialize, Deserialize, Debug, Clone, Copy, Display, EnumIter, EnumString)] #[strum(ascii_case_insensitive)] pub enum DefinedTag { @@ -23,7 +22,6 @@ pub enum DefinedTag { Science, } - impl From for String { fn from(tag: DefinedTag) -> Self { tag.to_string() From d952099dc32d2e13e5b6548816f0dfab4780a89d Mon Sep 17 00:00:00 2001 From: ReeseHatfield Date: Sun, 30 Aug 2026 07:54:53 -0400 Subject: [PATCH 6/6] clippy --- src/cmd/export.rs | 2 +- src/config.rs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/cmd/export.rs b/src/cmd/export.rs index 08ac7fa..7ce0dec 100644 --- a/src/cmd/export.rs +++ b/src/cmd/export.rs @@ -28,7 +28,7 @@ pub struct ExportOptions { impl ExportOptions { pub fn output_or_default(&self) -> String { - self.output.clone().unwrap_or_else(|| get_default_output()) + self.output.clone().unwrap_or_else(get_default_output) } } diff --git a/src/config.rs b/src/config.rs index e82ad1e..301a275 100644 --- a/src/config.rs +++ b/src/config.rs @@ -295,7 +295,7 @@ mod test { let conf = OsedaConfig { title: "my-project".to_string(), author: "JaneDoe".to_string(), - tags: vec![DefinedTag::from(DefinedTag::ComputerScience).to_string()], + tags: vec![DefinedTag::ComputerScience.to_string()], last_updated: chrono::Utc::now(), color: Color::Black.into_hex(), description: String::from("Test Description"), @@ -313,7 +313,7 @@ mod test { let conf = OsedaConfig { title: "my-project".to_string(), author: "JaneDoe".to_string(), - tags: vec![DefinedTag::from(DefinedTag::ComputerScience).to_string()], + tags: vec![DefinedTag::ComputerScience.to_string()], last_updated: chrono::Utc::now(), color: Color::Black.into_hex(), description: String::from("Test Description"), @@ -331,7 +331,7 @@ mod test { let conf = OsedaConfig { title: "correct-name".to_string(), author: "JaneDoe".to_string(), - tags: vec![DefinedTag::from(DefinedTag::ComputerScience).to_string()], + tags: vec![DefinedTag::ComputerScience.to_string()], last_updated: chrono::Utc::now(), color: Color::Black.into_hex(), description: String::new(), @@ -351,7 +351,7 @@ mod test { let conf = OsedaConfig { title: "oseda".to_string(), author: "JaneDoe".to_string(), - tags: vec![DefinedTag::from(DefinedTag::ComputerScience).to_string()], + tags: vec![DefinedTag::ComputerScience.to_string()], last_updated: chrono::Utc::now(), color: Color::Black.into_hex(), description: String::from("Test Description"),