Skip to main content

oxigeo_cli/util/
creation_options.rs

1//! Creation options parsing for GDAL-compatible KEY=VALUE output flags
2
3/// Parse a KEY=VALUE string into a (key, value) tuple.
4/// Used as a clap `value_parser`.
5pub fn parse_key_value(s: &str) -> std::result::Result<(String, String), String> {
6    let pos = s
7        .find('=')
8        .ok_or_else(|| format!("invalid KEY=VALUE: no '=' in '{s}'"))?;
9    let key = s[..pos].to_string();
10    let value = s[pos + 1..].to_string();
11    if key.is_empty() {
12        return Err(format!("empty key in '{s}'"));
13    }
14    Ok((key, value))
15}
16
17/// Mapped creation options after interpreting well-known GDAL keys.
18pub struct MappedOptions {
19    /// LZW compression requested (`COMPRESS=LZW`).
20    pub compress_lzw: bool,
21    /// Deflate compression requested (`COMPRESS=DEFLATE`).
22    pub compress_deflate: bool,
23    /// Tiled output requested (`TILED=YES`).
24    pub tiled: bool,
25    /// BigTIFF output requested (`BIGTIFF=YES`).
26    pub bigtiff: bool,
27    /// Key=value pairs that were not recognised.
28    pub unknown_pairs: Vec<(String, String)>,
29}
30
31/// Interpret a slice of (key, value) pairs into structured options.
32/// Unknown keys are forwarded in `unknown_pairs` and warned about.
33pub fn map_creation_options(pairs: &[(String, String)]) -> MappedOptions {
34    let mut out = MappedOptions {
35        compress_lzw: false,
36        compress_deflate: false,
37        tiled: false,
38        bigtiff: false,
39        unknown_pairs: Vec::new(),
40    };
41    for (k, v) in pairs {
42        match k.to_uppercase().as_str() {
43            "COMPRESS" => match v.to_uppercase().as_str() {
44                "LZW" => out.compress_lzw = true,
45                "DEFLATE" => out.compress_deflate = true,
46                _ => {
47                    tracing::warn!("Unknown COMPRESS value: {v}");
48                    out.unknown_pairs.push((k.clone(), v.clone()));
49                }
50            },
51            "TILED" if v.to_uppercase() == "YES" => out.tiled = true,
52            "BIGTIFF" if v.to_uppercase() == "YES" => out.bigtiff = true,
53            _ => {
54                tracing::warn!("Unknown creation option: {k}={v}");
55                out.unknown_pairs.push((k.clone(), v.clone()));
56            }
57        }
58    }
59    out
60}
61
62#[cfg(test)]
63mod tests {
64    use super::*;
65
66    #[test]
67    fn test_co_multiple_flags() {
68        let result = parse_key_value("COMPRESS=LZW");
69        assert!(result.is_ok());
70        let (k, v) = result.expect("parse should succeed");
71        assert_eq!(k, "COMPRESS");
72        assert_eq!(v, "LZW");
73    }
74
75    #[test]
76    fn test_co_invalid_no_equals_errors() {
77        let result = parse_key_value("BADFLAG");
78        assert!(result.is_err());
79    }
80
81    #[test]
82    fn test_co_empty_key_errors() {
83        let result = parse_key_value("=VALUE");
84        assert!(result.is_err());
85    }
86
87    #[test]
88    fn test_map_compress_lzw() {
89        let pairs = vec![("COMPRESS".to_string(), "LZW".to_string())];
90        let mapped = map_creation_options(&pairs);
91        assert!(mapped.compress_lzw);
92        assert!(!mapped.compress_deflate);
93        assert!(mapped.unknown_pairs.is_empty());
94    }
95
96    #[test]
97    fn test_map_compress_deflate() {
98        let pairs = vec![("COMPRESS".to_string(), "DEFLATE".to_string())];
99        let mapped = map_creation_options(&pairs);
100        assert!(!mapped.compress_lzw);
101        assert!(mapped.compress_deflate);
102    }
103
104    #[test]
105    fn test_map_tiled_yes() {
106        let pairs = vec![("TILED".to_string(), "YES".to_string())];
107        let mapped = map_creation_options(&pairs);
108        assert!(mapped.tiled);
109    }
110
111    #[test]
112    fn test_map_bigtiff_yes() {
113        let pairs = vec![("BIGTIFF".to_string(), "YES".to_string())];
114        let mapped = map_creation_options(&pairs);
115        assert!(mapped.bigtiff);
116    }
117
118    #[test]
119    fn test_map_unknown_forwarded() {
120        let pairs = vec![("UNKNOWN_KEY".to_string(), "some_value".to_string())];
121        let mapped = map_creation_options(&pairs);
122        assert_eq!(mapped.unknown_pairs.len(), 1);
123    }
124}