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 try_version(&self) -> Option<Result<Version, debversion::ParseError>> {
1289 self.0.children_with_tokens().find_map(|it| {
1290 if let Some(token) = it.as_token() {
1291 if token.kind() == VERSION {
1292 let text = token.text()[1..token.text().len() - 1].to_string();
1293 return Some(text.parse());
1294 }
1295 }
1296 None
1297 })
1298 }
1299
1300 pub fn version(&self) -> Option<Version> {
1305 self.try_version().and_then(|r| r.ok())
1306 }
1307
1308 pub fn package(&self) -> Option<String> {
1310 self.0.children_with_tokens().find_map(|it| {
1311 if let Some(token) = it.as_token() {
1312 if token.kind() == IDENTIFIER {
1313 return Some(token.text().to_string());
1314 }
1315 }
1316 None
1317 })
1318 }
1319
1320 pub fn distributions(&self) -> Option<Vec<String>> {
1322 let node = self.0.children().find(|it| it.kind() == DISTRIBUTIONS);
1323
1324 node.map(|node| {
1325 node.children_with_tokens()
1326 .filter_map(|it| {
1327 if let Some(token) = it.as_token() {
1328 if token.kind() == IDENTIFIER {
1329 return Some(token.text().to_string());
1330 }
1331 }
1332 None
1333 })
1334 .collect::<Vec<_>>()
1335 })
1336 }
1337
1338 pub fn set_distributions(&mut self, _distributions: Vec<String>) {
1340 let node = self
1341 .0
1342 .children_with_tokens()
1343 .find(|it| it.kind() == DISTRIBUTIONS);
1344 let mut builder = GreenNodeBuilder::new();
1345 builder.start_node(DISTRIBUTIONS.into());
1346 for (i, distribution) in _distributions.iter().enumerate() {
1347 if i > 0 {
1348 builder.token(WHITESPACE.into(), " ");
1349 }
1350 builder.token(IDENTIFIER.into(), distribution);
1351 }
1352 builder.finish_node();
1353
1354 let (range, green) = if let Some(node) = node {
1355 (
1356 node.index()..node.index() + 1,
1357 vec![builder.finish().into()],
1358 )
1359 } else if let Some(version) = self
1360 .0
1361 .children_with_tokens()
1362 .find(|it| it.kind() == VERSION)
1363 {
1364 (
1365 version.index()..version.index() + 1,
1366 vec![
1367 GreenToken::new(WHITESPACE.into(), " ").into(),
1368 builder.finish().into(),
1369 ],
1370 )
1371 } else if let Some(metadata) = self
1372 .0
1373 .children_with_tokens()
1374 .find(|it| it.kind() == METADATA)
1375 {
1376 (
1377 metadata.index() - 1..metadata.index() - 1,
1378 vec![
1379 GreenToken::new(WHITESPACE.into(), " ").into(),
1380 builder.finish().into(),
1381 ],
1382 )
1383 } else {
1384 (
1385 self.0.children().count()..self.0.children().count(),
1386 vec![
1387 GreenToken::new(WHITESPACE.into(), " ").into(),
1388 builder.finish().into(),
1389 ],
1390 )
1391 };
1392
1393 let new_root = SyntaxNode::new_root_mut(self.0.green().splice_children(range, green));
1394 self.replace_root(new_root);
1395 }
1396
1397 pub fn set_version(&mut self, version: &Version) {
1399 let node = self
1401 .0
1402 .children_with_tokens()
1403 .find(|it| it.kind() == VERSION);
1404 let (range, green) = if let Some(token) = node {
1405 (
1406 token.index()..token.index() + 1,
1407 vec![GreenToken::new(VERSION.into(), &format!("({})", version)).into()],
1408 )
1409 } else {
1410 let index = self
1411 .0
1412 .children_with_tokens()
1413 .position(|it| it.kind() == IDENTIFIER)
1414 .unwrap_or(0);
1415 (
1416 index + 1..index + 1,
1417 vec![
1418 GreenToken::new(WHITESPACE.into(), " ").into(),
1419 GreenToken::new(VERSION.into(), &format!("({})", version)).into(),
1420 ],
1421 )
1422 };
1423 let new_root = SyntaxNode::new_root_mut(self.0.green().splice_children(range, green));
1424
1425 self.replace_root(new_root);
1426 }
1427
1428 pub fn set_package(&mut self, package: String) {
1430 let node = self
1431 .0
1432 .children_with_tokens()
1433 .find(|it| it.kind() == IDENTIFIER);
1434
1435 let new_root = if let Some(token) = node {
1436 SyntaxNode::new_root_mut(self.0.green().splice_children(
1437 token.index()..token.index() + 1,
1438 vec![GreenToken::new(IDENTIFIER.into(), &package).into()],
1439 ))
1440 } else {
1441 SyntaxNode::new_root_mut(self.0.green().splice_children(
1442 0..0,
1443 vec![
1444 GreenToken::new(IDENTIFIER.into(), &package).into(),
1445 GreenToken::new(WHITESPACE.into(), " ").into(),
1446 ],
1447 ))
1448 };
1449
1450 self.replace_root(new_root);
1451 }
1452
1453 pub fn set_metadata(&mut self, key: &str, value: &str) {
1455 if let Some(mut node) = self
1457 .metadata_nodes()
1458 .find(|it| it.key().map(|k| k == key).unwrap_or(false))
1459 {
1460 node.set_value(value);
1461 } else if let Some(metadata) = self
1462 .0
1463 .children_with_tokens()
1464 .find(|it| it.kind() == METADATA)
1465 {
1466 let mut builder = GreenNodeBuilder::new();
1467 builder.start_node(METADATA_ENTRY.into());
1468 builder.start_node(METADATA_KEY.into());
1469 builder.token(IDENTIFIER.into(), key);
1470 builder.finish_node();
1471 builder.token(EQUALS.into(), "=");
1472 builder.start_node(METADATA_VALUE.into());
1473 builder.token(IDENTIFIER.into(), value);
1474 builder.finish_node();
1475 builder.finish_node();
1476
1477 let metadata = metadata.as_node().unwrap();
1478
1479 let count = metadata.children_with_tokens().count();
1480 self.0.splice_children(
1481 metadata.index()..metadata.index() + 1,
1482 vec![SyntaxNode::new_root_mut(metadata.green().splice_children(
1483 count..count,
1484 vec![
1485 GreenToken::new(WHITESPACE.into(), " ").into(),
1486 builder.finish().into(),
1487 ],
1488 ))
1489 .into()],
1490 );
1491 } else {
1492 let mut builder = GreenNodeBuilder::new();
1493 builder.start_node(METADATA.into());
1494 builder.token(SEMICOLON.into(), ";");
1495 builder.token(WHITESPACE.into(), " ");
1496 builder.start_node(METADATA_ENTRY.into());
1497 builder.start_node(METADATA_KEY.into());
1498 builder.token(IDENTIFIER.into(), key);
1499 builder.finish_node();
1500 builder.token(EQUALS.into(), "=");
1501 builder.start_node(METADATA_VALUE.into());
1502 builder.token(IDENTIFIER.into(), value);
1503 builder.finish_node();
1504 builder.finish_node();
1505
1506 let new_root = SyntaxNode::new_root_mut(builder.finish());
1507
1508 if let Some(distributions) = self
1510 .0
1511 .children_with_tokens()
1512 .find(|it| it.kind() == DISTRIBUTIONS)
1513 {
1514 self.0.splice_children(
1515 distributions.index() + 1..distributions.index() + 1,
1516 vec![new_root.into()],
1517 );
1518 } else if let Some(nl) = self
1519 .0
1520 .children_with_tokens()
1521 .find(|it| it.kind() == NEWLINE)
1522 {
1523 self.0
1525 .splice_children(nl.index()..nl.index(), vec![new_root.into()]);
1526 } else {
1527 let count = self.0.children_with_tokens().count();
1528 self.0.splice_children(count..count, vec![new_root.into()]);
1529 }
1530 }
1531 }
1532
1533 pub fn metadata_nodes(&self) -> impl Iterator<Item = MetadataEntry> + '_ {
1535 let node = self.0.children().find(|it| it.kind() == METADATA);
1536
1537 node.into_iter().flat_map(|node| {
1538 node.children_with_tokens()
1539 .filter_map(|it| MetadataEntry::cast(it.into_node()?))
1540 })
1541 }
1542
1543 pub fn metadata(&self) -> impl Iterator<Item = (String, String)> + '_ {
1545 self.metadata_nodes().filter_map(|entry| {
1546 if let (Some(key), Some(value)) = (entry.key(), entry.value()) {
1547 Some((key, value))
1548 } else {
1549 None
1550 }
1551 })
1552 }
1553
1554 pub fn urgency(&self) -> Option<Urgency> {
1556 for (key, value) in self.metadata() {
1557 if key.as_str() == "urgency" {
1558 return Some(value.parse().unwrap());
1559 }
1560 }
1561 None
1562 }
1563}
1564
1565impl EntryFooter {
1566 pub fn email(&self) -> Option<String> {
1568 self.0.children_with_tokens().find_map(|it| {
1569 if let Some(token) = it.as_token() {
1570 let text = token.text();
1571 if token.kind() == EMAIL {
1572 return Some(text[1..text.len() - 1].to_string());
1573 }
1574 }
1575 None
1576 })
1577 }
1578
1579 pub fn maintainer(&self) -> Option<String> {
1581 self.0
1582 .children()
1583 .find_map(Maintainer::cast)
1584 .map(|m| m.text())
1585 .filter(|s| !s.is_empty())
1586 }
1587
1588 pub fn set_maintainer(&mut self, maintainer: String) {
1590 let node = self
1591 .0
1592 .children_with_tokens()
1593 .find(|it| it.kind() == MAINTAINER);
1594 let new_root = if let Some(node) = node {
1595 SyntaxNode::new_root_mut(self.0.green().splice_children(
1596 node.index()..node.index() + 1,
1597 vec![GreenToken::new(MAINTAINER.into(), &maintainer).into()],
1598 ))
1599 } else if let Some(node) = self.0.children_with_tokens().find(|it| it.kind() == INDENT) {
1600 SyntaxNode::new_root_mut(self.0.green().splice_children(
1601 node.index() + 1..node.index() + 1,
1602 vec![GreenToken::new(MAINTAINER.into(), &maintainer).into()],
1603 ))
1604 } else {
1605 SyntaxNode::new_root_mut(self.0.green().splice_children(
1606 0..0,
1607 vec![
1608 GreenToken::new(INDENT.into(), " -- ").into(),
1609 GreenToken::new(MAINTAINER.into(), &maintainer).into(),
1610 ],
1611 ))
1612 };
1613
1614 self.replace_root(new_root);
1615 }
1616
1617 pub fn set_email(&mut self, _email: String) {
1619 let node = self.0.children_with_tokens().find(|it| it.kind() == EMAIL);
1620 let new_root = if let Some(node) = node {
1621 SyntaxNode::new_root_mut(self.0.green().splice_children(
1622 node.index()..node.index() + 1,
1623 vec![GreenToken::new(EMAIL.into(), &format!("<{}>", _email)).into()],
1624 ))
1625 } else if let Some(node) = self
1626 .0
1627 .children_with_tokens()
1628 .find(|it| it.kind() == MAINTAINER)
1629 {
1630 SyntaxNode::new_root_mut(self.0.green().splice_children(
1631 node.index() + 1..node.index() + 1,
1632 vec![GreenToken::new(EMAIL.into(), &format!("<{}>", _email)).into()],
1633 ))
1634 } else if let Some(node) = self.0.children_with_tokens().find(|it| it.kind() == INDENT) {
1635 SyntaxNode::new_root_mut(self.0.green().splice_children(
1636 node.index() + 1..node.index() + 1,
1637 vec![
1638 GreenToken::new(MAINTAINER.into(), "").into(),
1639 GreenToken::new(WHITESPACE.into(), " ").into(),
1640 GreenToken::new(EMAIL.into(), &format!("<{}>", _email)).into(),
1641 ],
1642 ))
1643 } else {
1644 SyntaxNode::new_root_mut(self.0.green().splice_children(
1645 0..0,
1646 vec![
1647 GreenToken::new(INDENT.into(), " -- ").into(),
1648 GreenToken::new(MAINTAINER.into(), "").into(),
1649 GreenToken::new(WHITESPACE.into(), " ").into(),
1650 GreenToken::new(EMAIL.into(), &format!("<{}>", _email)).into(),
1651 ],
1652 ))
1653 };
1654
1655 self.replace_root(new_root);
1656 }
1657
1658 pub fn timestamp(&self) -> Option<String> {
1660 self.0
1661 .children()
1662 .find_map(Timestamp::cast)
1663 .map(|m| m.text())
1664 }
1665
1666 pub fn set_timestamp(&mut self, timestamp: String) {
1668 let node = self
1669 .0
1670 .children_with_tokens()
1671 .find(|it| it.kind() == TIMESTAMP);
1672 let new_root = if let Some(node) = node {
1673 SyntaxNode::new_root_mut(self.0.green().splice_children(
1674 node.index()..node.index() + 1,
1675 vec![GreenToken::new(TIMESTAMP.into(), ×tamp).into()],
1676 ))
1677 } else if let Some(node) = self.0.children_with_tokens().find(|it| it.kind() == INDENT) {
1678 SyntaxNode::new_root_mut(self.0.green().splice_children(
1679 node.index() + 1..node.index() + 1,
1680 vec![GreenToken::new(TIMESTAMP.into(), ×tamp).into()],
1681 ))
1682 } else if let Some(node) = self.0.children_with_tokens().find(|it| it.kind() == EMAIL) {
1683 SyntaxNode::new_root_mut(self.0.green().splice_children(
1684 node.index() + 1..node.index() + 1,
1685 vec![GreenToken::new(TIMESTAMP.into(), ×tamp).into()],
1686 ))
1687 } else {
1688 SyntaxNode::new_root_mut(self.0.green().splice_children(
1689 0..0,
1690 vec![
1691 GreenToken::new(INDENT.into(), " -- ").into(),
1692 GreenToken::new(TIMESTAMP.into(), ×tamp).into(),
1693 ],
1694 ))
1695 };
1696
1697 self.replace_root(new_root);
1698 }
1699}
1700
1701impl EntryBody {
1702 fn text(&self) -> String {
1703 self.0
1704 .children_with_tokens()
1705 .filter_map(|it| {
1706 if let Some(token) = it.as_token() {
1707 if token.kind() == DETAIL {
1708 return Some(token.text().to_string());
1709 }
1710 }
1711 None
1712 })
1713 .collect::<Vec<_>>()
1714 .concat()
1715 }
1716}
1717
1718impl Timestamp {
1719 fn text(&self) -> String {
1720 self.0.text().to_string()
1721 }
1722}
1723
1724impl Maintainer {
1725 fn text(&self) -> String {
1726 self.0.text().to_string()
1727 }
1728}
1729
1730impl Entry {
1731 pub fn header(&self) -> Option<EntryHeader> {
1733 self.0.children().find_map(EntryHeader::cast)
1734 }
1735
1736 pub fn body(&self) -> Option<EntryBody> {
1738 self.0.children().find_map(EntryBody::cast)
1739 }
1740
1741 pub fn footer(&self) -> Option<EntryFooter> {
1743 self.0.children().find_map(EntryFooter::cast)
1744 }
1745
1746 pub fn package(&self) -> Option<String> {
1748 self.header().and_then(|h| h.package())
1749 }
1750
1751 pub fn set_package(&mut self, package: String) {
1753 if let Some(mut header) = self.header() {
1754 let header_index = header.0.index();
1755 header.set_package(package);
1756 self.0
1757 .splice_children(header_index..header_index + 1, vec![header.0.into()]);
1758 } else {
1759 self.create_header().set_package(package);
1760 }
1761 }
1762
1763 pub fn try_version(&self) -> Option<Result<Version, debversion::ParseError>> {
1770 self.header().and_then(|h| h.try_version())
1771 }
1772
1773 pub fn version(&self) -> Option<Version> {
1778 self.try_version().and_then(|r| r.ok())
1779 }
1780
1781 pub fn set_version(&mut self, version: &Version) {
1783 if let Some(mut header) = self.header() {
1784 let header_index = header.0.index();
1785 header.set_version(version);
1786 self.0
1787 .splice_children(header_index..header_index + 1, vec![header.0.into()]);
1788 } else {
1789 self.create_header().set_version(version);
1790 }
1791 }
1792
1793 pub fn distributions(&self) -> Option<Vec<String>> {
1795 self.header().and_then(|h| h.distributions())
1796 }
1797
1798 pub fn set_distributions(&mut self, distributions: Vec<String>) {
1800 if let Some(mut header) = self.header() {
1801 let header_index = header.0.index();
1802 header.set_distributions(distributions);
1803 self.0
1804 .splice_children(header_index..header_index + 1, vec![header.0.into()]);
1805 } else {
1806 self.create_header().set_distributions(distributions);
1807 }
1808 }
1809
1810 pub fn email(&self) -> Option<String> {
1812 self.footer().and_then(|f| f.email())
1813 }
1814
1815 pub fn maintainer_node(&self) -> Option<Maintainer> {
1817 self.footer()
1818 .and_then(|f| f.0.children().find_map(Maintainer::cast))
1819 }
1820
1821 pub fn maintainer(&self) -> Option<String> {
1823 self.footer().and_then(|f| f.maintainer())
1824 }
1825
1826 pub fn set_maintainer(&mut self, maintainer: (String, String)) {
1828 if let Some(mut footer) = self.footer() {
1829 let footer_index = footer.0.index();
1830 footer.set_maintainer(maintainer.0);
1831 footer.set_email(maintainer.1);
1832 self.0
1833 .splice_children(footer_index..footer_index + 1, vec![footer.0.into()]);
1834 } else {
1835 let mut footer = self.create_footer();
1836 footer.set_maintainer(maintainer.0);
1837 footer.set_email(maintainer.1);
1838 }
1839 }
1840
1841 pub fn timestamp_node(&self) -> Option<Timestamp> {
1843 self.footer()
1844 .and_then(|f| f.0.children().find_map(Timestamp::cast))
1845 }
1846
1847 pub fn timestamp(&self) -> Option<String> {
1849 self.footer().and_then(|f| f.timestamp())
1850 }
1851
1852 pub fn set_timestamp(&mut self, timestamp: String) {
1854 if let Some(mut footer) = self.footer() {
1855 let footer_index = footer.0.index();
1856 footer.set_timestamp(timestamp);
1857 self.0
1858 .splice_children(footer_index..footer_index + 1, vec![footer.0.into()]);
1859 } else {
1860 self.create_footer().set_timestamp(timestamp);
1861 }
1862 }
1863
1864 #[cfg(feature = "chrono")]
1866 pub fn set_datetime(&mut self, datetime: DateTime<FixedOffset>) {
1867 self.set_timestamp(format!("{}", datetime.format("%a, %d %b %Y %H:%M:%S %z")));
1868 }
1869
1870 #[cfg(feature = "chrono")]
1872 pub fn datetime(&self) -> Option<DateTime<FixedOffset>> {
1873 self.timestamp().and_then(|ts| parse_time_string(&ts).ok())
1874 }
1875
1876 pub fn urgency(&self) -> Option<Urgency> {
1878 self.header().and_then(|h| h.urgency())
1879 }
1880
1881 fn create_header(&self) -> EntryHeader {
1882 let mut builder = GreenNodeBuilder::new();
1883 builder.start_node(ENTRY_HEADER.into());
1884 builder.token(NEWLINE.into(), "\n");
1885 builder.finish_node();
1886 let syntax = SyntaxNode::new_root_mut(builder.finish());
1887 self.0.splice_children(0..0, vec![syntax.into()]);
1888 EntryHeader(self.0.children().next().unwrap().clone_for_update())
1889 }
1890
1891 fn create_footer(&self) -> EntryFooter {
1892 let mut builder = GreenNodeBuilder::new();
1893 builder.start_node(ENTRY_FOOTER.into());
1894 builder.token(NEWLINE.into(), "\n");
1895 builder.finish_node();
1896 let syntax = SyntaxNode::new_root_mut(builder.finish());
1897 let count = self.0.children().count();
1898 self.0.splice_children(count..count, vec![syntax.into()]);
1899 EntryFooter(self.0.children().last().unwrap().clone_for_update())
1900 }
1901
1902 pub fn set_urgency(&mut self, urgency: Urgency) {
1904 self.set_metadata("urgency", urgency.to_string().as_str());
1905 }
1906
1907 pub fn set_metadata(&mut self, key: &str, value: &str) {
1909 if let Some(mut header) = self.header() {
1910 let header_index = header.0.index();
1911 header.set_metadata(key, value);
1912 self.0
1913 .splice_children(header_index..header_index + 1, vec![header.0.into()]);
1914 } else {
1915 self.create_header().set_metadata(key, value);
1916 }
1917 }
1918
1919 pub fn try_add_change_for_author(
1926 &self,
1927 change: &[&str],
1928 author: (String, String),
1929 ) -> Result<(), crate::textwrap::Error> {
1930 let changes_lines = self.change_lines().collect::<Vec<_>>();
1931 let by_author = crate::changes::changes_by_author(changes_lines.iter().map(|s| s.as_str()))
1932 .collect::<Vec<_>>();
1933
1934 if by_author.iter().all(|(a, _, _)| a.is_none()) {
1936 if let Some(maintainer_name) = self.maintainer() {
1937 if author.0 != maintainer_name {
1938 self.prepend_change_line(
1939 crate::changes::format_section_title(maintainer_name.as_str()).as_str(),
1940 );
1941 if !self.change_lines().last().unwrap().is_empty() {
1942 self.append_change_line("");
1943 }
1944 self.append_change_line(
1945 crate::changes::format_section_title(author.0.as_str()).as_str(),
1946 );
1947 }
1948 }
1949 } else if let Some(last_section) = by_author.last().as_ref() {
1950 if last_section.0 != Some(author.0.as_str()) {
1951 self.append_change_line("");
1952 self.append_change_line(
1953 crate::changes::format_section_title(author.0.as_str()).as_str(),
1954 );
1955 }
1956 }
1957
1958 if let Some(last) = self.change_lines().last() {
1959 if last.trim().is_empty() {
1960 self.pop_change_line();
1961 }
1962 }
1963
1964 for line in crate::textwrap::try_rewrap_changes(change.iter().copied())? {
1965 self.append_change_line(line.as_ref());
1966 }
1967 Ok(())
1968 }
1969
1970 #[deprecated(
1983 since = "0.2.10",
1984 note = "Use try_add_change_for_author for proper error handling"
1985 )]
1986 pub fn add_change_for_author(&self, change: &[&str], author: (String, String)) {
1987 self.try_add_change_for_author(change, author).unwrap()
1988 }
1989
1990 pub fn prepend_change_line(&self, line: &str) {
1992 let mut builder = GreenNodeBuilder::new();
1993 builder.start_node(ENTRY_BODY.into());
1994 if !line.is_empty() {
1995 builder.token(INDENT.into(), " ");
1996 builder.token(DETAIL.into(), line);
1997 }
1998 builder.token(NEWLINE.into(), "\n");
1999 builder.finish_node();
2000
2001 let mut it = self.0.children();
2003 let header = it.find(|n| n.kind() == ENTRY_HEADER);
2004
2005 let previous_line = it.find(|n| n.kind() == EMPTY_LINE).or(header);
2006
2007 let index = previous_line.map_or(0, |l| l.index() + 1);
2008
2009 let syntax = SyntaxNode::new_root_mut(builder.finish());
2010
2011 self.0.splice_children(index..index, vec![syntax.into()]);
2012 }
2013
2014 pub fn pop_change_line(&self) -> Option<String> {
2016 let last_child = self.0.children().filter(|n| n.kind() == ENTRY_BODY).last();
2018
2019 if let Some(last_child) = last_child {
2020 let text = last_child.children_with_tokens().find_map(|it| {
2021 if let Some(token) = it.as_token() {
2022 if token.kind() == DETAIL {
2023 return Some(token.text().to_string());
2024 }
2025 }
2026 None
2027 });
2028 self.0
2029 .splice_children(last_child.index()..last_child.index() + 1, vec![]);
2030 text
2031 } else {
2032 None
2033 }
2034 }
2035
2036 pub fn append_change_line(&self, line: &str) {
2038 let mut builder = GreenNodeBuilder::new();
2039 builder.start_node(ENTRY_BODY.into());
2040 if !line.is_empty() {
2041 builder.token(INDENT.into(), " ");
2042 builder.token(DETAIL.into(), line);
2043 }
2044 builder.token(NEWLINE.into(), "\n");
2045 builder.finish_node();
2046
2047 let last_child = self
2049 .0
2050 .children()
2051 .filter(|n| n.kind() == ENTRY_BODY)
2052 .last()
2053 .unwrap_or_else(|| {
2054 let children: Vec<_> = self.0.children().collect();
2057 if children.len() >= 2
2058 && children[0].kind() == ENTRY_HEADER
2059 && children[1].kind() == EMPTY_LINE
2060 {
2061 children[1].clone()
2062 } else {
2063 children[0].clone()
2064 }
2065 });
2066
2067 let syntax = SyntaxNode::new_root_mut(builder.finish()).into();
2068 self.0
2069 .splice_children(last_child.index() + 1..last_child.index() + 1, vec![syntax]);
2070 }
2071
2072 pub fn add_bullet(&self, text: &str) {
2099 let wrapped = crate::textwrap::textwrap(
2101 text,
2102 Some(crate::textwrap::DEFAULT_WIDTH),
2103 Some(crate::textwrap::INITIAL_INDENT),
2104 Some(" "),
2105 );
2106
2107 for line in wrapped {
2109 self.append_change_line(&line);
2110 }
2111 }
2112
2113 pub fn change_lines(&self) -> impl Iterator<Item = String> + '_ {
2115 let mut lines = self
2116 .0
2117 .children()
2118 .filter_map(|n| {
2119 if let Some(ref change) = EntryBody::cast(n.clone()) {
2120 Some(change.text())
2121 } else if n.kind() == EMPTY_LINE {
2122 Some("".to_string())
2123 } else {
2124 None
2125 }
2126 })
2127 .collect::<Vec<_>>();
2128
2129 while let Some(last) = lines.last() {
2130 if last.is_empty() {
2131 lines.pop();
2132 } else {
2133 break;
2134 }
2135 }
2136
2137 lines.into_iter().skip_while(|it| it.is_empty())
2138 }
2139
2140 pub fn ensure_first_line(&self, line: &str) {
2144 let first_line = self.change_lines().next().map(|it| it.trim().to_string());
2145
2146 if first_line != Some(line.to_string()) {
2147 self.prepend_change_line(line);
2148 }
2149 }
2150
2151 pub fn is_unreleased(&self) -> Option<bool> {
2153 let distro_is_unreleased = self.distributions().as_ref().map(|ds| {
2154 let ds = ds.iter().map(|d| d.as_str()).collect::<Vec<&str>>();
2155 crate::distributions_is_unreleased(ds.as_slice())
2156 });
2157
2158 let footer_is_unreleased = if self.maintainer().is_none() && self.email().is_none() {
2159 Some(true)
2160 } else {
2161 None
2162 };
2163
2164 match (distro_is_unreleased, footer_is_unreleased) {
2165 (Some(true), _) => Some(true),
2166 (_, Some(true)) => Some(true),
2167 (Some(false), _) => Some(false),
2168 (_, Some(false)) => Some(false),
2169 _ => None,
2170 }
2171 }
2172
2173 pub fn iter_changes_by_author(&self) -> Vec<(Option<String>, Vec<usize>, Vec<String>)> {
2178 let changes: Vec<String> = self.change_lines().map(|s| s.to_string()).collect();
2179 crate::changes::changes_by_author(changes.iter().map(|s| s.as_str()))
2180 .map(|(author, linenos, lines)| {
2181 let author_name = author.map(|s| s.to_string());
2182 let change_lines = lines.into_iter().map(|s| s.to_string()).collect();
2183 (author_name, linenos, change_lines)
2184 })
2185 .collect()
2186 }
2187
2188 pub fn get_authors(&self) -> std::collections::HashSet<String> {
2193 let changes: Vec<String> = self.change_lines().map(|s| s.to_string()).collect();
2194 let change_strs: Vec<&str> = changes.iter().map(|s| s.as_str()).collect();
2195 crate::changes::find_extra_authors(&change_strs)
2196 .into_iter()
2197 .map(|s| s.to_string())
2198 .collect()
2199 }
2200
2201 pub fn get_maintainer_identity(&self) -> Option<crate::Identity> {
2205 if let (Some(name), Some(email)) = (self.maintainer(), self.email()) {
2206 Some(crate::Identity::new(name, email))
2207 } else {
2208 None
2209 }
2210 }
2211
2212 pub fn try_add_changes_for_author(
2236 &self,
2237 author_name: &str,
2238 changes: Vec<&str>,
2239 ) -> Result<(), crate::textwrap::Error> {
2240 let mut change_lines: Vec<String> = self.change_lines().collect();
2241 let original_len = change_lines.len();
2242 let default_author = self.get_maintainer_identity().map(|id| (id.name, id.email));
2243
2244 crate::changes::try_add_change_for_author(
2245 &mut change_lines,
2246 author_name,
2247 changes,
2248 default_author,
2249 )?;
2250
2251 if change_lines.len() > original_len {
2256 let original_changes: Vec<_> = self.change_lines().collect();
2258
2259 let inserted_at_start = original_len > 0 && change_lines[0] != original_changes[0];
2261
2262 if inserted_at_start {
2263 while self.pop_change_line().is_some() {}
2266 for line in change_lines {
2267 self.append_change_line(&line);
2268 }
2269 } else {
2270 for line in change_lines.iter().skip(original_len) {
2272 self.append_change_line(line);
2273 }
2274 }
2275 }
2276 Ok(())
2277 }
2278
2279 #[deprecated(
2289 since = "0.2.10",
2290 note = "Use try_add_changes_for_author for proper error handling"
2291 )]
2292 pub fn add_changes_for_author(&self, author_name: &str, changes: Vec<&str>) {
2293 self.try_add_changes_for_author(author_name, changes)
2294 .unwrap()
2295 }
2296}
2297
2298#[cfg(feature = "chrono")]
2299const CHANGELOG_TIME_FORMAT: &str = "%a, %d %b %Y %H:%M:%S %z";
2300
2301#[cfg(feature = "chrono")]
2302fn parse_time_string(time_str: &str) -> Result<DateTime<FixedOffset>, chrono::ParseError> {
2303 if let Ok(dt) = DateTime::parse_from_str(time_str, CHANGELOG_TIME_FORMAT) {
2305 return Ok(dt);
2306 }
2307
2308 if let Some(after_comma) = time_str.split_once(", ") {
2312 DateTime::parse_from_str(after_comma.1, "%d %b %Y %H:%M:%S %z")
2313 } else {
2314 DateTime::parse_from_str(time_str, CHANGELOG_TIME_FORMAT)
2316 }
2317}
2318
2319#[cfg(test)]
2320mod tests {
2321 use super::*;
2322
2323 #[test]
2324 fn test_parse_simple() {
2325 const CHANGELOG: &str = r#"breezy (3.3.4-1) unstable; urgency=low
2326
2327 * New upstream release.
2328
2329 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
2330
2331breezy (3.3.3-2) unstable; urgency=medium
2332
2333 * Drop unnecessary dependency on python3-six. Closes: #1039011
2334 * Drop dependency on cython3-dbg. Closes: #1040544
2335
2336 -- Jelmer Vernooij <jelmer@debian.org> Sat, 24 Jun 2023 14:58:57 +0100
2337
2338# Oh, and here is a comment
2339"#;
2340 let parsed = parse(CHANGELOG);
2341 assert_eq!(parsed.errors(), &Vec::<String>::new());
2342 let node = parsed.syntax_node();
2343 assert_eq!(
2344 format!("{:#?}", node),
2345 r###"ROOT@0..405
2346 ENTRY@0..140
2347 ENTRY_HEADER@0..39
2348 IDENTIFIER@0..6 "breezy"
2349 WHITESPACE@6..7 " "
2350 VERSION@7..16 "(3.3.4-1)"
2351 WHITESPACE@16..17 " "
2352 DISTRIBUTIONS@17..25
2353 IDENTIFIER@17..25 "unstable"
2354 METADATA@25..38
2355 SEMICOLON@25..26 ";"
2356 WHITESPACE@26..27 " "
2357 METADATA_ENTRY@27..38
2358 METADATA_KEY@27..34
2359 IDENTIFIER@27..34 "urgency"
2360 EQUALS@34..35 "="
2361 METADATA_VALUE@35..38
2362 IDENTIFIER@35..38 "low"
2363 NEWLINE@38..39 "\n"
2364 EMPTY_LINE@39..40
2365 NEWLINE@39..40 "\n"
2366 ENTRY_BODY@40..66
2367 INDENT@40..42 " "
2368 DETAIL@42..65 "* New upstream release."
2369 NEWLINE@65..66 "\n"
2370 EMPTY_LINE@66..67
2371 NEWLINE@66..67 "\n"
2372 ENTRY_FOOTER@67..140
2373 INDENT@67..71 " -- "
2374 MAINTAINER@71..86
2375 TEXT@71..77 "Jelmer"
2376 WHITESPACE@77..78 " "
2377 TEXT@78..86 "Vernooij"
2378 WHITESPACE@86..87 " "
2379 EMAIL@87..106 "<jelmer@debian.org>"
2380 WHITESPACE@106..108 " "
2381 TIMESTAMP@108..139
2382 TEXT@108..112 "Mon,"
2383 WHITESPACE@112..113 " "
2384 TEXT@113..115 "04"
2385 WHITESPACE@115..116 " "
2386 TEXT@116..119 "Sep"
2387 WHITESPACE@119..120 " "
2388 TEXT@120..124 "2023"
2389 WHITESPACE@124..125 " "
2390 TEXT@125..133 "18:13:45"
2391 WHITESPACE@133..134 " "
2392 TEXT@134..139 "-0500"
2393 NEWLINE@139..140 "\n"
2394 EMPTY_LINE@140..141
2395 NEWLINE@140..141 "\n"
2396 ENTRY@141..376
2397 ENTRY_HEADER@141..183
2398 IDENTIFIER@141..147 "breezy"
2399 WHITESPACE@147..148 " "
2400 VERSION@148..157 "(3.3.3-2)"
2401 WHITESPACE@157..158 " "
2402 DISTRIBUTIONS@158..166
2403 IDENTIFIER@158..166 "unstable"
2404 METADATA@166..182
2405 SEMICOLON@166..167 ";"
2406 WHITESPACE@167..168 " "
2407 METADATA_ENTRY@168..182
2408 METADATA_KEY@168..175
2409 IDENTIFIER@168..175 "urgency"
2410 EQUALS@175..176 "="
2411 METADATA_VALUE@176..182
2412 IDENTIFIER@176..182 "medium"
2413 NEWLINE@182..183 "\n"
2414 EMPTY_LINE@183..184
2415 NEWLINE@183..184 "\n"
2416 ENTRY_BODY@184..249
2417 INDENT@184..186 " "
2418 DETAIL@186..248 "* Drop unnecessary de ..."
2419 NEWLINE@248..249 "\n"
2420 ENTRY_BODY@249..302
2421 INDENT@249..251 " "
2422 DETAIL@251..301 "* Drop dependency on ..."
2423 NEWLINE@301..302 "\n"
2424 EMPTY_LINE@302..303
2425 NEWLINE@302..303 "\n"
2426 ENTRY_FOOTER@303..376
2427 INDENT@303..307 " -- "
2428 MAINTAINER@307..322
2429 TEXT@307..313 "Jelmer"
2430 WHITESPACE@313..314 " "
2431 TEXT@314..322 "Vernooij"
2432 WHITESPACE@322..323 " "
2433 EMAIL@323..342 "<jelmer@debian.org>"
2434 WHITESPACE@342..344 " "
2435 TIMESTAMP@344..375
2436 TEXT@344..348 "Sat,"
2437 WHITESPACE@348..349 " "
2438 TEXT@349..351 "24"
2439 WHITESPACE@351..352 " "
2440 TEXT@352..355 "Jun"
2441 WHITESPACE@355..356 " "
2442 TEXT@356..360 "2023"
2443 WHITESPACE@360..361 " "
2444 TEXT@361..369 "14:58:57"
2445 WHITESPACE@369..370 " "
2446 TEXT@370..375 "+0100"
2447 NEWLINE@375..376 "\n"
2448 EMPTY_LINE@376..377
2449 NEWLINE@376..377 "\n"
2450 COMMENT@377..405 "# Oh, and here is a c ..."
2451"###
2452 );
2453
2454 let mut root = parsed.tree_mut();
2455 let entries: Vec<_> = root.iter().collect();
2456 assert_eq!(entries.len(), 2);
2457 let entry = &entries[0];
2458 assert_eq!(entry.package(), Some("breezy".into()));
2459 assert_eq!(entry.version(), Some("3.3.4-1".parse().unwrap()));
2460 assert_eq!(entry.distributions(), Some(vec!["unstable".into()]));
2461 assert_eq!(entry.urgency(), Some(Urgency::Low));
2462 assert_eq!(entry.maintainer(), Some("Jelmer Vernooij".into()));
2463 assert_eq!(entry.email(), Some("jelmer@debian.org".into()));
2464 assert_eq!(
2465 entry.timestamp(),
2466 Some("Mon, 04 Sep 2023 18:13:45 -0500".into())
2467 );
2468 #[cfg(feature = "chrono")]
2469 assert_eq!(
2470 entry.datetime(),
2471 Some("2023-09-04T18:13:45-05:00".parse().unwrap())
2472 );
2473 let changes_lines: Vec<_> = entry.change_lines().collect();
2474 assert_eq!(changes_lines, vec!["* New upstream release.".to_string()]);
2475
2476 assert_eq!(node.text(), CHANGELOG);
2477
2478 let first = root.pop_first().unwrap();
2479 assert_eq!(first.version(), Some("3.3.4-1".parse().unwrap()));
2480 assert_eq!(
2481 root.to_string(),
2482 r#"breezy (3.3.3-2) unstable; urgency=medium
2483
2484 * Drop unnecessary dependency on python3-six. Closes: #1039011
2485 * Drop dependency on cython3-dbg. Closes: #1040544
2486
2487 -- Jelmer Vernooij <jelmer@debian.org> Sat, 24 Jun 2023 14:58:57 +0100
2488
2489# Oh, and here is a comment
2490"#
2491 );
2492 }
2493
2494 #[test]
2495 fn test_from_io_read() {
2496 let changelog = r#"breezy (3.3.4-1) unstable; urgency=low
2497
2498 * New upstream release.
2499
2500 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
2501"#;
2502
2503 let input = changelog.as_bytes();
2504 let input = Box::new(std::io::Cursor::new(input)) as Box<dyn std::io::Read>;
2505 let parsed = ChangeLog::read(input).unwrap();
2506 assert_eq!(parsed.to_string(), changelog);
2507 }
2508
2509 #[test]
2510 #[cfg(feature = "chrono")]
2511 fn test_new_entry() {
2512 let mut cl = ChangeLog::new();
2513 cl.new_entry()
2514 .package("breezy".into())
2515 .version("3.3.4-1".parse().unwrap())
2516 .distributions(vec!["unstable".into()])
2517 .urgency(Urgency::Low)
2518 .maintainer(("Jelmer Vernooij".into(), "jelmer@debian.org".into()))
2519 .change_line("* A change.".into())
2520 .datetime("Mon, 04 Sep 2023 18:13:45 -0500")
2521 .finish();
2522 assert_eq!(
2523 r###"breezy (3.3.4-1) unstable; urgency=low
2524
2525 * A change.
2526
2527 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
2528"###,
2529 cl.to_string()
2530 );
2531
2532 assert!(!cl.iter().next().unwrap().is_unreleased().unwrap());
2533 }
2534
2535 #[test]
2536 #[cfg(feature = "chrono")]
2537 fn test_new_empty_default() {
2538 let mut cl = ChangeLog::new();
2539 cl.new_entry()
2540 .package("breezy".into())
2541 .version("3.3.4-1".parse().unwrap())
2542 .maintainer(("Jelmer Vernooij".into(), "jelmer@debian.org".into()))
2543 .change_line("* A change.".into())
2544 .datetime("Mon, 04 Sep 2023 18:13:45 -0500")
2545 .finish();
2546 assert_eq!(
2547 r###"breezy (3.3.4-1) UNRELEASED; urgency=low
2548
2549 * A change.
2550
2551 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
2552"###,
2553 cl.to_string()
2554 );
2555 }
2556
2557 #[test]
2558 fn test_new_empty_entry() {
2559 let mut cl = ChangeLog::new();
2560 cl.new_empty_entry()
2561 .change_line("* A change.".into())
2562 .finish();
2563 assert_eq!(
2564 r###"
2565
2566 * A change.
2567
2568 --
2569"###,
2570 cl.to_string()
2571 );
2572 assert_eq!(cl.iter().next().unwrap().is_unreleased(), Some(true));
2573 }
2574
2575 #[test]
2576 fn test_parse_invalid_line() {
2577 let text = r#"THIS IS NOT A PARSEABLE LINE
2578lintian-brush (0.35) UNRELEASED; urgency=medium
2579
2580 * Support updating templated debian/control files that use cdbs
2581 template.
2582
2583 -- Joe Example <joe@example.com> Fri, 04 Oct 2019 02:36:13 +0000
2584"#;
2585 let cl = ChangeLog::read_relaxed(text.as_bytes()).unwrap();
2586 let entry = cl.iter().nth(1).unwrap();
2587 assert_eq!(entry.package(), Some("lintian-brush".into()));
2588 assert_eq!(entry.version(), Some("0.35".parse().unwrap()));
2589 assert_eq!(entry.urgency(), Some(Urgency::Medium));
2590 assert_eq!(entry.maintainer(), Some("Joe Example".into()));
2591 assert_eq!(entry.email(), Some("joe@example.com".into()));
2592 assert_eq!(entry.distributions(), Some(vec!["UNRELEASED".into()]));
2593 #[cfg(feature = "chrono")]
2594 assert_eq!(
2595 entry.datetime(),
2596 Some("2019-10-04T02:36:13+00:00".parse().unwrap())
2597 );
2598 }
2599
2600 #[cfg(test)]
2601 mod entry_manipulate_tests {
2602 use super::*;
2603
2604 #[test]
2605 fn test_append_change_line() {
2606 let mut cl = ChangeLog::new();
2607
2608 let entry = cl
2609 .new_empty_entry()
2610 .change_line("* A change.".into())
2611 .finish();
2612
2613 entry.append_change_line("* Another change.");
2614
2615 assert_eq!(
2616 r###"
2617
2618 * A change.
2619 * Another change.
2620
2621 --
2622"###,
2623 cl.to_string()
2624 );
2625 }
2626
2627 #[test]
2628 fn test_prepend_change_line() {
2629 let mut cl = ChangeLog::new();
2630
2631 let entry = cl
2632 .new_empty_entry()
2633 .change_line("* A change.".into())
2634 .finish();
2635
2636 entry.prepend_change_line("* Another change.");
2637
2638 assert_eq!(
2639 r###"
2640
2641 * Another change.
2642 * A change.
2643
2644 --
2645"###,
2646 cl.to_string()
2647 );
2648
2649 assert_eq!(entry.maintainer(), None);
2650 assert_eq!(entry.email(), None);
2651 assert_eq!(entry.timestamp(), None);
2652 assert_eq!(entry.package(), None);
2653 assert_eq!(entry.version(), None);
2654 }
2655 }
2656
2657 #[cfg(test)]
2658 mod auto_add_change_tests {
2659 #[test]
2660 fn test_unreleased_existing() {
2661 let text = r#"lintian-brush (0.35) unstable; urgency=medium
2662
2663 * This line already existed.
2664
2665 [ Jane Example ]
2666 * And this one has an existing author.
2667
2668 --
2669"#;
2670 let mut cl = super::ChangeLog::read(text.as_bytes()).unwrap();
2671
2672 let entry = cl.iter().next().unwrap();
2673 assert_eq!(entry.package(), Some("lintian-brush".into()));
2674 assert_eq!(entry.is_unreleased(), Some(true));
2675
2676 let entry = cl
2677 .try_auto_add_change(
2678 &["* And this one is new."],
2679 ("Joe Example".to_string(), "joe@example.com".to_string()),
2680 None::<String>,
2681 None,
2682 )
2683 .unwrap();
2684
2685 assert_eq!(cl.iter().count(), 1);
2686
2687 assert_eq!(entry.package(), Some("lintian-brush".into()));
2688 assert_eq!(entry.is_unreleased(), Some(true));
2689 assert_eq!(
2690 entry.change_lines().collect::<Vec<_>>(),
2691 &[
2692 "* This line already existed.",
2693 "",
2694 "[ Jane Example ]",
2695 "* And this one has an existing author.",
2696 "",
2697 "[ Joe Example ]",
2698 "* And this one is new.",
2699 ]
2700 );
2701 }
2702 }
2703
2704 #[test]
2705 fn test_ensure_first_line() {
2706 let text = r#"lintian-brush (0.35) unstable; urgency=medium
2707
2708 * This line already existed.
2709
2710 [ Jane Example ]
2711 * And this one has an existing author.
2712
2713 --
2714"#;
2715 let cl = ChangeLog::read(text.as_bytes()).unwrap();
2716
2717 let entry = cl.iter().next().unwrap();
2718 assert_eq!(entry.package(), Some("lintian-brush".into()));
2719
2720 entry.ensure_first_line("* QA upload.");
2721 entry.ensure_first_line("* QA upload.");
2722
2723 assert_eq!(
2724 r#"lintian-brush (0.35) unstable; urgency=medium
2725
2726 * QA upload.
2727 * This line already existed.
2728
2729 [ Jane Example ]
2730 * And this one has an existing author.
2731
2732 --
2733"#,
2734 cl.to_string()
2735 );
2736 }
2737
2738 #[test]
2739 fn test_set_version() {
2740 let mut entry: Entry = r#"breezy (3.3.4-1) unstable; urgency=low
2741
2742 * New upstream release.
2743
2744 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
2745"#
2746 .parse()
2747 .unwrap();
2748
2749 entry.set_version(&"3.3.5-1".parse().unwrap());
2750
2751 assert_eq!(
2752 r#"breezy (3.3.5-1) unstable; urgency=low
2753
2754 * New upstream release.
2755
2756 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
2757"#,
2758 entry.to_string()
2759 );
2760 }
2761
2762 #[test]
2763 fn test_set_package() {
2764 let mut entry: Entry = r#"breezy (3.3.4-1) unstable; urgency=low
2765
2766 * New upstream release.
2767
2768 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
2769"#
2770 .parse()
2771 .unwrap();
2772
2773 entry.set_package("bzr".into());
2774
2775 assert_eq!(
2776 r#"bzr (3.3.4-1) unstable; urgency=low
2777
2778 * New upstream release.
2779
2780 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
2781"#,
2782 entry.to_string()
2783 );
2784 }
2785
2786 #[test]
2787 fn test_set_distributions() {
2788 let mut entry: Entry = r#"breezy (3.3.4-1) unstable; urgency=low
2789
2790 * New upstream release.
2791
2792 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
2793"#
2794 .parse()
2795 .unwrap();
2796
2797 entry.set_distributions(vec!["unstable".into(), "experimental".into()]);
2798
2799 assert_eq!(
2800 r#"breezy (3.3.4-1) unstable experimental; urgency=low
2801
2802 * New upstream release.
2803
2804 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
2805"#,
2806 entry.to_string()
2807 );
2808 }
2809
2810 #[test]
2811 fn test_set_distributions_no_existing() {
2812 let mut entry: Entry = r#"breezy (3.3.4-1); urgency=low
2813
2814 * New upstream release.
2815
2816 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
2817"#
2818 .parse()
2819 .unwrap();
2820
2821 entry.set_distributions(vec!["unstable".into()]);
2822
2823 assert!(entry.to_string().contains("unstable"));
2824 }
2825
2826 #[test]
2827 fn test_set_maintainer() {
2828 let mut entry: Entry = r#"breezy (3.3.4-1) unstable; urgency=low
2829
2830 * New upstream release.
2831
2832 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
2833"#
2834 .parse()
2835 .unwrap();
2836
2837 entry.set_maintainer(("Joe Example".into(), "joe@example.com".into()));
2838
2839 assert_eq!(
2840 r#"breezy (3.3.4-1) unstable; urgency=low
2841
2842 * New upstream release.
2843
2844 -- Joe Example <joe@example.com> Mon, 04 Sep 2023 18:13:45 -0500
2845"#,
2846 entry.to_string()
2847 );
2848 }
2849
2850 #[test]
2851 fn test_set_timestamp() {
2852 let mut entry: Entry = r#"breezy (3.3.4-1) unstable; urgency=low
2853
2854 * New upstream release.
2855
2856 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
2857"#
2858 .parse()
2859 .unwrap();
2860
2861 entry.set_timestamp("Mon, 04 Sep 2023 18:13:46 -0500".into());
2862
2863 assert_eq!(
2864 r#"breezy (3.3.4-1) unstable; urgency=low
2865
2866 * New upstream release.
2867
2868 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:46 -0500
2869"#,
2870 entry.to_string()
2871 );
2872 }
2873
2874 #[test]
2875 #[cfg(feature = "chrono")]
2876 fn test_set_datetime() {
2877 let mut entry: Entry = r#"breezy (3.3.4-1) unstable; urgency=low
2878
2879 * New upstream release.
2880
2881 -- Jelmer Vernooij <joe@example.com> Mon, 04 Sep 2023 18:13:45 -0500
2882"#
2883 .parse()
2884 .unwrap();
2885
2886 entry.set_datetime("2023-09-04T18:13:46-05:00".parse().unwrap());
2887
2888 assert_eq!(
2889 r#"breezy (3.3.4-1) unstable; urgency=low
2890
2891 * New upstream release.
2892
2893 -- Jelmer Vernooij <joe@example.com> Mon, 04 Sep 2023 18:13:46 -0500
2894"#,
2895 entry.to_string()
2896 );
2897 }
2898
2899 #[test]
2900 fn test_set_urgency() {
2901 let mut entry: Entry = r#"breezy (3.3.4-1) unstable; urgency=low
2902
2903 * New upstream release.
2904
2905 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
2906"#
2907 .parse()
2908 .unwrap();
2909
2910 entry.set_urgency(Urgency::Medium);
2911
2912 assert_eq!(
2913 r#"breezy (3.3.4-1) unstable; urgency=medium
2914
2915 * New upstream release.
2916
2917 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
2918"#,
2919 entry.to_string()
2920 );
2921 }
2922
2923 #[test]
2924 fn test_set_metadata() {
2925 let mut entry: Entry = r#"breezy (3.3.4-1) unstable; urgency=low
2926
2927 * New upstream release.
2928
2929 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
2930"#
2931 .parse()
2932 .unwrap();
2933
2934 entry.set_metadata("foo", "bar");
2935
2936 assert_eq!(
2937 r#"breezy (3.3.4-1) unstable; urgency=low foo=bar
2938
2939 * New upstream release.
2940
2941 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
2942"#,
2943 entry.to_string()
2944 );
2945 }
2946
2947 #[test]
2948 fn test_set_metadata_replace_existing() {
2949 let mut entry: Entry = r#"breezy (3.3.4-1) unstable; urgency=low foo=old
2950
2951 * New upstream release.
2952
2953 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
2954"#
2955 .parse()
2956 .unwrap();
2957
2958 entry.set_metadata("foo", "new");
2959
2960 assert_eq!(
2961 r#"breezy (3.3.4-1) unstable; urgency=low foo=new
2962
2963 * New upstream release.
2964
2965 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
2966"#,
2967 entry.to_string()
2968 );
2969 }
2970
2971 #[test]
2972 fn test_set_metadata_after_distributions() {
2973 let mut entry: Entry = r#"breezy (3.3.4-1) unstable experimental; urgency=low
2974
2975 * New upstream release.
2976
2977 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
2978"#
2979 .parse()
2980 .unwrap();
2981
2982 entry.set_metadata("foo", "bar");
2983
2984 assert_eq!(
2985 r#"breezy (3.3.4-1) unstable experimental; urgency=low foo=bar
2986
2987 * New upstream release.
2988
2989 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
2990"#,
2991 entry.to_string()
2992 );
2993 }
2994
2995 #[test]
2996 fn test_add_change_for_author() {
2997 let entry: Entry = r#"breezy (3.3.4-1) unstable; urgency=low
2998
2999 * New upstream release.
3000
3001 [ Jelmer Vernooij ]
3002 * A change by the maintainer.
3003
3004 -- Joe Example <joe@example.com> Mon, 04 Sep 2023 18:13:45 -0500
3005"#
3006 .parse()
3007 .unwrap();
3008
3009 entry
3010 .try_add_change_for_author(
3011 &["A change by the maintainer."],
3012 ("Jelmer Vernooij".into(), "jelmer@debian.org".into()),
3013 )
3014 .unwrap();
3015 }
3016
3017 #[test]
3018 fn test_changelog_from_entry_iter() {
3019 let text = r#"breezy (3.3.4-1) unstable; urgency=low
3020
3021 * New upstream release.
3022
3023 -- Jelmer Vernooij <jelmer@jelmer.uk> Mon, 04 Sep 2023 18:13:45 -0500
3024"#;
3025
3026 let entry: Entry = text.parse().unwrap();
3027
3028 let cl = std::iter::once(entry).collect::<ChangeLog>();
3029
3030 assert_eq!(cl.to_string(), text);
3031 }
3032
3033 #[test]
3034 fn test_pop_change_line() {
3035 let entry: Entry = r#"breezy (3.3.4-1) unstable; urgency=low
3036
3037 * New upstream release.
3038 * Fixed bug #123.
3039 * Added new feature.
3040
3041 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
3042"#
3043 .parse()
3044 .unwrap();
3045
3046 assert_eq!(
3048 entry.pop_change_line(),
3049 Some("* Added new feature.".to_string())
3050 );
3051 assert_eq!(
3052 entry.pop_change_line(),
3053 Some("* Fixed bug #123.".to_string())
3054 );
3055 assert_eq!(
3056 entry.pop_change_line(),
3057 Some("* New upstream release.".to_string())
3058 );
3059
3060 assert_eq!(entry.pop_change_line(), None);
3062 }
3063
3064 #[test]
3065 fn test_pop_change_line_empty_entry() {
3066 let entry: Entry = r#"breezy (3.3.4-1) unstable; urgency=low
3067
3068 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
3069"#
3070 .parse()
3071 .unwrap();
3072
3073 assert_eq!(entry.pop_change_line(), None);
3074 }
3075
3076 #[test]
3077 fn test_pop_change_line_empty_string() {
3078 let entry: Entry = r#"breezy (3.3.4-1) unstable; urgency=low
3079
3080 * Something
3081
3082 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
3083"#
3084 .parse()
3085 .unwrap();
3086
3087 entry.pop_change_line();
3088 entry.append_change_line("");
3089 assert_eq!(entry.pop_change_line(), None);
3091 }
3092
3093 #[test]
3094 fn test_append_change_line() {
3095 let entry: Entry = r#"breezy (3.3.4-1) unstable; urgency=low
3096
3097 * New upstream release.
3098
3099 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
3100"#
3101 .parse()
3102 .unwrap();
3103
3104 entry.append_change_line("* Fixed bug #456.");
3105
3106 assert_eq!(
3107 entry.to_string(),
3108 r#"breezy (3.3.4-1) unstable; urgency=low
3109
3110 * New upstream release.
3111 * Fixed bug #456.
3112
3113 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
3114"#
3115 );
3116 }
3117
3118 #[test]
3119 fn test_append_change_line_empty() {
3120 let entry: Entry = r#"breezy (3.3.4-1) unstable; urgency=low
3121
3122 * New upstream release.
3123
3124 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
3125"#
3126 .parse()
3127 .unwrap();
3128
3129 entry.append_change_line("");
3130
3131 let lines: Vec<String> = entry.change_lines().collect();
3132 assert_eq!(lines.len(), 1);
3134 assert_eq!(lines[0], "* New upstream release.".to_string());
3135 }
3136
3137 #[test]
3138 fn test_changelog_write_to_path() {
3139 use tempfile::NamedTempFile;
3140
3141 let changelog: ChangeLog = r#"breezy (3.3.4-1) unstable; urgency=low
3142
3143 * New upstream release.
3144
3145 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
3146"#
3147 .parse()
3148 .unwrap();
3149
3150 let temp_file = NamedTempFile::new().unwrap();
3151 let path = temp_file.path().to_path_buf();
3152
3153 changelog.write_to_path(&path).unwrap();
3154
3155 let contents = std::fs::read_to_string(&path).unwrap();
3156 assert_eq!(contents, changelog.to_string());
3157 }
3158
3159 #[test]
3160 fn test_changelog_into_iter() {
3161 let changelog: ChangeLog = r#"breezy (3.3.4-1) unstable; urgency=low
3162
3163 * New upstream release.
3164
3165 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
3166
3167breezy (3.3.3-1) unstable; urgency=low
3168
3169 * Previous release.
3170
3171 -- Jelmer Vernooij <jelmer@debian.org> Mon, 03 Sep 2023 18:13:45 -0500
3172"#
3173 .parse()
3174 .unwrap();
3175
3176 let entries: Vec<Entry> = changelog.into_iter().collect();
3177 assert_eq!(entries.len(), 2);
3178 }
3179
3180 #[test]
3181 fn test_set_version_no_existing() {
3182 let mut entry: Entry = r#"breezy (3.3.4-1) unstable; urgency=low
3183
3184 * New upstream release.
3185
3186 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
3187"#
3188 .parse()
3189 .unwrap();
3190
3191 entry.set_version(&"1.0.0".parse().unwrap());
3192
3193 assert!(entry.to_string().contains("(1.0.0)"));
3194 }
3195
3196 #[test]
3197 fn test_entry_footer_set_email_edge_cases() {
3198 let entry: Entry = r#"breezy (3.3.4-1) unstable; urgency=low
3199
3200 * New upstream release.
3201
3202 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
3203"#
3204 .parse()
3205 .unwrap();
3206
3207 assert_eq!(entry.email(), Some("jelmer@debian.org".to_string()));
3209 }
3210
3211 #[test]
3212 fn test_entry_footer_set_maintainer_edge_cases() {
3213 let mut entry: Entry = r#"breezy (3.3.4-1) unstable; urgency=low
3214
3215 * New upstream release.
3216
3217 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
3218"#
3219 .parse()
3220 .unwrap();
3221
3222 entry.set_maintainer(("New Maintainer".into(), "new@example.com".into()));
3224
3225 assert!(entry
3226 .to_string()
3227 .contains("New Maintainer <new@example.com>"));
3228 }
3229
3230 #[test]
3231 fn test_entry_footer_set_timestamp_edge_cases() {
3232 let mut entry: Entry = r#"breezy (3.3.4-1) unstable; urgency=low
3233
3234 * New upstream release.
3235
3236 -- Jelmer Vernooij <jelmer@debian.org>
3237"#
3238 .parse()
3239 .unwrap();
3240
3241 entry.set_timestamp("Mon, 04 Sep 2023 18:13:45 -0500".into());
3243
3244 assert!(entry
3245 .to_string()
3246 .contains("Mon, 04 Sep 2023 18:13:45 -0500"));
3247 }
3248
3249 #[test]
3250 fn test_parse_multiple_distributions_frozen_unstable() {
3251 const CHANGELOG: &str = r#"at (3.1.8-10) frozen unstable; urgency=high
3254
3255 * Suidunregister /usr/bin (closes: Bug#59421).
3256
3257 -- Siggy Brentrup <bsb@winnegan.de> Mon, 3 Apr 2000 13:56:47 +0200
3258"#;
3259
3260 let parsed = parse(CHANGELOG);
3261 assert_eq!(parsed.errors(), &Vec::<String>::new());
3262
3263 let root = parsed.tree();
3264 let entries: Vec<_> = root.iter().collect();
3265 assert_eq!(entries.len(), 1);
3266
3267 let entry = &entries[0];
3268 assert_eq!(entry.package(), Some("at".into()));
3269 assert_eq!(entry.version(), Some("3.1.8-10".parse().unwrap()));
3270 assert_eq!(
3271 entry.distributions(),
3272 Some(vec!["frozen".into(), "unstable".into()])
3273 );
3274 }
3275
3276 #[test]
3277 fn test_parse_old_metadata_format_with_comma() {
3278 const CHANGELOG: &str = r#"at (3.1.8-9) frozen unstable; urgency=low, closes=53715 56047 56607 55560 55514
3281
3282 * Added SIGCHLD handler to release zombies (closes 53715 56047 56607)
3283
3284 -- Siggy Brentrup <bsb@winnegan.de> Sun, 30 Jan 2000 22:00:46 +0100
3285"#;
3286
3287 let parsed = parse(CHANGELOG);
3288
3289 if !parsed.errors().is_empty() {
3291 eprintln!("Parse errors: {:?}", parsed.errors());
3292 }
3293 assert_eq!(parsed.errors(), &Vec::<String>::new());
3294
3295 let root = parsed.tree();
3296 let entries: Vec<_> = root.iter().collect();
3297 assert_eq!(entries.len(), 1);
3298
3299 let entry = &entries[0];
3300 assert_eq!(entry.package(), Some("at".into()));
3301 assert_eq!(entry.version(), Some("3.1.8-9".parse().unwrap()));
3302 assert_eq!(
3303 entry.distributions(),
3304 Some(vec!["frozen".into(), "unstable".into()])
3305 );
3306 assert_eq!(entry.urgency(), Some(Urgency::Low));
3307
3308 let header = entry.header().unwrap();
3310 let metadata: Vec<(String, String)> = header.metadata().collect();
3311
3312 assert_eq!(metadata.len(), 2);
3314 assert!(metadata.iter().any(|(k, v)| k == "urgency" && v == "low"));
3315
3316 let closes_value = metadata
3318 .iter()
3319 .find(|(k, _)| k == "closes")
3320 .map(|(_, v)| v)
3321 .expect("closes metadata should exist");
3322
3323 assert_eq!(closes_value, "53715 56047 56607 55560 55514");
3324 }
3325
3326 #[test]
3327 fn test_entry_iter_changes_by_author() {
3328 let entry: Entry = r#"breezy (3.3.4-1) unstable; urgency=low
3329
3330 [ Author 1 ]
3331 * Change by Author 1
3332
3333 [ Author 2 ]
3334 * Change by Author 2
3335 * Another change by Author 2
3336
3337 * Unattributed change
3338
3339 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
3340"#
3341 .parse()
3342 .unwrap();
3343
3344 let changes = entry.iter_changes_by_author();
3345
3346 assert_eq!(changes.len(), 3);
3347
3348 assert_eq!(changes[0].0, Some("Author 1".to_string()));
3349 assert_eq!(changes[0].2, vec!["* Change by Author 1".to_string()]);
3350
3351 assert_eq!(changes[1].0, Some("Author 2".to_string()));
3352 assert_eq!(
3353 changes[1].2,
3354 vec![
3355 "* Change by Author 2".to_string(),
3356 "* Another change by Author 2".to_string()
3357 ]
3358 );
3359
3360 assert_eq!(changes[2].0, None);
3361 assert_eq!(changes[2].2, vec!["* Unattributed change".to_string()]);
3362 }
3363
3364 #[test]
3365 fn test_entry_get_authors() {
3366 let entry: Entry = r#"breezy (3.3.4-1) unstable; urgency=low
3367
3368 [ Author 1 ]
3369 * Change by Author 1
3370
3371 [ Author 2 ]
3372 * Change by Author 2
3373
3374 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
3375"#
3376 .parse()
3377 .unwrap();
3378
3379 let authors = entry.get_authors();
3380
3381 assert_eq!(authors.len(), 2);
3382 assert!(authors.contains("Author 1"));
3383 assert!(authors.contains("Author 2"));
3384 assert!(!authors.contains("Jelmer Vernooij"));
3386 }
3387
3388 #[test]
3389 fn test_entry_get_maintainer_identity() {
3390 let entry: Entry = r#"breezy (3.3.4-1) unstable; urgency=low
3391
3392 * New upstream release.
3393
3394 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
3395"#
3396 .parse()
3397 .unwrap();
3398
3399 let identity = entry.get_maintainer_identity().unwrap();
3400
3401 assert_eq!(identity.name, "Jelmer Vernooij");
3402 assert_eq!(identity.email, "jelmer@debian.org");
3403 }
3404
3405 #[test]
3406 fn test_entry_get_maintainer_identity_missing() {
3407 let entry: Entry = r#"breezy (3.3.4-1) unstable; urgency=low
3408
3409 * New upstream release.
3410
3411"#
3412 .parse()
3413 .unwrap();
3414
3415 let identity = entry.get_maintainer_identity();
3416
3417 assert!(identity.is_none());
3418 }
3419
3420 #[test]
3421 fn test_changelog_iter_by_author() {
3422 let changelog: ChangeLog = r#"breezy (3.3.4-1) unstable; urgency=low
3423
3424 * New upstream release.
3425
3426 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
3427
3428breezy (3.3.3-1) unstable; urgency=low
3429
3430 * Bug fix release.
3431
3432 -- Jane Doe <jane@example.com> Sun, 03 Sep 2023 17:12:30 -0500
3433
3434breezy (3.3.2-1) unstable; urgency=low
3435
3436 * Another release.
3437
3438 -- Jelmer Vernooij <jelmer@debian.org> Sat, 02 Sep 2023 16:11:15 -0500
3439"#
3440 .parse()
3441 .unwrap();
3442
3443 let authors: Vec<(String, String, Vec<Entry>)> = changelog.iter_by_author().collect();
3444
3445 assert_eq!(authors.len(), 2);
3446 assert_eq!(authors[0].0, "Jane Doe");
3447 assert_eq!(authors[0].1, "jane@example.com");
3448 assert_eq!(authors[0].2.len(), 1);
3449 assert_eq!(authors[1].0, "Jelmer Vernooij");
3450 assert_eq!(authors[1].1, "jelmer@debian.org");
3451 assert_eq!(authors[1].2.len(), 2);
3452 }
3453
3454 #[test]
3455 fn test_changelog_get_all_authors() {
3456 let changelog: ChangeLog = r#"breezy (3.3.4-1) unstable; urgency=low
3457
3458 [ Contributor 1 ]
3459 * Contribution
3460
3461 * Main change
3462
3463 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
3464
3465breezy (3.3.3-1) unstable; urgency=low
3466
3467 * Bug fix release.
3468
3469 -- Jane Doe <jane@example.com> Sun, 03 Sep 2023 17:12:30 -0500
3470"#
3471 .parse()
3472 .unwrap();
3473
3474 let authors = changelog.get_all_authors();
3475
3476 assert_eq!(authors.len(), 3);
3477
3478 let author_names: std::collections::HashSet<String> = authors
3479 .iter()
3480 .map(|identity| identity.name.clone())
3481 .collect();
3482
3483 assert!(author_names.contains("Jelmer Vernooij"));
3484 assert!(author_names.contains("Jane Doe"));
3485 assert!(author_names.contains("Contributor 1"));
3486 }
3487
3488 #[test]
3489 fn test_add_changes_for_author_no_existing_sections() {
3490 let entry: Entry = r#"breezy (3.3.4-1) unstable; urgency=low
3491
3492 * Existing change
3493
3494 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
3495"#
3496 .parse()
3497 .unwrap();
3498
3499 entry
3500 .try_add_changes_for_author("Alice", vec!["* Alice's change"])
3501 .unwrap();
3502
3503 let lines: Vec<_> = entry.change_lines().collect();
3504
3505 assert!(lines.iter().any(|l| l.contains("[ Jelmer Vernooij ]")));
3507 assert!(lines.iter().any(|l| l.contains("[ Alice ]")));
3509 assert!(lines.iter().any(|l| l.contains("Existing change")));
3511 assert!(lines.iter().any(|l| l.contains("Alice's change")));
3512 }
3513
3514 #[test]
3515 fn test_add_changes_for_author_with_existing_sections() {
3516 let entry: Entry = r#"breezy (3.3.4-1) unstable; urgency=low
3517
3518 [ Author 1 ]
3519 * Change by Author 1
3520
3521 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
3522"#
3523 .parse()
3524 .unwrap();
3525
3526 entry
3527 .try_add_changes_for_author("Alice", vec!["* Alice's new change"])
3528 .unwrap();
3529
3530 let lines: Vec<_> = entry.change_lines().collect();
3531
3532 assert!(lines.iter().any(|l| l.contains("[ Author 1 ]")));
3534 assert!(lines.iter().any(|l| l.contains("[ Alice ]")));
3536 assert!(lines.iter().any(|l| l.contains("Change by Author 1")));
3538 assert!(lines.iter().any(|l| l.contains("Alice's new change")));
3539 }
3540
3541 #[test]
3542 fn test_add_changes_for_author_same_author() {
3543 let entry: Entry = r#"breezy (3.3.4-1) unstable; urgency=low
3544
3545 [ Alice ]
3546 * First change
3547
3548 -- Jelmer Vernooij <jelmer@debian.org> Mon, 04 Sep 2023 18:13:45 -0500
3549"#
3550 .parse()
3551 .unwrap();
3552
3553 entry
3554 .try_add_changes_for_author("Alice", vec!["* Second change"])
3555 .unwrap();
3556
3557 let lines: Vec<_> = entry.change_lines().collect();
3558
3559 let alice_count = lines.iter().filter(|l| l.contains("[ Alice ]")).count();
3561 assert_eq!(alice_count, 1);
3562
3563 assert!(lines.iter().any(|l| l.contains("First change")));
3565 assert!(lines.iter().any(|l| l.contains("Second change")));
3566 }
3567
3568 #[test]
3569 fn test_datetime_with_incorrect_day_of_week() {
3570 let entry: Entry = r#"blah (0.1-2) UNRELEASED; urgency=medium
3573
3574 * New release.
3575
3576 -- Jelmer Vernooij <jelmer@debian.org> Mon, 22 Mar 2011 16:47:42 +0000
3577"#
3578 .parse()
3579 .unwrap();
3580
3581 assert_eq!(
3583 entry.timestamp(),
3584 Some("Mon, 22 Mar 2011 16:47:42 +0000".into())
3585 );
3586
3587 let datetime = entry.datetime();
3589 assert!(
3590 datetime.is_some(),
3591 "datetime() should not return None for timestamp with incorrect day-of-week"
3592 );
3593 assert_eq!(datetime.unwrap().to_rfc3339(), "2011-03-22T16:47:42+00:00");
3594 }
3595
3596 #[test]
3597 fn test_line_col() {
3598 let text = r#"foo (1.0-1) unstable; urgency=low
3599
3600 * First change
3601
3602 -- Maintainer <email@example.com> Mon, 01 Jan 2024 12:00:00 +0000
3603
3604bar (2.0-1) experimental; urgency=high
3605
3606 * Second change
3607 * Third change
3608
3609 -- Another <another@example.com> Tue, 02 Jan 2024 13:00:00 +0000
3610"#;
3611 let changelog = text.parse::<ChangeLog>().unwrap();
3612
3613 assert_eq!(changelog.line(), 0);
3615 assert_eq!(changelog.column(), 0);
3616 assert_eq!(changelog.line_col(), (0, 0));
3617
3618 let entries: Vec<_> = changelog.iter().collect();
3620 assert_eq!(entries.len(), 2);
3621
3622 assert_eq!(entries[0].line(), 0);
3624 assert_eq!(entries[0].column(), 0);
3625 assert_eq!(entries[0].line_col(), (0, 0));
3626
3627 assert_eq!(entries[1].line(), 6);
3629 assert_eq!(entries[1].column(), 0);
3630 assert_eq!(entries[1].line_col(), (6, 0));
3631
3632 let header = entries[0].header().unwrap();
3634 assert_eq!(header.line(), 0);
3635 assert_eq!(header.column(), 0);
3636
3637 let body = entries[0].body().unwrap();
3638 assert_eq!(body.line(), 2); let footer = entries[0].footer().unwrap();
3641 assert_eq!(footer.line(), 4); let maintainer = entries[0].maintainer_node().unwrap();
3645 assert_eq!(maintainer.line(), 4); let timestamp = entries[0].timestamp_node().unwrap();
3648 assert_eq!(timestamp.line(), 4); let header2 = entries[1].header().unwrap();
3652 assert_eq!(header2.line(), 6);
3653
3654 let footer2 = entries[1].footer().unwrap();
3655 assert_eq!(footer2.line(), 11);
3656 }
3657}