use subversion_sys::svn_version_t;
pub struct Version(pub(crate) *const svn_version_t);
impl Version {
fn equal(&self, other: &Version) -> bool {
!matches!(unsafe { subversion_sys::svn_ver_equal(self.0, other.0) }, 0)
}
pub fn compatible(&self, other: &Version) -> bool {
!matches!(
unsafe { subversion_sys::svn_ver_compatible(self.0, other.0) },
0
)
}
pub fn major(&self) -> i32 {
unsafe { self.0.as_ref().unwrap().major }
}
pub fn minor(&self) -> i32 {
unsafe { self.0.as_ref().unwrap().minor }
}
pub fn patch(&self) -> i32 {
unsafe { self.0.as_ref().unwrap().patch }
}
pub fn tag(&self) -> &str {
unsafe {
let tag = self.0.as_ref().unwrap().tag;
std::ffi::CStr::from_ptr(tag).to_str().unwrap()
}
}
}
impl PartialEq for Version {
fn eq(&self, other: &Version) -> bool {
self.equal(other)
}
}
impl Eq for Version {}
impl std::fmt::Display for Version {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let tag = self.tag();
if tag.is_empty() {
write!(f, "{}.{}.{}", self.major(), self.minor(), self.patch())
} else {
write!(
f,
"{}.{}.{}-{}",
self.major(),
self.minor(),
self.patch(),
tag
)
}
}
}
impl std::fmt::Debug for Version {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Version")
.field("major", &self.major())
.field("minor", &self.minor())
.field("patch", &self.patch())
.field("tag", &self.tag())
.finish()
}
}
pub struct VersionExtended(
*const subversion_sys::svn_version_extended_t,
#[allow(dead_code)] apr::Pool<'static>,
);
impl VersionExtended {
pub fn get(verbose: bool) -> Self {
unsafe {
let pool = apr::Pool::new();
let ptr = subversion_sys::svn_version_extended(verbose as i32, pool.as_mut_ptr());
VersionExtended(ptr, pool)
}
}
pub fn build_date(&self) -> Option<&str> {
unsafe {
let date_ptr = subversion_sys::svn_version_ext_build_date(self.0);
if date_ptr.is_null() {
None
} else {
Some(std::ffi::CStr::from_ptr(date_ptr).to_str().unwrap_or(""))
}
}
}
pub fn build_time(&self) -> Option<&str> {
unsafe {
let time_ptr = subversion_sys::svn_version_ext_build_time(self.0);
if time_ptr.is_null() {
None
} else {
Some(std::ffi::CStr::from_ptr(time_ptr).to_str().unwrap_or(""))
}
}
}
pub fn build_host(&self) -> Option<&str> {
unsafe {
let host_ptr = subversion_sys::svn_version_ext_build_host(self.0);
if host_ptr.is_null() {
None
} else {
Some(std::ffi::CStr::from_ptr(host_ptr).to_str().unwrap_or(""))
}
}
}
pub fn copyright(&self) -> Option<&str> {
unsafe {
let copyright_ptr = subversion_sys::svn_version_ext_copyright(self.0);
if copyright_ptr.is_null() {
None
} else {
Some(
std::ffi::CStr::from_ptr(copyright_ptr)
.to_str()
.unwrap_or(""),
)
}
}
}
pub fn runtime_host(&self) -> Option<&str> {
unsafe {
let host_ptr = subversion_sys::svn_version_ext_runtime_host(self.0);
if host_ptr.is_null() {
None
} else {
Some(std::ffi::CStr::from_ptr(host_ptr).to_str().unwrap_or(""))
}
}
}
pub fn runtime_osname(&self) -> Option<&str> {
unsafe {
let os_ptr = subversion_sys::svn_version_ext_runtime_osname(self.0);
if os_ptr.is_null() {
None
} else {
Some(std::ffi::CStr::from_ptr(os_ptr).to_str().unwrap_or(""))
}
}
}
}
unsafe impl Send for VersionExtended {}
unsafe impl Sync for VersionExtended {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_version_extended() {
let ext = VersionExtended::get(false);
if let Some(build_date) = ext.build_date() {
assert!(!build_date.is_empty(), "Build date should not be empty");
}
if let Some(build_time) = ext.build_time() {
assert!(!build_time.is_empty(), "Build time should not be empty");
}
if let Some(build_host) = ext.build_host() {
assert!(!build_host.is_empty(), "Build host should not be empty");
}
if let Some(copyright) = ext.copyright() {
assert!(!copyright.is_empty(), "Copyright should not be empty");
}
if let Some(runtime_host) = ext.runtime_host() {
assert!(!runtime_host.is_empty(), "Runtime host should not be empty");
}
if let Some(runtime_osname) = ext.runtime_osname() {
assert!(
!runtime_osname.is_empty(),
"Runtime OS name should not be empty"
);
}
}
#[test]
fn test_version_extended_verbose() {
let ext = VersionExtended::get(true);
let copyright = ext.copyright();
assert!(copyright.is_some(), "Verbose mode should provide copyright");
assert!(
!copyright.unwrap().is_empty(),
"Copyright should not be empty"
);
}
#[test]
fn test_version_extended_methods_return_values() {
let ext = VersionExtended::get(false);
if let Some(copyright) = ext.copyright() {
assert!(
!copyright.is_empty(),
"Copyright should not be empty string"
);
assert_ne!(copyright, "xyzzy", "Copyright should not be placeholder");
}
if let Some(build_date) = ext.build_date() {
assert!(
!build_date.is_empty(),
"Build date should not be empty string"
);
assert_ne!(build_date, "xyzzy", "Build date should not be placeholder");
}
if let Some(build_time) = ext.build_time() {
assert!(
!build_time.is_empty(),
"Build time should not be empty string"
);
assert_ne!(build_time, "xyzzy", "Build time should not be placeholder");
}
if let Some(build_host) = ext.build_host() {
assert!(
!build_host.is_empty(),
"Build host should not be empty string"
);
assert_ne!(build_host, "xyzzy", "Build host should not be placeholder");
}
if let Some(runtime_host) = ext.runtime_host() {
assert!(
!runtime_host.is_empty(),
"Runtime host should not be empty string"
);
assert_ne!(
runtime_host, "xyzzy",
"Runtime host should not be placeholder"
);
}
if let Some(runtime_osname) = ext.runtime_osname() {
assert!(
!runtime_osname.is_empty(),
"Runtime OS should not be empty string"
);
assert_ne!(
runtime_osname, "xyzzy",
"Runtime OS should not be placeholder"
);
}
}
#[test]
fn test_version_extended_verbose_provides_copyright() {
let ext = VersionExtended::get(true);
let copyright = ext.copyright();
if let Some(c) = copyright {
assert!(!c.is_empty(), "Copyright should not be empty");
assert_ne!(c, "xyzzy", "Copyright should not be placeholder");
assert!(
c.contains("Apache") || c.contains("Subversion") || c.contains("Copyright"),
"Copyright should contain expected keywords, got: {}",
c
);
}
}
#[test]
fn test_version_major_minor_patch() {
let version = unsafe {
let v = subversion_sys::svn_subr_version();
Version(v)
};
let major = version.major();
assert!(
major >= 1,
"SVN major version should be >= 1, got {}",
major
);
assert!(major > 0, "Major version should be positive");
assert_ne!(major, -1, "Major version should not be -1");
let minor = version.minor();
assert!(
minor >= 0,
"Minor version should be non-negative, got {}",
minor
);
assert_ne!(minor, -1, "Minor version should not be -1");
let patch = version.patch();
assert!(
patch >= 0,
"Patch version should be non-negative, got {}",
patch
);
assert_ne!(patch, -1, "Patch version should not be -1");
}
#[test]
fn test_version_tag() {
let version = unsafe {
let v = subversion_sys::svn_subr_version();
Version(v)
};
let tag = version.tag();
assert_ne!(tag, "xyzzy", "Version tag should not be placeholder");
}
#[test]
fn test_version_equal_same_version() {
let v1 = unsafe {
let v = subversion_sys::svn_subr_version();
Version(v)
};
let v2 = unsafe {
let v = subversion_sys::svn_subr_version();
Version(v)
};
assert!(v1.equal(&v2), "Same version should be equal to itself");
assert_eq!(v1, v2, "PartialEq should work for same version");
}
#[test]
fn test_version_compatible_with_itself() {
let v1 = unsafe {
let v = subversion_sys::svn_subr_version();
Version(v)
};
let v2 = unsafe {
let v = subversion_sys::svn_subr_version();
Version(v)
};
assert!(
v1.compatible(&v2),
"Version should be compatible with itself"
);
}
#[test]
fn test_version_partialeq_reflexive() {
let v = unsafe {
let v = subversion_sys::svn_subr_version();
Version(v)
};
assert_eq!(v, v, "Version should equal itself (reflexivity)");
}
}