From: Ruben Beltran del Rio Date: Sat, 9 Mar 2024 14:34:57 +0000 (+0100) Subject: Improve the error handling X-Git-Tag: 7.0.0~39 X-Git-Url: https://git.r.bdr.sh/rbdr/blog/commitdiff_plain/refs/heads/rust Improve the error handling --- diff --git a/src/archiver/gemini.rs b/src/archiver/gemini.rs index 8d56305..3b04bcf 100644 --- a/src/archiver/gemini.rs +++ b/src/archiver/gemini.rs @@ -1,5 +1,5 @@ use std::fs::write; -use std::io::Result; +use std::io::{Error, ErrorKind::Other, Result}; use std::path::PathBuf; use crate::template::{find, parse, TemplateContext}; @@ -8,8 +8,9 @@ const FILENAME: &str = "index.gmi"; pub fn archive(_: &PathBuf, template_directory: &PathBuf, target: &PathBuf, context: &TemplateContext) -> Result<()> { match find(template_directory, FILENAME) { Some(template) => { - let parsed_template = parse(&template); - let rendered_template = parsed_template.render(context); + let parsed_template = parse(&template) + .ok_or_else(|| Error::new(Other, "Unable to parse Gemini Archive template"))?; + let rendered_template = parsed_template.render(context)?; let location = target.join(FILENAME); write(location, rendered_template)?; }, diff --git a/src/archiver/gopher.rs b/src/archiver/gopher.rs index 820e4d1..28f4f46 100644 --- a/src/archiver/gopher.rs +++ b/src/archiver/gopher.rs @@ -1,5 +1,5 @@ use std::fs::write; -use std::io::Result; +use std::io::{Error, ErrorKind::Other, Result}; use std::path::PathBuf; use crate::template::{find, parse, TemplateContext}; @@ -8,8 +8,9 @@ const FILENAME: &str = "index.gph"; pub fn archive(_: &PathBuf, template_directory: &PathBuf, target: &PathBuf, context: &TemplateContext) -> Result<()> { match find(template_directory, FILENAME) { Some(template) => { - let parsed_template = parse(&template); - let rendered_template = parsed_template.render(context); + let parsed_template = parse(&template) + .ok_or_else(|| Error::new(Other, "Unable to parse Gopher Archive template"))?; + let rendered_template = parsed_template.render(context)?; let location = target.join(FILENAME); write(location, rendered_template)?; }, diff --git a/src/command/add_remote.rs b/src/command/add_remote.rs index 040e572..e9157f3 100644 --- a/src/command/add_remote.rs +++ b/src/command/add_remote.rs @@ -1,4 +1,4 @@ -use std::io::Result; +use std::io::{Error, ErrorKind::Other, Result}; use crate::configuration::Configuration; use crate::remote::add; @@ -16,7 +16,8 @@ impl super::Command for AddRemote { } fn execute(&self, input: Option<&String>, configuration: &Configuration, _: &String) -> Result<()> { - let input = input.expect("You must provide a location for the remote."); + let input = input + .ok_or_else(|| Error::new(Other, "You must provide a location for the remote."))?; add(&configuration.config_directory, &configuration.remote_config, input) } diff --git a/src/command/publish.rs b/src/command/publish.rs index 207b45d..34ca0d2 100644 --- a/src/command/publish.rs +++ b/src/command/publish.rs @@ -1,4 +1,4 @@ -use std::io::Result; +use std::io::{Error, ErrorKind::Other, Result}; use crate::configuration::Configuration; use std::process::{Command, Stdio}; @@ -19,14 +19,15 @@ impl super::Command for Publish { fn execute(&self, input: Option<&String>, configuration: &Configuration, _: &String) -> Result<()> { - let input = input.expect("You must provide a location to publish the blog"); + let input = input + .ok_or_else(|| Error::new(Other, "You must provide a location to publish the blog"))?; Command::new(COMMAND) .arg("--version") .stdout(Stdio::null()) .stderr(Stdio::null()) .status() - .expect("Publishing requires rsync"); + .map_err(|_| Error::new(Other, "Publishing requires rsync"))?; Command::new(COMMAND) @@ -36,7 +37,7 @@ impl super::Command for Publish { .stdout(Stdio::null()) .stderr(Stdio::null()) .status() - .expect("Publishing requires rsync"); + .map_err(|_| Error::new(Other, "Rsync failed to publish."))?; return Ok(()) } diff --git a/src/command/publish_archive.rs b/src/command/publish_archive.rs index 075421f..c727c16 100644 --- a/src/command/publish_archive.rs +++ b/src/command/publish_archive.rs @@ -1,4 +1,4 @@ -use std::io::Result; +use std::io::{Error, ErrorKind::Other, Result}; use crate::configuration::Configuration; use std::process::{Command, Stdio}; @@ -19,14 +19,15 @@ impl super::Command for PublishArchive { fn execute(&self, input: Option<&String>, configuration: &Configuration, _: &String) -> Result<()> { - let input = input.expect("You must provide a location to publish the archive"); + let input = input + .ok_or_else(|| Error::new(Other, "You must provide a location to publish the archive"))?; Command::new(COMMAND) .arg("--version") .stdout(Stdio::null()) .stderr(Stdio::null()) .status() - .expect("Publishing requires rsync"); + .map_err(|_| Error::new(Other, "Publishing requires rsync"))?; Command::new(COMMAND) @@ -36,7 +37,7 @@ impl super::Command for PublishArchive { .stdout(Stdio::null()) .stderr(Stdio::null()) .status() - .expect("Publishing requires rsync"); + .map_err(|_| Error::new(Other, "Rsync failed to publish."))?; return Ok(()) } diff --git a/src/command/sync_down.rs b/src/command/sync_down.rs index bba34b2..eb8e57a 100644 --- a/src/command/sync_down.rs +++ b/src/command/sync_down.rs @@ -16,8 +16,13 @@ impl super::Command for SyncDown { } fn execute(&self, _: Option<&String>, configuration: &Configuration, command: &String) -> Result<()> { - if command == self.command() { - return sync_down(&configuration.data_directory, &configuration.remote_config); + match sync_down(&configuration.data_directory, &configuration.remote_config) { + Ok(_) => {} + Err(e) => { + if command == self.command() { + return Err(e) + } + } } return Ok(()) } diff --git a/src/command/sync_up.rs b/src/command/sync_up.rs index a5d2a4c..c44e0b0 100644 --- a/src/command/sync_up.rs +++ b/src/command/sync_up.rs @@ -15,8 +15,16 @@ impl super::Command for SyncUp { vec![] } - fn execute(&self, _: Option<&String>, configuration: &Configuration, _: &String) -> Result<()> { - sync_up(&configuration.data_directory, &configuration.remote_config) + fn execute(&self, _: Option<&String>, configuration: &Configuration, command: &String) -> Result<()> { + match sync_up(&configuration.data_directory, &configuration.remote_config) { + Ok(_) => {} + Err(e) => { + if command == self.command() { + return Err(e) + } + } + } + return Ok(()) } fn after_dependencies(&self) -> Vec> { diff --git a/src/command/update.rs b/src/command/update.rs index 67cc462..8a3d6de 100644 --- a/src/command/update.rs +++ b/src/command/update.rs @@ -55,7 +55,8 @@ impl super::Command for Update { } fn execute(&self, input: Option<&String>, configuration: &Configuration, _: &String) -> Result<()> { - let input = input.expect("You must provide a path to a post"); + let input = input + .ok_or_else(|| Error::new(ErrorKind::InvalidInput, "You must provide a path to a post"))?; let post_location = PathBuf::from(input); if !post_location.exists() { return Err(Error::new(ErrorKind::NotFound, "The path provided does not exist")); diff --git a/src/generator/html.rs b/src/generator/html.rs index b7ddd45..ab47b7a 100644 --- a/src/generator/html.rs +++ b/src/generator/html.rs @@ -1,5 +1,5 @@ use std::fs::write; -use std::io::Result; +use std::io::{Error, ErrorKind::Other, Result}; use std::path::PathBuf; use crate::template::{find, parse, TemplateContext}; @@ -8,8 +8,9 @@ const FILENAME: &str = "index.html"; pub fn generate(_: &PathBuf, template_directory: &PathBuf, target: &PathBuf, context: &TemplateContext) -> Result<()> { match find(template_directory, FILENAME) { Some(template) => { - let parsed_template = parse(&template); - let rendered_template = parsed_template.render(context); + let parsed_template = parse(&template) + .ok_or_else(|| Error::new(Other, "Unable to parse HTML template"))?; + let rendered_template = parsed_template.render(context)?; let location = target.join(FILENAME); write(location, rendered_template)?; }, diff --git a/src/generator/rss.rs b/src/generator/rss.rs index 1aac73d..6a13bb8 100644 --- a/src/generator/rss.rs +++ b/src/generator/rss.rs @@ -1,5 +1,5 @@ use std::fs::write; -use std::io::Result; +use std::io::{Error, ErrorKind::Other, Result}; use std::path::PathBuf; use crate::template::{find, parse, TemplateContext}; @@ -8,8 +8,9 @@ const FILENAME: &str = "feed.xml"; pub fn generate(_: &PathBuf, template_directory: &PathBuf, target: &PathBuf, context: &TemplateContext) -> Result<()> { match find(template_directory, FILENAME) { Some(template) => { - let parsed_template = parse(&template); - let rendered_template = parsed_template.render(context); + let parsed_template = parse(&template) + .ok_or_else(|| Error::new(Other, "Unable to parse RSS template"))?; + let rendered_template = parsed_template.render(context)?; let location = target.join(FILENAME); write(location, rendered_template)?; }, diff --git a/src/generator/txt.rs b/src/generator/txt.rs index ae260fe..f505480 100644 --- a/src/generator/txt.rs +++ b/src/generator/txt.rs @@ -1,5 +1,5 @@ use std::fs::write; -use std::io::Result; +use std::io::{Error, ErrorKind::Other, Result}; use std::path::PathBuf; use crate::template::{find, parse, TemplateContext}; @@ -8,8 +8,9 @@ const FILENAME: &str = "index.txt"; pub fn generate(_: &PathBuf, template_directory: &PathBuf, target: &PathBuf, context: &TemplateContext) -> Result<()> { match find(template_directory, FILENAME) { Some(template) => { - let parsed_template = parse(&template); - let rendered_template = parsed_template.render(context); + let parsed_template = parse(&template) + .ok_or_else(|| Error::new(Other, "Unable to parse TXT template"))?; + let rendered_template = parsed_template.render(context)?; let location = target.join(FILENAME); write(location, rendered_template)?; }, diff --git a/src/main.rs b/src/main.rs index 8867dc0..a5c2cf6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,6 +18,22 @@ use command::{available_commands, Command, help::Help}; use configuration::Configuration; fn main() -> Result<()> { + let result = run(); + + if cfg!(debug_assertions) { + result + } else { + match result { + Ok(_) => Ok(()), + Err(e) => { + eprintln!("Error: {}", e); + std::process::exit(1); + } + } + } +} + +fn run() -> Result<()> { let configuration = Configuration::new(); let commands = available_commands(); let arguments: Vec = args().collect(); diff --git a/src/remote/git.rs b/src/remote/git.rs index a4c1c28..66c5a89 100644 --- a/src/remote/git.rs +++ b/src/remote/git.rs @@ -1,6 +1,6 @@ -use std::io::Result; +use std::io::{Error, ErrorKind::Other, Result}; use std::path::PathBuf; -use std::process::Command; +use std::process::{Command, Stdio}; use std::time::{SystemTime, UNIX_EPOCH}; pub struct Git; @@ -19,7 +19,7 @@ impl super::Remote for Git { fn sync_up(&self, remote: &str, directory: &PathBuf) -> Result<()> { let timestamp = SystemTime::now().duration_since(UNIX_EPOCH) - .expect("Invalid time") + .map_err(|_| Error::new(Other, "Invalid time"))? .as_millis(); let commands = vec![ @@ -33,8 +33,10 @@ impl super::Remote for Git { Command::new("sh") .arg("-c") .arg(&command) + .stdout(Stdio::null()) + .stderr(Stdio::null()) .status() - .expect("Failed while performing sync up with git"); + .map_err(|_| Error::new(Other, "Failed while performing sync up with git"))?; } Ok(()) @@ -52,8 +54,10 @@ impl super::Remote for Git { Command::new("sh") .arg("-c") .arg(&command) + .stdout(Stdio::null()) + .stderr(Stdio::null()) .status() - .expect("Failed while performing sync down with git"); + .map_err(|_| Error::new(Other, "Failed while performing sync down with git"))?; } Ok(()) } diff --git a/src/remote/mod.rs b/src/remote/mod.rs index de90ef5..19514af 100644 --- a/src/remote/mod.rs +++ b/src/remote/mod.rs @@ -27,7 +27,7 @@ pub fn remove(remote_config: &PathBuf) -> Result<()> { pub fn sync_up(data_directory: &PathBuf, remote_config: &PathBuf) -> Result<()> { let remote_address = read_remote(remote_config) - .expect("No remote is configured"); + .ok_or_else(|| Error::new(Other, "No remote is configured"))?; create_dir_all(data_directory)?; let remotes = available_remotes(); for remote in remotes { @@ -40,7 +40,7 @@ pub fn sync_up(data_directory: &PathBuf, remote_config: &PathBuf) -> Result<()> pub fn sync_down(data_directory: &PathBuf, remote_config: &PathBuf) -> Result<()> { let remote_address = read_remote(remote_config) - .expect("No remote is configured"); + .ok_or_else(|| Error::new(Other, "No remote is configured"))?; create_dir_all(data_directory)?; let remotes = available_remotes(); for remote in remotes { diff --git a/src/template.rs b/src/template.rs index 0332506..0213fc3 100644 --- a/src/template.rs +++ b/src/template.rs @@ -1,3 +1,4 @@ +use std::io::{Error, ErrorKind::Other, Result}; use std::collections::HashMap; use std::fs::File; use std::path::PathBuf; @@ -83,20 +84,19 @@ pub struct ParsedTemplate { } impl ParsedTemplate { - pub fn render(&self, context: &TemplateContext) -> String { + pub fn render(&self, context: &TemplateContext) -> Result { ParsedTemplate::render_tokens(&self.tokens, context) } - pub fn render_tokens(tokens: &Vec, context: &TemplateContext) -> String { + pub fn render_tokens(tokens: &Vec, context: &TemplateContext) -> Result { let mut rendered_template: String = String::new(); for token in tokens { match token { Token::Text(contents) => rendered_template.push_str(&contents), Token::DisplayDirective { content } => { - match TemplateContextGetter::get(context, &content) { - Some(value) => rendered_template.push_str(&value.render()), - None => panic!("{} is not a valid key", content) - } + let value = TemplateContextGetter::get(context, &content) + .ok_or_else(|| Error::new(Other, format!("{} is not a valid key", content)))?; + rendered_template.push_str(&value.render()); }, Token::ConditionalDirective { condition, children} => { let mut negator = false; @@ -105,50 +105,59 @@ impl ParsedTemplate { negator = true; condition = condition[1..].to_string(); } - match TemplateContextGetter::get(context, &condition) { - Some(TemplateValue::Bool(value)) => { + + let value = TemplateContextGetter::get(context, &condition) + .ok_or_else(|| Error::new(Other, format!("{} is not a valid key", condition)))?; + + match value { + TemplateValue::Bool(value) => { if negator ^ value { - rendered_template.push_str(&ParsedTemplate::render_tokens(children, context)) + rendered_template.push_str(&ParsedTemplate::render_tokens(children, context)?) } + Ok(()) }, - _ => panic!("{} is not a boolean value", condition) - } + _ => Err(Error::new(Other, format!("{} is not a boolean value", condition))), + }?; }, Token::IteratorDirective { collection, member_label, children } => { - match TemplateContextGetter::get(context, &collection) { - Some(TemplateValue::Collection(collection)) => { + let value = TemplateContextGetter::get(context, &collection) + .ok_or_else(|| Error::new(Other, format!("{} is not a valid key", collection)))?; + + match value { + TemplateValue::Collection(collection) => { for member in collection { let mut child_context = context.clone(); child_context.insert( member_label.to_string(), TemplateValue::Context(member) ); - rendered_template.push_str(&ParsedTemplate::render_tokens(&children, &child_context)) + rendered_template.push_str(&ParsedTemplate::render_tokens(&children, &child_context)?) } + Ok(()) }, - _ => panic!("{} is not a collection", collection) - } + _ => Err(Error::new(Other, format!("{} is not a collection", collection))), + }?; } } } - rendered_template + Ok(rendered_template) } } -pub fn parse(template: &str) -> ParsedTemplate { +pub fn parse(template: &str) -> Option { let mut tokens = Vec::new(); - tokenize(template, &mut tokens); - ParsedTemplate { + tokenize(template, &mut tokens).ok()?; + Some(ParsedTemplate { tokens - } + }) } -fn tokenize(template: &str, tokens: &mut Vec) { +fn tokenize(template: &str, tokens: &mut Vec) -> Result<()> { let mut remaining_template = template; while !remaining_template.is_empty() && remaining_template.contains("{{") { let directive_start_index = remaining_template.find("{{") - .expect("Was expecting at least one tag opener"); + .ok_or_else(|| Error::new(Other, "Was expecting at least one tag opener"))?; if directive_start_index > 0 { let text = remaining_template[..directive_start_index].to_string(); tokens.push(Token::Text(text.to_string())); @@ -156,7 +165,7 @@ fn tokenize(template: &str, tokens: &mut Vec) { remaining_template = &remaining_template[directive_start_index..]; let directive_end_index = remaining_template.find("}}") - .expect("Was expecting }} after {{") + 2; + .ok_or_else(|| Error::new(Other, "Was expecting }} after {{"))? + 2; let directive = &remaining_template[..directive_end_index]; remaining_template = &remaining_template[directive_end_index..]; @@ -179,7 +188,7 @@ fn tokenize(template: &str, tokens: &mut Vec) { let closing_block = remaining_template.find("{{?}}").unwrap(); let directive_block = &remaining_template[..closing_block]; remaining_template = &remaining_template[closing_block + 5..]; - tokenize(directive_block, &mut children); + tokenize(directive_block, &mut children)?; tokens.push(Token::ConditionalDirective{ condition: content.to_string(), children @@ -190,7 +199,7 @@ fn tokenize(template: &str, tokens: &mut Vec) { let closing_block = remaining_template.find("{{~}}").unwrap(); let directive_block = &remaining_template[..closing_block]; remaining_template = &remaining_template[closing_block + 5..]; - tokenize(directive_block, &mut children); + tokenize(directive_block, &mut children)?; if parts.len() == 2 { tokens.push(Token::IteratorDirective { collection: parts[0].trim().to_string(), @@ -206,6 +215,7 @@ fn tokenize(template: &str, tokens: &mut Vec) { } } tokens.push(Token::Text(remaining_template.to_string())); + Ok(()) } // File helpers. diff --git a/templates/index.txt b/templates/index.txt index ca0fba4..52d363e 100644 --- a/templates/index.txt +++ b/templates/index.txt @@ -4,7 +4,7 @@ {{= post.raw}} ------------------------------------ {{~}} -{{? posts_count == 0}} +{{? !has_posts }} ## This is a fresh blog! There are no posts yet.