mirror of
https://github.com/lsd-rs/lsd
synced 2024-12-14 14:12:31 +00:00
add relative path function
This commit is contained in:
parent
746a929ebf
commit
3141278c65
4 changed files with 180 additions and 47 deletions
|
@ -95,7 +95,12 @@ impl Core {
|
|||
meta_list.push(meta);
|
||||
}
|
||||
_ => {
|
||||
match meta.recurse_into(base_path, depth, self.flags.display, &self.flags.ignore_globs) {
|
||||
match meta.recurse_into(
|
||||
base_path,
|
||||
depth,
|
||||
self.flags.display,
|
||||
&self.flags.ignore_globs,
|
||||
) {
|
||||
Ok(content) => {
|
||||
meta.content = content;
|
||||
meta_list.push(meta);
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
use crate::color::{ColoredString, Colors};
|
||||
use crate::flags::{Block, Display, Flags, Layout};
|
||||
use crate::icon::Icons;
|
||||
use crate::meta::{FileType, Meta};
|
||||
use crate::meta::name::DisplayOption;
|
||||
use crate::meta::{FileType, Meta};
|
||||
use ansi_term::{ANSIString, ANSIStrings};
|
||||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
use term_grid::{Cell, Direction, Filling, Grid, GridOptions};
|
||||
use terminal_size::terminal_size;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
@ -23,7 +22,17 @@ pub fn grid(metas: &[Meta], flags: &Flags, colors: &Colors, icons: &Icons) -> St
|
|||
|
||||
let current_dir = std::env::current_dir().unwrap();
|
||||
|
||||
inner_display_grid(¤t_dir, metas, &flags, colors, icons, 0, term_width)
|
||||
inner_display_grid(
|
||||
&DisplayOption::Relative {
|
||||
base_path: ¤t_dir,
|
||||
},
|
||||
metas,
|
||||
&flags,
|
||||
colors,
|
||||
icons,
|
||||
0,
|
||||
term_width,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn tree(metas: &[Meta], flags: &Flags, colors: &Colors, icons: &Icons) -> String {
|
||||
|
@ -31,7 +40,7 @@ pub fn tree(metas: &[Meta], flags: &Flags, colors: &Colors, icons: &Icons) -> St
|
|||
}
|
||||
|
||||
fn inner_display_grid(
|
||||
base_path: &Path,
|
||||
display_option: &DisplayOption,
|
||||
metas: &[Meta],
|
||||
flags: &Flags,
|
||||
colors: &Colors,
|
||||
|
@ -59,15 +68,20 @@ fn inner_display_grid(
|
|||
let skip_dirs = (depth == 0) && (flags.display != Display::DisplayDirectoryItself);
|
||||
|
||||
// print the files first.
|
||||
for (index, meta) in metas.iter().enumerate() {
|
||||
for meta in metas.iter() {
|
||||
// Maybe skip showing the directory meta now; show its contents later.
|
||||
if let (true, FileType::Directory { .. }) = (skip_dirs, meta.file_type) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let display_option = DisplayOption::Relative{ base_path };
|
||||
|
||||
let blocks = get_output(&meta, &colors, &icons, &flags, &display_option, &padding_rules);
|
||||
let blocks = get_output(
|
||||
&meta,
|
||||
&colors,
|
||||
&icons,
|
||||
&flags,
|
||||
&display_option,
|
||||
&padding_rules,
|
||||
);
|
||||
|
||||
for block in blocks {
|
||||
let block_str = block.to_string();
|
||||
|
@ -105,8 +119,16 @@ fn inner_display_grid(
|
|||
output += &display_folder_path(&meta);
|
||||
}
|
||||
|
||||
let display_option = if should_display_folder_path {
|
||||
DisplayOption::FileName
|
||||
} else {
|
||||
DisplayOption::Relative {
|
||||
base_path: &meta.path,
|
||||
}
|
||||
};
|
||||
|
||||
output += &inner_display_grid(
|
||||
&meta.path,
|
||||
&display_option,
|
||||
meta.content.as_ref().unwrap(),
|
||||
&flags,
|
||||
colors,
|
||||
|
@ -139,7 +161,14 @@ fn inner_display_tree(
|
|||
});
|
||||
|
||||
for meta in metas.iter() {
|
||||
for block in get_output(&meta, &colors, &icons, &flags, &DisplayOption::FileName, &padding_rules) {
|
||||
for block in get_output(
|
||||
&meta,
|
||||
&colors,
|
||||
&icons,
|
||||
&flags,
|
||||
&DisplayOption::FileName,
|
||||
&padding_rules,
|
||||
) {
|
||||
let block_str = block.to_string();
|
||||
|
||||
grid.add(Cell {
|
||||
|
@ -346,7 +375,7 @@ mod tests {
|
|||
let output = name.render(
|
||||
&Colors::new(color::Theme::NoColor),
|
||||
&Icons::new(icon::Theme::NoIcon),
|
||||
&DisplayOption::FileName
|
||||
&DisplayOption::FileName,
|
||||
);
|
||||
|
||||
assert_eq!(get_visible_width(&output), *l);
|
||||
|
@ -378,7 +407,7 @@ mod tests {
|
|||
.render(
|
||||
&Colors::new(color::Theme::NoColor),
|
||||
&Icons::new(icon::Theme::Fancy),
|
||||
&DisplayOption::FileName
|
||||
&DisplayOption::FileName,
|
||||
)
|
||||
.to_string();
|
||||
|
||||
|
@ -410,7 +439,7 @@ mod tests {
|
|||
.render(
|
||||
&Colors::new(color::Theme::NoLscolors),
|
||||
&Icons::new(icon::Theme::NoIcon),
|
||||
&DisplayOption::FileName
|
||||
&DisplayOption::FileName,
|
||||
)
|
||||
.to_string();
|
||||
|
||||
|
@ -446,7 +475,7 @@ mod tests {
|
|||
.render(
|
||||
&Colors::new(color::Theme::NoColor),
|
||||
&Icons::new(icon::Theme::NoIcon),
|
||||
&DisplayOption::FileName
|
||||
&DisplayOption::FileName,
|
||||
)
|
||||
.to_string();
|
||||
|
||||
|
|
|
@ -96,8 +96,10 @@ impl Icons {
|
|||
res += icon;
|
||||
res += ICON_SPACE;
|
||||
res
|
||||
} else if let Some(icon) = name.extension()
|
||||
.and_then(|extension| self.icons_by_extension.get(extension)){
|
||||
} else if let Some(icon) = name
|
||||
.extension()
|
||||
.and_then(|extension| self.icons_by_extension.get(extension))
|
||||
{
|
||||
// Use the known extensions.
|
||||
res += icon;
|
||||
res += ICON_SPACE;
|
||||
|
|
157
src/meta/name.rs
157
src/meta/name.rs
|
@ -2,12 +2,13 @@ use crate::color::{ColoredString, Colors, Elem};
|
|||
use crate::icon::Icons;
|
||||
use crate::meta::filetype::FileType;
|
||||
use std::cmp::{Ordering, PartialOrd};
|
||||
use std::path::{Path, PathBuf, Component};
|
||||
use std::ffi::OsStr;
|
||||
use std::path::{Component, Path, PathBuf};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum DisplayOption<'a> {
|
||||
FileName,
|
||||
Relative{base_path: &'a Path},
|
||||
Relative { base_path: &'a Path },
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq)]
|
||||
|
@ -23,18 +24,43 @@ impl Name {
|
|||
self.path.file_name().and_then(OsStr::to_str).unwrap()
|
||||
}
|
||||
|
||||
fn relative_path(&self, base_path: &Path) -> std::borrow::Cow<'_, str> {
|
||||
if self.path.starts_with(Component::ParentDir) {
|
||||
self.path.to_string_lossy()
|
||||
} else if self.path.starts_with(base_path) {
|
||||
let relative_path = self.path.strip_prefix(base_path).unwrap().to_string_lossy();
|
||||
if relative_path == "" { std::borrow::Cow::Borrowed(".")
|
||||
} else {
|
||||
relative_path
|
||||
}
|
||||
fn relative_path<T: AsRef<Path> + Clone>(&self, base_path: T) -> PathBuf {
|
||||
use std::borrow::Cow;
|
||||
let target_path = if self.path.is_absolute() {
|
||||
Cow::Borrowed(&self.path)
|
||||
} else {
|
||||
std::borrow::Cow::Borrowed(&self.display_name)
|
||||
Cow::Owned(self.path.canonicalize().unwrap())
|
||||
};
|
||||
|
||||
let base_path = if base_path.as_ref().is_absolute() {
|
||||
Cow::Borrowed(base_path.as_ref())
|
||||
} else {
|
||||
Cow::Owned(base_path.as_ref().canonicalize().unwrap())
|
||||
};
|
||||
|
||||
if target_path == base_path {
|
||||
return PathBuf::from("");
|
||||
}
|
||||
|
||||
let shared_components: PathBuf = target_path
|
||||
.components()
|
||||
.zip(base_path.components())
|
||||
.take_while(|(target_component, base_component)| target_component == base_component)
|
||||
.map(|tuple| tuple.0)
|
||||
.collect();
|
||||
|
||||
base_path
|
||||
.strip_prefix(&shared_components)
|
||||
.unwrap()
|
||||
.components()
|
||||
.map(|_| Component::ParentDir)
|
||||
.chain(
|
||||
target_path
|
||||
.strip_prefix(&shared_components)
|
||||
.unwrap()
|
||||
.components(),
|
||||
)
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn new(path: &Path, file_type: FileType) -> Self {
|
||||
|
@ -50,15 +76,28 @@ impl Name {
|
|||
Self {
|
||||
path: PathBuf::from(path),
|
||||
extension,
|
||||
display_name: path.file_name().and_then(OsStr::to_str).unwrap_or("?").to_owned(),
|
||||
display_name: path
|
||||
.file_name()
|
||||
.and_then(OsStr::to_str)
|
||||
.unwrap_or("?")
|
||||
.to_owned(),
|
||||
file_type,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render(&self, colors: &Colors, icons: &Icons, display_option: &DisplayOption) -> ColoredString {
|
||||
pub fn render(
|
||||
&self,
|
||||
colors: &Colors,
|
||||
icons: &Icons,
|
||||
display_option: &DisplayOption,
|
||||
) -> ColoredString {
|
||||
let content = match display_option {
|
||||
DisplayOption::FileName => format!("{}{}", icons.get(self), self.file_name()),
|
||||
DisplayOption::Relative{base_path} => format!("{}{}", icons.get(self), self.relative_path(base_path)),
|
||||
DisplayOption::Relative { base_path } => format!(
|
||||
"{}{}",
|
||||
icons.get(self),
|
||||
self.relative_path(base_path).to_string_lossy()
|
||||
),
|
||||
};
|
||||
|
||||
let elem = match self.file_type {
|
||||
|
@ -86,24 +125,31 @@ impl Name {
|
|||
|
||||
impl Ord for Name {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
self.display_name.cmp(&other.display_name)
|
||||
self.display_name
|
||||
.to_lowercase()
|
||||
.cmp(&other.display_name.to_lowercase())
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for Name {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
self.display_name.partial_cmp(&other.display_name)
|
||||
self.display_name
|
||||
.to_lowercase()
|
||||
.partial_cmp(&other.display_name.to_lowercase())
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Name {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.path.eq(&other.path)
|
||||
self.display_name
|
||||
.to_lowercase()
|
||||
.eq(&other.display_name.to_lowercase())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::DisplayOption;
|
||||
use super::Name;
|
||||
use crate::color::{self, Colors};
|
||||
use crate::icon::{self, Icons};
|
||||
|
@ -116,7 +162,7 @@ mod test {
|
|||
use std::fs::{self, File};
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::symlink;
|
||||
use std::path::Path;
|
||||
use std::path::{Path, PathBuf};
|
||||
#[cfg(unix)]
|
||||
use std::process::Command;
|
||||
use tempfile::tempdir;
|
||||
|
@ -138,7 +184,7 @@ mod test {
|
|||
|
||||
assert_eq!(
|
||||
Colour::Fixed(184).paint(" file.txt"),
|
||||
name.render(&colors, &icons)
|
||||
name.render(&colors, &icons, &DisplayOption::FileName)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -156,7 +202,7 @@ mod test {
|
|||
|
||||
assert_eq!(
|
||||
Colour::Fixed(33).paint(" directory"),
|
||||
meta.name.render(&colors, &icons)
|
||||
meta.name.render(&colors, &icons, &DisplayOption::FileName)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -179,11 +225,11 @@ mod test {
|
|||
|
||||
let colors = Colors::new(color::Theme::NoLscolors);
|
||||
let file_type = FileType::new(&meta, &Permissions::from(&meta));
|
||||
let name = Name::new(&tmp_dir.into_path(), &symlink_path, file_type);
|
||||
let name = Name::new(&symlink_path, file_type);
|
||||
|
||||
assert_eq!(
|
||||
Colour::Fixed(44).paint(" target.tmp"),
|
||||
name.render(&colors, &icons)
|
||||
name.render(&colors, &icons, &DisplayOption::FileName)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -205,11 +251,11 @@ mod test {
|
|||
|
||||
let colors = Colors::new(color::Theme::NoLscolors);
|
||||
let file_type = FileType::new(&meta, &Permissions::from(&meta));
|
||||
let name = Name::new(&tmp_dir.into_path(), &pipe_path, file_type);
|
||||
let name = Name::new(&pipe_path, file_type);
|
||||
|
||||
assert_eq!(
|
||||
Colour::Fixed(184).paint(" pipe.tmp"),
|
||||
name.render(&colors, &icons)
|
||||
name.render(&colors, &icons, &DisplayOption::FileName)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -227,7 +273,10 @@ mod test {
|
|||
|
||||
assert_eq!(
|
||||
"file.txt",
|
||||
meta.name.render(&colors, &icons).to_string().as_str()
|
||||
meta.name
|
||||
.render(&colors, &icons, &DisplayOption::FileName)
|
||||
.to_string()
|
||||
.as_str()
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -263,7 +312,7 @@ mod test {
|
|||
|
||||
#[test]
|
||||
fn test_order_impl_is_case_insensitive() {
|
||||
let path_1 = Path::new("AAAA");
|
||||
let path_1 = Path::new("/AAAA");
|
||||
let name_1 = Name::new(
|
||||
&path_1,
|
||||
FileType::File {
|
||||
|
@ -272,7 +321,7 @@ mod test {
|
|||
},
|
||||
);
|
||||
|
||||
let path_2 = Path::new("aaaa");
|
||||
let path_2 = Path::new("/aaaa");
|
||||
let name_2 = Name::new(
|
||||
&path_2,
|
||||
FileType::File {
|
||||
|
@ -286,7 +335,7 @@ mod test {
|
|||
|
||||
#[test]
|
||||
fn test_partial_order_impl() {
|
||||
let path_a = Path::new("aaaa");
|
||||
let path_a = Path::new("/aaaa");
|
||||
let name_a = Name::new(
|
||||
&path_a,
|
||||
FileType::File {
|
||||
|
@ -295,7 +344,7 @@ mod test {
|
|||
},
|
||||
);
|
||||
|
||||
let path_z = Path::new("zzzz");
|
||||
let path_z = Path::new("/zzzz");
|
||||
let name_z = Name::new(
|
||||
&path_z,
|
||||
FileType::File {
|
||||
|
@ -375,4 +424,52 @@ mod test {
|
|||
|
||||
assert_eq!(true, name_1 == name_2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parent_relative_path() {
|
||||
let name = Name::new(
|
||||
Path::new("/home/parent1/child"),
|
||||
FileType::File {
|
||||
uid: false,
|
||||
exec: false,
|
||||
},
|
||||
);
|
||||
let base_path = Path::new("/home/parent2");
|
||||
|
||||
assert_eq!(
|
||||
PathBuf::from("../parent1/child"),
|
||||
name.relative_path(base_path),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_current_relative_path() {
|
||||
let name = Name::new(
|
||||
Path::new("/home/parent1/child"),
|
||||
FileType::File {
|
||||
uid: false,
|
||||
exec: false,
|
||||
},
|
||||
);
|
||||
let base_path = PathBuf::from("/home/parent1");
|
||||
|
||||
assert_eq!(PathBuf::from("child"), name.relative_path(base_path),)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_grand_parent_relative_path() {
|
||||
let name = Name::new(
|
||||
Path::new("/home/grand-parent1/parent1/child"),
|
||||
FileType::File {
|
||||
uid: false,
|
||||
exec: false,
|
||||
},
|
||||
);
|
||||
let base_path = PathBuf::from("/home/grand-parent2/parent1");
|
||||
|
||||
assert_eq!(
|
||||
PathBuf::from("../../grand-parent1/parent1/child"),
|
||||
name.relative_path(base_path),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue