use crate::config::CFG;
use crate::config::FILENAME_COPY_COUNTER_MAX;
use crate::config::FILENAME_DOTFILE_MARKER;
use crate::config::FILENAME_LEN_MAX;
use crate::error::FileError;
use std::path::Path;
use std::path::PathBuf;
pub fn shorten_filename(mut file_path: PathBuf) -> PathBuf {
let note_extension = file_path
.extension()
.unwrap_or_default()
.to_str()
.unwrap_or_default();
let note_extension_len = note_extension.len();
let mut note_stem = file_path
.file_stem()
.unwrap_or_default()
.to_str()
.unwrap_or_default()
.to_string();
if note_stem.len() != remove_copy_counter(¬e_stem).len() {
note_stem.push_str(&CFG.filename.copy_counter_extra_separator);
};
let mut note_stem_short = String::new();
for i in (0..FILENAME_LEN_MAX - (note_extension_len + 1)).rev() {
if let Some(s) = note_stem.get(..=i) {
note_stem_short = s.to_string();
break;
}
}
let mut note_filename = note_stem_short;
note_filename.push('.');
note_filename.push_str(note_extension);
file_path.set_file_name(note_filename);
file_path
}
pub fn is_well_formed_filename(path: &Path) -> bool {
let filename = path.file_name().unwrap_or_default();
let ext = path
.extension()
.unwrap_or_default()
.to_str()
.unwrap_or_default();
let is_filename = !filename.is_empty() && (filename == path);
let filename = filename.to_str().unwrap_or_default();
let is_dot_file = filename.starts_with(FILENAME_DOTFILE_MARKER)
&& (filename == filename.trim())
&& filename.split_whitespace().count() == 1;
let has_extension = !ext.is_empty()
&& (ext == ext.trim())
&& ext.split_whitespace().count() == 1;
is_filename && (is_dot_file || has_extension)
}
pub fn find_unused(p: PathBuf) -> Result<PathBuf, FileError> {
if !p.exists() {
return Ok(p);
};
let (sort_tag, _, stem, _copy_counter, ext) = disassemble(&p);
let mut new_path = p.clone();
for n in 1..FILENAME_COPY_COUNTER_MAX {
let stem_copy_counter = append_copy_counter(stem, n);
let filename = assemble(sort_tag, &stem_copy_counter, "", ext);
new_path.set_file_name(filename);
if !new_path.exists() {
break;
}
}
if new_path.exists() {
return Err(FileError::NoFreeFileName {
directory: p.parent().unwrap_or_else(|| Path::new("")).to_path_buf(),
});
}
Ok(new_path)
}
pub fn exclude_copy_counter_eq(p1: &Path, p2: &Path) -> bool {
let (sort_tag1, _, stem1, _, ext1) = disassemble(p1);
let (sort_tag2, _, stem2, _, ext2) = disassemble(p2);
sort_tag1 == sort_tag2 && stem1 == stem2 && ext1 == ext2
}
pub fn disassemble(p: &Path) -> (&str, &str, &str, &str, &str) {
let sort_tag_stem_copy_counter_ext = p
.file_name()
.unwrap_or_default()
.to_str()
.unwrap_or_default();
let sort_tag_stem_copy_counter = p
.file_stem()
.unwrap_or_default()
.to_str()
.unwrap_or_default();
let stem_copy_counter = sort_tag_stem_copy_counter
.trim_start_matches(&CFG.filename.sort_tag_chars.chars().collect::<Vec<char>>()[..]);
let sort_tag =
&sort_tag_stem_copy_counter[0..sort_tag_stem_copy_counter.len() - stem_copy_counter.len()];
let stem_copy_counter_ext = if sort_tag_stem_copy_counter_ext.len() > sort_tag.len() {
&sort_tag_stem_copy_counter_ext[sort_tag.len()..]
} else {
""
};
let stem_copy_counter = if sort_tag_stem_copy_counter.len() > sort_tag.len() {
&sort_tag_stem_copy_counter[sort_tag.len()..]
} else {
""
};
let stem = remove_copy_counter(stem_copy_counter);
let copy_counter = &stem_copy_counter[stem.len()..];
let ext = p
.extension()
.unwrap_or_default()
.to_str()
.unwrap_or_default();
(sort_tag, stem_copy_counter_ext, stem, copy_counter, ext)
}
pub fn assemble(sort_tag: &str, stem: &str, copy_counter: &str, extension: &str) -> String {
let mut filename = sort_tag.to_string();
filename.push_str(stem);
filename.push_str(copy_counter);
if !extension.is_empty() {
filename.push('.');
filename.push_str(extension);
};
filename
}
#[inline]
pub fn remove_copy_counter(tag: &str) -> &str {
let tag1 = if let Some(t) = tag.strip_suffix(&CFG.filename.copy_counter_closing_brackets) {
t
} else {
return tag;
};
let tag2 = tag1.trim_end_matches(|c: char| c.is_numeric());
if tag2.len() == tag1.len() {
return tag;
};
let tag3 = if let Some(t) = tag2.strip_suffix(&CFG.filename.copy_counter_opening_brackets) {
t
} else {
return tag;
};
tag3
}
#[inline]
pub fn append_copy_counter(stem: &str, n: usize) -> String {
let mut stem = stem.to_string();
stem.push_str(&CFG.filename.copy_counter_opening_brackets);
stem.push_str(&n.to_string());
stem.push_str(&CFG.filename.copy_counter_closing_brackets);
stem
}
#[derive(PartialEq, Debug, Clone)]
pub enum MarkupLanguage {
Markdown,
RestructuredText,
Html,
Txt,
Unknown,
None,
}
impl MarkupLanguage {
pub fn or(self, rhs: Self) -> Self {
match self {
MarkupLanguage::None => rhs,
_ => self,
}
}
}
impl From<&str> for MarkupLanguage {
#[inline]
fn from(file_extension: &str) -> Self {
for e in &CFG.filename.extensions_md {
if e == file_extension {
return MarkupLanguage::Markdown;
}
}
for e in &CFG.filename.extensions_rst {
if e == file_extension {
return MarkupLanguage::RestructuredText;
}
}
for e in &CFG.filename.extensions_html {
if e == file_extension {
return MarkupLanguage::Html;
}
}
for e in &CFG.filename.extensions_txt {
if e == file_extension {
return MarkupLanguage::Txt;
}
}
for e in &CFG.filename.extensions_no_viewer {
if e == file_extension {
return MarkupLanguage::Unknown;
}
}
if file_extension == CFG.filename.extension_default {
return MarkupLanguage::Txt;
}
MarkupLanguage::None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_shorten_filename() {
use std::ffi::OsString;
use std::path::PathBuf;
let input = PathBuf::from("long directory name/abc.ext");
let output = shorten_filename(input);
assert_eq!(OsString::from("long directory name/abc.ext"), output);
let input = PathBuf::from("long directory name/long filename.ext");
let output = shorten_filename(input);
assert_eq!(OsString::from("long directory name/long f.ext"), output);
let input = "fn";
let mut input = append_copy_counter(input, 1);
let mut expected = input.clone();
expected.push_str(&CFG.filename.copy_counter_extra_separator);
input.push_str(".ext");
expected.push_str(".ext");
let output = shorten_filename(PathBuf::from(input));
assert_eq!(OsString::from(expected), output);
}
#[test]
fn test_is_well_formed() {
use std::path::Path;
assert_eq!(
is_well_formed_filename(Path::new("long filename.ext")),
true
);
assert_eq!(
is_well_formed_filename(Path::new("long directory name/long filename.ext")),
false
);
assert_eq!(is_well_formed_filename(Path::new(".dotfile")), true);
assert_eq!(is_well_formed_filename(Path::new(".dotfile.ext")), true);
assert_eq!(is_well_formed_filename(Path::new(".dot file")), false);
assert_eq!(is_well_formed_filename(Path::new("filename.e xt")), false);
assert_eq!(is_well_formed_filename(Path::new("filename. ext")), false);
assert_eq!(is_well_formed_filename(Path::new("filename.ext ")), false);
}
#[test]
fn test_disassemble_filename() {
let expected = (
"1_2_3-",
"my_title--my_subtitle(1).md",
"my_title--my_subtitle",
"(1)",
"md",
);
let result = disassemble(Path::new("/my/dir/1_2_3-my_title--my_subtitle(1).md"));
assert_eq!(expected, result);
let expected = (
"2021.04.12-",
"my_title--my_subtitle(1).md",
"my_title--my_subtitle",
"(1)",
"md",
);
let result = disassemble(Path::new("/my/dir/2021.04.12-my_title--my_subtitle(1).md"));
assert_eq!(expected, result);
let expected = (
"2021 04 12 ",
"my_title--my_subtitle(1).md",
"my_title--my_subtitle",
"(1)",
"md",
);
let result = disassemble(Path::new("/my/dir/2021 04 12 my_title--my_subtitle(1).md"));
assert_eq!(expected, result);
let expected = ("2021 04 12 ", "", "", "", "");
let result = disassemble(Path::new("/my/dir/2021 04 12 "));
assert_eq!(expected, result);
let expected = ("2021 04 12 ", ".md", "", "", "md");
let result = disassemble(Path::new("/my/dir/2021 04 12 .md"));
assert_eq!(expected, result);
let expected = ("2021 04 12 ", "(9).md", "", "(9)", "md");
let result = disassemble(Path::new("/my/dir/2021 04 12 (9).md"));
assert_eq!(expected, result);
}
#[test]
fn test_assemble_filename() {
let expected = "1_2_3-my_file-1-.md".to_string();
let result = assemble("1_2_3-", "my_file", "-1-", "md");
assert_eq!(expected, result);
}
#[test]
fn test_remove_copy_counter() {
let expected = "my_stem";
let result = remove_copy_counter("my_stem(78)");
assert_eq!(expected, result);
let expected = "my_stem-";
let result = remove_copy_counter("my_stem-(78)");
assert_eq!(expected, result);
let expected = "my_stem_";
let result = remove_copy_counter("my_stem_(78)");
assert_eq!(expected, result);
assert_eq!(expected, result);
let expected = "my_stem_(78))";
let result = remove_copy_counter("my_stem_(78))");
assert_eq!(expected, result);
let expected = "my_stem_)78)";
let result = remove_copy_counter("my_stem_)78)");
assert_eq!(expected, result);
}
#[test]
fn test_append_sort_tag_extension() {
let expected = "my_stem(987)";
let result = append_copy_counter("my_stem", 987);
assert_eq!(expected, result);
}
#[test]
fn test_filename_exclude_copy_counter_eq() {
let p1 = Path::new("/mypath/123-title(1).md");
let p2 = Path::new("/mypath/123-title(3).md");
let expected = true;
let result = exclude_copy_counter_eq(p1, p2);
assert_eq!(expected, result);
let p1 = Path::new("/mypath/123-title(1).md");
let p2 = Path::new("/mypath/123-titlX(3).md");
let expected = false;
let result = exclude_copy_counter_eq(p1, p2);
assert_eq!(expected, result);
}
}