Skip to main content

deps_gradle/parser/
properties.rs

1//! Parser for gradle.properties files.
2//!
3//! Provides key-value parsing and directory-walking lookup.
4
5use std::collections::HashMap;
6use std::path::Path;
7
8/// Parses a gradle.properties content into key-value pairs.
9///
10/// Lines starting with `#` or empty lines are ignored.
11/// Each line is split on the first `=`.
12pub fn parse_properties(content: &str) -> HashMap<String, String> {
13    content
14        .lines()
15        .filter(|l| !l.trim_start().starts_with('#') && !l.trim().is_empty())
16        .filter_map(|l| l.split_once('='))
17        .map(|(k, v)| (k.trim().to_string(), v.trim().to_string()))
18        .collect()
19}
20
21/// Finds and parses gradle.properties files by walking up from `start_dir`.
22///
23/// Merges properties from all levels, with child values overriding parent values.
24pub fn load_gradle_properties(start_dir: &Path) -> HashMap<String, String> {
25    let mut result = HashMap::new();
26    let mut chain = Vec::new();
27    let mut dir = Some(start_dir);
28
29    while let Some(d) = dir {
30        let props_file = d.join("gradle.properties");
31        if props_file.exists() {
32            chain.push(props_file);
33        }
34        dir = d.parent();
35    }
36
37    // Apply from root to leaf so child values override parent
38    for path in chain.into_iter().rev() {
39        if let Ok(content) = std::fs::read_to_string(&path) {
40            result.extend(parse_properties(&content));
41        }
42    }
43
44    result
45}
46
47#[cfg(test)]
48mod tests {
49    use super::*;
50
51    #[test]
52    fn test_parse_basic() {
53        let content = "kotlinVersion=2.1.10\nspringVersion=3.2.0\n";
54        let props = parse_properties(content);
55        assert_eq!(
56            props.get("kotlinVersion").map(|s| s.as_str()),
57            Some("2.1.10")
58        );
59        assert_eq!(
60            props.get("springVersion").map(|s| s.as_str()),
61            Some("3.2.0")
62        );
63    }
64
65    #[test]
66    fn test_parse_ignores_comments() {
67        let content = "# this is a comment\nkey=value\n";
68        let props = parse_properties(content);
69        assert_eq!(props.len(), 1);
70        assert_eq!(props.get("key").map(|s| s.as_str()), Some("value"));
71    }
72
73    #[test]
74    fn test_parse_ignores_empty_lines() {
75        let content = "\nkey=value\n\n";
76        let props = parse_properties(content);
77        assert_eq!(props.len(), 1);
78    }
79
80    #[test]
81    fn test_parse_trims_whitespace() {
82        let content = "  key  =  value  \n";
83        let props = parse_properties(content);
84        assert_eq!(props.get("key").map(|s| s.as_str()), Some("value"));
85    }
86
87    #[test]
88    fn test_parse_value_with_equals() {
89        // Only splits on the first '='
90        let content = "url=https://example.com?a=b\n";
91        let props = parse_properties(content);
92        assert_eq!(
93            props.get("url").map(|s| s.as_str()),
94            Some("https://example.com?a=b")
95        );
96    }
97
98    #[test]
99    fn test_parse_empty() {
100        let props = parse_properties("");
101        assert!(props.is_empty());
102    }
103}