const DOT: u16 = 0x2e;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct UnsupportedVersion {
pub name: String,
pub version: Vec<u16>,
}
pub fn unsupported_message(unsupported: &UnsupportedVersion) -> String {
return format!(
"{} has version {}, which is not a plain major.minor.patch release; bump it by hand in the same commit.",
unsupported.name,
json_quote_units(&unsupported.version)
);
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ReleaseBump {
pub from: String,
pub to: String,
}
fn is_release_component(component: &[u16]) -> bool {
let digits: bool = !component.is_empty()
&& component
.iter()
.all(|unit: &u16| return (u16::from(b'0')..=u16::from(b'9')).contains(unit));
return digits && !(component.len() > 1 && component[0] == u16::from(b'0'));
}
fn increment_decimal(digits: &[u16]) -> String {
let mut result: Vec<u8> = Vec::new();
let mut carry: bool = true;
for unit in digits.iter().rev() {
let digit: u8 = u8::try_from(*unit).unwrap_or(b'0');
if carry && digit == b'9' {
result.push(b'0');
} else if carry {
result.push(digit + 1);
carry = false;
} else {
result.push(digit);
}
}
if carry {
result.push(b'1');
}
return result
.iter()
.rev()
.map(|byte: &u8| return char::from(*byte))
.collect();
}
pub fn patch_bump_version(name: &str, version: &[u16]) -> Result<ReleaseBump, UnsupportedVersion> {
let components: Vec<&[u16]> = version.split(|unit: &u16| return *unit == DOT).collect();
let [major, minor, patch] = components.as_slice() else {
return Err(UnsupportedVersion {
name: String::from(name),
version: version.to_vec(),
});
};
if !(is_release_component(major) && is_release_component(minor) && is_release_component(patch))
{
return Err(UnsupportedVersion {
name: String::from(name),
version: version.to_vec(),
});
}
return Ok(ReleaseBump {
from: String::from_utf16_lossy(version),
to: format!(
"{}.{}.{}",
String::from_utf16_lossy(major),
String::from_utf16_lossy(minor),
increment_decimal(patch)
),
});
}
pub fn json_quote_units(units: &[u16]) -> String {
let mut quoted: String = String::from("\"");
for decoded in char::decode_utf16(units.iter().copied()) {
match decoded {
Ok('"') => quoted.push_str("\\\""),
Ok('\\') => quoted.push_str("\\\\"),
Ok('\u{8}') => quoted.push_str("\\b"),
Ok('\u{c}') => quoted.push_str("\\f"),
Ok('\n') => quoted.push_str("\\n"),
Ok('\r') => quoted.push_str("\\r"),
Ok('\t') => quoted.push_str("\\t"),
Ok(control) if control < ' ' => {
quoted.push_str(&format!("\\u{:04x}", u32::from(control)));
}
Ok(literal) => quoted.push(literal),
Err(unpaired) => {
quoted.push_str(&format!("\\u{:04x}", unpaired.unpaired_surrogate()));
}
}
}
quoted.push('"');
return quoted;
}
#[cfg(test)]
#[path = "dependent_version_release_tests.rs"]
mod tests;