1use crate::{
37 lex::lex,
38 lex::SyntaxKind::{self, *},
39 Indentation,
40};
41use rowan::ast::AstNode;
42use std::path::Path;
43use std::str::FromStr;
44
45#[derive(Debug, Clone, PartialEq, Eq, Hash)]
47pub struct PositionedParseError {
48 pub message: String,
50 pub range: rowan::TextRange,
52 pub code: Option<String>,
54}
55
56impl std::fmt::Display for PositionedParseError {
57 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58 write!(f, "{}", self.message)
59 }
60}
61
62impl std::error::Error for PositionedParseError {}
63
64#[derive(Debug, Clone, PartialEq, Eq, Hash)]
66pub struct ParseError(pub Vec<String>);
67
68impl std::fmt::Display for ParseError {
69 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
70 for err in &self.0 {
71 writeln!(f, "{}", err)?;
72 }
73 Ok(())
74 }
75}
76
77impl std::error::Error for ParseError {}
78
79#[derive(Debug)]
81pub enum Error {
82 ParseError(ParseError),
84
85 IoError(std::io::Error),
87
88 InvalidValue(String),
90}
91
92impl std::fmt::Display for Error {
93 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
94 match &self {
95 Error::ParseError(err) => write!(f, "{}", err),
96 Error::IoError(err) => write!(f, "{}", err),
97 Error::InvalidValue(msg) => write!(f, "Invalid value: {}", msg),
98 }
99 }
100}
101
102impl From<ParseError> for Error {
103 fn from(err: ParseError) -> Self {
104 Self::ParseError(err)
105 }
106}
107
108impl From<std::io::Error> for Error {
109 fn from(err: std::io::Error) -> Self {
110 Self::IoError(err)
111 }
112}
113
114impl std::error::Error for Error {}
115
116#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
120pub enum Lang {}
121impl rowan::Language for Lang {
122 type Kind = SyntaxKind;
123 fn kind_from_raw(raw: rowan::SyntaxKind) -> Self::Kind {
124 unsafe { std::mem::transmute::<u16, SyntaxKind>(raw.0) }
125 }
126 fn kind_to_raw(kind: Self::Kind) -> rowan::SyntaxKind {
127 kind.into()
128 }
129}
130
131use rowan::GreenNode;
134
135use rowan::GreenNodeBuilder;
139
140pub(crate) struct Parse {
143 pub(crate) green_node: GreenNode,
144 #[allow(unused)]
145 pub(crate) errors: Vec<String>,
146 pub(crate) positioned_errors: Vec<PositionedParseError>,
147}
148
149pub(crate) fn parse(text: &str) -> Parse {
150 struct Parser<'a> {
151 tokens: Vec<(SyntaxKind, &'a str)>,
154 builder: GreenNodeBuilder<'static>,
156 errors: Vec<String>,
159 positioned_errors: Vec<PositionedParseError>,
161 token_positions: Vec<(SyntaxKind, rowan::TextSize, rowan::TextSize)>,
163 current_token_index: usize,
165 }
166
167 impl<'a> Parser<'a> {
168 fn skip_to_paragraph_boundary(&mut self) {
170 while self.current().is_some() {
171 match self.current() {
172 Some(NEWLINE) => {
173 self.bump();
174 if self.at_paragraph_start() {
176 break;
177 }
178 }
179 _ => {
180 self.bump();
181 }
182 }
183 }
184 }
185
186 fn at_paragraph_start(&self) -> bool {
188 match self.current() {
189 Some(KEY) => true,
190 Some(COMMENT) => true,
191 None => true, _ => false,
193 }
194 }
195
196 fn recover_entry(&mut self) {
198 while self.current().is_some() && self.current() != Some(NEWLINE) {
200 self.bump();
201 }
202 if self.current() == Some(NEWLINE) {
204 self.bump();
205 }
206 }
207 fn parse_entry(&mut self) {
208 while self.current() == Some(COMMENT) {
210 self.bump();
211
212 match self.current() {
213 Some(NEWLINE) => {
214 self.bump();
215 }
216 None => {
217 return;
218 }
219 Some(g) => {
220 self.builder.start_node(ERROR.into());
221 self.add_positioned_error(
222 format!("expected newline after comment, got {g:?}"),
223 Some("unexpected_token_after_comment".to_string()),
224 );
225 self.bump();
226 self.builder.finish_node();
227 self.recover_entry();
228 return;
229 }
230 }
231 }
232
233 self.builder.start_node(ENTRY.into());
234 let mut entry_has_errors = false;
235
236 if self.current() == Some(KEY) {
238 self.bump();
239 self.skip_ws();
240 } else {
241 entry_has_errors = true;
242 self.builder.start_node(ERROR.into());
243
244 match self.current() {
246 Some(VALUE) | Some(WHITESPACE) => {
247 self.add_positioned_error(
248 "field name cannot start with whitespace or special characters"
249 .to_string(),
250 Some("invalid_field_name".to_string()),
251 );
252 while self.current() == Some(VALUE) || self.current() == Some(WHITESPACE) {
254 self.bump();
255 }
256 }
257 Some(COLON) => {
258 self.add_positioned_error(
259 "field name missing before colon".to_string(),
260 Some("missing_field_name".to_string()),
261 );
262 }
263 Some(NEWLINE) => {
264 self.add_positioned_error(
265 "empty line where field expected".to_string(),
266 Some("empty_field_line".to_string()),
267 );
268 self.builder.finish_node();
269 self.builder.finish_node();
270 return;
271 }
272 _ => {
273 self.add_positioned_error(
274 format!("expected field name, got {:?}", self.current()),
275 Some("missing_key".to_string()),
276 );
277 if self.current().is_some() {
278 self.bump();
279 }
280 }
281 }
282 self.builder.finish_node();
283 }
284
285 if self.current() == Some(COLON) {
287 self.bump();
288 self.skip_ws();
289 } else {
290 entry_has_errors = true;
291 self.builder.start_node(ERROR.into());
292
293 match self.current() {
295 Some(VALUE) => {
296 self.add_positioned_error(
297 "missing colon ':' after field name".to_string(),
298 Some("missing_colon".to_string()),
299 );
300 }
302 Some(NEWLINE) => {
303 self.add_positioned_error(
304 "field name without value (missing colon and value)".to_string(),
305 Some("incomplete_field".to_string()),
306 );
307 self.builder.finish_node();
308 self.builder.finish_node();
309 return;
310 }
311 Some(KEY) => {
312 self.add_positioned_error(
313 "field name followed by another field name (missing colon and value)"
314 .to_string(),
315 Some("consecutive_field_names".to_string()),
316 );
317 self.builder.finish_node();
319 self.builder.finish_node();
320 return;
321 }
322 _ => {
323 self.add_positioned_error(
324 format!("expected colon ':', got {:?}", self.current()),
325 Some("missing_colon".to_string()),
326 );
327 if self.current().is_some() {
328 self.bump();
329 }
330 }
331 }
332 self.builder.finish_node();
333 }
334
335 loop {
337 while self.current() == Some(WHITESPACE) || self.current() == Some(VALUE) {
338 self.bump();
339 }
340
341 match self.current() {
342 None => {
343 break;
344 }
345 Some(NEWLINE) => {
346 self.bump();
347 }
348 Some(KEY) => {
349 break;
351 }
352 Some(g) => {
353 self.builder.start_node(ERROR.into());
354 self.add_positioned_error(
355 format!("unexpected token in field value: {g:?}"),
356 Some("unexpected_value_token".to_string()),
357 );
358 self.bump();
359 self.builder.finish_node();
360 }
361 }
362
363 if self.current() == Some(INDENT) {
365 self.bump();
366 self.skip_ws();
367
368 if self.current() == Some(NEWLINE) || self.current().is_none() {
372 self.builder.start_node(ERROR.into());
373 self.add_positioned_error(
374 "empty continuation line (line with only whitespace)".to_string(),
375 Some("empty_continuation_line".to_string()),
376 );
377 self.builder.finish_node();
378 break;
379 }
380 } else {
381 break;
382 }
383 }
384
385 self.builder.finish_node();
386
387 if entry_has_errors && !self.at_paragraph_start() && self.current().is_some() {
389 self.recover_entry();
390 }
391 }
392
393 fn parse_paragraph(&mut self) {
394 self.builder.start_node(PARAGRAPH.into());
395
396 let mut consecutive_errors = 0;
397 const MAX_CONSECUTIVE_ERRORS: usize = 5;
398
399 while self.current() != Some(NEWLINE) && self.current().is_some() {
400 let error_count_before = self.positioned_errors.len();
401
402 if self.current() == Some(KEY) || self.current() == Some(COMMENT) {
404 self.parse_entry();
405
406 if self.positioned_errors.len() == error_count_before {
408 consecutive_errors = 0;
409 } else {
410 consecutive_errors += 1;
411 }
412 } else {
413 consecutive_errors += 1;
415
416 self.builder.start_node(ERROR.into());
417 match self.current() {
418 Some(VALUE) => {
419 self.add_positioned_error(
420 "orphaned text without field name".to_string(),
421 Some("orphaned_text".to_string()),
422 );
423 while self.current() == Some(VALUE)
425 || self.current() == Some(WHITESPACE)
426 {
427 self.bump();
428 }
429 }
430 Some(COLON) => {
431 self.add_positioned_error(
432 "orphaned colon without field name".to_string(),
433 Some("orphaned_colon".to_string()),
434 );
435 self.bump();
436 }
437 Some(INDENT) => {
438 self.add_positioned_error(
439 "unexpected indentation without field".to_string(),
440 Some("unexpected_indent".to_string()),
441 );
442 self.bump();
443 }
444 _ => {
445 self.add_positioned_error(
446 format!(
447 "unexpected token at paragraph level: {:?}",
448 self.current()
449 ),
450 Some("unexpected_paragraph_token".to_string()),
451 );
452 self.bump();
453 }
454 }
455 self.builder.finish_node();
456 }
457
458 if consecutive_errors >= MAX_CONSECUTIVE_ERRORS {
460 self.add_positioned_error(
461 "too many consecutive parse errors, skipping to next paragraph".to_string(),
462 Some("parse_recovery".to_string()),
463 );
464 self.skip_to_paragraph_boundary();
465 break;
466 }
467 }
468
469 self.builder.finish_node();
470 }
471
472 fn parse(mut self) -> Parse {
473 self.builder.start_node(ROOT.into());
475 while self.current().is_some() {
476 self.skip_ws_and_newlines();
477 if self.current().is_some() {
478 self.parse_paragraph();
479 }
480 }
481 self.skip_ws_and_newlines();
483 self.builder.finish_node();
485
486 Parse {
488 green_node: self.builder.finish(),
489 errors: self.errors,
490 positioned_errors: self.positioned_errors,
491 }
492 }
493 fn bump(&mut self) {
495 let (kind, text) = self.tokens.pop().unwrap();
496 self.builder.token(kind.into(), text);
497 if self.current_token_index > 0 {
498 self.current_token_index -= 1;
499 }
500 }
501 fn current(&self) -> Option<SyntaxKind> {
503 self.tokens.last().map(|(kind, _)| *kind)
504 }
505
506 fn add_positioned_error(&mut self, message: String, code: Option<String>) {
508 let range = if self.current_token_index < self.token_positions.len() {
509 let (_, start, end) = self.token_positions[self.current_token_index];
510 rowan::TextRange::new(start, end)
511 } else {
512 let end = self
514 .token_positions
515 .last()
516 .map(|(_, _, end)| *end)
517 .unwrap_or_else(|| rowan::TextSize::from(0));
518 rowan::TextRange::new(end, end)
519 };
520
521 self.positioned_errors.push(PositionedParseError {
522 message: message.clone(),
523 range,
524 code,
525 });
526 self.errors.push(message);
527 }
528 fn skip_ws(&mut self) {
529 while self.current() == Some(WHITESPACE) || self.current() == Some(COMMENT) {
530 self.bump()
531 }
532 }
533 fn skip_ws_and_newlines(&mut self) {
534 while self.current() == Some(WHITESPACE)
535 || self.current() == Some(COMMENT)
536 || self.current() == Some(NEWLINE)
537 {
538 self.builder.start_node(EMPTY_LINE.into());
539 while self.current() != Some(NEWLINE) && self.current().is_some() {
540 self.bump();
541 }
542 if self.current() == Some(NEWLINE) {
543 self.bump();
544 }
545 self.builder.finish_node();
546 }
547 }
548 }
549
550 let mut tokens = lex(text).collect::<Vec<_>>();
551
552 let mut token_positions = Vec::new();
554 let mut position = rowan::TextSize::from(0);
555 for (kind, text) in &tokens {
556 let start = position;
557 let end = start + rowan::TextSize::of(*text);
558 token_positions.push((*kind, start, end));
559 position = end;
560 }
561
562 tokens.reverse();
564 let current_token_index = tokens.len().saturating_sub(1);
565
566 Parser {
567 tokens,
568 builder: GreenNodeBuilder::new(),
569 errors: Vec::new(),
570 positioned_errors: Vec::new(),
571 token_positions,
572 current_token_index,
573 }
574 .parse()
575}
576
577type SyntaxNode = rowan::SyntaxNode<Lang>;
583#[allow(unused)]
584type SyntaxToken = rowan::SyntaxToken<Lang>;
585#[allow(unused)]
586type SyntaxElement = rowan::NodeOrToken<SyntaxNode, SyntaxToken>;
587
588impl Parse {
589 #[cfg(test)]
590 fn syntax(&self) -> SyntaxNode {
591 SyntaxNode::new_root(self.green_node.clone())
592 }
593
594 fn root_mut(&self) -> Deb822 {
595 Deb822::cast(SyntaxNode::new_root_mut(self.green_node.clone())).unwrap()
596 }
597}
598
599fn line_col_at_offset(node: &SyntaxNode, offset: rowan::TextSize) -> (usize, usize) {
602 let root = node.ancestors().last().unwrap_or_else(|| node.clone());
603 let mut line = 0;
604 let mut last_newline_offset = rowan::TextSize::from(0);
605
606 for element in root.preorder_with_tokens() {
607 if let rowan::WalkEvent::Enter(rowan::NodeOrToken::Token(token)) = element {
608 if token.text_range().start() >= offset {
609 break;
610 }
611
612 for (idx, _) in token.text().match_indices('\n') {
614 line += 1;
615 last_newline_offset =
616 token.text_range().start() + rowan::TextSize::from((idx + 1) as u32);
617 }
618 }
619 }
620
621 let column: usize = (offset - last_newline_offset).into();
622 (line, column)
623}
624
625macro_rules! ast_node {
626 ($ast:ident, $kind:ident) => {
627 #[doc = "An AST node representing a `"]
628 #[doc = stringify!($ast)]
629 #[doc = "`."]
630 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
631 #[repr(transparent)]
632 pub struct $ast(SyntaxNode);
633 impl $ast {
634 #[allow(unused)]
635 fn cast(node: SyntaxNode) -> Option<Self> {
636 if node.kind() == $kind {
637 Some(Self(node))
638 } else {
639 None
640 }
641 }
642
643 pub fn line(&self) -> usize {
645 line_col_at_offset(&self.0, self.0.text_range().start()).0
646 }
647
648 pub fn column(&self) -> usize {
650 line_col_at_offset(&self.0, self.0.text_range().start()).1
651 }
652
653 pub fn line_col(&self) -> (usize, usize) {
656 line_col_at_offset(&self.0, self.0.text_range().start())
657 }
658 }
659
660 impl AstNode for $ast {
661 type Language = Lang;
662
663 fn can_cast(kind: SyntaxKind) -> bool {
664 kind == $kind
665 }
666
667 fn cast(syntax: SyntaxNode) -> Option<Self> {
668 Self::cast(syntax)
669 }
670
671 fn syntax(&self) -> &SyntaxNode {
672 &self.0
673 }
674 }
675
676 impl std::fmt::Display for $ast {
677 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
678 write!(f, "{}", self.0.text())
679 }
680 }
681 };
682}
683
684ast_node!(Deb822, ROOT);
685ast_node!(Paragraph, PARAGRAPH);
686ast_node!(Entry, ENTRY);
687
688impl Default for Deb822 {
689 fn default() -> Self {
690 Self::new()
691 }
692}
693
694impl Deb822 {
695 pub fn new() -> Deb822 {
697 let mut builder = GreenNodeBuilder::new();
698
699 builder.start_node(ROOT.into());
700 builder.finish_node();
701 Deb822(SyntaxNode::new_root_mut(builder.finish()))
702 }
703
704 pub fn parse(text: &str) -> crate::Parse<Deb822> {
706 crate::Parse::parse_deb822(text)
707 }
708
709 #[must_use]
725 pub fn wrap_and_sort(
726 &self,
727 sort_paragraphs: Option<&dyn Fn(&Paragraph, &Paragraph) -> std::cmp::Ordering>,
728 wrap_and_sort_paragraph: Option<&dyn Fn(&Paragraph) -> Paragraph>,
729 ) -> Deb822 {
730 let mut builder = GreenNodeBuilder::new();
731 builder.start_node(ROOT.into());
732 let mut current = vec![];
733 let mut paragraphs = vec![];
734 for c in self.0.children_with_tokens() {
735 match c.kind() {
736 PARAGRAPH => {
737 paragraphs.push((
738 current,
739 Paragraph::cast(c.as_node().unwrap().clone()).unwrap(),
740 ));
741 current = vec![];
742 }
743 COMMENT | ERROR => {
744 current.push(c);
745 }
746 EMPTY_LINE => {
747 current.extend(
748 c.as_node()
749 .unwrap()
750 .children_with_tokens()
751 .skip_while(|c| matches!(c.kind(), EMPTY_LINE | NEWLINE | WHITESPACE)),
752 );
753 }
754 _ => {}
755 }
756 }
757 if let Some(sort_paragraph) = sort_paragraphs {
758 paragraphs.sort_by(|a, b| {
759 let a_key = &a.1;
760 let b_key = &b.1;
761 sort_paragraph(a_key, b_key)
762 });
763 }
764
765 for (i, paragraph) in paragraphs.into_iter().enumerate() {
766 if i > 0 {
767 builder.start_node(EMPTY_LINE.into());
768 builder.token(NEWLINE.into(), "\n");
769 builder.finish_node();
770 }
771 for c in paragraph.0.into_iter() {
772 builder.token(c.kind().into(), c.as_token().unwrap().text());
773 }
774 let new_paragraph = if let Some(ref ws) = wrap_and_sort_paragraph {
775 ws(¶graph.1)
776 } else {
777 paragraph.1
778 };
779 inject(&mut builder, new_paragraph.0);
780 }
781
782 for c in current {
783 builder.token(c.kind().into(), c.as_token().unwrap().text());
784 }
785
786 builder.finish_node();
787 Self(SyntaxNode::new_root_mut(builder.finish()))
788 }
789
790 pub fn normalize_field_spacing(&mut self) -> bool {
809 let mut any_changed = false;
810
811 let mut paragraphs: Vec<_> = self.paragraphs().collect();
813
814 for para in &mut paragraphs {
816 if para.normalize_field_spacing() {
817 any_changed = true;
818 }
819 }
820
821 any_changed
822 }
823
824 pub fn paragraphs(&self) -> impl Iterator<Item = Paragraph> {
826 self.0.children().filter_map(Paragraph::cast)
827 }
828
829 fn convert_index(&self, index: usize) -> Option<usize> {
831 let mut current_pos = 0usize;
832 if index == 0 {
833 return Some(0);
834 }
835 for (i, node) in self.0.children_with_tokens().enumerate() {
836 if node.kind() == PARAGRAPH {
837 if current_pos == index {
838 return Some(i);
839 }
840 current_pos += 1;
841 }
842 }
843
844 None
845 }
846
847 fn delete_trailing_space(&self, start: usize) {
849 for (i, node) in self.0.children_with_tokens().enumerate() {
850 if i < start {
851 continue;
852 }
853 if node.kind() != EMPTY_LINE {
854 return;
855 }
856 self.0.splice_children(start..start + 1, []);
859 }
860 }
861
862 fn insert_empty_paragraph(&mut self, index: Option<usize>) -> Paragraph {
864 let paragraph = Paragraph::new();
865 let mut to_insert = vec![];
866 if self.0.children().count() > 0 {
867 let mut builder = GreenNodeBuilder::new();
868 builder.start_node(EMPTY_LINE.into());
869 builder.token(NEWLINE.into(), "\n");
870 builder.finish_node();
871 to_insert.push(SyntaxNode::new_root_mut(builder.finish()).into());
872 }
873 to_insert.push(paragraph.0.clone().into());
874 let insertion_point = match index {
875 Some(i) => {
876 if to_insert.len() > 1 {
877 to_insert.swap(0, 1);
878 }
879 i
880 }
881 None => self.0.children().count(),
882 };
883 self.0
884 .splice_children(insertion_point..insertion_point, to_insert);
885 paragraph
886 }
887
888 pub fn insert_paragraph(&mut self, index: usize) -> Paragraph {
908 self.insert_empty_paragraph(self.convert_index(index))
909 }
910
911 pub fn remove_paragraph(&mut self, index: usize) {
929 if let Some(index) = self.convert_index(index) {
930 self.0.splice_children(index..index + 1, []);
931 self.delete_trailing_space(index);
932 }
933 }
934
935 pub fn move_paragraph(&mut self, from_index: usize, to_index: usize) {
955 if from_index == to_index {
956 return;
957 }
958
959 let paragraph_count = self.paragraphs().count();
961 if from_index >= paragraph_count || to_index >= paragraph_count {
962 return;
963 }
964
965 let paragraph_to_move = self.paragraphs().nth(from_index).unwrap().0.clone();
967
968 let from_physical = self.convert_index(from_index).unwrap();
970
971 let mut start_idx = from_physical;
973 if from_physical > 0 {
974 if let Some(prev_node) = self.0.children_with_tokens().nth(from_physical - 1) {
975 if prev_node.kind() == EMPTY_LINE {
976 start_idx = from_physical - 1;
977 }
978 }
979 }
980
981 self.0.splice_children(start_idx..from_physical + 1, []);
983 self.delete_trailing_space(start_idx);
984
985 let insert_at = if to_index > from_index {
989 let target_idx = to_index - 1;
992 if let Some(target_physical) = self.convert_index(target_idx) {
993 target_physical + 1
994 } else {
995 self.0.children().count()
997 }
998 } else {
999 if let Some(target_physical) = self.convert_index(to_index) {
1002 target_physical
1003 } else {
1004 self.0.children().count()
1005 }
1006 };
1007
1008 let mut to_insert = vec![];
1010
1011 let needs_empty_line_before = if insert_at == 0 {
1013 false
1015 } else if insert_at > 0 {
1016 if let Some(node_at_insert) = self.0.children_with_tokens().nth(insert_at - 1) {
1018 node_at_insert.kind() != EMPTY_LINE
1019 } else {
1020 false
1021 }
1022 } else {
1023 false
1024 };
1025
1026 if needs_empty_line_before {
1027 let mut builder = GreenNodeBuilder::new();
1028 builder.start_node(EMPTY_LINE.into());
1029 builder.token(NEWLINE.into(), "\n");
1030 builder.finish_node();
1031 to_insert.push(SyntaxNode::new_root_mut(builder.finish()).into());
1032 }
1033
1034 to_insert.push(paragraph_to_move.into());
1035
1036 let needs_empty_line_after = if insert_at < self.0.children().count() {
1038 if let Some(node_after) = self.0.children_with_tokens().nth(insert_at) {
1040 node_after.kind() != EMPTY_LINE
1041 } else {
1042 false
1043 }
1044 } else {
1045 false
1046 };
1047
1048 if needs_empty_line_after {
1049 let mut builder = GreenNodeBuilder::new();
1050 builder.start_node(EMPTY_LINE.into());
1051 builder.token(NEWLINE.into(), "\n");
1052 builder.finish_node();
1053 to_insert.push(SyntaxNode::new_root_mut(builder.finish()).into());
1054 }
1055
1056 self.0.splice_children(insert_at..insert_at, to_insert);
1058 }
1059
1060 pub fn add_paragraph(&mut self) -> Paragraph {
1062 self.insert_empty_paragraph(None)
1063 }
1064
1065 pub fn swap_paragraphs(&mut self, index1: usize, index2: usize) {
1095 if index1 == index2 {
1096 return;
1097 }
1098
1099 let mut children: Vec<_> = self.0.children().map(|n| n.clone().into()).collect();
1101
1102 let mut para_child_indices = vec![];
1104 for (child_idx, child) in self.0.children().enumerate() {
1105 if child.kind() == PARAGRAPH {
1106 para_child_indices.push(child_idx);
1107 }
1108 }
1109
1110 if index1 >= para_child_indices.len() {
1112 panic!("index1 {} out of bounds", index1);
1113 }
1114 if index2 >= para_child_indices.len() {
1115 panic!("index2 {} out of bounds", index2);
1116 }
1117
1118 let child_idx1 = para_child_indices[index1];
1119 let child_idx2 = para_child_indices[index2];
1120
1121 children.swap(child_idx1, child_idx2);
1123
1124 let num_children = children.len();
1126 self.0.splice_children(0..num_children, children);
1127 }
1128
1129 pub fn from_file(path: impl AsRef<Path>) -> Result<Self, Error> {
1131 let text = std::fs::read_to_string(path)?;
1132 Ok(Self::from_str(&text)?)
1133 }
1134
1135 pub fn from_file_relaxed(
1137 path: impl AsRef<Path>,
1138 ) -> Result<(Self, Vec<String>), std::io::Error> {
1139 let text = std::fs::read_to_string(path)?;
1140 Ok(Self::from_str_relaxed(&text))
1141 }
1142
1143 pub fn from_str_relaxed(s: &str) -> (Self, Vec<String>) {
1145 let parsed = parse(s);
1146 (parsed.root_mut(), parsed.errors)
1147 }
1148
1149 pub fn read<R: std::io::Read>(mut r: R) -> Result<Self, Error> {
1151 let mut buf = String::new();
1152 r.read_to_string(&mut buf)?;
1153 Ok(Self::from_str(&buf)?)
1154 }
1155
1156 pub fn read_relaxed<R: std::io::Read>(mut r: R) -> Result<(Self, Vec<String>), std::io::Error> {
1158 let mut buf = String::new();
1159 r.read_to_string(&mut buf)?;
1160 Ok(Self::from_str_relaxed(&buf))
1161 }
1162}
1163
1164fn inject(builder: &mut GreenNodeBuilder, node: SyntaxNode) {
1165 builder.start_node(node.kind().into());
1166 for child in node.children_with_tokens() {
1167 match child {
1168 rowan::NodeOrToken::Node(child) => {
1169 inject(builder, child);
1170 }
1171 rowan::NodeOrToken::Token(token) => {
1172 builder.token(token.kind().into(), token.text());
1173 }
1174 }
1175 }
1176 builder.finish_node();
1177}
1178
1179impl FromIterator<Paragraph> for Deb822 {
1180 fn from_iter<T: IntoIterator<Item = Paragraph>>(iter: T) -> Self {
1181 let mut builder = GreenNodeBuilder::new();
1182 builder.start_node(ROOT.into());
1183 for (i, paragraph) in iter.into_iter().enumerate() {
1184 if i > 0 {
1185 builder.start_node(EMPTY_LINE.into());
1186 builder.token(NEWLINE.into(), "\n");
1187 builder.finish_node();
1188 }
1189 inject(&mut builder, paragraph.0);
1190 }
1191 builder.finish_node();
1192 Self(SyntaxNode::new_root_mut(builder.finish()))
1193 }
1194}
1195
1196impl From<Vec<(String, String)>> for Paragraph {
1197 fn from(v: Vec<(String, String)>) -> Self {
1198 v.into_iter().collect()
1199 }
1200}
1201
1202impl From<Vec<(&str, &str)>> for Paragraph {
1203 fn from(v: Vec<(&str, &str)>) -> Self {
1204 v.into_iter().collect()
1205 }
1206}
1207
1208impl FromIterator<(String, String)> for Paragraph {
1209 fn from_iter<T: IntoIterator<Item = (String, String)>>(iter: T) -> Self {
1210 let mut builder = GreenNodeBuilder::new();
1211 builder.start_node(PARAGRAPH.into());
1212 for (key, value) in iter {
1213 builder.start_node(ENTRY.into());
1214 builder.token(KEY.into(), &key);
1215 builder.token(COLON.into(), ":");
1216 builder.token(WHITESPACE.into(), " ");
1217 for (i, line) in value.split('\n').enumerate() {
1218 if i > 0 {
1219 builder.token(INDENT.into(), " ");
1220 }
1221 builder.token(VALUE.into(), line);
1222 builder.token(NEWLINE.into(), "\n");
1223 }
1224 builder.finish_node();
1225 }
1226 builder.finish_node();
1227 Self(SyntaxNode::new_root_mut(builder.finish()))
1228 }
1229}
1230
1231impl<'a> FromIterator<(&'a str, &'a str)> for Paragraph {
1232 fn from_iter<T: IntoIterator<Item = (&'a str, &'a str)>>(iter: T) -> Self {
1233 let mut builder = GreenNodeBuilder::new();
1234 builder.start_node(PARAGRAPH.into());
1235 for (key, value) in iter {
1236 builder.start_node(ENTRY.into());
1237 builder.token(KEY.into(), key);
1238 builder.token(COLON.into(), ":");
1239 builder.token(WHITESPACE.into(), " ");
1240 for (i, line) in value.split('\n').enumerate() {
1241 if i > 0 {
1242 builder.token(INDENT.into(), " ");
1243 }
1244 builder.token(VALUE.into(), line);
1245 builder.token(NEWLINE.into(), "\n");
1246 }
1247 builder.finish_node();
1248 }
1249 builder.finish_node();
1250 Self(SyntaxNode::new_root_mut(builder.finish()))
1251 }
1252}
1253
1254#[derive(Debug, Clone, PartialEq, Eq)]
1256pub enum IndentPattern {
1257 Fixed(usize),
1259 FieldNameLength,
1261}
1262
1263impl IndentPattern {
1264 fn to_string(&self, field_name: &str) -> String {
1266 match self {
1267 IndentPattern::Fixed(spaces) => " ".repeat(*spaces),
1268 IndentPattern::FieldNameLength => " ".repeat(field_name.len() + 2),
1269 }
1270 }
1271}
1272
1273impl Paragraph {
1274 pub fn new() -> Paragraph {
1276 let mut builder = GreenNodeBuilder::new();
1277
1278 builder.start_node(PARAGRAPH.into());
1279 builder.finish_node();
1280 Paragraph(SyntaxNode::new_root_mut(builder.finish()))
1281 }
1282
1283 #[must_use]
1295 pub fn wrap_and_sort(
1296 &self,
1297 indentation: Indentation,
1298 immediate_empty_line: bool,
1299 max_line_length_one_liner: Option<usize>,
1300 sort_entries: Option<&dyn Fn(&Entry, &Entry) -> std::cmp::Ordering>,
1301 format_value: Option<&dyn Fn(&str, &str) -> String>,
1302 ) -> Paragraph {
1303 let mut builder = GreenNodeBuilder::new();
1304
1305 let mut current = vec![];
1306 let mut entries = vec![];
1307
1308 builder.start_node(PARAGRAPH.into());
1309 for c in self.0.children_with_tokens() {
1310 match c.kind() {
1311 ENTRY => {
1312 entries.push((current, Entry::cast(c.as_node().unwrap().clone()).unwrap()));
1313 current = vec![];
1314 }
1315 ERROR | COMMENT => {
1316 current.push(c);
1317 }
1318 _ => {}
1319 }
1320 }
1321
1322 if let Some(sort_entry) = sort_entries {
1323 entries.sort_by(|a, b| {
1324 let a_key = &a.1;
1325 let b_key = &b.1;
1326 sort_entry(a_key, b_key)
1327 });
1328 }
1329
1330 for (pre, entry) in entries.into_iter() {
1331 for c in pre.into_iter() {
1332 builder.token(c.kind().into(), c.as_token().unwrap().text());
1333 }
1334
1335 inject(
1336 &mut builder,
1337 entry
1338 .wrap_and_sort(
1339 indentation,
1340 immediate_empty_line,
1341 max_line_length_one_liner,
1342 format_value,
1343 )
1344 .0,
1345 );
1346 }
1347
1348 for c in current {
1349 builder.token(c.kind().into(), c.as_token().unwrap().text());
1350 }
1351
1352 builder.finish_node();
1353 Self(SyntaxNode::new_root_mut(builder.finish()))
1354 }
1355
1356 pub fn normalize_field_spacing(&mut self) -> bool {
1376 let mut any_changed = false;
1377
1378 let mut entries: Vec<_> = self.entries().collect();
1380
1381 for entry in &mut entries {
1383 if entry.normalize_field_spacing() {
1384 any_changed = true;
1385 }
1386 }
1387
1388 any_changed
1389 }
1390
1391 pub fn get(&self, key: &str) -> Option<String> {
1395 self.entries()
1396 .find(|e| {
1397 e.key()
1398 .as_deref()
1399 .is_some_and(|k| k.eq_ignore_ascii_case(key))
1400 })
1401 .map(|e| e.value())
1402 }
1403
1404 pub fn get_entry(&self, key: &str) -> Option<Entry> {
1408 self.entries().find(|e| {
1409 e.key()
1410 .as_deref()
1411 .is_some_and(|k| k.eq_ignore_ascii_case(key))
1412 })
1413 }
1414
1415 pub fn get_with_indent(&self, key: &str, indent_pattern: &IndentPattern) -> Option<String> {
1442 use crate::lex::SyntaxKind::{INDENT, VALUE};
1443
1444 self.entries()
1445 .find(|e| {
1446 e.key()
1447 .as_deref()
1448 .is_some_and(|k| k.eq_ignore_ascii_case(key))
1449 })
1450 .and_then(|e| {
1451 let field_key = e.key()?;
1452 let expected_indent = indent_pattern.to_string(&field_key);
1453 let expected_len = expected_indent.len();
1454
1455 let mut result = String::new();
1456 let mut first = true;
1457 let mut last_indent: Option<String> = None;
1458
1459 for token in e.0.children_with_tokens().filter_map(|it| it.into_token()) {
1460 match token.kind() {
1461 INDENT => {
1462 last_indent = Some(token.text().to_string());
1463 }
1464 VALUE => {
1465 if !first {
1466 result.push('\n');
1467 if let Some(ref indent_text) = last_indent {
1469 if indent_text.len() > expected_len {
1470 result.push_str(&indent_text[expected_len..]);
1471 }
1472 }
1473 }
1474 result.push_str(token.text());
1475 first = false;
1476 last_indent = None;
1477 }
1478 _ => {}
1479 }
1480 }
1481
1482 Some(result)
1483 })
1484 }
1485
1486 pub fn get_multiline(&self, key: &str) -> Option<String> {
1513 self.get_with_indent(key, &IndentPattern::Fixed(1))
1514 }
1515
1516 pub fn set_multiline(
1542 &mut self,
1543 key: &str,
1544 value: &str,
1545 field_order: Option<&[&str]>,
1546 ) -> Result<(), Error> {
1547 self.try_set_with_forced_indent(key, value, &IndentPattern::Fixed(1), field_order)
1548 }
1549
1550 pub fn contains_key(&self, key: &str) -> bool {
1552 self.get(key).is_some()
1553 }
1554
1555 pub fn entries(&self) -> impl Iterator<Item = Entry> + '_ {
1557 self.0.children().filter_map(Entry::cast)
1558 }
1559
1560 pub fn items(&self) -> impl Iterator<Item = (String, String)> + '_ {
1562 self.entries()
1563 .filter_map(|e| e.key().map(|k| (k, e.value())))
1564 }
1565
1566 pub fn get_all<'a>(&'a self, key: &'a str) -> impl Iterator<Item = String> + 'a {
1570 self.items().filter_map(move |(k, v)| {
1571 if k.eq_ignore_ascii_case(key) {
1572 Some(v)
1573 } else {
1574 None
1575 }
1576 })
1577 }
1578
1579 pub fn keys(&self) -> impl Iterator<Item = String> + '_ {
1581 self.entries().filter_map(|e| e.key())
1582 }
1583
1584 pub fn remove(&mut self, key: &str) {
1588 for mut entry in self.entries() {
1589 if entry
1590 .key()
1591 .as_deref()
1592 .is_some_and(|k| k.eq_ignore_ascii_case(key))
1593 {
1594 entry.detach();
1595 }
1596 }
1597 }
1598
1599 pub fn insert(&mut self, key: &str, value: &str) {
1601 let entry = Entry::new(key, value);
1602 let count = self.0.children_with_tokens().count();
1603 self.0.splice_children(count..count, vec![entry.0.into()]);
1604 }
1605
1606 pub fn insert_comment_before(&mut self, comment: &str) {
1625 use rowan::GreenNodeBuilder;
1626
1627 let mut builder = GreenNodeBuilder::new();
1630 builder.start_node(EMPTY_LINE.into());
1631 builder.token(COMMENT.into(), &format!("# {}", comment));
1632 builder.token(NEWLINE.into(), "\n");
1633 builder.finish_node();
1634 let green = builder.finish();
1635
1636 let comment_node = SyntaxNode::new_root_mut(green);
1638
1639 let index = self.0.index();
1640 let parent = self.0.parent().expect("Paragraph must have a parent");
1641 parent.splice_children(index..index, vec![comment_node.into()]);
1642 }
1643
1644 fn detect_indent_pattern(&self) -> IndentPattern {
1652 let indent_data: Vec<(String, usize)> = self
1654 .entries()
1655 .filter_map(|entry| {
1656 let field_key = entry.key()?;
1657 let indent = entry.get_indent()?;
1658 Some((field_key, indent.len()))
1659 })
1660 .collect();
1661
1662 if indent_data.is_empty() {
1663 return IndentPattern::FieldNameLength;
1665 }
1666
1667 let first_indent_len = indent_data[0].1;
1669 let all_same = indent_data.iter().all(|(_, len)| *len == first_indent_len);
1670
1671 if all_same {
1672 return IndentPattern::Fixed(first_indent_len);
1674 }
1675
1676 let all_match_field_length = indent_data
1678 .iter()
1679 .all(|(field_key, indent_len)| *indent_len == field_key.len() + 2);
1680
1681 if all_match_field_length {
1682 return IndentPattern::FieldNameLength;
1684 }
1685
1686 IndentPattern::FieldNameLength
1688 }
1689
1690 pub fn try_set(&mut self, key: &str, value: &str) -> Result<(), Error> {
1695 self.try_set_with_indent_pattern(key, value, None, None)
1696 }
1697
1698 pub fn set(&mut self, key: &str, value: &str) {
1703 self.try_set(key, value)
1704 .expect("Invalid value: empty continuation line")
1705 }
1706
1707 pub fn set_with_field_order(&mut self, key: &str, value: &str, field_order: &[&str]) {
1709 self.try_set_with_indent_pattern(key, value, None, Some(field_order))
1710 .expect("Invalid value: empty continuation line")
1711 }
1712
1713 pub fn try_set_with_indent_pattern(
1730 &mut self,
1731 key: &str,
1732 value: &str,
1733 default_indent_pattern: Option<&IndentPattern>,
1734 field_order: Option<&[&str]>,
1735 ) -> Result<(), Error> {
1736 let existing_entry = self.entries().find(|entry| {
1738 entry
1739 .key()
1740 .as_deref()
1741 .is_some_and(|k| k.eq_ignore_ascii_case(key))
1742 });
1743
1744 let indent = existing_entry
1746 .as_ref()
1747 .and_then(|entry| entry.get_indent())
1748 .unwrap_or_else(|| {
1749 if let Some(pattern) = default_indent_pattern {
1751 pattern.to_string(key)
1752 } else {
1753 self.detect_indent_pattern().to_string(key)
1754 }
1755 });
1756
1757 let post_colon_ws = existing_entry
1758 .as_ref()
1759 .and_then(|entry| entry.get_post_colon_whitespace())
1760 .unwrap_or_else(|| " ".to_string());
1761
1762 let actual_key = existing_entry
1764 .as_ref()
1765 .and_then(|e| e.key())
1766 .unwrap_or_else(|| key.to_string());
1767
1768 let new_entry = Entry::try_with_formatting(&actual_key, value, &post_colon_ws, &indent)?;
1769
1770 for entry in self.entries() {
1772 if entry
1773 .key()
1774 .as_deref()
1775 .is_some_and(|k| k.eq_ignore_ascii_case(key))
1776 {
1777 self.0.splice_children(
1778 entry.0.index()..entry.0.index() + 1,
1779 vec![new_entry.0.into()],
1780 );
1781 return Ok(());
1782 }
1783 }
1784
1785 if let Some(order) = field_order {
1787 let insertion_index = self.find_insertion_index(key, order);
1788 self.0
1789 .splice_children(insertion_index..insertion_index, vec![new_entry.0.into()]);
1790 } else {
1791 let insertion_index = self.0.children_with_tokens().count();
1793 self.0
1794 .splice_children(insertion_index..insertion_index, vec![new_entry.0.into()]);
1795 }
1796 Ok(())
1797 }
1798
1799 pub fn set_with_indent_pattern(
1816 &mut self,
1817 key: &str,
1818 value: &str,
1819 default_indent_pattern: Option<&IndentPattern>,
1820 field_order: Option<&[&str]>,
1821 ) {
1822 self.try_set_with_indent_pattern(key, value, default_indent_pattern, field_order)
1823 .expect("Invalid value: empty continuation line")
1824 }
1825
1826 pub fn try_set_with_forced_indent(
1840 &mut self,
1841 key: &str,
1842 value: &str,
1843 indent_pattern: &IndentPattern,
1844 field_order: Option<&[&str]>,
1845 ) -> Result<(), Error> {
1846 let existing_entry = self.entries().find(|entry| {
1848 entry
1849 .key()
1850 .as_deref()
1851 .is_some_and(|k| k.eq_ignore_ascii_case(key))
1852 });
1853
1854 let post_colon_ws = existing_entry
1856 .as_ref()
1857 .and_then(|entry| entry.get_post_colon_whitespace())
1858 .unwrap_or_else(|| " ".to_string());
1859
1860 let actual_key = existing_entry
1862 .as_ref()
1863 .and_then(|e| e.key())
1864 .unwrap_or_else(|| key.to_string());
1865
1866 let indent = indent_pattern.to_string(&actual_key);
1868 let new_entry = Entry::try_with_formatting(&actual_key, value, &post_colon_ws, &indent)?;
1869
1870 for entry in self.entries() {
1872 if entry
1873 .key()
1874 .as_deref()
1875 .is_some_and(|k| k.eq_ignore_ascii_case(key))
1876 {
1877 self.0.splice_children(
1878 entry.0.index()..entry.0.index() + 1,
1879 vec![new_entry.0.into()],
1880 );
1881 return Ok(());
1882 }
1883 }
1884
1885 if let Some(order) = field_order {
1887 let insertion_index = self.find_insertion_index(key, order);
1888 self.0
1889 .splice_children(insertion_index..insertion_index, vec![new_entry.0.into()]);
1890 } else {
1891 let insertion_index = self.0.children_with_tokens().count();
1893 self.0
1894 .splice_children(insertion_index..insertion_index, vec![new_entry.0.into()]);
1895 }
1896 Ok(())
1897 }
1898
1899 pub fn set_with_forced_indent(
1913 &mut self,
1914 key: &str,
1915 value: &str,
1916 indent_pattern: &IndentPattern,
1917 field_order: Option<&[&str]>,
1918 ) {
1919 self.try_set_with_forced_indent(key, value, indent_pattern, field_order)
1920 .expect("Invalid value: empty continuation line")
1921 }
1922
1923 pub fn change_field_indent(
1939 &mut self,
1940 key: &str,
1941 indent_pattern: &IndentPattern,
1942 ) -> Result<bool, Error> {
1943 let existing_entry = self.entries().find(|entry| {
1945 entry
1946 .key()
1947 .as_deref()
1948 .is_some_and(|k| k.eq_ignore_ascii_case(key))
1949 });
1950
1951 if let Some(entry) = existing_entry {
1952 let value = entry.value();
1953 let actual_key = entry.key().unwrap_or_else(|| key.to_string());
1954
1955 let post_colon_ws = entry
1957 .get_post_colon_whitespace()
1958 .unwrap_or_else(|| " ".to_string());
1959
1960 let indent = indent_pattern.to_string(&actual_key);
1962 let new_entry =
1963 Entry::try_with_formatting(&actual_key, &value, &post_colon_ws, &indent)?;
1964
1965 self.0.splice_children(
1967 entry.0.index()..entry.0.index() + 1,
1968 vec![new_entry.0.into()],
1969 );
1970 Ok(true)
1971 } else {
1972 Ok(false)
1973 }
1974 }
1975
1976 fn find_insertion_index(&self, key: &str, field_order: &[&str]) -> usize {
1978 let new_field_position = field_order
1980 .iter()
1981 .position(|&field| field.eq_ignore_ascii_case(key));
1982
1983 let mut insertion_index = self.0.children_with_tokens().count();
1984
1985 for (i, child) in self.0.children_with_tokens().enumerate() {
1987 if let Some(node) = child.as_node() {
1988 if let Some(entry) = Entry::cast(node.clone()) {
1989 if let Some(existing_key) = entry.key() {
1990 let existing_position = field_order
1991 .iter()
1992 .position(|&field| field.eq_ignore_ascii_case(&existing_key));
1993
1994 match (new_field_position, existing_position) {
1995 (Some(new_pos), Some(existing_pos)) => {
1997 if new_pos < existing_pos {
1998 insertion_index = i;
1999 break;
2000 }
2001 }
2002 (Some(_), None) => {
2004 }
2006 (None, Some(_)) => {
2008 }
2010 (None, None) => {
2012 if key < existing_key.as_str() {
2013 insertion_index = i;
2014 break;
2015 }
2016 }
2017 }
2018 }
2019 }
2020 }
2021 }
2022
2023 if new_field_position.is_some() && insertion_index == self.0.children_with_tokens().count()
2026 {
2027 let children: Vec<_> = self.0.children_with_tokens().enumerate().collect();
2029 for (i, child) in children.into_iter().rev() {
2030 if let Some(node) = child.as_node() {
2031 if let Some(entry) = Entry::cast(node.clone()) {
2032 if let Some(existing_key) = entry.key() {
2033 if field_order
2034 .iter()
2035 .any(|&f| f.eq_ignore_ascii_case(&existing_key))
2036 {
2037 insertion_index = i + 1;
2039 break;
2040 }
2041 }
2042 }
2043 }
2044 }
2045 }
2046
2047 insertion_index
2048 }
2049
2050 pub fn rename(&mut self, old_key: &str, new_key: &str) -> bool {
2054 for entry in self.entries() {
2055 if entry
2056 .key()
2057 .as_deref()
2058 .is_some_and(|k| k.eq_ignore_ascii_case(old_key))
2059 {
2060 self.0.splice_children(
2061 entry.0.index()..entry.0.index() + 1,
2062 vec![Entry::new(new_key, entry.value().as_str()).0.into()],
2063 );
2064 return true;
2065 }
2066 }
2067 false
2068 }
2069}
2070
2071impl Default for Paragraph {
2072 fn default() -> Self {
2073 Self::new()
2074 }
2075}
2076
2077impl std::str::FromStr for Paragraph {
2078 type Err = ParseError;
2079
2080 fn from_str(text: &str) -> Result<Self, Self::Err> {
2081 let deb822 = Deb822::from_str(text)?;
2082
2083 let mut paragraphs = deb822.paragraphs();
2084
2085 paragraphs
2086 .next()
2087 .ok_or_else(|| ParseError(vec!["no paragraphs".to_string()]))
2088 }
2089}
2090
2091#[cfg(feature = "python-debian")]
2092impl<'py> pyo3::IntoPyObject<'py> for Paragraph {
2093 type Target = pyo3::PyAny;
2094 type Output = pyo3::Bound<'py, Self::Target>;
2095 type Error = pyo3::PyErr;
2096
2097 fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
2098 use pyo3::prelude::*;
2099 let d = pyo3::types::PyDict::new(py);
2100 for (k, v) in self.items() {
2101 d.set_item(k, v)?;
2102 }
2103 let m = py.import("debian.deb822")?;
2104 let cls = m.getattr("Deb822")?;
2105 cls.call1((d,))
2106 }
2107}
2108
2109#[cfg(feature = "python-debian")]
2110impl<'py> pyo3::IntoPyObject<'py> for &Paragraph {
2111 type Target = pyo3::PyAny;
2112 type Output = pyo3::Bound<'py, Self::Target>;
2113 type Error = pyo3::PyErr;
2114
2115 fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
2116 use pyo3::prelude::*;
2117 let d = pyo3::types::PyDict::new(py);
2118 for (k, v) in self.items() {
2119 d.set_item(k, v)?;
2120 }
2121 let m = py.import("debian.deb822")?;
2122 let cls = m.getattr("Deb822")?;
2123 cls.call1((d,))
2124 }
2125}
2126
2127#[cfg(feature = "python-debian")]
2128impl<'py> pyo3::FromPyObject<'_, 'py> for Paragraph {
2129 type Error = pyo3::PyErr;
2130
2131 fn extract(obj: pyo3::Borrowed<'_, 'py, pyo3::PyAny>) -> Result<Self, Self::Error> {
2132 use pyo3::types::PyAnyMethods;
2133 let d = obj.call_method0("__str__")?.extract::<String>()?;
2134 Paragraph::from_str(&d)
2135 .map_err(|e| pyo3::exceptions::PyValueError::new_err((e.to_string(),)))
2136 }
2137}
2138
2139impl Entry {
2140 pub fn text_range(&self) -> rowan::TextRange {
2142 self.0.text_range()
2143 }
2144
2145 pub fn key_range(&self) -> Option<rowan::TextRange> {
2147 self.0
2148 .children_with_tokens()
2149 .filter_map(|it| it.into_token())
2150 .find(|it| it.kind() == KEY)
2151 .map(|it| it.text_range())
2152 }
2153
2154 pub fn colon_range(&self) -> Option<rowan::TextRange> {
2156 self.0
2157 .children_with_tokens()
2158 .filter_map(|it| it.into_token())
2159 .find(|it| it.kind() == COLON)
2160 .map(|it| it.text_range())
2161 }
2162
2163 pub fn value_range(&self) -> Option<rowan::TextRange> {
2166 let value_tokens: Vec<_> = self
2167 .0
2168 .children_with_tokens()
2169 .filter_map(|it| it.into_token())
2170 .filter(|it| it.kind() == VALUE)
2171 .collect();
2172
2173 if value_tokens.is_empty() {
2174 return None;
2175 }
2176
2177 let first = value_tokens.first().unwrap();
2178 let last = value_tokens.last().unwrap();
2179 Some(rowan::TextRange::new(
2180 first.text_range().start(),
2181 last.text_range().end(),
2182 ))
2183 }
2184
2185 pub fn value_line_ranges(&self) -> Vec<rowan::TextRange> {
2188 self.0
2189 .children_with_tokens()
2190 .filter_map(|it| it.into_token())
2191 .filter(|it| it.kind() == VALUE)
2192 .map(|it| it.text_range())
2193 .collect()
2194 }
2195
2196 pub fn new(key: &str, value: &str) -> Entry {
2198 Self::with_indentation(key, value, " ")
2199 }
2200
2201 pub fn with_indentation(key: &str, value: &str, indent: &str) -> Entry {
2208 Entry::with_formatting(key, value, " ", indent)
2209 }
2210
2211 pub fn try_with_formatting(
2222 key: &str,
2223 value: &str,
2224 post_colon_ws: &str,
2225 indent: &str,
2226 ) -> Result<Entry, Error> {
2227 let mut builder = GreenNodeBuilder::new();
2228
2229 builder.start_node(ENTRY.into());
2230 builder.token(KEY.into(), key);
2231 builder.token(COLON.into(), ":");
2232
2233 let mut i = 0;
2235 while i < post_colon_ws.len() {
2236 if post_colon_ws[i..].starts_with('\n') {
2237 builder.token(NEWLINE.into(), "\n");
2238 i += 1;
2239 } else {
2240 let start = i;
2242 while i < post_colon_ws.len() && !post_colon_ws[i..].starts_with('\n') {
2243 i += post_colon_ws[i..].chars().next().unwrap().len_utf8();
2244 }
2245 builder.token(WHITESPACE.into(), &post_colon_ws[start..i]);
2246 }
2247 }
2248
2249 for (line_idx, line) in value.split('\n').enumerate() {
2250 if line_idx > 0 {
2251 if line.trim().is_empty() {
2254 return Err(Error::InvalidValue(format!(
2255 "empty continuation line (line with only whitespace) at line {}",
2256 line_idx + 1
2257 )));
2258 }
2259 builder.token(INDENT.into(), indent);
2260 }
2261 builder.token(VALUE.into(), line);
2262 builder.token(NEWLINE.into(), "\n");
2263 }
2264 builder.finish_node();
2265 Ok(Entry(SyntaxNode::new_root_mut(builder.finish())))
2266 }
2267
2268 pub fn with_formatting(key: &str, value: &str, post_colon_ws: &str, indent: &str) -> Entry {
2279 Self::try_with_formatting(key, value, post_colon_ws, indent)
2280 .expect("Invalid value: empty continuation line")
2281 }
2282
2283 #[must_use]
2284 pub fn wrap_and_sort(
2297 &self,
2298 mut indentation: Indentation,
2299 immediate_empty_line: bool,
2300 max_line_length_one_liner: Option<usize>,
2301 format_value: Option<&dyn Fn(&str, &str) -> String>,
2302 ) -> Entry {
2303 let mut builder = GreenNodeBuilder::new();
2304
2305 let mut content = vec![];
2306 builder.start_node(ENTRY.into());
2307 for c in self.0.children_with_tokens() {
2308 let text = c.as_token().map(|t| t.text());
2309 match c.kind() {
2310 KEY => {
2311 builder.token(KEY.into(), text.unwrap());
2312 if indentation == Indentation::FieldNameLength {
2313 indentation = Indentation::Spaces(text.unwrap().len() as u32);
2314 }
2315 }
2316 COLON => {
2317 builder.token(COLON.into(), ":");
2318 }
2319 INDENT => {
2320 }
2322 ERROR | COMMENT | VALUE | WHITESPACE | NEWLINE => {
2323 content.push(c);
2324 }
2325 EMPTY_LINE | ENTRY | ROOT | PARAGRAPH => unreachable!(),
2326 }
2327 }
2328
2329 let indentation = if let crate::Indentation::Spaces(i) = indentation {
2330 i
2331 } else {
2332 1
2333 };
2334
2335 assert!(indentation > 0);
2336
2337 while let Some(c) = content.last() {
2339 if c.kind() == NEWLINE || c.kind() == WHITESPACE {
2340 content.pop();
2341 } else {
2342 break;
2343 }
2344 }
2345
2346 let tokens = if let Some(ref format_value) = format_value {
2349 if !content
2350 .iter()
2351 .any(|c| c.kind() == ERROR || c.kind() == COMMENT)
2352 {
2353 let concat = content
2354 .iter()
2355 .filter_map(|c| c.as_token().map(|t| t.text()))
2356 .collect::<String>();
2357 let formatted = format_value(self.key().as_ref().unwrap(), &concat);
2358 crate::lex::lex_inline(&formatted)
2359 .map(|(k, t)| (k, t.to_string()))
2360 .collect::<Vec<_>>()
2361 } else {
2362 content
2363 .into_iter()
2364 .map(|n| n.into_token().unwrap())
2365 .map(|i| (i.kind(), i.text().to_string()))
2366 .collect::<Vec<_>>()
2367 }
2368 } else {
2369 content
2370 .into_iter()
2371 .map(|n| n.into_token().unwrap())
2372 .map(|i| (i.kind(), i.text().to_string()))
2373 .collect::<Vec<_>>()
2374 };
2375
2376 rebuild_value(
2377 &mut builder,
2378 tokens,
2379 self.key().map_or(0, |k| k.len()),
2380 indentation,
2381 immediate_empty_line,
2382 max_line_length_one_liner,
2383 );
2384
2385 builder.finish_node();
2386 Self(SyntaxNode::new_root_mut(builder.finish()))
2387 }
2388
2389 pub fn key(&self) -> Option<String> {
2391 self.0
2392 .children_with_tokens()
2393 .filter_map(|it| it.into_token())
2394 .find(|it| it.kind() == KEY)
2395 .map(|it| it.text().to_string())
2396 }
2397
2398 pub fn value(&self) -> String {
2400 let mut parts = self
2401 .0
2402 .children_with_tokens()
2403 .filter_map(|it| it.into_token())
2404 .filter(|it| it.kind() == VALUE)
2405 .map(|it| it.text().to_string());
2406
2407 match parts.next() {
2408 None => String::new(),
2409 Some(first) => {
2410 let mut result = first;
2411 for part in parts {
2412 result.push('\n');
2413 result.push_str(&part);
2414 }
2415 result
2416 }
2417 }
2418 }
2419
2420 fn get_indent(&self) -> Option<String> {
2423 self.0
2424 .children_with_tokens()
2425 .filter_map(|it| it.into_token())
2426 .find(|it| it.kind() == INDENT)
2427 .map(|it| it.text().to_string())
2428 }
2429
2430 fn get_post_colon_whitespace(&self) -> Option<String> {
2434 let mut found_colon = false;
2435 let mut whitespace = String::new();
2436
2437 for token in self
2438 .0
2439 .children_with_tokens()
2440 .filter_map(|it| it.into_token())
2441 {
2442 if token.kind() == COLON {
2443 found_colon = true;
2444 continue;
2445 }
2446
2447 if found_colon {
2448 if token.kind() == WHITESPACE || token.kind() == NEWLINE || token.kind() == INDENT {
2449 whitespace.push_str(token.text());
2450 } else {
2451 break;
2453 }
2454 }
2455 }
2456
2457 if whitespace.is_empty() {
2458 None
2459 } else {
2460 Some(whitespace)
2461 }
2462 }
2463
2464 pub fn normalize_field_spacing(&mut self) -> bool {
2485 use rowan::GreenNodeBuilder;
2486
2487 let original_text = self.0.text().to_string();
2489
2490 let mut builder = GreenNodeBuilder::new();
2492 builder.start_node(ENTRY.into());
2493
2494 let mut seen_colon = false;
2495 let mut skip_whitespace = false;
2496
2497 for child in self.0.children_with_tokens() {
2498 match child.kind() {
2499 KEY => {
2500 builder.token(KEY.into(), child.as_token().unwrap().text());
2501 }
2502 COLON => {
2503 builder.token(COLON.into(), ":");
2504 seen_colon = true;
2505 skip_whitespace = true;
2506 }
2507 WHITESPACE if skip_whitespace => {
2508 continue;
2510 }
2511 VALUE if skip_whitespace => {
2512 builder.token(WHITESPACE.into(), " ");
2514 builder.token(VALUE.into(), child.as_token().unwrap().text());
2515 skip_whitespace = false;
2516 }
2517 NEWLINE if skip_whitespace && seen_colon => {
2518 builder.token(NEWLINE.into(), "\n");
2521 skip_whitespace = false;
2522 }
2523 _ => {
2524 if let Some(token) = child.as_token() {
2526 builder.token(token.kind().into(), token.text());
2527 }
2528 }
2529 }
2530 }
2531
2532 builder.finish_node();
2533 let normalized_green = builder.finish();
2534 let normalized = SyntaxNode::new_root_mut(normalized_green);
2535
2536 let changed = original_text != normalized.text().to_string();
2538
2539 if changed {
2540 if let Some(parent) = self.0.parent() {
2542 let index = self.0.index();
2543 parent.splice_children(index..index + 1, vec![normalized.into()]);
2544 }
2545 }
2546
2547 changed
2548 }
2549
2550 pub fn detach(&mut self) {
2552 self.0.detach();
2553 }
2554}
2555
2556impl FromStr for Deb822 {
2557 type Err = ParseError;
2558
2559 fn from_str(s: &str) -> Result<Self, Self::Err> {
2560 Deb822::parse(s).to_result()
2561 }
2562}
2563
2564#[test]
2565fn test_parse_simple() {
2566 const CONTROLV1: &str = r#"Source: foo
2567Maintainer: Foo Bar <foo@example.com>
2568Section: net
2569
2570# This is a comment
2571
2572Package: foo
2573Architecture: all
2574Depends:
2575 bar,
2576 blah
2577Description: This is a description
2578 And it is
2579 .
2580 multiple
2581 lines
2582"#;
2583 let parsed = parse(CONTROLV1);
2584 let node = parsed.syntax();
2585 assert_eq!(
2586 format!("{:#?}", node),
2587 r###"ROOT@0..203
2588 PARAGRAPH@0..63
2589 ENTRY@0..12
2590 KEY@0..6 "Source"
2591 COLON@6..7 ":"
2592 WHITESPACE@7..8 " "
2593 VALUE@8..11 "foo"
2594 NEWLINE@11..12 "\n"
2595 ENTRY@12..50
2596 KEY@12..22 "Maintainer"
2597 COLON@22..23 ":"
2598 WHITESPACE@23..24 " "
2599 VALUE@24..49 "Foo Bar <foo@example. ..."
2600 NEWLINE@49..50 "\n"
2601 ENTRY@50..63
2602 KEY@50..57 "Section"
2603 COLON@57..58 ":"
2604 WHITESPACE@58..59 " "
2605 VALUE@59..62 "net"
2606 NEWLINE@62..63 "\n"
2607 EMPTY_LINE@63..64
2608 NEWLINE@63..64 "\n"
2609 EMPTY_LINE@64..84
2610 COMMENT@64..83 "# This is a comment"
2611 NEWLINE@83..84 "\n"
2612 EMPTY_LINE@84..85
2613 NEWLINE@84..85 "\n"
2614 PARAGRAPH@85..203
2615 ENTRY@85..98
2616 KEY@85..92 "Package"
2617 COLON@92..93 ":"
2618 WHITESPACE@93..94 " "
2619 VALUE@94..97 "foo"
2620 NEWLINE@97..98 "\n"
2621 ENTRY@98..116
2622 KEY@98..110 "Architecture"
2623 COLON@110..111 ":"
2624 WHITESPACE@111..112 " "
2625 VALUE@112..115 "all"
2626 NEWLINE@115..116 "\n"
2627 ENTRY@116..137
2628 KEY@116..123 "Depends"
2629 COLON@123..124 ":"
2630 NEWLINE@124..125 "\n"
2631 INDENT@125..126 " "
2632 VALUE@126..130 "bar,"
2633 NEWLINE@130..131 "\n"
2634 INDENT@131..132 " "
2635 VALUE@132..136 "blah"
2636 NEWLINE@136..137 "\n"
2637 ENTRY@137..203
2638 KEY@137..148 "Description"
2639 COLON@148..149 ":"
2640 WHITESPACE@149..150 " "
2641 VALUE@150..171 "This is a description"
2642 NEWLINE@171..172 "\n"
2643 INDENT@172..173 " "
2644 VALUE@173..182 "And it is"
2645 NEWLINE@182..183 "\n"
2646 INDENT@183..184 " "
2647 VALUE@184..185 "."
2648 NEWLINE@185..186 "\n"
2649 INDENT@186..187 " "
2650 VALUE@187..195 "multiple"
2651 NEWLINE@195..196 "\n"
2652 INDENT@196..197 " "
2653 VALUE@197..202 "lines"
2654 NEWLINE@202..203 "\n"
2655"###
2656 );
2657 assert_eq!(parsed.errors, Vec::<String>::new());
2658
2659 let root = parsed.root_mut();
2660 assert_eq!(root.paragraphs().count(), 2);
2661 let source = root.paragraphs().next().unwrap();
2662 assert_eq!(
2663 source.keys().collect::<Vec<_>>(),
2664 vec!["Source", "Maintainer", "Section"]
2665 );
2666 assert_eq!(source.get("Source").as_deref(), Some("foo"));
2667 assert_eq!(
2668 source.get("Maintainer").as_deref(),
2669 Some("Foo Bar <foo@example.com>")
2670 );
2671 assert_eq!(source.get("Section").as_deref(), Some("net"));
2672 assert_eq!(
2673 source.items().collect::<Vec<_>>(),
2674 vec![
2675 ("Source".into(), "foo".into()),
2676 ("Maintainer".into(), "Foo Bar <foo@example.com>".into()),
2677 ("Section".into(), "net".into()),
2678 ]
2679 );
2680
2681 let binary = root.paragraphs().nth(1).unwrap();
2682 assert_eq!(
2683 binary.keys().collect::<Vec<_>>(),
2684 vec!["Package", "Architecture", "Depends", "Description"]
2685 );
2686 assert_eq!(binary.get("Package").as_deref(), Some("foo"));
2687 assert_eq!(binary.get("Architecture").as_deref(), Some("all"));
2688 assert_eq!(binary.get("Depends").as_deref(), Some("bar,\nblah"));
2689 assert_eq!(
2690 binary.get("Description").as_deref(),
2691 Some("This is a description\nAnd it is\n.\nmultiple\nlines")
2692 );
2693
2694 assert_eq!(node.text(), CONTROLV1);
2695}
2696
2697#[test]
2698fn test_with_trailing_whitespace() {
2699 const CONTROLV1: &str = r#"Source: foo
2700Maintainer: Foo Bar <foo@example.com>
2701
2702
2703"#;
2704 let parsed = parse(CONTROLV1);
2705 let node = parsed.syntax();
2706 assert_eq!(
2707 format!("{:#?}", node),
2708 r###"ROOT@0..52
2709 PARAGRAPH@0..50
2710 ENTRY@0..12
2711 KEY@0..6 "Source"
2712 COLON@6..7 ":"
2713 WHITESPACE@7..8 " "
2714 VALUE@8..11 "foo"
2715 NEWLINE@11..12 "\n"
2716 ENTRY@12..50
2717 KEY@12..22 "Maintainer"
2718 COLON@22..23 ":"
2719 WHITESPACE@23..24 " "
2720 VALUE@24..49 "Foo Bar <foo@example. ..."
2721 NEWLINE@49..50 "\n"
2722 EMPTY_LINE@50..51
2723 NEWLINE@50..51 "\n"
2724 EMPTY_LINE@51..52
2725 NEWLINE@51..52 "\n"
2726"###
2727 );
2728 assert_eq!(parsed.errors, Vec::<String>::new());
2729
2730 let root = parsed.root_mut();
2731 assert_eq!(root.paragraphs().count(), 1);
2732 let source = root.paragraphs().next().unwrap();
2733 assert_eq!(
2734 source.items().collect::<Vec<_>>(),
2735 vec![
2736 ("Source".into(), "foo".into()),
2737 ("Maintainer".into(), "Foo Bar <foo@example.com>".into()),
2738 ]
2739 );
2740}
2741
2742fn rebuild_value(
2743 builder: &mut GreenNodeBuilder,
2744 mut tokens: Vec<(SyntaxKind, String)>,
2745 key_len: usize,
2746 indentation: u32,
2747 immediate_empty_line: bool,
2748 max_line_length_one_liner: Option<usize>,
2749) {
2750 let first_line_len = tokens
2751 .iter()
2752 .take_while(|(k, _t)| *k != NEWLINE)
2753 .map(|(_k, t)| t.len())
2754 .sum::<usize>() + key_len + 2 ;
2755
2756 let has_newline = tokens.iter().any(|(k, _t)| *k == NEWLINE);
2757
2758 let mut last_was_newline = false;
2759 if max_line_length_one_liner
2760 .map(|mll| first_line_len <= mll)
2761 .unwrap_or(false)
2762 && !has_newline
2763 {
2764 for (k, t) in tokens {
2766 builder.token(k.into(), &t);
2767 }
2768 } else {
2769 if immediate_empty_line && has_newline {
2771 builder.token(NEWLINE.into(), "\n");
2772 last_was_newline = true;
2773 } else {
2774 builder.token(WHITESPACE.into(), " ");
2775 }
2776 let mut start_idx = 0;
2778 while start_idx < tokens.len() {
2779 if tokens[start_idx].0 == NEWLINE || tokens[start_idx].0 == WHITESPACE {
2780 start_idx += 1;
2781 } else {
2782 break;
2783 }
2784 }
2785 tokens.drain(..start_idx);
2786 let indent_str = " ".repeat(indentation as usize);
2788 for (k, t) in tokens {
2789 if last_was_newline {
2790 builder.token(INDENT.into(), &indent_str);
2791 }
2792 builder.token(k.into(), &t);
2793 last_was_newline = k == NEWLINE;
2794 }
2795 }
2796
2797 if !last_was_newline {
2798 builder.token(NEWLINE.into(), "\n");
2799 }
2800}
2801
2802#[cfg(test)]
2803mod tests {
2804 use super::*;
2805 #[test]
2806 fn test_parse() {
2807 let d: super::Deb822 = r#"Source: foo
2808Maintainer: Foo Bar <jelmer@jelmer.uk>
2809Section: net
2810
2811Package: foo
2812Architecture: all
2813Depends: libc6
2814Description: This is a description
2815 With details
2816"#
2817 .parse()
2818 .unwrap();
2819 let mut ps = d.paragraphs();
2820 let p = ps.next().unwrap();
2821
2822 assert_eq!(p.get("Source").as_deref(), Some("foo"));
2823 assert_eq!(
2824 p.get("Maintainer").as_deref(),
2825 Some("Foo Bar <jelmer@jelmer.uk>")
2826 );
2827 assert_eq!(p.get("Section").as_deref(), Some("net"));
2828
2829 let b = ps.next().unwrap();
2830 assert_eq!(b.get("Package").as_deref(), Some("foo"));
2831 }
2832
2833 #[test]
2834 fn test_after_multi_line() {
2835 let d: super::Deb822 = r#"Source: golang-github-blah-blah
2836Section: devel
2837Priority: optional
2838Standards-Version: 4.2.0
2839Maintainer: Some Maintainer <example@example.com>
2840Build-Depends: debhelper (>= 11~),
2841 dh-golang,
2842 golang-any
2843Homepage: https://github.com/j-keck/arping
2844"#
2845 .parse()
2846 .unwrap();
2847 let mut ps = d.paragraphs();
2848 let p = ps.next().unwrap();
2849 assert_eq!(p.get("Source").as_deref(), Some("golang-github-blah-blah"));
2850 assert_eq!(p.get("Section").as_deref(), Some("devel"));
2851 assert_eq!(p.get("Priority").as_deref(), Some("optional"));
2852 assert_eq!(p.get("Standards-Version").as_deref(), Some("4.2.0"));
2853 assert_eq!(
2854 p.get("Maintainer").as_deref(),
2855 Some("Some Maintainer <example@example.com>")
2856 );
2857 assert_eq!(
2858 p.get("Build-Depends").as_deref(),
2859 Some("debhelper (>= 11~),\ndh-golang,\ngolang-any")
2860 );
2861 assert_eq!(
2862 p.get("Homepage").as_deref(),
2863 Some("https://github.com/j-keck/arping")
2864 );
2865 }
2866
2867 #[test]
2868 fn test_remove_field() {
2869 let d: super::Deb822 = r#"Source: foo
2870# Comment
2871Maintainer: Foo Bar <jelmer@jelmer.uk>
2872Section: net
2873
2874Package: foo
2875Architecture: all
2876Depends: libc6
2877Description: This is a description
2878 With details
2879"#
2880 .parse()
2881 .unwrap();
2882 let mut ps = d.paragraphs();
2883 let mut p = ps.next().unwrap();
2884 p.set("Foo", "Bar");
2885 p.remove("Section");
2886 p.remove("Nonexistent");
2887 assert_eq!(p.get("Foo").as_deref(), Some("Bar"));
2888 assert_eq!(
2889 p.to_string(),
2890 r#"Source: foo
2891# Comment
2892Maintainer: Foo Bar <jelmer@jelmer.uk>
2893Foo: Bar
2894"#
2895 );
2896 }
2897
2898 #[test]
2899 fn test_rename_field() {
2900 let d: super::Deb822 = r#"Source: foo
2901Vcs-Browser: https://salsa.debian.org/debian/foo
2902"#
2903 .parse()
2904 .unwrap();
2905 let mut ps = d.paragraphs();
2906 let mut p = ps.next().unwrap();
2907 assert!(p.rename("Vcs-Browser", "Homepage"));
2908 assert_eq!(
2909 p.to_string(),
2910 r#"Source: foo
2911Homepage: https://salsa.debian.org/debian/foo
2912"#
2913 );
2914
2915 assert_eq!(
2916 p.get("Homepage").as_deref(),
2917 Some("https://salsa.debian.org/debian/foo")
2918 );
2919 assert_eq!(p.get("Vcs-Browser").as_deref(), None);
2920
2921 assert!(!p.rename("Nonexistent", "Homepage"));
2923 }
2924
2925 #[test]
2926 fn test_set_field() {
2927 let d: super::Deb822 = r#"Source: foo
2928Maintainer: Foo Bar <joe@example.com>
2929"#
2930 .parse()
2931 .unwrap();
2932 let mut ps = d.paragraphs();
2933 let mut p = ps.next().unwrap();
2934 p.set("Maintainer", "Somebody Else <jane@example.com>");
2935 assert_eq!(
2936 p.get("Maintainer").as_deref(),
2937 Some("Somebody Else <jane@example.com>")
2938 );
2939 assert_eq!(
2940 p.to_string(),
2941 r#"Source: foo
2942Maintainer: Somebody Else <jane@example.com>
2943"#
2944 );
2945 }
2946
2947 #[test]
2948 fn test_set_new_field() {
2949 let d: super::Deb822 = r#"Source: foo
2950"#
2951 .parse()
2952 .unwrap();
2953 let mut ps = d.paragraphs();
2954 let mut p = ps.next().unwrap();
2955 p.set("Maintainer", "Somebody <joe@example.com>");
2956 assert_eq!(
2957 p.get("Maintainer").as_deref(),
2958 Some("Somebody <joe@example.com>")
2959 );
2960 assert_eq!(
2961 p.to_string(),
2962 r#"Source: foo
2963Maintainer: Somebody <joe@example.com>
2964"#
2965 );
2966 }
2967
2968 #[test]
2969 fn test_add_paragraph() {
2970 let mut d = super::Deb822::new();
2971 let mut p = d.add_paragraph();
2972 p.set("Foo", "Bar");
2973 assert_eq!(p.get("Foo").as_deref(), Some("Bar"));
2974 assert_eq!(
2975 p.to_string(),
2976 r#"Foo: Bar
2977"#
2978 );
2979 assert_eq!(
2980 d.to_string(),
2981 r#"Foo: Bar
2982"#
2983 );
2984
2985 let mut p = d.add_paragraph();
2986 p.set("Foo", "Blah");
2987 assert_eq!(p.get("Foo").as_deref(), Some("Blah"));
2988 assert_eq!(
2989 d.to_string(),
2990 r#"Foo: Bar
2991
2992Foo: Blah
2993"#
2994 );
2995 }
2996
2997 #[test]
2998 fn test_crud_paragraph() {
2999 let mut d = super::Deb822::new();
3000 let mut p = d.insert_paragraph(0);
3001 p.set("Foo", "Bar");
3002 assert_eq!(p.get("Foo").as_deref(), Some("Bar"));
3003 assert_eq!(
3004 d.to_string(),
3005 r#"Foo: Bar
3006"#
3007 );
3008
3009 let mut p = d.insert_paragraph(0);
3011 p.set("Foo", "Blah");
3012 assert_eq!(p.get("Foo").as_deref(), Some("Blah"));
3013 assert_eq!(
3014 d.to_string(),
3015 r#"Foo: Blah
3016
3017Foo: Bar
3018"#
3019 );
3020
3021 d.remove_paragraph(1);
3023 assert_eq!(d.to_string(), "Foo: Blah\n\n");
3024
3025 p.set("Foo", "Baz");
3027 assert_eq!(d.to_string(), "Foo: Baz\n\n");
3028
3029 d.remove_paragraph(0);
3031 assert_eq!(d.to_string(), "");
3032 }
3033
3034 #[test]
3035 fn test_swap_paragraphs() {
3036 let mut d: super::Deb822 = vec![
3038 vec![("Foo", "Bar")].into_iter().collect(),
3039 vec![("A", "B")].into_iter().collect(),
3040 vec![("X", "Y")].into_iter().collect(),
3041 ]
3042 .into_iter()
3043 .collect();
3044
3045 d.swap_paragraphs(0, 2);
3046 assert_eq!(d.to_string(), "X: Y\n\nA: B\n\nFoo: Bar\n");
3047
3048 d.swap_paragraphs(0, 2);
3050 assert_eq!(d.to_string(), "Foo: Bar\n\nA: B\n\nX: Y\n");
3051
3052 d.swap_paragraphs(0, 1);
3054 assert_eq!(d.to_string(), "A: B\n\nFoo: Bar\n\nX: Y\n");
3055
3056 let before = d.to_string();
3058 d.swap_paragraphs(1, 1);
3059 assert_eq!(d.to_string(), before);
3060 }
3061
3062 #[test]
3063 fn test_swap_paragraphs_preserves_content() {
3064 let mut d: super::Deb822 = vec![
3066 vec![("Field1", "Value1"), ("Field2", "Value2")]
3067 .into_iter()
3068 .collect(),
3069 vec![("FieldA", "ValueA"), ("FieldB", "ValueB")]
3070 .into_iter()
3071 .collect(),
3072 ]
3073 .into_iter()
3074 .collect();
3075
3076 d.swap_paragraphs(0, 1);
3077
3078 let mut paras = d.paragraphs();
3079 let p1 = paras.next().unwrap();
3080 assert_eq!(p1.get("FieldA").as_deref(), Some("ValueA"));
3081 assert_eq!(p1.get("FieldB").as_deref(), Some("ValueB"));
3082
3083 let p2 = paras.next().unwrap();
3084 assert_eq!(p2.get("Field1").as_deref(), Some("Value1"));
3085 assert_eq!(p2.get("Field2").as_deref(), Some("Value2"));
3086 }
3087
3088 #[test]
3089 #[should_panic(expected = "out of bounds")]
3090 fn test_swap_paragraphs_out_of_bounds() {
3091 let mut d: super::Deb822 = vec![
3092 vec![("Foo", "Bar")].into_iter().collect(),
3093 vec![("A", "B")].into_iter().collect(),
3094 ]
3095 .into_iter()
3096 .collect();
3097
3098 d.swap_paragraphs(0, 5);
3099 }
3100
3101 #[test]
3102 fn test_multiline_entry() {
3103 use super::SyntaxKind::*;
3104 use rowan::ast::AstNode;
3105
3106 let entry = super::Entry::new("foo", "bar\nbaz");
3107 let tokens: Vec<_> = entry
3108 .syntax()
3109 .descendants_with_tokens()
3110 .filter_map(|tok| tok.into_token())
3111 .collect();
3112
3113 assert_eq!("foo: bar\n baz\n", entry.to_string());
3114 assert_eq!("bar\nbaz", entry.value());
3115
3116 assert_eq!(
3117 vec![
3118 (KEY, "foo"),
3119 (COLON, ":"),
3120 (WHITESPACE, " "),
3121 (VALUE, "bar"),
3122 (NEWLINE, "\n"),
3123 (INDENT, " "),
3124 (VALUE, "baz"),
3125 (NEWLINE, "\n"),
3126 ],
3127 tokens
3128 .iter()
3129 .map(|token| (token.kind(), token.text()))
3130 .collect::<Vec<_>>()
3131 );
3132 }
3133
3134 #[test]
3135 fn test_apt_entry() {
3136 let text = r#"Package: cvsd
3137Binary: cvsd
3138Version: 1.0.24
3139Maintainer: Arthur de Jong <adejong@debian.org>
3140Build-Depends: debhelper (>= 9), po-debconf
3141Architecture: any
3142Standards-Version: 3.9.3
3143Format: 3.0 (native)
3144Files:
3145 b7a7d67a02974c52c408fdb5e118406d 890 cvsd_1.0.24.dsc
3146 b73ee40774c3086cb8490cdbb96ac883 258139 cvsd_1.0.24.tar.gz
3147Vcs-Browser: http://arthurdejong.org/viewvc/cvsd/
3148Vcs-Cvs: :pserver:anonymous@arthurdejong.org:/arthur/
3149Checksums-Sha256:
3150 a7bb7a3aacee19cd14ce5c26cb86e348b1608e6f1f6e97c6ea7c58efa440ac43 890 cvsd_1.0.24.dsc
3151 46bc517760c1070ae408693b89603986b53e6f068ae6bdc744e2e830e46b8cba 258139 cvsd_1.0.24.tar.gz
3152Homepage: http://arthurdejong.org/cvsd/
3153Package-List:
3154 cvsd deb vcs optional
3155Directory: pool/main/c/cvsd
3156Priority: source
3157Section: vcs
3158
3159"#;
3160 let d: super::Deb822 = text.parse().unwrap();
3161 let p = d.paragraphs().next().unwrap();
3162 assert_eq!(p.get("Binary").as_deref(), Some("cvsd"));
3163 assert_eq!(p.get("Version").as_deref(), Some("1.0.24"));
3164 assert_eq!(
3165 p.get("Maintainer").as_deref(),
3166 Some("Arthur de Jong <adejong@debian.org>")
3167 );
3168 }
3169
3170 #[test]
3171 fn test_format() {
3172 let d: super::Deb822 = r#"Source: foo
3173Maintainer: Foo Bar <foo@example.com>
3174Section: net
3175Blah: blah # comment
3176Multi-Line:
3177 Ahoi!
3178 Matey!
3179
3180"#
3181 .parse()
3182 .unwrap();
3183 let mut ps = d.paragraphs();
3184 let p = ps.next().unwrap();
3185 let result = p.wrap_and_sort(
3186 crate::Indentation::FieldNameLength,
3187 false,
3188 None,
3189 None::<&dyn Fn(&super::Entry, &super::Entry) -> std::cmp::Ordering>,
3190 None,
3191 );
3192 assert_eq!(
3193 result.to_string(),
3194 r#"Source: foo
3195Maintainer: Foo Bar <foo@example.com>
3196Section: net
3197Blah: blah # comment
3198Multi-Line: Ahoi!
3199 Matey!
3200"#
3201 );
3202 }
3203
3204 #[test]
3205 fn test_format_sort_paragraphs() {
3206 let d: super::Deb822 = r#"Source: foo
3207Maintainer: Foo Bar <foo@example.com>
3208
3209# This is a comment
3210Source: bar
3211Maintainer: Bar Foo <bar@example.com>
3212
3213"#
3214 .parse()
3215 .unwrap();
3216 let result = d.wrap_and_sort(
3217 Some(&|a: &super::Paragraph, b: &super::Paragraph| {
3218 a.get("Source").cmp(&b.get("Source"))
3219 }),
3220 Some(&|p| {
3221 p.wrap_and_sort(
3222 crate::Indentation::FieldNameLength,
3223 false,
3224 None,
3225 None::<&dyn Fn(&super::Entry, &super::Entry) -> std::cmp::Ordering>,
3226 None,
3227 )
3228 }),
3229 );
3230 assert_eq!(
3231 result.to_string(),
3232 r#"# This is a comment
3233Source: bar
3234Maintainer: Bar Foo <bar@example.com>
3235
3236Source: foo
3237Maintainer: Foo Bar <foo@example.com>
3238"#,
3239 );
3240 }
3241
3242 #[test]
3243 fn test_format_sort_fields() {
3244 let d: super::Deb822 = r#"Source: foo
3245Maintainer: Foo Bar <foo@example.com>
3246Build-Depends: debhelper (>= 9), po-debconf
3247Homepage: https://example.com/
3248
3249"#
3250 .parse()
3251 .unwrap();
3252 let result = d.wrap_and_sort(
3253 None,
3254 Some(&|p: &super::Paragraph| -> super::Paragraph {
3255 p.wrap_and_sort(
3256 crate::Indentation::FieldNameLength,
3257 false,
3258 None,
3259 Some(&|a: &super::Entry, b: &super::Entry| a.key().cmp(&b.key())),
3260 None,
3261 )
3262 }),
3263 );
3264 assert_eq!(
3265 result.to_string(),
3266 r#"Build-Depends: debhelper (>= 9), po-debconf
3267Homepage: https://example.com/
3268Maintainer: Foo Bar <foo@example.com>
3269Source: foo
3270"#
3271 );
3272 }
3273
3274 #[test]
3275 fn test_para_from_iter() {
3276 let p: super::Paragraph = vec![("Foo", "Bar"), ("Baz", "Qux")].into_iter().collect();
3277 assert_eq!(
3278 p.to_string(),
3279 r#"Foo: Bar
3280Baz: Qux
3281"#
3282 );
3283
3284 let p: super::Paragraph = vec![
3285 ("Foo".to_string(), "Bar".to_string()),
3286 ("Baz".to_string(), "Qux".to_string()),
3287 ]
3288 .into_iter()
3289 .collect();
3290
3291 assert_eq!(
3292 p.to_string(),
3293 r#"Foo: Bar
3294Baz: Qux
3295"#
3296 );
3297 }
3298
3299 #[test]
3300 fn test_deb822_from_iter() {
3301 let d: super::Deb822 = vec![
3302 vec![("Foo", "Bar"), ("Baz", "Qux")].into_iter().collect(),
3303 vec![("A", "B"), ("C", "D")].into_iter().collect(),
3304 ]
3305 .into_iter()
3306 .collect();
3307 assert_eq!(
3308 d.to_string(),
3309 r#"Foo: Bar
3310Baz: Qux
3311
3312A: B
3313C: D
3314"#
3315 );
3316 }
3317
3318 #[test]
3319 fn test_format_parse_error() {
3320 assert_eq!(ParseError(vec!["foo".to_string()]).to_string(), "foo\n");
3321 }
3322
3323 #[test]
3324 fn test_set_with_field_order() {
3325 let mut p = super::Paragraph::new();
3326 let custom_order = &["Foo", "Bar", "Baz"];
3327
3328 p.set_with_field_order("Baz", "3", custom_order);
3329 p.set_with_field_order("Foo", "1", custom_order);
3330 p.set_with_field_order("Bar", "2", custom_order);
3331 p.set_with_field_order("Unknown", "4", custom_order);
3332
3333 let keys: Vec<_> = p.keys().collect();
3334 assert_eq!(keys[0], "Foo");
3335 assert_eq!(keys[1], "Bar");
3336 assert_eq!(keys[2], "Baz");
3337 assert_eq!(keys[3], "Unknown");
3338 }
3339
3340 #[test]
3341 fn test_positioned_parse_error() {
3342 let error = PositionedParseError {
3343 message: "test error".to_string(),
3344 range: rowan::TextRange::new(rowan::TextSize::from(5), rowan::TextSize::from(10)),
3345 code: Some("test_code".to_string()),
3346 };
3347 assert_eq!(error.to_string(), "test error");
3348 assert_eq!(error.range.start(), rowan::TextSize::from(5));
3349 assert_eq!(error.range.end(), rowan::TextSize::from(10));
3350 assert_eq!(error.code, Some("test_code".to_string()));
3351 }
3352
3353 #[test]
3354 fn test_format_error() {
3355 assert_eq!(
3356 super::Error::ParseError(ParseError(vec!["foo".to_string()])).to_string(),
3357 "foo\n"
3358 );
3359 }
3360
3361 #[test]
3362 fn test_get_all() {
3363 let d: super::Deb822 = r#"Source: foo
3364Maintainer: Foo Bar <foo@example.com>
3365Maintainer: Bar Foo <bar@example.com>"#
3366 .parse()
3367 .unwrap();
3368 let p = d.paragraphs().next().unwrap();
3369 assert_eq!(
3370 p.get_all("Maintainer").collect::<Vec<_>>(),
3371 vec!["Foo Bar <foo@example.com>", "Bar Foo <bar@example.com>"]
3372 );
3373 }
3374
3375 #[test]
3376 fn test_get_with_indent_single_line() {
3377 let input = "Field: single line value\n";
3378 let deb = super::Deb822::from_str(input).unwrap();
3379 let para = deb.paragraphs().next().unwrap();
3380
3381 assert_eq!(
3383 para.get_with_indent("Field", &super::IndentPattern::Fixed(2)),
3384 Some("single line value".to_string())
3385 );
3386 assert_eq!(
3387 para.get_with_indent("Field", &super::IndentPattern::FieldNameLength),
3388 Some("single line value".to_string())
3389 );
3390 }
3391
3392 #[test]
3393 fn test_get_with_indent_fixed() {
3394 let input = "Field: First\n Second\n Third\n";
3395 let deb = super::Deb822::from_str(input).unwrap();
3396 let para = deb.paragraphs().next().unwrap();
3397
3398 let value = para
3400 .get_with_indent("Field", &super::IndentPattern::Fixed(2))
3401 .unwrap();
3402 assert_eq!(value, "First\n Second\n Third");
3403
3404 let value = para
3406 .get_with_indent("Field", &super::IndentPattern::Fixed(1))
3407 .unwrap();
3408 assert_eq!(value, "First\n Second\n Third");
3409
3410 let value = para
3412 .get_with_indent("Field", &super::IndentPattern::Fixed(3))
3413 .unwrap();
3414 assert_eq!(value, "First\nSecond\nThird");
3415 }
3416
3417 #[test]
3418 fn test_get_with_indent_field_name_length() {
3419 let input = "Description: First line\n Second line\n Third line\n";
3420 let deb = super::Deb822::from_str(input).unwrap();
3421 let para = deb.paragraphs().next().unwrap();
3422
3423 let value = para
3426 .get_with_indent("Description", &super::IndentPattern::FieldNameLength)
3427 .unwrap();
3428 assert_eq!(value, "First line\nSecond line\nThird line");
3429
3430 let value = para
3432 .get_with_indent("Description", &super::IndentPattern::Fixed(2))
3433 .unwrap();
3434 assert_eq!(
3435 value,
3436 "First line\n Second line\n Third line"
3437 );
3438 }
3439
3440 #[test]
3441 fn test_get_with_indent_nonexistent() {
3442 let input = "Field: value\n";
3443 let deb = super::Deb822::from_str(input).unwrap();
3444 let para = deb.paragraphs().next().unwrap();
3445
3446 assert_eq!(
3447 para.get_with_indent("NonExistent", &super::IndentPattern::Fixed(2)),
3448 None
3449 );
3450 }
3451
3452 #[test]
3453 fn test_get_entry() {
3454 let input = r#"Package: test-package
3455Maintainer: Test User <test@example.com>
3456Description: A simple test package
3457 with multiple lines
3458"#;
3459 let deb = super::Deb822::from_str(input).unwrap();
3460 let para = deb.paragraphs().next().unwrap();
3461
3462 let entry = para.get_entry("Package");
3464 assert!(entry.is_some());
3465 let entry = entry.unwrap();
3466 assert_eq!(entry.key(), Some("Package".to_string()));
3467 assert_eq!(entry.value(), "test-package");
3468
3469 let entry = para.get_entry("package");
3471 assert!(entry.is_some());
3472 assert_eq!(entry.unwrap().value(), "test-package");
3473
3474 let entry = para.get_entry("Description");
3476 assert!(entry.is_some());
3477 assert_eq!(
3478 entry.unwrap().value(),
3479 "A simple test package\nwith multiple lines"
3480 );
3481
3482 assert_eq!(para.get_entry("NonExistent"), None);
3484 }
3485
3486 #[test]
3487 fn test_entry_ranges() {
3488 let input = r#"Package: test-package
3489Maintainer: Test User <test@example.com>
3490Description: A simple test package
3491 with multiple lines
3492 of description text"#;
3493
3494 let deb822 = super::Deb822::from_str(input).unwrap();
3495 let paragraph = deb822.paragraphs().next().unwrap();
3496 let entries: Vec<_> = paragraph.entries().collect();
3497
3498 let package_entry = &entries[0];
3500 assert_eq!(package_entry.key(), Some("Package".to_string()));
3501
3502 let key_range = package_entry.key_range().unwrap();
3504 assert_eq!(
3505 &input[key_range.start().into()..key_range.end().into()],
3506 "Package"
3507 );
3508
3509 let colon_range = package_entry.colon_range().unwrap();
3511 assert_eq!(
3512 &input[colon_range.start().into()..colon_range.end().into()],
3513 ":"
3514 );
3515
3516 let value_range = package_entry.value_range().unwrap();
3518 assert_eq!(
3519 &input[value_range.start().into()..value_range.end().into()],
3520 "test-package"
3521 );
3522
3523 let text_range = package_entry.text_range();
3525 assert_eq!(
3526 &input[text_range.start().into()..text_range.end().into()],
3527 "Package: test-package\n"
3528 );
3529
3530 let value_lines = package_entry.value_line_ranges();
3532 assert_eq!(value_lines.len(), 1);
3533 assert_eq!(
3534 &input[value_lines[0].start().into()..value_lines[0].end().into()],
3535 "test-package"
3536 );
3537 }
3538
3539 #[test]
3540 fn test_multiline_entry_ranges() {
3541 let input = r#"Description: Short description
3542 Extended description line 1
3543 Extended description line 2"#;
3544
3545 let deb822 = super::Deb822::from_str(input).unwrap();
3546 let paragraph = deb822.paragraphs().next().unwrap();
3547 let entry = paragraph.entries().next().unwrap();
3548
3549 assert_eq!(entry.key(), Some("Description".to_string()));
3550
3551 let value_range = entry.value_range().unwrap();
3553 let full_value = &input[value_range.start().into()..value_range.end().into()];
3554 assert!(full_value.contains("Short description"));
3555 assert!(full_value.contains("Extended description line 1"));
3556 assert!(full_value.contains("Extended description line 2"));
3557
3558 let value_lines = entry.value_line_ranges();
3560 assert_eq!(value_lines.len(), 3);
3561
3562 assert_eq!(
3563 &input[value_lines[0].start().into()..value_lines[0].end().into()],
3564 "Short description"
3565 );
3566 assert_eq!(
3567 &input[value_lines[1].start().into()..value_lines[1].end().into()],
3568 "Extended description line 1"
3569 );
3570 assert_eq!(
3571 &input[value_lines[2].start().into()..value_lines[2].end().into()],
3572 "Extended description line 2"
3573 );
3574 }
3575
3576 #[test]
3577 fn test_entries_public_access() {
3578 let input = r#"Package: test
3579Version: 1.0"#;
3580
3581 let deb822 = super::Deb822::from_str(input).unwrap();
3582 let paragraph = deb822.paragraphs().next().unwrap();
3583
3584 let entries: Vec<_> = paragraph.entries().collect();
3586 assert_eq!(entries.len(), 2);
3587 assert_eq!(entries[0].key(), Some("Package".to_string()));
3588 assert_eq!(entries[1].key(), Some("Version".to_string()));
3589 }
3590
3591 #[test]
3592 fn test_empty_value_ranges() {
3593 let input = r#"EmptyField: "#;
3594
3595 let deb822 = super::Deb822::from_str(input).unwrap();
3596 let paragraph = deb822.paragraphs().next().unwrap();
3597 let entry = paragraph.entries().next().unwrap();
3598
3599 assert_eq!(entry.key(), Some("EmptyField".to_string()));
3600
3601 assert!(entry.key_range().is_some());
3603 assert!(entry.colon_range().is_some());
3604
3605 let value_lines = entry.value_line_ranges();
3607 assert!(value_lines.len() <= 1);
3610 }
3611
3612 #[test]
3613 fn test_range_ordering() {
3614 let input = r#"Field: value"#;
3615
3616 let deb822 = super::Deb822::from_str(input).unwrap();
3617 let paragraph = deb822.paragraphs().next().unwrap();
3618 let entry = paragraph.entries().next().unwrap();
3619
3620 let key_range = entry.key_range().unwrap();
3621 let colon_range = entry.colon_range().unwrap();
3622 let value_range = entry.value_range().unwrap();
3623 let text_range = entry.text_range();
3624
3625 assert!(key_range.end() <= colon_range.start());
3627 assert!(colon_range.end() <= value_range.start());
3628 assert!(key_range.start() >= text_range.start());
3629 assert!(value_range.end() <= text_range.end());
3630 }
3631
3632 #[test]
3633 fn test_error_recovery_missing_colon() {
3634 let input = r#"Source foo
3635Maintainer: Test User <test@example.com>
3636"#;
3637 let (deb822, errors) = super::Deb822::from_str_relaxed(input);
3638
3639 assert!(!errors.is_empty());
3641 assert!(errors.iter().any(|e| e.contains("missing colon")));
3642
3643 let paragraph = deb822.paragraphs().next().unwrap();
3645 assert_eq!(
3646 paragraph.get("Maintainer").as_deref(),
3647 Some("Test User <test@example.com>")
3648 );
3649 }
3650
3651 #[test]
3652 fn test_error_recovery_missing_field_name() {
3653 let input = r#": orphaned value
3654Package: test
3655"#;
3656
3657 let (deb822, errors) = super::Deb822::from_str_relaxed(input);
3658
3659 assert!(!errors.is_empty());
3661 assert!(errors
3662 .iter()
3663 .any(|e| e.contains("field name") || e.contains("missing")));
3664
3665 let paragraphs: Vec<_> = deb822.paragraphs().collect();
3667 let mut found_package = false;
3668 for paragraph in paragraphs.iter() {
3669 if paragraph.get("Package").is_some() {
3670 found_package = true;
3671 assert_eq!(paragraph.get("Package").as_deref(), Some("test"));
3672 }
3673 }
3674 assert!(found_package, "Package field not found in any paragraph");
3675 }
3676
3677 #[test]
3678 fn test_error_recovery_orphaned_text() {
3679 let input = r#"Package: test
3680some orphaned text without field name
3681Version: 1.0
3682"#;
3683 let (deb822, errors) = super::Deb822::from_str_relaxed(input);
3684
3685 assert!(!errors.is_empty());
3687 assert!(errors.iter().any(|e| e.contains("orphaned")
3688 || e.contains("unexpected")
3689 || e.contains("field name")));
3690
3691 let mut all_fields = std::collections::HashMap::new();
3693 for paragraph in deb822.paragraphs() {
3694 for (key, value) in paragraph.items() {
3695 all_fields.insert(key, value);
3696 }
3697 }
3698
3699 assert_eq!(all_fields.get("Package"), Some(&"test".to_string()));
3700 assert_eq!(all_fields.get("Version"), Some(&"1.0".to_string()));
3701 }
3702
3703 #[test]
3704 fn test_error_recovery_consecutive_field_names() {
3705 let input = r#"Package: test
3706Description
3707Maintainer: Another field without proper value
3708Version: 1.0
3709"#;
3710 let (deb822, errors) = super::Deb822::from_str_relaxed(input);
3711
3712 assert!(!errors.is_empty());
3714 assert!(errors.iter().any(|e| e.contains("consecutive")
3715 || e.contains("missing")
3716 || e.contains("incomplete")));
3717
3718 let mut all_fields = std::collections::HashMap::new();
3720 for paragraph in deb822.paragraphs() {
3721 for (key, value) in paragraph.items() {
3722 all_fields.insert(key, value);
3723 }
3724 }
3725
3726 assert_eq!(all_fields.get("Package"), Some(&"test".to_string()));
3727 assert_eq!(
3728 all_fields.get("Maintainer"),
3729 Some(&"Another field without proper value".to_string())
3730 );
3731 assert_eq!(all_fields.get("Version"), Some(&"1.0".to_string()));
3732 }
3733
3734 #[test]
3735 fn test_error_recovery_malformed_multiline() {
3736 let input = r#"Package: test
3737Description: Short desc
3738 Proper continuation
3739invalid continuation without indent
3740 Another proper continuation
3741Version: 1.0
3742"#;
3743 let (deb822, errors) = super::Deb822::from_str_relaxed(input);
3744
3745 assert!(!errors.is_empty());
3747
3748 let paragraph = deb822.paragraphs().next().unwrap();
3750 assert_eq!(paragraph.get("Package").as_deref(), Some("test"));
3751 assert_eq!(paragraph.get("Version").as_deref(), Some("1.0"));
3752 }
3753
3754 #[test]
3755 fn test_error_recovery_mixed_errors() {
3756 let input = r#"Package test without colon
3757: orphaned colon
3758Description: Valid field
3759some orphaned text
3760Another-Field: Valid too
3761"#;
3762 let (deb822, errors) = super::Deb822::from_str_relaxed(input);
3763
3764 assert!(!errors.is_empty());
3766 assert!(errors.len() >= 2);
3767
3768 let paragraph = deb822.paragraphs().next().unwrap();
3770 assert_eq!(paragraph.get("Description").as_deref(), Some("Valid field"));
3771 assert_eq!(paragraph.get("Another-Field").as_deref(), Some("Valid too"));
3772 }
3773
3774 #[test]
3775 fn test_error_recovery_paragraph_boundary() {
3776 let input = r#"Package: first-package
3777Description: First paragraph
3778
3779corrupted data here
3780: more corruption
3781completely broken line
3782
3783Package: second-package
3784Version: 1.0
3785"#;
3786 let (deb822, errors) = super::Deb822::from_str_relaxed(input);
3787
3788 assert!(!errors.is_empty());
3790
3791 let paragraphs: Vec<_> = deb822.paragraphs().collect();
3793 assert_eq!(paragraphs.len(), 2);
3794
3795 assert_eq!(
3796 paragraphs[0].get("Package").as_deref(),
3797 Some("first-package")
3798 );
3799 assert_eq!(
3800 paragraphs[1].get("Package").as_deref(),
3801 Some("second-package")
3802 );
3803 assert_eq!(paragraphs[1].get("Version").as_deref(), Some("1.0"));
3804 }
3805
3806 #[test]
3807 fn test_error_recovery_with_positioned_errors() {
3808 let input = r#"Package test
3809Description: Valid
3810"#;
3811 let parsed = super::parse(input);
3812
3813 assert!(!parsed.positioned_errors.is_empty());
3815
3816 let first_error = &parsed.positioned_errors[0];
3817 assert!(!first_error.message.is_empty());
3818 assert!(first_error.range.start() <= first_error.range.end());
3819 assert!(first_error.code.is_some());
3820
3821 let error_text = &input[first_error.range.start().into()..first_error.range.end().into()];
3823 assert!(!error_text.is_empty());
3824 }
3825
3826 #[test]
3827 fn test_error_recovery_preserves_whitespace() {
3828 let input = r#"Source: package
3829Maintainer Test User <test@example.com>
3830Section: utils
3831
3832"#;
3833 let (deb822, errors) = super::Deb822::from_str_relaxed(input);
3834
3835 assert!(!errors.is_empty());
3837
3838 let output = deb822.to_string();
3840 assert!(output.contains("Section: utils"));
3841
3842 let paragraph = deb822.paragraphs().next().unwrap();
3844 assert_eq!(paragraph.get("Source").as_deref(), Some("package"));
3845 assert_eq!(paragraph.get("Section").as_deref(), Some("utils"));
3846 }
3847
3848 #[test]
3849 fn test_error_recovery_empty_fields() {
3850 let input = r#"Package: test
3851Description:
3852Maintainer: Valid User
3853EmptyField:
3854Version: 1.0
3855"#;
3856 let (deb822, _errors) = super::Deb822::from_str_relaxed(input);
3857
3858 let mut all_fields = std::collections::HashMap::new();
3860 for paragraph in deb822.paragraphs() {
3861 for (key, value) in paragraph.items() {
3862 all_fields.insert(key, value);
3863 }
3864 }
3865
3866 assert_eq!(all_fields.get("Package"), Some(&"test".to_string()));
3867 assert_eq!(all_fields.get("Description"), Some(&"".to_string()));
3868 assert_eq!(
3869 all_fields.get("Maintainer"),
3870 Some(&"Valid User".to_string())
3871 );
3872 assert_eq!(all_fields.get("EmptyField"), Some(&"".to_string()));
3873 assert_eq!(all_fields.get("Version"), Some(&"1.0".to_string()));
3874 }
3875
3876 #[test]
3877 fn test_insert_comment_before() {
3878 let d: super::Deb822 = vec![
3879 vec![("Source", "foo"), ("Maintainer", "Bar <bar@example.com>")]
3880 .into_iter()
3881 .collect(),
3882 vec![("Package", "foo"), ("Architecture", "all")]
3883 .into_iter()
3884 .collect(),
3885 ]
3886 .into_iter()
3887 .collect();
3888
3889 let mut p1 = d.paragraphs().next().unwrap();
3891 p1.insert_comment_before("This is the source paragraph");
3892
3893 let mut p2 = d.paragraphs().nth(1).unwrap();
3895 p2.insert_comment_before("This is the binary paragraph");
3896
3897 let output = d.to_string();
3898 assert_eq!(
3899 output,
3900 r#"# This is the source paragraph
3901Source: foo
3902Maintainer: Bar <bar@example.com>
3903
3904# This is the binary paragraph
3905Package: foo
3906Architecture: all
3907"#
3908 );
3909 }
3910
3911 #[test]
3912 fn test_parse_continuation_with_colon() {
3913 let input = "Package: test\nDescription: short\n line: with colon\n";
3915 let result = input.parse::<Deb822>();
3916 assert!(result.is_ok());
3917
3918 let deb822 = result.unwrap();
3919 let para = deb822.paragraphs().next().unwrap();
3920 assert_eq!(para.get("Package").as_deref(), Some("test"));
3921 assert_eq!(
3922 para.get("Description").as_deref(),
3923 Some("short\nline: with colon")
3924 );
3925 }
3926
3927 #[test]
3928 fn test_parse_continuation_starting_with_colon() {
3929 let input = "Package: test\nDescription: short\n :value\n";
3931 let result = input.parse::<Deb822>();
3932 assert!(result.is_ok());
3933
3934 let deb822 = result.unwrap();
3935 let para = deb822.paragraphs().next().unwrap();
3936 assert_eq!(para.get("Package").as_deref(), Some("test"));
3937 assert_eq!(para.get("Description").as_deref(), Some("short\n:value"));
3938 }
3939
3940 #[test]
3941 fn test_normalize_field_spacing_single_space() {
3942 let input = "Field: value\n";
3944 let deb822 = input.parse::<Deb822>().unwrap();
3945 let mut para = deb822.paragraphs().next().unwrap();
3946
3947 para.normalize_field_spacing();
3948 assert_eq!(para.to_string(), "Field: value\n");
3949 }
3950
3951 #[test]
3952 fn test_normalize_field_spacing_extra_spaces() {
3953 let input = "Field: value\n";
3955 let deb822 = input.parse::<Deb822>().unwrap();
3956 let mut para = deb822.paragraphs().next().unwrap();
3957
3958 para.normalize_field_spacing();
3959 assert_eq!(para.to_string(), "Field: value\n");
3960 }
3961
3962 #[test]
3963 fn test_normalize_field_spacing_no_space() {
3964 let input = "Field:value\n";
3966 let deb822 = input.parse::<Deb822>().unwrap();
3967 let mut para = deb822.paragraphs().next().unwrap();
3968
3969 para.normalize_field_spacing();
3970 assert_eq!(para.to_string(), "Field: value\n");
3971 }
3972
3973 #[test]
3974 fn test_normalize_field_spacing_multiple_fields() {
3975 let input = "Field1: value1\nField2:value2\nField3: value3\n";
3977 let deb822 = input.parse::<Deb822>().unwrap();
3978 let mut para = deb822.paragraphs().next().unwrap();
3979
3980 para.normalize_field_spacing();
3981 assert_eq!(
3982 para.to_string(),
3983 "Field1: value1\nField2: value2\nField3: value3\n"
3984 );
3985 }
3986
3987 #[test]
3988 fn test_normalize_field_spacing_multiline_value() {
3989 let input = "Description: short\n continuation line\n . \n final line\n";
3991 let deb822 = input.parse::<Deb822>().unwrap();
3992 let mut para = deb822.paragraphs().next().unwrap();
3993
3994 para.normalize_field_spacing();
3995 assert_eq!(
3996 para.to_string(),
3997 "Description: short\n continuation line\n . \n final line\n"
3998 );
3999 }
4000
4001 #[test]
4002 fn test_normalize_field_spacing_empty_value_with_whitespace() {
4003 let input = "Field: \n";
4005 let deb822 = input.parse::<Deb822>().unwrap();
4006 let mut para = deb822.paragraphs().next().unwrap();
4007
4008 para.normalize_field_spacing();
4009 assert_eq!(para.to_string(), "Field:\n");
4011 }
4012
4013 #[test]
4014 fn test_normalize_field_spacing_no_value() {
4015 let input = "Depends:\n";
4017 let deb822 = input.parse::<Deb822>().unwrap();
4018 let mut para = deb822.paragraphs().next().unwrap();
4019
4020 para.normalize_field_spacing();
4021 assert_eq!(para.to_string(), "Depends:\n");
4023 }
4024
4025 #[test]
4026 fn test_normalize_field_spacing_multiple_paragraphs() {
4027 let input = "Field1: value1\n\nField2: value2\n";
4029 let mut deb822 = input.parse::<Deb822>().unwrap();
4030
4031 deb822.normalize_field_spacing();
4032 assert_eq!(deb822.to_string(), "Field1: value1\n\nField2: value2\n");
4033 }
4034
4035 #[test]
4036 fn test_normalize_field_spacing_preserves_comments() {
4037 let input = "# Comment\nField: value\n";
4039 let mut deb822 = input.parse::<Deb822>().unwrap();
4040
4041 deb822.normalize_field_spacing();
4042 assert_eq!(deb822.to_string(), "# Comment\nField: value\n");
4043 }
4044
4045 #[test]
4046 fn test_normalize_field_spacing_preserves_values() {
4047 let input = "Source: foo-bar\nMaintainer:Foo Bar <test@example.com>\n";
4049 let deb822 = input.parse::<Deb822>().unwrap();
4050 let mut para = deb822.paragraphs().next().unwrap();
4051
4052 para.normalize_field_spacing();
4053
4054 assert_eq!(para.get("Source").as_deref(), Some("foo-bar"));
4055 assert_eq!(
4056 para.get("Maintainer").as_deref(),
4057 Some("Foo Bar <test@example.com>")
4058 );
4059 }
4060
4061 #[test]
4062 fn test_normalize_field_spacing_tab_after_colon() {
4063 let input = "Field:\tvalue\n";
4065 let deb822 = input.parse::<Deb822>().unwrap();
4066 let mut para = deb822.paragraphs().next().unwrap();
4067
4068 para.normalize_field_spacing();
4069 assert_eq!(para.to_string(), "Field: value\n");
4070 }
4071
4072 #[test]
4073 fn test_set_preserves_indentation() {
4074 let original = r#"Source: example
4076Build-Depends: foo,
4077 bar,
4078 baz
4079"#;
4080
4081 let mut para: super::Paragraph = original.parse().unwrap();
4082
4083 para.set("Build-Depends", "foo,\nbar,\nbaz");
4085
4086 let expected = r#"Source: example
4088Build-Depends: foo,
4089 bar,
4090 baz
4091"#;
4092 assert_eq!(para.to_string(), expected);
4093 }
4094
4095 #[test]
4096 fn test_set_new_field_detects_field_name_length_indent() {
4097 let original = r#"Source: example
4099Build-Depends: foo,
4100 bar,
4101 baz
4102Depends: lib1,
4103 lib2
4104"#;
4105
4106 let mut para: super::Paragraph = original.parse().unwrap();
4107
4108 para.set("Recommends", "pkg1,\npkg2,\npkg3");
4110
4111 assert!(para
4113 .to_string()
4114 .contains("Recommends: pkg1,\n pkg2,"));
4115 }
4116
4117 #[test]
4118 fn test_set_new_field_detects_fixed_indent() {
4119 let original = r#"Source: example
4121Build-Depends: foo,
4122 bar,
4123 baz
4124Depends: lib1,
4125 lib2
4126"#;
4127
4128 let mut para: super::Paragraph = original.parse().unwrap();
4129
4130 para.set("Recommends", "pkg1,\npkg2,\npkg3");
4132
4133 assert!(para
4135 .to_string()
4136 .contains("Recommends: pkg1,\n pkg2,\n pkg3\n"));
4137 }
4138
4139 #[test]
4140 fn test_set_new_field_no_multiline_fields() {
4141 let original = r#"Source: example
4143Maintainer: Test <test@example.com>
4144"#;
4145
4146 let mut para: super::Paragraph = original.parse().unwrap();
4147
4148 para.set("Depends", "foo,\nbar,\nbaz");
4150
4151 let expected = r#"Source: example
4153Maintainer: Test <test@example.com>
4154Depends: foo,
4155 bar,
4156 baz
4157"#;
4158 assert_eq!(para.to_string(), expected);
4159 }
4160
4161 #[test]
4162 fn test_set_new_field_mixed_indentation() {
4163 let original = r#"Source: example
4165Build-Depends: foo,
4166 bar
4167Depends: lib1,
4168 lib2
4169"#;
4170
4171 let mut para: super::Paragraph = original.parse().unwrap();
4172
4173 para.set("Recommends", "pkg1,\npkg2");
4175
4176 assert!(para
4178 .to_string()
4179 .contains("Recommends: pkg1,\n pkg2\n"));
4180 }
4181
4182 #[test]
4183 fn test_entry_with_indentation() {
4184 let entry = super::Entry::with_indentation("Test-Field", "value1\nvalue2\nvalue3", " ");
4186
4187 assert_eq!(
4188 entry.to_string(),
4189 "Test-Field: value1\n value2\n value3\n"
4190 );
4191 }
4192
4193 #[test]
4194 fn test_set_with_indent_pattern_fixed() {
4195 let original = r#"Source: example
4197Maintainer: Test <test@example.com>
4198"#;
4199
4200 let mut para: super::Paragraph = original.parse().unwrap();
4201
4202 para.set_with_indent_pattern(
4204 "Depends",
4205 "foo,\nbar,\nbaz",
4206 Some(&super::IndentPattern::Fixed(4)),
4207 None,
4208 );
4209
4210 let expected = r#"Source: example
4212Maintainer: Test <test@example.com>
4213Depends: foo,
4214 bar,
4215 baz
4216"#;
4217 assert_eq!(para.to_string(), expected);
4218 }
4219
4220 #[test]
4221 fn test_set_with_indent_pattern_field_name_length() {
4222 let original = r#"Source: example
4224Maintainer: Test <test@example.com>
4225"#;
4226
4227 let mut para: super::Paragraph = original.parse().unwrap();
4228
4229 para.set_with_indent_pattern(
4231 "Build-Depends",
4232 "libfoo,\nlibbar,\nlibbaz",
4233 Some(&super::IndentPattern::FieldNameLength),
4234 None,
4235 );
4236
4237 let expected = r#"Source: example
4239Maintainer: Test <test@example.com>
4240Build-Depends: libfoo,
4241 libbar,
4242 libbaz
4243"#;
4244 assert_eq!(para.to_string(), expected);
4245 }
4246
4247 #[test]
4248 fn test_set_with_indent_pattern_override_auto_detection() {
4249 let original = r#"Source: example
4251Build-Depends: foo,
4252 bar,
4253 baz
4254"#;
4255
4256 let mut para: super::Paragraph = original.parse().unwrap();
4257
4258 para.set_with_indent_pattern(
4260 "Depends",
4261 "lib1,\nlib2,\nlib3",
4262 Some(&super::IndentPattern::Fixed(2)),
4263 None,
4264 );
4265
4266 let expected = r#"Source: example
4268Build-Depends: foo,
4269 bar,
4270 baz
4271Depends: lib1,
4272 lib2,
4273 lib3
4274"#;
4275 assert_eq!(para.to_string(), expected);
4276 }
4277
4278 #[test]
4279 fn test_set_with_indent_pattern_none_auto_detects() {
4280 let original = r#"Source: example
4282Build-Depends: foo,
4283 bar,
4284 baz
4285"#;
4286
4287 let mut para: super::Paragraph = original.parse().unwrap();
4288
4289 para.set_with_indent_pattern("Depends", "lib1,\nlib2", None, None);
4291
4292 let expected = r#"Source: example
4294Build-Depends: foo,
4295 bar,
4296 baz
4297Depends: lib1,
4298 lib2
4299"#;
4300 assert_eq!(para.to_string(), expected);
4301 }
4302
4303 #[test]
4304 fn test_set_with_indent_pattern_with_field_order() {
4305 let original = r#"Source: example
4307Maintainer: Test <test@example.com>
4308"#;
4309
4310 let mut para: super::Paragraph = original.parse().unwrap();
4311
4312 para.set_with_indent_pattern(
4314 "Priority",
4315 "optional",
4316 Some(&super::IndentPattern::Fixed(4)),
4317 Some(&["Source", "Priority", "Maintainer"]),
4318 );
4319
4320 let expected = r#"Source: example
4322Priority: optional
4323Maintainer: Test <test@example.com>
4324"#;
4325 assert_eq!(para.to_string(), expected);
4326 }
4327
4328 #[test]
4329 fn test_set_with_indent_pattern_replace_existing() {
4330 let original = r#"Source: example
4332Depends: foo,
4333 bar
4334"#;
4335
4336 let mut para: super::Paragraph = original.parse().unwrap();
4337
4338 para.set_with_indent_pattern(
4340 "Depends",
4341 "lib1,\nlib2,\nlib3",
4342 Some(&super::IndentPattern::Fixed(3)),
4343 None,
4344 );
4345
4346 let expected = r#"Source: example
4348Depends: lib1,
4349 lib2,
4350 lib3
4351"#;
4352 assert_eq!(para.to_string(), expected);
4353 }
4354
4355 #[test]
4356 fn test_change_field_indent() {
4357 let original = r#"Source: example
4359Depends: foo,
4360 bar,
4361 baz
4362"#;
4363 let mut para: super::Paragraph = original.parse().unwrap();
4364
4365 let result = para
4367 .change_field_indent("Depends", &super::IndentPattern::Fixed(2))
4368 .unwrap();
4369 assert!(result, "Field should have been found and updated");
4370
4371 let expected = r#"Source: example
4372Depends: foo,
4373 bar,
4374 baz
4375"#;
4376 assert_eq!(para.to_string(), expected);
4377 }
4378
4379 #[test]
4380 fn test_change_field_indent_nonexistent() {
4381 let original = r#"Source: example
4383"#;
4384 let mut para: super::Paragraph = original.parse().unwrap();
4385
4386 let result = para
4388 .change_field_indent("Depends", &super::IndentPattern::Fixed(2))
4389 .unwrap();
4390 assert!(!result, "Should return false for non-existent field");
4391
4392 assert_eq!(para.to_string(), original);
4394 }
4395
4396 #[test]
4397 fn test_change_field_indent_case_insensitive() {
4398 let original = r#"Build-Depends: foo,
4400 bar
4401"#;
4402 let mut para: super::Paragraph = original.parse().unwrap();
4403
4404 let result = para
4406 .change_field_indent("build-depends", &super::IndentPattern::Fixed(1))
4407 .unwrap();
4408 assert!(result, "Should find field case-insensitively");
4409
4410 let expected = r#"Build-Depends: foo,
4411 bar
4412"#;
4413 assert_eq!(para.to_string(), expected);
4414 }
4415
4416 #[test]
4417 fn test_entry_get_indent() {
4418 let original = r#"Build-Depends: foo,
4420 bar,
4421 baz
4422"#;
4423 let para: super::Paragraph = original.parse().unwrap();
4424 let entry = para.entries().next().unwrap();
4425
4426 assert_eq!(entry.get_indent(), Some(" ".to_string()));
4427 }
4428
4429 #[test]
4430 fn test_entry_get_indent_single_line() {
4431 let original = r#"Source: example
4433"#;
4434 let para: super::Paragraph = original.parse().unwrap();
4435 let entry = para.entries().next().unwrap();
4436
4437 assert_eq!(entry.get_indent(), None);
4438 }
4439}
4440
4441#[test]
4442fn test_move_paragraph_forward() {
4443 let mut d: Deb822 = vec![
4444 vec![("Foo", "Bar"), ("Baz", "Qux")].into_iter().collect(),
4445 vec![("A", "B"), ("C", "D")].into_iter().collect(),
4446 vec![("X", "Y"), ("Z", "W")].into_iter().collect(),
4447 ]
4448 .into_iter()
4449 .collect();
4450 d.move_paragraph(0, 2);
4451 assert_eq!(
4452 d.to_string(),
4453 "A: B\nC: D\n\nX: Y\nZ: W\n\nFoo: Bar\nBaz: Qux\n"
4454 );
4455}
4456
4457#[test]
4458fn test_move_paragraph_backward() {
4459 let mut d: Deb822 = vec![
4460 vec![("Foo", "Bar"), ("Baz", "Qux")].into_iter().collect(),
4461 vec![("A", "B"), ("C", "D")].into_iter().collect(),
4462 vec![("X", "Y"), ("Z", "W")].into_iter().collect(),
4463 ]
4464 .into_iter()
4465 .collect();
4466 d.move_paragraph(2, 0);
4467 assert_eq!(
4468 d.to_string(),
4469 "X: Y\nZ: W\n\nFoo: Bar\nBaz: Qux\n\nA: B\nC: D\n"
4470 );
4471}
4472
4473#[test]
4474fn test_move_paragraph_middle() {
4475 let mut d: Deb822 = vec![
4476 vec![("Foo", "Bar"), ("Baz", "Qux")].into_iter().collect(),
4477 vec![("A", "B"), ("C", "D")].into_iter().collect(),
4478 vec![("X", "Y"), ("Z", "W")].into_iter().collect(),
4479 ]
4480 .into_iter()
4481 .collect();
4482 d.move_paragraph(2, 1);
4483 assert_eq!(
4484 d.to_string(),
4485 "Foo: Bar\nBaz: Qux\n\nX: Y\nZ: W\n\nA: B\nC: D\n"
4486 );
4487}
4488
4489#[test]
4490fn test_move_paragraph_same_index() {
4491 let mut d: Deb822 = vec![
4492 vec![("Foo", "Bar"), ("Baz", "Qux")].into_iter().collect(),
4493 vec![("A", "B"), ("C", "D")].into_iter().collect(),
4494 ]
4495 .into_iter()
4496 .collect();
4497 let original = d.to_string();
4498 d.move_paragraph(1, 1);
4499 assert_eq!(d.to_string(), original);
4500}
4501
4502#[test]
4503fn test_move_paragraph_single() {
4504 let mut d: Deb822 = vec![vec![("Foo", "Bar")].into_iter().collect()]
4505 .into_iter()
4506 .collect();
4507 let original = d.to_string();
4508 d.move_paragraph(0, 0);
4509 assert_eq!(d.to_string(), original);
4510}
4511
4512#[test]
4513fn test_move_paragraph_invalid_index() {
4514 let mut d: Deb822 = vec![
4515 vec![("Foo", "Bar")].into_iter().collect(),
4516 vec![("A", "B")].into_iter().collect(),
4517 ]
4518 .into_iter()
4519 .collect();
4520 let original = d.to_string();
4521 d.move_paragraph(0, 5);
4522 assert_eq!(d.to_string(), original);
4523}
4524
4525#[test]
4526fn test_move_paragraph_with_comments() {
4527 let text = r#"Foo: Bar
4528
4529# This is a comment
4530
4531A: B
4532
4533X: Y
4534"#;
4535 let mut d: Deb822 = text.parse().unwrap();
4536 d.move_paragraph(0, 2);
4537 assert_eq!(
4538 d.to_string(),
4539 "# This is a comment\n\nA: B\n\nX: Y\n\nFoo: Bar\n"
4540 );
4541}
4542
4543#[test]
4544fn test_case_insensitive_get() {
4545 let text = "Package: test\nVersion: 1.0\n";
4546 let d: Deb822 = text.parse().unwrap();
4547 let p = d.paragraphs().next().unwrap();
4548
4549 assert_eq!(p.get("Package").as_deref(), Some("test"));
4551 assert_eq!(p.get("package").as_deref(), Some("test"));
4552 assert_eq!(p.get("PACKAGE").as_deref(), Some("test"));
4553 assert_eq!(p.get("PaCkAgE").as_deref(), Some("test"));
4554
4555 assert_eq!(p.get("Version").as_deref(), Some("1.0"));
4556 assert_eq!(p.get("version").as_deref(), Some("1.0"));
4557 assert_eq!(p.get("VERSION").as_deref(), Some("1.0"));
4558}
4559
4560#[test]
4561fn test_case_insensitive_set() {
4562 let text = "Package: test\n";
4563 let d: Deb822 = text.parse().unwrap();
4564 let mut p = d.paragraphs().next().unwrap();
4565
4566 p.set("package", "updated");
4568 assert_eq!(p.get("Package").as_deref(), Some("updated"));
4569 assert_eq!(p.get("package").as_deref(), Some("updated"));
4570
4571 p.set("PACKAGE", "updated2");
4573 assert_eq!(p.get("Package").as_deref(), Some("updated2"));
4574
4575 assert_eq!(p.keys().count(), 1);
4577}
4578
4579#[test]
4580fn test_case_insensitive_remove() {
4581 let text = "Package: test\nVersion: 1.0\n";
4582 let d: Deb822 = text.parse().unwrap();
4583 let mut p = d.paragraphs().next().unwrap();
4584
4585 p.remove("package");
4587 assert_eq!(p.get("Package"), None);
4588 assert_eq!(p.get("Version").as_deref(), Some("1.0"));
4589
4590 p.remove("VERSION");
4592 assert_eq!(p.get("Version"), None);
4593
4594 assert_eq!(p.keys().count(), 0);
4596}
4597
4598#[test]
4599fn test_case_preservation() {
4600 let text = "Package: test\n";
4601 let d: Deb822 = text.parse().unwrap();
4602 let mut p = d.paragraphs().next().unwrap();
4603
4604 let original_text = d.to_string();
4606 assert_eq!(original_text, "Package: test\n");
4607
4608 p.set("package", "updated");
4610
4611 let updated_text = d.to_string();
4613 assert_eq!(updated_text, "Package: updated\n");
4614}
4615
4616#[test]
4617fn test_case_insensitive_contains_key() {
4618 let text = "Package: test\n";
4619 let d: Deb822 = text.parse().unwrap();
4620 let p = d.paragraphs().next().unwrap();
4621
4622 assert!(p.contains_key("Package"));
4623 assert!(p.contains_key("package"));
4624 assert!(p.contains_key("PACKAGE"));
4625 assert!(!p.contains_key("NonExistent"));
4626}
4627
4628#[test]
4629fn test_case_insensitive_get_all() {
4630 let text = "Package: test1\npackage: test2\n";
4631 let d: Deb822 = text.parse().unwrap();
4632 let p = d.paragraphs().next().unwrap();
4633
4634 let values: Vec<String> = p.get_all("PACKAGE").collect();
4635 assert_eq!(values, vec!["test1", "test2"]);
4636}
4637
4638#[test]
4639fn test_case_insensitive_rename() {
4640 let text = "Package: test\n";
4641 let d: Deb822 = text.parse().unwrap();
4642 let mut p = d.paragraphs().next().unwrap();
4643
4644 assert!(p.rename("package", "NewName"));
4646 assert_eq!(p.get("NewName").as_deref(), Some("test"));
4647 assert_eq!(p.get("Package"), None);
4648}
4649
4650#[test]
4651fn test_rename_changes_case() {
4652 let text = "Package: test\n";
4653 let d: Deb822 = text.parse().unwrap();
4654 let mut p = d.paragraphs().next().unwrap();
4655
4656 assert!(p.rename("package", "PACKAGE"));
4658
4659 let updated_text = d.to_string();
4661 assert_eq!(updated_text, "PACKAGE: test\n");
4662
4663 assert_eq!(p.get("package").as_deref(), Some("test"));
4665 assert_eq!(p.get("Package").as_deref(), Some("test"));
4666 assert_eq!(p.get("PACKAGE").as_deref(), Some("test"));
4667}
4668
4669#[test]
4670fn test_reject_whitespace_only_continuation_line() {
4671 let text = "Build-Depends:\n \ndebhelper\n";
4677 let parsed = Deb822::parse(text);
4678
4679 assert!(
4682 !parsed.errors().is_empty(),
4683 "Expected parse errors for whitespace-only continuation line"
4684 );
4685}
4686
4687#[test]
4688fn test_reject_empty_continuation_line_in_multiline_field() {
4689 let text = "Depends: foo,\n bar,\n \n baz\n";
4691 let parsed = Deb822::parse(text);
4692
4693 assert!(
4695 !parsed.errors().is_empty(),
4696 "Empty continuation line should generate parse errors"
4697 );
4698
4699 let has_empty_line_error = parsed
4701 .errors()
4702 .iter()
4703 .any(|e| e.contains("empty continuation line"));
4704 assert!(
4705 has_empty_line_error,
4706 "Should have an error about empty continuation line"
4707 );
4708}
4709
4710#[test]
4711#[should_panic(expected = "empty continuation line")]
4712fn test_set_rejects_empty_continuation_lines() {
4713 let text = "Package: test\n";
4715 let deb822 = text.parse::<Deb822>().unwrap();
4716 let mut para = deb822.paragraphs().next().unwrap();
4717
4718 let value_with_empty_line = "foo\n \nbar";
4721 para.set("Depends", value_with_empty_line);
4722}
4723
4724#[test]
4725fn test_try_set_returns_error_for_empty_continuation_lines() {
4726 let text = "Package: test\n";
4728 let deb822 = text.parse::<Deb822>().unwrap();
4729 let mut para = deb822.paragraphs().next().unwrap();
4730
4731 let value_with_empty_line = "foo\n \nbar";
4733 let result = para.try_set("Depends", value_with_empty_line);
4734
4735 assert!(
4737 result.is_err(),
4738 "try_set() should return an error for empty continuation lines"
4739 );
4740
4741 match result {
4743 Err(Error::InvalidValue(msg)) => {
4744 assert!(
4745 msg.contains("empty continuation line"),
4746 "Error message should mention empty continuation line"
4747 );
4748 }
4749 _ => panic!("Expected InvalidValue error"),
4750 }
4751}
4752
4753#[test]
4754fn test_try_set_with_indent_pattern_returns_error() {
4755 let text = "Package: test\n";
4757 let deb822 = text.parse::<Deb822>().unwrap();
4758 let mut para = deb822.paragraphs().next().unwrap();
4759
4760 let value_with_empty_line = "foo\n \nbar";
4761 let result = para.try_set_with_indent_pattern(
4762 "Depends",
4763 value_with_empty_line,
4764 Some(&IndentPattern::Fixed(2)),
4765 None,
4766 );
4767
4768 assert!(
4769 result.is_err(),
4770 "try_set_with_indent_pattern() should return an error"
4771 );
4772}
4773
4774#[test]
4775fn test_try_set_succeeds_for_valid_value() {
4776 let text = "Package: test\n";
4778 let deb822 = text.parse::<Deb822>().unwrap();
4779 let mut para = deb822.paragraphs().next().unwrap();
4780
4781 let valid_value = "foo\nbar";
4783 let result = para.try_set("Depends", valid_value);
4784
4785 assert!(result.is_ok(), "try_set() should succeed for valid values");
4786 assert_eq!(para.get("Depends").as_deref(), Some("foo\nbar"));
4787}
4788
4789#[test]
4790fn test_field_with_empty_first_line() {
4791 let text = "Foo:\n blah\n blah\n";
4794 let parsed = Deb822::parse(text);
4795
4796 assert!(
4798 parsed.errors().is_empty(),
4799 "Empty first line should be valid. Got errors: {:?}",
4800 parsed.errors()
4801 );
4802
4803 let deb822 = parsed.tree();
4804 let para = deb822.paragraphs().next().unwrap();
4805 assert_eq!(para.get("Foo").as_deref(), Some("blah\nblah"));
4806}
4807
4808#[test]
4809fn test_try_set_with_empty_first_line() {
4810 let text = "Package: test\n";
4812 let deb822 = text.parse::<Deb822>().unwrap();
4813 let mut para = deb822.paragraphs().next().unwrap();
4814
4815 let value = "\nblah\nmore";
4817 let result = para.try_set("Depends", value);
4818
4819 assert!(
4820 result.is_ok(),
4821 "try_set() should succeed for values with empty first line. Got: {:?}",
4822 result
4823 );
4824}
4825
4826#[test]
4827fn test_field_with_value_then_empty_continuation() {
4828 let text = "Foo: bar\n \n";
4830 let parsed = Deb822::parse(text);
4831
4832 assert!(
4834 !parsed.errors().is_empty(),
4835 "Field with value then empty continuation line should be rejected"
4836 );
4837
4838 let has_empty_line_error = parsed
4840 .errors()
4841 .iter()
4842 .any(|e| e.contains("empty continuation line"));
4843 assert!(
4844 has_empty_line_error,
4845 "Should have error about empty continuation line"
4846 );
4847}
4848
4849#[test]
4850fn test_line_col() {
4851 let text = r#"Source: foo
4852Maintainer: Foo Bar <jelmer@jelmer.uk>
4853Section: net
4854
4855Package: foo
4856Architecture: all
4857Depends: libc6
4858Description: This is a description
4859 With details
4860"#;
4861 let deb822 = text.parse::<Deb822>().unwrap();
4862
4863 let paras: Vec<_> = deb822.paragraphs().collect();
4865 assert_eq!(paras.len(), 2);
4866
4867 assert_eq!(paras[0].line(), 0);
4869 assert_eq!(paras[0].column(), 0);
4870
4871 assert_eq!(paras[1].line(), 4);
4873 assert_eq!(paras[1].column(), 0);
4874
4875 let entries: Vec<_> = paras[0].entries().collect();
4877 assert_eq!(entries[0].line(), 0); assert_eq!(entries[1].line(), 1); assert_eq!(entries[2].line(), 2); assert_eq!(entries[0].column(), 0); assert_eq!(entries[1].column(), 0); assert_eq!(paras[1].line_col(), (4, 0));
4887 assert_eq!(entries[0].line_col(), (0, 0));
4888
4889 let second_para_entries: Vec<_> = paras[1].entries().collect();
4891 assert_eq!(second_para_entries[3].line(), 7); }