notabene/changelog/
parsed.rs

1//! Borrowed versions of changelog types.
2//!
3//! These types also contain location information, used by the [`Linter`](crate::linter::Linter).
4use serde::Serialize;
5
6use crate::span::{Locator, Span, Spanned};
7
8use super::traits::Release as _;
9use super::{owned, traits};
10
11type SpannedStr<'a> = Spanned<&'a str>;
12
13#[derive(Debug, Default, PartialEq, Serialize)]
14pub struct ParsedChangelog<'a> {
15    pub(crate) source: &'a str,
16    pub(crate) title: Option<SpannedStr<'a>>,
17    pub(crate) unreleased: Option<ParsedUnreleased<'a>>,
18    pub(crate) releases: Vec<ParsedRelease<'a>>,
19    pub(crate) invalid_spans: Vec<InvalidSpan>,
20}
21
22#[derive(Debug, Default, PartialEq, Serialize)]
23pub struct ParsedUnreleased<'a> {
24    pub(crate) heading_span: Span,
25    pub(crate) url: Option<String>,
26    pub(crate) changes: Vec<ParsedChanges<'a>>,
27}
28
29#[derive(Debug, Default, PartialEq, Serialize)]
30pub struct ParsedRelease<'a> {
31    pub(crate) heading_span: Span,
32    pub(crate) version: SpannedStr<'a>,
33    pub(crate) url: Option<String>,
34    pub(crate) date: Option<SpannedStr<'a>>,
35    pub(crate) yanked: Option<SpannedStr<'a>>,
36    pub(crate) changes: Vec<ParsedChanges<'a>>,
37}
38
39#[derive(Debug, Default, PartialEq, Serialize)]
40pub struct ParsedChanges<'a> {
41    pub(crate) heading_span: Span,
42    pub(crate) kind: SpannedStr<'a>,
43    pub(crate) items: Vec<SpannedStr<'a>>,
44}
45
46#[derive(Debug, PartialEq, Serialize)]
47pub enum InvalidSpan {
48    InvalidTitle(Span),
49    InvalidSectionHeading(Span),
50    UndefinedLinkReference(Span),
51    DuplicateUnreleased(Span),
52    DuplicateTitle(Span),
53    InvalidUnreleasedPosition(Span),
54}
55
56impl<'a> traits::Changelog for ParsedChangelog<'a> {
57    type Unreleased = ParsedUnreleased<'a>;
58    type Release = ParsedRelease<'a>;
59
60    fn title(&self) -> Option<&str> {
61        self.title.as_deref()
62    }
63
64    fn unreleased(&self) -> Option<&Self::Unreleased> {
65        self.unreleased.as_ref()
66    }
67
68    fn releases(&self) -> &[Self::Release] {
69        &self.releases
70    }
71}
72
73impl<'a> traits::Unreleased for ParsedUnreleased<'a> {
74    type Changes = ParsedChanges<'a>;
75
76    fn url(&self) -> Option<&str> {
77        self.url.as_deref()
78    }
79
80    fn changes(&self) -> &[Self::Changes] {
81        &self.changes
82    }
83}
84
85impl<'a> traits::Release for ParsedRelease<'a> {
86    type Changes = ParsedChanges<'a>;
87
88    fn version(&self) -> &str {
89        &self.version
90    }
91
92    fn url(&self) -> Option<&str> {
93        self.url.as_deref()
94    }
95
96    fn date(&self) -> Option<&str> {
97        self.date.as_deref()
98    }
99
100    fn yanked(&self) -> bool {
101        self.yanked.is_some_and(|s| s.value == "[YANKED]")
102    }
103
104    fn changes(&self) -> &[Self::Changes] {
105        &self.changes
106    }
107}
108
109impl<'a> traits::Changes for ParsedChanges<'a> {
110    fn kind(&self) -> &str {
111        self.kind.value
112    }
113
114    fn items(&self) -> impl Iterator<Item = &str> {
115        self.items.iter().map(|i| &**i)
116    }
117}
118
119impl<'a> ParsedChangelog<'a> {
120    pub fn parse(s: &'a str) -> Self {
121        crate::parser::parse(s)
122    }
123
124    pub(crate) fn locator(&self) -> Locator<'a> {
125        Locator::new(self.source)
126    }
127
128    pub(crate) fn locate(
129        &self,
130        diagnostic: &crate::Diagnostic<Span>,
131    ) -> crate::Diagnostic<crate::span::Position> {
132        self.locator().locate(diagnostic)
133    }
134
135    pub fn locate_all(
136        &self,
137        diagnostics: &[crate::Diagnostic],
138    ) -> Vec<crate::Diagnostic<crate::span::Position>> {
139        diagnostics.iter().map(|d| self.locate(d)).collect()
140    }
141
142    pub fn to_owned(&self) -> owned::OwnedChangelog {
143        owned::OwnedChangelog {
144            title: self.title.map(|s| s.value.to_owned()),
145            unreleased: self.unreleased.as_ref().map(|u| u.to_owned()),
146            releases: self.releases.iter().map(|r| r.to_owned()).collect(),
147        }
148    }
149}
150
151impl<'a> ParsedUnreleased<'a> {
152    pub fn to_owned(&self) -> owned::OwnedUnreleased {
153        owned::OwnedUnreleased {
154            url: self.url.clone(),
155            changes: self.changes.iter().map(|c| c.to_owned()).collect(),
156        }
157    }
158}
159
160impl<'a> ParsedRelease<'a> {
161    pub fn to_owned(&self) -> owned::OwnedRelease {
162        owned::OwnedRelease {
163            version: self.version.value.to_owned(),
164            url: self.url.clone(),
165            date: self.date.map(|s| s.value.to_owned()),
166            yanked: self.yanked(),
167            changes: self.changes.iter().map(|c| c.to_owned()).collect(),
168        }
169    }
170}
171
172impl<'a> ParsedChanges<'a> {
173    pub fn to_owned(&self) -> owned::OwnedChanges {
174        owned::OwnedChanges {
175            kind: self.kind.value.to_owned(),
176            items: self.items.iter().map(|i| i.value.to_owned()).collect(),
177        }
178    }
179}