1use misc::{pretty_print_packages, Errors};
2use owo_colors::OwoColorize;
3use semver::Version;
4use serde::{Deserialize, Serialize};
5use std::{collections::HashMap, path::PathBuf, vec};
6
7mod misc;
8pub mod remote;
9mod url;
10
11#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
12pub struct Package {
13 pub name: String,
14 pub features: Vec<String>,
15 pub all_features: bool,
16 pub no_default_features: bool,
17 pub version: Version,
18}
19
20#[derive(Serialize, Deserialize, Debug)]
21struct Crates {
22 installs: HashMap<String, Install>,
23}
24
25#[derive(Serialize, Deserialize, Debug)]
26struct Install {
27 #[serde(default)]
28 pub features: Vec<String>,
29 pub no_default_features: bool,
30 pub all_features: bool,
31}
32
33fn get_crates_path() -> PathBuf {
35 #[cfg(test)]
36 {
37 use std::env;
38 env::current_dir().unwrap().join("tests/.crates2.json")
39 }
40
41 #[cfg(not(test))]
42 {
43 let path = dirs::home_dir().unwrap().join(".cargo/.crates2.json");
44 assert!(path.exists());
45 path
46 }
47}
48
49pub fn get_packages() -> Vec<Package> {
62 let path = get_crates_path();
63 let crates: Crates = serde_json::from_str(
64 &std::fs::read_to_string(path).unwrap_or_else(|_| panic!("{}", Errors::ReadFile)),
65 )
66 .unwrap_or_else(|_| panic!("{}", Errors::JsonParse));
67
68 let mut packages = vec![];
69
70 for (id, install) in crates.installs {
71 let (name, version, skip) = slice_info(&id);
72
73 if skip {
74 continue;
75 }
76
77 packages.push(Package {
78 name: name.to_string(),
79 features: install.features,
80 all_features: install.all_features,
81 no_default_features: install.no_default_features,
82 version,
83 });
84 }
85
86 packages
87}
88
89pub fn install_packages(
90 packages: &[Package],
91 skip_install: bool,
92 skip_update: bool,
93 skip_remove: bool,
94) {
95 let installed_packages = get_packages();
96
97 let mut to_update: Vec<Package> = vec![];
98 let mut to_install: Vec<Package> = vec![];
99 let mut to_remove: Vec<Package> = vec![];
100
101 if !skip_install {
102 for package in packages {
103 if !installed_packages.iter().any(|p| p.name == package.name) {
104 to_install.push(package.clone());
105 }
106 }
107 }
108
109 if !skip_update {
110 for package in &installed_packages {
111 if let Some(p) = packages.iter().find(|np| np.name == package.name) {
112 if p.version > package.version {
113 to_update.push(p.clone());
114 }
115 }
116 }
117 }
118
119 if !skip_remove {
120 for package in &installed_packages {
121 if !packages.iter().any(|np| np.name == package.name) {
122 to_remove.push(package.clone());
123 }
124 }
125 }
126
127 pretty_print_packages(&to_install.clone(), &to_update.clone(), &to_remove.clone());
128
129 #[cfg(not(test))]
131 {
132 use crate::misc::{execute_cmd, CommandType};
133 use dialoguer::Confirm;
134
135 if Confirm::new().with_prompt("Proceed?").interact().unwrap() {
136 for package in to_install {
139 execute_cmd(&package, CommandType::Install);
140 }
141
142 for package in to_update {
143 execute_cmd(&package, CommandType::Install);
144 }
145
146 for package in to_remove {
147 execute_cmd(&package, CommandType::Remove);
148 }
149 }
150 }
151}
152
153fn slice_info(package_str: &str) -> (String, Version, bool) {
161 let splits: Vec<&str> = package_str.splitn(3, ' ').collect();
162 let name = splits[0].to_string();
163 let version = Version::parse(splits[1]).unwrap();
164 let local_package = splits[2].contains("path+file://") || splits[2].contains("git+https://");
165 if local_package {
166 println!(
167 "{} {}",
168 name.blue(),
169 "Package ignored because it is either a local or git Package".red()
170 );
171 }
172 (name, version, local_package)
173}
174
175#[test]
176fn test_slice_info() {
177 use std::str::FromStr;
178
179 let (name, version, skip) = slice_info("foo 0.1.0 (path+file:///home/user/foo)");
180 assert_eq!(name, "foo");
181 assert_eq!(version, Version::from_str("0.1.0").unwrap());
182 assert!(skip);
183
184 let (name, version, skip) = slice_info("foo 0.1.0 (registry+https://example.com/foo)");
185 assert_eq!(name, "foo");
186 assert_eq!(version, Version::from_str("0.1.0").unwrap());
187 assert!(!skip);
188
189 let (name, version, skip) = slice_info("foo 0.1.0 (git+https://github.com/foo/bar#hash)");
190 assert_eq!(name, "foo");
191 assert_eq!(version, Version::from_str("0.1.0").unwrap());
192 assert!(skip);
193}
194
195#[test]
196fn test_get_packages() {
197 let packages = get_packages();
198 assert_eq!(packages.len(), 3);
199}
200
201#[test]
202fn test_install_packages() {
203 let fake_packages: Vec<Package> = vec![
204 Package {
205 all_features: true,
206 features: vec![],
207 name: "foo".to_string(),
208 no_default_features: false,
209 version: Version::parse("0.1.0").unwrap(),
210 },
211 Package {
212 name: "package".to_string(),
213 version: Version::parse("0.5.3").unwrap(),
214 all_features: false,
215 no_default_features: false,
216 features: vec!["feature1".to_string(), "feature2".to_string()],
217 },
218 ];
219
220 install_packages(&fake_packages, false, false, false);
221}