add relative path function

This commit is contained in:
dvvvvvv 2020-02-04 13:32:36 +09:00 committed by Abin Simon
parent 746a929ebf
commit 3141278c65
4 changed files with 180 additions and 47 deletions

View file

@ -95,7 +95,12 @@ impl Core {
meta_list.push(meta); 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) => { Ok(content) => {
meta.content = content; meta.content = content;
meta_list.push(meta); meta_list.push(meta);

View file

@ -1,11 +1,10 @@
use crate::color::{ColoredString, Colors}; use crate::color::{ColoredString, Colors};
use crate::flags::{Block, Display, Flags, Layout}; use crate::flags::{Block, Display, Flags, Layout};
use crate::icon::Icons; use crate::icon::Icons;
use crate::meta::{FileType, Meta};
use crate::meta::name::DisplayOption; use crate::meta::name::DisplayOption;
use crate::meta::{FileType, Meta};
use ansi_term::{ANSIString, ANSIStrings}; use ansi_term::{ANSIString, ANSIStrings};
use std::collections::HashMap; use std::collections::HashMap;
use std::path::Path;
use term_grid::{Cell, Direction, Filling, Grid, GridOptions}; use term_grid::{Cell, Direction, Filling, Grid, GridOptions};
use terminal_size::terminal_size; use terminal_size::terminal_size;
use unicode_width::UnicodeWidthStr; 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(); let current_dir = std::env::current_dir().unwrap();
inner_display_grid(&current_dir, metas, &flags, colors, icons, 0, term_width) inner_display_grid(
&DisplayOption::Relative {
base_path: &current_dir,
},
metas,
&flags,
colors,
icons,
0,
term_width,
)
} }
pub fn tree(metas: &[Meta], flags: &Flags, colors: &Colors, icons: &Icons) -> String { 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( fn inner_display_grid(
base_path: &Path, display_option: &DisplayOption,
metas: &[Meta], metas: &[Meta],
flags: &Flags, flags: &Flags,
colors: &Colors, colors: &Colors,
@ -59,15 +68,20 @@ fn inner_display_grid(
let skip_dirs = (depth == 0) && (flags.display != Display::DisplayDirectoryItself); let skip_dirs = (depth == 0) && (flags.display != Display::DisplayDirectoryItself);
// print the files first. // 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. // Maybe skip showing the directory meta now; show its contents later.
if let (true, FileType::Directory { .. }) = (skip_dirs, meta.file_type) { if let (true, FileType::Directory { .. }) = (skip_dirs, meta.file_type) {
continue; continue;
} }
let display_option = DisplayOption::Relative{ base_path }; let blocks = get_output(
&meta,
let blocks = get_output(&meta, &colors, &icons, &flags, &display_option, &padding_rules); &colors,
&icons,
&flags,
&display_option,
&padding_rules,
);
for block in blocks { for block in blocks {
let block_str = block.to_string(); let block_str = block.to_string();
@ -105,8 +119,16 @@ fn inner_display_grid(
output += &display_folder_path(&meta); 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( output += &inner_display_grid(
&meta.path, &display_option,
meta.content.as_ref().unwrap(), meta.content.as_ref().unwrap(),
&flags, &flags,
colors, colors,
@ -139,7 +161,14 @@ fn inner_display_tree(
}); });
for meta in metas.iter() { 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(); let block_str = block.to_string();
grid.add(Cell { grid.add(Cell {
@ -346,7 +375,7 @@ mod tests {
let output = name.render( let output = name.render(
&Colors::new(color::Theme::NoColor), &Colors::new(color::Theme::NoColor),
&Icons::new(icon::Theme::NoIcon), &Icons::new(icon::Theme::NoIcon),
&DisplayOption::FileName &DisplayOption::FileName,
); );
assert_eq!(get_visible_width(&output), *l); assert_eq!(get_visible_width(&output), *l);
@ -378,7 +407,7 @@ mod tests {
.render( .render(
&Colors::new(color::Theme::NoColor), &Colors::new(color::Theme::NoColor),
&Icons::new(icon::Theme::Fancy), &Icons::new(icon::Theme::Fancy),
&DisplayOption::FileName &DisplayOption::FileName,
) )
.to_string(); .to_string();
@ -410,7 +439,7 @@ mod tests {
.render( .render(
&Colors::new(color::Theme::NoLscolors), &Colors::new(color::Theme::NoLscolors),
&Icons::new(icon::Theme::NoIcon), &Icons::new(icon::Theme::NoIcon),
&DisplayOption::FileName &DisplayOption::FileName,
) )
.to_string(); .to_string();
@ -446,7 +475,7 @@ mod tests {
.render( .render(
&Colors::new(color::Theme::NoColor), &Colors::new(color::Theme::NoColor),
&Icons::new(icon::Theme::NoIcon), &Icons::new(icon::Theme::NoIcon),
&DisplayOption::FileName &DisplayOption::FileName,
) )
.to_string(); .to_string();

View file

@ -96,8 +96,10 @@ impl Icons {
res += icon; res += icon;
res += ICON_SPACE; res += ICON_SPACE;
res res
} else if let Some(icon) = name.extension() } else if let Some(icon) = name
.and_then(|extension| self.icons_by_extension.get(extension)){ .extension()
.and_then(|extension| self.icons_by_extension.get(extension))
{
// Use the known extensions. // Use the known extensions.
res += icon; res += icon;
res += ICON_SPACE; res += ICON_SPACE;

View file

@ -2,12 +2,13 @@ use crate::color::{ColoredString, Colors, Elem};
use crate::icon::Icons; use crate::icon::Icons;
use crate::meta::filetype::FileType; use crate::meta::filetype::FileType;
use std::cmp::{Ordering, PartialOrd}; use std::cmp::{Ordering, PartialOrd};
use std::path::{Path, PathBuf, Component};
use std::ffi::OsStr; use std::ffi::OsStr;
use std::path::{Component, Path, PathBuf};
#[derive(Debug)]
pub enum DisplayOption<'a> { pub enum DisplayOption<'a> {
FileName, FileName,
Relative{base_path: &'a Path}, Relative { base_path: &'a Path },
} }
#[derive(Clone, Debug, Eq)] #[derive(Clone, Debug, Eq)]
@ -23,18 +24,43 @@ impl Name {
self.path.file_name().and_then(OsStr::to_str).unwrap() self.path.file_name().and_then(OsStr::to_str).unwrap()
} }
fn relative_path(&self, base_path: &Path) -> std::borrow::Cow<'_, str> { fn relative_path<T: AsRef<Path> + Clone>(&self, base_path: T) -> PathBuf {
if self.path.starts_with(Component::ParentDir) { use std::borrow::Cow;
self.path.to_string_lossy() let target_path = if self.path.is_absolute() {
} else if self.path.starts_with(base_path) { Cow::Borrowed(&self.path)
let relative_path = self.path.strip_prefix(base_path).unwrap().to_string_lossy();
if relative_path == "" { std::borrow::Cow::Borrowed(".")
} else { } else {
relative_path Cow::Owned(self.path.canonicalize().unwrap())
} };
let base_path = if base_path.as_ref().is_absolute() {
Cow::Borrowed(base_path.as_ref())
} else { } else {
std::borrow::Cow::Borrowed(&self.display_name) 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 { pub fn new(path: &Path, file_type: FileType) -> Self {
@ -50,15 +76,28 @@ impl Name {
Self { Self {
path: PathBuf::from(path), path: PathBuf::from(path),
extension, 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, 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 { let content = match display_option {
DisplayOption::FileName => format!("{}{}", icons.get(self), self.file_name()), 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 { let elem = match self.file_type {
@ -86,24 +125,31 @@ impl Name {
impl Ord for Name { impl Ord for Name {
fn cmp(&self, other: &Self) -> Ordering { 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 { impl PartialOrd for Name {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> { 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 { impl PartialEq for Name {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
self.path.eq(&other.path) self.display_name
.to_lowercase()
.eq(&other.display_name.to_lowercase())
} }
} }
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::DisplayOption;
use super::Name; use super::Name;
use crate::color::{self, Colors}; use crate::color::{self, Colors};
use crate::icon::{self, Icons}; use crate::icon::{self, Icons};
@ -116,7 +162,7 @@ mod test {
use std::fs::{self, File}; use std::fs::{self, File};
#[cfg(unix)] #[cfg(unix)]
use std::os::unix::fs::symlink; use std::os::unix::fs::symlink;
use std::path::Path; use std::path::{Path, PathBuf};
#[cfg(unix)] #[cfg(unix)]
use std::process::Command; use std::process::Command;
use tempfile::tempdir; use tempfile::tempdir;
@ -138,7 +184,7 @@ mod test {
assert_eq!( assert_eq!(
Colour::Fixed(184).paint(" file.txt"), Colour::Fixed(184).paint(" file.txt"),
name.render(&colors, &icons) name.render(&colors, &icons, &DisplayOption::FileName)
); );
} }
@ -156,7 +202,7 @@ mod test {
assert_eq!( assert_eq!(
Colour::Fixed(33).paint(" directory"), 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 colors = Colors::new(color::Theme::NoLscolors);
let file_type = FileType::new(&meta, &Permissions::from(&meta)); 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!( assert_eq!(
Colour::Fixed(44).paint(" target.tmp"), 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 colors = Colors::new(color::Theme::NoLscolors);
let file_type = FileType::new(&meta, &Permissions::from(&meta)); 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!( assert_eq!(
Colour::Fixed(184).paint(" pipe.tmp"), Colour::Fixed(184).paint(" pipe.tmp"),
name.render(&colors, &icons) name.render(&colors, &icons, &DisplayOption::FileName)
); );
} }
@ -227,7 +273,10 @@ mod test {
assert_eq!( assert_eq!(
"file.txt", "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] #[test]
fn test_order_impl_is_case_insensitive() { fn test_order_impl_is_case_insensitive() {
let path_1 = Path::new("AAAA"); let path_1 = Path::new("/AAAA");
let name_1 = Name::new( let name_1 = Name::new(
&path_1, &path_1,
FileType::File { 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( let name_2 = Name::new(
&path_2, &path_2,
FileType::File { FileType::File {
@ -286,7 +335,7 @@ mod test {
#[test] #[test]
fn test_partial_order_impl() { fn test_partial_order_impl() {
let path_a = Path::new("aaaa"); let path_a = Path::new("/aaaa");
let name_a = Name::new( let name_a = Name::new(
&path_a, &path_a,
FileType::File { 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( let name_z = Name::new(
&path_z, &path_z,
FileType::File { FileType::File {
@ -375,4 +424,52 @@ mod test {
assert_eq!(true, name_1 == name_2); 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),
)
}
} }