1pub const LINTIAN_DATA_PATH: &str = "/usr/share/lintian/data";
5
6pub const RELEASE_DATES_PATH: &str = "/usr/share/lintian/data/debian-policy/release-dates.json";
8
9#[derive(Debug, Clone, serde::Deserialize)]
10pub struct PolicyRelease {
12 pub version: StandardsVersion,
14 pub timestamp: chrono::DateTime<chrono::Utc>,
16 pub closes: Vec<i32>,
18 pub epoch: Option<i32>,
20 pub author: Option<String>,
22 pub changes: Vec<String>,
24}
25
26#[derive(Debug, Clone, serde::Deserialize)]
27#[allow(dead_code)]
28struct Preamble {
29 pub cargo: String,
30 pub title: String,
31}
32
33#[derive(Debug, Clone, serde::Deserialize)]
34#[allow(dead_code)]
35struct PolicyReleases {
36 pub preamble: Preamble,
37 pub releases: Vec<PolicyRelease>,
38}
39
40#[derive(Debug, Clone)]
41pub struct StandardsVersion(Vec<i32>);
43
44impl StandardsVersion {
45 fn normalize(&self, n: usize) -> Self {
46 let mut version = self.0.clone();
47 version.resize(n, 0);
48 Self(version)
49 }
50}
51
52impl std::cmp::PartialEq for StandardsVersion {
53 fn eq(&self, other: &Self) -> bool {
54 let n = std::cmp::max(self.0.len(), other.0.len());
56 let self_normalized = self.normalize(n);
57 let other_normalized = other.normalize(n);
58 self_normalized.0 == other_normalized.0
59 }
60}
61
62impl std::cmp::Ord for StandardsVersion {
63 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
64 let n = std::cmp::max(self.0.len(), other.0.len());
66 let self_normalized = self.normalize(n);
67 let other_normalized = other.normalize(n);
68 self_normalized.0.cmp(&other_normalized.0)
69 }
70}
71
72impl std::cmp::PartialOrd for StandardsVersion {
73 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
74 Some(self.cmp(other))
75 }
76}
77
78impl std::cmp::Eq for StandardsVersion {}
79
80impl std::str::FromStr for StandardsVersion {
81 type Err = core::num::ParseIntError;
82
83 fn from_str(s: &str) -> Result<Self, Self::Err> {
84 let mut parts = s.split('.').map(|part| part.parse::<i32>());
85 let mut version = Vec::new();
86 for part in &mut parts {
87 version.push(part?);
88 }
89 Ok(StandardsVersion(version))
90 }
91}
92
93impl<'a> serde::Deserialize<'a> for StandardsVersion {
94 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
95 where
96 D: serde::Deserializer<'a>,
97 {
98 let s = String::deserialize(deserializer)?;
99 s.parse().map_err(serde::de::Error::custom)
100 }
101}
102
103impl std::fmt::Display for StandardsVersion {
104 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
105 write!(
106 f,
107 "{}",
108 self.0
109 .iter()
110 .map(|part| part.to_string())
111 .collect::<Vec<_>>()
112 .join(".")
113 )
114 }
115}
116
117pub fn iter_standards_versions() -> impl Iterator<Item = PolicyRelease> {
119 let data = std::fs::read(RELEASE_DATES_PATH).expect("Failed to read release dates");
120 let data: PolicyReleases =
121 serde_json::from_slice(&data).expect("Failed to parse release dates");
122 data.releases.into_iter()
123}
124
125pub fn latest_standards_version() -> StandardsVersion {
127 iter_standards_versions()
128 .next()
129 .expect("No standards versions found")
130 .version
131}
132
133#[cfg(test)]
134mod tests {
135 #[test]
136 fn test_standards_version() {
137 let version: super::StandardsVersion = "4.2.0".parse().unwrap();
138 assert_eq!(version.0, vec![4, 2, 0]);
139 assert_eq!(version.to_string(), "4.2.0");
140 assert_eq!(version, "4.2".parse().unwrap());
141 assert_eq!(version, "4.2.0".parse().unwrap());
142 }
143
144 #[test]
145 fn test_parse_releases() {
146 let input = r###"{
147 "preamble" : {
148 "cargo" : "releases",
149 "title" : "Debian Policy Releases"
150 },
151 "releases" : [
152 {
153 "author" : "Sean Whitton <spwhitton@spwhitton.name>",
154 "changes" : [
155 "",
156 "debian-policy (4.7.0.0) unstable; urgency=medium",
157 "",
158 " [ Sean Whitton ]",
159 " * Policy: Prefer native overriding mechanisms to diversions & alternatives",
160 " Wording: Luca Boccassi <bluca@debian.org>",
161 " Seconded: Sean Whitton <spwhitton@spwhitton.name>",
162 " Seconded: Russ Allbery <rra@debian.org>",
163 " Seconded: Holger Levsen <holger@layer-acht.org>",
164 " Closes: #1035733",
165 " * Policy: Improve alternative build dependency discussion",
166 " Wording: Russ Allbery <rra@debian.org>",
167 " Seconded: Wouter Verhelst <wouter@debian.org>",
168 " Seconded: Sean Whitton <spwhitton@spwhitton.name>",
169 " Closes: #968226",
170 " * Policy: No network access for required targets for contrib & non-free",
171 " Wording: Aurelien Jarno <aurel32@debian.org>",
172 " Seconded: Sam Hartman <hartmans@debian.org>",
173 " Seconded: Tobias Frost <tobi@debian.org>",
174 " Seconded: Holger Levsen <holger@layer-acht.org>",
175 " Closes: #1068192",
176 "",
177 " [ Russ Allbery ]",
178 " * Policy: Add mention of the new non-free-firmware archive area",
179 " Wording: Gunnar Wolf <gwolf@gwolf.org>",
180 " Seconded: Holger Levsen <holger@layer-acht.org>",
181 " Seconded: Russ Allbery <rra@debian.org>",
182 " Closes: #1029211",
183 " * Policy: Source packages in main may build binary packages in contrib",
184 " Wording: Simon McVittie <smcv@debian.org>",
185 " Seconded: Holger Levsen <holger@layer-acht.org>",
186 " Seconded: Russ Allbery <rra@debian.org>",
187 " Closes: #994008",
188 " * Policy: Allow hard links in source packages",
189 " Wording: Russ Allbery <rra@debian.org>",
190 " Seconded: Helmut Grohne <helmut@subdivi.de>",
191 " Seconded: Guillem Jover <guillem@debian.org>",
192 " Closes: #970234",
193 " * Policy: Binary and Description fields may be absent in .changes",
194 " Wording: Russ Allbery <rra@debian.org>",
195 " Seconded: Sam Hartman <hartmans@debian.org>",
196 " Seconded: Guillem Jover <guillem@debian.org>",
197 " Closes: #963524",
198 " * Policy: systemd units are required to start and stop system services",
199 " Wording: Luca Boccassi <bluca@debian.org>",
200 " Wording: Russ Allbery <rra@debian.org>",
201 " Seconded: Luca Boccassi <bluca@debian.org>",
202 " Seconded: Sam Hartman <hartmans@debian.org>",
203 " Closes: #1039102"
204 ],
205 "closes" : [
206 963524,
207 968226,
208 970234,
209 994008,
210 1029211,
211 1035733,
212 1039102,
213 1068192
214 ],
215 "epoch" : 1712466535,
216 "timestamp" : "2024-04-07T05:08:55Z",
217 "version" : "4.7.0.0"
218 }
219 ]
220}"###;
221 let data: super::PolicyReleases = serde_json::from_str(input).unwrap();
222 assert_eq!(data.releases.len(), 1);
223 }
224}