aboutsummaryrefslogtreecommitdiff
path: root/dotup_cli/src
diff options
context:
space:
mode:
Diffstat (limited to 'dotup_cli/src')
-rw-r--r--dotup_cli/src/.main.rs.rustfmt87
-rw-r--r--dotup_cli/src/commands/init.rs20
-rw-r--r--dotup_cli/src/commands/install.rs31
-rw-r--r--dotup_cli/src/commands/link.rs181
-rw-r--r--dotup_cli/src/commands/mod.rs11
-rw-r--r--dotup_cli/src/commands/uninstall.rs31
-rw-r--r--dotup_cli/src/commands/unlink.rs38
-rw-r--r--dotup_cli/src/commands/utils.rs100
-rw-r--r--dotup_cli/src/config.rs6
-rw-r--r--dotup_cli/src/main.rs87
10 files changed, 592 insertions, 0 deletions
diff --git a/dotup_cli/src/.main.rs.rustfmt b/dotup_cli/src/.main.rs.rustfmt
new file mode 100644
index 0000000..6d020e1
--- /dev/null
+++ b/dotup_cli/src/.main.rs.rustfmt
@@ -0,0 +1,87 @@
1#![allow(unused)]
2
3pub mod commands;
4pub mod config;
5
6pub use config::Config;
7
8pub mod prelude {
9 pub use super::Config;
10 pub use dotup::{Archive, Depot, DepotConfig, Link, LinkDesc, LinkID};
11}
12
13use clap::{AppSettings, Clap};
14use flexi_logger::Logger;
15use std::{
16 collections::HashMap,
17 iter::FromIterator,
18 path::{Path, PathBuf},
19};
20
21use prelude::*;
22
23const DEFAULT_DEPOT_NAME: &str = "depot.toml";
24
25#[derive(Clap)]
26#[clap(version = "0.1", author = "d464")]
27#[clap(setting = AppSettings::ColoredHelp)]
28struct Opts {
29 /// Path to the depot file.
30 #[clap(long, default_value = DEFAULT_DEPOT_NAME)]
31 depot: PathBuf,
32
33 /// Disable output to the console
34 #[clap(short, long)]
35 quiet: bool,
36
37 /// A level of verbosity, and can be used multiple times
38 ///
39 /// Level 0 - Warnings (Default)
40 /// Level 1 - Info
41 /// Level 2 - Debug
42 /// Level 3 - Trace
43 #[clap(short, long, parse(from_occurrences))]
44 verbose: i32,
45
46 #[clap(subcommand)]
47 subcmd: SubCommand,
48}
49
50#[derive(Clap)]
51enum SubCommand {
52 Init(commands::init::Opts),
53 Link(commands::link::Opts),
54 Unlink(commands::unlink::Opts),
55 Install(commands::install::Opts),
56 Uninstall(commands::uninstall::Opts),
57}
58
59fn main() -> anyhow::Result<()> {
60 let opts = Opts::parse();
61
62 if !opts.quiet {
63 let log_level = match opts.verbose {
64 0 => "warn",
65 1 => "info",
66 2 => "debug",
67 3 | _ => "trace",
68 };
69
70 Logger::try_with_env_or_str(log_level)?
71 .format(flexi_logger::colored_default_format)
72 //.set_palette("196;208;32;198;15".to_string())
73 .start()?;
74 }
75
76 let config = Config {
77 archive_path: opts.depot,
78 };
79
80 match opts.subcmd {
81 SubCommand::Init(opts) => commands::init::main(config, opts),
82 SubCommand::Link(opts) => commands::link::main(config, opts),
83 SubCommand::Unlink(opts) => commands::unlink::main(config, opts),
84 SubCommand::Install(opts) => commands::install::main(config, opts),
85 SubCommand::Uninstall(opts) => commands::uninstall::main(config, opts),
86 }
87}
diff --git a/dotup_cli/src/commands/init.rs b/dotup_cli/src/commands/init.rs
new file mode 100644
index 0000000..bfec6ca
--- /dev/null
+++ b/dotup_cli/src/commands/init.rs
@@ -0,0 +1,20 @@
1use clap::Clap;
2
3use super::prelude::*;
4
5#[derive(Clap)]
6pub struct Opts {}
7
8pub fn main(config: Config, opts: Opts) -> anyhow::Result<()> {
9 if !dotup::utils::is_file(&config.archive_path)? {
10 let archive = Archive::default();
11 log::info!("Creating archive");
12 utils::write_archive(&config.archive_path, &archive)?;
13 } else {
14 log::info!(
15 "Archive file already exists : {}",
16 config.archive_path.display()
17 );
18 }
19 Ok(())
20}
diff --git a/dotup_cli/src/commands/install.rs b/dotup_cli/src/commands/install.rs
new file mode 100644
index 0000000..72cabf3
--- /dev/null
+++ b/dotup_cli/src/commands/install.rs
@@ -0,0 +1,31 @@
1use clap::Clap;
2use std::path::PathBuf;
3
4use super::prelude::*;
5
6#[derive(Clap)]
7pub struct Opts {
8 /// The location where links will be installed to.
9 /// Defaults to home directory.
10 #[clap(long)]
11 install_base: Option<PathBuf>,
12
13 /// The files/directories to install
14 #[clap(required = true, min_values = 1)]
15 paths: Vec<PathBuf>,
16}
17
18pub fn main(config: Config, opts: Opts) -> anyhow::Result<()> {
19 let install_base = match opts.install_base {
20 Some(path) => path,
21 None => utils::home_directory()?,
22 };
23 let depot = utils::read_depot(&config.archive_path)?;
24
25 for link in utils::collect_links_by_base_paths(&depot, &opts.paths) {
26 log::info!("Installing link {}", link);
27 depot.install_link(link, &install_base)?;
28 }
29
30 Ok(())
31}
diff --git a/dotup_cli/src/commands/link.rs b/dotup_cli/src/commands/link.rs
new file mode 100644
index 0000000..fd99253
--- /dev/null
+++ b/dotup_cli/src/commands/link.rs
@@ -0,0 +1,181 @@
1use clap::Clap;
2use std::{
3 fs::{DirEntry, Metadata},
4 path::{Path, PathBuf},
5};
6
7use super::prelude::*;
8
9#[derive(Clap)]
10pub struct Opts {
11 #[clap(long)]
12 directory: bool,
13
14 paths: Vec<PathBuf>,
15}
16
17/*
18 config/
19 nvim/
20 init.vim
21 lua/
22 setup.lua
23 bash/
24 .bashrc
25
26 link nvim .config/nvim
27 nvim/init.vim -> .config/nvim/init.vim
28 nvim/lua/setup.lua -> config/nvim/lua/setup.lua
29
30 link bash .
31 bash/.bashrc -> ./.bashrc
32
33 link --directory scripts .scripts
34 scripts/ -> ./.scripts
35*/
36
37pub fn main(config: Config, opts: Opts) -> anyhow::Result<()> {
38 let mut depot = utils::read_depot(&config.archive_path)?;
39
40 let (origins, destination) = match opts.paths.as_slice() {
41 p @ [] | p @ [_] => (p, None),
42 [o @ .., dest] => (o, Some(dest)),
43 _ => unreachable!(),
44 };
45
46 if let Some(destination) = destination {
47 for path in collect_file_type(origins, FileType::File)? {
48 let link_desc = LinkDesc {
49 origin: path,
50 destination: destination.clone(),
51 };
52 log::info!("Creating link : {}", link_desc);
53 depot.create_link(link_desc)?;
54 }
55 } else {
56 let base_path = match origins {
57 [] => std::env::current_dir()?,
58 [path] => path.clone(),
59 _ => unreachable!(),
60 };
61
62 for link in utils::collect_links_by_base_paths(&depot, std::iter::once(base_path)) {
63 log::info!("{}", link);
64 }
65 }
66
67 //if let Some(destination) = destination {
68 // for origin in origins {
69 // let origin_canonical = origin.canonicalize()?;
70 // let base = if origin_canonical.is_file() {
71 // origin_canonical.parent().unwrap().to_path_buf()
72 // } else {
73 // origin_canonical.to_path_buf()
74 // };
75
76 // link(&mut depot, origin.as_path(), destination.as_path(), &base)?;
77 // }
78 //} else {
79 // log::warn!("Missing destination");
80 //}
81
82 utils::write_depot(&depot)?;
83
84 Ok(())
85}
86
87#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
88enum FileType {
89 File,
90 Directory,
91}
92
93/// Collects canonical files of the given type starting from, and including, entry_paths
94fn collect_file_type(
95 entry_paths: impl IntoIterator<Item = impl AsRef<Path>>,
96 collect_type: FileType,
97) -> anyhow::Result<Vec<PathBuf>> {
98 let entry_paths: Vec<PathBuf> = entry_paths
99 .into_iter()
100 .map(|p| p.as_ref().to_path_buf())
101 .collect();
102 let mut collected = Vec::new();
103 let mut pending: Vec<_> = entry_paths.iter().cloned().filter(|p| p.is_dir()).collect();
104
105 for path in entry_paths {
106 let path = path.canonicalize()?;
107 if (path.is_file() && collect_type == FileType::File)
108 || (path.is_dir() && collect_type == FileType::Directory)
109 {
110 collected.push(path);
111 }
112 }
113
114 while let Some(dir_path) = pending.pop() {
115 for entry in dir_path.read_dir()? {
116 let entry = entry?;
117 let filetype = entry.file_type()?;
118
119 if filetype.is_file() && collect_type == FileType::File {
120 collected.push(entry.path());
121 } else if filetype.is_dir() {
122 if collect_type == FileType::Directory {
123 collected.push(entry.path());
124 }
125 pending.push(entry.path());
126 }
127 }
128 }
129
130 Ok(collected)
131}
132
133fn link(depot: &mut Depot, origin: &Path, destination: &Path, base: &Path) -> anyhow::Result<()> {
134 let metadata = std::fs::metadata(origin)?;
135 if metadata.is_file() {
136 link_file(depot, origin, destination, base)?;
137 } else if metadata.is_dir() {
138 link_directory_recursive(depot, origin, destination, base)?;
139 } else {
140 unimplemented!()
141 }
142 Ok(())
143}
144
145fn link_file(
146 depot: &mut Depot,
147 origin: &Path,
148 destination: &Path,
149 base: &Path,
150) -> anyhow::Result<()> {
151 let origin_canonical = origin
152 .canonicalize()
153 .expect("Failed to canonicalize origin path");
154 let partial = origin_canonical
155 .strip_prefix(base)
156 .expect("Failed to remove prefix from origin path");
157 let destination = destination.join(partial);
158
159 let link_desc = LinkDesc {
160 origin: origin_canonical,
161 destination,
162 };
163
164 log::debug!("Linking file {:#?}", link_desc);
165 depot.create_link(link_desc)?;
166
167 Ok(())
168}
169
170fn link_directory_recursive(
171 depot: &mut Depot,
172 dir_path: &Path,
173 destination: &Path,
174 base: &Path,
175) -> anyhow::Result<()> {
176 for origin in dir_path.read_dir()? {
177 let origin = origin?.path();
178 link(depot, &origin, destination, base)?;
179 }
180 Ok(())
181}
diff --git a/dotup_cli/src/commands/mod.rs b/dotup_cli/src/commands/mod.rs
new file mode 100644
index 0000000..f372662
--- /dev/null
+++ b/dotup_cli/src/commands/mod.rs
@@ -0,0 +1,11 @@
1pub mod init;
2pub mod install;
3pub mod link;
4pub mod uninstall;
5pub mod unlink;
6pub mod utils;
7
8mod prelude {
9 pub use super::utils;
10 pub use crate::prelude::*;
11}
diff --git a/dotup_cli/src/commands/uninstall.rs b/dotup_cli/src/commands/uninstall.rs
new file mode 100644
index 0000000..dad55a5
--- /dev/null
+++ b/dotup_cli/src/commands/uninstall.rs
@@ -0,0 +1,31 @@
1use clap::Clap;
2use std::path::PathBuf;
3
4use super::prelude::*;
5
6#[derive(Clap)]
7pub struct Opts {
8 /// The location where links will be uninstalled from.
9 /// Defaults to home directory.
10 #[clap(long)]
11 install_base: Option<PathBuf>,
12
13 /// The files/directories to uninstall
14 #[clap(required = true, min_values = 1)]
15 paths: Vec<PathBuf>,
16}
17
18pub fn main(config: Config, opts: Opts) -> anyhow::Result<()> {
19 let install_base = match opts.install_base {
20 Some(path) => path,
21 None => utils::home_directory()?,
22 };
23 let depot = utils::read_depot(&config.archive_path)?;
24
25 for link in utils::collect_links_by_base_paths(&depot, &opts.paths) {
26 log::info!("Uninstalling link : {}", link);
27 depot.uninstall_link(link, &install_base)?;
28 }
29
30 Ok(())
31}
diff --git a/dotup_cli/src/commands/unlink.rs b/dotup_cli/src/commands/unlink.rs
new file mode 100644
index 0000000..7ebb19c
--- /dev/null
+++ b/dotup_cli/src/commands/unlink.rs
@@ -0,0 +1,38 @@
1use clap::Clap;
2use std::{
3 fs::{DirEntry, Metadata},
4 path::{Path, PathBuf},
5};
6
7use super::prelude::*;
8
9#[derive(Clap)]
10pub struct Opts {
11 /// Specifies the install base if the links are also to be uninstalled.
12 #[clap(long)]
13 uninstall: Option<PathBuf>,
14
15 #[clap(required = true, min_values = 1)]
16 paths: Vec<PathBuf>,
17}
18
19pub fn main(config: Config, opts: Opts) -> anyhow::Result<()> {
20 let mut depot = utils::read_depot(&config.archive_path)?;
21
22 for link_id in utils::collect_link_ids_by_base_paths(&depot, &opts.paths) {
23 let link = depot.get_link(link_id).unwrap();
24 log::info!(
25 "Unlinking(uninstall = {}) : {}",
26 opts.uninstall.is_some(),
27 link
28 );
29 if let Some(ref install_base) = opts.uninstall {
30 depot.uninstall_link(link, &install_base)?;
31 }
32 depot.remove_link(link_id);
33 }
34
35 utils::write_depot(&depot)?;
36
37 Ok(())
38}
diff --git a/dotup_cli/src/commands/utils.rs b/dotup_cli/src/commands/utils.rs
new file mode 100644
index 0000000..4160078
--- /dev/null
+++ b/dotup_cli/src/commands/utils.rs
@@ -0,0 +1,100 @@
1use std::path::{Path, PathBuf};
2
3use crate::prelude::*;
4
5pub fn home_directory() -> anyhow::Result<PathBuf> {
6 match std::env::var("HOME") {
7 Ok(val) => Ok(PathBuf::from(val)),
8 Err(e) => {
9 log::error!("Failed to get home directory from enviornment variable");
10 Err(e.into())
11 }
12 }
13}
14
15pub fn write_archive(path: impl AsRef<Path>, archive: &Archive) -> anyhow::Result<()> {
16 let path = path.as_ref();
17 log::debug!("Writing archive to {}", path.display());
18 match dotup::archive_write(path, archive) {
19 Ok(_) => Ok(()),
20 Err(e) => {
21 log::error!(
22 "Failed to write archive to : {}\nError : {}",
23 path.display(),
24 e
25 );
26 Err(e.into())
27 }
28 }
29}
30
31pub fn write_depot(depot: &Depot) -> anyhow::Result<()> {
32 let write_path = depot.archive_path();
33 let archive = depot.archive();
34 match dotup::archive_write(write_path, &archive) {
35 Ok(_) => Ok(()),
36 Err(e) => {
37 log::error!(
38 "Failed to write depot archive to : {}\nError : {}",
39 write_path.display(),
40 e
41 );
42 Err(e.into())
43 }
44 }
45}
46
47pub fn read_archive(path: impl AsRef<Path>) -> anyhow::Result<Archive> {
48 let path = path.as_ref();
49 match dotup::archive_read(path) {
50 Ok(archive) => Ok(archive),
51 Err(e) => {
52 log::error!(
53 "Failed to read archive from : {}\nError : {}",
54 path.display(),
55 e
56 );
57 Err(e.into())
58 }
59 }
60}
61
62pub fn read_depot(archive_path: impl AsRef<Path>) -> anyhow::Result<Depot> {
63 let archive_path = archive_path.as_ref().to_path_buf();
64 let archive = read_archive(&archive_path)?;
65 let depot_config = DepotConfig {
66 archive_path,
67 archive,
68 };
69 let depot = Depot::new(depot_config)?;
70 Ok(depot)
71}
72
73pub fn collect_links_by_base_paths(
74 depot: &Depot,
75 paths: impl IntoIterator<Item = impl AsRef<Path>>,
76) -> Vec<&Link> {
77 let canonical_paths: Vec<_> = paths
78 .into_iter()
79 .map(|p| p.as_ref().canonicalize().unwrap())
80 .collect();
81
82 depot
83 .links()
84 .filter(|&l| {
85 canonical_paths
86 .iter()
87 .any(|p| l.origin_canonical().starts_with(p))
88 })
89 .collect()
90}
91
92pub fn collect_link_ids_by_base_paths(
93 depot: &Depot,
94 paths: impl IntoIterator<Item = impl AsRef<Path>>,
95) -> Vec<LinkID> {
96 collect_links_by_base_paths(depot, paths)
97 .into_iter()
98 .map(|l| l.id())
99 .collect()
100}
diff --git a/dotup_cli/src/config.rs b/dotup_cli/src/config.rs
new file mode 100644
index 0000000..ac4fc66
--- /dev/null
+++ b/dotup_cli/src/config.rs
@@ -0,0 +1,6 @@
1use std::path::PathBuf;
2
3#[derive(Debug)]
4pub struct Config {
5 pub archive_path: PathBuf,
6}
diff --git a/dotup_cli/src/main.rs b/dotup_cli/src/main.rs
new file mode 100644
index 0000000..6d020e1
--- /dev/null
+++ b/dotup_cli/src/main.rs
@@ -0,0 +1,87 @@
1#![allow(unused)]
2
3pub mod commands;
4pub mod config;
5
6pub use config::Config;
7
8pub mod prelude {
9 pub use super::Config;
10 pub use dotup::{Archive, Depot, DepotConfig, Link, LinkDesc, LinkID};
11}
12
13use clap::{AppSettings, Clap};
14use flexi_logger::Logger;
15use std::{
16 collections::HashMap,
17 iter::FromIterator,
18 path::{Path, PathBuf},
19};
20
21use prelude::*;
22
23const DEFAULT_DEPOT_NAME: &str = "depot.toml";
24
25#[derive(Clap)]
26#[clap(version = "0.1", author = "d464")]
27#[clap(setting = AppSettings::ColoredHelp)]
28struct Opts {
29 /// Path to the depot file.
30 #[clap(long, default_value = DEFAULT_DEPOT_NAME)]
31 depot: PathBuf,
32
33 /// Disable output to the console
34 #[clap(short, long)]
35 quiet: bool,
36
37 /// A level of verbosity, and can be used multiple times
38 ///
39 /// Level 0 - Warnings (Default)
40 /// Level 1 - Info
41 /// Level 2 - Debug
42 /// Level 3 - Trace
43 #[clap(short, long, parse(from_occurrences))]
44 verbose: i32,
45
46 #[clap(subcommand)]
47 subcmd: SubCommand,
48}
49
50#[derive(Clap)]
51enum SubCommand {
52 Init(commands::init::Opts),
53 Link(commands::link::Opts),
54 Unlink(commands::unlink::Opts),
55 Install(commands::install::Opts),
56 Uninstall(commands::uninstall::Opts),
57}
58
59fn main() -> anyhow::Result<()> {
60 let opts = Opts::parse();
61
62 if !opts.quiet {
63 let log_level = match opts.verbose {
64 0 => "warn",
65 1 => "info",
66 2 => "debug",
67 3 | _ => "trace",
68 };
69
70 Logger::try_with_env_or_str(log_level)?
71 .format(flexi_logger::colored_default_format)
72 //.set_palette("196;208;32;198;15".to_string())
73 .start()?;
74 }
75
76 let config = Config {
77 archive_path: opts.depot,
78 };
79
80 match opts.subcmd {
81 SubCommand::Init(opts) => commands::init::main(config, opts),
82 SubCommand::Link(opts) => commands::link::main(config, opts),
83 SubCommand::Unlink(opts) => commands::unlink::main(config, opts),
84 SubCommand::Install(opts) => commands::install::main(config, opts),
85 SubCommand::Uninstall(opts) => commands::uninstall::main(config, opts),
86 }
87}