Skip to content

Commit

Permalink
Merge pull request #8 from rafaeldelboni/refact/renames-configuration…
Browse files Browse the repository at this point in the history
…-override-destination

refact: Add Configuration to override the destination
  • Loading branch information
rafaeldelboni authored Aug 18, 2022
2 parents 14ef505 + dc09dc4 commit 92df179
Show file tree
Hide file tree
Showing 7 changed files with 189 additions and 52 deletions.
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,17 @@ Tool for managing dotfiles directories; Heavily based on [rcm](http://thoughtbot
- [x] Option to override host name from config (-B --hostname)
- [x] Add defaults to the internal settings structure if nothing is defined
- [x] Read .dotfile folder(s) and files structure and store it
- [x] Consider multiple dotfiles folders configuration and reorganize file list (-a --add-dir)
- [x] Consider ignore files and filters then from the list (-x --exclude)
- [x] Consider .dot files and filters then from the list
- [ ] Consider inclusion list for extra files and reorganize file list (-i --include)
- [ ] Consider multiple dotfiles folders configuration and reorganize file list (-a --add-dir)
- [ ] Consider tags and tag folders and reorganize file list (-t --tag)
- [ ] Consider hosts and host folders and reorganize file list
- [ ] Dialog to ask to override existing files if already exists in your home directory but does not match the file in your dotfiles directory
- [ ] Option to always override (-f --force)
- [ ] Add drop/delete command, this deletes dotfiles managed by paro (-d --down)
- [ ] Add dry-run command (-D --dry-run)
- [ ] Add version command (-v --version)
- [ ] Consider .dot files and filters then from the list
- CI Pipeline to build releases
- [ ] Linux (x86_64)
- [ ] Linux (arm)
Expand All @@ -33,7 +33,7 @@ Tool for managing dotfiles directories; Heavily based on [rcm](http://thoughtbot
- [ ] Documentation
- [ ] Instalation Script (Like rustup install)
- Extras Features
- [ ] Configuration to override the destination file path will be symlinked or copied (-o --override-path)
- [x] Configuration to override the destination file path will be symlinked or copied (-n --destination)
- [ ] Sync command (delete files that are set to be ignored) (-S --sync)
- [ ] Create an inclusion list for already doted files in your dotfiles directory to be included as symlink or copy (-I --include-dotted)
- [ ] Split config files in two where you have configs and ignore files in different files
Expand Down Expand Up @@ -77,6 +77,10 @@ Install files that match <file-pattern>. Despite being excluded by the -x flag o
setting in the config.
This can be repeated with additional patterns.

#### -n, --destination <folder-name>
Override the destination folder by <folder-name>. By default this value is the current
user home directory.

#### -t, --tag <tag>
Do not install files that match <file-pattern>. Tagged files go in a directory named for
the tag, prefixed with tag-. Therefore, files under .dotfiles/tag-git are only installed
Expand Down
54 changes: 47 additions & 7 deletions src/clap_parser.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::settings::ParoSettings;
use crate::types::Settings;
use clap::{App, Arg, ArgAction, ArgMatches, Command};

pub struct ClapParser {
Expand All @@ -13,6 +13,13 @@ fn to_vec_string(matches: &ArgMatches, id: &str) -> Vec<String> {
.collect()
}

fn to_string_unwrap(matches: &ArgMatches, id: &str) -> String {
matches
.get_one::<String>(id)
.unwrap_or(&"".to_string())
.to_string()
}

impl ClapParser {
pub fn new() -> Self {
let app = Command::new("paro")
Expand Down Expand Up @@ -78,6 +85,19 @@ impl ClapParser {
.takes_value(true)
.action(ArgAction::Append),
)
.arg(
Arg::new("destination")
.short('n')
.long("destination")
.value_name("folder-name")
.help("Override the destination folder by <folder-name>.")
.long_help(
"Override the destination folder by <folder-name>. \
By default this value is the current user home directory.",
)
.takes_value(true)
.action(ArgAction::Set),
)
.arg(
Arg::new("hostname")
.short('B')
Expand Down Expand Up @@ -128,21 +148,19 @@ impl ClapParser {
Self { clap: app }
}

pub fn into_settings(self, manual_args: Vec<&str>) -> ParoSettings {
pub fn into_settings(self, manual_args: Vec<&str>) -> Settings {
let matches = if manual_args.is_empty() {
self.clap.get_matches()
} else {
self.clap.get_matches_from(manual_args)
};
ParoSettings {
Settings {
tags: to_vec_string(&matches, "tags"),
excludes: to_vec_string(&matches, "excludes"),
includes: to_vec_string(&matches, "includes"),
directories: to_vec_string(&matches, "directories"),
hostname: matches
.get_one::<String>("hostname")
.unwrap_or(&"".to_string())
.to_string(),
destination: to_string_unwrap(&matches, "destination"),
hostname: to_string_unwrap(&matches, "hostname"),
force: matches.get_one::<bool>("force").copied().unwrap(),
down: matches.get_one::<bool>("down").copied().unwrap(),
dry_run: matches.get_one::<bool>("dry-run").copied().unwrap(),
Expand Down Expand Up @@ -171,13 +189,23 @@ mod tests {
);
}

#[test]
fn test_to_string_unwrap() {
let matches =
ClapParser::new()
.clap
.get_matches_from(vec!["paro", "-n", "/super/dir"]);
assert_eq!(to_string_unwrap(&matches, "destination"), "/super/dir");
}

#[test]
fn test_clap_defaults() {
let settings = ClapParser::new().into_settings(vec!["paro"]);
assert_eq!(settings.tags, Vec::<String>::new());
assert_eq!(settings.excludes, Vec::<String>::new());
assert_eq!(settings.includes, Vec::<String>::new());
assert_eq!(settings.directories, Vec::<String>::new());
assert_eq!(settings.destination, String::new());
assert_eq!(settings.hostname, String::new());
assert_eq!(settings.force, false);
assert_eq!(settings.down, false);
Expand Down Expand Up @@ -238,4 +266,16 @@ mod tests {
]);
assert_eq!(settings.hostname, "will-override-my-machine");
}

#[test]
fn test_clap_destination() {
let settings = ClapParser::new().into_settings(vec![
"paro",
"-n",
"/new/home",
"-n",
"/new/home2",
]);
assert_eq!(settings.destination, "/new/home2");
}
}
13 changes: 11 additions & 2 deletions src/config_parser.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::settings::ParoSettings;
use crate::types::Settings;
use config::{Config, File, FileFormat};

pub struct ConfigParser {
Expand All @@ -25,6 +25,8 @@ impl ConfigParser {
.unwrap()
.set_default("directories", Vec::<String>::new())
.unwrap()
.set_default("destination", String::new())
.unwrap()
.set_default("hostname", String::new())
.unwrap()
.set_default("force", false)
Expand All @@ -38,7 +40,7 @@ impl ConfigParser {
}
}

pub fn into_settings(self) -> ParoSettings {
pub fn into_settings(self) -> Settings {
self.config.try_deserialize().unwrap()
}
}
Expand All @@ -59,6 +61,7 @@ mod tests {
assert_eq!(settings.excludes, Vec::<String>::new());
assert_eq!(settings.includes, Vec::<String>::new());
assert_eq!(settings.directories, Vec::<String>::new());
assert_eq!(settings.destination, String::new());
assert_eq!(settings.hostname, String::new());
assert_eq!(settings.force, false);
assert_eq!(settings.down, false);
Expand Down Expand Up @@ -95,6 +98,12 @@ mod tests {
assert_eq!(settings.hostname, "hostname-in-config");
}

#[test]
fn test_config_destination() {
let settings = ConfigParser::new(&config_file()).into_settings();
assert_eq!(settings.destination, "/destination");
}

#[test]
fn test_config_force() {
let settings = ConfigParser::new(&config_file()).into_settings();
Expand Down
119 changes: 91 additions & 28 deletions src/files.rs
Original file line number Diff line number Diff line change
@@ -1,30 +1,61 @@
use crate::types::PathBufPair;
use regex::RegexSet;
use std::path::{Path, PathBuf};
use walkdir::{DirEntry, WalkDir};

pub fn walk_directories(directories: Vec<String>) -> Vec<DirEntry> {
let mut paths: Vec<DirEntry> = Vec::<DirEntry>::new();
fn change_root_dir(
origin_path: &Path,
current: &String,
new: &String,
) -> PathBuf {
match origin_path.strip_prefix(Path::new(&current)) {
Ok(t) => PathBuf::from(new).join(t),
Err(_) => origin_path.to_path_buf(),
}
}

fn is_hidden(entry: &DirEntry) -> bool {
entry
.file_name()
.to_str()
.map(|s| s.starts_with('.'))
.unwrap_or(false)
}

// TODO use paro Settings as argument
pub fn walk_directories(
directories: Vec<String>,
destination: String,
) -> Vec<PathBufPair> {
let mut paths: Vec<PathBufPair> = Vec::<PathBufPair>::new();
for dir in directories {
for entry in WalkDir::new(dir) {
match entry {
Ok(t) => paths.push(t),
Err(e) => println!("Error: {}", e),
}
let mut entries = WalkDir::new(&dir).into_iter();
loop {
match entries.next() {
None => break,
Some(Ok(entry)) => {
if is_hidden(&entry) {
if entry.file_type().is_dir() && entry.depth() > 0 {
entries.skip_current_dir();
}
continue;
}
paths.push(PathBufPair(
entry.clone(),
change_root_dir(entry.path(), &dir, &destination),
));
}
Some(Err(err)) => println!("ERROR: {}", err),
};
}
}
paths
}

pub fn remove_files(files: &mut Vec<DirEntry>, excludes: Vec<String>) {
pub fn remove_files(files: &mut Vec<PathBufPair>, excludes: Vec<String>) {
println!("{:?}", excludes);
let set = RegexSet::new(excludes).unwrap();
files.retain(|x| {
println!(
"{:?}|{:?}\n",
set.matches(x.path().to_str().unwrap()),
x.path().to_str().unwrap()
);
!set.is_match(x.path().to_str().unwrap())
});
files.retain(|x| !set.is_match(x.0.path().to_str().unwrap()));
}

#[cfg(test)]
Expand All @@ -33,36 +64,41 @@ mod tests {

#[test]
fn test_walk_directories() {
let files = walk_directories(vec![
"tests/example-dotfiles/folder".to_string(),
"tests/example-dotfiles/tag-um".to_string(),
]);
let files = walk_directories(
vec![
"tests/example-dotfiles/folder".to_string(),
"tests/example-dotfiles/tag-um".to_string(),
],
"/destiny".to_string(),
);
let mut str_files: Vec<String> = files
.clone()
.into_iter()
.map(|e| e.path().to_str().unwrap().to_string())
.map(|e| e.0.path().to_str().unwrap().to_string())
.collect();
str_files.sort();

assert_eq!(str_files.len(), 5);
assert_eq!(str_files.len(), 4);
assert_eq!(
str_files,
vec![
"tests/example-dotfiles/folder",
"tests/example-dotfiles/folder/something.txt",
"tests/example-dotfiles/tag-um",
"tests/example-dotfiles/tag-um/.file.txt",
"tests/example-dotfiles/tag-um/file.txt",
]
);
}

#[test]
fn test_remove_files() {
let mut files = walk_directories(vec![
"tests/example-dotfiles/folder".to_string(),
"tests/example-dotfiles/tag-um".to_string(),
]);
let mut files = walk_directories(
vec![
"tests/example-dotfiles/folder".to_string(),
"tests/example-dotfiles/tag-um".to_string(),
],
"/destiny".to_string(),
);
remove_files(
&mut files,
vec![
Expand All @@ -74,7 +110,7 @@ mod tests {
let mut str_files: Vec<String> = files
.clone()
.into_iter()
.map(|e| e.path().to_str().unwrap().to_string())
.map(|e| e.0.path().to_str().unwrap().to_string())
.collect();
str_files.sort();

Expand All @@ -87,4 +123,31 @@ mod tests {
]
);
}

#[test]
fn test_change_root_dir() {
let path = Path::new("/test/file.txt");
assert_eq!(
change_root_dir(path, &"/test".to_string(), &"/new".to_string())
.to_string_lossy(),
"/new/file.txt"
);

// should ignore if root is not in the current path
let path2 = Path::new("/test/file.txt");
assert_eq!(
change_root_dir(path2, &"/non-root".to_string(), &"/new".to_string())
.to_string_lossy(),
"/test/file.txt"
);
}

#[test]
fn test_is_hidden() {
let mut files = WalkDir::new("tests/example-dotfiles/tag-um/")
.sort_by_file_name()
.into_iter();
assert_eq!(is_hidden(&files.next().unwrap().unwrap()), false);
assert_eq!(is_hidden(&files.next().unwrap().unwrap()), true);
}
}
11 changes: 7 additions & 4 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,23 @@ mod clap_parser;
mod config_parser;
mod files;
mod nix_helper;
mod settings;
mod types;

use crate::{clap_parser::ClapParser, config_parser::ConfigParser};

fn main() {
let mut config_files = nix_helper::get_default_config_files();
config_files.push("tests/settings".to_string()); // just for now while testing
let defaults = settings::ParoSettings::defaults();
let defaults = types::Settings::defaults();
let config = ConfigParser::new(&config_files).into_settings();
let clap = ClapParser::new().into_settings(vec![]);
let merged = defaults.clone().merge(config.clone()).merge(clap.clone());

// { TODO move this to a function
let mut files = files::walk_directories(merged.directories.clone());
let mut files = files::walk_directories(
merged.directories.clone(),
merged.destination.clone(),
);
files::remove_files(&mut files, merged.excludes.clone());
// }

Expand All @@ -30,6 +33,6 @@ fn main() {

println!("files:");
for file in files {
println!("{:?}", file.path());
println!("{:?} {:?} -> {:?}", file.0.depth(), file.0.path(), file.1);
}
}
Loading

0 comments on commit 92df179

Please sign in to comment.