From ca5960cf4b84f5db8299159efd3ed43a021318b1 Mon Sep 17 00:00:00 2001 From: Kevin K Date: Mon, 13 Jul 2015 16:27:04 -0400 Subject: [PATCH] wip: implementing #58 and better errors --- src/bin/clog.rs | 28 +++++-------- src/clog.rs | 105 +++++++++++++++++++++++++++++++++++------------- src/error.rs | 59 +++++++++++++++++++++++++++ 3 files changed, 148 insertions(+), 44 deletions(-) create mode 100644 src/error.rs diff --git a/src/bin/clog.rs b/src/bin/clog.rs index 06bf97f..1411f9d 100644 --- a/src/bin/clog.rs +++ b/src/bin/clog.rs @@ -55,24 +55,18 @@ project directory (i.e. /myproject/.clog.toml) you do not need to use --work-tre let start_nsec = time::get_time().nsec; - let clog = Clog::from_matches(&matches).unwrap_or_else(|e| { println!("{}",e); std::process::exit(1); }); + let clog = Clog::from_matches(&matches).unwrap_or_else(|e| { wlnerr!("{}",e); ::std::process::exit(1); }); - let sm = SectionMap::from_commits(clog.get_commits()); + if let Some(file) = clog.changelog { + let mut contents = String::new(); + clog.write_changelog_to(file).unwrap_or_else(|e| { wlnerr!("{}",e); ::std::process::exit(1); }); - let mut contents = String::new(); - - File::open(&Path::new(&clog.changelog[..])).map(|mut f| f.read_to_string(&mut contents).ok()).ok(); - - let mut file = File::create(&Path::new(&clog.changelog[..])).ok().unwrap(); - let mut writer = LogWriter::new(&mut file, &clog); - - writer.write_header().ok().expect("failed to write header"); - for (sec, secmap) in sm.sections { - writer.write_section(&sec[..], &secmap.iter().collect::>()).ok().expect(&format!("failed to write {}", sec)[..]); + let end_nsec = time::get_time().nsec; + let elapsed_mssec = (end_nsec - start_nsec) / 1000000; + println!("changelog written. (took {} ms)", elapsed_mssec); + } else { + let out = stdout(); + let out_buf = BufWriter::new(out.lock()); + clog.write_changelog_to(out_buf).unwrap_or_else(|e| { wlnerr!("{}",e); ::std::process::exit(1); }); } - writer.write(&contents[..]).ok().expect("failed to write contents"); - - let end_nsec = time::get_time().nsec; - let elapsed_mssec = (end_nsec - start_nsec) / 1000000; - println!("changelog updated. (took {} ms)", elapsed_mssec); } diff --git a/src/clog.rs b/src/clog.rs index 55e858d..6b7ca4e 100644 --- a/src/clog.rs +++ b/src/clog.rs @@ -15,9 +15,40 @@ use semver; use git::{Commits, Commit}; use log_writer::LogWriter; use sectionmap::SectionMap; +use error::Error; use CLOG_CONFIG_FILE; +/// Convienience type for returning results of building a `Clog` struct +/// +/// # Example +/// +/// ```no_run +/// # use clog::Clog; +/// let clog = Clog::new().unwrap_or_else(|e| { +/// // Prints the error and exits appropriately +/// e.exit(); +/// }); +/// ``` +pub type BuilderResult = Result; + +/// Convienience type for returning results of writing a changelog with a `Clog` +/// struct +/// +/// # Example +/// +/// ```no_run +/// # use clog::Clog; +/// # use std::io; +/// let out = io::stdout(); +/// let clog = Clog::new(); +/// clog.write_changelog_with(out.lock()).unwrap_or_else(|e| { +/// // Prints the error and exits appropriately +/// e.exit(); +/// }); +/// ``` +pub type WriterResult = Result<(), Error>; + /// Determines the link style used in commit links. Defaults to `LinksStyle::Github` /// /// # Example @@ -105,7 +136,7 @@ pub struct Clog { /// Where to stop looking for commits using a hash (or short hash). (Defaults to `HEAD`) pub to: String, /// The file to use as the changelog. (Defaults to `changelog.md`) - pub changelog: String, + pub changelog: Option, /// Maps out the sections and aliases used to trigger those sections. The keys are the section /// name, and the values are an array of aliases. pub section_map: HashMap>, @@ -158,17 +189,6 @@ impl fmt::Debug for Clog { } } -/// Convienience type for returning results of building a `Clog` struct -/// -/// # Example -/// ```no_run -/// # use clog::Clog; -/// let clog = Clog::new().unwrap_or_else(|e| { -/// println!("Error initializing: {}", e); -/// std::process::exit(1); -/// }); -/// ``` -pub type ClogResult = Result>; impl Clog { fn _new() -> Clog { @@ -196,7 +216,7 @@ impl Clog { subtitle: "".to_owned(), from: "".to_owned(), to: "HEAD".to_owned(), - changelog: "changelog.md".to_owned(), + changelog: None, section_map: sections, git_dir: None, git_work_tree: None, @@ -216,7 +236,7 @@ impl Clog { /// std::process::exit(1); /// }); /// ``` - pub fn new() -> ClogResult { + pub fn new() -> BuilderResult { debugln!("Creating public default clog"); Clog::from_file(CLOG_CONFIG_FILE) } @@ -234,7 +254,7 @@ impl Clog { /// std::process::exit(1); /// }); /// ``` - pub fn with_all>(git_dir: P, work_tree: P, cfg_file: P) -> ClogResult { + pub fn with_all>(git_dir: P, work_tree: P, cfg_file: P) -> BuilderResult { debugln!("Creating clog with \n\tgit_dir: {:?}\n\twork_tree: {:?}\n\tcfg_file: {:?}", git_dir.as_ref(), work_tree.as_ref(), @@ -259,7 +279,7 @@ impl Clog { /// std::process::exit(1); /// }); /// ``` - pub fn with_dir_and_file>(dir: P, cfg_file: P) -> ClogResult { + pub fn with_dir_and_file>(dir: P, cfg_file: P) -> BuilderResult { debugln!("Creating clog with \n\tdir: {:?}\n\tcfg_file: {:?}", dir.as_ref(), cfg_file.as_ref()); @@ -302,7 +322,7 @@ impl Clog { /// std::process::exit(1); /// }); /// ``` - pub fn with_dir>(dir: P) -> ClogResult { + pub fn with_dir>(dir: P) -> BuilderResult { debugln!("Creating clog with \n\tdir: {:?}", dir.as_ref()); let clog = try!(Clog::_with_dir(dir)); clog.try_config_file(Path::new(CLOG_CONFIG_FILE)) @@ -322,7 +342,7 @@ impl Clog { /// std::process::exit(1); /// }); /// ``` - pub fn with_dirs>(git_dir: P, work_tree: P) -> ClogResult { + pub fn with_dirs>(git_dir: P, work_tree: P) -> BuilderResult { debugln!("Creating clog with \n\tgit_dir: {:?}\n\twork_tree: {:?}", git_dir.as_ref(), work_tree.as_ref()); @@ -347,14 +367,14 @@ impl Clog { /// std::process::exit(1); /// }); /// ``` - pub fn from_file>(file: P) -> ClogResult { + pub fn from_file>(file: P) -> BuilderResult { debugln!("Creating clog with \n\tfile: {:?}", file.as_ref()); // Determine if the cfg_file was relative or not let cfg_file = if file.as_ref().is_relative() { debugln!("file is relative"); let cwd = match env::current_dir() { Ok(d) => d, - Err(e) => return Err(Box::new(e)), + Err(e) => return Err(Error::CurrentDirErr(e)), }; Path::new(&cwd).join(file.as_ref()) } else { @@ -368,7 +388,7 @@ impl Clog { Clog::with_dir_and_file(dir, cfg_file) } - fn try_config_file(mut self, cfg_file: &Path) -> ClogResult { + fn try_config_file(mut self, cfg_file: &Path) -> BuilderResult { debugln!("Trying to use config file: {:?}", cfg_file); let mut toml_from_latest = None; let mut toml_repo = None; @@ -381,7 +401,7 @@ impl Clog { let mut toml_s = String::with_capacity(100); if let Err(e) = toml_f.read_to_string(&mut toml_s) { - return Err(Box::new(e)) + return Err(Error::TomlReadErr(e)); } toml_s.shrink_to_fit(); @@ -391,14 +411,14 @@ impl Clog { let toml_table = match toml.parse() { Some(table) => table, None => { - return Err(Box::new(format!("Error parsing file: {}\n\nPlease check the format or specify the options manually", cfg_file.to_str().unwrap_or("UNABLE TO DISPLAY")))) + return Err(Error::ConfigParseErr(cfg_file.to_str().unwrap_or("UNABLE TO DISPLAY"))); } }; let clog_table = match toml_table.get("clog") { Some(table) => table, None => { - return Err(Box::new(format!("Error parsing file {}\n\nPlease check the format or specify the options manually", cfg_file.to_str().unwrap_or("UNABLE TO DISPLAY")))) + return Err(Error::ConfigFormatErr(cfg_file.to_str().unwrap_or("UNABLE TO DISPLAY"))); } }; @@ -459,7 +479,7 @@ impl Clog { } if let Some(outfile) = toml_outfile { - self.changelog = outfile; + self.changelog = Some(outfile); } debugln!("Returning clog:\n{:?}", self); @@ -577,7 +597,7 @@ impl Clog { } if let Some(file) = matches.value_of("outfile") { - clog.changelog = file.to_owned(); + clog.changelog = Some(file.to_owned()); } debugln!("Returning clog:\n{:?}", clog); @@ -746,7 +766,7 @@ impl Clog { /// clog.changelog("/myproject/my_changelog.md"); /// ``` pub fn changelog>(&mut self, c: S) -> &mut Clog { - self.changelog = c.into(); + self.changelog = Some(c.into()); self } @@ -1021,6 +1041,37 @@ impl Clog { writer.write(&contents[..]).ok().expect("failed to write contents"); } + /// Writes the changelog to a specified file, and prepends new commits if file exists, or + /// creates the file if it doesn't + /// + /// # Example + /// ```no_run + /// # use clog::Clog; + /// let mut clog = Clog::new().unwrap_or_else(|e| { + /// println!("Error initializing: {}", e); + /// std::process::exit(1); + /// }); + /// + /// clog.write_changelog_to("/myproject/new_changelog.md"); + /// ``` + pub fn write_changelog_with(&self, w: W) -> Result<(),Box> + where W: BufWriter { + let sm = SectionMap::from_commits(self.get_commits()); + + let mut contents = String::new(); + + File::open(cl.as_ref()).map(|mut f| f.read_to_string(&mut contents).ok()).ok(); + + // let mut file = File::create(cl.as_ref()).ok().unwrap(); + let mut writer = LogWriter::new(&mut w, self); + + writer.write_header().ok().expect("failed to write header"); + for (sec, secmap) in sm.sections { + writer.write_section(&sec[..], &secmap.iter().collect::>()).ok().expect(&format!("failed to write {}", sec)[..]); + } + writer.write(&contents[..]).ok().expect("failed to write contents"); + } + /// Writes the changelog to the default location and file or wherever was specified by the TOML /// or configuration options. `Clog` prepends new commits if file exists, or /// creates the file if it doesn't. diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..7af0cda --- /dev/null +++ b/src/error.rs @@ -0,0 +1,59 @@ +use std::error::Error as StdError; +use std::fmt; + +#[derive(Debug)] +pub enum Error { + ConfigParseErr(String), + ConfigFormatErr(String), + CurrentDirErr(StdError), + TomlReadErr(StdError), + +} + + +// Shamelessly taken and adopted from https://github.com/BurntSushi :) +impl Error { + /// Return whether this was a fatal error or not. + pub fn fatal(&self) -> bool { + match *self { + + } + } + + /// Print this error and immediately exit the program. + /// + /// If the error is non-fatal then the error is printed to stdout and the + /// exit status will be `0`. Otherwise, when the error is fatal, the error + /// is printed to stderr and the exit status will be `1`. + pub fn exit(&self) -> ! { + if self.fatal() { + wlnerr!("{}\n", self); + ::std::process::exit(1) + } else { + println!("{}", self); + ::std::process::exit(0) + } + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + + } + } +} + +impl StdError for Error { + fn description(&self) -> &str { + match *self { + + } + } + + fn cause(&self) -> Option<&StdError> { + match *self { + + } + } +}