Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat globs can be relative for newglob and cli arg #79

Merged
merged 11 commits into from
May 31, 2019
17 changes: 15 additions & 2 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use crate::sort::SortOrder;
use clap::{App, Arg};
use glob::glob;
use std::env::current_dir;
use std::path::PathBuf;

/// Args contains the arguments that have been successfully parsed by the clap cli app
Expand All @@ -21,6 +22,8 @@ pub struct Args {
pub max_length: usize,
/// Start in fullscreen mode
pub fullscreen: bool,
/// New base directory defaults to std::env::current_dir
pub base_dir: PathBuf,
}

/// cli sets up the command line app and parses the arguments, using clap.
Expand Down Expand Up @@ -83,8 +86,17 @@ pub fn cli() -> Result<Args, String> {
Some(v) => v,
None => panic!("No value for paths!"),
};
let path_glob = crate::convert_to_globable(path_glob)?;
let glob_matches = glob(&path_glob).map_err(|e| e.to_string())?;
// find current directory so glob provided can be relative
let mut base_dir = match current_dir() {
Ok(c) => c,
Err(_) => PathBuf::new(),
};
let path_glob = crate::path_to_glob(&base_dir, path_glob)?;
// find new base directory
if let Ok(new_base_dir) = crate::new_base_dir(&path_glob) {
base_dir = new_base_dir;
}
let glob_matches = glob(&path_glob.to_string_lossy()).map_err(|e| e.to_string())?;
for path in glob_matches {
match path {
Ok(p) => push_image_path(&mut files, p),
Expand Down Expand Up @@ -117,6 +129,7 @@ pub fn cli() -> Result<Args, String> {
reverse,
max_length,
fullscreen,
base_dir,
})
}

Expand Down
85 changes: 76 additions & 9 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,85 @@ pub mod sort;
pub mod ui;

use shellexpand::full;
use std::fs::canonicalize;
use std::path::PathBuf;

/// Converts the provided path by user to a path that can be glob'd
/// Converts the provided path by user to a path that can be glob'd, note this function takes the
/// current_directory in order to handle relative paths
/// Paths are normalized removing ".." and "."
/// Environment variables, like ~ and $HOME, are expanded
/// On Unix escaped spaces are removed for example: folder\ path -> folder path
/// Directories are changed from /home/etc to /home/etc/*
pub fn convert_to_globable(path: &str) -> Result<String, String> {
let expanded_path = full(path).map_err(|e| format!("\"{}\": {}", e.var_name, e.cause))?;
// remove escaped spaces
let absolute_path = String::from(expanded_path).replace(r"\ ", " ");
pub fn path_to_glob(current_dir: &PathBuf, path: &str) -> Result<PathBuf, String> {
const ESCAPED_SPACE: &str = r"\ ";
const SPACE: &str = " ";

let mut expanded_path = match full(path) {
Ok(path) => {
let mut path_str = path.to_string();
// remove escaped spaces for Unix
if cfg!(unix) {
path_str = path_str.replace(ESCAPED_SPACE, SPACE);
}
PathBuf::from(&path_str)
}
Err(e) => return Err(format!("\"{}\": {}", e.var_name, e.cause)),
};

if expanded_path.is_relative() {
expanded_path = current_dir.join(expanded_path);
}
// normalize path
let mut expanded_path = match normalize_path(expanded_path) {
Ok(path) => path,
Err(e) => return Err(e.to_string()),
};
// If path is a dir, add /* to glob
let mut pathbuf = PathBuf::from(&absolute_path);
if pathbuf.is_dir() {
pathbuf = pathbuf.join("*");
if expanded_path.is_dir() {
expanded_path.push("*");
}
Ok(expanded_path)
}

/// Normalizes paths removing "." and ".."
/// This is a helper function to path_to_glob
fn normalize_path(path: PathBuf) -> Result<PathBuf, String> {
if let Ok(path) = canonicalize(&path) {
Ok(path)
} else {
// if canonicalize failed it's most likely because the path contains '*'s
// remove those and see if it is successful, and add them back on if it is
use std::ffi::OsStr;

let mut stack: Vec<&OsStr> = Vec::new();
let mut invalid_expanded_path = path.clone();
for parent in path.ancestors() {
if let Some(child) = parent.file_name() {
if invalid_expanded_path.exists() {
break;
}
stack.push(child);
} else {
// parent is '..' remove it
invalid_expanded_path.pop();
}
invalid_expanded_path.pop();
}
let mut new_path = canonicalize(&invalid_expanded_path).map_err(|e| e.to_string())?;
while let Some(sub_path) = stack.pop() {
new_path.push(sub_path);
}
Ok(new_path)
}
}

/// Takes in the output of path_to_glob and finds the closest parent in that path
/// This is the new base directory
pub fn new_base_dir(path: &PathBuf) -> Result<PathBuf, String> {
for parent in path.ancestors() {
if parent.is_dir() {
return Ok(parent.to_path_buf());
}
}
Ok(pathbuf.to_string_lossy().to_string())
Err(format!("Failed to get new base directory"))
NickHackman marked this conversation as resolved.
Show resolved Hide resolved
}
56 changes: 22 additions & 34 deletions src/program/command_mode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,11 @@ impl FromStr for Commands {

/// Globs the passed path, returning an error if no images are in that path, glob::glob fails, or
/// path is unexpected
fn glob_path(path: &str) -> Result<Vec<PathBuf>, String> {
fn glob_path(path: &PathBuf) -> Result<Vec<PathBuf>, String> {
use crate::cli::push_image_path;

let mut new_images: Vec<PathBuf> = Vec::new();
let globable_path = crate::convert_to_globable(path)?;
let path_matches = glob::glob(&globable_path).map_err(|e| e.to_string())?;
let path_matches = glob::glob(&path.to_string_lossy()).map_err(|e| e.to_string())?;
for path in path_matches {
match path {
Ok(p) => {
Expand All @@ -86,10 +85,8 @@ fn glob_path(path: &str) -> Result<Vec<PathBuf>, String> {
}
}
}
if path.find(' ').is_some() && new_images.is_empty() {
return Err("Newglob accepts only one argument, but more were provided".to_string());
} else if new_images.is_empty() {
let err_msg = format!("Path \"{}\" had no images", path);
if new_images.is_empty() {
let err_msg = format!("Path \"{}\" had no images", path.display());
return Err(err_msg);
}
Ok(new_images)
Expand Down Expand Up @@ -117,28 +114,6 @@ fn parse_user_input(input: String) -> Result<(Commands, String), String> {
Ok((command, arguments))
}

/// When provided a newglob set current_dir to the nearest directory
fn find_new_base_dir(new_path: &str) -> Option<PathBuf> {
let expanded_path = match full(new_path) {
Ok(path) => path,
Err(_e) => {
return None;
}
};
let pathbuf = PathBuf::from(&expanded_path.to_string());
if pathbuf.is_dir() {
Some(pathbuf)
} else {
// Provided newglob is a path to an image or a glob
for parent in pathbuf.ancestors() {
if parent.is_dir() {
return Some(parent.to_path_buf());
}
}
None
}
}

impl<'a> Program<'a> {
/// User input is taken in and displayed on infobar, cmd is either '/' or ':'
/// Returning empty string signifies switching modes back to normal mode
Expand Down Expand Up @@ -177,7 +152,14 @@ impl<'a> Program<'a> {

/// Takes a path to a directory or glob and adds these images to self.paths.images
fn newglob(&mut self, path_to_newglob: &str) {
let new_images = match glob_path(path_to_newglob) {
let path = match crate::path_to_glob(&self.paths.base_dir, path_to_newglob) {
Ok(path) => path,
Err(e) => {
self.ui_state.mode = Mode::Error(e.to_string());
return;
}
};
let new_images = match glob_path(&path) {
Ok(new_images) => new_images,
Err(e) => {
self.ui_state.mode = Mode::Error(e.to_string());
Expand All @@ -191,8 +173,8 @@ impl<'a> Program<'a> {
};
self.paths.images = new_images;
// Set current directory to new one
let new_base_dir = find_new_base_dir(&path_to_newglob.replace("\\ ", " "));
if let Some(base_dir) = new_base_dir {
let new_base_dir = crate::new_base_dir(&path);
if let Ok(base_dir) = new_base_dir {
self.paths.base_dir = base_dir
}
self.sorter.sort(&mut self.paths.images);
Expand Down Expand Up @@ -340,8 +322,14 @@ impl<'a> Program<'a> {
}
match full(&arguments) {
Ok(path) => {
self.paths.dest_folder =
PathBuf::from(path.to_string().replace("\\ ", " "));
const ESCAPED_SPACE: &str = "\\ ";
const SPACE: &str = " ";

let mut path = path.to_string();
if cfg!(unix) {
path = path.replace(ESCAPED_SPACE, SPACE);
}
self.paths.dest_folder = PathBuf::from(path);
}
Err(e) => {
self.ui_state.mode =
Expand Down
5 changes: 1 addition & 4 deletions src/program/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ impl<'a> Program<'a> {
let reverse = args.reverse;
let sort_order = args.sort_order;
let max_length = args.max_length;
let base_dir = args.base_dir;

let max_viewable = if max_length > 0 && max_length <= images.len() {
max_length
Expand All @@ -62,10 +63,6 @@ impl<'a> Program<'a> {
let sorter = Sorter::new(sort_order, reverse);
sorter.sort(&mut images);

let base_dir = match std::env::current_dir() {
Ok(c) => c,
Err(_) => PathBuf::new(),
};
let font_bytes = include_bytes!("../../resources/Roboto-Medium.ttf");
let font_bytes = match RWops::from_bytes(font_bytes) {
Ok(b) => b,
Expand Down