]> git.r.bdr.sh - rbdr/blog/commitdiff
Improve the error handling
authorRuben Beltran del Rio <redacted>
Sat, 9 Mar 2024 14:34:57 +0000 (15:34 +0100)
committerRuben Beltran del Rio <redacted>
Sat, 9 Mar 2024 14:34:57 +0000 (15:34 +0100)
16 files changed:
src/archiver/gemini.rs
src/archiver/gopher.rs
src/command/add_remote.rs
src/command/publish.rs
src/command/publish_archive.rs
src/command/sync_down.rs
src/command/sync_up.rs
src/command/update.rs
src/generator/html.rs
src/generator/rss.rs
src/generator/txt.rs
src/main.rs
src/remote/git.rs
src/remote/mod.rs
src/template.rs
templates/index.txt

index 8d5630547764af2dc948e6f350d08092b11413dd..3b04bcf4ad92d98ea4d3f90511c9ee09c7ea60e2 100644 (file)
@@ -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)?;
         },
index 820e4d18e6fc974a0f890695d3a716de9f1ffbe5..28f4f46f5e6a48f513f1a9deabb033e99edd1709 100644 (file)
@@ -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)?;
         },
index 040e5729b25201ce0160433f1170635a8097b987..e9157f3a5b8208fe83a08c5ed47d7784f7673ea7 100644 (file)
@@ -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)
     }
 
index 207b45ddeda0cb56d913ffa863a0ee30ad2a12a7..34ca0d28388fec1a414a1ba79b609aa6e26ffe68 100644 (file)
@@ -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(())
     }
 
index 075421f0ac1b25c1f2738724f656832af6692363..c727c16bc87c72377286da92323a50e94e9bb445 100644 (file)
@@ -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(())
     }
 
index bba34b29a0837a1e01086dd82186986e64995a97..eb8e57afdf0b01c0b9b28cedb76eb0541608e922 100644 (file)
@@ -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(())
     }
index a5d2a4cfc5cb817401c4fcb1eb3ef5ea11c1c162..c44e0b03b375e782ef6aca4dbd664e10f6daa088 100644 (file)
@@ -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<Box<dyn super::Command>> {
index 67cc462c110b32da48f064d045ab756a23c48fda..8a3d6de47e27e41d53d66b6b13344fc6ee2e5832 100644 (file)
@@ -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"));
index b7ddd45770432f76a3f272da1dd6713e9e97e178..ab47b7aca8f35635843a12f0ad1672418775529d 100644 (file)
@@ -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)?;
         },
index 1aac73d059b381bff323f5f469c8389b7be4cbfc..6a13bb8486aa0da11b5ef1b49cd5799137db4b2b 100644 (file)
@@ -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)?;
         },
index ae260fef598d43da3f9c01cfd75df66e0504c6d6..f505480489b1533643424fec9d8d6f46185f56ca 100644 (file)
@@ -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)?;
         },
index 8867dc08f93337ab31d5aca96ec47be5bc773fb4..a5c2cf646a22b7e219d7c594c456e745044c73c9 100644 (file)
@@ -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<String> = args().collect();
index a4c1c28d13a1faab79178af9144b0f9e73672814..66c5a89b115756bb06438a3f381c40080217e56e 100644 (file)
@@ -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(())
     }
index de90ef57f4f2b6bb585ae38c996fe75cf849963f..19514af5ba35e8c2ed66e7b1d10c334ad30cff8c 100644 (file)
@@ -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 {
index 03325063a94e45f5fe291f7396e17ac32c1879c0..0213fc3c0d5c849a68fa824c6ad36ecd54c16529 100644 (file)
@@ -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<String> {
         ParsedTemplate::render_tokens(&self.tokens, context)
     }
 
-    pub fn render_tokens(tokens: &Vec<Token>, context: &TemplateContext) -> String {
+    pub fn render_tokens(tokens: &Vec<Token>, context: &TemplateContext) -> Result<String> {
         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<ParsedTemplate> {
     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<Token>) {
+fn tokenize(template: &str, tokens: &mut Vec<Token>) -> 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<Token>) {
         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<Token>) {
                         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<Token>) {
                         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<Token>) {
         }
     }
     tokens.push(Token::Text(remaining_template.to_string()));
+    Ok(())
 }
 
 // File helpers.
index ca0fba4e11acd77a84cb15881f899bd4a5c70cc5..52d363e71d4a14754259eaee33536b6732833fa9 100644 (file)
@@ -4,7 +4,7 @@
 {{= post.raw}}
 ------------------------------------
 {{~}}
-{{? posts_count == 0}}
+{{? !has_posts }}
 ## This is a fresh blog!
 
 There are no posts yet.