Skip to content

Commit

Permalink
Changed internal error handling to use error-chain crate
Browse files Browse the repository at this point in the history
  • Loading branch information
saschagrunert committed Mar 18, 2017
1 parent ff44cb1 commit ff3cf33
Show file tree
Hide file tree
Showing 7 changed files with 360 additions and 292 deletions.
162 changes: 118 additions & 44 deletions Cargo.lock

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "git-journal"
version = "1.4.1"
version = "1.4.2"
license = "MIT"
readme = "README.md"
keywords = ["parser", "git", "log", "changelog", "journal"]
Expand All @@ -21,13 +21,14 @@ name = "gitjournal"
[dependencies]
chrono = "0"
clap = { version = "2", features = ["yaml"] }
error-chain = "0"
git2 = "0"
lazy_static = "0"
log = "0"
mowl = "1"
nom = { version = "2", features = ["regexp_macros"] }
regex = "0.1"
rayon = "0"
regex = "0.1"
serde = "0"
serde_derive = "0"
term = "0"
Expand Down
8 changes: 4 additions & 4 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use std::fs::File;
use std::path::PathBuf;
use std::io::prelude::*;

use errors::{GitJournalResult, error};
use errors::*;

/// The configuration structure for git-journal.
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
Expand Down Expand Up @@ -89,14 +89,14 @@ impl Config {
/// # Errors
/// When toml encoding or file creation failed.
///
pub fn save_default_config(&self, path: &str) -> GitJournalResult<String> {
pub fn save_default_config(&self, path: &str) -> Result<String> {
// Serialize self to toml
let toml_string = toml::to_string(&self).unwrap();
info!("{:?}", toml_string);

// Get the correct path
let path_buf = self.get_path_with_filename(path);
let path_string = path_buf.to_str().ok_or_else(|| error("IO", "Cannot convert path to string"))?;
let path_string = path_buf.to_str().ok_or_else(|| "Cannot convert path to string")?;

// Write the path to string
let mut file = File::create(&path_buf)?;
Expand All @@ -116,7 +116,7 @@ impl Config {
/// # Errors
/// When toml decoding or file opening failed.
///
pub fn load(&mut self, path: &str) -> GitJournalResult<()> {
pub fn load(&mut self, path: &str) -> Result<()> {
let path_buf = self.get_path_with_filename(path);
let mut file = File::open(&path_buf)?;
let mut toml_string = String::new();
Expand Down
75 changes: 12 additions & 63 deletions src/errors.rs
Original file line number Diff line number Diff line change
@@ -1,67 +1,16 @@
//! Basic error handling mechanisms

use std::error::Error;
use std::fmt;

/// The result type for `GitJournal`
pub type GitJournalResult<T> = Result<T, Box<Error>>;

/// Concrete errors
struct GitJournalError {
description: String,
detail: Option<String>,
cause: Option<Box<Error + Send>>,
}

impl fmt::Display for GitJournalError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.description)?;
if let Some(ref s) = self.detail {
write!(f, ": {}", s)?;
}
Ok(())
}
}

impl fmt::Debug for GitJournalError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}

impl Error for GitJournalError {
fn description(&self) -> &str {
&self.description
}

#[cfg_attr(feature = "cargo-clippy", allow(let_and_return))]
fn cause(&self) -> Option<&Error> {
self.cause.as_ref().map(|c| {
let e: &Error = &**c;
e
})
use std::{io, num};
use {git2, log, term, toml};

error_chain! {
foreign_links {
Git(git2::Error) #[doc="A git error."];
Io(io::Error) #[doc="An I/O error."];
Log(log::ShutdownLoggerError) #[doc="A logger error error."];
Term(term::Error) #[doc="A terminal error."];
TomlDeser(toml::de::Error) #[doc="A toml deserialization error."];
ParseInt(num::ParseIntError) #[doc="A integer parsing error."];
TomlSer(toml::ser::Error) #[doc="A toml serialization error."];
}
}

/// Raise an internal error
pub fn error(error: &str, detail: &str) -> Box<Error> {
Box::new(GitJournalError {
description: error.to_string(),
detail: Some(detail.to_string()),
cause: None,
})
}

pub fn bail(error: &fmt::Display) -> Box<Error> {
Box::new(GitJournalError {
description: error.to_string(),
detail: None,
cause: None,
})
}

macro_rules! bail {
($($fmt:tt)*) => (
return Err(::errors::bail(&format_args!($($fmt)*)))
)
}
83 changes: 47 additions & 36 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ extern crate serde;
extern crate term;
extern crate toml;

#[macro_use]
extern crate error_chain;

#[macro_use]
extern crate serde_derive;

Expand All @@ -54,11 +57,11 @@ use rayon::prelude::*;
use toml::Value;

pub use config::Config;
pub use errors::{GitJournalResult, error};
use errors::*;
use parser::{Parser, ParsedTag, Tags};

#[macro_use]
mod errors;
pub mod errors;
mod parser;
pub mod config;

Expand Down Expand Up @@ -87,7 +90,7 @@ impl GitJournal {
/// When not providing a path with a valid git repository ('.git' folder or the initial parsing
/// of the git tags failed.
///
pub fn new(path: &str) -> GitJournalResult<Self> {
pub fn new(path: &str) -> Result<Self> {
// Setup the logger if not already set
if mowl::init_with_level(LogLevel::Info).is_err() {
warn!("Logger already set.");
Expand Down Expand Up @@ -120,7 +123,9 @@ impl GitJournal {
let name = name.ok_or_else(|| git2::Error::from_str("Could not receive tag name"))?;
let obj = repo.revparse_single(name)?;
if let Ok(tag) = obj.into_tag() {
let tag_name = tag.name().ok_or_else(|| git2::Error::from_str("Could not parse tag name"))?.to_owned();
let tag_name = tag.name()
.ok_or_else(|| git2::Error::from_str("Could not parse tag name"))?
.to_owned();
new_tags.push((tag.target_id(), tag_name));
}
}
Expand All @@ -144,11 +149,11 @@ impl GitJournal {

// Return the git journal object
Ok(GitJournal {
config: new_config,
parser: new_parser,
path: path_buf.to_str().unwrap_or("").to_owned(),
tags: new_tags,
})
config: new_config,
parser: new_parser,
path: path_buf.to_str().unwrap_or("").to_owned(),
tags: new_tags,
})
}

/// Does the setup on the target git repository.
Expand Down Expand Up @@ -208,7 +213,7 @@ impl GitJournal {
/// - When the writing of the default configuration fails.
/// - When installation of the commit message (preparation) hook fails.
///
pub fn setup(&self) -> GitJournalResult<()> {
pub fn setup(&self) -> Result<()> {
// Save the default config
let output_file = Config::new().save_default_config(&self.path)?;
info!("Defaults written to '{}' file.", output_file);
Expand All @@ -222,7 +227,7 @@ impl GitJournal {
Ok(())
}

fn install_git_hook(&self, name: &str, content: &str) -> GitJournalResult<()> {
fn install_git_hook(&self, name: &str, content: &str) -> Result<()> {
let mut hook_path = PathBuf::from(&self.path);
hook_path.push(".git/hooks");
hook_path.push(name);
Expand All @@ -232,7 +237,9 @@ impl GitJournal {
warn!("There is already a hook available in '{}'. Please verifiy \
the hook by hand after the installation.",
hook_path.display());
hook_file = OpenOptions::new().read(true).append(true).open(&hook_path)?;
hook_file = OpenOptions::new().read(true)
.append(true)
.open(&hook_path)?;
let mut hook_content = String::new();
hook_file.read_to_string(&mut hook_content)?;
if hook_content.contains(content) {
Expand All @@ -251,14 +258,14 @@ impl GitJournal {
}

#[cfg(unix)]
fn chmod(&self, path: &Path, perms: u32) -> GitJournalResult<()> {
fn chmod(&self, path: &Path, perms: u32) -> Result<()> {
use std::os::unix::prelude::PermissionsExt;
fs::set_permissions(path, fs::Permissions::from_mode(perms))?;
Ok(())
}

#[cfg(windows)]
fn chmod(&self, _path: &Path, _perms: u32) -> GitJournalResult<()> {
fn chmod(&self, _path: &Path, _perms: u32) -> Result<()> {
Ok(())
}

Expand All @@ -278,7 +285,7 @@ impl GitJournal {
/// # Errors
/// When the path is not available or writing the commit message fails.
///
pub fn prepare(&self, path: &str, commit_type: Option<&str>) -> GitJournalResult<()> {
pub fn prepare(&self, path: &str, commit_type: Option<&str>) -> Result<()> {
// If the message is not valid, assume a new commit and provide the template.
if let Err(error) = self.verify(path) {
// But if the message is provided via the cli with `-m`, then abort since
Expand All @@ -298,14 +305,14 @@ impl GitJournal {
let mut file = OpenOptions::new().write(true).open(path)?;
let mut old_msg_vec = commit_message.lines()
.filter_map(|line| if !line.is_empty() {
if line.starts_with('#') {
Some(line.to_owned())
} else {
Some("# ".to_owned() + line)
}
} else {
None
})
if line.starts_with('#') {
Some(line.to_owned())
} else {
Some("# ".to_owned() + line)
}
} else {
None
})
.collect::<Vec<_>>();
if !old_msg_vec.is_empty() {
old_msg_vec.insert(0, "# The provided commit message:".to_owned());
Expand Down Expand Up @@ -338,7 +345,7 @@ impl GitJournal {
/// # Errors
/// When the commit message is not valid due to RFC0001 or opening of the given file failed.
///
pub fn verify(&self, path: &str) -> GitJournalResult<()> {
pub fn verify(&self, path: &str) -> Result<()> {
// Open the file and read to string
let mut file = File::open(path)?;
let mut commit_message = String::new();
Expand Down Expand Up @@ -389,7 +396,7 @@ impl GitJournal {
max_tags_count: &u32,
all: &bool,
skip_unreleased: &bool)
-> GitJournalResult<()> {
-> Result<()> {

let repo = Repository::open(&self.path)?;
let mut revwalk = repo.revwalk()?;
Expand Down Expand Up @@ -427,9 +434,10 @@ impl GitJournal {
'revloop: for (index, id) in revwalk.enumerate() {
let oid = id?;
let commit = repo.find_commit(oid)?;
for tag in self.tags
.iter()
.filter(|tag| tag.0.as_bytes() == oid.as_bytes() && !tag.1.contains(tag_skip_pattern)) {
for tag in self.tags.iter().filter(|tag| {
tag.0.as_bytes() == oid.as_bytes() &&
!tag.1.contains(tag_skip_pattern)
}) {

// Parsing entries of the last tag done
if !current_tag.message_ids.is_empty() {
Expand Down Expand Up @@ -473,12 +481,13 @@ impl GitJournal {
}

// Process with the full CPU power
worker_vec.par_iter_mut().for_each(|&mut (ref message, ref oid, ref mut result)| match self.parser
.parse_commit_message(message, Some(*oid)) {
Ok(parsed_message) => {
*result = Some(parsed_message);
worker_vec.par_iter_mut().for_each(|&mut (ref message, ref oid, ref mut result)| {
match self.parser.parse_commit_message(message, Some(*oid)) {
Ok(parsed_message) => {
*result = Some(parsed_message);
}
Err(e) => warn!("Skipping commit: {}", e),
}
Err(e) => warn!("Skipping commit: {}", e),
});

// Assemble results together via the message_id
Expand Down Expand Up @@ -523,7 +532,7 @@ impl GitJournal {
/// # Errors
/// If the generation of the template was impossible.
///
pub fn generate_template(&self) -> GitJournalResult<()> {
pub fn generate_template(&self) -> Result<()> {
let mut tags = vec![parser::TOML_DEFAULT_KEY.to_owned()];

// Get all the tags
Expand Down Expand Up @@ -590,7 +599,7 @@ impl GitJournal {
/// # Errors
/// If some commit message could not be print.
///
pub fn print_log(&self, compact: bool, template: Option<&str>, output: Option<&str>) -> GitJournalResult<()> {
pub fn print_log(&self, compact: bool, template: Option<&str>, output: Option<&str>) -> Result<()> {

// Choose the template
let mut default_template = PathBuf::from(&self.path);
Expand Down Expand Up @@ -620,7 +629,9 @@ impl GitJournal {

// Print the log to the file if necessary
if let Some(output) = output {
let mut output_file = OpenOptions::new().create(true).append(true).open(output)?;
let mut output_file = OpenOptions::new().create(true)
.append(true)
.open(output)?;
output_file.write_all(&output_vec)?;
info!("Output written to '{}'.", output);
}
Expand Down

0 comments on commit ff3cf33

Please sign in to comment.