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
60 changes: 60 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ glob = "0.3"
fs_extra = "1.1"
natord = "1.0.9"
shellexpand = "1.0"
lazy_static = "1.3.0"
regex = "1"

[dependencies.sdl2]
version = "0.32"
Expand Down
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
92 changes: 83 additions & 9 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@

#[macro_use]
extern crate clap;
extern crate regex;
#[macro_use]
extern crate lazy_static;

pub mod cli;
pub mod infobar;
Expand All @@ -23,19 +26,90 @@ pub mod screen;
pub mod sort;
pub mod ui;

use regex::Regex;
use shellexpand::full;
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"\ ", " ");
/// Symlinks are followed
pub fn path_to_glob(current_dir: &PathBuf, path: &str) -> Result<PathBuf, String> {
const GLOB: &str = "*";

let mut expanded_path = match full(path) {
Ok(path) => {
let mut path_str = path.to_string();
// remove escaped characters for Unix
if cfg!(unix) {
lazy_static! {
static ref REGEX_REMOVE_ESCAPED_CHARS: Regex = match Regex::new(r"\\(.)") {
Ok(regex) => regex,
Err(e) => panic!("Logic Error: {}", e),
};
}
path_str = REGEX_REMOVE_ESCAPED_CHARS
.replace_all(&path_str, "$1")
.to_string();
}
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);
}
let mut expanded_path = normalize_path(expanded_path);
// 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(GLOB);
}
Ok(expanded_path)
}

/// Normalizes paths removing "." and ".."
/// This follows symlinks like std::fs::canonicalize
/// This is a helper function to path_to_glob
///
///
/// This implementation is preferred to using std::fs::canonicalize due to canonicalize requiring
/// the path to exist, causing all globbing to fail and having to remove the glob, normalize then add it back on
fn normalize_path(path: PathBuf) -> PathBuf {
use std::fs::read_link;
use std::path::Component;

let mut normalized = PathBuf::new();
for component in path.components() {
match component {
Component::ParentDir => {
normalized.pop();
}
Component::CurDir => continue,
_ => {
normalized.push(component);
if let Ok(actual_path) = read_link(&normalized) {
normalized.set_file_name(actual_path);
}
}
}
}
normalized
}

/// 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 for path: \"{}\"",
path.display()
))
}
Loading