diff --git a/crates/lib/src/changelog.rs b/crates/lib/src/changelog.rs index d212b38..3ff7880 100644 --- a/crates/lib/src/changelog.rs +++ b/crates/lib/src/changelog.rs @@ -1,5 +1,149 @@ +use std::{ + collections::{HashMap, HashSet}, + str::FromStr, +}; + +use pest::Parser; use pest_derive::Parser; +use crate::version::Version; + +#[derive(Debug)] +pub struct Changelog { + pub sections: Vec
, +} + +#[derive(Debug, Eq, PartialEq)] +pub struct Section { + pub version: Version, + pub date: Option, + pub categories: HashMap>, +} + +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub enum CategoryType { + MajorFeatures, + Features, + MinorFeatures, + Graphics, + Sounds, + Optimizations, + Balancing, + CombatBalancing, + CircuitNetwork, + Changes, + Bugfixes, + Modding, + Scripting, + Gui, + Control, + Translation, + Debug, + EaseOfUse, + Info, + Locale, + Other(String), +} + #[derive(Parser)] #[grammar = "changelog/grammar.pest"] -pub struct ChangelogParser; +struct ChangelogParser; + +#[derive(Debug)] +pub enum ChangelogParseError { + Pest(pest::error::Error), +} + +pub fn parse(content: &str) -> Result { + let mut result = Changelog { sections: vec![] }; + + let changelog = ChangelogParser::parse(Rule::changelog, content) + .map_err(ChangelogParseError::Pest)? + .next() + .unwrap(); + + for section_pair in changelog.into_inner() { + match section_pair.as_rule() { + Rule::section => { + let mut inner_rules = section_pair.into_inner(); + let ver_str = inner_rules.next().unwrap().as_str(); + let version = Version::from_str(ver_str).unwrap(); + + let mut section = Section { + version, + date: None, + categories: HashMap::new(), + }; + + for remaining in inner_rules { + match remaining.as_rule() { + Rule::date => { + section.date = Some(remaining.as_str().to_owned()); + } + Rule::category => { + let mut inner_rules = remaining.into_inner(); + let category_type = + CategoryType::from_str(inner_rules.next().unwrap().as_str()) + .unwrap(); + let entries = section.categories.entry(category_type).or_default(); + + for entry_pair in inner_rules { + match entry_pair.as_rule() { + Rule::entry => { + let str = entry_pair + .into_inner() + .map(|e| e.as_str()) + .collect::>() + .join("\n"); + + entries.insert(str); + } + _ => unreachable!(), + } + } + } + _ => unreachable!(), + } + } + + result.sections.push(section); + } + Rule::EOI => (), + _ => unreachable!(), + } + } + + result.sections.sort_by(|a, b| b.version.cmp(&a.version)); + + Ok(result) +} + +impl FromStr for CategoryType { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + "Major Features" => Ok(Self::MajorFeatures), + "Features" => Ok(Self::Features), + "Minor Features" => Ok(Self::MinorFeatures), + "Graphics" => Ok(Self::Graphics), + "Sounds" => Ok(Self::Sounds), + "Optimizations" => Ok(Self::Optimizations), + "Balancing" => Ok(Self::Balancing), + "Combat Balancing" => Ok(Self::CombatBalancing), + "Circuit Network" => Ok(Self::CircuitNetwork), + "Changes" => Ok(Self::Changes), + "Bugfixes" => Ok(Self::Bugfixes), + "Modding" => Ok(Self::Modding), + "Scripting" => Ok(Self::Scripting), + "Gui" => Ok(Self::Gui), + "Control" => Ok(Self::Control), + "Translation" => Ok(Self::Translation), + "Debug" => Ok(Self::Debug), + "Ease of use" => Ok(Self::EaseOfUse), + "Info" => Ok(Self::Info), + "Locale" => Ok(Self::Locale), + o => Ok(Self::Other(o.to_owned())), + } + } +} diff --git a/crates/lib/src/changelog/grammar.pest b/crates/lib/src/changelog/grammar.pest index 7ada90b..6687f08 100644 --- a/crates/lib/src/changelog/grammar.pest +++ b/crates/lib/src/changelog/grammar.pest @@ -23,7 +23,7 @@ entry_additional = _{ entry_sub_prefix ~ entry_line } entry = ${ entry_prefix ~ entry_line ~ (NEWLINE+ ~ entry_additional)* } category = ${ heading_line ~ (NEWLINE+ ~ entry)+ } -categories = ${ category ~ (NEWLINE+ ~ category)* } +categories = _{ category ~ (NEWLINE+ ~ category)* } section = ${ section_start ~ NEWLINE ~ version_line ~ diff --git a/crates/lib/tests/parsing.rs b/crates/lib/tests/parsing.rs index e107bac..97c2eb5 100644 --- a/crates/lib/tests/parsing.rs +++ b/crates/lib/tests/parsing.rs @@ -1,18 +1,18 @@ -use facti_lib::changelog::{ChangelogParser, Rule}; -use pest::Parser; +use facti_lib::changelog; #[test] fn test_basic_changelog() { let content = include_str!("parsing/changelog.txt"); - let changelog = ChangelogParser::parse(Rule::changelog, content); + let changelog = changelog::parse(content); assert!(changelog.is_ok()); + dbg!(changelog.unwrap()); } #[test] fn test_multi_section() { let content = include_str!("parsing/multi_section.txt"); - let changelog = ChangelogParser::parse(Rule::changelog, content); + let changelog = changelog::parse(content); assert!(changelog.is_ok()); } @@ -20,7 +20,32 @@ fn test_multi_section() { #[test] fn test_colon_in_heading() { let content = include_str!("parsing/colon_in_heading.txt"); - let changelog = ChangelogParser::parse(Rule::changelog, content); + let changelog = changelog::parse(content); assert!(changelog.is_ok()); } + +#[test] +fn test_real_parse() { + let content = include_str!("parsing/small.txt"); + let changelog = changelog::parse(content); + + assert!(changelog.is_ok()); +} + +#[test] +fn test_minimal() { + let content = include_str!("parsing/minimal.txt"); + let changelog = changelog::parse(content); + + assert!(changelog.is_ok()); +} + +#[test] +fn test_wrong_section_order() { + let content = include_str!("parsing/wrong_section_order.txt"); + let changelog = changelog::parse(content); + + // TODO: Add asserts to check it's now in correct order + assert!(changelog.is_ok()); +} diff --git a/crates/lib/tests/parsing/minimal.txt b/crates/lib/tests/parsing/minimal.txt new file mode 100644 index 0000000..326b1bd --- /dev/null +++ b/crates/lib/tests/parsing/minimal.txt @@ -0,0 +1,2 @@ +--------------------------------------------------------------------------------------------------- +Version: 1.2.3 diff --git a/crates/lib/tests/parsing/small.txt b/crates/lib/tests/parsing/small.txt new file mode 100644 index 0000000..0ec0ece --- /dev/null +++ b/crates/lib/tests/parsing/small.txt @@ -0,0 +1,5 @@ +--------------------------------------------------------------------------------------------------- +Version: 1.2.3 +Date: 2023-08-14 + Features: + - Made parser. diff --git a/crates/lib/tests/parsing/wrong_section_order.txt b/crates/lib/tests/parsing/wrong_section_order.txt new file mode 100644 index 0000000..35403d4 --- /dev/null +++ b/crates/lib/tests/parsing/wrong_section_order.txt @@ -0,0 +1,6 @@ +--------------------------------------------------------------------------------------------------- +Version: 1.0.0 +Date: 2022-01-01 +--------------------------------------------------------------------------------------------------- +Version: 2.0.0 +Date: 2023-05-05