use std::{
collections::BTreeSet,
ffi::OsString,
io::{BufReader, Error, ErrorKind, Result},
path::PathBuf,
};
use clap::Args;
use regex::Regex;
use crate::{file_utils, RunCommand};
static TAG_LINE_PATTERN: &str = r"^\s*tags\s*:\s*[ ]*\[.*\]\s*";
#[derive(Args, Debug)]
pub struct AddTag {
#[clap(required = true)]
tags: Vec<String>,
#[clap(short, long, parse(from_os_str), value_name = "FILE")]
path: PathBuf,
}
impl RunCommand for AddTag {
fn run(&self) {
self.add_tag()
}
}
impl AddTag {
fn add_tag(&self) {
let Self { tags, path } = self;
let files = file_utils::list_all_files(path);
for file in files {
println!("Updating file : {}", file.as_os_str().to_str().unwrap());
do_add_tag(&tags, &file);
}
println!("Complete.");
}
}
pub fn do_add_tag(new_tags: &Vec<String>, file: &OsString) {
let (line, start, end) = extract_tag_line(&file).unwrap();
let new_line = extend_tag(line, new_tags);
file_utils::replace_in_file(&file, start, end, new_line).unwrap();
}
fn extend_tag(old_tag_line: String, new_tags: &Vec<String>) -> String {
let mut tags = old_tag_line
.split_once("tags")
.unwrap()
.1
.split_once(":")
.unwrap()
.1
.split_once("[")
.unwrap()
.1
.split_once("]")
.unwrap()
.0
.split(",")
.map(|s| s.trim().to_string())
.collect::<BTreeSet<String>>();
tags.extend(new_tags.iter().cloned());
format!(
"tags: [{}]",
tags.into_iter().collect::<Vec<String>>().join(", ")
)
}
pub fn extract_tag_line(file: &OsString) -> Result<(String, usize, usize)> {
use std::io::BufRead;
use std::fs::OpenOptions;
let file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(file)
.unwrap();
let re = Regex::new(TAG_LINE_PATTERN).unwrap();
let mut reader = BufReader::new(file);
let (mut i, mut _j) = (0, 0);
loop {
let mut line = String::new();
match reader.read_line(&mut line) {
Ok(0) => return Err(Error::new(ErrorKind::Other, "No tag line found")),
Ok(size) => {
if re.is_match(&line) {
_j = i + size;
return Ok((String::from(line), i, _j));
} else {
i += size;
}
}
Err(e) => {
eprintln!("{}", e);
return Err(e);
}
}
}
}
#[test]
fn test_modify_tag() {
let line = "tags: [a, b, c]";
let new_tags = vec!["d".to_string(), "e".to_string()];
let result = extend_tag(line.to_string(), &new_tags);
assert_eq!(result, "tags: [a, b, c, d, e]");
let re = Regex::new(TAG_LINE_PATTERN).unwrap();
assert_eq!(re.is_match(line), true);
}
#[test]
fn test_regex() {
let re = Regex::new(TAG_LINE_PATTERN).unwrap();
let line = "tags: [a, b, c]";
assert_eq!(re.is_match(line), true);
let line = " tags : [ a , b, c ] ";
assert_eq!(re.is_match(line), true);
}