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