use crate::dir::Dir;
use crate::line;
use crate::line::{Invalid, LineType};
use crate::params::Params;
use crate::prompt;
use crate::prompt::AlreadyExistPromptOptions;
use crate::utils;
use anyhow::Context;
use crossterm::style::Stylize;
use std::fmt::Debug;
use std::fs;
use std::io;
use std::io::BufRead;
use std::os::unix;
use std::path::Path;
use std::path::PathBuf;
#[derive(Debug)]
enum Action {
Skip,
Backup,
Overwrite,
}
#[derive(Debug)]
pub struct Engine {
action: Option<Action>,
params: Params,
}
impl Engine {
pub fn new(params: Params) -> Self {
let mut action: Option<Action> = None;
if params.always_skip {
action = Some(Action::Skip);
}
if params.always_backup {
action = Some(Action::Backup);
}
Self { action, params }
}
fn process_file(&mut self, sls: PathBuf) -> anyhow::Result<()> {
let file = fs::File::open(&sls).with_context(|| {
format!("Tried to open {}, but unexpectedly failed.", sls.display())
})?;
let reader = io::BufReader::new(file);
for (i, line) in reader.lines().enumerate() {
let line_no = (i + 1) as u64;
let line = line.with_context(|| {
format!("Error reading line {} of file {}.", line_no, sls.display())
})?;
self.process_line(&sls, line_no, line)?;
}
Ok(())
}
fn process_line(&mut self, sls: &Path, line_no: u64, line: String) -> anyhow::Result<()> {
let stdout = io::stdout();
match line::line_type(&line) {
LineType::Empty | LineType::Comment => {
return Ok(());
}
LineType::Invalid(invalid) => {
let err_mess = match invalid {
Invalid::NoMatch => format!(
"Invalid line in {}, line number {}.
Can't match up against the symlink specification format.",
sls.to_string_lossy(),
line_no
),
Invalid::TargetDoesNotExist => format!(
"Invalid line in {}, line number {}.
The target does not exist.",
sls.to_string_lossy(),
line_no
),
};
prompt::error_prompt(&err_mess)?;
}
LineType::SlsSpec { target, link } => {
let link_str = link.to_string_lossy();
if !link.is_symlink() && !link.exists() {
unix::fs::symlink(&target, &link).with_context(|| {
format!(
"Failed to create {} -> {}",
link_str,
target.to_string_lossy()
)
})?;
println!("(d) {} -> {}", link_str, target.to_string_lossy());
return Ok(());
}
if link.is_symlink()
&& fs::read_link(&link).with_context(|| format!("A symlink of path {} already exists, but failed to read it to check if it is the one you want to create or not.
Nothing was done. Check for a problem and rerun this program.", link_str))?
== target
{
println!("{}", format!("(.) {} -> {}", link_str, target.to_string_lossy()).dark_grey());
return Ok(());
}
if let Some(ref action) = self.action {
match action {
Action::Skip => utils::skip(stdout, &target, &link)?,
Action::Backup => {
utils::backup(stdout, &self.params.backup_dir, &target, &link)?
}
Action::Overwrite => utils::overwrite(stdout, &target, &link)?,
}
return Ok(());
}
match prompt::already_exist_prompt(&target.to_string_lossy(), &link_str)? {
AlreadyExistPromptOptions::Skip => {
utils::skip(stdout, &target, &link)?;
}
AlreadyExistPromptOptions::AlwaysSkip => {
utils::skip(stdout, &target, &link)?;
self.action = Some(Action::Skip);
}
AlreadyExistPromptOptions::Backup => {
utils::backup(stdout, &self.params.backup_dir, &target, &link)?
}
AlreadyExistPromptOptions::AlwaysBackup => {
utils::backup(stdout, &self.params.backup_dir, &target, &link)?;
self.action = Some(Action::Backup);
}
AlreadyExistPromptOptions::Overwrite => {
utils::overwrite(stdout, &target, &link)?;
}
AlreadyExistPromptOptions::AlwaysOverwrite => {
utils::overwrite(stdout, &target, &link)?;
self.action = Some(Action::Overwrite);
}
}
}
}
Ok(())
}
pub fn run(mut self) -> anyhow::Result<()> {
let dir = Dir::build(self.params.dir.clone())?;
for sls in dir.iter_on_sls_files(&self.params.filename[..]) {
self.process_file(sls)?;
}
Ok(())
}
}