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);
|
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);
|
||||||
|
|
|
@ -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(¤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 {
|
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();
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
153
src/meta/name.rs
153
src/meta/name.rs
|
@ -2,9 +2,10 @@ 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 },
|
||||||
|
@ -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),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue