aboutsummaryrefslogtreecommitdiff
path: root/dotup_cli/src/commands/status.rs
blob: b7221dbec67f5efeb44e08ef70abbc36b6fffb81 (plain)
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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
use std::path::{Path, PathBuf};

use ansi_term::Colour;
use clap::Parser;
use dotup::{Depot, Link};

use crate::{utils, Config};

/// Shows information about links
///
/// If a link is created for a file that already had a link then the old link will be overwritten.
/// By default creating a link to a directory will recursively link all files under that
/// directory, to actually link a directory use the --directory flag.
#[derive(Parser)]
pub struct Opts {
    /// The location where links will be installed to.
    /// Defaults to the home directory.
    #[clap(long)]
    install_base: Option<PathBuf>,

    /// The paths to show the status of
    paths: Vec<PathBuf>,
}

#[derive(Debug)]
struct State {
    max_depth: u32,
    install_path: PathBuf,
}

pub fn main(config: Config, opts: Opts) -> anyhow::Result<()> {
    let mut depot = utils::read_depot(&config.archive_path)?;

    // walk dir
    // if node is file:
    //      if linked
    //          print name in green and destination blue
    //      if  invalid
    //          print name and destination red
    //      if not linked
    //          print name in gray
    // if node is directory:
    //      if linked
    //          print name in green and destination blue
    //      if invalid
    //          print name and destination red
    //      if not linked:
    //          print name in gray
    //      if contains files that are linked/invalid:
    //          recurse into directory
    //

    let depot_base = depot.base_path();
    let mut paths = Vec::new();
    for path in opts.paths {
        let canonical = dotup::utils::weakly_canonical(&path);
        if canonical.starts_with(depot_base) {
            paths.push(canonical);
        } else {
            log::warn!("Path '{}' is outside the depot", path.display());
        }
    }

    if paths.is_empty() {
        paths.push(PathBuf::from("."));
    }

    let state = State {
        max_depth: u32::MAX,
        install_path: config.install_path,
    };

    let (directories, files) = utils::collect_read_dir_split(".")?;
    for path in directories.into_iter().chain(files.into_iter()) {
        display_status_path(&depot, &state, &path, 0);
    }

    utils::write_depot(&depot)?;

    Ok(())
}

fn display_status_path(depot: &Depot, state: &State, path: &Path, depth: u32) {
    if depth == state.max_depth {
        return;
    }

    if path.is_dir() {
        display_status_directory(depot, state, path, depth);
    } else {
        display_status_file(depot, state, path, depth);
    }
}

fn display_status_directory(depot: &Depot, state: &State, path: &Path, depth: u32) {
    assert!(path.is_dir());
    if depth == state.max_depth || !depot.subpath_has_links(path) {
        return;
    }

    if let Some(link) = depot.get_link_by_path(path) {
        print_link(depot, state, link, depth);
    } else {
        for entry in std::fs::read_dir(path).unwrap() {
            let entry = match entry {
                Ok(entry) => entry,
                Err(_) => continue,
            };
            let entry_path = entry.path().canonicalize().unwrap();
            let entry_path_stripped = entry_path
                .strip_prefix(std::env::current_dir().unwrap())
                .unwrap();

            print_identation(depth);
            println!(
                "{}",
                Colour::Yellow.paint(&format!("{}", path.file_name().unwrap().to_string_lossy()))
            );

            display_status_path(depot, state, &entry_path_stripped, depth + 1);
        }
    }
}

fn display_status_file(depot: &Depot, state: &State, path: &Path, depth: u32) {
    print_identation(depth);
    if let Some(link) = depot.get_link_by_path(path) {
        print_link(depot, state, link, depth);
    } else {
        print_unlinked(path, depth);
    }
}

fn print_link(depot: &Depot, state: &State, link: &Link, depth: u32) {
    let origin = link.origin();
    let dest = link.destination();
    let filename = match origin.file_name() {
        Some(filename) => filename,
        None => return,
    };

    print_identation(depth);
    if depot.is_link_installed(link, &state.install_path) {
        println!(
            "{} -> {}",
            Colour::Green.paint(&format!("{}", filename.to_string_lossy())),
            Colour::Blue.paint(&format!("{}", dest.display())),
        );
    } else {
        println!(
            "{}",
            Colour::Red.paint(&format!(
                "{} -> {}",
                filename.to_string_lossy(),
                dest.display()
            ))
        );
    }
}

fn print_unlinked(path: &Path, depth: u32) {
    let filename = match path.file_name() {
        Some(filename) => filename,
        None => return,
    };

    print_identation(depth);
    println!("{}", filename.to_string_lossy());
}

fn print_identation(depth: u32) {
    for i in 0..depth {
        print!("  ");
    }
}