Extract errors into one thing and return a result more often

This commit is contained in:
Félix Saparelli 2018-09-08 23:51:44 +12:00
parent a6163cc599
commit 2c5c145042
7 changed files with 115 additions and 102 deletions

62
src/error.rs Normal file
View File

@ -0,0 +1,62 @@
use globset;
use notify;
use std::{error::Error as StdError, fmt, io};
pub type Result<T> = ::std::result::Result<T, Error>;
pub enum Error {
Canonicalization(String, io::Error),
Glob(globset::Error),
Io(io::Error),
Notify(notify::Error),
}
impl StdError for Error {}
impl From<globset::Error> for Error {
fn from(err: globset::Error) -> Self {
Error::Glob(err)
}
}
impl From<io::Error> for Error {
fn from(err: io::Error) -> Self {
Error::Io(err)
}
}
impl From<notify::Error> for Error {
fn from(err: notify::Error) -> Self {
match err {
notify::Error::Io(err) => Error::Io(err),
other => Error::Notify(other),
}
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"{} error: {}",
match self {
Error::Canonicalization(_, _) => "Path",
Error::Glob(_) => "Globset",
Error::Io(_) => "I/O",
Error::Notify(_) => "Notify",
},
match self {
Error::Canonicalization(path, err) => {
format!("couldn't canonicalize '{}':\n{}", path, err)
}
err => format!("{}", err),
}
)
}
}
impl fmt::Debug for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}

View File

@ -113,9 +113,9 @@ impl Gitignore {
impl GitignoreFile {
pub fn new(path: &Path) -> Result<GitignoreFile, Error> {
let mut file = try!(fs::File::open(path));
let mut file = fs::File::open(path)?;
let mut contents = String::new();
try!(file.read_to_string(&mut contents));
file.read_to_string(&mut contents)?;
let lines = contents.lines().collect();
let root = path.parent().unwrap();
@ -138,14 +138,14 @@ impl GitignoreFile {
pat = pat + "/**";
}
let glob = try!(GlobBuilder::new(&pat).literal_separator(true).build());
let glob = GlobBuilder::new(&pat).literal_separator(true).build()?;
builder.add(glob);
patterns.push(p);
}
Ok(GitignoreFile {
set: try!(builder.build()),
set: builder.build()?,
patterns: patterns,
root: root.to_owned(),
})

View File

@ -19,6 +19,7 @@ extern crate winapi;
extern crate mktemp;
pub mod cli;
pub mod error;
mod gitignore;
mod notification_filter;
mod pathop;

View File

@ -1,6 +1,6 @@
extern crate watchexec;
use watchexec::{cli, run};
use watchexec::{cli, error, run};
fn main() -> run::Result<()> {
fn main() -> error::Result<()> {
run(cli::get_args())
}

View File

@ -1,36 +1,26 @@
extern crate glob;
use std::io;
use std::path::Path;
use globset;
use error;
use gitignore::Gitignore;
use globset::{Glob, GlobSet, GlobSetBuilder};
use gitignore;
use std::path::Path;
pub struct NotificationFilter {
filters: GlobSet,
filter_count: usize,
ignores: GlobSet,
ignore_files: gitignore::Gitignore,
}
#[derive(Debug)]
pub enum Error {
Glob(globset::Error),
Io(io::Error),
ignore_files: Gitignore,
}
impl NotificationFilter {
pub fn new(
filters: Vec<String>,
ignores: Vec<String>,
ignore_files: gitignore::Gitignore,
) -> Result<NotificationFilter, Error> {
ignore_files: Gitignore,
) -> error::Result<NotificationFilter> {
let mut filter_set_builder = GlobSetBuilder::new();
for f in &filters {
filter_set_builder.add(try!(Glob::new(f)));
filter_set_builder.add(Glob::new(f)?);
debug!("Adding filter: \"{}\"", f);
}
@ -41,18 +31,14 @@ impl NotificationFilter {
ignore_path = Path::new("**").join(&ignore_path);
}
let pattern = ignore_path.to_str().unwrap();
ignore_set_builder.add(try!(Glob::new(pattern)));
ignore_set_builder.add(Glob::new(pattern)?);
debug!("Adding ignore: \"{}\"", pattern);
}
let filter_set = try!(filter_set_builder.build());
let ignore_set = try!(ignore_set_builder.build());
Ok(NotificationFilter {
filters: filter_set,
filters: filter_set_builder.build()?,
filter_count: filters.len(),
ignores: ignore_set,
ignores: ignore_set_builder.build()?,
ignore_files: ignore_files,
})
}
@ -80,18 +66,6 @@ impl NotificationFilter {
}
}
impl From<io::Error> for Error {
fn from(err: io::Error) -> Error {
Error::Io(err)
}
}
impl From<globset::Error> for Error {
fn from(err: globset::Error) -> Error {
Error::Glob(err)
}
}
#[cfg(test)]
mod tests {
use super::NotificationFilter;

View File

@ -1,23 +1,22 @@
use std::collections::HashMap;
use std::fs::canonicalize;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::sync::mpsc::{channel, Receiver};
use std::sync::{Arc, RwLock};
use std::time::Duration;
use cli;
use env_logger;
use error::{Error, Result};
use gitignore;
use log;
use notification_filter::NotificationFilter;
use notify::Error;
use notify;
use pathop::PathOp;
use process::{self, Process};
use signal::{self, Signal};
use watcher::{Event, Watcher};
pub type Result<T> = ::std::result::Result<T, Box<::std::error::Error>>;
fn init_logger(debug: bool) {
let mut log_builder = env_logger::Builder::new();
let level = if debug {
@ -32,21 +31,6 @@ fn init_logger(debug: bool) {
.init();
}
#[cfg(target_os = "linux")]
fn should_switch_to_poll(e: &Error) -> bool {
use nix::libc;
match e {
&Error::Io(ref e) if e.raw_os_error() == Some(libc::ENOSPC) => true,
_ => false,
}
}
#[cfg(not(target_os = "linux"))]
fn should_switch_to_poll(_: &Error) -> bool {
false
}
pub fn run(args: cli::Args) -> Result<()> {
let child_process: Arc<RwLock<Option<Process>>> = Arc::new(RwLock::new(None));
let weak_child = Arc::downgrade(&child_process);
@ -68,48 +52,39 @@ pub fn run(args: cli::Args) -> Result<()> {
init_logger(args.debug);
let paths: Result<Vec<PathBuf>> = args
.paths
.iter()
.map(|p| {
Ok(Path::new(&p)
.canonicalize()
.map_err(|e| format!("Unable to canonicalize path: \"{}\", {}", p, e))?
.to_owned())
})
.collect();
let paths = paths?;
let mut paths = vec![];
for path in args.paths {
paths.push(canonicalize(&path).map_err(|e| Error::Canonicalization(path, e))?);
}
let gitignore = if !args.no_vcs_ignore {
gitignore::load(&paths)
} else {
gitignore::load(&[])
};
let filter = NotificationFilter::new(args.filters, args.ignores, gitignore)
.expect("unable to create notification filter");
let gitignore = gitignore::load(if args.no_vcs_ignore { &[] } else { &paths });
let filter = NotificationFilter::new(args.filters, args.ignores, gitignore)?;
let (tx, rx) = channel();
let watcher = match Watcher::new(tx.clone(), &paths, args.poll, args.poll_interval) {
Ok(watcher) => watcher,
Err(ref e) if !args.poll && should_switch_to_poll(e) => {
warn!(
"System notification limit is too small, \
falling back to polling mode."
);
if cfg!(target_os = "linux") {
warn!(
"For better performance increase system limit: \n \
sysctl fs.inotify.max_user_watches=524288"
);
let (poll, poll_interval) = (args.poll, args.poll_interval).clone();
let watcher = Watcher::new(tx.clone(), &paths, args.poll, args.poll_interval).or_else(|err| {
if poll {
return Err(err);
}
#[cfg(target_os = "linux")]
{
use nix::libc;
let mut fallback = false;
if let notify::Error::Io(ref e) = err {
if e.raw_os_error() == Some(libc::ENOSPC) {
warn!("System notification limit is too small, falling back to polling mode. For better performance increase system limit:\n\tsysctl fs.inotify.max_user_watches=524288");
fallback = true;
}
}
if fallback {
return Watcher::new(tx, &paths, true, poll_interval);
}
Watcher::new(tx, &paths, true, args.poll_interval)
.expect("polling watcher should always work")
}
Err(e) => {
panic!("Error setting up watcher: {}", e);
}
};
Err(err)
})?;
if watcher.is_polling() {
warn!("Polling for changes every {} ms", args.poll_interval);
@ -205,6 +180,7 @@ pub fn run(args: cli::Args) -> Result<()> {
break;
}
}
Ok(())
}

View File

@ -31,17 +31,17 @@ impl Watcher {
use notify::Watcher;
let imp = if poll {
let mut watcher = try!(PollWatcher::with_delay_ms(tx, interval_ms));
let mut watcher = PollWatcher::with_delay_ms(tx, interval_ms)?;
for path in paths {
try!(watcher.watch(path, RecursiveMode::Recursive));
watcher.watch(path, RecursiveMode::Recursive)?;
debug!("Watching {:?}", path);
}
WatcherImpl::Poll(watcher)
} else {
let mut watcher = try!(raw_watcher(tx));
let mut watcher = raw_watcher(tx)?;
for path in paths {
try!(watcher.watch(path, RecursiveMode::Recursive));
watcher.watch(path, RecursiveMode::Recursive)?;
debug!("Watching {:?}", path);
}