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
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
use crate::package_manager::{PackageManager, PackageManagerTrait};
use anyhow::Result;
use colored::Colorize;
use itertools::Itertools;
use serde::{Deserialize, Serialize};
use std::{
cmp::Ordering,
iter::{self, IntoIterator},
ops::Deref,
string::String,
};
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct Package {
/// The package manager this package belongs to.
source: PackageManager,
/// Name of this package.
name: String,
/// A list of command line flags this package should be installed with.
#[serde(default)]
flags: Vec<String>,
}
impl Package {
/// Instantiate.
pub fn new(source: PackageManager, name: String, flags: Vec<String>) -> Self {
Self {
source,
name,
flags,
}
}
/// Only the package name.
pub fn name(&self) -> &str {
&self.name
}
/// The name including the flags.
pub fn full_command(&self) -> String {
self.flags.iter().chain(iter::once(&self.name)).join(" ")
}
/// The full command needed to install this package.
#[cfg(not(target_os = "windows"))]
pub fn install_command(&self) -> String {
if self.source.needs_root() {
format!(
"sudo {} {}",
self.source.install_command(),
self.full_command()
)
} else {
format!("{} {}", self.source.install_command(), self.full_command())
}
}
/// The full command needed to install this package.
#[cfg(target_os = "windows")]
pub fn install_command(&self) -> String {
format!("{} {}", self.source.install_command(), self.full_command())
}
/// The full name in fancy colors.
pub fn color_full_name(&self) -> String {
if self.flags.is_empty() {
format!(
"{} ({})",
self.name.yellow(),
self.source.full_name().green()
)
} else {
format!(
"{} {} ({})",
self.flags.iter().join(" ").dimmed(),
self.name.yellow(),
self.source.full_name().green(),
)
}
}
/// The command line flags.
///
/// Used by the test_macro.
#[allow(unused)]
pub fn flags(&self) -> &Vec<String> {
&self.flags
}
/// Check if this package is already installed.
pub fn is_installed(&self) -> Result<bool> {
self.source.package_is_installed(&self)
}
/// Check if the package manager can be found.
pub fn is_available(&self) -> bool {
self.source.is_available()
}
}
impl Ord for Package {
fn cmp(&self, other: &Self) -> Ordering {
self.full_command().cmp(&other.full_command())
}
}
impl PartialOrd for Package {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Packages(Vec<Package>);
impl Packages {
/// An empty list.
pub fn empty() -> Self {
Self(vec![])
}
/// Parse a line into a list of packages.
pub fn from_line(line: &str) -> Self {
// First we split the line into separating characters
let lines = line
.split(|c| c == ';' || c == '|' || c == '&')
// Then try to find the proper package manager for each line, this also filters out
// lines that are not related to the package manager
.filter_map(|line| {
// Filter out matches with less than 4 characters, it's impossible to install a
// package that we can catch like that
if line.len() < 4 {
return None;
}
// Attempt to find a matching package manager with the line
match PackageManager::from_line(line) {
// Pass the line along
Some(manager) => Some((line, manager)),
None => None,
}
})
// Parse the packages in the line with the package manager supplied
.map(|(line, package_manager)| package_manager.catch(line))
// Create a long list of the list of lists
.flatten()
.collect();
Self(lines)
}
/// Get the union of this and another list of packages.
pub fn merge(&mut self, other: &mut Packages) {
// Add the other packages
self.0.append(&mut other.0);
// Sort them so we can remove deduplicates
self.0.sort();
// Remove the duplicates
self.0.dedup();
}
/// Remove all packages that have been saved already.
pub fn filter_saved_packages(&mut self, old: &Packages) {
self.0 = self
.0
.iter()
.filter(|package| !old.iter().any(|old_package| *package == old_package))
.cloned()
.collect();
}
/// Construct a commit message depending on the amount of packages that need to be committed.
pub fn commit_message(&self) -> String {
match self.0.len() {
0 => panic!("Can't create a commit message for empty changes"),
1 => format!("Emplace - mirror package \"{}\"", self.0[0].full_command()),
n => format!("Emplace - mirror {} packages", n),
}
}
}
impl IntoIterator for Packages {
type Item = Package;
type IntoIter = std::vec::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl Deref for Packages {
type Target = Vec<Package>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl From<Vec<Package>> for Packages {
fn from(x: Vec<Package>) -> Self {
Packages(x)
}
}