ttags 0.4.3

Generate ctags using Tree Sitter
Documentation
use clap::ArgMatches;
use pathdiff::diff_paths;
use std::env;
use std::ffi::OsStr;
use std::fs::{File, OpenOptions};
use std::io::{self, BufReader, BufWriter, Read, Write};
use std::path::{Path, PathBuf};
use sugar_path::SugarPath;
use walkdir::WalkDir;

use crate::cli;
use crate::tag::Tag;

impl Default for Config {
    fn default() -> Self {
        Self::new()
    }
}

pub struct Config {
    pub files: Vec<String>,
    pub tag_path: String,
    pub relative_path: String,
    pub append: bool,
    pub lsp: bool,
}

impl Config {
    pub fn new() -> Self {
        let matches = cli::build_cli().get_matches();

        let lsp = matches.subcommand_name() == Some("lsp");
        let files = Self::fetch_files(&matches, lsp);
        let tag_path = Self::path_to_string(Self::fetch_tag_file(&matches));
        let relative_path = Self::path_to_string(Self::fetch_relative_path(&matches));
        let append = matches.is_present("append") || lsp;

        Self {
            files,
            tag_path,
            relative_path,
            append,
            lsp,
        }
    }

    pub fn path_relative_to_file(&self, filename: &str) -> String {
        Self::path_to_string(diff_paths(filename, &self.relative_path).unwrap())
    }

    pub fn output(&self) -> BufWriter<Box<dyn Write>> {
        BufWriter::new(if self.going_to_stdout() {
            Box::new(io::stdout())
        } else {
            let mut options = OpenOptions::new();
            let options = options.write(true).read(true).create(true);

            Box::new(options.open(&self.tag_path).unwrap())
        })
    }

    pub fn appending(&self) -> bool {
        self.append && !self.going_to_stdout()
    }

    pub fn clear_tag_file(&self) {
        if !self.going_to_stdout() {
            File::create(&self.tag_path).expect("Failed clearing file");
        }
    }

    pub fn current_tag_contents(&self) -> Vec<Tag> {
        let file = File::open(&self.tag_path).expect("Failed reading tags file");
        let mut reader = BufReader::new(file);
        let mut contents = String::new();

        reader.read_to_string(&mut contents).unwrap();

        contents
            .trim()
            .split('\n')
            .filter(|line| !line.starts_with("!_") && !line.is_empty())
            .map(Tag::parse)
            .collect()
    }

    pub fn going_to_stdout(&self) -> bool {
        Path::new(&self.tag_path).file_name() == Some(OsStr::new("-"))
    }

    fn fetch_files(matches: &ArgMatches<'_>, lsp: bool) -> Vec<String> {
        if lsp {
            vec![]
        } else {
            matches
                .values_of("files")
                .unwrap()
                .flat_map(|f| {
                    let path = Path::new(&f).resolve();

                    if path.is_dir() {
                        WalkDir::new(path)
                            .into_iter()
                            .filter(|entry| entry.as_ref().unwrap().path().is_file())
                            .map(|entry| Self::path_to_string(entry.unwrap().path().to_path_buf()))
                            .collect()
                    } else {
                        vec![Self::path_to_string(path)]
                    }
                })
                .collect()
        }
    }

    fn fetch_tag_file(matches: &ArgMatches<'_>) -> PathBuf {
        Path::new(matches.value_of("tag_file").unwrap()).resolve()
    }

    fn fetch_relative_path(matches: &ArgMatches<'_>) -> PathBuf {
        let tag_file = Self::fetch_tag_file(matches);

        if !matches.is_present("relative")
            && tag_file.as_path().file_name() != Some(OsStr::new("-"))
        {
            tag_file.parent().unwrap().to_path_buf()
        } else {
            env::current_dir().unwrap()
        }
    }

    fn path_to_string(path: PathBuf) -> String {
        path.into_os_string().into_string().unwrap()
    }
}