blob: 243db644621e6de73053f031384955e73080339d [file] [log] [blame] [edit]
// Copyright 2023 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use std::fmt;
use std::str::FromStr;
use nom::character::complete::{alpha1, digit1};
use nom::combinator::map;
use nom::sequence::pair;
use nom::{branch::alt, bytes::complete::take_while, combinator::eof, multi::many0, IResult};
use anyhow::{Error, Result};
#[derive(Debug, PartialEq, Eq)]
pub enum VersionComponent {
Component(String),
Separator(String),
}
/// Defines the Version strings (for manipulation API)
///
/// https://mgorny.pl/articles/the-ultimate-guide-to-eapi-7.html#version-strings-for-manipulation
#[derive(Debug, PartialEq, Eq)]
pub struct VersionComponents {
pub components: Vec<VersionComponent>,
}
fn digit_component(input: &str) -> IResult<&str, VersionComponent> {
map(digit1, |s: &str| VersionComponent::Component(s.to_string()))(input)
}
fn alpha_component(input: &str) -> IResult<&str, VersionComponent> {
map(alpha1, |s: &str| VersionComponent::Component(s.to_string()))(input)
}
fn seperator_component(input: &str) -> IResult<&str, VersionComponent> {
map(
take_while(|c: char| !c.is_ascii_alphanumeric()),
|s: &str| VersionComponent::Separator(s.to_string()),
)(input)
}
fn expression(input: &str) -> IResult<&str, VersionComponents> {
let mut parts = Vec::new();
let (input, part) = seperator_component(input)?;
if let VersionComponent::Separator(ref s) = part {
if !s.is_empty() {
parts.push(part);
}
}
let (input, results) = many0(pair(
alt((digit_component, alpha_component)),
seperator_component,
))(input)?;
let size = results.len();
for (idx, (component, separator)) in results.into_iter().enumerate() {
parts.push(component);
// Avoid pushing an empty separator at the end
if let VersionComponent::Separator(ref s) = separator {
if idx != size - 1 || !s.is_empty() {
parts.push(separator);
}
}
}
let (input, _) = eof(input)?;
Ok((input, VersionComponents { components: parts }))
}
impl FromStr for VersionComponents {
type Err = Error;
fn from_str(input: &str) -> Result<Self, Self::Err> {
let (_, version) = expression(input).map_err(|err| err.to_owned())?;
Ok(version)
}
}
impl fmt::Display for VersionComponents {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for component in &self.components {
match component {
VersionComponent::Component(c) => write!(f, "{}", c)?,
VersionComponent::Separator(s) => write!(f, "{}", s)?,
};
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
fn s(s: &str) -> VersionComponent {
VersionComponent::Separator(s.to_string())
}
fn c(c: &str) -> VersionComponent {
VersionComponent::Component(c.to_string())
}
#[test]
fn test_empty() -> Result<()> {
let actual: VersionComponents = "".parse()?;
let expected: Vec<VersionComponent> = vec![];
assert_eq!(expected, actual.components);
assert_eq!("", format!("{}", actual));
Ok(())
}
#[test]
fn test_table() -> Result<()> {
/*
Taken from https://mgorny.pl/articles/the-ultimate-guide-to-eapi-7.html#version-strings-for-manipulation
Type s c s c s c s c s c
Index 0 1 1 2 2 3 3 4 4 5
1.2.3 1 . 2 . 3
1.2b_alpha4 1 . 2 b _ alpha 4
2Ab9s 2 Ab 9 s
A.4. A . 4 .
.11. . 11 .
*/
let table = [
("1.2.3", vec![c("1"), s("."), c("2"), s("."), c("3")]),
(
"1.2b_alpha4",
vec![
c("1"),
s("."),
c("2"),
s(""),
c("b"),
s("_"),
c("alpha"),
s(""),
c("4"),
],
),
(
"2Ab9s",
vec![c("2"), s(""), c("Ab"), s(""), c("9"), s(""), c("s")],
),
("A.4.", vec![c("A"), s("."), c("4"), s(".")]),
(".11.", vec![s("."), c("11"), s(".")]),
];
for (input, expected) in table {
let actual: VersionComponents = input.parse()?;
assert_eq!(expected, actual.components);
assert_eq!(input, format!("{}", actual));
}
Ok(())
}
}