summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorCanop <cano.petrole@gmail.com>2020-11-02 17:08:44 +0100
committerCanop <cano.petrole@gmail.com>2020-11-02 17:08:44 +0100
commitcf6eab4a8ad7f1cd8881a9f4203bb8aaddb20e28 (patch)
tree341004a5d1c4d1bec2f2b77c5f0398e60a556b95
parentc24285aab79e3155d26f9a39b0a7efca0c41fd11 (diff)
display date and size on symlinks
-rw-r--r--src/browser/browser_state.rs3
-rw-r--r--src/command/completion.rs3
-rw-r--r--src/file_sum/sum_computation.rs2
-rw-r--r--src/git/ignore.rs3
-rw-r--r--src/lib.rs1
-rw-r--r--src/path.rs161
-rw-r--r--src/path/anchor.rs (renamed from src/path_anchor.rs)0
-rw-r--r--src/path/closest.rs20
-rw-r--r--src/path/escape.rs16
-rw-r--r--src/path/from.rs69
-rw-r--r--src/path/mod.rs13
-rw-r--r--src/path/normalize.rs64
-rw-r--r--src/tree/tree.rs7
-rw-r--r--src/verb/internal_focus.rs3
-rw-r--r--src/verb/invocation_parser.rs2
-rw-r--r--src/verb/verb.rs3
16 files changed, 195 insertions, 175 deletions
diff --git a/src/browser/browser_state.rs b/src/browser/browser_state.rs
index 61a1591..080452e 100644
--- a/src/browser/browser_state.rs
+++ b/src/browser/browser_state.rs
@@ -7,8 +7,7 @@ use {
flag::Flag,
git,
pattern::*,
- path,
- path_anchor::PathAnchor,
+ path::{self, PathAnchor},
print,
skin::PanelSkin,
task_sync::Dam,
diff --git a/src/command/completion.rs b/src/command/completion.rs
index 709765d..bf647a2 100644
--- a/src/command/completion.rs
+++ b/src/command/completion.rs
@@ -6,8 +6,7 @@ use {
AppContext,
Selection,
},
- path,
- path_anchor::PathAnchor,
+ path::{self, PathAnchor},
verb::PrefixSearchResult,
},
std::io,
diff --git a/src/file_sum/sum_computation.rs b/src/file_sum/sum_computation.rs
index 343781d..f3e3cbe 100644
--- a/src/file_sum/sum_computation.rs
+++ b/src/file_sum/sum_computation.rs
@@ -155,7 +155,7 @@ pub fn compute_dir_sum(path: &Path, dam: &Dam) -> Option<FileSum> {
/// compute the sum for a regular file (not a folder)
pub fn compute_file_sum(path: &Path) -> FileSum {
- match fs::metadata(path) {
+ match fs::symlink_metadata(path) {
Ok(md) => {
let seconds = extract_seconds(&md);
diff --git a/src/git/ignore.rs b/src/git/ignore.rs
index e10e77b..7c596bf 100644
--- a/src/git/ignore.rs
+++ b/src/git/ignore.rs
@@ -26,6 +26,7 @@ struct GitIgnoreRule {
}
impl GitIgnoreRule {
+ /// parse a line of a .gitignore file
fn from(line: &str, dir: &Path) -> Option<GitIgnoreRule> {
if line.starts_with('#') {
return None; // comment line
@@ -48,7 +49,7 @@ impl GitIgnoreRule {
}
if let Ok(pattern) = glob::Pattern::new(&p) {
let pattern_options = glob::MatchOptions {
- case_sensitive: true, // not really sure about this one
+ case_sensitive: true,
require_literal_leading_dot: false,
require_literal_separator: has_separator,
};
diff --git a/src/lib.rs b/src/lib.rs
index 0def338..4363c72 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -34,7 +34,6 @@ pub mod keys;
pub mod image;
pub mod launchable;
pub mod path;
-pub mod path_anchor;
pub mod pattern;
pub mod permissions;
pub mod preview;
diff --git a/src/path.rs b/src/path.rs
deleted file mode 100644
index f1e2d63..0000000
--- a/src/path.rs
+++ /dev/null
@@ -1,161 +0,0 @@
-use {
- crate::{
- path_anchor::PathAnchor,
- },
- directories::UserDirs,
- regex::{self, Captures},
- std::{
- collections::HashMap,
- path::{Component, Path, PathBuf},
- },
-};
-
-/// build a usable path from a user input which may be absolute
-/// (if it starts with / or ~) or relative to the supplied base_dir.
-/// (we might want to try detect windows drives in the future, too)
-///
-pub fn path_from<P: AsRef<Path>>(base_dir: P, anchor: PathAnchor, input: &str) -> PathBuf {
- let tilde = regex!(r"^~(/|$)");
- if input.starts_with('/') {
- // if the input starts with a `/`, we use it as is
- input.into()
- } else if tilde.is_match(input) {
- // if the input starts with `~` as first token, we replace
- // this `~` with the user home directory
- PathBuf::from(
- &*tilde
- .replace(input, |c: &Captures| {
- if let Some(user_dirs) = UserDirs::new() {
- format!("{}{}", user_dirs.home_dir().to_string_lossy(), &c[1],)
- } else {
- warn!("no user dirs found, no expansion of ~");
- c[0].to_string()
- }
- })
- )
- } else {
- // we put the input behind the source (the selected directory
- // or its parent) and we normalize so that the user can type
- // paths with `../`
- let base_dir = match anchor {
- PathAnchor::Parent => base_dir.as_ref()
- .parent().unwrap_or_else(||base_dir.as_ref())
- .to_path_buf(),
- _ => closest_dir(base_dir.as_ref()),
- };
- normalize_path(base_dir.join(input))
- }
-}
-
-pub fn path_str_from<P: AsRef<Path>>(base_dir: P, input: &str) -> String {
- path_from(base_dir, PathAnchor::Unspecified, input).to_string_lossy().to_string()
-}
-
-/// return the closest enclosing directory
-pub fn closest_dir(mut path: &Path) -> PathBuf {
- loop {
- if path.exists() && path.is_dir() {
- return path.to_path_buf();
- }
- match path.parent() {
- Some(parent) => path = parent,
- None => {
- debug!("no existing parent"); // unexpected
- return path.to_path_buf();
- }
- }
- }
-}
-
-/// replace a group in the execution string, using
-/// data from the user input and from the selected line
-pub fn do_exec_replacement(ec: &Captures<'_>, replacement_map: &HashMap<String, String>) -> String {
- let name = ec.get(1).unwrap().as_str();
- if let Some(repl) = replacement_map.get(name) {
- if let Some(fmt) = ec.get(2) {
- match fmt.as_str() {
- "path-from-directory" => path_str_from(replacement_map.get("directory").unwrap(), repl),
- "path-from-parent" => path_str_from(replacement_map.get("parent").unwrap(), repl),
- _ => format!("invalid format: {:?}", fmt.as_str()),
- }
- } else {
- repl.to_string()
- }
- } else {
- format!("{{{}}}", name)
- }
-}
-
-/// from a path, build a string usable in a shell command, wrapping
-/// it in quotes if necessary (and then escaping internal quotes).
-/// Don't do unnecessary transformation, so that the produced string
-/// is prettier on screen.
-pub fn escape_for_shell(path: &Path) -> String {
- let path = path.to_string_lossy();
- if regex!(r"^[\w/.-]*$").is_match(&path) {
- path.to_string()
- } else {
- format!("'{}'", &path.replace('\'', r"'\''"))
- }
-}
-
-/// Improve the path to try remove and solve .. token.
-///
-/// This assumes that `a/b/../c` is `a/c` which might be different from
-/// what the OS would have chosen when b is a link. This is OK
-/// for broot verb arguments but can't be generally used elsewhere
-/// (a more general solution would probably query the FS and just
-/// resolve b in case of links).
-///
-/// This function ensures a given path ending with '/' still
-/// ends with '/' after normalization.
-pub fn normalize_path<P: AsRef<Path>>(path: P) -> PathBuf {
- let ends_with_slash = path.as_ref().to_str().map_or(false, |s| s.ends_with('/'));
- let mut normalized = PathBuf::new();
- for component in path.as_ref().components() {
- match &component {
- Component::ParentDir => {
- if !normalized.pop() {
- normalized.push(component);
- }
- }
- _ => {
- normalized.push(component);
- }
- }
- }
- if ends_with_slash {
- normalized.push("");
- }
- normalized
-}
-
-#[cfg(test)]
-mod path_normalize_tests {
-
- use super::normalize_path;
-
- fn check(before: &str, after: &str) {
- println!("-----------------\nnormalizing {:?}", before);
- assert_eq!(normalize_path(before.to_string()).to_string_lossy(), after);
- }
-
- #[test]
- fn test_path_normalization() {
- check("/abc/test/../thing.png", "/abc/thing.png");
- check("/abc/def/../../thing.png", "/thing.png");
- check("/home/dys/test", "/home/dys/test");
- check("/home/dys", "/home/dys");
- check("/home/dys/", "/home/dys/");
- check("/home/dys/..", "/home");
- check("/home/dys/../", "/home/");
- check("/..", "/..");
- check("../test", "../test");
- check("/home/dys/../../../test", "/../test");
- check("π/2", "π/2");
- check(
- "/home/dys/dev/broot/../../../canop/test",
- "/home/canop/test",
- );
- }
-}
diff --git a/src/path_anchor.rs b/src/path/anchor.rs
index 66e33a3..66e33a3 100644
--- a/src/path_anchor.rs
+++ b/src/path/anchor.rs
diff --git a/src/path/closest.rs b/src/path/closest.rs
new file mode 100644
index 0000000..5d46c1d
--- /dev/null
+++ b/src/path/closest.rs
@@ -0,0 +1,20 @@
+use {
+ std::path::{Path, PathBuf},
+};
+
+
+/// return the closest enclosing directory
+pub fn closest_dir(mut path: &Path) -> PathBuf {
+ loop {
+ if path.exists() && path.is_dir() {
+ return path.to_path_buf();
+ }
+ match path.parent() {
+ Some(parent) => path = parent,
+ None => {
+ debug!("no existing parent"); // unexpected
+ return path.to_path_buf();
+ }
+ }
+ }
+}
diff --git a/src/path/escape.rs b/src/path/escape.rs
new file mode 100644
index 0000000..e5f6b69
--- /dev/null
+++ b/src/path/escape.rs
@@ -0,0 +1,16 @@
+use {
+ std::path::Path,
+};
+
+/// from a path, build a string usable in a shell command, wrapping
+/// it in quotes if necessary (and then escaping internal quotes).
+/// Don't do unnecessary transformation, so that the produced string
+/// is prettier on screen.
+pub fn escape_for_shell(path: &Path) -> String {
+ let path = path.to_string_lossy();
+ if regex!(r"^[\w/.-]*$").is_match(&path) {
+ path.to_string()
+ } else {
+ format!("'{}'", &path.replace('\'', r"'\''"))
+ }
+}
diff --git a/src/path/from.rs b/src/path/from.rs
new file mode 100644
index 0000000..214c480
--- /dev/null
+++ b/src/path/from.rs
@@ -0,0 +1,69 @@
+use {
+ super::*,
+ directories::UserDirs,
+ regex::{self, Captures},
+ std::{
+ collections::HashMap,
+ path::{Path, PathBuf},
+ },
+};
+
+/// build a usable path from a user input which may be absolute
+/// (if it starts with / or ~) or relative to the supplied base_dir.
+/// (we might want to try detect windows drives in the future, too)
+///
+pub fn path_from<P: AsRef<Path>>(base_dir: P, anchor: PathAnchor, input: &str) -> PathBuf {
+ let tilde = regex!(r"^~(/|$)");
+ if input.starts_with('/') {
+ // if the input starts with a `/`, we use it as is
+ input.into()
+ } else if tilde.is_match(input) {
+ // if the input starts with `~` as first token, we replace
+ // this `~` with the user home directory
+ PathBuf::from(
+ &*tilde
+ .replace(input, |c: &Captures| {
+ if let Some(user_dirs) = UserDirs::new() {
+ format!("{}{}", user_dirs.home_dir().to_string_lossy(), &c[1],)
+ } else {
+ warn!("no user dirs found, no expansion of ~");
+ c[0].to_string()
+ }
+ })
+ )
+ } else {
+ // we put the input behind the source (the selected directory
+ // or its parent) and we normalize so that the user can type
+ // paths with `../`
+ let base_dir = match anchor {
+ PathAnchor::Parent => base_dir.as_ref()
+ .parent().unwrap_or_else(||base_dir.as_ref())
+ .to_path_buf(),
+ _ => closest_dir(base_dir.as_ref()),
+ };
+ normalize_path(base_dir.join(input))
+ }
+}
+
+pub fn path_str_from<P: AsRef<Path>>(base_dir: P, input: &str) -> String {
+ path_from(base_dir, PathAnchor::Unspecified, input).to_string_lossy().to_string()
+}
+
+/// replace a group in the execution string, using
+/// data from the user input and from the selected line
+pub fn do_exec_replacement(ec: &Captures<'_>, replacement_map: &HashMap<String, String>) -> String {
+ let name = ec.get(1).unwrap().as_str();
+ if let Some(repl) = replacement_map.get(name) {
+ if let Some(fmt) = ec.get(2) {
+ match fmt.as_str() {
+ "path-from-directory" => path_str_from(replacement_map.get("directory").unwrap(), repl),
+ "path-from-parent" => path_str_from(replacement_map.get("parent").unwrap(), repl),
+ _ => format!("invalid format: {:?}", fmt.as_str()),
+ }
+ } else {
+ repl.to_string()
+ }
+ } else {
+ format!("{{{}}}", name)
+ }
+}
diff --git a/src/path/mod.rs b/src/path/mod.rs
new file mode 100644
index 0000000..02f7aae
--- /dev/null
+++ b/src/path/mod.rs
@@ -0,0 +1,13 @@
+mod anchor;
+mod closest;
+mod escape;
+mod from;
+mod normalize;
+
+pub use {
+ anchor::*,
+ closest::*,
+ escape::*,
+ from::*,
+ normalize::*,
+};
diff --git a/src/path/normalize.rs b/src/path/normalize.rs
new file mode 100644
index 0000000..a2af045
--- /dev/null
+++ b/src/path/normalize.rs
@@ -0,0 +1,64 @@
+use {
+ std::path::{Component, Path, PathBuf},
+};
+
+/// Improve the path to try remove and solve .. token.
+///
+/// This assumes that `a/b/../c` is `a/c` which might be different from
+/// what the OS would have chosen when b is a link. This is OK
+/// for broot verb arguments but can't be generally used elsewhere
+/// (a more general solution would probably query the FS and just
+/// resolve b in case of links).
+///
+/// This function ensures a given path ending with '/' still
+/// ends with '/' after normalization.
+pub fn normalize_path<P: AsRef<Path>>(path: P) -> PathBuf {
+ let ends_with_slash = path.as_ref().to_str().map_or(false, |s| s.ends_with('/'));
+ let mut normalized = PathBuf::new();
+ for component in path.as_ref().components() {
+ match &component {
+ Component::ParentDir => {
+ if !normalized.pop() {
+ normalized.push(component);
+ }
+ }
+ _ => {
+ normalized.push(component);
+ }
+ }
+ }
+ if ends_with_slash {
+ normalized.push("");
+ }
+ normalized
+}
+
+#[cfg(test)]
+mod path_normalize_tests {
+
+ use super::normalize_path;
+
+ fn check(before: &str, after: &str) {
+ println!("-----------------\nnormalizing {:?}", before);
+ assert_eq!(normalize_path(before.to_string()).to_string_lossy(), after);
+ }
+
+ #[test]
+ fn test_path_normalization() {
+ check("/abc/test/../thing.png", "/abc/thing.png");
+ check("/abc/def/../../thing.png", "/thing.png");
+ check("/home/dys/test", "/home/dys/test");
+ check("/home/dys", "/home/dys");
+ check("/home/dys/", "/home/dys/");
+ check("/home/dys/..", "/home");
+ check("/home/dys/../", "/home/");
+ check("/..", "/..");
+ check("../test", "../test");
+ check("/home/dys/../../../test", "/../test");
+ check("π/2", "π/2");
+ check(
+ "/home/dys/dev/broot/../../../canop/test",
+ "/home/canop/test",
+ );
+ }
+}
diff --git a/src/tree/tree.rs b/src/tree/tree.rs
index 00f371b..465e9cf 100644
--- a/src/tree/tree.rs
+++ b/src/tree/tree.rs
@@ -328,8 +328,11 @@ impl Tree {
/// long computation which is needed for directories)
pub fn fetch_regular_file_sums(&mut self) {
for i in 1..self.lines.len() {
- if self.lines[i].is_file() {
- self.lines[i].sum = Some(FileSum::from_file(&self.lines[i].path));
+ match self.lines[i].line_type {
+ TreeLineType::Dir | TreeLineType::Pruning => {}
+ _ => {
+ self.lines[i].sum = Some(FileSum::from_file(&self.lines[i].path));
+ },
}
}
self.sort_siblings();
diff --git a/src/verb/internal_focus.rs b/src/verb/internal_focus.rs
index 7a6a4d8..c7a055a 100644
--- a/src/verb/internal_focus.rs
+++ b/src/verb/internal_focus.rs
@@ -7,8 +7,7 @@ use {
browser::BrowserState,
command::TriggerType,
display::Screen,
- path,
- path_anchor::PathAnchor,
+ path::{self, PathAnchor},
preview::PreviewState,
task_sync::Dam,
tree::TreeOptions,
diff --git a/src/verb/invocation_parser.rs b/src/verb/invocation_parser.rs
index 7f60e26..9f7b6c9 100644
--- a/src/verb/invocation_parser.rs
+++ b/src/verb/invocation_parser.rs
@@ -3,7 +3,7 @@ use {
crate::{
app::*,
errors::ConfError,
- path_anchor::PathAnchor,
+ path::PathAnchor,
},
regex::Regex,
std::{
diff --git a/src/verb/verb.rs b/src/verb/verb.rs
index 33ab3c5..7a19638 100644
--- a/src/verb/verb.rs
+++ b/src/verb/verb.rs
@@ -4,8 +4,7 @@ use {
app::{Selection, SelectionType, Status},
errors::ConfError,
keys,
- path,
- path_anchor::PathAnchor,
+ path::{self, PathAnchor},
},
crossterm::event::{KeyCode, KeyEvent, KeyModifiers},
std::path::PathBuf,