1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
//! GDAL Version Inspection Utilities
//!
//! ## Example
//!
//! Get the same string provided by using `--version` with the various GDAL CLI tools,
//! use [`VersionInfo::version_summary`]:
//!
//! ```rust, no_run
//! use gdal::version::VersionInfo;
//! let gdal_ver = VersionInfo::version_summary();
//! println!("{gdal_ver}")
//! ```
//! ```text,
//! GDAL 3.5.1, released 2022/06/30
//! ```
//!
//! For all the available version properties (except [`VersionInfo::license`], which is long),
//! use [VersionInfo::version_report]:
//!
//! ```rust, no_run
//! # use gdal::version::VersionInfo;
//! let report = VersionInfo::version_report();
//! println!("{report}");
//! ```
//! ```text
//! GDALVersionInfo {
//!     RELEASE_NAME: "3.5.1"
//!     RELEASE_DATE: "20220630"
//!     VERSION_NUM: "3050100"
//!     BUILD_INFO {
//!         PAM_ENABLED: "YES"
//!         PROJ_BUILD_VERSION: "9.0.1"
//!         OGR_ENABLED: "YES"
//!         PROJ_RUNTIME_VERSION: "9.0.1"
//!         GEOS_ENABLED: "YES"
//!         GEOS_VERSION: "3.11.0-CAPI-1.17.0"
//!     }
//! }
//! ```
//!
//! See [`VersionInfo`] for further options.

use crate::utils::_string;
use std::collections::HashMap;
use std::ffi::CString;
use std::fmt::Write;

/// Calls `GDALVersionInfo`, expecting `key` as one of the following values:
///
/// “VERSION_NUM”, “RELEASE_DATE”, “RELEASE_NAME”, "-–version”, “LICENSE”, “BUILD_INFO”.
///
/// See [`VersionInfo`] for a more ergonomic means of accessing these components.
///
/// Details: [`const char *GDALVersionInfo(const char*)`](https://gdal.org/api/raster_c_api.html#_CPPv415GDALVersionInfoPKc)
pub fn version_info(key: &str) -> String {
    let c_key = CString::new(key.as_bytes()).unwrap();
    _string(unsafe { gdal_sys::GDALVersionInfo(c_key.as_ptr()) })
}

/// Convenience functions for the various pre-defined queryable properties of GDAL version information.
///
/// See [module documentation](crate::version) for examples.
///
pub struct VersionInfo;
impl VersionInfo {
    /// Returns one line version message suitable for use in response to version requests. i.e. “GDAL 1.1.7, released 2002/04/16”
    pub fn version_summary() -> String {
        version_info("--version")
    }
    /// Returns `GDAL_VERSION_NUM` formatted as a string. i.e. “1170”
    pub fn version_num() -> String {
        version_info("VERSION_NUM")
    }
    /// Returns `GDAL_RELEASE_DATE` formatted as a string. i.e. “20020416"
    pub fn release_date() -> String {
        version_info("RELEASE_DATE")
    }
    /// Returns the `GDAL_RELEASE_NAME`. ie. “1.1.7”
    pub fn release_name() -> String {
        version_info("RELEASE_NAME")
    }
    /// Returns the content of the `LICENSE.TXT` file from the `GDAL_DATA` directory.
    pub fn license() -> String {
        version_info("LICENSE")
    }
    /// Get a dictionary of GDAL build configuration options, such as `GEOS_VERSION` and
    /// `OGR_ENABLED`.
    pub fn build_info() -> HashMap<String, String> {
        let text = version_info("BUILD_INFO");
        text.lines()
            .filter_map(|l| l.split_once('='))
            .map(|p| (p.0.to_string(), p.1.to_string()))
            .collect()
    }

    /// Determine if GDAL is compiled with [GEOS](https://libgeos.org/) support.
    pub fn has_geos() -> bool {
        version_info("BUILD_INFO").contains("GEOS_ENABLED=YES")
    }

    /// Render all available version and build details in a multiline, debug string
    pub fn version_report() -> String {
        let mut buff: String = "GDALVersionInfo {\n".into();

        fn kv(buff: &mut String, l: usize, k: &str, v: &str) {
            writeln!(buff, "{:indent$}{k}: \"{v}\"", " ", indent = l * 4).unwrap();
        }

        kv(&mut buff, 1, "RELEASE_NAME", &Self::release_name());
        kv(&mut buff, 1, "RELEASE_DATE", &Self::release_date());
        kv(&mut buff, 1, "VERSION_NUM", &Self::version_num());
        buff.push_str("    BUILD_INFO {\n");

        Self::build_info()
            .iter()
            .for_each(|(k, v)| kv(&mut buff, 2, k, v));

        buff.push_str("    }\n");
        buff.push('}');
        buff
    }
}

#[cfg(test)]
mod tests {
    use super::version_info;
    use crate::version::VersionInfo;

    #[test]
    fn test_version_info() {
        let release_date = version_info("RELEASE_DATE");
        let release_name = version_info("RELEASE_NAME");
        let version_text = version_info("--version");

        let mut date_iter = release_date.chars();

        let expected_text: String = format!(
            "GDAL {}, released {}/{}/{}",
            release_name,
            date_iter.by_ref().take(4).collect::<String>(),
            date_iter.by_ref().take(2).collect::<String>(),
            date_iter.by_ref().take(2).collect::<String>(),
        );

        assert_eq!(version_text, expected_text);
    }

    #[test]
    fn test_version_info_functions() {
        let rel_name = VersionInfo::release_name();
        assert!(!rel_name.is_empty());

        let build = VersionInfo::build_info();
        assert!(!build.is_empty());

        let rpt = VersionInfo::version_report();
        assert!(rpt.contains(&rel_name));

        let license = VersionInfo::license();
        assert!(!license.is_empty());
    }

    #[test]
    fn test_has_geos() {
        let has_geos = VersionInfo::build_info()
            .get("GEOS_ENABLED")
            .unwrap_or(&"NO".into())
            == "YES";
        assert_eq!(VersionInfo::has_geos(), has_geos);
    }
}