aboutsummaryrefslogtreecommitdiff
path: root/dotup_cli/src/commands
diff options
context:
space:
mode:
Diffstat (limited to 'dotup_cli/src/commands')
-rw-r--r--dotup_cli/src/commands/init.rs22
-rw-r--r--dotup_cli/src/commands/install.rs25
-rw-r--r--dotup_cli/src/commands/link.rs134
-rw-r--r--dotup_cli/src/commands/mod.rs11
-rw-r--r--dotup_cli/src/commands/mv.rs53
-rw-r--r--dotup_cli/src/commands/status.rs175
-rw-r--r--dotup_cli/src/commands/uninstall.rs26
-rw-r--r--dotup_cli/src/commands/unlink.rs43
8 files changed, 0 insertions, 489 deletions
diff --git a/dotup_cli/src/commands/init.rs b/dotup_cli/src/commands/init.rs
deleted file mode 100644
index 45129bf..0000000
--- a/dotup_cli/src/commands/init.rs
+++ /dev/null
@@ -1,22 +0,0 @@
1use super::prelude::*;
2
3/// Creates an empty depot file if one doesnt already exist.
4///
5/// By default this will create the file in the current directory
6/// but the --depot flag can be used to change this path.
7#[derive(Parser)]
8pub struct Opts {}
9
10pub fn main(config: Config, opts: Opts) -> anyhow::Result<()> {
11 if !dotup::utils::is_file(&config.archive_path)? {
12 let archive = Archive::default();
13 log::info!("Creating archive at {}", &config.archive_path.display());
14 utils::write_archive(&config.archive_path, &archive)?;
15 } else {
16 log::warn!(
17 "Archive file already exists : '{}'",
18 config.archive_path.display()
19 );
20 }
21 Ok(())
22}
diff --git a/dotup_cli/src/commands/install.rs b/dotup_cli/src/commands/install.rs
deleted file mode 100644
index 6d9fbf7..0000000
--- a/dotup_cli/src/commands/install.rs
+++ /dev/null
@@ -1,25 +0,0 @@
1use std::path::PathBuf;
2
3use super::prelude::*;
4
5/// Install links. (Creates symlinks).
6///
7/// Installing a link will create the necessary directories.
8/// If a file or directory already exists at the location a link would be installed this command will fail.
9#[derive(Parser)]
10pub struct Opts {
11 /// The files/directories to install.
12 #[clap(min_values = 1, default_value = ".")]
13 paths: Vec<PathBuf>,
14}
15
16pub fn main(config: Config, opts: Opts) -> anyhow::Result<()> {
17 let depot = utils::read_depot(&config.archive_path)?;
18
19 for link in utils::collect_links_by_base_paths(&depot, &opts.paths) {
20 log::info!("Installing link {}", link);
21 depot.install_link(link, &config.install_path)?;
22 }
23
24 Ok(())
25}
diff --git a/dotup_cli/src/commands/link.rs b/dotup_cli/src/commands/link.rs
deleted file mode 100644
index d1f61ea..0000000
--- a/dotup_cli/src/commands/link.rs
+++ /dev/null
@@ -1,134 +0,0 @@
1use std::{
2 fs::{DirEntry, Metadata},
3 path::{Path, PathBuf},
4};
5
6use super::prelude::*;
7
8/// Creates links
9///
10/// If a link is created for a file that already had a link then the old link will be overwritten.
11/// By default creating a link to a directory will recursively link all files under that
12/// directory, to actually link a directory use the --directory flag.
13#[derive(Parser)]
14pub struct Opts {
15 /// Treats the paths as directories. This will create links to the actual directories instead
16 /// of recursively linking all files under them.
17 #[clap(long)]
18 directory: bool,
19
20 /// The paths to link. The last path is the destination.
21 paths: Vec<PathBuf>,
22}
23
24// TODO: require destination
25// remove else branch
26pub fn main(config: Config, opts: Opts) -> anyhow::Result<()> {
27 let mut depot = utils::read_depot(&config.archive_path)?;
28
29 let (origins, destination) = match opts.paths.as_slice() {
30 p @ [] | p @ [_] => (p, None),
31 [o @ .., dest] => (o, Some(dest)),
32 _ => unreachable!(),
33 };
34
35 if let Some(destination) = destination {
36 let params = if opts.directory {
37 origins
38 .iter()
39 .map(|p| LinkCreateParams {
40 origin: p.to_path_buf(),
41 destination: destination.clone(),
42 })
43 .collect()
44 } else {
45 let mut params = Vec::new();
46 for origin in origins {
47 generate_link_params(&depot, origin, destination, origin, &mut params)?;
48 }
49 params
50 };
51
52 for link_params in params {
53 log::info!("Creating link : {}", link_params);
54 depot.create_link(link_params)?;
55 }
56 } else {
57 let base_path = match origins {
58 [] => std::env::current_dir()?,
59 [path] => path.clone(),
60 _ => unreachable!(),
61 };
62
63 for link in utils::collect_links_by_base_paths(&depot, std::iter::once(base_path)) {
64 log::info!("{}", link);
65 }
66 }
67
68 utils::write_depot(&depot)?;
69
70 Ok(())
71}
72
73fn generate_link_params(
74 depot: &Depot,
75 origin: &Path,
76 destination: &Path,
77 base: &Path,
78 params: &mut Vec<LinkCreateParams>,
79) -> anyhow::Result<()> {
80 let metadata = std::fs::metadata(origin)?;
81 if metadata.is_file() {
82 generate_file_link_params(depot, origin, destination, base, params)?;
83 } else if metadata.is_dir() {
84 generate_directory_link_params_recursive(depot, origin, destination, base, params)?;
85 }
86 Ok(())
87}
88
89fn generate_file_link_params(
90 depot: &Depot,
91 origin: &Path,
92 destination: &Path,
93 base: &Path,
94 params: &mut Vec<LinkCreateParams>,
95) -> anyhow::Result<()> {
96 let origin_canonical = origin
97 .canonicalize()
98 .expect("Failed to canonicalize origin path");
99 let base_canonical = base
100 .canonicalize()
101 .expect("Failed to canonicalize base path");
102
103 log::debug!("Origin canonical : {}", origin_canonical.display());
104 log::debug!("Base : {}", base.display());
105
106 let partial = origin_canonical
107 .strip_prefix(base_canonical)
108 .expect("Failed to remove prefix from origin path");
109 let destination = destination.join(partial);
110 let origin = origin_canonical
111 .strip_prefix(depot.base_path())
112 .unwrap_or(&origin_canonical);
113
114 let link_params = LinkCreateParams {
115 origin: origin.to_path_buf(),
116 destination,
117 };
118 params.push(link_params);
119 Ok(())
120}
121
122fn generate_directory_link_params_recursive(
123 depot: &Depot,
124 dir_path: &Path,
125 destination: &Path,
126 base: &Path,
127 params: &mut Vec<LinkCreateParams>,
128) -> anyhow::Result<()> {
129 for origin in dir_path.read_dir()? {
130 let origin = origin?.path();
131 generate_link_params(depot, &origin, destination, base, params)?;
132 }
133 Ok(())
134}
diff --git a/dotup_cli/src/commands/mod.rs b/dotup_cli/src/commands/mod.rs
deleted file mode 100644
index bd92599..0000000
--- a/dotup_cli/src/commands/mod.rs
+++ /dev/null
@@ -1,11 +0,0 @@
1pub mod init;
2pub mod install;
3pub mod link;
4pub mod mv;
5pub mod status;
6pub mod uninstall;
7pub mod unlink;
8
9mod prelude {
10 pub use crate::prelude::*;
11}
diff --git a/dotup_cli/src/commands/mv.rs b/dotup_cli/src/commands/mv.rs
deleted file mode 100644
index aae2715..0000000
--- a/dotup_cli/src/commands/mv.rs
+++ /dev/null
@@ -1,53 +0,0 @@
1use std::path::{Path, PathBuf};
2
3use super::prelude::*;
4
5/// Install links. (Creates symlinks).
6///
7/// Installing a link will create the necessary directories.
8/// If a file or directory already exists at the location a link would be installed this command will fail.
9#[derive(Parser)]
10pub struct Opts {
11 /// The files/directories to move
12 #[clap(min_values = 2)]
13 paths: Vec<PathBuf>,
14}
15
16pub fn main(config: Config, opts: Opts) -> anyhow::Result<()> {
17 let mut depot = utils::read_depot(&config.archive_path)?;
18
19 let (sources, destination) = match opts.paths.as_slice() {
20 [source, destination] => {}
21 [sources @ .., destination] => {
22 let mut curr_destination = destination.to_owned();
23 for source in sources {
24 let filename = match source.file_name() {
25 Some(filename) => filename,
26 None => {
27 log::warn!("Ignoring '{}', unknown file name", source.display());
28 continue;
29 }
30 };
31 curr_destination.push(filename);
32 std::fs::rename(source, &curr_destination)?;
33 if let Some(id) = depot.get_link_id_by_path(&source) {
34 depot.rename_link(id, &curr_destination);
35 }
36 curr_destination.pop();
37 }
38 }
39 _ => unreachable!(),
40 };
41
42 utils::write_depot(&depot)?;
43
44 Ok(())
45}
46
47fn rename(depot: &mut Depot, source: &Path, destination: &Path) -> anyhow::Result<()> {
48 std::fs::rename(source, &destination)?;
49 if let Some(id) = depot.get_link_id_by_path(&source) {
50 depot.rename_link(id, &destination);
51 }
52 Ok(())
53}
diff --git a/dotup_cli/src/commands/status.rs b/dotup_cli/src/commands/status.rs
deleted file mode 100644
index b7221db..0000000
--- a/dotup_cli/src/commands/status.rs
+++ /dev/null
@@ -1,175 +0,0 @@
1use std::path::{Path, PathBuf};
2
3use ansi_term::Colour;
4use clap::Parser;
5use dotup::{Depot, Link};
6
7use crate::{utils, Config};
8
9/// Shows information about links
10///
11/// If a link is created for a file that already had a link then the old link will be overwritten.
12/// By default creating a link to a directory will recursively link all files under that
13/// directory, to actually link a directory use the --directory flag.
14#[derive(Parser)]
15pub struct Opts {
16 /// The location where links will be installed to.
17 /// Defaults to the home directory.
18 #[clap(long)]
19 install_base: Option<PathBuf>,
20
21 /// The paths to show the status of
22 paths: Vec<PathBuf>,
23}
24
25#[derive(Debug)]
26struct State {
27 max_depth: u32,
28 install_path: PathBuf,
29}
30
31pub fn main(config: Config, opts: Opts) -> anyhow::Result<()> {
32 let mut depot = utils::read_depot(&config.archive_path)?;
33
34 // walk dir
35 // if node is file:
36 // if linked
37 // print name in green and destination blue
38 // if invalid
39 // print name and destination red
40 // if not linked
41 // print name in gray
42 // if node is directory:
43 // if linked
44 // print name in green and destination blue
45 // if invalid
46 // print name and destination red
47 // if not linked:
48 // print name in gray
49 // if contains files that are linked/invalid:
50 // recurse into directory
51 //
52
53 let depot_base = depot.base_path();
54 let mut paths = Vec::new();
55 for path in opts.paths {
56 let canonical = dotup::utils::weakly_canonical(&path);
57 if canonical.starts_with(depot_base) {
58 paths.push(canonical);
59 } else {
60 log::warn!("Path '{}' is outside the depot", path.display());
61 }
62 }
63
64 if paths.is_empty() {
65 paths.push(PathBuf::from("."));
66 }
67
68 let state = State {
69 max_depth: u32::MAX,
70 install_path: config.install_path,
71 };
72
73 let (directories, files) = utils::collect_read_dir_split(".")?;
74 for path in directories.into_iter().chain(files.into_iter()) {
75 display_status_path(&depot, &state, &path, 0);
76 }
77
78 utils::write_depot(&depot)?;
79
80 Ok(())
81}
82
83fn display_status_path(depot: &Depot, state: &State, path: &Path, depth: u32) {
84 if depth == state.max_depth {
85 return;
86 }
87
88 if path.is_dir() {
89 display_status_directory(depot, state, path, depth);
90 } else {
91 display_status_file(depot, state, path, depth);
92 }
93}
94
95fn display_status_directory(depot: &Depot, state: &State, path: &Path, depth: u32) {
96 assert!(path.is_dir());
97 if depth == state.max_depth || !depot.subpath_has_links(path) {
98 return;
99 }
100
101 if let Some(link) = depot.get_link_by_path(path) {
102 print_link(depot, state, link, depth);
103 } else {
104 for entry in std::fs::read_dir(path).unwrap() {
105 let entry = match entry {
106 Ok(entry) => entry,
107 Err(_) => continue,
108 };
109 let entry_path = entry.path().canonicalize().unwrap();
110 let entry_path_stripped = entry_path
111 .strip_prefix(std::env::current_dir().unwrap())
112 .unwrap();
113
114 print_identation(depth);
115 println!(
116 "{}",
117 Colour::Yellow.paint(&format!("{}", path.file_name().unwrap().to_string_lossy()))
118 );
119
120 display_status_path(depot, state, &entry_path_stripped, depth + 1);
121 }
122 }
123}
124
125fn display_status_file(depot: &Depot, state: &State, path: &Path, depth: u32) {
126 print_identation(depth);
127 if let Some(link) = depot.get_link_by_path(path) {
128 print_link(depot, state, link, depth);
129 } else {
130 print_unlinked(path, depth);
131 }
132}
133
134fn print_link(depot: &Depot, state: &State, link: &Link, depth: u32) {
135 let origin = link.origin();
136 let dest = link.destination();
137 let filename = match origin.file_name() {
138 Some(filename) => filename,
139 None => return,
140 };
141
142 print_identation(depth);
143 if depot.is_link_installed(link, &state.install_path) {
144 println!(
145 "{} -> {}",
146 Colour::Green.paint(&format!("{}", filename.to_string_lossy())),
147 Colour::Blue.paint(&format!("{}", dest.display())),
148 );
149 } else {
150 println!(
151 "{}",
152 Colour::Red.paint(&format!(
153 "{} -> {}",
154 filename.to_string_lossy(),
155 dest.display()
156 ))
157 );
158 }
159}
160
161fn print_unlinked(path: &Path, depth: u32) {
162 let filename = match path.file_name() {
163 Some(filename) => filename,
164 None => return,
165 };
166
167 print_identation(depth);
168 println!("{}", filename.to_string_lossy());
169}
170
171fn print_identation(depth: u32) {
172 for i in 0..depth {
173 print!(" ");
174 }
175}
diff --git a/dotup_cli/src/commands/uninstall.rs b/dotup_cli/src/commands/uninstall.rs
deleted file mode 100644
index fe44bf0..0000000
--- a/dotup_cli/src/commands/uninstall.rs
+++ /dev/null
@@ -1,26 +0,0 @@
1use std::path::PathBuf;
2
3use super::prelude::*;
4
5/// Uninstalls links. (Removes symlinks).
6///
7/// Uninstalling a link for a file that didnt have a link will do nothing.
8/// Uninstalling a directory will recursively uninstall all files under it.
9/// Symlinks are only deleted if they were pointing to the correct file.
10#[derive(Parser)]
11pub struct Opts {
12 /// The files/directories to uninstall.
13 #[clap(min_values = 1, default_value = ".")]
14 paths: Vec<PathBuf>,
15}
16
17pub fn main(config: Config, opts: Opts) -> anyhow::Result<()> {
18 let depot = utils::read_depot(&config.archive_path)?;
19
20 for link in utils::collect_links_by_base_paths(&depot, &opts.paths) {
21 log::info!("Uninstalling link : {}", link);
22 depot.uninstall_link(link, &config.install_path)?;
23 }
24
25 Ok(())
26}
diff --git a/dotup_cli/src/commands/unlink.rs b/dotup_cli/src/commands/unlink.rs
deleted file mode 100644
index abe23e3..0000000
--- a/dotup_cli/src/commands/unlink.rs
+++ /dev/null
@@ -1,43 +0,0 @@
1use std::{
2 fs::{DirEntry, Metadata},
3 path::{Path, PathBuf},
4};
5
6use super::prelude::*;
7
8/// Unlinks files/directories.
9///
10/// This will recursively remove links. If a path is a directory then it will remove all links
11/// recursively.
12/// The links are not uninstall by default, see the --uninstall parameter.
13#[derive(Parser)]
14pub struct Opts {
15 /// Specify the install base if the links are also to be uninstalled.
16 #[clap(long)]
17 uninstall: Option<PathBuf>,
18
19 /// The paths to unlink.
20 #[clap(required = true, min_values = 1)]
21 paths: Vec<PathBuf>,
22}
23
24pub fn main(config: Config, opts: Opts) -> anyhow::Result<()> {
25 let mut depot = utils::read_depot(&config.archive_path)?;
26
27 for link_id in utils::collect_link_ids_by_base_paths(&depot, &opts.paths) {
28 let link = depot.get_link(link_id).unwrap();
29 log::info!(
30 "Unlinking(uninstall = {}) : {}",
31 opts.uninstall.is_some(),
32 link
33 );
34 if let Some(ref install_base) = opts.uninstall {
35 depot.uninstall_link(link, &install_base)?;
36 }
37 depot.remove_link(link_id);
38 }
39
40 utils::write_depot(&depot)?;
41
42 Ok(())
43}