Skip to main content

debian_workspace/
package_class.rs

1//! Heuristics for classifying binary packages.
2//!
3//! Mirrors lintian's `Lintian::Processable::Installable::Class` so that
4//! callers across the workspace can share the same definitions.
5
6const META_DESCRIPTION_NEEDLES: &[&str] = &[
7    "metapackage",
8    "meta package",
9    "meta-package",
10    "dependency package",
11    "dummy package",
12    "empty package",
13];
14
15const META_SECTIONS: &[&str] = &["tasks", "metapackages"];
16
17/// Returns true if the binary package is probably transitional.
18///
19/// Mirrors lintian's `is_transitional`: case-insensitive match of
20/// `transitional package` anywhere in the description.
21pub fn is_transitional(description: &str) -> bool {
22    description
23        .to_ascii_lowercase()
24        .contains("transitional package")
25}
26
27/// Returns true if the binary package is probably a meta or task package.
28///
29/// Mirrors lintian's `is_meta_package`. Returns true when:
30/// - the description (case-insensitively) contains `metapackage`,
31///   `meta package`, `meta-package`, or `(dependency|dummy|empty) package`; or
32/// - the section is `tasks` or `metapackages`, optionally prefixed by an
33///   archive area (e.g. `contrib/metapackages`); or
34/// - the package name starts with `task-`.
35pub fn is_meta_package(name: &str, description: &str, section: Option<&str>) -> bool {
36    let lower_description = description.to_ascii_lowercase();
37    if META_DESCRIPTION_NEEDLES
38        .iter()
39        .any(|needle| lower_description.contains(needle))
40    {
41        return true;
42    }
43    if let Some(section) = section {
44        let tail = section.rsplit('/').next().unwrap_or(section);
45        if META_SECTIONS.contains(&tail) {
46            return true;
47        }
48    }
49    if name.starts_with("task-") {
50        return true;
51    }
52    false
53}
54
55#[cfg(test)]
56mod tests {
57    use super::*;
58
59    #[test]
60    fn transitional_matches_description() {
61        assert!(is_transitional("This is a transitional package."));
62        assert!(is_transitional("Transitional Package for foo"));
63    }
64
65    #[test]
66    fn transitional_does_not_match_other_text() {
67        assert!(!is_transitional("Regular package"));
68        assert!(!is_transitional("transition helper"));
69    }
70
71    #[test]
72    fn meta_matches_description_metapackage() {
73        assert!(is_meta_package("foo", "metapackage for foo", None));
74        assert!(is_meta_package("foo", "Meta-Package for foo", None));
75        assert!(is_meta_package("foo", "Meta package for foo", None));
76    }
77
78    #[test]
79    fn meta_matches_description_dummy_variants() {
80        assert!(is_meta_package("foo", "dependency package", None));
81        assert!(is_meta_package(
82            "foo",
83            "Dummy Package pulling in bits",
84            None
85        ));
86        assert!(is_meta_package("foo", "Empty package", None));
87    }
88
89    #[test]
90    fn meta_matches_section() {
91        assert!(is_meta_package("foo", "regular", Some("metapackages")));
92        assert!(is_meta_package("foo", "regular", Some("tasks")));
93        assert!(is_meta_package(
94            "foo",
95            "regular",
96            Some("contrib/metapackages")
97        ));
98    }
99
100    #[test]
101    fn meta_matches_task_prefix() {
102        assert!(is_meta_package("task-desktop", "regular", None));
103    }
104
105    #[test]
106    fn meta_negative() {
107        assert!(!is_meta_package("foo", "regular package", Some("libs")));
108        assert!(!is_meta_package("foo", "regular package", None));
109        // section ending in a path that happens to suffix-match — must not
110        // false-positive on something like "subtasks".
111        assert!(!is_meta_package("foo", "regular", Some("subtasks")));
112    }
113}