1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
//! badm is a command-line tool use to store dotfiles, or configuration files.

#![allow(clippy::all)]
// #![deny(missing_docs)]
#![allow(dead_code)]

pub mod commands;
pub(crate) mod config;
mod errors;
pub mod paths;

pub use crate::config::Config;
pub use crate::errors::InputError;

#[macro_use] extern crate failure;

use std::fs::{self, File};
use std::io::{self, prelude::*, BufWriter};
use std::path::{Path, PathBuf};

// TODO: create dotfile struct

pub struct DirScanner {
    entries: Vec<PathBuf>,
    recursive: bool,
}

impl DirScanner {
    pub fn get_entries(mut self, dir: &Path) -> io::Result<Vec<PathBuf>> {
        self.collect_entries(dir)?;

        self.entries = self
            .entries
            .into_iter()
            .map(|path| {
                if path.is_relative() {
                    fs::canonicalize(path)
                } else {
                    Ok(path)
                }
            })
            .filter_map(Result::ok)
            .collect();

        Ok(self.entries)
    }

    fn new() -> Self {
        DirScanner {
            entries: Vec::new(),
            recursive: false,
        }
    }

    pub fn recursive(mut self) -> Self {
        self.recursive = true;
        self
    }

    fn collect_entries(&mut self, dir: &Path) -> io::Result<()> {
        if dir.is_dir() {
            for entry in fs::read_dir(dir)? {
                let path = entry.map(|e| e.path())?;

                if path.is_dir() && self.recursive {
                    if !path.ends_with(".git") {
                        self.collect_entries(&path)?;
                    }
                } else {
                    self.entries.push(path)
                }
            }
        }
        Ok(())
    }
}

impl Default for DirScanner {
    fn default() -> Self {
        DirScanner {
            entries: vec![],
            recursive: false,
        }
    }
}

/// Moves, stores, and creates files and symlinks.
pub struct FileHandler;

impl FileHandler {
    /// Store a file in the dotfiles directory, create a symlink at the original
    /// source of the stowed file.
    pub fn store_file(src: &Path, dst: &Path) -> io::Result<()> {
        FileHandler::move_file(src, dst)?;
        FileHandler::create_symlink(dst, src)
    }

    /// Read file at path src and write to created/truncated file at path dst
    pub fn move_file(src: &Path, dst: &Path) -> io::Result<()> {
        // read file path to String
        let contents = crate::paths::read_path(src)?;

        // write String contents to dst file
        let dst_file = File::create(dst)?;
        let mut writer = BufWriter::new(dst_file);
        writer.write_all(contents.as_bytes())?;

        // remove file at src location
        fs::remove_file(&src)
    }

    /// Create a symlink at "dst" pointing to "src."
    ///
    /// For Unix platforms, std::os::unix::fs::symlink is used to create
    /// symlinks. For Windows, std::os::windows::fs::symlink_file is used.
    pub fn create_symlink(src: &Path, dst: &Path) -> io::Result<()> {
        #[cfg(not(target_os = "windows"))] use std::os::unix::fs::symlink;

        #[cfg(target_os = "windows")]
        use std::os::windows::fs::symlink_file as symlink;
        symlink(src, dst)
    }
}