debian_analyzer/
lintian.rs

1//! Lintian data structures and utilities
2
3/// The path to the Lintian data directory
4pub const LINTIAN_DATA_PATH: &str = "/usr/share/lintian/data";
5
6/// The path to the Lintian tags file
7pub const RELEASE_DATES_PATH: &str = "/usr/share/lintian/data/debian-policy/release-dates.json";
8
9#[derive(Debug, Clone, serde::Deserialize)]
10/// A release of the Debian Policy
11pub struct PolicyRelease {
12    /// The version of the release
13    pub version: StandardsVersion,
14    /// When the release was published
15    pub timestamp: chrono::DateTime<chrono::Utc>,
16    /// List of bug numbers closed by this release
17    pub closes: Vec<i32>,
18    /// The epoch of the release
19    pub epoch: Option<i32>,
20    /// The author of the release
21    pub author: Option<String>,
22    /// The changes made in this release
23    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)]
41/// A version of the Debian Policy
42pub 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        // Normalize to the same length
55        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        // Normalize to the same length
65        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
117/// Returns an iterator over all known standards versions
118pub 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
125/// Returns the latest standards version
126pub 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}