1use std::{collections::BTreeSet, fmt};
2
3#[non_exhaustive]
5#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
6pub struct CargoDependency {
7 pub branch: Option<&'static str>,
8 pub default_features: Option<bool>,
9 pub features: BTreeSet<&'static str>,
10 pub git: Option<&'static str>,
11 pub path: Option<&'static str>,
12 pub registry: Option<&'static str>,
13 pub version: Option<&'static str>,
14 pub workspace: Option<bool>,
15}
16
17impl CargoDependency {
18 pub fn from_workspace() -> Self {
19 Self::from_workspace_with_features(BTreeSet::new())
20 }
21
22 pub fn from_workspace_with_features(features: BTreeSet<&'static str>) -> Self {
23 Self {
24 features,
25 workspace: Some(true),
26 ..Default::default()
27 }
28 }
29
30 pub fn merge_or_replace_with(&self, other: &Self) -> Self {
36 if let Some(path) = &other.path {
37 Self {
38 branch: None,
39 default_features: other.default_features.or(self.default_features),
40 features: self.features.union(&other.features).copied().collect(),
41 git: None,
42 path: Some(path),
43 registry: other.registry.or(self.registry),
44 version: other.version.or(self.version),
45 workspace: None,
46 }
47 } else if let Some(git) = &other.git {
48 Self {
49 branch: other.branch,
50 default_features: other.default_features.or(self.default_features),
51 features: self.features.union(&other.features).copied().collect(),
52 git: Some(git),
53 path: None,
54 registry: other.registry.or(self.registry),
55 version: other.version.or(self.version),
56 workspace: None,
57 }
58 } else if let Some(workspace) = &other.workspace {
59 Self {
60 branch: other.branch,
61 default_features: other.default_features.or(self.default_features),
62 features: self.features.union(&other.features).copied().collect(),
63 git: other.git,
64 path: other.path,
65 registry: other.registry,
66 version: other.version,
67 workspace: Some(*workspace),
68 }
69 } else {
70 Self {
71 branch: self.branch,
72 default_features: other.default_features.or(self.default_features),
73 features: self.features.union(&other.features).copied().collect(),
74 git: self.git,
75 path: self.path,
76 registry: other.registry.or(self.registry),
77 workspace: self.workspace,
78 version: other.version.or(self.version),
79 }
80 }
81 }
82
83 pub fn with_path(path: &'static str) -> Self {
84 Self::with_path_and_features(path, BTreeSet::new())
85 }
86
87 pub fn with_path_and_features(path: &'static str, features: BTreeSet<&'static str>) -> Self {
88 Self {
89 features,
90 path: Some(path),
91 ..Default::default()
92 }
93 }
94
95 pub fn with_version(version: &'static str) -> Self {
96 Self::with_version_and_features(version, BTreeSet::new())
97 }
98
99 pub fn with_version_and_features(
100 version: &'static str,
101 features: BTreeSet<&'static str>,
102 ) -> Self {
103 Self {
104 features,
105 version: Some(version),
106 ..Default::default()
107 }
108 }
109
110 pub fn with_version_from_registry(version: &'static str, registry: &'static str) -> Self {
111 Self::with_version_and_features_from_registry(version, registry, BTreeSet::new())
112 }
113
114 pub fn with_version_and_features_from_registry(
115 version: &'static str,
116 registry: &'static str,
117 features: BTreeSet<&'static str>,
118 ) -> Self {
119 Self {
120 features,
121 registry: Some(registry),
122 version: Some(version),
123 ..Default::default()
124 }
125 }
126
127 pub fn with_git(git: &'static str) -> Self {
128 Self::with_git_and_features(git, BTreeSet::new())
129 }
130
131 pub fn with_git_and_features(git: &'static str, features: BTreeSet<&'static str>) -> Self {
132 Self {
133 features,
134 git: Some(git),
135 ..Default::default()
136 }
137 }
138
139 pub fn with_git_and_branch(git: &'static str, branch: &'static str) -> Self {
140 Self::with_git_and_branch_and_features(git, branch, BTreeSet::new())
141 }
142
143 pub fn with_git_and_branch_and_features(
144 git: &'static str,
145 branch: &'static str,
146 features: BTreeSet<&'static str>,
147 ) -> Self {
148 Self {
149 features,
150 git: Some(git),
151 branch: Some(branch),
152 ..Default::default()
153 }
154 }
155}
156
157impl fmt::Display for CargoDependency {
158 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
159 let mut attributes = Vec::new();
160
161 if let Some(path) = self.path {
162 attributes.push(format!("path = {}", quote_value(path)));
163 } else if let Some(git) = self.git {
164 attributes.push(format!("git = {}", quote_value(git)));
165 if let Some(branch) = self.branch {
166 attributes.push(format!("branch = {}", quote_value(branch)));
167 }
168 } else if self.workspace == Some(true) {
169 attributes.push("workspace = true".to_owned());
170 }
171
172 if let Some(version) = self.version {
173 attributes.push(format!("version = {}", quote_value(version)));
174 if let Some(registry) = self.registry {
175 attributes.push(format!("registry = {}", quote_value(registry)));
176 }
177 }
178
179 if let Some(default_features) = self.default_features {
180 attributes.push(format!("default_features = {default_features}"));
181 }
182
183 if !self.features.is_empty() {
184 attributes.push(format!(
185 "features = [{}]",
186 self.features
187 .iter()
188 .map(|f| quote_value(f))
189 .collect::<Vec<_>>()
190 .join(", ")
191 ));
192 }
193
194 write!(f, "{{ {} }}", attributes.join(", "))
195 }
196}
197
198fn quote_value(val: &str) -> String {
199 format!("\"{}\"", val.replace('\\', "\\\\").replace('\"', "\\\""))
200}