1use crate::lex::lex;
2use crate::SyntaxKind;
3use crate::SyntaxKind::*;
4#[cfg(feature = "chrono")]
5use chrono::{DateTime, FixedOffset};
6use debversion::Version;
7use rowan::ast::AstNode;
8use std::str::FromStr;
9
10pub trait IntoTimestamp {
15 fn into_timestamp(self) -> String;
17}
18
19impl IntoTimestamp for String {
20 fn into_timestamp(self) -> String {
21 self
22 }
23}
24
25impl IntoTimestamp for &str {
26 fn into_timestamp(self) -> String {
27 self.to_string()
28 }
29}
30
31#[cfg(feature = "chrono")]
32impl<Tz: chrono::TimeZone> IntoTimestamp for DateTime<Tz>
33where
34 Tz::Offset: std::fmt::Display,
35{
36 fn into_timestamp(self) -> String {
37 const CHANGELOG_TIME_FORMAT: &str = "%a, %d %b %Y %H:%M:%S %z";
38 self.format(CHANGELOG_TIME_FORMAT).to_string()
39 }
40}
41
42#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, PartialOrd, Ord)]
43pub enum Urgency {
45 #[default]
46 Low,
48
49 Medium,
51
52 High,
54
55 Emergency,
57
58 Critical,
60}
61
62impl std::fmt::Display for Urgency {
63 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
64 match self {
65 Urgency::Low => f.write_str("low"),
66 Urgency::Medium => f.write_str("medium"),
67 Urgency::High => f.write_str("high"),
68 Urgency::Emergency => f.write_str("emergency"),
69 Urgency::Critical => f.write_str("critical"),
70 }
71 }
72}
73
74impl FromStr for Urgency {
75 type Err = ParseError;
76
77 fn from_str(s: &str) -> Result<Self, Self::Err> {
78 match s.to_lowercase().as_str() {
79 "low" => Ok(Urgency::Low),
80 "medium" => Ok(Urgency::Medium),
81 "high" => Ok(Urgency::High),
82 "emergency" => Ok(Urgency::Emergency),
83 "critical" => Ok(Urgency::Critical),
84 _ => Err(ParseError(vec![format!("invalid urgency: {}", s)])),
85 }
86 }
87}
88
89#[derive(Debug)]
90pub enum Error {
92 Io(std::io::Error),
94
95 Parse(ParseError),
97}
98
99impl std::fmt::Display for Error {
100 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
101 match &self {
102 Error::Io(e) => write!(f, "IO error: {}", e),
103 Error::Parse(e) => write!(f, "Parse error: {}", e),
104 }
105 }
106}
107
108impl From<std::io::Error> for Error {
109 fn from(e: std::io::Error) -> Self {
110 Error::Io(e)
111 }
112}
113
114impl std::error::Error for Error {}
115
116#[derive(Debug, Clone, PartialEq, Eq, Hash)]
117pub struct ParseError(Vec<String>);
119
120impl std::fmt::Display for ParseError {
121 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
122 for err in &self.0 {
123 writeln!(f, "{}", err)?;
124 }
125 Ok(())
126 }
127}
128
129impl std::error::Error for ParseError {}
130
131impl From<ParseError> for Error {
132 fn from(e: ParseError) -> Self {
133 Error::Parse(e)
134 }
135}
136
137#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
141pub enum Lang {}
142impl rowan::Language for Lang {
143 type Kind = SyntaxKind;
144 fn kind_from_raw(raw: rowan::SyntaxKind) -> Self::Kind {
145 unsafe { std::mem::transmute::<u16, SyntaxKind>(raw.0) }
146 }
147 fn kind_to_raw(kind: Self::Kind) -> rowan::SyntaxKind {
148 kind.into()
149 }
150}
151
152use rowan::{GreenNode, GreenToken};
155
156use rowan::GreenNodeBuilder;
160
161#[derive(Debug)]
166pub struct Parse<T> {
167 green: GreenNode,
168 errors: Vec<String>,
169 _ty: std::marker::PhantomData<T>,
170}
171
172impl<T> Clone for Parse<T> {
175 fn clone(&self) -> Self {
176 Parse {
177 green: self.green.clone(),
178 errors: self.errors.clone(),
179 _ty: std::marker::PhantomData,
180 }
181 }
182}
183
184impl<T> PartialEq for Parse<T> {
185 fn eq(&self, other: &Self) -> bool {
186 self.green == other.green && self.errors == other.errors
187 }
188}
189
190impl<T> Eq for Parse<T> {}
191
192unsafe impl<T> Send for Parse<T> {}
194unsafe impl<T> Sync for Parse<T> {}
195
196impl<T> Parse<T> {
197 pub fn new(green: GreenNode, errors: Vec<String>) -> Self {
199 Parse {
200 green,
201 errors,
202 _ty: std::marker::PhantomData,
203 }
204 }
205
206 pub fn green(&self) -> &GreenNode {
208 &self.green
209 }
210
211 pub fn errors(&self) -> &[String] {
213 &self.errors
214 }
215
216 pub fn ok(&self) -> bool {
218 self.errors.is_empty()
219 }
220
221 pub fn to_result(self) -> Result<T, ParseError>
223 where
224 T: AstNode<Language = Lang>,
225 {
226 if self.errors.is_empty() {
227 let node = SyntaxNode::new_root(self.green);
228 Ok(T::cast(node).expect("root node has wrong type"))
229 } else {
230 Err(ParseError(self.errors))
231 }
232 }
233
234 pub fn to_mut_result(self) -> Result<T, ParseError>
236 where
237 T: AstNode<Language = Lang>,
238 {
239 if self.errors.is_empty() {
240 let node = SyntaxNode::new_root_mut(self.green);
241 Ok(T::cast(node).expect("root node has wrong type"))
242 } else {
243 Err(ParseError(self.errors))
244 }
245 }
246
247 pub fn tree(&self) -> T
249 where
250 T: AstNode<Language = Lang>,
251 {
252 assert!(
253 self.errors.is_empty(),
254 "tried to get tree with errors: {:?}",
255 self.errors
256 );
257 let node = SyntaxNode::new_root(self.green.clone());
258 T::cast(node).expect("root node has wrong type")
259 }
260
261 pub fn syntax_node(&self) -> SyntaxNode {
263 SyntaxNode::new_root(self.green.clone())
264 }
265
266 pub fn tree_mut(&self) -> T
268 where
269 T: AstNode<Language = Lang>,
270 {
271 assert!(
272 self.errors.is_empty(),
273 "tried to get tree with errors: {:?}",
274 self.errors
275 );
276 let node = SyntaxNode::new_root_mut(self.green.clone());
277 T::cast(node).expect("root node has wrong type")
278 }
279}
280
281fn parse(text: &str) -> Parse<ChangeLog> {
282 struct Parser {
283 tokens: Vec<(SyntaxKind, String)>,
286 builder: GreenNodeBuilder<'static>,
288 errors: Vec<String>,
291 }
292
293 impl Parser {
294 fn error(&mut self, msg: String) {
295 self.builder.start_node(ERROR.into());
296 if self.current().is_some() {
297 self.bump();
298 }
299 self.errors.push(msg);
300 self.builder.finish_node();
301 }
302
303 fn parse_entry_header(&mut self) {
304 self.builder.start_node(ENTRY_HEADER.into());
305 self.expect(IDENTIFIER);
306
307 self.skip_ws();
308
309 if self.current() == Some(NEWLINE) {
310 self.bump();
311 self.builder.finish_node();
312 return;
313 }
314
315 self.expect(VERSION);
316
317 self.skip_ws();
318
319 self.builder.start_node(DISTRIBUTIONS.into());
320 loop {
321 match self.current() {
322 Some(IDENTIFIER) => self.bump(),
323 Some(NEWLINE) => {
324 self.bump();
325 self.builder.finish_node();
326 self.builder.finish_node();
327 return;
328 }
329 Some(SEMICOLON) => {
330 break;
331 }
332 _ => {
333 self.error("expected distribution or semicolon".to_string());
334 break;
335 }
336 }
337 self.skip_ws();
338 }
339 self.builder.finish_node();
340
341 self.skip_ws();
342
343 self.builder.start_node(METADATA.into());
344 if self.current() == Some(SEMICOLON) {
345 self.bump();
346 loop {
347 self.skip_ws();
348
349 if self.current() == Some(NEWLINE) {
350 break;
351 }
352
353 self.builder.start_node(METADATA_ENTRY.into());
354 if self.current() == Some(IDENTIFIER) {
355 self.builder.start_node(METADATA_KEY.into());
356 self.bump();
357 self.builder.finish_node();
358 } else {
359 self.error("expected metadata key".to_string());
360 self.builder.finish_node();
361 break;
362 }
363
364 if self.current() == Some(EQUALS) {
365 self.bump();
366 } else {
367 self.error("expected equals".to_string());
368 self.builder.finish_node();
369 break;
370 }
371
372 if self.current() == Some(IDENTIFIER) {
373 self.builder.start_node(METADATA_VALUE.into());
374 self.bump();
375 loop {
378 match (self.current(), self.next()) {
379 (Some(WHITESPACE), Some(IDENTIFIER)) => {
381 if self.tokens.len() >= 3 {
385 if let Some((kind, _)) =
386 self.tokens.get(self.tokens.len() - 3)
387 {
388 if *kind == EQUALS {
389 break; }
391 }
392 }
393 self.bump(); }
395 (Some(WHITESPACE), _) => self.bump(),
396 (Some(IDENTIFIER), _) => self.bump(),
397 _ => break,
398 }
399 }
400 self.builder.finish_node();
401 } else {
402 self.error("expected metadata value".to_string());
403 self.builder.finish_node();
404 break;
405 }
406 self.builder.finish_node();
407
408 self.skip_ws();
410 if self.current() == Some(ERROR) {
411 if let Some((_, text)) = self.tokens.last() {
413 if text == "," {
414 self.bump(); continue;
416 }
417 }
418 }
419 }
420 } else if self.current() == Some(NEWLINE) {
421 } else {
422 self.error("expected semicolon or newline".to_string());
423 }
424 self.builder.finish_node();
425
426 self.expect(NEWLINE);
427 self.builder.finish_node();
428 }
429
430 fn parse_entry(&mut self) {
431 self.builder.start_node(ENTRY.into());
432 self.parse_entry_header();
433 loop {
434 match self
435 .tokens
436 .last()
437 .map(|(kind, token)| (kind, token.as_str()))
438 {
439 None => {
440 break;
442 }
443 Some((NEWLINE, _)) => {
445 self.builder.start_node(EMPTY_LINE.into());
446 self.bump();
447 self.builder.finish_node();
448 }
449 Some((INDENT, " ")) => {
451 self.parse_entry_detail();
452 }
453 Some((INDENT, " -- ")) => {
455 self.parse_entry_footer();
456 break;
457 }
458 _ => break,
459 }
460 }
461
462 self.builder.finish_node();
463 }
464
465 pub fn parse_entry_detail(&mut self) {
466 self.builder.start_node(ENTRY_BODY.into());
467 self.expect(INDENT);
468
469 match self.current() {
470 Some(DETAIL) => {
471 self.bump();
472 }
473 Some(NEWLINE) => {}
474 _ => {
475 self.error("expected detail".to_string());
476 }
477 }
478
479 self.expect(NEWLINE);
480 self.builder.finish_node();
481 }
482
483 pub fn parse_entry_footer(&mut self) {
484 self.builder.start_node(ENTRY_FOOTER.into());
485
486 if self.current() != Some(INDENT) {
487 self.error("expected indent".to_string());
488 } else {
489 let dashes = &self.tokens.last().unwrap().1;
490 if dashes != " -- " {
491 self.error("expected --".to_string());
492 } else {
493 self.bump();
494 }
495 }
496
497 self.builder.start_node(MAINTAINER.into());
498 while self.current() == Some(TEXT)
499 || (self.current() == Some(WHITESPACE) && self.next() != Some(EMAIL))
500 {
501 self.bump();
502 }
503 self.builder.finish_node();
504
505 if self.current().is_some() && self.current() != Some(NEWLINE) {
506 self.expect(WHITESPACE);
507 }
508
509 if self.current().is_some() && self.current() != Some(NEWLINE) {
510 self.expect(EMAIL);
511 }
512
513 if self.tokens.last().map(|(k, t)| (*k, t.as_str())) == Some((WHITESPACE, " ")) {
514 self.bump();
515 } else if self.current() == Some(WHITESPACE) {
516 self.error("expected two spaces".to_string());
517 } else if self.current() == Some(NEWLINE) {
518 self.bump();
519 self.builder.finish_node();
520 return;
521 } else {
522 self.error(format!("expected whitespace, got {:?}", self.current()));
523 }
524
525 self.builder.start_node(TIMESTAMP.into());
526
527 loop {
528 if self.current() != Some(TEXT) && self.current() != Some(WHITESPACE) {
529 break;
530 }
531 self.bump();
532 }
533 self.builder.finish_node();
534
535 self.expect(NEWLINE);
536 self.builder.finish_node();
537 }
538
539 fn parse(mut self) -> Parse<ChangeLog> {
540 self.builder.start_node(ROOT.into());
541 loop {
542 match self.current() {
543 None => break,
544 Some(NEWLINE) => {
545 self.builder.start_node(EMPTY_LINE.into());
546 self.bump();
547 self.builder.finish_node();
548 }
549 Some(COMMENT) => {
550 self.bump();
551 }
552 Some(IDENTIFIER) => {
553 self.parse_entry();
554 }
555 t => {
556 self.error(format!("unexpected token {:?}", t));
557 break;
558 }
559 }
560 }
561 self.builder.finish_node();
563
564 Parse::new(self.builder.finish(), self.errors)
566 }
567 fn bump(&mut self) {
569 let (kind, text) = self.tokens.pop().unwrap();
570 self.builder.token(kind.into(), text.as_str());
571 }
572 fn current(&self) -> Option<SyntaxKind> {
574 self.tokens.last().map(|(kind, _)| *kind)
575 }
576
577 fn next(&self) -> Option<SyntaxKind> {
578 self.tokens
579 .get(self.tokens.len() - 2)
580 .map(|(kind, _)| *kind)
581 }
582
583 fn expect(&mut self, expected: SyntaxKind) {
584 if self.current() != Some(expected) {
585 self.error(format!("expected {:?}, got {:?}", expected, self.current()));
586 } else {
587 self.bump();
588 }
589 }
590 fn skip_ws(&mut self) {
591 while self.current() == Some(WHITESPACE) {
592 self.bump()
593 }
594 }
595 }
596
597 let mut tokens = lex(text);
598 tokens.reverse();
599 Parser {
600 tokens,
601 builder: GreenNodeBuilder::new(),
602 errors: Vec::new(),
603 }
604 .parse()
605}
606
607pub type SyntaxNode = rowan::SyntaxNode<Lang>;
614#[allow(unused)]
615pub type SyntaxToken = rowan::SyntaxToken<Lang>;
616#[allow(unused)]
617type SyntaxElement = rowan::NodeOrToken<SyntaxNode, SyntaxToken>;
618
619pub(crate) fn line_col_at_offset(node: &SyntaxNode, offset: rowan::TextSize) -> (usize, usize) {
622 let root = node.ancestors().last().unwrap_or_else(|| node.clone());
623 let mut line = 0;
624 let mut last_newline_offset = rowan::TextSize::from(0);
625
626 for element in root.preorder_with_tokens() {
627 if let rowan::WalkEvent::Enter(rowan::NodeOrToken::Token(token)) = element {
628 if token.text_range().start() >= offset {
629 break;
630 }
631
632 for (idx, _) in token.text().match_indices('\n') {
634 line += 1;
635 last_newline_offset =
636 token.text_range().start() + rowan::TextSize::from((idx + 1) as u32);
637 }
638 }
639 }
640
641 let column: usize = (offset - last_newline_offset).into();
642 (line, column)
643}
644
645macro_rules! ast_node {
646 ($ast:ident, $kind:ident) => {
647 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
648 #[repr(transparent)]
649 pub struct $ast(SyntaxNode);
651
652 impl AstNode for $ast {
653 type Language = Lang;
654
655 fn can_cast(kind: SyntaxKind) -> bool {
656 kind == $kind
657 }
658
659 fn cast(syntax: SyntaxNode) -> Option<Self> {
660 if Self::can_cast(syntax.kind()) {
661 Some(Self(syntax))
662 } else {
663 None
664 }
665 }
666
667 fn syntax(&self) -> &SyntaxNode {
668 &self.0
669 }
670 }
671
672 impl $ast {
673 #[allow(dead_code)]
674 fn replace_root(&mut self, new_root: SyntaxNode) {
675 self.0 = Self::cast(new_root).unwrap().0;
676 }
677
678 pub fn line(&self) -> usize {
680 line_col_at_offset(&self.0, self.0.text_range().start()).0
681 }
682
683 pub fn column(&self) -> usize {
685 line_col_at_offset(&self.0, self.0.text_range().start()).1
686 }
687
688 pub fn line_col(&self) -> (usize, usize) {
691 line_col_at_offset(&self.0, self.0.text_range().start())
692 }
693 }
694
695 impl std::fmt::Display for $ast {
696 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
697 f.write_str(self.0.text().to_string().as_str())
698 }
699 }
700 };
701}
702
703ast_node!(ChangeLog, ROOT);
704ast_node!(Entry, ENTRY);
705ast_node!(EntryHeader, ENTRY_HEADER);
706ast_node!(EntryBody, ENTRY_BODY);
707ast_node!(EntryFooter, ENTRY_FOOTER);
708ast_node!(Maintainer, MAINTAINER);
709ast_node!(Timestamp, TIMESTAMP);
710ast_node!(MetadataEntry, METADATA_ENTRY);
711ast_node!(MetadataKey, METADATA_KEY);
712ast_node!(MetadataValue, METADATA_VALUE);
713
714impl MetadataEntry {
715 pub fn key(&self) -> Option<String> {
717 self.0
718 .children()
719 .find_map(MetadataKey::cast)
720 .map(|k| k.to_string())
721 }
722
723 pub fn value(&self) -> Option<String> {
725 self.0
726 .children()
727 .find_map(MetadataValue::cast)
728 .map(|k| k.to_string())
729 }
730
731 pub fn set_value(&mut self, value: &str) {
733 let node = self
734 .0
735 .children_with_tokens()
736 .find(|it| it.kind() == METADATA_VALUE);
737 let mut builder = GreenNodeBuilder::new();
738 builder.start_node(METADATA_VALUE.into());
739 builder.token(IDENTIFIER.into(), value);
740 builder.finish_node();
741 let root = SyntaxNode::new_root_mut(builder.finish());
742
743 let range = if let Some(node) = node {
744 node.index()..node.index() + 1
745 } else {
746 let count = self.0.children().count();
747 count..count
748 };
749
750 self.0.splice_children(range, vec![root.into()]);
751 }
752}
753
754pub struct EntryBuilder {
756 root: SyntaxNode,
757 package: Option<String>,
758 version: Option<Version>,
759 distributions: Option<Vec<String>>,
760 urgency: Option<Urgency>,
761 maintainer: Option<(String, String)>,
762 timestamp_string: Option<String>,
763 change_lines: Vec<String>,
764}
765
766impl EntryBuilder {
767 #[must_use]
769 pub fn package(mut self, package: String) -> Self {
770 self.package = Some(package);
771 self
772 }
773
774 #[must_use]
776 pub fn version(mut self, version: Version) -> Self {
777 self.version = Some(version);
778 self
779 }
780
781 #[must_use]
783 pub fn distributions(mut self, distributions: Vec<String>) -> Self {
784 self.distributions = Some(distributions);
785 self
786 }
787
788 #[must_use]
789 pub fn distribution(mut self, distribution: String) -> Self {
790 self.distributions
791 .get_or_insert_with(Vec::new)
792 .push(distribution);
793 self
794 }
795
796 #[must_use]
797 pub fn urgency(mut self, urgency: Urgency) -> Self {
798 self.urgency = Some(urgency);
799 self
800 }
801
802 #[must_use]
803 pub fn maintainer(mut self, maintainer: (String, String)) -> Self {
804 self.maintainer = Some(maintainer);
805 self
806 }
807
808 #[must_use]
810 pub fn datetime(mut self, timestamp: impl IntoTimestamp) -> Self {
811 self.timestamp_string = Some(timestamp.into_timestamp());
812 self
813 }
814
815 #[must_use]
816 pub fn change_line(mut self, line: String) -> Self {
817 self.change_lines.push(line);
818 self
819 }
820
821 pub fn verify(&self) -> Result<(), String> {
822 if self.package.is_none() {
823 return Err("package is required".to_string());
824 }
825 if self.version.is_none() {
826 return Err("version is required".to_string());
827 }
828 match self.distributions {
829 None => {
830 return Err("at least one distribution is required".to_string());
831 }
832 Some(ref distributions) => {
833 if distributions.is_empty() {
834 return Err("at least one distribution is required".to_string());
835 }
836 }
837 }
838 if self.change_lines.is_empty() {
839 return Err("at least one change line is required".to_string());
840 }
841 Ok(())
842 }
843
844 fn metadata(&self) -> impl Iterator<Item = (String, String)> {
845 let mut ret = vec![];
846 if let Some(urgency) = self.urgency.as_ref() {
847 ret.push(("urgency".to_string(), urgency.to_string()));
848 }
849 ret.into_iter()
850 }
851
852 pub fn finish(self) -> Entry {
853 if self.root.children().find_map(Entry::cast).is_some() {
854 let mut builder = GreenNodeBuilder::new();
855 builder.start_node(EMPTY_LINE.into());
856 builder.token(NEWLINE.into(), "\n");
857 builder.finish_node();
858 let syntax = SyntaxNode::new_root_mut(builder.finish());
859 self.root.splice_children(0..0, vec![syntax.into()]);
860 }
861
862 let mut builder = GreenNodeBuilder::new();
863 builder.start_node(ENTRY.into());
864 builder.start_node(ENTRY_HEADER.into());
865 if let Some(package) = self.package.as_ref() {
866 builder.token(IDENTIFIER.into(), package.as_str());
867 }
868 if let Some(version) = self.version.as_ref() {
869 builder.token(WHITESPACE.into(), " ");
870 builder.token(VERSION.into(), format!("({})", version).as_str());
871 }
872 if let Some(distributions) = self.distributions.as_ref() {
873 builder.token(WHITESPACE.into(), " ");
874 builder.start_node(DISTRIBUTIONS.into());
875 let mut it = distributions.iter().peekable();
876 while it.peek().is_some() {
877 builder.token(IDENTIFIER.into(), it.next().unwrap());
878 if it.peek().is_some() {
879 builder.token(WHITESPACE.into(), " ");
880 }
881 }
882 builder.finish_node(); }
884 let mut metadata = self.metadata().peekable();
885 if metadata.peek().is_some() {
886 builder.token(SEMICOLON.into(), ";");
887 builder.token(WHITESPACE.into(), " ");
888 builder.start_node(METADATA.into());
889 for (key, value) in metadata {
890 builder.start_node(METADATA_ENTRY.into());
891 builder.start_node(METADATA_KEY.into());
892 builder.token(IDENTIFIER.into(), key.as_str());
893 builder.finish_node(); builder.token(EQUALS.into(), "=");
895 builder.start_node(METADATA_VALUE.into());
896 builder.token(METADATA_VALUE.into(), value.as_str());
897 builder.finish_node(); builder.finish_node(); }
900 builder.finish_node(); }
902 builder.token(NEWLINE.into(), "\n");
903 builder.finish_node(); builder.start_node(EMPTY_LINE.into());
906 builder.token(NEWLINE.into(), "\n");
907 builder.finish_node(); for line in self.change_lines {
910 builder.start_node(ENTRY_BODY.into());
911 builder.token(INDENT.into(), " ");
912 builder.token(DETAIL.into(), line.as_str());
913 builder.token(NEWLINE.into(), "\n");
914 builder.finish_node(); }
916
917 builder.start_node(EMPTY_LINE.into());
918 builder.token(NEWLINE.into(), "\n");
919 builder.finish_node(); builder.start_node(ENTRY_FOOTER.into());
922 builder.token(INDENT.into(), " -- ");
923 if let Some(maintainer) = self.maintainer.as_ref() {
924 builder.start_node(MAINTAINER.into());
925 let mut it = maintainer.0.split(' ').peekable();
926 while let Some(p) = it.next() {
927 builder.token(TEXT.into(), p);
928 if it.peek().is_some() {
929 builder.token(WHITESPACE.into(), " ");
930 }
931 }
932 builder.finish_node(); }
934
935 if let Some(maintainer) = self.maintainer.as_ref() {
936 builder.token(WHITESPACE.into(), " ");
937 builder.token(EMAIL.into(), format!("<{}>", maintainer.1).as_str());
938 }
939
940 if let Some(timestamp) = self.timestamp_string.as_ref() {
941 builder.token(WHITESPACE.into(), " ");
942
943 builder.start_node(TIMESTAMP.into());
944 let mut it = timestamp.split(' ').peekable();
945 while let Some(p) = it.next() {
946 builder.token(TEXT.into(), p);
947 if it.peek().is_some() {
948 builder.token(WHITESPACE.into(), " ");
949 }
950 }
951 builder.finish_node(); }
953 builder.token(NEWLINE.into(), "\n");
954 builder.finish_node(); builder.finish_node(); let syntax = SyntaxNode::new_root_mut(builder.finish());
958 self.root.splice_children(0..0, vec![syntax.clone().into()]);
959 Entry(syntax)
960 }
961}
962
963impl IntoIterator for ChangeLog {
964 type Item = Entry;
965 type IntoIter = std::vec::IntoIter<Entry>;
966
967 fn into_iter(self) -> Self::IntoIter {
968 self.iter().collect::<Vec<_>>().into_iter()
970 }
971}
972
973fn replay(builder: &mut GreenNodeBuilder, node: SyntaxNode) {
974 builder.start_node(node.kind().into());
975 for child in node.children_with_tokens() {
976 match child {
977 SyntaxElement::Node(n) => replay(builder, n),
978 SyntaxElement::Token(t) => {
979 builder.token(t.kind().into(), t.text());
980 }
981 }
982 }
983 builder.finish_node();
984}
985
986impl FromIterator<Entry> for ChangeLog {
987 fn from_iter<T: IntoIterator<Item = Entry>>(iter: T) -> Self {
988 let mut builder = GreenNodeBuilder::new();
989 builder.start_node(ROOT.into());
990 for entry in iter {
991 replay(&mut builder, entry.0.clone());
992 }
993 builder.finish_node();
994 ChangeLog(SyntaxNode::new_root_mut(builder.finish()))
995 }
996}
997
998impl ChangeLog {
999 pub fn new() -> ChangeLog {
1001 let mut builder = GreenNodeBuilder::new();
1002
1003 builder.start_node(ROOT.into());
1004 builder.finish_node();
1005
1006 let syntax = SyntaxNode::new_root_mut(builder.finish());
1007 ChangeLog(syntax)
1008 }
1009
1010 pub fn parse(text: &str) -> Parse<ChangeLog> {
1012 parse(text)
1013 }
1014
1015 pub fn iter(&self) -> impl Iterator<Item = Entry> + '_ {
1017 self.0.children().filter_map(Entry::cast)
1018 }
1019
1020 #[deprecated(since = "0.2.0", note = "use `iter` instead")]
1022 pub fn entries(&self) -> impl Iterator<Item = Entry> + '_ {
1023 self.iter()
1024 }
1025
1026 pub fn new_empty_entry(&mut self) -> EntryBuilder {
1028 EntryBuilder {
1029 root: self.0.clone(),
1030 package: None,
1031 version: None,
1032 distributions: None,
1033 urgency: None,
1034 maintainer: None,
1035 timestamp_string: None,
1036 change_lines: vec![],
1037 }
1038 }
1039
1040 fn first_valid_entry(&self) -> Option<Entry> {
1041 self.iter().find(|entry| {
1042 entry.package().is_some() && entry.header().is_some() && entry.footer().is_some()
1043 })
1044 }
1045
1046 pub fn new_entry(&mut self) -> EntryBuilder {
1048 let base_entry = self.first_valid_entry();
1049 let package = base_entry
1050 .as_ref()
1051 .and_then(|first_entry| first_entry.package());
1052 let mut version = base_entry
1053 .as_ref()
1054 .and_then(|first_entry| first_entry.version());
1055 if let Some(version) = version.as_mut() {
1056 version.increment_debian();
1057 }
1058 EntryBuilder {
1059 root: if self.0.is_mutable() {
1060 self.0.clone()
1061 } else {
1062 self.0.clone_for_update()
1063 },
1064 package,
1065 version,
1066 distributions: Some(vec![crate::UNRELEASED.into()]),
1067 urgency: Some(Urgency::default()),
1068 maintainer: crate::get_maintainer(),
1069 #[cfg(feature = "chrono")]
1070 timestamp_string: Some(chrono::Utc::now().into_timestamp()),
1071 #[cfg(not(feature = "chrono"))]
1072 timestamp_string: None,
1073 change_lines: vec![],
1074 }
1075 }
1076
1077 pub fn try_auto_add_change(
1094 &mut self,
1095 change: &[&str],
1096 author: (String, String),
1097 datetime: Option<impl IntoTimestamp>,
1098 urgency: Option<Urgency>,
1099 ) -> Result<Entry, crate::textwrap::Error> {
1100 match self.first_valid_entry() {
1101 Some(entry) if entry.is_unreleased() != Some(false) => {
1102 entry.try_add_change_for_author(change, author)?;
1104 Ok(entry)
1107 }
1108 Some(_entry) => {
1109 let mut builder = self.new_entry();
1111 builder = builder.maintainer(author);
1112 if let Some(datetime) = datetime {
1113 builder = builder.datetime(datetime);
1114 }
1115 if let Some(urgency) = urgency {
1116 builder = builder.urgency(urgency);
1117 }
1118 for change in change {
1119 builder = builder.change_line(change.to_string());
1120 }
1121 Ok(builder.finish())
1122 }
1123 None => {
1124 panic!("No existing entries found in changelog");
1125 }
1126 }
1127 }
1128
1129 #[deprecated(
1147 since = "0.2.10",
1148 note = "Use try_auto_add_change for proper error handling"
1149 )]
1150 pub fn auto_add_change(
1151 &mut self,
1152 change: &[&str],
1153 author: (String, String),
1154 datetime: Option<chrono::DateTime<chrono::FixedOffset>>,
1155 urgency: Option<Urgency>,
1156 ) -> Entry {
1157 self.try_auto_add_change(change, author, datetime, urgency)
1158 .unwrap()
1159 }
1160
1161 pub fn pop_first(&mut self) -> Option<Entry> {
1163 let mut it = self.iter();
1164 if let Some(entry) = it.next() {
1165 while let Some(sibling) = entry.0.next_sibling() {
1167 if sibling.kind() == EMPTY_LINE {
1168 sibling.detach();
1169 } else {
1170 break;
1171 }
1172 }
1173 entry.0.detach();
1174 Some(entry)
1175 } else {
1176 None
1177 }
1178 }
1179
1180 pub fn read_path(path: impl AsRef<std::path::Path>) -> Result<ChangeLog, Error> {
1182 let mut file = std::fs::File::open(path)?;
1183 Self::read(&mut file)
1184 }
1185
1186 pub fn read<R: std::io::Read>(mut r: R) -> Result<ChangeLog, Error> {
1188 let mut buf = String::new();
1189 r.read_to_string(&mut buf)?;
1190 Ok(buf.parse()?)
1191 }
1192
1193 pub fn read_relaxed<R: std::io::Read>(mut r: R) -> Result<ChangeLog, Error> {
1195 let mut buf = String::new();
1196 r.read_to_string(&mut buf)?;
1197
1198 let parsed = parse(&buf);
1199 let node = SyntaxNode::new_root_mut(parsed.green().clone());
1201 Ok(ChangeLog::cast(node).expect("root node has wrong type"))
1202 }
1203
1204 pub fn write<W: std::io::Write>(&self, mut w: W) -> Result<(), Error> {
1206 let buf = self.to_string();
1207 w.write_all(buf.as_bytes())?;
1208 Ok(())
1209 }
1210
1211 pub fn write_to_path(&self, p: &std::path::Path) -> Result<(), Error> {
1213 let f = std::fs::File::create(p)?;
1214 self.write(f)?;
1215 Ok(())
1216 }
1217
1218 pub fn iter_by_author(&self) -> impl Iterator<Item = (String, String, Vec<Entry>)> + '_ {
1223 crate::iter_entries_by_author(self)
1224 }
1225
1226 pub fn get_all_authors(&self) -> std::collections::HashSet<crate::Identity> {
1230 let mut authors = std::collections::HashSet::new();
1231
1232 for entry in self.iter() {
1234 if let Some(identity) = entry.get_maintainer_identity() {
1235 authors.insert(identity);
1236 }
1237 }
1238
1239 for entry in self.iter() {
1241 for author_name in entry.get_authors() {
1242 authors.insert(crate::Identity::new(author_name, "".to_string()));
1244 }
1245 }
1246
1247 authors
1248 }
1249}
1250
1251impl Default for ChangeLog {
1252 fn default() -> Self {
1253 Self::new()
1254 }
1255}
1256
1257impl FromStr for ChangeLog {
1258 type Err = ParseError;
1259
1260 fn from_str(s: &str) -> Result<Self, Self::Err> {
1261 ChangeLog::parse(s).to_mut_result()
1262 }
1263}
1264
1265impl FromStr for Entry {
1266 type Err = ParseError;
1267
1268 fn from_str(s: &str) -> Result<Self, Self::Err> {
1269 let cl: ChangeLog = s.parse()?;
1270 let mut entries = cl.iter();
1271 let entry = entries
1272 .next()
1273 .ok_or_else(|| ParseError(vec!["no entries found".to_string()]))?;
1274 if entries.next().is_some() {
1275 return Err(ParseError(vec!["multiple entries found".to_string()]));
1276 }
1277 Ok(entry)
1278 }
1279}
1280
1281impl EntryHeader {
1282 pub fn version(&self) -> Option<Version> {
1284 self.0.children_with_tokens().find_map(|it| {
1285 if let Some(token) = it.as_token() {
1286 if token.kind() == VERSION {
1287 let text = token.text()[1..token.text().len() - 1].to_string();
1288 return Some(text.parse().unwrap());
1289 }
1290 }
1291 None
1292 })
1293 }
1294
1295 pub fn package(&self) -> Option<String> {
1297 self.0.children_with_tokens().find_map(|it| {
1298 if let Some(token) = it.as_token() {
1299 if token.kind() == IDENTIFIER {
1300 return Some(token.text().to_string());
1301 }
1302 }
1303 None
1304 })
1305 }
1306
1307 pub fn distributions(&self) -> Option<Vec<String>> {
1309 let node = self.0.children().find(|it| it.kind() == DISTRIBUTIONS);
1310
1311 node.map(|node| {
1312 node.children_with_tokens()
1313 .filter_map(|it| {
1314 if let Some(token) = it.as_token() {
1315 if token.kind() == IDENTIFIER {
1316 return Some(token.text().to_string());
1317 }
1318 }
1319 None
1320 })
1321 .collect::<Vec<_>>()
1322 })
1323 }
1324
1325 pub fn set_distributions(&mut self, _distributions: Vec<String>) {
1327 let node = self
1328 .0
1329 .children_with_tokens()
1330 .find(|it| it.kind() == DISTRIBUTIONS);
1331 let mut builder = GreenNodeBuilder::new();
1332 builder.start_node(DISTRIBUTIONS.into());
1333 for (i, distribution) in _distributions.iter().enumerate() {
1334 if i > 0 {
1335 builder.token(WHITESPACE.into(), " ");
1336 }
1337 builder.token(IDENTIFIER.into(), distribution);
1338 }
1339 builder.finish_node();
1340
1341 let (range, green) = if let Some(node) = node {
1342 (
1343 node.index()..node.index() + 1,
1344 vec![builder.finish().into()],
1345 )
1346 } else if let Some(version) = self
1347 .0
1348 .children_with_tokens()
1349 .find(|it| it.kind() == VERSION)
1350 {
1351 (
1352 version.index()..version.index() + 1,
1353 vec![
1354 GreenToken::new(WHITESPACE.into(), " ").into(),
1355 builder.finish().into(),
1356 ],
1357 )
1358 } else if let Some(metadata) = self
1359 .0
1360 .children_with_tokens()
1361 .find(|it| it.kind() == METADATA)
1362 {
1363 (
1364 metadata.index() - 1..metadata.index() - 1,
1365 vec![
1366 GreenToken::new(WHITESPACE.into(), " ").into(),
1367 builder.finish().into(),
1368 ],
1369 )
1370 } else {
1371 (
1372 self.0.children().count()..self.0.children().count(),
1373 vec![
1374 GreenToken::new(WHITESPACE.into(), " ").into(),
1375 builder.finish().into(),
1376 ],
1377 )
1378 };
1379
1380 let new_root = SyntaxNode::new_root_mut(self.0.green().splice_children(range, green));
1381 self.replace_root(new_root);
1382 }
1383
1384 pub fn set_version(&mut self, version: &Version) {
1386 let node = self
1388 .0
1389 .children_with_tokens()
1390 .find(|it| it.kind() == VERSION);
1391 let (range, green) = if let Some(token) = node {
1392 (
1393 token.index()..token.index() + 1,
1394 vec![GreenToken::new(VERSION.into(), &format!("({})", version)).into()],
1395 )
1396 } else {
1397 let index = self
1398 .0
1399 .children_with_tokens()
1400 .position(|it| it.kind() == IDENTIFIER)
1401 .unwrap_or(0);
1402 (
1403 index + 1..index + 1,
1404 vec![
1405 GreenToken::new(WHITESPACE.into(), " ").into(),
1406 GreenToken::new(VERSION.into(), &format!("({})", version)).into(),
1407 ],
1408 )
1409 };
1410 let new_root = SyntaxNode::new_root_mut(self.0.green().splice_children(range, green));
1411
1412 self.replace_root(new_root);
1413 }
1414
1415 pub fn set_package(&mut self, package: String) {
1417 let node = self
1418 .0
1419 .children_with_tokens()
1420 .find(|it| it.kind() == IDENTIFIER);
1421
1422 let new_root = if let Some(token) = node {
1423 SyntaxNode::new_root_mut(self.0.green().splice_children(
1424 token.index()..token.index() + 1,
1425 vec![GreenToken::new(IDENTIFIER.into(), &package).into()],
1426 ))
1427 } else {
1428 SyntaxNode::new_root_mut(self.0.green().splice_children(
1429 0..0,
1430 vec![
1431 GreenToken::new(IDENTIFIER.into(), &package).into(),
1432 GreenToken::new(WHITESPACE.into(), " ").into(),
1433 ],
1434 ))
1435 };
1436
1437 self.replace_root(new_root);
1438 }
1439
1440 pub fn set_metadata(&mut self, key: &str, value: &str) {
1442 if let Some(mut node) = self
1444 .metadata_nodes()
1445 .find(|it| it.key().map(|k| k == key).unwrap_or(false))
1446 {
1447 node.set_value(value);
1448 } else if let Some(metadata) = self
1449 .0
1450 .children_with_tokens()
1451 .find(|it| it.kind() == METADATA)
1452 {
1453 let mut builder = GreenNodeBuilder::new();
1454 builder.start_node(METADATA_ENTRY.into());
1455 builder.start_node(METADATA_KEY.into());
1456 builder.token(IDENTIFIER.into(), key);
1457 builder.finish_node();
1458 builder.token(EQUALS.into(), "=");
1459 builder.start_node(METADATA_VALUE.into());
1460 builder.token(IDENTIFIER.into(), value);
1461 builder.finish_node();
1462 builder.finish_node();
1463
1464 let metadata = metadata.as_node().unwrap();
1465
1466 let count = metadata.children_with_tokens().count();
1467 self.0.splice_children(
1468 metadata.index()..metadata.index() + 1,
1469 vec![SyntaxNode::new_root_mut(metadata.green().splice_children(
1470 count..count,
1471 vec![
1472 GreenToken::new(WHITESPACE.into(), " ").into(),
1473 builder.finish().into(),
1474 ],
1475 ))
1476 .into()],
1477 );
1478 } else {
1479 let mut builder = GreenNodeBuilder::new();
1480 builder.start_node(METADATA.into());
1481 builder.token(SEMICOLON.into(), ";");
1482 builder.token(WHITESPACE.into(), " ");
1483 builder.start_node(METADATA_ENTRY.into());
1484 builder.start_node(METADATA_KEY.into());
1485 builder.token(IDENTIFIER.into(), key);
1486 builder.finish_node();
1487 builder.token(EQUALS.into(), "=");
1488 builder.start_node(METADATA_VALUE.into());
1489 builder.token(IDENTIFIER.into(), value);
1490 builder.finish_node();
1491 builder.finish_node();
1492
1493 let new_root = SyntaxNode::new_root_mut(builder.finish());
1494
1495 if let Some(distributions) = self
1497 .0
1498 .children_with_tokens()
1499 .find(|it| it.kind() == DISTRIBUTIONS)
1500 {
1501 self.0.splice_children(
1502 distributions.index() + 1..distributions.index() + 1,
1503 vec![new_root.into()],
1504 );
1505 } else if let Some(nl) = self
1506 .0
1507 .children_with_tokens()
1508 .find(|it| it.kind() == NEWLINE)
1509 {
1510 self.0
1512 .splice_children(nl.index()..nl.index(), vec![new_root.into()]);
1513 } else {
1514 let count = self.0.children_with_tokens().count();
1515 self.0.splice_children(count..count, vec![new_root.into()]);
1516 }
1517 }
1518 }
1519
1520 pub fn metadata_nodes(&self) -> impl Iterator<Item = MetadataEntry> + '_ {
1522 let node = self.0.children().find(|it| it.kind() == METADATA);
1523
1524 node.into_iter().flat_map(|node| {
1525 node.children_with_tokens()
1526 .filter_map(|it| MetadataEntry::cast(it.into_node()?))
1527 })
1528 }
1529
1530 pub fn metadata(&self) -> impl Iterator<Item = (String, String)> + '_ {
1532 self.metadata_nodes().filter_map(|entry| {
1533 if let (Some(key), Some(value)) = (entry.key(), entry.value()) {
1534 Some((key, value))
1535 } else {
1536 None
1537 }
1538 })
1539 }
1540
1541 pub fn urgency(&self) -> Option<Urgency> {
1543 for (key, value) in self.metadata() {
1544 if key.as_str() == "urgency" {
1545 return Some(value.parse().unwrap());
1546 }
1547 }
1548 None
1549 }
1550}
1551
1552impl EntryFooter {
1553 pub fn email(&self) -> Option<String> {
1555 self.0.children_with_tokens().find_map(|it| {
1556 if let Some(token) = it.as_token() {
1557 let text = token.text();
1558 if token.kind() == EMAIL {
1559 return Some(text[1..text.len() - 1].to_string());
1560 }
1561 }
1562 None
1563 })
1564 }
1565
1566 pub fn maintainer(&self) -> Option<String> {
1568 self.0
1569 .children()
1570 .find_map(Maintainer::cast)
1571 .map(|m| m.text())
1572 .filter(|s| !s.is_empty())
1573 }
1574
1575 pub fn set_maintainer(&mut self, maintainer: String) {
1577 let node = self
1578 .0
1579 .children_with_tokens()
1580 .find(|it| it.kind() == MAINTAINER);
1581 let new_root = if let Some(node) = node {
1582 SyntaxNode::new_root_mut(self.0.green().splice_children(
1583 node.index()..node.index() + 1,
1584 vec![GreenToken::new(MAINTAINER.into(), &maintainer).into()],
1585 ))
1586 } else if let Some(node) = self.0.children_with_tokens().find(|it| it.kind() == INDENT) {
1587 SyntaxNode::new_root_mut(self.0.green().splice_children(
1588 node.index() + 1..node.index() + 1,
1589 vec![GreenToken::new(MAINTAINER.into(), &maintainer).into()],
1590 ))
1591 } else {
1592 SyntaxNode::new_root_mut(self.0.green().splice_children(
1593 0..0,
1594 vec![
1595 GreenToken::new(INDENT.into(), " -- ").into(),
1596 GreenToken::new(MAINTAINER.into(), &maintainer).into(),
1597 ],
1598 ))
1599 };
1600
1601 self.replace_root(new_root);
1602 }
1603
1604 pub fn set_email(&mut self, _email: String) {
1606 let node = self.0.children_with_tokens().find(|it| it.kind() == EMAIL);
1607 let new_root = if let Some(node) = node {
1608 SyntaxNode::new_root_mut(self.0.green().splice_children(
1609 node.index()..node.index() + 1,
1610 vec![GreenToken::new(EMAIL.into(), &format!("<{}>", _email)).into()],
1611 ))
1612 } else if let Some(node) = self
1613 .0
1614 .children_with_tokens()
1615 .find(|it| it.kind() == MAINTAINER)
1616 {
1617 SyntaxNode::new_root_mut(self.0.green().splice_children(
1618 node.index() + 1..node.index() + 1,
1619 vec![GreenToken::new(EMAIL.into(), &format!("<{}>", _email)).into()],
1620 ))
1621 } else if let Some(node) = self.0.children_with_tokens().find(|it| it.kind() == INDENT) {
1622 SyntaxNode::new_root_mut(self.0.green().splice_children(
1623 node.index() + 1..node.index() + 1,
1624 vec![
1625 GreenToken::new(MAINTAINER.into(), "").into(),
1626 GreenToken::new(WHITESPACE.into(), " ").into(),
1627 GreenToken::new(EMAIL.into(), &format!("<{}>", _email)).into(),
1628 ],
1629 ))
1630 } else {
1631 SyntaxNode::new_root_mut(self.0.green().splice_children(
1632 0..0,
1633 vec![
1634 GreenToken::new(INDENT.into(), " -- ").into(),
1635 GreenToken::new(MAINTAINER.into(), "").into(),
1636 GreenToken::new(WHITESPACE.into(), " ").into(),
1637 GreenToken::new(EMAIL.into(), &format!("<{}>", _email)).into(),
1638 ],
1639 ))
1640 };
1641
1642 self.replace_root(new_root);
1643 }
1644
1645 pub fn timestamp(&self) -> Option<String> {
1647 self.0
1648 .children()
1649 .find_map(Timestamp::cast)
1650 .map(|m| m.text())
1651 }
1652
1653 pub fn set_timestamp(&mut self, timestamp: String) {
1655 let node = self
1656 .0
1657 .children_with_tokens()
1658 .find(|it| it.kind() == TIMESTAMP);
1659 let new_root = if let Some(node) = node {
1660 SyntaxNode::new_root_mut(self.0.green().splice_children(
1661 node.index()..node.index() + 1,
1662 vec![GreenToken::new(TIMESTAMP.into(), ×tamp).into()],
1663 ))
1664 } else if let Some(node) = self.0.children_with_tokens().find(|it| it.kind() == INDENT) {
1665 SyntaxNode::new_root_mut(self.0.green().splice_children(
1666 node.index() + 1..node.index() + 1,
1667 vec![GreenToken::new(TIMESTAMP.into(), ×tamp).into()],
1668 ))
1669 } else if let Some(node) = self.0.children_with_tokens().find(|it| it.kind() == EMAIL) {
1670 SyntaxNode::new_root_mut(self.0.green().splice_children(
1671 node.index() + 1..node.index() + 1,
1672 vec![GreenToken::new(TIMESTAMP.into(), ×tamp).into()],
1673 ))
1674 } else {
1675 SyntaxNode::new_root_mut(self.0.green().splice_children(
1676 0..0,
1677 vec![
1678 GreenToken::new(INDENT.into(), " -- ").into(),
1679 GreenToken::new(TIMESTAMP.into(), ×tamp).into(),
1680 ],
1681 ))
1682 };
1683
1684 self.replace_root(new_root);
1685 }
1686}
1687
1688impl EntryBody {
1689 fn text(&self) -> String {
1690 self.0
1691 .children_with_tokens()
1692 .filter_map(|it| {
1693 if let Some(token) = it.as_token() {
1694 if token.kind() == DETAIL {
1695 return Some(token.text().to_string());
1696 }
1697 }
1698 None
1699 })
1700 .collect::<Vec<_>>()
1701 .concat()
1702 }
1703}
1704
1705impl Timestamp {
1706 fn text(&self) -> String {
1707 self.0.text().to_string()
1708 }
1709}
1710
1711impl Maintainer {
1712 fn text(&self) -> String {
1713 self.0.text().to_string()
1714 }
1715}
1716
1717impl Entry {
1718 pub fn header(&self) -> Option<EntryHeader> {
1720 self.0.children().find_map(EntryHeader::cast)
1721 }
1722
1723 pub fn body(&self) -> Option<EntryBody> {
1725 self.0.children().find_map(EntryBody::cast)
1726 }
1727
1728 pub fn footer(&self) -> Option<EntryFooter> {
1730 self.0.children().find_map(EntryFooter::cast)
1731 }
1732
1733 pub fn package(&self) -> Option<String> {
1735 self.header().and_then(|h| h.package())
1736 }
1737
1738 pub fn set_package(&mut self, package: String) {
1740 if let Some(mut header) = self.header() {
1741 let header_index = header.0.index();
1742 header.set_package(package);
1743 self.0
1744 .splice_children(header_index..header_index + 1, vec![header.0.into()]);
1745 } else {
1746 self.create_header().set_package(package);
1747 }
1748 }
1749
1750 pub fn version(&self) -> Option<Version> {
1752 self.header().and_then(|h| h.version())
1753 }
1754
1755 pub fn set_version(&mut self, version: &Version) {
1757 if let Some(mut header) = self.header() {
1758 let header_index = header.0.index();
1759 header.set_version(version);
1760 self.0
1761 .splice_children(header_index..header_index + 1, vec![header.0.into()]);
1762 } else {
1763 self.create_header().set_version(version);
1764 }
1765 }
1766
1767 pub fn distributions(&self) -> Option<Vec<String>> {
1769 self.header().and_then(|h| h.distributions())
1770 }
1771
1772 pub fn set_distributions(&mut self, distributions: Vec<String>) {
1774 if let Some(mut header) = self.header() {
1775 let header_index = header.0.index();
1776 header.set_distributions(distributions);
1777 self.0
1778 .splice_children(header_index..header_index + 1, vec![header.0.into()]);
1779 } else {
1780 self.create_header().set_distributions(distributions);
1781 }
1782 }
1783
1784 pub fn email(&self) -> Option<String> {
1786 self.footer().and_then(|f| f.email())
1787 }
1788
1789 pub fn maintainer_node(&self) -> Option<Maintainer> {
1791 self.footer()
1792 .and_then(|f| f.0.children().find_map(Maintainer::cast))
1793 }
1794
1795 pub fn maintainer(&self) -> Option<String> {
1797 self.footer().and_then(|f| f.maintainer())
1798 }
1799
1800 pub fn set_maintainer(&mut self, maintainer: (String, String)) {
1802 if let Some(mut footer) = self.footer() {
1803 let footer_index = footer.0.index();
1804 footer.set_maintainer(maintainer.0);
1805 footer.set_email(maintainer.1);
1806 self.0
1807 .splice_children(footer_index..footer_index + 1, vec![footer.0.into()]);
1808 } else {
1809 let mut footer = self.create_footer();
1810 footer.set_maintainer(maintainer.0);
1811 footer.set_email(maintainer.1);
1812 }
1813 }
1814
1815 pub fn timestamp_node(&self) -> Option<Timestamp> {
1817 self.footer()
1818 .and_then(|f| f.0.children().find_map(Timestamp::cast))
1819 }
1820
1821 pub fn timestamp(&self) -> Option<String> {
1823 self.footer().and_then(|f| f.timestamp())
1824 }
1825
1826 pub fn set_timestamp(&mut self, timestamp: String) {
1828 if let Some(mut footer) = self.footer() {
1829 let footer_index = footer.0.index();
1830 footer.set_timestamp(timestamp);
1831 self.0
1832 .splice_children(footer_index..footer_index + 1, vec![footer.0.into()]);
1833 } else {
1834 self.create_footer().set_timestamp(timestamp);
1835 }
1836 }
1837
1838 #[cfg(feature = "chrono")]
1840 pub fn set_datetime(&mut self, datetime: DateTime<FixedOffset>) {
1841 self.set_timestamp(format!("{}", datetime.format("%a, %d %b %Y %H:%M:%S %z")));
1842 }
1843
1844 #[cfg(feature = "chrono")]
1846 pub fn datetime(&self) -> Option<DateTime<FixedOffset>> {
1847 self.timestamp().and_then(|ts| parse_time_string(&ts).ok())
1848 }
1849
1850 pub fn urgency(&self) -> Option<Urgency> {
1852 self.header().and_then(|h| h.urgency())
1853 }
1854
1855 fn create_header(&self) -> EntryHeader {
1856 let mut builder = GreenNodeBuilder::new();
1857 builder.start_node(ENTRY_HEADER.into());
1858 builder.token(NEWLINE.into(), "\n");
1859 builder.finish_node();
1860 let syntax = SyntaxNode::new_root_mut(builder.finish());
1861 self.0.splice_children(0..0, vec![syntax.into()]);
1862 EntryHeader(self.0.children().next().unwrap().clone_for_update())
1863 }
1864
1865 fn create_footer(&self) -> EntryFooter {
1866 let mut builder = GreenNodeBuilder::new();
1867 builder.start_node(ENTRY_FOOTER.into());
1868 builder.token(NEWLINE.into(), "\n");
1869 builder.finish_node();
1870 let syntax = SyntaxNode::new_root_mut(builder.finish());
1871 let count = self.0.children().count();
1872 self.0.splice_children(count..count, vec![syntax.into()]);
1873 EntryFooter(self.0.children().last().unwrap().clone_for_update())
1874 }
1875
1876 pub fn set_urgency(&mut self, urgency: Urgency) {
1878 self.set_metadata("urgency", urgency.to_string().as_str());
1879 }
1880
1881 pub fn set_metadata(&mut self, key: &str, value: &str) {
1883 if let Some(mut header) = self.header() {
1884 let header_index = header.0.index();
1885 header.set_metadata(key, value);
1886 self.0
1887 .splice_children(header_index..header_index + 1, vec![header.0.into()]);
1888 } else {
1889 self.create_header().set_metadata(key, value);
1890 }
1891 }
1892
1893 pub fn try_add_change_for_author(
1900 &self,
1901 change: &[&str],
1902 author: (String, String),
1903 ) -> Result<(), crate::textwrap::Error> {
1904 let changes_lines = self.change_lines().collect::<Vec<_>>();
1905 let by_author = crate::changes::changes_by_author(changes_lines.iter().map(|s| s.as_str()))
1906 .collect::<Vec<_>>();
1907
1908 if by_author.iter().all(|(a, _, _)| a.is_none()) {
1910 if let Some(maintainer_name) = self.maintainer() {
1911 if author.0 != maintainer_name {
1912 self.prepend_change_line(
1913 crate::changes::format_section_title(maintainer_name.as_str()).as_str(),
1914 );
1915 if !self.change_lines().last().unwrap().is_empty() {
1916 self.append_change_line("");
1917 }
1918 self.append_change_line(
1919 crate::changes::format_section_title(author.0.as_str()).as_str(),
1920 );
1921 }
1922 }
1923 } else if let Some(last_section) = by_author.last().as_ref() {
1924 if last_section.0 != Some(author.0.as_str()) {
1925 self.append_change_line("");
1926 self.append_change_line(
1927 crate::changes::format_section_title(author.0.as_str()).as_str(),
1928 );
1929 }
1930 }
1931
1932 if let Some(last) = self.change_lines().last() {
1933 if last.trim().is_empty() {
1934 self.pop_change_line();
1935 }
1936 }
1937
1938 for line in crate::textwrap::try_rewrap_changes(change.iter().copied())? {
1939 self.append_change_line(line.as_ref());
1940 }
1941 Ok(())
1942 }
1943
1944 #[deprecated(
1957 since = "0.2.10",
1958 note = "Use try_add_change_for_author for proper error handling"
1959 )]
1960 pub fn add_change_for_author(&self, change: &[&str], author: (String, String)) {
1961 self.try_add_change_for_author(change, author).unwrap()
1962 }
1963
1964 pub fn prepend_change_line(&self, line: &str) {
1966 let mut builder = GreenNodeBuilder::new();
1967 builder.start_node(ENTRY_BODY.into());
1968 if !line.is_empty() {
1969 builder.token(INDENT.into(), " ");
1970 builder.token(DETAIL.into(), line);
1971 }
1972 builder.token(NEWLINE.into(), "\n");
1973 builder.finish_node();
1974
1975 let mut it = self.0.children();
1977 let header = it.find(|n| n.kind() == ENTRY_HEADER);
1978
1979 let previous_line = it.find(|n| n.kind() == EMPTY_LINE).or(header);
1980
1981 let index = previous_line.map_or(0, |l| l.index() + 1);
1982
1983 let syntax = SyntaxNode::new_root_mut(builder.finish());
1984
1985 self.0.splice_children(index..index, vec![syntax.into()]);
1986 }
1987
1988 pub fn pop_change_line(&self) -> Option<String> {
1990 let last_child = self.0.children().filter(|n| n.kind() == ENTRY_BODY).last();
1992
1993 if let Some(last_child) = last_child {
1994 let text = last_child.children_with_tokens().find_map(|it| {
1995 if let Some(token) = it.as_token() {
1996 if token.kind() == DETAIL {
1997 return Some(token.text().to_string());
1998 }
1999 }
2000 None
2001 });
2002 self.0
2003 .splice_children(last_child.index()..last_child.index() + 1, vec![]);
2004 text
2005 } else {
2006 None
2007 }
2008 }
2009
2010 pub fn append_change_line(&self, line: &str) {
2012 let mut builder = GreenNodeBuilder::new();
2013 builder.start_node(ENTRY_BODY.into());
2014 if !line.is_empty() {
2015 builder.token(INDENT.into(), " ");
2016 builder.token(DETAIL.into(), line);
2017 }
2018 builder.token(NEWLINE.into(), "\n");
2019 builder.finish_node();
2020
2021 let last_child = self
2023 .0
2024 .children()
2025 .filter(|n| n.kind() == ENTRY_BODY)
2026 .last()
2027 .unwrap_or_else(|| {
2028 let children: Vec<_> = self.0.children().collect();
2031 if children.len() >= 2
2032 && children[0].kind() == ENTRY_HEADER
2033 && children[1].kind() == EMPTY_LINE
2034 {
2035 children[1].clone()
2036 } else {
2037 children[0].clone()
2038 }
2039 });
2040
2041 let syntax = SyntaxNode::new_root_mut(builder.finish()).into();
2042 self.0
2043 .splice_children(last_child.index() + 1..last_child.index() + 1, vec![syntax]);
2044 }
2045
2046 pub fn add_bullet(&self, text: &str) {
2073 let wrapped = crate::textwrap::textwrap(
2075 text,
2076 Some(crate::textwrap::DEFAULT_WIDTH),
2077 Some(crate::textwrap::INITIAL_INDENT),
2078 Some(" "),
2079 );
2080
2081 for line in wrapped {
2083 self.append_change_line(&line);
2084 }
2085 }
2086
2087 pub fn change_lines(&self) -> impl Iterator<Item = String> + '_ {
2089 let mut lines = self
2090 .0
2091 .children()
2092 .filter_map(|n| {
2093 if let Some(ref change) = EntryBody::cast(n.clone()) {
2094 Some(change.text())
2095 } else if n.kind() == EMPTY_LINE {
2096 Some("".to_string())
2097 } else {
2098 None
2099 }
2100 })
2101 .collect::<Vec<_>>();
2102
2103 while let Some(last) = lines.last() {
2104 if last.is_empty() {
2105 lines.pop();
2106 } else {
2107 break;
2108 }
2109 }
2110
2111 lines.into_iter().skip_while(|it| it.is_empty())
2112 }
2113
2114 pub fn ensure_first_line(&self, line: &str) {
2118 let first_line = self.change_lines().next().map(|it| it.trim().to_string());
2119
2120 if first_line != Some(line.to_string()) {
2121 self.prepend_change_line(line);
2122 }
2123 }
2124
2125 pub fn is_unreleased(&self) -> Option<bool> {
2127 let distro_is_unreleased = self.distributions().as_ref().map(|ds| {
2128 let ds = ds.iter().map(|d| d.as_str()).collect::<Vec<&str>>();
2129 crate::distributions_is_unreleased(ds.as_slice())
2130 });
2131
2132 let footer_is_unreleased = if self.maintainer().is_none() && self.email().is_none() {
2133 Some(true)
2134 } else {
2135 None
2136 };
2137
2138 match (distro_is_unreleased, footer_is_unreleased) {
2139 (Some(true), _) => Some(true),
2140 (_, Some(true)) => Some(true),
2141 (Some(false), _) => Some(false),
2142 (_, Some(false)) => Some(false),
2143 _ => None,
2144 }
2145 }
2146
2147 pub fn iter_changes_by_author(&self) -> Vec<(Option<String>, Vec<usize>, Vec<String>)> {
2152 let changes: Vec<String> = self.change_lines().map(|s| s.to_string()).collect();
2153 crate::changes::changes_by_author(changes.iter().map(|s| s.as_str()))
2154 .map(|(author, linenos, lines)| {
2155 let author_name = author.map(|s| s.to_string());
2156 let change_lines = lines.into_iter().map(|s| s.to_string()).collect();
2157 (author_name, linenos, change_lines)
2158 })
2159 .collect()
2160 }
2161
2162 pub fn get_authors(&self) -> std::collections::HashSet<String> {
2167 let changes: Vec<String> = self.change_lines().map(|s| s.to_string()).collect();
2168 let change_strs: Vec<&str> = changes.iter().map(|s| s.as_str()).collect();
2169 crate::changes::find_extra_authors(&change_strs)
2170 .into_iter()
2171 .map(|s| s.to_string())
2172 .collect()
2173 }
2174
2175 pub fn get_maintainer_identity(&self) -> Option<crate::Identity> {
2179 if let (Some(name), Some(email)) = (self.maintainer(), self.email()) {
2180 Some(crate::Identity::new(name, email))
2181 } else {
2182 None
2183 }
2184 }
2185
2186 pub fn try_add_changes_for_author(
2210 &self,
2211 author_name: &str,
2212 changes: Vec<&str>,
2213 ) -> Result<(), crate::textwrap::Error> {
2214 let mut change_lines: Vec<String> = self.change_lines().collect();
2215 let original_len = change_lines.len();
2216 let default_author = self.get_maintainer_identity().map(|id| (id.name, id.email));
2217
2218 crate::changes::try_add_change_for_author(
2219 &mut change_lines,
2220 author_name,
2221 changes,
2222 default_author,
2223 )?;
2224
2225 if change_lines.len() > original_len {
2230 let original_changes: Vec<_> = self.change_lines().collect();
2232
2233 let inserted_at_start = original_len > 0 && change_lines[0] != original_changes[0];
2235
2236 if inserted_at_start {
2237 while self.pop_change_line().is_some() {}
2240 for line in change_lines {
2241 self.append_change_line(&line);
2242 }
2243 } else {
2244 for line in change_lines.iter().skip(original_len) {
2246 self.append_change_line(line);
2247 }
2248 }
2249 }
2250 Ok(())
2251 }
2252
2253 #[deprecated(
2263 since = "0.2.10",
2264 note = "Use try_add_changes_for_author for proper error handling"
2265 )]
2266 pub fn add_changes_for_author(&self, author_name: &str, changes: Vec<&str>) {
2267 self.try_add_changes_for_author(author_name, changes)
2268 .unwrap()
2269 }
2270}
2271
2272#[cfg(feature = "chrono")]
2273const CHANGELOG_TIME_FORMAT: &str = "%a, %d %b %Y %H:%M:%S %z";
2274
2275#[cfg(feature = "chrono")]
2276fn parse_time_string(time_str: &str) -> Result<DateTime<FixedOffset>, chrono::ParseError> {
2277 if let Ok(dt) = DateTime::parse_from_str(time_str, CHANGELOG_TIME_FORMAT) {
2279 return Ok(dt);
2280 }
2281
2282 if let Some(after_comma) = time_str.split_once(", ") {
2286 DateTime::parse_from_str(after_comma.1, "%d %b %Y %H:%M:%S %z")
2287 } else {
2288 DateTime::parse_from_str(time_str, CHANGELOG_TIME_FORMAT)
2290 }
2291}
2292
2293#[cfg(test)]
2294mod tests {
2295 use super::*;
2296
2297 #[test]
2298 fn test_parse_simple() {
2299 const CHANGELOG: &str = r#"breezy (3.3.4-1) unstable; urgency=low
2300
2301 * New upstream release.
2302
2303 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
2304
2305breezy (3.3.3-2) unstable; urgency=medium
2306
2307 * Drop unnecessary dependency on python3-six. Closes: #1039011
2308 * Drop dependency on cython3-dbg. Closes: #1040544
2309
2310 -- Jelmer Vernooij <jelmer@debian.org> Sat, 24 Jun 2023 14:58:57 +0100
2311
2312# Oh, and here is a comment
2313"#;
2314 let parsed = parse(CHANGELOG);
2315 assert_eq!(parsed.errors(), &Vec::<String>::new());
2316 let node = parsed.syntax_node();
2317 assert_eq!(
2318 format!("{:#?}", node),
2319 r###"ROOT@0..405
2320 ENTRY@0..140
2321 ENTRY_HEADER@0..39
2322 IDENTIFIER@0..6 "breezy"
2323 WHITESPACE@6..7 " "
2324 VERSION@7..16 "(3.3.4-1)"
2325 WHITESPACE@16..17 " "
2326 DISTRIBUTIONS@17..25
2327 IDENTIFIER@17..25 "unstable"
2328 METADATA@25..38
2329 SEMICOLON@25..26 ";"
2330 WHITESPACE@26..27 " "
2331 METADATA_ENTRY@27..38
2332 METADATA_KEY@27..34
2333 IDENTIFIER@27..34 "urgency"
2334 EQUALS@34..35 "="
2335 METADATA_VALUE@35..38
2336 IDENTIFIER@35..38 "low"
2337 NEWLINE@38..39 "\n"
2338 EMPTY_LINE@39..40
2339 NEWLINE@39..40 "\n"
2340 ENTRY_BODY@40..66
2341 INDENT@40..42 " "
2342 DETAIL@42..65 "* New upstream release."
2343 NEWLINE@65..66 "\n"
2344 EMPTY_LINE@66..67
2345 NEWLINE@66..67 "\n"
2346 ENTRY_FOOTER@67..140
2347 INDENT@67..71 " -- "
2348 MAINTAINER@71..86
2349 TEXT@71..77 "Jelmer"
2350 WHITESPACE@77..78 " "
2351 TEXT@78..86 "Vernooij"
2352 WHITESPACE@86..87 " "
2353 EMAIL@87..106 "<jelmer@debian.org>"
2354 WHITESPACE@106..108 " "
2355 TIMESTAMP@108..139
2356 TEXT@108..112 "Mon,"
2357 WHITESPACE@112..113 " "
2358 TEXT@113..115 "04"
2359 WHITESPACE@115..116 " "
2360 TEXT@116..119 "Sep"
2361 WHITESPACE@119..120 " "
2362 TEXT@120..124 "2023"
2363 WHITESPACE@124..125 " "
2364 TEXT@125..133 "18:13:45"
2365 WHITESPACE@133..134 " "
2366 TEXT@134..139 "-0500"
2367 NEWLINE@139..140 "\n"
2368 EMPTY_LINE@140..141
2369 NEWLINE@140..141 "\n"
2370 ENTRY@141..376
2371 ENTRY_HEADER@141..183
2372 IDENTIFIER@141..147 "breezy"
2373 WHITESPACE@147..148 " "
2374 VERSION@148..157 "(3.3.3-2)"
2375 WHITESPACE@157..158 " "
2376 DISTRIBUTIONS@158..166
2377 IDENTIFIER@158..166 "unstable"
2378 METADATA@166..182
2379 SEMICOLON@166..167 ";"
2380 WHITESPACE@167..168 " "
2381 METADATA_ENTRY@168..182
2382 METADATA_KEY@168..175
2383 IDENTIFIER@168..175 "urgency"
2384 EQUALS@175..176 "="
2385 METADATA_VALUE@176..182
2386 IDENTIFIER@176..182 "medium"
2387 NEWLINE@182..183 "\n"
2388 EMPTY_LINE@183..184
2389 NEWLINE@183..184 "\n"
2390 ENTRY_BODY@184..249
2391 INDENT@184..186 " "
2392 DETAIL@186..248 "* Drop unnecessary de ..."
2393 NEWLINE@248..249 "\n"
2394 ENTRY_BODY@249..302
2395 INDENT@249..251 " "
2396 DETAIL@251..301 "* Drop dependency on ..."
2397 NEWLINE@301..302 "\n"
2398 EMPTY_LINE@302..303
2399 NEWLINE@302..303 "\n"
2400 ENTRY_FOOTER@303..376
2401 INDENT@303..307 " -- "
2402 MAINTAINER@307..322
2403 TEXT@307..313 "Jelmer"
2404 WHITESPACE@313..314 " "
2405 TEXT@314..322 "Vernooij"
2406 WHITESPACE@322..323 " "
2407 EMAIL@323..342 "<jelmer@debian.org>"
2408 WHITESPACE@342..344 " "
2409 TIMESTAMP@344..375
2410 TEXT@344..348 "Sat,"
2411 WHITESPACE@348..349 " "
2412 TEXT@349..351 "24"
2413 WHITESPACE@351..352 " "
2414 TEXT@352..355 "Jun"
2415 WHITESPACE@355..356 " "
2416 TEXT@356..360 "2023"
2417 WHITESPACE@360..361 " "
2418 TEXT@361..369 "14:58:57"
2419 WHITESPACE@369..370 " "
2420 TEXT@370..375 "+0100"
2421 NEWLINE@375..376 "\n"
2422 EMPTY_LINE@376..377
2423 NEWLINE@376..377 "\n"
2424 COMMENT@377..405 "# Oh, and here is a c ..."
2425"###
2426 );
2427
2428 let mut root = parsed.tree_mut();
2429 let entries: Vec<_> = root.iter().collect();
2430 assert_eq!(entries.len(), 2);
2431 let entry = &entries[0];
2432 assert_eq!(entry.package(), Some("breezy".into()));
2433 assert_eq!(entry.version(), Some("3.3.4-1".parse().unwrap()));
2434 assert_eq!(entry.distributions(), Some(vec!["unstable".into()]));
2435 assert_eq!(entry.urgency(), Some(Urgency::Low));
2436 assert_eq!(entry.maintainer(), Some("Jelmer Vernooij".into()));
2437 assert_eq!(entry.email(), Some("jelmer@debian.org".into()));
2438 assert_eq!(
2439 entry.timestamp(),
2440 Some("Mon, 04 Sep 2023 18:13:45 -0500".into())
2441 );
2442 #[cfg(feature = "chrono")]
2443 assert_eq!(
2444 entry.datetime(),
2445 Some("2023-09-04T18:13:45-05:00".parse().unwrap())
2446 );
2447 let changes_lines: Vec<_> = entry.change_lines().collect();
2448 assert_eq!(changes_lines, vec!["* New upstream release.".to_string()]);
2449
2450 assert_eq!(node.text(), CHANGELOG);
2451
2452 let first = root.pop_first().unwrap();
2453 assert_eq!(first.version(), Some("3.3.4-1".parse().unwrap()));
2454 assert_eq!(
2455 root.to_string(),
2456 r#"breezy (3.3.3-2) unstable; urgency=medium
2457
2458 * Drop unnecessary dependency on python3-six. Closes: #1039011
2459 * Drop dependency on cython3-dbg. Closes: #1040544
2460
2461 -- Jelmer Vernooij <jelmer@debian.org> Sat, 24 Jun 2023 14:58:57 +0100
2462
2463# Oh, and here is a comment
2464"#
2465 );
2466 }
2467
2468 #[test]
2469 fn test_from_io_read() {
2470 let changelog = r#"breezy (3.3.4-1) unstable; urgency=low
2471
2472 * New upstream release.
2473
2474 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
2475"#;
2476
2477 let input = changelog.as_bytes();
2478 let input = Box::new(std::io::Cursor::new(input)) as Box<dyn std::io::Read>;
2479 let parsed = ChangeLog::read(input).unwrap();
2480 assert_eq!(parsed.to_string(), changelog);
2481 }
2482
2483 #[test]
2484 #[cfg(feature = "chrono")]
2485 fn test_new_entry() {
2486 let mut cl = ChangeLog::new();
2487 cl.new_entry()
2488 .package("breezy".into())
2489 .version("3.3.4-1".parse().unwrap())
2490 .distributions(vec!["unstable".into()])
2491 .urgency(Urgency::Low)
2492 .maintainer(("Jelmer Vernooij".into(), "jelmer@debian.org".into()))
2493 .change_line("* A change.".into())
2494 .datetime("Mon, 04 Sep 2023 18:13:45 -0500")
2495 .finish();
2496 assert_eq!(
2497 r###"breezy (3.3.4-1) unstable; urgency=low
2498
2499 * A change.
2500
2501 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
2502"###,
2503 cl.to_string()
2504 );
2505
2506 assert!(!cl.iter().next().unwrap().is_unreleased().unwrap());
2507 }
2508
2509 #[test]
2510 #[cfg(feature = "chrono")]
2511 fn test_new_empty_default() {
2512 let mut cl = ChangeLog::new();
2513 cl.new_entry()
2514 .package("breezy".into())
2515 .version("3.3.4-1".parse().unwrap())
2516 .maintainer(("Jelmer Vernooij".into(), "jelmer@debian.org".into()))
2517 .change_line("* A change.".into())
2518 .datetime("Mon, 04 Sep 2023 18:13:45 -0500")
2519 .finish();
2520 assert_eq!(
2521 r###"breezy (3.3.4-1) UNRELEASED; urgency=low
2522
2523 * A change.
2524
2525 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
2526"###,
2527 cl.to_string()
2528 );
2529 }
2530
2531 #[test]
2532 fn test_new_empty_entry() {
2533 let mut cl = ChangeLog::new();
2534 cl.new_empty_entry()
2535 .change_line("* A change.".into())
2536 .finish();
2537 assert_eq!(
2538 r###"
2539
2540 * A change.
2541
2542 --
2543"###,
2544 cl.to_string()
2545 );
2546 assert_eq!(cl.iter().next().unwrap().is_unreleased(), Some(true));
2547 }
2548
2549 #[test]
2550 fn test_parse_invalid_line() {
2551 let text = r#"THIS IS NOT A PARSEABLE LINE
2552lintian-brush (0.35) UNRELEASED; urgency=medium
2553
2554 * Support updating templated debian/control files that use cdbs
2555 template.
2556
2557 -- Joe Example <joe@example.com> Fri, 04 Oct 2019 02:36:13 +0000
2558"#;
2559 let cl = ChangeLog::read_relaxed(text.as_bytes()).unwrap();
2560 let entry = cl.iter().nth(1).unwrap();
2561 assert_eq!(entry.package(), Some("lintian-brush".into()));
2562 assert_eq!(entry.version(), Some("0.35".parse().unwrap()));
2563 assert_eq!(entry.urgency(), Some(Urgency::Medium));
2564 assert_eq!(entry.maintainer(), Some("Joe Example".into()));
2565 assert_eq!(entry.email(), Some("joe@example.com".into()));
2566 assert_eq!(entry.distributions(), Some(vec!["UNRELEASED".into()]));
2567 #[cfg(feature = "chrono")]
2568 assert_eq!(
2569 entry.datetime(),
2570 Some("2019-10-04T02:36:13+00:00".parse().unwrap())
2571 );
2572 }
2573
2574 #[cfg(test)]
2575 mod entry_manipulate_tests {
2576 use super::*;
2577
2578 #[test]
2579 fn test_append_change_line() {
2580 let mut cl = ChangeLog::new();
2581
2582 let entry = cl
2583 .new_empty_entry()
2584 .change_line("* A change.".into())
2585 .finish();
2586
2587 entry.append_change_line("* Another change.");
2588
2589 assert_eq!(
2590 r###"
2591
2592 * A change.
2593 * Another change.
2594
2595 --
2596"###,
2597 cl.to_string()
2598 );
2599 }
2600
2601 #[test]
2602 fn test_prepend_change_line() {
2603 let mut cl = ChangeLog::new();
2604
2605 let entry = cl
2606 .new_empty_entry()
2607 .change_line("* A change.".into())
2608 .finish();
2609
2610 entry.prepend_change_line("* Another change.");
2611
2612 assert_eq!(
2613 r###"
2614
2615 * Another change.
2616 * A change.
2617
2618 --
2619"###,
2620 cl.to_string()
2621 );
2622
2623 assert_eq!(entry.maintainer(), None);
2624 assert_eq!(entry.email(), None);
2625 assert_eq!(entry.timestamp(), None);
2626 assert_eq!(entry.package(), None);
2627 assert_eq!(entry.version(), None);
2628 }
2629 }
2630
2631 #[cfg(test)]
2632 mod auto_add_change_tests {
2633 #[test]
2634 fn test_unreleased_existing() {
2635 let text = r#"lintian-brush (0.35) unstable; urgency=medium
2636
2637 * This line already existed.
2638
2639 [ Jane Example ]
2640 * And this one has an existing author.
2641
2642 --
2643"#;
2644 let mut cl = super::ChangeLog::read(text.as_bytes()).unwrap();
2645
2646 let entry = cl.iter().next().unwrap();
2647 assert_eq!(entry.package(), Some("lintian-brush".into()));
2648 assert_eq!(entry.is_unreleased(), Some(true));
2649
2650 let entry = cl
2651 .try_auto_add_change(
2652 &["* And this one is new."],
2653 ("Joe Example".to_string(), "joe@example.com".to_string()),
2654 None::<String>,
2655 None,
2656 )
2657 .unwrap();
2658
2659 assert_eq!(cl.iter().count(), 1);
2660
2661 assert_eq!(entry.package(), Some("lintian-brush".into()));
2662 assert_eq!(entry.is_unreleased(), Some(true));
2663 assert_eq!(
2664 entry.change_lines().collect::<Vec<_>>(),
2665 &[
2666 "* This line already existed.",
2667 "",
2668 "[ Jane Example ]",
2669 "* And this one has an existing author.",
2670 "",
2671 "[ Joe Example ]",
2672 "* And this one is new.",
2673 ]
2674 );
2675 }
2676 }
2677
2678 #[test]
2679 fn test_ensure_first_line() {
2680 let text = r#"lintian-brush (0.35) unstable; urgency=medium
2681
2682 * This line already existed.
2683
2684 [ Jane Example ]
2685 * And this one has an existing author.
2686
2687 --
2688"#;
2689 let cl = ChangeLog::read(text.as_bytes()).unwrap();
2690
2691 let entry = cl.iter().next().unwrap();
2692 assert_eq!(entry.package(), Some("lintian-brush".into()));
2693
2694 entry.ensure_first_line("* QA upload.");
2695 entry.ensure_first_line("* QA upload.");
2696
2697 assert_eq!(
2698 r#"lintian-brush (0.35) unstable; urgency=medium
2699
2700 * QA upload.
2701 * This line already existed.
2702
2703 [ Jane Example ]
2704 * And this one has an existing author.
2705
2706 --
2707"#,
2708 cl.to_string()
2709 );
2710 }
2711
2712 #[test]
2713 fn test_set_version() {
2714 let mut entry: Entry = r#"breezy (3.3.4-1) unstable; urgency=low
2715
2716 * New upstream release.
2717
2718 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
2719"#
2720 .parse()
2721 .unwrap();
2722
2723 entry.set_version(&"3.3.5-1".parse().unwrap());
2724
2725 assert_eq!(
2726 r#"breezy (3.3.5-1) unstable; urgency=low
2727
2728 * New upstream release.
2729
2730 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
2731"#,
2732 entry.to_string()
2733 );
2734 }
2735
2736 #[test]
2737 fn test_set_package() {
2738 let mut entry: Entry = r#"breezy (3.3.4-1) unstable; urgency=low
2739
2740 * New upstream release.
2741
2742 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
2743"#
2744 .parse()
2745 .unwrap();
2746
2747 entry.set_package("bzr".into());
2748
2749 assert_eq!(
2750 r#"bzr (3.3.4-1) unstable; urgency=low
2751
2752 * New upstream release.
2753
2754 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
2755"#,
2756 entry.to_string()
2757 );
2758 }
2759
2760 #[test]
2761 fn test_set_distributions() {
2762 let mut entry: Entry = r#"breezy (3.3.4-1) unstable; urgency=low
2763
2764 * New upstream release.
2765
2766 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
2767"#
2768 .parse()
2769 .unwrap();
2770
2771 entry.set_distributions(vec!["unstable".into(), "experimental".into()]);
2772
2773 assert_eq!(
2774 r#"breezy (3.3.4-1) unstable experimental; urgency=low
2775
2776 * New upstream release.
2777
2778 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
2779"#,
2780 entry.to_string()
2781 );
2782 }
2783
2784 #[test]
2785 fn test_set_distributions_no_existing() {
2786 let mut entry: Entry = r#"breezy (3.3.4-1); urgency=low
2787
2788 * New upstream release.
2789
2790 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
2791"#
2792 .parse()
2793 .unwrap();
2794
2795 entry.set_distributions(vec!["unstable".into()]);
2796
2797 assert!(entry.to_string().contains("unstable"));
2798 }
2799
2800 #[test]
2801 fn test_set_maintainer() {
2802 let mut entry: Entry = r#"breezy (3.3.4-1) unstable; urgency=low
2803
2804 * New upstream release.
2805
2806 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
2807"#
2808 .parse()
2809 .unwrap();
2810
2811 entry.set_maintainer(("Joe Example".into(), "joe@example.com".into()));
2812
2813 assert_eq!(
2814 r#"breezy (3.3.4-1) unstable; urgency=low
2815
2816 * New upstream release.
2817
2818 -- Joe Example <joe@example.com> Mon, 04 Sep 2023 18:13:45 -0500
2819"#,
2820 entry.to_string()
2821 );
2822 }
2823
2824 #[test]
2825 fn test_set_timestamp() {
2826 let mut entry: Entry = r#"breezy (3.3.4-1) unstable; urgency=low
2827
2828 * New upstream release.
2829
2830 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
2831"#
2832 .parse()
2833 .unwrap();
2834
2835 entry.set_timestamp("Mon, 04 Sep 2023 18:13:46 -0500".into());
2836
2837 assert_eq!(
2838 r#"breezy (3.3.4-1) unstable; urgency=low
2839
2840 * New upstream release.
2841
2842 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:46 -0500
2843"#,
2844 entry.to_string()
2845 );
2846 }
2847
2848 #[test]
2849 #[cfg(feature = "chrono")]
2850 fn test_set_datetime() {
2851 let mut entry: Entry = r#"breezy (3.3.4-1) unstable; urgency=low
2852
2853 * New upstream release.
2854
2855 -- Jelmer Vernooij <joe@example.com> Mon, 04 Sep 2023 18:13:45 -0500
2856"#
2857 .parse()
2858 .unwrap();
2859
2860 entry.set_datetime("2023-09-04T18:13:46-05:00".parse().unwrap());
2861
2862 assert_eq!(
2863 r#"breezy (3.3.4-1) unstable; urgency=low
2864
2865 * New upstream release.
2866
2867 -- Jelmer Vernooij <joe@example.com> Mon, 04 Sep 2023 18:13:46 -0500
2868"#,
2869 entry.to_string()
2870 );
2871 }
2872
2873 #[test]
2874 fn test_set_urgency() {
2875 let mut entry: Entry = r#"breezy (3.3.4-1) unstable; urgency=low
2876
2877 * New upstream release.
2878
2879 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
2880"#
2881 .parse()
2882 .unwrap();
2883
2884 entry.set_urgency(Urgency::Medium);
2885
2886 assert_eq!(
2887 r#"breezy (3.3.4-1) unstable; urgency=medium
2888
2889 * New upstream release.
2890
2891 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
2892"#,
2893 entry.to_string()
2894 );
2895 }
2896
2897 #[test]
2898 fn test_set_metadata() {
2899 let mut entry: Entry = r#"breezy (3.3.4-1) unstable; urgency=low
2900
2901 * New upstream release.
2902
2903 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
2904"#
2905 .parse()
2906 .unwrap();
2907
2908 entry.set_metadata("foo", "bar");
2909
2910 assert_eq!(
2911 r#"breezy (3.3.4-1) unstable; urgency=low foo=bar
2912
2913 * New upstream release.
2914
2915 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
2916"#,
2917 entry.to_string()
2918 );
2919 }
2920
2921 #[test]
2922 fn test_set_metadata_replace_existing() {
2923 let mut entry: Entry = r#"breezy (3.3.4-1) unstable; urgency=low foo=old
2924
2925 * New upstream release.
2926
2927 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
2928"#
2929 .parse()
2930 .unwrap();
2931
2932 entry.set_metadata("foo", "new");
2933
2934 assert_eq!(
2935 r#"breezy (3.3.4-1) unstable; urgency=low foo=new
2936
2937 * New upstream release.
2938
2939 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
2940"#,
2941 entry.to_string()
2942 );
2943 }
2944
2945 #[test]
2946 fn test_set_metadata_after_distributions() {
2947 let mut entry: Entry = r#"breezy (3.3.4-1) unstable experimental; urgency=low
2948
2949 * New upstream release.
2950
2951 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
2952"#
2953 .parse()
2954 .unwrap();
2955
2956 entry.set_metadata("foo", "bar");
2957
2958 assert_eq!(
2959 r#"breezy (3.3.4-1) unstable experimental; urgency=low foo=bar
2960
2961 * New upstream release.
2962
2963 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
2964"#,
2965 entry.to_string()
2966 );
2967 }
2968
2969 #[test]
2970 fn test_add_change_for_author() {
2971 let entry: Entry = r#"breezy (3.3.4-1) unstable; urgency=low
2972
2973 * New upstream release.
2974
2975 [ Jelmer Vernooij ]
2976 * A change by the maintainer.
2977
2978 -- Joe Example <joe@example.com> Mon, 04 Sep 2023 18:13:45 -0500
2979"#
2980 .parse()
2981 .unwrap();
2982
2983 entry
2984 .try_add_change_for_author(
2985 &["A change by the maintainer."],
2986 ("Jelmer Vernooij".into(), "jelmer@debian.org".into()),
2987 )
2988 .unwrap();
2989 }
2990
2991 #[test]
2992 fn test_changelog_from_entry_iter() {
2993 let text = r#"breezy (3.3.4-1) unstable; urgency=low
2994
2995 * New upstream release.
2996
2997 -- Jelmer Vernooij <jelmer@jelmer.uk> Mon, 04 Sep 2023 18:13:45 -0500
2998"#;
2999
3000 let entry: Entry = text.parse().unwrap();
3001
3002 let cl = std::iter::once(entry).collect::<ChangeLog>();
3003
3004 assert_eq!(cl.to_string(), text);
3005 }
3006
3007 #[test]
3008 fn test_pop_change_line() {
3009 let entry: Entry = r#"breezy (3.3.4-1) unstable; urgency=low
3010
3011 * New upstream release.
3012 * Fixed bug #123.
3013 * Added new feature.
3014
3015 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
3016"#
3017 .parse()
3018 .unwrap();
3019
3020 assert_eq!(
3022 entry.pop_change_line(),
3023 Some("* Added new feature.".to_string())
3024 );
3025 assert_eq!(
3026 entry.pop_change_line(),
3027 Some("* Fixed bug #123.".to_string())
3028 );
3029 assert_eq!(
3030 entry.pop_change_line(),
3031 Some("* New upstream release.".to_string())
3032 );
3033
3034 assert_eq!(entry.pop_change_line(), None);
3036 }
3037
3038 #[test]
3039 fn test_pop_change_line_empty_entry() {
3040 let entry: Entry = r#"breezy (3.3.4-1) unstable; urgency=low
3041
3042 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
3043"#
3044 .parse()
3045 .unwrap();
3046
3047 assert_eq!(entry.pop_change_line(), None);
3048 }
3049
3050 #[test]
3051 fn test_pop_change_line_empty_string() {
3052 let entry: Entry = r#"breezy (3.3.4-1) unstable; urgency=low
3053
3054 * Something
3055
3056 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
3057"#
3058 .parse()
3059 .unwrap();
3060
3061 entry.pop_change_line();
3062 entry.append_change_line("");
3063 assert_eq!(entry.pop_change_line(), None);
3065 }
3066
3067 #[test]
3068 fn test_append_change_line() {
3069 let entry: Entry = r#"breezy (3.3.4-1) unstable; urgency=low
3070
3071 * New upstream release.
3072
3073 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
3074"#
3075 .parse()
3076 .unwrap();
3077
3078 entry.append_change_line("* Fixed bug #456.");
3079
3080 assert_eq!(
3081 entry.to_string(),
3082 r#"breezy (3.3.4-1) unstable; urgency=low
3083
3084 * New upstream release.
3085 * Fixed bug #456.
3086
3087 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
3088"#
3089 );
3090 }
3091
3092 #[test]
3093 fn test_append_change_line_empty() {
3094 let entry: Entry = r#"breezy (3.3.4-1) unstable; urgency=low
3095
3096 * New upstream release.
3097
3098 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
3099"#
3100 .parse()
3101 .unwrap();
3102
3103 entry.append_change_line("");
3104
3105 let lines: Vec<String> = entry.change_lines().collect();
3106 assert_eq!(lines.len(), 1);
3108 assert_eq!(lines[0], "* New upstream release.".to_string());
3109 }
3110
3111 #[test]
3112 fn test_changelog_write_to_path() {
3113 use tempfile::NamedTempFile;
3114
3115 let changelog: ChangeLog = r#"breezy (3.3.4-1) unstable; urgency=low
3116
3117 * New upstream release.
3118
3119 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
3120"#
3121 .parse()
3122 .unwrap();
3123
3124 let temp_file = NamedTempFile::new().unwrap();
3125 let path = temp_file.path().to_path_buf();
3126
3127 changelog.write_to_path(&path).unwrap();
3128
3129 let contents = std::fs::read_to_string(&path).unwrap();
3130 assert_eq!(contents, changelog.to_string());
3131 }
3132
3133 #[test]
3134 fn test_changelog_into_iter() {
3135 let changelog: ChangeLog = r#"breezy (3.3.4-1) unstable; urgency=low
3136
3137 * New upstream release.
3138
3139 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
3140
3141breezy (3.3.3-1) unstable; urgency=low
3142
3143 * Previous release.
3144
3145 -- Jelmer Vernooij <jelmer@debian.org> Mon, 03 Sep 2023 18:13:45 -0500
3146"#
3147 .parse()
3148 .unwrap();
3149
3150 let entries: Vec<Entry> = changelog.into_iter().collect();
3151 assert_eq!(entries.len(), 2);
3152 }
3153
3154 #[test]
3155 fn test_set_version_no_existing() {
3156 let mut entry: Entry = r#"breezy (3.3.4-1) unstable; urgency=low
3157
3158 * New upstream release.
3159
3160 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
3161"#
3162 .parse()
3163 .unwrap();
3164
3165 entry.set_version(&"1.0.0".parse().unwrap());
3166
3167 assert!(entry.to_string().contains("(1.0.0)"));
3168 }
3169
3170 #[test]
3171 fn test_entry_footer_set_email_edge_cases() {
3172 let entry: Entry = r#"breezy (3.3.4-1) unstable; urgency=low
3173
3174 * New upstream release.
3175
3176 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
3177"#
3178 .parse()
3179 .unwrap();
3180
3181 assert_eq!(entry.email(), Some("jelmer@debian.org".to_string()));
3183 }
3184
3185 #[test]
3186 fn test_entry_footer_set_maintainer_edge_cases() {
3187 let mut entry: Entry = r#"breezy (3.3.4-1) unstable; urgency=low
3188
3189 * New upstream release.
3190
3191 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
3192"#
3193 .parse()
3194 .unwrap();
3195
3196 entry.set_maintainer(("New Maintainer".into(), "new@example.com".into()));
3198
3199 assert!(entry
3200 .to_string()
3201 .contains("New Maintainer <new@example.com>"));
3202 }
3203
3204 #[test]
3205 fn test_entry_footer_set_timestamp_edge_cases() {
3206 let mut entry: Entry = r#"breezy (3.3.4-1) unstable; urgency=low
3207
3208 * New upstream release.
3209
3210 -- Jelmer Vernooij <jelmer@debian.org>
3211"#
3212 .parse()
3213 .unwrap();
3214
3215 entry.set_timestamp("Mon, 04 Sep 2023 18:13:45 -0500".into());
3217
3218 assert!(entry
3219 .to_string()
3220 .contains("Mon, 04 Sep 2023 18:13:45 -0500"));
3221 }
3222
3223 #[test]
3224 fn test_parse_multiple_distributions_frozen_unstable() {
3225 const CHANGELOG: &str = r#"at (3.1.8-10) frozen unstable; urgency=high
3228
3229 * Suidunregister /usr/bin (closes: Bug#59421).
3230
3231 -- Siggy Brentrup <bsb@winnegan.de> Mon, 3 Apr 2000 13:56:47 +0200
3232"#;
3233
3234 let parsed = parse(CHANGELOG);
3235 assert_eq!(parsed.errors(), &Vec::<String>::new());
3236
3237 let root = parsed.tree();
3238 let entries: Vec<_> = root.iter().collect();
3239 assert_eq!(entries.len(), 1);
3240
3241 let entry = &entries[0];
3242 assert_eq!(entry.package(), Some("at".into()));
3243 assert_eq!(entry.version(), Some("3.1.8-10".parse().unwrap()));
3244 assert_eq!(
3245 entry.distributions(),
3246 Some(vec!["frozen".into(), "unstable".into()])
3247 );
3248 }
3249
3250 #[test]
3251 fn test_parse_old_metadata_format_with_comma() {
3252 const CHANGELOG: &str = r#"at (3.1.8-9) frozen unstable; urgency=low, closes=53715 56047 56607 55560 55514
3255
3256 * Added SIGCHLD handler to release zombies (closes 53715 56047 56607)
3257
3258 -- Siggy Brentrup <bsb@winnegan.de> Sun, 30 Jan 2000 22:00:46 +0100
3259"#;
3260
3261 let parsed = parse(CHANGELOG);
3262
3263 if !parsed.errors().is_empty() {
3265 eprintln!("Parse errors: {:?}", parsed.errors());
3266 }
3267 assert_eq!(parsed.errors(), &Vec::<String>::new());
3268
3269 let root = parsed.tree();
3270 let entries: Vec<_> = root.iter().collect();
3271 assert_eq!(entries.len(), 1);
3272
3273 let entry = &entries[0];
3274 assert_eq!(entry.package(), Some("at".into()));
3275 assert_eq!(entry.version(), Some("3.1.8-9".parse().unwrap()));
3276 assert_eq!(
3277 entry.distributions(),
3278 Some(vec!["frozen".into(), "unstable".into()])
3279 );
3280 assert_eq!(entry.urgency(), Some(Urgency::Low));
3281
3282 let header = entry.header().unwrap();
3284 let metadata: Vec<(String, String)> = header.metadata().collect();
3285
3286 assert_eq!(metadata.len(), 2);
3288 assert!(metadata.iter().any(|(k, v)| k == "urgency" && v == "low"));
3289
3290 let closes_value = metadata
3292 .iter()
3293 .find(|(k, _)| k == "closes")
3294 .map(|(_, v)| v)
3295 .expect("closes metadata should exist");
3296
3297 assert_eq!(closes_value, "53715 56047 56607 55560 55514");
3298 }
3299
3300 #[test]
3301 fn test_entry_iter_changes_by_author() {
3302 let entry: Entry = r#"breezy (3.3.4-1) unstable; urgency=low
3303
3304 [ Author 1 ]
3305 * Change by Author 1
3306
3307 [ Author 2 ]
3308 * Change by Author 2
3309 * Another change by Author 2
3310
3311 * Unattributed change
3312
3313 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
3314"#
3315 .parse()
3316 .unwrap();
3317
3318 let changes = entry.iter_changes_by_author();
3319
3320 assert_eq!(changes.len(), 3);
3321
3322 assert_eq!(changes[0].0, Some("Author 1".to_string()));
3323 assert_eq!(changes[0].2, vec!["* Change by Author 1".to_string()]);
3324
3325 assert_eq!(changes[1].0, Some("Author 2".to_string()));
3326 assert_eq!(
3327 changes[1].2,
3328 vec![
3329 "* Change by Author 2".to_string(),
3330 "* Another change by Author 2".to_string()
3331 ]
3332 );
3333
3334 assert_eq!(changes[2].0, None);
3335 assert_eq!(changes[2].2, vec!["* Unattributed change".to_string()]);
3336 }
3337
3338 #[test]
3339 fn test_entry_get_authors() {
3340 let entry: Entry = r#"breezy (3.3.4-1) unstable; urgency=low
3341
3342 [ Author 1 ]
3343 * Change by Author 1
3344
3345 [ Author 2 ]
3346 * Change by Author 2
3347
3348 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
3349"#
3350 .parse()
3351 .unwrap();
3352
3353 let authors = entry.get_authors();
3354
3355 assert_eq!(authors.len(), 2);
3356 assert!(authors.contains("Author 1"));
3357 assert!(authors.contains("Author 2"));
3358 assert!(!authors.contains("Jelmer Vernooij"));
3360 }
3361
3362 #[test]
3363 fn test_entry_get_maintainer_identity() {
3364 let entry: Entry = r#"breezy (3.3.4-1) unstable; urgency=low
3365
3366 * New upstream release.
3367
3368 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
3369"#
3370 .parse()
3371 .unwrap();
3372
3373 let identity = entry.get_maintainer_identity().unwrap();
3374
3375 assert_eq!(identity.name, "Jelmer Vernooij");
3376 assert_eq!(identity.email, "jelmer@debian.org");
3377 }
3378
3379 #[test]
3380 fn test_entry_get_maintainer_identity_missing() {
3381 let entry: Entry = r#"breezy (3.3.4-1) unstable; urgency=low
3382
3383 * New upstream release.
3384
3385"#
3386 .parse()
3387 .unwrap();
3388
3389 let identity = entry.get_maintainer_identity();
3390
3391 assert!(identity.is_none());
3392 }
3393
3394 #[test]
3395 fn test_changelog_iter_by_author() {
3396 let changelog: ChangeLog = r#"breezy (3.3.4-1) unstable; urgency=low
3397
3398 * New upstream release.
3399
3400 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
3401
3402breezy (3.3.3-1) unstable; urgency=low
3403
3404 * Bug fix release.
3405
3406 -- Jane Doe <jane@example.com> Sun, 03 Sep 2023 17:12:30 -0500
3407
3408breezy (3.3.2-1) unstable; urgency=low
3409
3410 * Another release.
3411
3412 -- Jelmer Vernooij <jelmer@debian.org> Sat, 02 Sep 2023 16:11:15 -0500
3413"#
3414 .parse()
3415 .unwrap();
3416
3417 let authors: Vec<(String, String, Vec<Entry>)> = changelog.iter_by_author().collect();
3418
3419 assert_eq!(authors.len(), 2);
3420 assert_eq!(authors[0].0, "Jane Doe");
3421 assert_eq!(authors[0].1, "jane@example.com");
3422 assert_eq!(authors[0].2.len(), 1);
3423 assert_eq!(authors[1].0, "Jelmer Vernooij");
3424 assert_eq!(authors[1].1, "jelmer@debian.org");
3425 assert_eq!(authors[1].2.len(), 2);
3426 }
3427
3428 #[test]
3429 fn test_changelog_get_all_authors() {
3430 let changelog: ChangeLog = r#"breezy (3.3.4-1) unstable; urgency=low
3431
3432 [ Contributor 1 ]
3433 * Contribution
3434
3435 * Main change
3436
3437 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
3438
3439breezy (3.3.3-1) unstable; urgency=low
3440
3441 * Bug fix release.
3442
3443 -- Jane Doe <jane@example.com> Sun, 03 Sep 2023 17:12:30 -0500
3444"#
3445 .parse()
3446 .unwrap();
3447
3448 let authors = changelog.get_all_authors();
3449
3450 assert_eq!(authors.len(), 3);
3451
3452 let author_names: std::collections::HashSet<String> = authors
3453 .iter()
3454 .map(|identity| identity.name.clone())
3455 .collect();
3456
3457 assert!(author_names.contains("Jelmer Vernooij"));
3458 assert!(author_names.contains("Jane Doe"));
3459 assert!(author_names.contains("Contributor 1"));
3460 }
3461
3462 #[test]
3463 fn test_add_changes_for_author_no_existing_sections() {
3464 let entry: Entry = r#"breezy (3.3.4-1) unstable; urgency=low
3465
3466 * Existing change
3467
3468 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
3469"#
3470 .parse()
3471 .unwrap();
3472
3473 entry
3474 .try_add_changes_for_author("Alice", vec!["* Alice's change"])
3475 .unwrap();
3476
3477 let lines: Vec<_> = entry.change_lines().collect();
3478
3479 assert!(lines.iter().any(|l| l.contains("[ Jelmer Vernooij ]")));
3481 assert!(lines.iter().any(|l| l.contains("[ Alice ]")));
3483 assert!(lines.iter().any(|l| l.contains("Existing change")));
3485 assert!(lines.iter().any(|l| l.contains("Alice's change")));
3486 }
3487
3488 #[test]
3489 fn test_add_changes_for_author_with_existing_sections() {
3490 let entry: Entry = r#"breezy (3.3.4-1) unstable; urgency=low
3491
3492 [ Author 1 ]
3493 * Change by Author 1
3494
3495 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
3496"#
3497 .parse()
3498 .unwrap();
3499
3500 entry
3501 .try_add_changes_for_author("Alice", vec!["* Alice's new change"])
3502 .unwrap();
3503
3504 let lines: Vec<_> = entry.change_lines().collect();
3505
3506 assert!(lines.iter().any(|l| l.contains("[ Author 1 ]")));
3508 assert!(lines.iter().any(|l| l.contains("[ Alice ]")));
3510 assert!(lines.iter().any(|l| l.contains("Change by Author 1")));
3512 assert!(lines.iter().any(|l| l.contains("Alice's new change")));
3513 }
3514
3515 #[test]
3516 fn test_add_changes_for_author_same_author() {
3517 let entry: Entry = r#"breezy (3.3.4-1) unstable; urgency=low
3518
3519 [ Alice ]
3520 * First change
3521
3522 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
3523"#
3524 .parse()
3525 .unwrap();
3526
3527 entry
3528 .try_add_changes_for_author("Alice", vec!["* Second change"])
3529 .unwrap();
3530
3531 let lines: Vec<_> = entry.change_lines().collect();
3532
3533 let alice_count = lines.iter().filter(|l| l.contains("[ Alice ]")).count();
3535 assert_eq!(alice_count, 1);
3536
3537 assert!(lines.iter().any(|l| l.contains("First change")));
3539 assert!(lines.iter().any(|l| l.contains("Second change")));
3540 }
3541
3542 #[test]
3543 fn test_datetime_with_incorrect_day_of_week() {
3544 let entry: Entry = r#"blah (0.1-2) UNRELEASED; urgency=medium
3547
3548 * New release.
3549
3550 -- Jelmer Vernooij <jelmer@debian.org> Mon, 22 Mar 2011 16:47:42 +0000
3551"#
3552 .parse()
3553 .unwrap();
3554
3555 assert_eq!(
3557 entry.timestamp(),
3558 Some("Mon, 22 Mar 2011 16:47:42 +0000".into())
3559 );
3560
3561 let datetime = entry.datetime();
3563 assert!(
3564 datetime.is_some(),
3565 "datetime() should not return None for timestamp with incorrect day-of-week"
3566 );
3567 assert_eq!(datetime.unwrap().to_rfc3339(), "2011-03-22T16:47:42+00:00");
3568 }
3569
3570 #[test]
3571 fn test_line_col() {
3572 let text = r#"foo (1.0-1) unstable; urgency=low
3573
3574 * First change
3575
3576 -- Maintainer <email@example.com> Mon, 01 Jan 2024 12:00:00 +0000
3577
3578bar (2.0-1) experimental; urgency=high
3579
3580 * Second change
3581 * Third change
3582
3583 -- Another <another@example.com> Tue, 02 Jan 2024 13:00:00 +0000
3584"#;
3585 let changelog = text.parse::<ChangeLog>().unwrap();
3586
3587 assert_eq!(changelog.line(), 0);
3589 assert_eq!(changelog.column(), 0);
3590 assert_eq!(changelog.line_col(), (0, 0));
3591
3592 let entries: Vec<_> = changelog.iter().collect();
3594 assert_eq!(entries.len(), 2);
3595
3596 assert_eq!(entries[0].line(), 0);
3598 assert_eq!(entries[0].column(), 0);
3599 assert_eq!(entries[0].line_col(), (0, 0));
3600
3601 assert_eq!(entries[1].line(), 6);
3603 assert_eq!(entries[1].column(), 0);
3604 assert_eq!(entries[1].line_col(), (6, 0));
3605
3606 let header = entries[0].header().unwrap();
3608 assert_eq!(header.line(), 0);
3609 assert_eq!(header.column(), 0);
3610
3611 let body = entries[0].body().unwrap();
3612 assert_eq!(body.line(), 2); let footer = entries[0].footer().unwrap();
3615 assert_eq!(footer.line(), 4); let maintainer = entries[0].maintainer_node().unwrap();
3619 assert_eq!(maintainer.line(), 4); let timestamp = entries[0].timestamp_node().unwrap();
3622 assert_eq!(timestamp.line(), 4); let header2 = entries[1].header().unwrap();
3626 assert_eq!(header2.line(), 6);
3627
3628 let footer2 = entries[1].footer().unwrap();
3629 assert_eq!(footer2.line(), 11);
3630 }
3631}