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 snapshot(&self) -> Self {
720 Deb822(SyntaxNode::new_root_mut(self.0.green().into_owned()))
721 }
722
723 pub fn new() -> Deb822 {
725 let mut builder = GreenNodeBuilder::new();
726
727 builder.start_node(ROOT.into());
728 builder.finish_node();
729 Deb822(SyntaxNode::new_root_mut(builder.finish()))
730 }
731
732 pub fn parse(text: &str) -> crate::Parse<Deb822> {
734 crate::Parse::parse_deb822(text)
735 }
736
737 #[must_use]
753 pub fn wrap_and_sort(
754 &self,
755 sort_paragraphs: Option<&dyn Fn(&Paragraph, &Paragraph) -> std::cmp::Ordering>,
756 wrap_and_sort_paragraph: Option<&dyn Fn(&Paragraph) -> Paragraph>,
757 ) -> Deb822 {
758 let mut builder = GreenNodeBuilder::new();
759 builder.start_node(ROOT.into());
760 let mut current = vec![];
761 let mut paragraphs = vec![];
762 for c in self.0.children_with_tokens() {
763 match c.kind() {
764 PARAGRAPH => {
765 paragraphs.push((
766 current,
767 Paragraph::cast(c.as_node().unwrap().clone()).unwrap(),
768 ));
769 current = vec![];
770 }
771 COMMENT | ERROR => {
772 current.push(c);
773 }
774 EMPTY_LINE => {
775 current.extend(
776 c.as_node()
777 .unwrap()
778 .children_with_tokens()
779 .skip_while(|c| matches!(c.kind(), EMPTY_LINE | NEWLINE | WHITESPACE)),
780 );
781 }
782 _ => {}
783 }
784 }
785 if let Some(sort_paragraph) = sort_paragraphs {
786 paragraphs.sort_by(|a, b| {
787 let a_key = &a.1;
788 let b_key = &b.1;
789 sort_paragraph(a_key, b_key)
790 });
791 }
792
793 for (i, paragraph) in paragraphs.into_iter().enumerate() {
794 if i > 0 {
795 builder.start_node(EMPTY_LINE.into());
796 builder.token(NEWLINE.into(), "\n");
797 builder.finish_node();
798 }
799 for c in paragraph.0.into_iter() {
800 builder.token(c.kind().into(), c.as_token().unwrap().text());
801 }
802 let new_paragraph = if let Some(ref ws) = wrap_and_sort_paragraph {
803 ws(¶graph.1)
804 } else {
805 paragraph.1
806 };
807 inject(&mut builder, new_paragraph.0);
808 }
809
810 for c in current {
811 builder.token(c.kind().into(), c.as_token().unwrap().text());
812 }
813
814 builder.finish_node();
815 Self(SyntaxNode::new_root_mut(builder.finish()))
816 }
817
818 pub fn normalize_field_spacing(&mut self) -> bool {
837 let mut any_changed = false;
838
839 let mut paragraphs: Vec<_> = self.paragraphs().collect();
841
842 for para in &mut paragraphs {
844 if para.normalize_field_spacing() {
845 any_changed = true;
846 }
847 }
848
849 any_changed
850 }
851
852 pub fn paragraphs(&self) -> impl Iterator<Item = Paragraph> {
854 self.0.children().filter_map(Paragraph::cast)
855 }
856
857 fn convert_index(&self, index: usize) -> Option<usize> {
859 let mut current_pos = 0usize;
860 if index == 0 {
861 return Some(0);
862 }
863 for (i, node) in self.0.children_with_tokens().enumerate() {
864 if node.kind() == PARAGRAPH {
865 if current_pos == index {
866 return Some(i);
867 }
868 current_pos += 1;
869 }
870 }
871
872 None
873 }
874
875 fn delete_trailing_space(&self, start: usize) {
877 for (i, node) in self.0.children_with_tokens().enumerate() {
878 if i < start {
879 continue;
880 }
881 if node.kind() != EMPTY_LINE {
882 return;
883 }
884 self.0.splice_children(start..start + 1, []);
887 }
888 }
889
890 fn insert_empty_paragraph(&mut self, index: Option<usize>) -> Paragraph {
892 let paragraph = Paragraph::new();
893 let mut to_insert = vec![];
894 if self.0.children().count() > 0 {
895 let mut builder = GreenNodeBuilder::new();
896 builder.start_node(EMPTY_LINE.into());
897 builder.token(NEWLINE.into(), "\n");
898 builder.finish_node();
899 to_insert.push(SyntaxNode::new_root_mut(builder.finish()).into());
900 }
901 to_insert.push(paragraph.0.clone().into());
902 let insertion_point = match index {
903 Some(i) => {
904 if to_insert.len() > 1 {
905 to_insert.swap(0, 1);
906 }
907 i
908 }
909 None => self.0.children().count(),
910 };
911 self.0
912 .splice_children(insertion_point..insertion_point, to_insert);
913 paragraph
914 }
915
916 pub fn insert_paragraph(&mut self, index: usize) -> Paragraph {
936 self.insert_empty_paragraph(self.convert_index(index))
937 }
938
939 pub fn remove_paragraph(&mut self, index: usize) {
957 if let Some(index) = self.convert_index(index) {
958 self.0.splice_children(index..index + 1, []);
959 self.delete_trailing_space(index);
960 }
961 }
962
963 pub fn move_paragraph(&mut self, from_index: usize, to_index: usize) {
983 if from_index == to_index {
984 return;
985 }
986
987 let paragraph_count = self.paragraphs().count();
989 if from_index >= paragraph_count || to_index >= paragraph_count {
990 return;
991 }
992
993 let paragraph_to_move = self.paragraphs().nth(from_index).unwrap().0.clone();
995
996 let from_physical = self.convert_index(from_index).unwrap();
998
999 let mut start_idx = from_physical;
1001 if from_physical > 0 {
1002 if let Some(prev_node) = self.0.children_with_tokens().nth(from_physical - 1) {
1003 if prev_node.kind() == EMPTY_LINE {
1004 start_idx = from_physical - 1;
1005 }
1006 }
1007 }
1008
1009 self.0.splice_children(start_idx..from_physical + 1, []);
1011 self.delete_trailing_space(start_idx);
1012
1013 let insert_at = if to_index > from_index {
1017 let target_idx = to_index - 1;
1020 if let Some(target_physical) = self.convert_index(target_idx) {
1021 target_physical + 1
1022 } else {
1023 self.0.children().count()
1025 }
1026 } else {
1027 if let Some(target_physical) = self.convert_index(to_index) {
1030 target_physical
1031 } else {
1032 self.0.children().count()
1033 }
1034 };
1035
1036 let mut to_insert = vec![];
1038
1039 let needs_empty_line_before = if insert_at == 0 {
1041 false
1043 } else if insert_at > 0 {
1044 if let Some(node_at_insert) = self.0.children_with_tokens().nth(insert_at - 1) {
1046 node_at_insert.kind() != EMPTY_LINE
1047 } else {
1048 false
1049 }
1050 } else {
1051 false
1052 };
1053
1054 if needs_empty_line_before {
1055 let mut builder = GreenNodeBuilder::new();
1056 builder.start_node(EMPTY_LINE.into());
1057 builder.token(NEWLINE.into(), "\n");
1058 builder.finish_node();
1059 to_insert.push(SyntaxNode::new_root_mut(builder.finish()).into());
1060 }
1061
1062 to_insert.push(paragraph_to_move.into());
1063
1064 let needs_empty_line_after = if insert_at < self.0.children().count() {
1066 if let Some(node_after) = self.0.children_with_tokens().nth(insert_at) {
1068 node_after.kind() != EMPTY_LINE
1069 } else {
1070 false
1071 }
1072 } else {
1073 false
1074 };
1075
1076 if needs_empty_line_after {
1077 let mut builder = GreenNodeBuilder::new();
1078 builder.start_node(EMPTY_LINE.into());
1079 builder.token(NEWLINE.into(), "\n");
1080 builder.finish_node();
1081 to_insert.push(SyntaxNode::new_root_mut(builder.finish()).into());
1082 }
1083
1084 self.0.splice_children(insert_at..insert_at, to_insert);
1086 }
1087
1088 pub fn add_paragraph(&mut self) -> Paragraph {
1090 self.insert_empty_paragraph(None)
1091 }
1092
1093 pub fn swap_paragraphs(&mut self, index1: usize, index2: usize) {
1123 if index1 == index2 {
1124 return;
1125 }
1126
1127 let mut children: Vec<_> = self.0.children().map(|n| n.clone().into()).collect();
1129
1130 let mut para_child_indices = vec![];
1132 for (child_idx, child) in self.0.children().enumerate() {
1133 if child.kind() == PARAGRAPH {
1134 para_child_indices.push(child_idx);
1135 }
1136 }
1137
1138 if index1 >= para_child_indices.len() {
1140 panic!("index1 {} out of bounds", index1);
1141 }
1142 if index2 >= para_child_indices.len() {
1143 panic!("index2 {} out of bounds", index2);
1144 }
1145
1146 let child_idx1 = para_child_indices[index1];
1147 let child_idx2 = para_child_indices[index2];
1148
1149 children.swap(child_idx1, child_idx2);
1151
1152 let num_children = children.len();
1154 self.0.splice_children(0..num_children, children);
1155 }
1156
1157 pub fn from_file(path: impl AsRef<Path>) -> Result<Self, Error> {
1159 let text = std::fs::read_to_string(path)?;
1160 Ok(Self::from_str(&text)?)
1161 }
1162
1163 pub fn from_file_relaxed(
1165 path: impl AsRef<Path>,
1166 ) -> Result<(Self, Vec<String>), std::io::Error> {
1167 let text = std::fs::read_to_string(path)?;
1168 Ok(Self::from_str_relaxed(&text))
1169 }
1170
1171 pub fn from_str_relaxed(s: &str) -> (Self, Vec<String>) {
1173 let parsed = parse(s);
1174 (parsed.root_mut(), parsed.errors)
1175 }
1176
1177 pub fn read<R: std::io::Read>(mut r: R) -> Result<Self, Error> {
1179 let mut buf = String::new();
1180 r.read_to_string(&mut buf)?;
1181 Ok(Self::from_str(&buf)?)
1182 }
1183
1184 pub fn read_relaxed<R: std::io::Read>(mut r: R) -> Result<(Self, Vec<String>), std::io::Error> {
1186 let mut buf = String::new();
1187 r.read_to_string(&mut buf)?;
1188 Ok(Self::from_str_relaxed(&buf))
1189 }
1190}
1191
1192fn inject(builder: &mut GreenNodeBuilder, node: SyntaxNode) {
1193 builder.start_node(node.kind().into());
1194 for child in node.children_with_tokens() {
1195 match child {
1196 rowan::NodeOrToken::Node(child) => {
1197 inject(builder, child);
1198 }
1199 rowan::NodeOrToken::Token(token) => {
1200 builder.token(token.kind().into(), token.text());
1201 }
1202 }
1203 }
1204 builder.finish_node();
1205}
1206
1207impl FromIterator<Paragraph> for Deb822 {
1208 fn from_iter<T: IntoIterator<Item = Paragraph>>(iter: T) -> Self {
1209 let mut builder = GreenNodeBuilder::new();
1210 builder.start_node(ROOT.into());
1211 for (i, paragraph) in iter.into_iter().enumerate() {
1212 if i > 0 {
1213 builder.start_node(EMPTY_LINE.into());
1214 builder.token(NEWLINE.into(), "\n");
1215 builder.finish_node();
1216 }
1217 inject(&mut builder, paragraph.0);
1218 }
1219 builder.finish_node();
1220 Self(SyntaxNode::new_root_mut(builder.finish()))
1221 }
1222}
1223
1224impl From<Vec<(String, String)>> for Paragraph {
1225 fn from(v: Vec<(String, String)>) -> Self {
1226 v.into_iter().collect()
1227 }
1228}
1229
1230impl From<Vec<(&str, &str)>> for Paragraph {
1231 fn from(v: Vec<(&str, &str)>) -> Self {
1232 v.into_iter().collect()
1233 }
1234}
1235
1236impl FromIterator<(String, String)> for Paragraph {
1237 fn from_iter<T: IntoIterator<Item = (String, String)>>(iter: T) -> Self {
1238 let mut builder = GreenNodeBuilder::new();
1239 builder.start_node(PARAGRAPH.into());
1240 for (key, value) in iter {
1241 builder.start_node(ENTRY.into());
1242 builder.token(KEY.into(), &key);
1243 builder.token(COLON.into(), ":");
1244 builder.token(WHITESPACE.into(), " ");
1245 for (i, line) in value.split('\n').enumerate() {
1246 if i > 0 {
1247 builder.token(INDENT.into(), " ");
1248 }
1249 builder.token(VALUE.into(), line);
1250 builder.token(NEWLINE.into(), "\n");
1251 }
1252 builder.finish_node();
1253 }
1254 builder.finish_node();
1255 Self(SyntaxNode::new_root_mut(builder.finish()))
1256 }
1257}
1258
1259impl<'a> FromIterator<(&'a str, &'a str)> for Paragraph {
1260 fn from_iter<T: IntoIterator<Item = (&'a str, &'a str)>>(iter: T) -> Self {
1261 let mut builder = GreenNodeBuilder::new();
1262 builder.start_node(PARAGRAPH.into());
1263 for (key, value) in iter {
1264 builder.start_node(ENTRY.into());
1265 builder.token(KEY.into(), key);
1266 builder.token(COLON.into(), ":");
1267 builder.token(WHITESPACE.into(), " ");
1268 for (i, line) in value.split('\n').enumerate() {
1269 if i > 0 {
1270 builder.token(INDENT.into(), " ");
1271 }
1272 builder.token(VALUE.into(), line);
1273 builder.token(NEWLINE.into(), "\n");
1274 }
1275 builder.finish_node();
1276 }
1277 builder.finish_node();
1278 Self(SyntaxNode::new_root_mut(builder.finish()))
1279 }
1280}
1281
1282#[derive(Debug, Clone, PartialEq, Eq)]
1284pub enum IndentPattern {
1285 Fixed(usize),
1287 FieldNameLength,
1289}
1290
1291impl IndentPattern {
1292 fn to_string(&self, field_name: &str) -> String {
1294 match self {
1295 IndentPattern::Fixed(spaces) => " ".repeat(*spaces),
1296 IndentPattern::FieldNameLength => " ".repeat(field_name.len() + 2),
1297 }
1298 }
1299}
1300
1301impl Paragraph {
1302 pub fn new() -> Paragraph {
1304 let mut builder = GreenNodeBuilder::new();
1305
1306 builder.start_node(PARAGRAPH.into());
1307 builder.finish_node();
1308 Paragraph(SyntaxNode::new_root_mut(builder.finish()))
1309 }
1310
1311 pub fn snapshot(&self) -> Self {
1320 Paragraph(SyntaxNode::new_root_mut(self.0.green().into_owned()))
1321 }
1322
1323 #[must_use]
1335 pub fn wrap_and_sort(
1336 &self,
1337 indentation: Indentation,
1338 immediate_empty_line: bool,
1339 max_line_length_one_liner: Option<usize>,
1340 sort_entries: Option<&dyn Fn(&Entry, &Entry) -> std::cmp::Ordering>,
1341 format_value: Option<&dyn Fn(&str, &str) -> String>,
1342 ) -> Paragraph {
1343 let mut builder = GreenNodeBuilder::new();
1344
1345 let mut current = vec![];
1346 let mut entries = vec![];
1347
1348 builder.start_node(PARAGRAPH.into());
1349 for c in self.0.children_with_tokens() {
1350 match c.kind() {
1351 ENTRY => {
1352 entries.push((current, Entry::cast(c.as_node().unwrap().clone()).unwrap()));
1353 current = vec![];
1354 }
1355 ERROR | COMMENT => {
1356 current.push(c);
1357 }
1358 _ => {}
1359 }
1360 }
1361
1362 if let Some(sort_entry) = sort_entries {
1363 entries.sort_by(|a, b| {
1364 let a_key = &a.1;
1365 let b_key = &b.1;
1366 sort_entry(a_key, b_key)
1367 });
1368 }
1369
1370 for (pre, entry) in entries.into_iter() {
1371 for c in pre.into_iter() {
1372 builder.token(c.kind().into(), c.as_token().unwrap().text());
1373 }
1374
1375 inject(
1376 &mut builder,
1377 entry
1378 .wrap_and_sort(
1379 indentation,
1380 immediate_empty_line,
1381 max_line_length_one_liner,
1382 format_value,
1383 )
1384 .0,
1385 );
1386 }
1387
1388 for c in current {
1389 builder.token(c.kind().into(), c.as_token().unwrap().text());
1390 }
1391
1392 builder.finish_node();
1393 Self(SyntaxNode::new_root_mut(builder.finish()))
1394 }
1395
1396 pub fn normalize_field_spacing(&mut self) -> bool {
1416 let mut any_changed = false;
1417
1418 let mut entries: Vec<_> = self.entries().collect();
1420
1421 for entry in &mut entries {
1423 if entry.normalize_field_spacing() {
1424 any_changed = true;
1425 }
1426 }
1427
1428 any_changed
1429 }
1430
1431 pub fn get(&self, key: &str) -> Option<String> {
1435 self.entries()
1436 .find(|e| {
1437 e.key()
1438 .as_deref()
1439 .is_some_and(|k| k.eq_ignore_ascii_case(key))
1440 })
1441 .map(|e| e.value())
1442 }
1443
1444 pub fn get_entry(&self, key: &str) -> Option<Entry> {
1448 self.entries().find(|e| {
1449 e.key()
1450 .as_deref()
1451 .is_some_and(|k| k.eq_ignore_ascii_case(key))
1452 })
1453 }
1454
1455 pub fn get_with_indent(&self, key: &str, indent_pattern: &IndentPattern) -> Option<String> {
1482 use crate::lex::SyntaxKind::{INDENT, VALUE};
1483
1484 self.entries()
1485 .find(|e| {
1486 e.key()
1487 .as_deref()
1488 .is_some_and(|k| k.eq_ignore_ascii_case(key))
1489 })
1490 .and_then(|e| {
1491 let field_key = e.key()?;
1492 let expected_indent = indent_pattern.to_string(&field_key);
1493 let expected_len = expected_indent.len();
1494
1495 let mut result = String::new();
1496 let mut first = true;
1497 let mut last_indent: Option<String> = None;
1498
1499 for token in e.0.children_with_tokens().filter_map(|it| it.into_token()) {
1500 match token.kind() {
1501 INDENT => {
1502 last_indent = Some(token.text().to_string());
1503 }
1504 VALUE => {
1505 if !first {
1506 result.push('\n');
1507 if let Some(ref indent_text) = last_indent {
1509 if indent_text.len() > expected_len {
1510 result.push_str(&indent_text[expected_len..]);
1511 }
1512 }
1513 }
1514 result.push_str(token.text());
1515 first = false;
1516 last_indent = None;
1517 }
1518 _ => {}
1519 }
1520 }
1521
1522 Some(result)
1523 })
1524 }
1525
1526 pub fn get_multiline(&self, key: &str) -> Option<String> {
1553 self.get_with_indent(key, &IndentPattern::Fixed(1))
1554 }
1555
1556 pub fn set_multiline(
1582 &mut self,
1583 key: &str,
1584 value: &str,
1585 field_order: Option<&[&str]>,
1586 ) -> Result<(), Error> {
1587 self.try_set_with_forced_indent(key, value, &IndentPattern::Fixed(1), field_order)
1588 }
1589
1590 pub fn contains_key(&self, key: &str) -> bool {
1592 self.get(key).is_some()
1593 }
1594
1595 pub fn entries(&self) -> impl Iterator<Item = Entry> + '_ {
1597 self.0.children().filter_map(Entry::cast)
1598 }
1599
1600 pub fn items(&self) -> impl Iterator<Item = (String, String)> + '_ {
1602 self.entries()
1603 .filter_map(|e| e.key().map(|k| (k, e.value())))
1604 }
1605
1606 pub fn get_all<'a>(&'a self, key: &'a str) -> impl Iterator<Item = String> + 'a {
1610 self.items().filter_map(move |(k, v)| {
1611 if k.eq_ignore_ascii_case(key) {
1612 Some(v)
1613 } else {
1614 None
1615 }
1616 })
1617 }
1618
1619 pub fn keys(&self) -> impl Iterator<Item = String> + '_ {
1621 self.entries().filter_map(|e| e.key())
1622 }
1623
1624 pub fn remove(&mut self, key: &str) {
1628 for mut entry in self.entries() {
1629 if entry
1630 .key()
1631 .as_deref()
1632 .is_some_and(|k| k.eq_ignore_ascii_case(key))
1633 {
1634 entry.detach();
1635 }
1636 }
1637 }
1638
1639 pub fn insert(&mut self, key: &str, value: &str) {
1641 let entry = Entry::new(key, value);
1642 let count = self.0.children_with_tokens().count();
1643 self.0.splice_children(count..count, vec![entry.0.into()]);
1644 }
1645
1646 pub fn insert_comment_before(&mut self, comment: &str) {
1665 use rowan::GreenNodeBuilder;
1666
1667 let mut builder = GreenNodeBuilder::new();
1670 builder.start_node(EMPTY_LINE.into());
1671 builder.token(COMMENT.into(), &format!("# {}", comment));
1672 builder.token(NEWLINE.into(), "\n");
1673 builder.finish_node();
1674 let green = builder.finish();
1675
1676 let comment_node = SyntaxNode::new_root_mut(green);
1678
1679 let index = self.0.index();
1680 let parent = self.0.parent().expect("Paragraph must have a parent");
1681 parent.splice_children(index..index, vec![comment_node.into()]);
1682 }
1683
1684 fn detect_indent_pattern(&self) -> IndentPattern {
1692 let indent_data: Vec<(String, usize)> = self
1694 .entries()
1695 .filter_map(|entry| {
1696 let field_key = entry.key()?;
1697 let indent = entry.get_indent()?;
1698 Some((field_key, indent.len()))
1699 })
1700 .collect();
1701
1702 if indent_data.is_empty() {
1703 return IndentPattern::FieldNameLength;
1705 }
1706
1707 let first_indent_len = indent_data[0].1;
1709 let all_same = indent_data.iter().all(|(_, len)| *len == first_indent_len);
1710
1711 if all_same {
1712 return IndentPattern::Fixed(first_indent_len);
1714 }
1715
1716 let all_match_field_length = indent_data
1718 .iter()
1719 .all(|(field_key, indent_len)| *indent_len == field_key.len() + 2);
1720
1721 if all_match_field_length {
1722 return IndentPattern::FieldNameLength;
1724 }
1725
1726 IndentPattern::FieldNameLength
1728 }
1729
1730 pub fn try_set(&mut self, key: &str, value: &str) -> Result<(), Error> {
1735 self.try_set_with_indent_pattern(key, value, None, None)
1736 }
1737
1738 pub fn set(&mut self, key: &str, value: &str) {
1743 self.try_set(key, value)
1744 .expect("Invalid value: empty continuation line")
1745 }
1746
1747 pub fn set_with_field_order(&mut self, key: &str, value: &str, field_order: &[&str]) {
1749 self.try_set_with_indent_pattern(key, value, None, Some(field_order))
1750 .expect("Invalid value: empty continuation line")
1751 }
1752
1753 pub fn try_set_with_indent_pattern(
1770 &mut self,
1771 key: &str,
1772 value: &str,
1773 default_indent_pattern: Option<&IndentPattern>,
1774 field_order: Option<&[&str]>,
1775 ) -> Result<(), Error> {
1776 let existing_entry = self.entries().find(|entry| {
1778 entry
1779 .key()
1780 .as_deref()
1781 .is_some_and(|k| k.eq_ignore_ascii_case(key))
1782 });
1783
1784 let indent = existing_entry
1786 .as_ref()
1787 .and_then(|entry| entry.get_indent())
1788 .unwrap_or_else(|| {
1789 if let Some(pattern) = default_indent_pattern {
1791 pattern.to_string(key)
1792 } else {
1793 self.detect_indent_pattern().to_string(key)
1794 }
1795 });
1796
1797 let post_colon_ws = existing_entry
1798 .as_ref()
1799 .and_then(|entry| entry.get_post_colon_whitespace())
1800 .unwrap_or_else(|| " ".to_string());
1801
1802 let actual_key = existing_entry
1804 .as_ref()
1805 .and_then(|e| e.key())
1806 .unwrap_or_else(|| key.to_string());
1807
1808 let new_entry = Entry::try_with_formatting(&actual_key, value, &post_colon_ws, &indent)?;
1809
1810 for entry in self.entries() {
1812 if entry
1813 .key()
1814 .as_deref()
1815 .is_some_and(|k| k.eq_ignore_ascii_case(key))
1816 {
1817 self.0.splice_children(
1818 entry.0.index()..entry.0.index() + 1,
1819 vec![new_entry.0.into()],
1820 );
1821 return Ok(());
1822 }
1823 }
1824
1825 if let Some(order) = field_order {
1827 let insertion_index = self.find_insertion_index(key, order);
1828 self.0
1829 .splice_children(insertion_index..insertion_index, vec![new_entry.0.into()]);
1830 } else {
1831 let insertion_index = self.0.children_with_tokens().count();
1833 self.0
1834 .splice_children(insertion_index..insertion_index, vec![new_entry.0.into()]);
1835 }
1836 Ok(())
1837 }
1838
1839 pub fn set_with_indent_pattern(
1856 &mut self,
1857 key: &str,
1858 value: &str,
1859 default_indent_pattern: Option<&IndentPattern>,
1860 field_order: Option<&[&str]>,
1861 ) {
1862 self.try_set_with_indent_pattern(key, value, default_indent_pattern, field_order)
1863 .expect("Invalid value: empty continuation line")
1864 }
1865
1866 pub fn try_set_with_forced_indent(
1880 &mut self,
1881 key: &str,
1882 value: &str,
1883 indent_pattern: &IndentPattern,
1884 field_order: Option<&[&str]>,
1885 ) -> Result<(), Error> {
1886 let existing_entry = self.entries().find(|entry| {
1888 entry
1889 .key()
1890 .as_deref()
1891 .is_some_and(|k| k.eq_ignore_ascii_case(key))
1892 });
1893
1894 let post_colon_ws = existing_entry
1896 .as_ref()
1897 .and_then(|entry| entry.get_post_colon_whitespace())
1898 .unwrap_or_else(|| " ".to_string());
1899
1900 let actual_key = existing_entry
1902 .as_ref()
1903 .and_then(|e| e.key())
1904 .unwrap_or_else(|| key.to_string());
1905
1906 let indent = indent_pattern.to_string(&actual_key);
1908 let new_entry = Entry::try_with_formatting(&actual_key, value, &post_colon_ws, &indent)?;
1909
1910 for entry in self.entries() {
1912 if entry
1913 .key()
1914 .as_deref()
1915 .is_some_and(|k| k.eq_ignore_ascii_case(key))
1916 {
1917 self.0.splice_children(
1918 entry.0.index()..entry.0.index() + 1,
1919 vec![new_entry.0.into()],
1920 );
1921 return Ok(());
1922 }
1923 }
1924
1925 if let Some(order) = field_order {
1927 let insertion_index = self.find_insertion_index(key, order);
1928 self.0
1929 .splice_children(insertion_index..insertion_index, vec![new_entry.0.into()]);
1930 } else {
1931 let insertion_index = self.0.children_with_tokens().count();
1933 self.0
1934 .splice_children(insertion_index..insertion_index, vec![new_entry.0.into()]);
1935 }
1936 Ok(())
1937 }
1938
1939 pub fn set_with_forced_indent(
1953 &mut self,
1954 key: &str,
1955 value: &str,
1956 indent_pattern: &IndentPattern,
1957 field_order: Option<&[&str]>,
1958 ) {
1959 self.try_set_with_forced_indent(key, value, indent_pattern, field_order)
1960 .expect("Invalid value: empty continuation line")
1961 }
1962
1963 pub fn change_field_indent(
1979 &mut self,
1980 key: &str,
1981 indent_pattern: &IndentPattern,
1982 ) -> Result<bool, Error> {
1983 let existing_entry = self.entries().find(|entry| {
1985 entry
1986 .key()
1987 .as_deref()
1988 .is_some_and(|k| k.eq_ignore_ascii_case(key))
1989 });
1990
1991 if let Some(entry) = existing_entry {
1992 let value = entry.value();
1993 let actual_key = entry.key().unwrap_or_else(|| key.to_string());
1994
1995 let post_colon_ws = entry
1997 .get_post_colon_whitespace()
1998 .unwrap_or_else(|| " ".to_string());
1999
2000 let indent = indent_pattern.to_string(&actual_key);
2002 let new_entry =
2003 Entry::try_with_formatting(&actual_key, &value, &post_colon_ws, &indent)?;
2004
2005 self.0.splice_children(
2007 entry.0.index()..entry.0.index() + 1,
2008 vec![new_entry.0.into()],
2009 );
2010 Ok(true)
2011 } else {
2012 Ok(false)
2013 }
2014 }
2015
2016 fn find_insertion_index(&self, key: &str, field_order: &[&str]) -> usize {
2018 let new_field_position = field_order
2020 .iter()
2021 .position(|&field| field.eq_ignore_ascii_case(key));
2022
2023 let mut insertion_index = self.0.children_with_tokens().count();
2024
2025 for (i, child) in self.0.children_with_tokens().enumerate() {
2027 if let Some(node) = child.as_node() {
2028 if let Some(entry) = Entry::cast(node.clone()) {
2029 if let Some(existing_key) = entry.key() {
2030 let existing_position = field_order
2031 .iter()
2032 .position(|&field| field.eq_ignore_ascii_case(&existing_key));
2033
2034 match (new_field_position, existing_position) {
2035 (Some(new_pos), Some(existing_pos)) => {
2037 if new_pos < existing_pos {
2038 insertion_index = i;
2039 break;
2040 }
2041 }
2042 (Some(_), None) => {
2044 }
2046 (None, Some(_)) => {
2048 }
2050 (None, None) => {
2052 if key < existing_key.as_str() {
2053 insertion_index = i;
2054 break;
2055 }
2056 }
2057 }
2058 }
2059 }
2060 }
2061 }
2062
2063 if new_field_position.is_some() && insertion_index == self.0.children_with_tokens().count()
2066 {
2067 let children: Vec<_> = self.0.children_with_tokens().enumerate().collect();
2069 for (i, child) in children.into_iter().rev() {
2070 if let Some(node) = child.as_node() {
2071 if let Some(entry) = Entry::cast(node.clone()) {
2072 if let Some(existing_key) = entry.key() {
2073 if field_order
2074 .iter()
2075 .any(|&f| f.eq_ignore_ascii_case(&existing_key))
2076 {
2077 insertion_index = i + 1;
2079 break;
2080 }
2081 }
2082 }
2083 }
2084 }
2085 }
2086
2087 insertion_index
2088 }
2089
2090 pub fn rename(&mut self, old_key: &str, new_key: &str) -> bool {
2094 for entry in self.entries() {
2095 if entry
2096 .key()
2097 .as_deref()
2098 .is_some_and(|k| k.eq_ignore_ascii_case(old_key))
2099 {
2100 self.0.splice_children(
2101 entry.0.index()..entry.0.index() + 1,
2102 vec![Entry::new(new_key, entry.value().as_str()).0.into()],
2103 );
2104 return true;
2105 }
2106 }
2107 false
2108 }
2109}
2110
2111impl Default for Paragraph {
2112 fn default() -> Self {
2113 Self::new()
2114 }
2115}
2116
2117impl std::str::FromStr for Paragraph {
2118 type Err = ParseError;
2119
2120 fn from_str(text: &str) -> Result<Self, Self::Err> {
2121 let deb822 = Deb822::from_str(text)?;
2122
2123 let mut paragraphs = deb822.paragraphs();
2124
2125 paragraphs
2126 .next()
2127 .ok_or_else(|| ParseError(vec!["no paragraphs".to_string()]))
2128 }
2129}
2130
2131#[cfg(feature = "python-debian")]
2132impl<'py> pyo3::IntoPyObject<'py> for Paragraph {
2133 type Target = pyo3::PyAny;
2134 type Output = pyo3::Bound<'py, Self::Target>;
2135 type Error = pyo3::PyErr;
2136
2137 fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
2138 use pyo3::prelude::*;
2139 let d = pyo3::types::PyDict::new(py);
2140 for (k, v) in self.items() {
2141 d.set_item(k, v)?;
2142 }
2143 let m = py.import("debian.deb822")?;
2144 let cls = m.getattr("Deb822")?;
2145 cls.call1((d,))
2146 }
2147}
2148
2149#[cfg(feature = "python-debian")]
2150impl<'py> pyo3::IntoPyObject<'py> for &Paragraph {
2151 type Target = pyo3::PyAny;
2152 type Output = pyo3::Bound<'py, Self::Target>;
2153 type Error = pyo3::PyErr;
2154
2155 fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
2156 use pyo3::prelude::*;
2157 let d = pyo3::types::PyDict::new(py);
2158 for (k, v) in self.items() {
2159 d.set_item(k, v)?;
2160 }
2161 let m = py.import("debian.deb822")?;
2162 let cls = m.getattr("Deb822")?;
2163 cls.call1((d,))
2164 }
2165}
2166
2167#[cfg(feature = "python-debian")]
2168impl<'py> pyo3::FromPyObject<'_, 'py> for Paragraph {
2169 type Error = pyo3::PyErr;
2170
2171 fn extract(obj: pyo3::Borrowed<'_, 'py, pyo3::PyAny>) -> Result<Self, Self::Error> {
2172 use pyo3::types::PyAnyMethods;
2173 let d = obj.call_method0("__str__")?.extract::<String>()?;
2174 Paragraph::from_str(&d)
2175 .map_err(|e| pyo3::exceptions::PyValueError::new_err((e.to_string(),)))
2176 }
2177}
2178
2179impl Entry {
2180 pub fn snapshot(&self) -> Self {
2189 Entry(SyntaxNode::new_root_mut(self.0.green().into_owned()))
2190 }
2191
2192 pub fn text_range(&self) -> rowan::TextRange {
2194 self.0.text_range()
2195 }
2196
2197 pub fn key_range(&self) -> Option<rowan::TextRange> {
2199 self.0
2200 .children_with_tokens()
2201 .filter_map(|it| it.into_token())
2202 .find(|it| it.kind() == KEY)
2203 .map(|it| it.text_range())
2204 }
2205
2206 pub fn colon_range(&self) -> Option<rowan::TextRange> {
2208 self.0
2209 .children_with_tokens()
2210 .filter_map(|it| it.into_token())
2211 .find(|it| it.kind() == COLON)
2212 .map(|it| it.text_range())
2213 }
2214
2215 pub fn value_range(&self) -> Option<rowan::TextRange> {
2218 let value_tokens: Vec<_> = self
2219 .0
2220 .children_with_tokens()
2221 .filter_map(|it| it.into_token())
2222 .filter(|it| it.kind() == VALUE)
2223 .collect();
2224
2225 if value_tokens.is_empty() {
2226 return None;
2227 }
2228
2229 let first = value_tokens.first().unwrap();
2230 let last = value_tokens.last().unwrap();
2231 Some(rowan::TextRange::new(
2232 first.text_range().start(),
2233 last.text_range().end(),
2234 ))
2235 }
2236
2237 pub fn value_line_ranges(&self) -> Vec<rowan::TextRange> {
2240 self.0
2241 .children_with_tokens()
2242 .filter_map(|it| it.into_token())
2243 .filter(|it| it.kind() == VALUE)
2244 .map(|it| it.text_range())
2245 .collect()
2246 }
2247
2248 pub fn new(key: &str, value: &str) -> Entry {
2250 Self::with_indentation(key, value, " ")
2251 }
2252
2253 pub fn with_indentation(key: &str, value: &str, indent: &str) -> Entry {
2260 Entry::with_formatting(key, value, " ", indent)
2261 }
2262
2263 pub fn try_with_formatting(
2274 key: &str,
2275 value: &str,
2276 post_colon_ws: &str,
2277 indent: &str,
2278 ) -> Result<Entry, Error> {
2279 let mut builder = GreenNodeBuilder::new();
2280
2281 builder.start_node(ENTRY.into());
2282 builder.token(KEY.into(), key);
2283 builder.token(COLON.into(), ":");
2284
2285 let mut i = 0;
2287 while i < post_colon_ws.len() {
2288 if post_colon_ws[i..].starts_with('\n') {
2289 builder.token(NEWLINE.into(), "\n");
2290 i += 1;
2291 } else {
2292 let start = i;
2294 while i < post_colon_ws.len() && !post_colon_ws[i..].starts_with('\n') {
2295 i += post_colon_ws[i..].chars().next().unwrap().len_utf8();
2296 }
2297 builder.token(WHITESPACE.into(), &post_colon_ws[start..i]);
2298 }
2299 }
2300
2301 for (line_idx, line) in value.split('\n').enumerate() {
2302 if line_idx > 0 {
2303 if line.trim().is_empty() {
2306 return Err(Error::InvalidValue(format!(
2307 "empty continuation line (line with only whitespace) at line {}",
2308 line_idx + 1
2309 )));
2310 }
2311 builder.token(INDENT.into(), indent);
2312 }
2313 builder.token(VALUE.into(), line);
2314 builder.token(NEWLINE.into(), "\n");
2315 }
2316 builder.finish_node();
2317 Ok(Entry(SyntaxNode::new_root_mut(builder.finish())))
2318 }
2319
2320 pub fn with_formatting(key: &str, value: &str, post_colon_ws: &str, indent: &str) -> Entry {
2331 Self::try_with_formatting(key, value, post_colon_ws, indent)
2332 .expect("Invalid value: empty continuation line")
2333 }
2334
2335 #[must_use]
2336 pub fn wrap_and_sort(
2349 &self,
2350 mut indentation: Indentation,
2351 immediate_empty_line: bool,
2352 max_line_length_one_liner: Option<usize>,
2353 format_value: Option<&dyn Fn(&str, &str) -> String>,
2354 ) -> Entry {
2355 let mut builder = GreenNodeBuilder::new();
2356
2357 let mut content = vec![];
2358 builder.start_node(ENTRY.into());
2359 for c in self.0.children_with_tokens() {
2360 let text = c.as_token().map(|t| t.text());
2361 match c.kind() {
2362 KEY => {
2363 builder.token(KEY.into(), text.unwrap());
2364 if indentation == Indentation::FieldNameLength {
2365 indentation = Indentation::Spaces(text.unwrap().len() as u32);
2366 }
2367 }
2368 COLON => {
2369 builder.token(COLON.into(), ":");
2370 }
2371 INDENT => {
2372 }
2374 ERROR | COMMENT | VALUE | WHITESPACE | NEWLINE => {
2375 content.push(c);
2376 }
2377 EMPTY_LINE | ENTRY | ROOT | PARAGRAPH => unreachable!(),
2378 }
2379 }
2380
2381 let indentation = if let crate::Indentation::Spaces(i) = indentation {
2382 i
2383 } else {
2384 1
2385 };
2386
2387 assert!(indentation > 0);
2388
2389 while let Some(c) = content.last() {
2391 if c.kind() == NEWLINE || c.kind() == WHITESPACE {
2392 content.pop();
2393 } else {
2394 break;
2395 }
2396 }
2397
2398 let tokens = if let Some(ref format_value) = format_value {
2401 if !content
2402 .iter()
2403 .any(|c| c.kind() == ERROR || c.kind() == COMMENT)
2404 {
2405 let concat = content
2406 .iter()
2407 .filter_map(|c| c.as_token().map(|t| t.text()))
2408 .collect::<String>();
2409 let formatted = format_value(self.key().as_ref().unwrap(), &concat);
2410 crate::lex::lex_inline(&formatted)
2411 .map(|(k, t)| (k, t.to_string()))
2412 .collect::<Vec<_>>()
2413 } else {
2414 content
2415 .into_iter()
2416 .map(|n| n.into_token().unwrap())
2417 .map(|i| (i.kind(), i.text().to_string()))
2418 .collect::<Vec<_>>()
2419 }
2420 } else {
2421 content
2422 .into_iter()
2423 .map(|n| n.into_token().unwrap())
2424 .map(|i| (i.kind(), i.text().to_string()))
2425 .collect::<Vec<_>>()
2426 };
2427
2428 rebuild_value(
2429 &mut builder,
2430 tokens,
2431 self.key().map_or(0, |k| k.len()),
2432 indentation,
2433 immediate_empty_line,
2434 max_line_length_one_liner,
2435 );
2436
2437 builder.finish_node();
2438 Self(SyntaxNode::new_root_mut(builder.finish()))
2439 }
2440
2441 pub fn key(&self) -> Option<String> {
2443 self.0
2444 .children_with_tokens()
2445 .filter_map(|it| it.into_token())
2446 .find(|it| it.kind() == KEY)
2447 .map(|it| it.text().to_string())
2448 }
2449
2450 pub fn value(&self) -> String {
2452 let mut parts = self
2453 .0
2454 .children_with_tokens()
2455 .filter_map(|it| it.into_token())
2456 .filter(|it| it.kind() == VALUE)
2457 .map(|it| it.text().to_string());
2458
2459 match parts.next() {
2460 None => String::new(),
2461 Some(first) => {
2462 let mut result = first;
2463 for part in parts {
2464 result.push('\n');
2465 result.push_str(&part);
2466 }
2467 result
2468 }
2469 }
2470 }
2471
2472 fn get_indent(&self) -> Option<String> {
2475 self.0
2476 .children_with_tokens()
2477 .filter_map(|it| it.into_token())
2478 .find(|it| it.kind() == INDENT)
2479 .map(|it| it.text().to_string())
2480 }
2481
2482 fn get_post_colon_whitespace(&self) -> Option<String> {
2486 let mut found_colon = false;
2487 let mut whitespace = String::new();
2488
2489 for token in self
2490 .0
2491 .children_with_tokens()
2492 .filter_map(|it| it.into_token())
2493 {
2494 if token.kind() == COLON {
2495 found_colon = true;
2496 continue;
2497 }
2498
2499 if found_colon {
2500 if token.kind() == WHITESPACE || token.kind() == NEWLINE || token.kind() == INDENT {
2501 whitespace.push_str(token.text());
2502 } else {
2503 break;
2505 }
2506 }
2507 }
2508
2509 if whitespace.is_empty() {
2510 None
2511 } else {
2512 Some(whitespace)
2513 }
2514 }
2515
2516 pub fn normalize_field_spacing(&mut self) -> bool {
2537 use rowan::GreenNodeBuilder;
2538
2539 let original_text = self.0.text().to_string();
2541
2542 let mut builder = GreenNodeBuilder::new();
2544 builder.start_node(ENTRY.into());
2545
2546 let mut seen_colon = false;
2547 let mut skip_whitespace = false;
2548
2549 for child in self.0.children_with_tokens() {
2550 match child.kind() {
2551 KEY => {
2552 builder.token(KEY.into(), child.as_token().unwrap().text());
2553 }
2554 COLON => {
2555 builder.token(COLON.into(), ":");
2556 seen_colon = true;
2557 skip_whitespace = true;
2558 }
2559 WHITESPACE if skip_whitespace => {
2560 continue;
2562 }
2563 VALUE if skip_whitespace => {
2564 builder.token(WHITESPACE.into(), " ");
2566 builder.token(VALUE.into(), child.as_token().unwrap().text());
2567 skip_whitespace = false;
2568 }
2569 NEWLINE if skip_whitespace && seen_colon => {
2570 builder.token(NEWLINE.into(), "\n");
2573 skip_whitespace = false;
2574 }
2575 _ => {
2576 if let Some(token) = child.as_token() {
2578 builder.token(token.kind().into(), token.text());
2579 }
2580 }
2581 }
2582 }
2583
2584 builder.finish_node();
2585 let normalized_green = builder.finish();
2586 let normalized = SyntaxNode::new_root_mut(normalized_green);
2587
2588 let changed = original_text != normalized.text().to_string();
2590
2591 if changed {
2592 if let Some(parent) = self.0.parent() {
2594 let index = self.0.index();
2595 parent.splice_children(index..index + 1, vec![normalized.into()]);
2596 }
2597 }
2598
2599 changed
2600 }
2601
2602 pub fn detach(&mut self) {
2604 self.0.detach();
2605 }
2606}
2607
2608impl FromStr for Deb822 {
2609 type Err = ParseError;
2610
2611 fn from_str(s: &str) -> Result<Self, Self::Err> {
2612 Deb822::parse(s).to_result()
2613 }
2614}
2615
2616#[test]
2617fn test_parse_simple() {
2618 const CONTROLV1: &str = r#"Source: foo
2619Maintainer: Foo Bar <foo@example.com>
2620Section: net
2621
2622# This is a comment
2623
2624Package: foo
2625Architecture: all
2626Depends:
2627 bar,
2628 blah
2629Description: This is a description
2630 And it is
2631 .
2632 multiple
2633 lines
2634"#;
2635 let parsed = parse(CONTROLV1);
2636 let node = parsed.syntax();
2637 assert_eq!(
2638 format!("{:#?}", node),
2639 r###"ROOT@0..203
2640 PARAGRAPH@0..63
2641 ENTRY@0..12
2642 KEY@0..6 "Source"
2643 COLON@6..7 ":"
2644 WHITESPACE@7..8 " "
2645 VALUE@8..11 "foo"
2646 NEWLINE@11..12 "\n"
2647 ENTRY@12..50
2648 KEY@12..22 "Maintainer"
2649 COLON@22..23 ":"
2650 WHITESPACE@23..24 " "
2651 VALUE@24..49 "Foo Bar <foo@example. ..."
2652 NEWLINE@49..50 "\n"
2653 ENTRY@50..63
2654 KEY@50..57 "Section"
2655 COLON@57..58 ":"
2656 WHITESPACE@58..59 " "
2657 VALUE@59..62 "net"
2658 NEWLINE@62..63 "\n"
2659 EMPTY_LINE@63..64
2660 NEWLINE@63..64 "\n"
2661 EMPTY_LINE@64..84
2662 COMMENT@64..83 "# This is a comment"
2663 NEWLINE@83..84 "\n"
2664 EMPTY_LINE@84..85
2665 NEWLINE@84..85 "\n"
2666 PARAGRAPH@85..203
2667 ENTRY@85..98
2668 KEY@85..92 "Package"
2669 COLON@92..93 ":"
2670 WHITESPACE@93..94 " "
2671 VALUE@94..97 "foo"
2672 NEWLINE@97..98 "\n"
2673 ENTRY@98..116
2674 KEY@98..110 "Architecture"
2675 COLON@110..111 ":"
2676 WHITESPACE@111..112 " "
2677 VALUE@112..115 "all"
2678 NEWLINE@115..116 "\n"
2679 ENTRY@116..137
2680 KEY@116..123 "Depends"
2681 COLON@123..124 ":"
2682 NEWLINE@124..125 "\n"
2683 INDENT@125..126 " "
2684 VALUE@126..130 "bar,"
2685 NEWLINE@130..131 "\n"
2686 INDENT@131..132 " "
2687 VALUE@132..136 "blah"
2688 NEWLINE@136..137 "\n"
2689 ENTRY@137..203
2690 KEY@137..148 "Description"
2691 COLON@148..149 ":"
2692 WHITESPACE@149..150 " "
2693 VALUE@150..171 "This is a description"
2694 NEWLINE@171..172 "\n"
2695 INDENT@172..173 " "
2696 VALUE@173..182 "And it is"
2697 NEWLINE@182..183 "\n"
2698 INDENT@183..184 " "
2699 VALUE@184..185 "."
2700 NEWLINE@185..186 "\n"
2701 INDENT@186..187 " "
2702 VALUE@187..195 "multiple"
2703 NEWLINE@195..196 "\n"
2704 INDENT@196..197 " "
2705 VALUE@197..202 "lines"
2706 NEWLINE@202..203 "\n"
2707"###
2708 );
2709 assert_eq!(parsed.errors, Vec::<String>::new());
2710
2711 let root = parsed.root_mut();
2712 assert_eq!(root.paragraphs().count(), 2);
2713 let source = root.paragraphs().next().unwrap();
2714 assert_eq!(
2715 source.keys().collect::<Vec<_>>(),
2716 vec!["Source", "Maintainer", "Section"]
2717 );
2718 assert_eq!(source.get("Source").as_deref(), Some("foo"));
2719 assert_eq!(
2720 source.get("Maintainer").as_deref(),
2721 Some("Foo Bar <foo@example.com>")
2722 );
2723 assert_eq!(source.get("Section").as_deref(), Some("net"));
2724 assert_eq!(
2725 source.items().collect::<Vec<_>>(),
2726 vec![
2727 ("Source".into(), "foo".into()),
2728 ("Maintainer".into(), "Foo Bar <foo@example.com>".into()),
2729 ("Section".into(), "net".into()),
2730 ]
2731 );
2732
2733 let binary = root.paragraphs().nth(1).unwrap();
2734 assert_eq!(
2735 binary.keys().collect::<Vec<_>>(),
2736 vec!["Package", "Architecture", "Depends", "Description"]
2737 );
2738 assert_eq!(binary.get("Package").as_deref(), Some("foo"));
2739 assert_eq!(binary.get("Architecture").as_deref(), Some("all"));
2740 assert_eq!(binary.get("Depends").as_deref(), Some("bar,\nblah"));
2741 assert_eq!(
2742 binary.get("Description").as_deref(),
2743 Some("This is a description\nAnd it is\n.\nmultiple\nlines")
2744 );
2745
2746 assert_eq!(node.text(), CONTROLV1);
2747}
2748
2749#[test]
2750fn test_with_trailing_whitespace() {
2751 const CONTROLV1: &str = r#"Source: foo
2752Maintainer: Foo Bar <foo@example.com>
2753
2754
2755"#;
2756 let parsed = parse(CONTROLV1);
2757 let node = parsed.syntax();
2758 assert_eq!(
2759 format!("{:#?}", node),
2760 r###"ROOT@0..52
2761 PARAGRAPH@0..50
2762 ENTRY@0..12
2763 KEY@0..6 "Source"
2764 COLON@6..7 ":"
2765 WHITESPACE@7..8 " "
2766 VALUE@8..11 "foo"
2767 NEWLINE@11..12 "\n"
2768 ENTRY@12..50
2769 KEY@12..22 "Maintainer"
2770 COLON@22..23 ":"
2771 WHITESPACE@23..24 " "
2772 VALUE@24..49 "Foo Bar <foo@example. ..."
2773 NEWLINE@49..50 "\n"
2774 EMPTY_LINE@50..51
2775 NEWLINE@50..51 "\n"
2776 EMPTY_LINE@51..52
2777 NEWLINE@51..52 "\n"
2778"###
2779 );
2780 assert_eq!(parsed.errors, Vec::<String>::new());
2781
2782 let root = parsed.root_mut();
2783 assert_eq!(root.paragraphs().count(), 1);
2784 let source = root.paragraphs().next().unwrap();
2785 assert_eq!(
2786 source.items().collect::<Vec<_>>(),
2787 vec![
2788 ("Source".into(), "foo".into()),
2789 ("Maintainer".into(), "Foo Bar <foo@example.com>".into()),
2790 ]
2791 );
2792}
2793
2794fn rebuild_value(
2795 builder: &mut GreenNodeBuilder,
2796 mut tokens: Vec<(SyntaxKind, String)>,
2797 key_len: usize,
2798 indentation: u32,
2799 immediate_empty_line: bool,
2800 max_line_length_one_liner: Option<usize>,
2801) {
2802 let first_line_len = tokens
2803 .iter()
2804 .take_while(|(k, _t)| *k != NEWLINE)
2805 .map(|(_k, t)| t.len())
2806 .sum::<usize>() + key_len + 2 ;
2807
2808 let has_newline = tokens.iter().any(|(k, _t)| *k == NEWLINE);
2809
2810 let mut last_was_newline = false;
2811 if max_line_length_one_liner
2812 .map(|mll| first_line_len <= mll)
2813 .unwrap_or(false)
2814 && !has_newline
2815 {
2816 for (k, t) in tokens {
2818 builder.token(k.into(), &t);
2819 }
2820 } else {
2821 if immediate_empty_line && has_newline {
2823 builder.token(NEWLINE.into(), "\n");
2824 last_was_newline = true;
2825 } else {
2826 builder.token(WHITESPACE.into(), " ");
2827 }
2828 let mut start_idx = 0;
2830 while start_idx < tokens.len() {
2831 if tokens[start_idx].0 == NEWLINE || tokens[start_idx].0 == WHITESPACE {
2832 start_idx += 1;
2833 } else {
2834 break;
2835 }
2836 }
2837 tokens.drain(..start_idx);
2838 let indent_str = " ".repeat(indentation as usize);
2840 for (k, t) in tokens {
2841 if last_was_newline {
2842 builder.token(INDENT.into(), &indent_str);
2843 }
2844 builder.token(k.into(), &t);
2845 last_was_newline = k == NEWLINE;
2846 }
2847 }
2848
2849 if !last_was_newline {
2850 builder.token(NEWLINE.into(), "\n");
2851 }
2852}
2853
2854#[cfg(test)]
2855mod tests {
2856 use super::*;
2857 #[test]
2858 fn test_parse() {
2859 let d: super::Deb822 = r#"Source: foo
2860Maintainer: Foo Bar <jelmer@jelmer.uk>
2861Section: net
2862
2863Package: foo
2864Architecture: all
2865Depends: libc6
2866Description: This is a description
2867 With details
2868"#
2869 .parse()
2870 .unwrap();
2871 let mut ps = d.paragraphs();
2872 let p = ps.next().unwrap();
2873
2874 assert_eq!(p.get("Source").as_deref(), Some("foo"));
2875 assert_eq!(
2876 p.get("Maintainer").as_deref(),
2877 Some("Foo Bar <jelmer@jelmer.uk>")
2878 );
2879 assert_eq!(p.get("Section").as_deref(), Some("net"));
2880
2881 let b = ps.next().unwrap();
2882 assert_eq!(b.get("Package").as_deref(), Some("foo"));
2883 }
2884
2885 #[test]
2886 fn test_after_multi_line() {
2887 let d: super::Deb822 = r#"Source: golang-github-blah-blah
2888Section: devel
2889Priority: optional
2890Standards-Version: 4.2.0
2891Maintainer: Some Maintainer <example@example.com>
2892Build-Depends: debhelper (>= 11~),
2893 dh-golang,
2894 golang-any
2895Homepage: https://github.com/j-keck/arping
2896"#
2897 .parse()
2898 .unwrap();
2899 let mut ps = d.paragraphs();
2900 let p = ps.next().unwrap();
2901 assert_eq!(p.get("Source").as_deref(), Some("golang-github-blah-blah"));
2902 assert_eq!(p.get("Section").as_deref(), Some("devel"));
2903 assert_eq!(p.get("Priority").as_deref(), Some("optional"));
2904 assert_eq!(p.get("Standards-Version").as_deref(), Some("4.2.0"));
2905 assert_eq!(
2906 p.get("Maintainer").as_deref(),
2907 Some("Some Maintainer <example@example.com>")
2908 );
2909 assert_eq!(
2910 p.get("Build-Depends").as_deref(),
2911 Some("debhelper (>= 11~),\ndh-golang,\ngolang-any")
2912 );
2913 assert_eq!(
2914 p.get("Homepage").as_deref(),
2915 Some("https://github.com/j-keck/arping")
2916 );
2917 }
2918
2919 #[test]
2920 fn test_remove_field() {
2921 let d: super::Deb822 = r#"Source: foo
2922# Comment
2923Maintainer: Foo Bar <jelmer@jelmer.uk>
2924Section: net
2925
2926Package: foo
2927Architecture: all
2928Depends: libc6
2929Description: This is a description
2930 With details
2931"#
2932 .parse()
2933 .unwrap();
2934 let mut ps = d.paragraphs();
2935 let mut p = ps.next().unwrap();
2936 p.set("Foo", "Bar");
2937 p.remove("Section");
2938 p.remove("Nonexistent");
2939 assert_eq!(p.get("Foo").as_deref(), Some("Bar"));
2940 assert_eq!(
2941 p.to_string(),
2942 r#"Source: foo
2943# Comment
2944Maintainer: Foo Bar <jelmer@jelmer.uk>
2945Foo: Bar
2946"#
2947 );
2948 }
2949
2950 #[test]
2951 fn test_rename_field() {
2952 let d: super::Deb822 = r#"Source: foo
2953Vcs-Browser: https://salsa.debian.org/debian/foo
2954"#
2955 .parse()
2956 .unwrap();
2957 let mut ps = d.paragraphs();
2958 let mut p = ps.next().unwrap();
2959 assert!(p.rename("Vcs-Browser", "Homepage"));
2960 assert_eq!(
2961 p.to_string(),
2962 r#"Source: foo
2963Homepage: https://salsa.debian.org/debian/foo
2964"#
2965 );
2966
2967 assert_eq!(
2968 p.get("Homepage").as_deref(),
2969 Some("https://salsa.debian.org/debian/foo")
2970 );
2971 assert_eq!(p.get("Vcs-Browser").as_deref(), None);
2972
2973 assert!(!p.rename("Nonexistent", "Homepage"));
2975 }
2976
2977 #[test]
2978 fn test_set_field() {
2979 let d: super::Deb822 = r#"Source: foo
2980Maintainer: Foo Bar <joe@example.com>
2981"#
2982 .parse()
2983 .unwrap();
2984 let mut ps = d.paragraphs();
2985 let mut p = ps.next().unwrap();
2986 p.set("Maintainer", "Somebody Else <jane@example.com>");
2987 assert_eq!(
2988 p.get("Maintainer").as_deref(),
2989 Some("Somebody Else <jane@example.com>")
2990 );
2991 assert_eq!(
2992 p.to_string(),
2993 r#"Source: foo
2994Maintainer: Somebody Else <jane@example.com>
2995"#
2996 );
2997 }
2998
2999 #[test]
3000 fn test_set_new_field() {
3001 let d: super::Deb822 = r#"Source: foo
3002"#
3003 .parse()
3004 .unwrap();
3005 let mut ps = d.paragraphs();
3006 let mut p = ps.next().unwrap();
3007 p.set("Maintainer", "Somebody <joe@example.com>");
3008 assert_eq!(
3009 p.get("Maintainer").as_deref(),
3010 Some("Somebody <joe@example.com>")
3011 );
3012 assert_eq!(
3013 p.to_string(),
3014 r#"Source: foo
3015Maintainer: Somebody <joe@example.com>
3016"#
3017 );
3018 }
3019
3020 #[test]
3021 fn test_add_paragraph() {
3022 let mut d = super::Deb822::new();
3023 let mut p = d.add_paragraph();
3024 p.set("Foo", "Bar");
3025 assert_eq!(p.get("Foo").as_deref(), Some("Bar"));
3026 assert_eq!(
3027 p.to_string(),
3028 r#"Foo: Bar
3029"#
3030 );
3031 assert_eq!(
3032 d.to_string(),
3033 r#"Foo: Bar
3034"#
3035 );
3036
3037 let mut p = d.add_paragraph();
3038 p.set("Foo", "Blah");
3039 assert_eq!(p.get("Foo").as_deref(), Some("Blah"));
3040 assert_eq!(
3041 d.to_string(),
3042 r#"Foo: Bar
3043
3044Foo: Blah
3045"#
3046 );
3047 }
3048
3049 #[test]
3050 fn test_crud_paragraph() {
3051 let mut d = super::Deb822::new();
3052 let mut p = d.insert_paragraph(0);
3053 p.set("Foo", "Bar");
3054 assert_eq!(p.get("Foo").as_deref(), Some("Bar"));
3055 assert_eq!(
3056 d.to_string(),
3057 r#"Foo: Bar
3058"#
3059 );
3060
3061 let mut p = d.insert_paragraph(0);
3063 p.set("Foo", "Blah");
3064 assert_eq!(p.get("Foo").as_deref(), Some("Blah"));
3065 assert_eq!(
3066 d.to_string(),
3067 r#"Foo: Blah
3068
3069Foo: Bar
3070"#
3071 );
3072
3073 d.remove_paragraph(1);
3075 assert_eq!(d.to_string(), "Foo: Blah\n\n");
3076
3077 p.set("Foo", "Baz");
3079 assert_eq!(d.to_string(), "Foo: Baz\n\n");
3080
3081 d.remove_paragraph(0);
3083 assert_eq!(d.to_string(), "");
3084 }
3085
3086 #[test]
3087 fn test_swap_paragraphs() {
3088 let mut d: super::Deb822 = vec![
3090 vec![("Foo", "Bar")].into_iter().collect(),
3091 vec![("A", "B")].into_iter().collect(),
3092 vec![("X", "Y")].into_iter().collect(),
3093 ]
3094 .into_iter()
3095 .collect();
3096
3097 d.swap_paragraphs(0, 2);
3098 assert_eq!(d.to_string(), "X: Y\n\nA: B\n\nFoo: Bar\n");
3099
3100 d.swap_paragraphs(0, 2);
3102 assert_eq!(d.to_string(), "Foo: Bar\n\nA: B\n\nX: Y\n");
3103
3104 d.swap_paragraphs(0, 1);
3106 assert_eq!(d.to_string(), "A: B\n\nFoo: Bar\n\nX: Y\n");
3107
3108 let before = d.to_string();
3110 d.swap_paragraphs(1, 1);
3111 assert_eq!(d.to_string(), before);
3112 }
3113
3114 #[test]
3115 fn test_swap_paragraphs_preserves_content() {
3116 let mut d: super::Deb822 = vec![
3118 vec![("Field1", "Value1"), ("Field2", "Value2")]
3119 .into_iter()
3120 .collect(),
3121 vec![("FieldA", "ValueA"), ("FieldB", "ValueB")]
3122 .into_iter()
3123 .collect(),
3124 ]
3125 .into_iter()
3126 .collect();
3127
3128 d.swap_paragraphs(0, 1);
3129
3130 let mut paras = d.paragraphs();
3131 let p1 = paras.next().unwrap();
3132 assert_eq!(p1.get("FieldA").as_deref(), Some("ValueA"));
3133 assert_eq!(p1.get("FieldB").as_deref(), Some("ValueB"));
3134
3135 let p2 = paras.next().unwrap();
3136 assert_eq!(p2.get("Field1").as_deref(), Some("Value1"));
3137 assert_eq!(p2.get("Field2").as_deref(), Some("Value2"));
3138 }
3139
3140 #[test]
3141 #[should_panic(expected = "out of bounds")]
3142 fn test_swap_paragraphs_out_of_bounds() {
3143 let mut d: super::Deb822 = vec![
3144 vec![("Foo", "Bar")].into_iter().collect(),
3145 vec![("A", "B")].into_iter().collect(),
3146 ]
3147 .into_iter()
3148 .collect();
3149
3150 d.swap_paragraphs(0, 5);
3151 }
3152
3153 #[test]
3154 fn test_multiline_entry() {
3155 use super::SyntaxKind::*;
3156 use rowan::ast::AstNode;
3157
3158 let entry = super::Entry::new("foo", "bar\nbaz");
3159 let tokens: Vec<_> = entry
3160 .syntax()
3161 .descendants_with_tokens()
3162 .filter_map(|tok| tok.into_token())
3163 .collect();
3164
3165 assert_eq!("foo: bar\n baz\n", entry.to_string());
3166 assert_eq!("bar\nbaz", entry.value());
3167
3168 assert_eq!(
3169 vec![
3170 (KEY, "foo"),
3171 (COLON, ":"),
3172 (WHITESPACE, " "),
3173 (VALUE, "bar"),
3174 (NEWLINE, "\n"),
3175 (INDENT, " "),
3176 (VALUE, "baz"),
3177 (NEWLINE, "\n"),
3178 ],
3179 tokens
3180 .iter()
3181 .map(|token| (token.kind(), token.text()))
3182 .collect::<Vec<_>>()
3183 );
3184 }
3185
3186 #[test]
3187 fn test_apt_entry() {
3188 let text = r#"Package: cvsd
3189Binary: cvsd
3190Version: 1.0.24
3191Maintainer: Arthur de Jong <adejong@debian.org>
3192Build-Depends: debhelper (>= 9), po-debconf
3193Architecture: any
3194Standards-Version: 3.9.3
3195Format: 3.0 (native)
3196Files:
3197 b7a7d67a02974c52c408fdb5e118406d 890 cvsd_1.0.24.dsc
3198 b73ee40774c3086cb8490cdbb96ac883 258139 cvsd_1.0.24.tar.gz
3199Vcs-Browser: http://arthurdejong.org/viewvc/cvsd/
3200Vcs-Cvs: :pserver:anonymous@arthurdejong.org:/arthur/
3201Checksums-Sha256:
3202 a7bb7a3aacee19cd14ce5c26cb86e348b1608e6f1f6e97c6ea7c58efa440ac43 890 cvsd_1.0.24.dsc
3203 46bc517760c1070ae408693b89603986b53e6f068ae6bdc744e2e830e46b8cba 258139 cvsd_1.0.24.tar.gz
3204Homepage: http://arthurdejong.org/cvsd/
3205Package-List:
3206 cvsd deb vcs optional
3207Directory: pool/main/c/cvsd
3208Priority: source
3209Section: vcs
3210
3211"#;
3212 let d: super::Deb822 = text.parse().unwrap();
3213 let p = d.paragraphs().next().unwrap();
3214 assert_eq!(p.get("Binary").as_deref(), Some("cvsd"));
3215 assert_eq!(p.get("Version").as_deref(), Some("1.0.24"));
3216 assert_eq!(
3217 p.get("Maintainer").as_deref(),
3218 Some("Arthur de Jong <adejong@debian.org>")
3219 );
3220 }
3221
3222 #[test]
3223 fn test_format() {
3224 let d: super::Deb822 = r#"Source: foo
3225Maintainer: Foo Bar <foo@example.com>
3226Section: net
3227Blah: blah # comment
3228Multi-Line:
3229 Ahoi!
3230 Matey!
3231
3232"#
3233 .parse()
3234 .unwrap();
3235 let mut ps = d.paragraphs();
3236 let p = ps.next().unwrap();
3237 let result = p.wrap_and_sort(
3238 crate::Indentation::FieldNameLength,
3239 false,
3240 None,
3241 None::<&dyn Fn(&super::Entry, &super::Entry) -> std::cmp::Ordering>,
3242 None,
3243 );
3244 assert_eq!(
3245 result.to_string(),
3246 r#"Source: foo
3247Maintainer: Foo Bar <foo@example.com>
3248Section: net
3249Blah: blah # comment
3250Multi-Line: Ahoi!
3251 Matey!
3252"#
3253 );
3254 }
3255
3256 #[test]
3257 fn test_format_sort_paragraphs() {
3258 let d: super::Deb822 = r#"Source: foo
3259Maintainer: Foo Bar <foo@example.com>
3260
3261# This is a comment
3262Source: bar
3263Maintainer: Bar Foo <bar@example.com>
3264
3265"#
3266 .parse()
3267 .unwrap();
3268 let result = d.wrap_and_sort(
3269 Some(&|a: &super::Paragraph, b: &super::Paragraph| {
3270 a.get("Source").cmp(&b.get("Source"))
3271 }),
3272 Some(&|p| {
3273 p.wrap_and_sort(
3274 crate::Indentation::FieldNameLength,
3275 false,
3276 None,
3277 None::<&dyn Fn(&super::Entry, &super::Entry) -> std::cmp::Ordering>,
3278 None,
3279 )
3280 }),
3281 );
3282 assert_eq!(
3283 result.to_string(),
3284 r#"# This is a comment
3285Source: bar
3286Maintainer: Bar Foo <bar@example.com>
3287
3288Source: foo
3289Maintainer: Foo Bar <foo@example.com>
3290"#,
3291 );
3292 }
3293
3294 #[test]
3295 fn test_format_sort_fields() {
3296 let d: super::Deb822 = r#"Source: foo
3297Maintainer: Foo Bar <foo@example.com>
3298Build-Depends: debhelper (>= 9), po-debconf
3299Homepage: https://example.com/
3300
3301"#
3302 .parse()
3303 .unwrap();
3304 let result = d.wrap_and_sort(
3305 None,
3306 Some(&|p: &super::Paragraph| -> super::Paragraph {
3307 p.wrap_and_sort(
3308 crate::Indentation::FieldNameLength,
3309 false,
3310 None,
3311 Some(&|a: &super::Entry, b: &super::Entry| a.key().cmp(&b.key())),
3312 None,
3313 )
3314 }),
3315 );
3316 assert_eq!(
3317 result.to_string(),
3318 r#"Build-Depends: debhelper (>= 9), po-debconf
3319Homepage: https://example.com/
3320Maintainer: Foo Bar <foo@example.com>
3321Source: foo
3322"#
3323 );
3324 }
3325
3326 #[test]
3327 fn test_para_from_iter() {
3328 let p: super::Paragraph = vec![("Foo", "Bar"), ("Baz", "Qux")].into_iter().collect();
3329 assert_eq!(
3330 p.to_string(),
3331 r#"Foo: Bar
3332Baz: Qux
3333"#
3334 );
3335
3336 let p: super::Paragraph = vec![
3337 ("Foo".to_string(), "Bar".to_string()),
3338 ("Baz".to_string(), "Qux".to_string()),
3339 ]
3340 .into_iter()
3341 .collect();
3342
3343 assert_eq!(
3344 p.to_string(),
3345 r#"Foo: Bar
3346Baz: Qux
3347"#
3348 );
3349 }
3350
3351 #[test]
3352 fn test_deb822_from_iter() {
3353 let d: super::Deb822 = vec![
3354 vec![("Foo", "Bar"), ("Baz", "Qux")].into_iter().collect(),
3355 vec![("A", "B"), ("C", "D")].into_iter().collect(),
3356 ]
3357 .into_iter()
3358 .collect();
3359 assert_eq!(
3360 d.to_string(),
3361 r#"Foo: Bar
3362Baz: Qux
3363
3364A: B
3365C: D
3366"#
3367 );
3368 }
3369
3370 #[test]
3371 fn test_format_parse_error() {
3372 assert_eq!(ParseError(vec!["foo".to_string()]).to_string(), "foo\n");
3373 }
3374
3375 #[test]
3376 fn test_set_with_field_order() {
3377 let mut p = super::Paragraph::new();
3378 let custom_order = &["Foo", "Bar", "Baz"];
3379
3380 p.set_with_field_order("Baz", "3", custom_order);
3381 p.set_with_field_order("Foo", "1", custom_order);
3382 p.set_with_field_order("Bar", "2", custom_order);
3383 p.set_with_field_order("Unknown", "4", custom_order);
3384
3385 let keys: Vec<_> = p.keys().collect();
3386 assert_eq!(keys[0], "Foo");
3387 assert_eq!(keys[1], "Bar");
3388 assert_eq!(keys[2], "Baz");
3389 assert_eq!(keys[3], "Unknown");
3390 }
3391
3392 #[test]
3393 fn test_positioned_parse_error() {
3394 let error = PositionedParseError {
3395 message: "test error".to_string(),
3396 range: rowan::TextRange::new(rowan::TextSize::from(5), rowan::TextSize::from(10)),
3397 code: Some("test_code".to_string()),
3398 };
3399 assert_eq!(error.to_string(), "test error");
3400 assert_eq!(error.range.start(), rowan::TextSize::from(5));
3401 assert_eq!(error.range.end(), rowan::TextSize::from(10));
3402 assert_eq!(error.code, Some("test_code".to_string()));
3403 }
3404
3405 #[test]
3406 fn test_format_error() {
3407 assert_eq!(
3408 super::Error::ParseError(ParseError(vec!["foo".to_string()])).to_string(),
3409 "foo\n"
3410 );
3411 }
3412
3413 #[test]
3414 fn test_get_all() {
3415 let d: super::Deb822 = r#"Source: foo
3416Maintainer: Foo Bar <foo@example.com>
3417Maintainer: Bar Foo <bar@example.com>"#
3418 .parse()
3419 .unwrap();
3420 let p = d.paragraphs().next().unwrap();
3421 assert_eq!(
3422 p.get_all("Maintainer").collect::<Vec<_>>(),
3423 vec!["Foo Bar <foo@example.com>", "Bar Foo <bar@example.com>"]
3424 );
3425 }
3426
3427 #[test]
3428 fn test_get_with_indent_single_line() {
3429 let input = "Field: single line value\n";
3430 let deb = super::Deb822::from_str(input).unwrap();
3431 let para = deb.paragraphs().next().unwrap();
3432
3433 assert_eq!(
3435 para.get_with_indent("Field", &super::IndentPattern::Fixed(2)),
3436 Some("single line value".to_string())
3437 );
3438 assert_eq!(
3439 para.get_with_indent("Field", &super::IndentPattern::FieldNameLength),
3440 Some("single line value".to_string())
3441 );
3442 }
3443
3444 #[test]
3445 fn test_get_with_indent_fixed() {
3446 let input = "Field: First\n Second\n Third\n";
3447 let deb = super::Deb822::from_str(input).unwrap();
3448 let para = deb.paragraphs().next().unwrap();
3449
3450 let value = para
3452 .get_with_indent("Field", &super::IndentPattern::Fixed(2))
3453 .unwrap();
3454 assert_eq!(value, "First\n Second\n Third");
3455
3456 let value = para
3458 .get_with_indent("Field", &super::IndentPattern::Fixed(1))
3459 .unwrap();
3460 assert_eq!(value, "First\n Second\n Third");
3461
3462 let value = para
3464 .get_with_indent("Field", &super::IndentPattern::Fixed(3))
3465 .unwrap();
3466 assert_eq!(value, "First\nSecond\nThird");
3467 }
3468
3469 #[test]
3470 fn test_get_with_indent_field_name_length() {
3471 let input = "Description: First line\n Second line\n Third line\n";
3472 let deb = super::Deb822::from_str(input).unwrap();
3473 let para = deb.paragraphs().next().unwrap();
3474
3475 let value = para
3478 .get_with_indent("Description", &super::IndentPattern::FieldNameLength)
3479 .unwrap();
3480 assert_eq!(value, "First line\nSecond line\nThird line");
3481
3482 let value = para
3484 .get_with_indent("Description", &super::IndentPattern::Fixed(2))
3485 .unwrap();
3486 assert_eq!(
3487 value,
3488 "First line\n Second line\n Third line"
3489 );
3490 }
3491
3492 #[test]
3493 fn test_get_with_indent_nonexistent() {
3494 let input = "Field: value\n";
3495 let deb = super::Deb822::from_str(input).unwrap();
3496 let para = deb.paragraphs().next().unwrap();
3497
3498 assert_eq!(
3499 para.get_with_indent("NonExistent", &super::IndentPattern::Fixed(2)),
3500 None
3501 );
3502 }
3503
3504 #[test]
3505 fn test_get_entry() {
3506 let input = r#"Package: test-package
3507Maintainer: Test User <test@example.com>
3508Description: A simple test package
3509 with multiple lines
3510"#;
3511 let deb = super::Deb822::from_str(input).unwrap();
3512 let para = deb.paragraphs().next().unwrap();
3513
3514 let entry = para.get_entry("Package");
3516 assert!(entry.is_some());
3517 let entry = entry.unwrap();
3518 assert_eq!(entry.key(), Some("Package".to_string()));
3519 assert_eq!(entry.value(), "test-package");
3520
3521 let entry = para.get_entry("package");
3523 assert!(entry.is_some());
3524 assert_eq!(entry.unwrap().value(), "test-package");
3525
3526 let entry = para.get_entry("Description");
3528 assert!(entry.is_some());
3529 assert_eq!(
3530 entry.unwrap().value(),
3531 "A simple test package\nwith multiple lines"
3532 );
3533
3534 assert_eq!(para.get_entry("NonExistent"), None);
3536 }
3537
3538 #[test]
3539 fn test_entry_ranges() {
3540 let input = r#"Package: test-package
3541Maintainer: Test User <test@example.com>
3542Description: A simple test package
3543 with multiple lines
3544 of description text"#;
3545
3546 let deb822 = super::Deb822::from_str(input).unwrap();
3547 let paragraph = deb822.paragraphs().next().unwrap();
3548 let entries: Vec<_> = paragraph.entries().collect();
3549
3550 let package_entry = &entries[0];
3552 assert_eq!(package_entry.key(), Some("Package".to_string()));
3553
3554 let key_range = package_entry.key_range().unwrap();
3556 assert_eq!(
3557 &input[key_range.start().into()..key_range.end().into()],
3558 "Package"
3559 );
3560
3561 let colon_range = package_entry.colon_range().unwrap();
3563 assert_eq!(
3564 &input[colon_range.start().into()..colon_range.end().into()],
3565 ":"
3566 );
3567
3568 let value_range = package_entry.value_range().unwrap();
3570 assert_eq!(
3571 &input[value_range.start().into()..value_range.end().into()],
3572 "test-package"
3573 );
3574
3575 let text_range = package_entry.text_range();
3577 assert_eq!(
3578 &input[text_range.start().into()..text_range.end().into()],
3579 "Package: test-package\n"
3580 );
3581
3582 let value_lines = package_entry.value_line_ranges();
3584 assert_eq!(value_lines.len(), 1);
3585 assert_eq!(
3586 &input[value_lines[0].start().into()..value_lines[0].end().into()],
3587 "test-package"
3588 );
3589 }
3590
3591 #[test]
3592 fn test_multiline_entry_ranges() {
3593 let input = r#"Description: Short description
3594 Extended description line 1
3595 Extended description line 2"#;
3596
3597 let deb822 = super::Deb822::from_str(input).unwrap();
3598 let paragraph = deb822.paragraphs().next().unwrap();
3599 let entry = paragraph.entries().next().unwrap();
3600
3601 assert_eq!(entry.key(), Some("Description".to_string()));
3602
3603 let value_range = entry.value_range().unwrap();
3605 let full_value = &input[value_range.start().into()..value_range.end().into()];
3606 assert!(full_value.contains("Short description"));
3607 assert!(full_value.contains("Extended description line 1"));
3608 assert!(full_value.contains("Extended description line 2"));
3609
3610 let value_lines = entry.value_line_ranges();
3612 assert_eq!(value_lines.len(), 3);
3613
3614 assert_eq!(
3615 &input[value_lines[0].start().into()..value_lines[0].end().into()],
3616 "Short description"
3617 );
3618 assert_eq!(
3619 &input[value_lines[1].start().into()..value_lines[1].end().into()],
3620 "Extended description line 1"
3621 );
3622 assert_eq!(
3623 &input[value_lines[2].start().into()..value_lines[2].end().into()],
3624 "Extended description line 2"
3625 );
3626 }
3627
3628 #[test]
3629 fn test_entries_public_access() {
3630 let input = r#"Package: test
3631Version: 1.0"#;
3632
3633 let deb822 = super::Deb822::from_str(input).unwrap();
3634 let paragraph = deb822.paragraphs().next().unwrap();
3635
3636 let entries: Vec<_> = paragraph.entries().collect();
3638 assert_eq!(entries.len(), 2);
3639 assert_eq!(entries[0].key(), Some("Package".to_string()));
3640 assert_eq!(entries[1].key(), Some("Version".to_string()));
3641 }
3642
3643 #[test]
3644 fn test_empty_value_ranges() {
3645 let input = r#"EmptyField: "#;
3646
3647 let deb822 = super::Deb822::from_str(input).unwrap();
3648 let paragraph = deb822.paragraphs().next().unwrap();
3649 let entry = paragraph.entries().next().unwrap();
3650
3651 assert_eq!(entry.key(), Some("EmptyField".to_string()));
3652
3653 assert!(entry.key_range().is_some());
3655 assert!(entry.colon_range().is_some());
3656
3657 let value_lines = entry.value_line_ranges();
3659 assert!(value_lines.len() <= 1);
3662 }
3663
3664 #[test]
3665 fn test_range_ordering() {
3666 let input = r#"Field: value"#;
3667
3668 let deb822 = super::Deb822::from_str(input).unwrap();
3669 let paragraph = deb822.paragraphs().next().unwrap();
3670 let entry = paragraph.entries().next().unwrap();
3671
3672 let key_range = entry.key_range().unwrap();
3673 let colon_range = entry.colon_range().unwrap();
3674 let value_range = entry.value_range().unwrap();
3675 let text_range = entry.text_range();
3676
3677 assert!(key_range.end() <= colon_range.start());
3679 assert!(colon_range.end() <= value_range.start());
3680 assert!(key_range.start() >= text_range.start());
3681 assert!(value_range.end() <= text_range.end());
3682 }
3683
3684 #[test]
3685 fn test_error_recovery_missing_colon() {
3686 let input = r#"Source foo
3687Maintainer: Test User <test@example.com>
3688"#;
3689 let (deb822, errors) = super::Deb822::from_str_relaxed(input);
3690
3691 assert!(!errors.is_empty());
3693 assert!(errors.iter().any(|e| e.contains("missing colon")));
3694
3695 let paragraph = deb822.paragraphs().next().unwrap();
3697 assert_eq!(
3698 paragraph.get("Maintainer").as_deref(),
3699 Some("Test User <test@example.com>")
3700 );
3701 }
3702
3703 #[test]
3704 fn test_error_recovery_missing_field_name() {
3705 let input = r#": orphaned value
3706Package: test
3707"#;
3708
3709 let (deb822, errors) = super::Deb822::from_str_relaxed(input);
3710
3711 assert!(!errors.is_empty());
3713 assert!(errors
3714 .iter()
3715 .any(|e| e.contains("field name") || e.contains("missing")));
3716
3717 let paragraphs: Vec<_> = deb822.paragraphs().collect();
3719 let mut found_package = false;
3720 for paragraph in paragraphs.iter() {
3721 if paragraph.get("Package").is_some() {
3722 found_package = true;
3723 assert_eq!(paragraph.get("Package").as_deref(), Some("test"));
3724 }
3725 }
3726 assert!(found_package, "Package field not found in any paragraph");
3727 }
3728
3729 #[test]
3730 fn test_error_recovery_orphaned_text() {
3731 let input = r#"Package: test
3732some orphaned text without field name
3733Version: 1.0
3734"#;
3735 let (deb822, errors) = super::Deb822::from_str_relaxed(input);
3736
3737 assert!(!errors.is_empty());
3739 assert!(errors.iter().any(|e| e.contains("orphaned")
3740 || e.contains("unexpected")
3741 || e.contains("field name")));
3742
3743 let mut all_fields = std::collections::HashMap::new();
3745 for paragraph in deb822.paragraphs() {
3746 for (key, value) in paragraph.items() {
3747 all_fields.insert(key, value);
3748 }
3749 }
3750
3751 assert_eq!(all_fields.get("Package"), Some(&"test".to_string()));
3752 assert_eq!(all_fields.get("Version"), Some(&"1.0".to_string()));
3753 }
3754
3755 #[test]
3756 fn test_error_recovery_consecutive_field_names() {
3757 let input = r#"Package: test
3758Description
3759Maintainer: Another field without proper value
3760Version: 1.0
3761"#;
3762 let (deb822, errors) = super::Deb822::from_str_relaxed(input);
3763
3764 assert!(!errors.is_empty());
3766 assert!(errors.iter().any(|e| e.contains("consecutive")
3767 || e.contains("missing")
3768 || e.contains("incomplete")));
3769
3770 let mut all_fields = std::collections::HashMap::new();
3772 for paragraph in deb822.paragraphs() {
3773 for (key, value) in paragraph.items() {
3774 all_fields.insert(key, value);
3775 }
3776 }
3777
3778 assert_eq!(all_fields.get("Package"), Some(&"test".to_string()));
3779 assert_eq!(
3780 all_fields.get("Maintainer"),
3781 Some(&"Another field without proper value".to_string())
3782 );
3783 assert_eq!(all_fields.get("Version"), Some(&"1.0".to_string()));
3784 }
3785
3786 #[test]
3787 fn test_error_recovery_malformed_multiline() {
3788 let input = r#"Package: test
3789Description: Short desc
3790 Proper continuation
3791invalid continuation without indent
3792 Another proper continuation
3793Version: 1.0
3794"#;
3795 let (deb822, errors) = super::Deb822::from_str_relaxed(input);
3796
3797 assert!(!errors.is_empty());
3799
3800 let paragraph = deb822.paragraphs().next().unwrap();
3802 assert_eq!(paragraph.get("Package").as_deref(), Some("test"));
3803 assert_eq!(paragraph.get("Version").as_deref(), Some("1.0"));
3804 }
3805
3806 #[test]
3807 fn test_error_recovery_mixed_errors() {
3808 let input = r#"Package test without colon
3809: orphaned colon
3810Description: Valid field
3811some orphaned text
3812Another-Field: Valid too
3813"#;
3814 let (deb822, errors) = super::Deb822::from_str_relaxed(input);
3815
3816 assert!(!errors.is_empty());
3818 assert!(errors.len() >= 2);
3819
3820 let paragraph = deb822.paragraphs().next().unwrap();
3822 assert_eq!(paragraph.get("Description").as_deref(), Some("Valid field"));
3823 assert_eq!(paragraph.get("Another-Field").as_deref(), Some("Valid too"));
3824 }
3825
3826 #[test]
3827 fn test_error_recovery_paragraph_boundary() {
3828 let input = r#"Package: first-package
3829Description: First paragraph
3830
3831corrupted data here
3832: more corruption
3833completely broken line
3834
3835Package: second-package
3836Version: 1.0
3837"#;
3838 let (deb822, errors) = super::Deb822::from_str_relaxed(input);
3839
3840 assert!(!errors.is_empty());
3842
3843 let paragraphs: Vec<_> = deb822.paragraphs().collect();
3845 assert_eq!(paragraphs.len(), 2);
3846
3847 assert_eq!(
3848 paragraphs[0].get("Package").as_deref(),
3849 Some("first-package")
3850 );
3851 assert_eq!(
3852 paragraphs[1].get("Package").as_deref(),
3853 Some("second-package")
3854 );
3855 assert_eq!(paragraphs[1].get("Version").as_deref(), Some("1.0"));
3856 }
3857
3858 #[test]
3859 fn test_error_recovery_with_positioned_errors() {
3860 let input = r#"Package test
3861Description: Valid
3862"#;
3863 let parsed = super::parse(input);
3864
3865 assert!(!parsed.positioned_errors.is_empty());
3867
3868 let first_error = &parsed.positioned_errors[0];
3869 assert!(!first_error.message.is_empty());
3870 assert!(first_error.range.start() <= first_error.range.end());
3871 assert!(first_error.code.is_some());
3872
3873 let error_text = &input[first_error.range.start().into()..first_error.range.end().into()];
3875 assert!(!error_text.is_empty());
3876 }
3877
3878 #[test]
3879 fn test_error_recovery_preserves_whitespace() {
3880 let input = r#"Source: package
3881Maintainer Test User <test@example.com>
3882Section: utils
3883
3884"#;
3885 let (deb822, errors) = super::Deb822::from_str_relaxed(input);
3886
3887 assert!(!errors.is_empty());
3889
3890 let output = deb822.to_string();
3892 assert!(output.contains("Section: utils"));
3893
3894 let paragraph = deb822.paragraphs().next().unwrap();
3896 assert_eq!(paragraph.get("Source").as_deref(), Some("package"));
3897 assert_eq!(paragraph.get("Section").as_deref(), Some("utils"));
3898 }
3899
3900 #[test]
3901 fn test_error_recovery_empty_fields() {
3902 let input = r#"Package: test
3903Description:
3904Maintainer: Valid User
3905EmptyField:
3906Version: 1.0
3907"#;
3908 let (deb822, _errors) = super::Deb822::from_str_relaxed(input);
3909
3910 let mut all_fields = std::collections::HashMap::new();
3912 for paragraph in deb822.paragraphs() {
3913 for (key, value) in paragraph.items() {
3914 all_fields.insert(key, value);
3915 }
3916 }
3917
3918 assert_eq!(all_fields.get("Package"), Some(&"test".to_string()));
3919 assert_eq!(all_fields.get("Description"), Some(&"".to_string()));
3920 assert_eq!(
3921 all_fields.get("Maintainer"),
3922 Some(&"Valid User".to_string())
3923 );
3924 assert_eq!(all_fields.get("EmptyField"), Some(&"".to_string()));
3925 assert_eq!(all_fields.get("Version"), Some(&"1.0".to_string()));
3926 }
3927
3928 #[test]
3929 fn test_insert_comment_before() {
3930 let d: super::Deb822 = vec![
3931 vec![("Source", "foo"), ("Maintainer", "Bar <bar@example.com>")]
3932 .into_iter()
3933 .collect(),
3934 vec![("Package", "foo"), ("Architecture", "all")]
3935 .into_iter()
3936 .collect(),
3937 ]
3938 .into_iter()
3939 .collect();
3940
3941 let mut p1 = d.paragraphs().next().unwrap();
3943 p1.insert_comment_before("This is the source paragraph");
3944
3945 let mut p2 = d.paragraphs().nth(1).unwrap();
3947 p2.insert_comment_before("This is the binary paragraph");
3948
3949 let output = d.to_string();
3950 assert_eq!(
3951 output,
3952 r#"# This is the source paragraph
3953Source: foo
3954Maintainer: Bar <bar@example.com>
3955
3956# This is the binary paragraph
3957Package: foo
3958Architecture: all
3959"#
3960 );
3961 }
3962
3963 #[test]
3964 fn test_parse_continuation_with_colon() {
3965 let input = "Package: test\nDescription: short\n line: with colon\n";
3967 let result = input.parse::<Deb822>();
3968 assert!(result.is_ok());
3969
3970 let deb822 = result.unwrap();
3971 let para = deb822.paragraphs().next().unwrap();
3972 assert_eq!(para.get("Package").as_deref(), Some("test"));
3973 assert_eq!(
3974 para.get("Description").as_deref(),
3975 Some("short\nline: with colon")
3976 );
3977 }
3978
3979 #[test]
3980 fn test_parse_continuation_starting_with_colon() {
3981 let input = "Package: test\nDescription: short\n :value\n";
3983 let result = input.parse::<Deb822>();
3984 assert!(result.is_ok());
3985
3986 let deb822 = result.unwrap();
3987 let para = deb822.paragraphs().next().unwrap();
3988 assert_eq!(para.get("Package").as_deref(), Some("test"));
3989 assert_eq!(para.get("Description").as_deref(), Some("short\n:value"));
3990 }
3991
3992 #[test]
3993 fn test_normalize_field_spacing_single_space() {
3994 let input = "Field: value\n";
3996 let deb822 = input.parse::<Deb822>().unwrap();
3997 let mut para = deb822.paragraphs().next().unwrap();
3998
3999 para.normalize_field_spacing();
4000 assert_eq!(para.to_string(), "Field: value\n");
4001 }
4002
4003 #[test]
4004 fn test_normalize_field_spacing_extra_spaces() {
4005 let input = "Field: value\n";
4007 let deb822 = input.parse::<Deb822>().unwrap();
4008 let mut para = deb822.paragraphs().next().unwrap();
4009
4010 para.normalize_field_spacing();
4011 assert_eq!(para.to_string(), "Field: value\n");
4012 }
4013
4014 #[test]
4015 fn test_normalize_field_spacing_no_space() {
4016 let input = "Field:value\n";
4018 let deb822 = input.parse::<Deb822>().unwrap();
4019 let mut para = deb822.paragraphs().next().unwrap();
4020
4021 para.normalize_field_spacing();
4022 assert_eq!(para.to_string(), "Field: value\n");
4023 }
4024
4025 #[test]
4026 fn test_normalize_field_spacing_multiple_fields() {
4027 let input = "Field1: value1\nField2:value2\nField3: value3\n";
4029 let deb822 = input.parse::<Deb822>().unwrap();
4030 let mut para = deb822.paragraphs().next().unwrap();
4031
4032 para.normalize_field_spacing();
4033 assert_eq!(
4034 para.to_string(),
4035 "Field1: value1\nField2: value2\nField3: value3\n"
4036 );
4037 }
4038
4039 #[test]
4040 fn test_normalize_field_spacing_multiline_value() {
4041 let input = "Description: short\n continuation line\n . \n final line\n";
4043 let deb822 = input.parse::<Deb822>().unwrap();
4044 let mut para = deb822.paragraphs().next().unwrap();
4045
4046 para.normalize_field_spacing();
4047 assert_eq!(
4048 para.to_string(),
4049 "Description: short\n continuation line\n . \n final line\n"
4050 );
4051 }
4052
4053 #[test]
4054 fn test_normalize_field_spacing_empty_value_with_whitespace() {
4055 let input = "Field: \n";
4057 let deb822 = input.parse::<Deb822>().unwrap();
4058 let mut para = deb822.paragraphs().next().unwrap();
4059
4060 para.normalize_field_spacing();
4061 assert_eq!(para.to_string(), "Field:\n");
4063 }
4064
4065 #[test]
4066 fn test_normalize_field_spacing_no_value() {
4067 let input = "Depends:\n";
4069 let deb822 = input.parse::<Deb822>().unwrap();
4070 let mut para = deb822.paragraphs().next().unwrap();
4071
4072 para.normalize_field_spacing();
4073 assert_eq!(para.to_string(), "Depends:\n");
4075 }
4076
4077 #[test]
4078 fn test_normalize_field_spacing_multiple_paragraphs() {
4079 let input = "Field1: value1\n\nField2: value2\n";
4081 let mut deb822 = input.parse::<Deb822>().unwrap();
4082
4083 deb822.normalize_field_spacing();
4084 assert_eq!(deb822.to_string(), "Field1: value1\n\nField2: value2\n");
4085 }
4086
4087 #[test]
4088 fn test_normalize_field_spacing_preserves_comments() {
4089 let input = "# Comment\nField: value\n";
4091 let mut deb822 = input.parse::<Deb822>().unwrap();
4092
4093 deb822.normalize_field_spacing();
4094 assert_eq!(deb822.to_string(), "# Comment\nField: value\n");
4095 }
4096
4097 #[test]
4098 fn test_normalize_field_spacing_preserves_values() {
4099 let input = "Source: foo-bar\nMaintainer:Foo Bar <test@example.com>\n";
4101 let deb822 = input.parse::<Deb822>().unwrap();
4102 let mut para = deb822.paragraphs().next().unwrap();
4103
4104 para.normalize_field_spacing();
4105
4106 assert_eq!(para.get("Source").as_deref(), Some("foo-bar"));
4107 assert_eq!(
4108 para.get("Maintainer").as_deref(),
4109 Some("Foo Bar <test@example.com>")
4110 );
4111 }
4112
4113 #[test]
4114 fn test_normalize_field_spacing_tab_after_colon() {
4115 let input = "Field:\tvalue\n";
4117 let deb822 = input.parse::<Deb822>().unwrap();
4118 let mut para = deb822.paragraphs().next().unwrap();
4119
4120 para.normalize_field_spacing();
4121 assert_eq!(para.to_string(), "Field: value\n");
4122 }
4123
4124 #[test]
4125 fn test_set_preserves_indentation() {
4126 let original = r#"Source: example
4128Build-Depends: foo,
4129 bar,
4130 baz
4131"#;
4132
4133 let mut para: super::Paragraph = original.parse().unwrap();
4134
4135 para.set("Build-Depends", "foo,\nbar,\nbaz");
4137
4138 let expected = r#"Source: example
4140Build-Depends: foo,
4141 bar,
4142 baz
4143"#;
4144 assert_eq!(para.to_string(), expected);
4145 }
4146
4147 #[test]
4148 fn test_set_new_field_detects_field_name_length_indent() {
4149 let original = r#"Source: example
4151Build-Depends: foo,
4152 bar,
4153 baz
4154Depends: lib1,
4155 lib2
4156"#;
4157
4158 let mut para: super::Paragraph = original.parse().unwrap();
4159
4160 para.set("Recommends", "pkg1,\npkg2,\npkg3");
4162
4163 assert!(para
4165 .to_string()
4166 .contains("Recommends: pkg1,\n pkg2,"));
4167 }
4168
4169 #[test]
4170 fn test_set_new_field_detects_fixed_indent() {
4171 let original = r#"Source: example
4173Build-Depends: foo,
4174 bar,
4175 baz
4176Depends: lib1,
4177 lib2
4178"#;
4179
4180 let mut para: super::Paragraph = original.parse().unwrap();
4181
4182 para.set("Recommends", "pkg1,\npkg2,\npkg3");
4184
4185 assert!(para
4187 .to_string()
4188 .contains("Recommends: pkg1,\n pkg2,\n pkg3\n"));
4189 }
4190
4191 #[test]
4192 fn test_set_new_field_no_multiline_fields() {
4193 let original = r#"Source: example
4195Maintainer: Test <test@example.com>
4196"#;
4197
4198 let mut para: super::Paragraph = original.parse().unwrap();
4199
4200 para.set("Depends", "foo,\nbar,\nbaz");
4202
4203 let expected = r#"Source: example
4205Maintainer: Test <test@example.com>
4206Depends: foo,
4207 bar,
4208 baz
4209"#;
4210 assert_eq!(para.to_string(), expected);
4211 }
4212
4213 #[test]
4214 fn test_set_new_field_mixed_indentation() {
4215 let original = r#"Source: example
4217Build-Depends: foo,
4218 bar
4219Depends: lib1,
4220 lib2
4221"#;
4222
4223 let mut para: super::Paragraph = original.parse().unwrap();
4224
4225 para.set("Recommends", "pkg1,\npkg2");
4227
4228 assert!(para
4230 .to_string()
4231 .contains("Recommends: pkg1,\n pkg2\n"));
4232 }
4233
4234 #[test]
4235 fn test_entry_with_indentation() {
4236 let entry = super::Entry::with_indentation("Test-Field", "value1\nvalue2\nvalue3", " ");
4238
4239 assert_eq!(
4240 entry.to_string(),
4241 "Test-Field: value1\n value2\n value3\n"
4242 );
4243 }
4244
4245 #[test]
4246 fn test_set_with_indent_pattern_fixed() {
4247 let original = r#"Source: example
4249Maintainer: Test <test@example.com>
4250"#;
4251
4252 let mut para: super::Paragraph = original.parse().unwrap();
4253
4254 para.set_with_indent_pattern(
4256 "Depends",
4257 "foo,\nbar,\nbaz",
4258 Some(&super::IndentPattern::Fixed(4)),
4259 None,
4260 );
4261
4262 let expected = r#"Source: example
4264Maintainer: Test <test@example.com>
4265Depends: foo,
4266 bar,
4267 baz
4268"#;
4269 assert_eq!(para.to_string(), expected);
4270 }
4271
4272 #[test]
4273 fn test_set_with_indent_pattern_field_name_length() {
4274 let original = r#"Source: example
4276Maintainer: Test <test@example.com>
4277"#;
4278
4279 let mut para: super::Paragraph = original.parse().unwrap();
4280
4281 para.set_with_indent_pattern(
4283 "Build-Depends",
4284 "libfoo,\nlibbar,\nlibbaz",
4285 Some(&super::IndentPattern::FieldNameLength),
4286 None,
4287 );
4288
4289 let expected = r#"Source: example
4291Maintainer: Test <test@example.com>
4292Build-Depends: libfoo,
4293 libbar,
4294 libbaz
4295"#;
4296 assert_eq!(para.to_string(), expected);
4297 }
4298
4299 #[test]
4300 fn test_set_with_indent_pattern_override_auto_detection() {
4301 let original = r#"Source: example
4303Build-Depends: foo,
4304 bar,
4305 baz
4306"#;
4307
4308 let mut para: super::Paragraph = original.parse().unwrap();
4309
4310 para.set_with_indent_pattern(
4312 "Depends",
4313 "lib1,\nlib2,\nlib3",
4314 Some(&super::IndentPattern::Fixed(2)),
4315 None,
4316 );
4317
4318 let expected = r#"Source: example
4320Build-Depends: foo,
4321 bar,
4322 baz
4323Depends: lib1,
4324 lib2,
4325 lib3
4326"#;
4327 assert_eq!(para.to_string(), expected);
4328 }
4329
4330 #[test]
4331 fn test_set_with_indent_pattern_none_auto_detects() {
4332 let original = r#"Source: example
4334Build-Depends: foo,
4335 bar,
4336 baz
4337"#;
4338
4339 let mut para: super::Paragraph = original.parse().unwrap();
4340
4341 para.set_with_indent_pattern("Depends", "lib1,\nlib2", None, None);
4343
4344 let expected = r#"Source: example
4346Build-Depends: foo,
4347 bar,
4348 baz
4349Depends: lib1,
4350 lib2
4351"#;
4352 assert_eq!(para.to_string(), expected);
4353 }
4354
4355 #[test]
4356 fn test_set_with_indent_pattern_with_field_order() {
4357 let original = r#"Source: example
4359Maintainer: Test <test@example.com>
4360"#;
4361
4362 let mut para: super::Paragraph = original.parse().unwrap();
4363
4364 para.set_with_indent_pattern(
4366 "Priority",
4367 "optional",
4368 Some(&super::IndentPattern::Fixed(4)),
4369 Some(&["Source", "Priority", "Maintainer"]),
4370 );
4371
4372 let expected = r#"Source: example
4374Priority: optional
4375Maintainer: Test <test@example.com>
4376"#;
4377 assert_eq!(para.to_string(), expected);
4378 }
4379
4380 #[test]
4381 fn test_set_with_indent_pattern_replace_existing() {
4382 let original = r#"Source: example
4384Depends: foo,
4385 bar
4386"#;
4387
4388 let mut para: super::Paragraph = original.parse().unwrap();
4389
4390 para.set_with_indent_pattern(
4392 "Depends",
4393 "lib1,\nlib2,\nlib3",
4394 Some(&super::IndentPattern::Fixed(3)),
4395 None,
4396 );
4397
4398 let expected = r#"Source: example
4400Depends: lib1,
4401 lib2,
4402 lib3
4403"#;
4404 assert_eq!(para.to_string(), expected);
4405 }
4406
4407 #[test]
4408 fn test_change_field_indent() {
4409 let original = r#"Source: example
4411Depends: foo,
4412 bar,
4413 baz
4414"#;
4415 let mut para: super::Paragraph = original.parse().unwrap();
4416
4417 let result = para
4419 .change_field_indent("Depends", &super::IndentPattern::Fixed(2))
4420 .unwrap();
4421 assert!(result, "Field should have been found and updated");
4422
4423 let expected = r#"Source: example
4424Depends: foo,
4425 bar,
4426 baz
4427"#;
4428 assert_eq!(para.to_string(), expected);
4429 }
4430
4431 #[test]
4432 fn test_change_field_indent_nonexistent() {
4433 let original = r#"Source: example
4435"#;
4436 let mut para: super::Paragraph = original.parse().unwrap();
4437
4438 let result = para
4440 .change_field_indent("Depends", &super::IndentPattern::Fixed(2))
4441 .unwrap();
4442 assert!(!result, "Should return false for non-existent field");
4443
4444 assert_eq!(para.to_string(), original);
4446 }
4447
4448 #[test]
4449 fn test_change_field_indent_case_insensitive() {
4450 let original = r#"Build-Depends: foo,
4452 bar
4453"#;
4454 let mut para: super::Paragraph = original.parse().unwrap();
4455
4456 let result = para
4458 .change_field_indent("build-depends", &super::IndentPattern::Fixed(1))
4459 .unwrap();
4460 assert!(result, "Should find field case-insensitively");
4461
4462 let expected = r#"Build-Depends: foo,
4463 bar
4464"#;
4465 assert_eq!(para.to_string(), expected);
4466 }
4467
4468 #[test]
4469 fn test_entry_get_indent() {
4470 let original = r#"Build-Depends: foo,
4472 bar,
4473 baz
4474"#;
4475 let para: super::Paragraph = original.parse().unwrap();
4476 let entry = para.entries().next().unwrap();
4477
4478 assert_eq!(entry.get_indent(), Some(" ".to_string()));
4479 }
4480
4481 #[test]
4482 fn test_entry_get_indent_single_line() {
4483 let original = r#"Source: example
4485"#;
4486 let para: super::Paragraph = original.parse().unwrap();
4487 let entry = para.entries().next().unwrap();
4488
4489 assert_eq!(entry.get_indent(), None);
4490 }
4491}
4492
4493#[test]
4494fn test_move_paragraph_forward() {
4495 let mut d: Deb822 = vec![
4496 vec![("Foo", "Bar"), ("Baz", "Qux")].into_iter().collect(),
4497 vec![("A", "B"), ("C", "D")].into_iter().collect(),
4498 vec![("X", "Y"), ("Z", "W")].into_iter().collect(),
4499 ]
4500 .into_iter()
4501 .collect();
4502 d.move_paragraph(0, 2);
4503 assert_eq!(
4504 d.to_string(),
4505 "A: B\nC: D\n\nX: Y\nZ: W\n\nFoo: Bar\nBaz: Qux\n"
4506 );
4507}
4508
4509#[test]
4510fn test_move_paragraph_backward() {
4511 let mut d: Deb822 = vec![
4512 vec![("Foo", "Bar"), ("Baz", "Qux")].into_iter().collect(),
4513 vec![("A", "B"), ("C", "D")].into_iter().collect(),
4514 vec![("X", "Y"), ("Z", "W")].into_iter().collect(),
4515 ]
4516 .into_iter()
4517 .collect();
4518 d.move_paragraph(2, 0);
4519 assert_eq!(
4520 d.to_string(),
4521 "X: Y\nZ: W\n\nFoo: Bar\nBaz: Qux\n\nA: B\nC: D\n"
4522 );
4523}
4524
4525#[test]
4526fn test_move_paragraph_middle() {
4527 let mut d: Deb822 = vec![
4528 vec![("Foo", "Bar"), ("Baz", "Qux")].into_iter().collect(),
4529 vec![("A", "B"), ("C", "D")].into_iter().collect(),
4530 vec![("X", "Y"), ("Z", "W")].into_iter().collect(),
4531 ]
4532 .into_iter()
4533 .collect();
4534 d.move_paragraph(2, 1);
4535 assert_eq!(
4536 d.to_string(),
4537 "Foo: Bar\nBaz: Qux\n\nX: Y\nZ: W\n\nA: B\nC: D\n"
4538 );
4539}
4540
4541#[test]
4542fn test_move_paragraph_same_index() {
4543 let mut d: Deb822 = vec![
4544 vec![("Foo", "Bar"), ("Baz", "Qux")].into_iter().collect(),
4545 vec![("A", "B"), ("C", "D")].into_iter().collect(),
4546 ]
4547 .into_iter()
4548 .collect();
4549 let original = d.to_string();
4550 d.move_paragraph(1, 1);
4551 assert_eq!(d.to_string(), original);
4552}
4553
4554#[test]
4555fn test_move_paragraph_single() {
4556 let mut d: Deb822 = vec![vec![("Foo", "Bar")].into_iter().collect()]
4557 .into_iter()
4558 .collect();
4559 let original = d.to_string();
4560 d.move_paragraph(0, 0);
4561 assert_eq!(d.to_string(), original);
4562}
4563
4564#[test]
4565fn test_move_paragraph_invalid_index() {
4566 let mut d: Deb822 = vec![
4567 vec![("Foo", "Bar")].into_iter().collect(),
4568 vec![("A", "B")].into_iter().collect(),
4569 ]
4570 .into_iter()
4571 .collect();
4572 let original = d.to_string();
4573 d.move_paragraph(0, 5);
4574 assert_eq!(d.to_string(), original);
4575}
4576
4577#[test]
4578fn test_move_paragraph_with_comments() {
4579 let text = r#"Foo: Bar
4580
4581# This is a comment
4582
4583A: B
4584
4585X: Y
4586"#;
4587 let mut d: Deb822 = text.parse().unwrap();
4588 d.move_paragraph(0, 2);
4589 assert_eq!(
4590 d.to_string(),
4591 "# This is a comment\n\nA: B\n\nX: Y\n\nFoo: Bar\n"
4592 );
4593}
4594
4595#[test]
4596fn test_case_insensitive_get() {
4597 let text = "Package: test\nVersion: 1.0\n";
4598 let d: Deb822 = text.parse().unwrap();
4599 let p = d.paragraphs().next().unwrap();
4600
4601 assert_eq!(p.get("Package").as_deref(), Some("test"));
4603 assert_eq!(p.get("package").as_deref(), Some("test"));
4604 assert_eq!(p.get("PACKAGE").as_deref(), Some("test"));
4605 assert_eq!(p.get("PaCkAgE").as_deref(), Some("test"));
4606
4607 assert_eq!(p.get("Version").as_deref(), Some("1.0"));
4608 assert_eq!(p.get("version").as_deref(), Some("1.0"));
4609 assert_eq!(p.get("VERSION").as_deref(), Some("1.0"));
4610}
4611
4612#[test]
4613fn test_case_insensitive_set() {
4614 let text = "Package: test\n";
4615 let d: Deb822 = text.parse().unwrap();
4616 let mut p = d.paragraphs().next().unwrap();
4617
4618 p.set("package", "updated");
4620 assert_eq!(p.get("Package").as_deref(), Some("updated"));
4621 assert_eq!(p.get("package").as_deref(), Some("updated"));
4622
4623 p.set("PACKAGE", "updated2");
4625 assert_eq!(p.get("Package").as_deref(), Some("updated2"));
4626
4627 assert_eq!(p.keys().count(), 1);
4629}
4630
4631#[test]
4632fn test_case_insensitive_remove() {
4633 let text = "Package: test\nVersion: 1.0\n";
4634 let d: Deb822 = text.parse().unwrap();
4635 let mut p = d.paragraphs().next().unwrap();
4636
4637 p.remove("package");
4639 assert_eq!(p.get("Package"), None);
4640 assert_eq!(p.get("Version").as_deref(), Some("1.0"));
4641
4642 p.remove("VERSION");
4644 assert_eq!(p.get("Version"), None);
4645
4646 assert_eq!(p.keys().count(), 0);
4648}
4649
4650#[test]
4651fn test_case_preservation() {
4652 let text = "Package: test\n";
4653 let d: Deb822 = text.parse().unwrap();
4654 let mut p = d.paragraphs().next().unwrap();
4655
4656 let original_text = d.to_string();
4658 assert_eq!(original_text, "Package: test\n");
4659
4660 p.set("package", "updated");
4662
4663 let updated_text = d.to_string();
4665 assert_eq!(updated_text, "Package: updated\n");
4666}
4667
4668#[test]
4669fn test_case_insensitive_contains_key() {
4670 let text = "Package: test\n";
4671 let d: Deb822 = text.parse().unwrap();
4672 let p = d.paragraphs().next().unwrap();
4673
4674 assert!(p.contains_key("Package"));
4675 assert!(p.contains_key("package"));
4676 assert!(p.contains_key("PACKAGE"));
4677 assert!(!p.contains_key("NonExistent"));
4678}
4679
4680#[test]
4681fn test_case_insensitive_get_all() {
4682 let text = "Package: test1\npackage: test2\n";
4683 let d: Deb822 = text.parse().unwrap();
4684 let p = d.paragraphs().next().unwrap();
4685
4686 let values: Vec<String> = p.get_all("PACKAGE").collect();
4687 assert_eq!(values, vec!["test1", "test2"]);
4688}
4689
4690#[test]
4691fn test_case_insensitive_rename() {
4692 let text = "Package: test\n";
4693 let d: Deb822 = text.parse().unwrap();
4694 let mut p = d.paragraphs().next().unwrap();
4695
4696 assert!(p.rename("package", "NewName"));
4698 assert_eq!(p.get("NewName").as_deref(), Some("test"));
4699 assert_eq!(p.get("Package"), None);
4700}
4701
4702#[test]
4703fn test_rename_changes_case() {
4704 let text = "Package: test\n";
4705 let d: Deb822 = text.parse().unwrap();
4706 let mut p = d.paragraphs().next().unwrap();
4707
4708 assert!(p.rename("package", "PACKAGE"));
4710
4711 let updated_text = d.to_string();
4713 assert_eq!(updated_text, "PACKAGE: test\n");
4714
4715 assert_eq!(p.get("package").as_deref(), Some("test"));
4717 assert_eq!(p.get("Package").as_deref(), Some("test"));
4718 assert_eq!(p.get("PACKAGE").as_deref(), Some("test"));
4719}
4720
4721#[test]
4722fn test_reject_whitespace_only_continuation_line() {
4723 let text = "Build-Depends:\n \ndebhelper\n";
4729 let parsed = Deb822::parse(text);
4730
4731 assert!(
4734 !parsed.errors().is_empty(),
4735 "Expected parse errors for whitespace-only continuation line"
4736 );
4737}
4738
4739#[test]
4740fn test_reject_empty_continuation_line_in_multiline_field() {
4741 let text = "Depends: foo,\n bar,\n \n baz\n";
4743 let parsed = Deb822::parse(text);
4744
4745 assert!(
4747 !parsed.errors().is_empty(),
4748 "Empty continuation line should generate parse errors"
4749 );
4750
4751 let has_empty_line_error = parsed
4753 .errors()
4754 .iter()
4755 .any(|e| e.contains("empty continuation line"));
4756 assert!(
4757 has_empty_line_error,
4758 "Should have an error about empty continuation line"
4759 );
4760}
4761
4762#[test]
4763#[should_panic(expected = "empty continuation line")]
4764fn test_set_rejects_empty_continuation_lines() {
4765 let text = "Package: test\n";
4767 let deb822 = text.parse::<Deb822>().unwrap();
4768 let mut para = deb822.paragraphs().next().unwrap();
4769
4770 let value_with_empty_line = "foo\n \nbar";
4773 para.set("Depends", value_with_empty_line);
4774}
4775
4776#[test]
4777fn test_try_set_returns_error_for_empty_continuation_lines() {
4778 let text = "Package: test\n";
4780 let deb822 = text.parse::<Deb822>().unwrap();
4781 let mut para = deb822.paragraphs().next().unwrap();
4782
4783 let value_with_empty_line = "foo\n \nbar";
4785 let result = para.try_set("Depends", value_with_empty_line);
4786
4787 assert!(
4789 result.is_err(),
4790 "try_set() should return an error for empty continuation lines"
4791 );
4792
4793 match result {
4795 Err(Error::InvalidValue(msg)) => {
4796 assert!(
4797 msg.contains("empty continuation line"),
4798 "Error message should mention empty continuation line"
4799 );
4800 }
4801 _ => panic!("Expected InvalidValue error"),
4802 }
4803}
4804
4805#[test]
4806fn test_try_set_with_indent_pattern_returns_error() {
4807 let text = "Package: test\n";
4809 let deb822 = text.parse::<Deb822>().unwrap();
4810 let mut para = deb822.paragraphs().next().unwrap();
4811
4812 let value_with_empty_line = "foo\n \nbar";
4813 let result = para.try_set_with_indent_pattern(
4814 "Depends",
4815 value_with_empty_line,
4816 Some(&IndentPattern::Fixed(2)),
4817 None,
4818 );
4819
4820 assert!(
4821 result.is_err(),
4822 "try_set_with_indent_pattern() should return an error"
4823 );
4824}
4825
4826#[test]
4827fn test_try_set_succeeds_for_valid_value() {
4828 let text = "Package: test\n";
4830 let deb822 = text.parse::<Deb822>().unwrap();
4831 let mut para = deb822.paragraphs().next().unwrap();
4832
4833 let valid_value = "foo\nbar";
4835 let result = para.try_set("Depends", valid_value);
4836
4837 assert!(result.is_ok(), "try_set() should succeed for valid values");
4838 assert_eq!(para.get("Depends").as_deref(), Some("foo\nbar"));
4839}
4840
4841#[test]
4842fn test_field_with_empty_first_line() {
4843 let text = "Foo:\n blah\n blah\n";
4846 let parsed = Deb822::parse(text);
4847
4848 assert!(
4850 parsed.errors().is_empty(),
4851 "Empty first line should be valid. Got errors: {:?}",
4852 parsed.errors()
4853 );
4854
4855 let deb822 = parsed.tree();
4856 let para = deb822.paragraphs().next().unwrap();
4857 assert_eq!(para.get("Foo").as_deref(), Some("blah\nblah"));
4858}
4859
4860#[test]
4861fn test_try_set_with_empty_first_line() {
4862 let text = "Package: test\n";
4864 let deb822 = text.parse::<Deb822>().unwrap();
4865 let mut para = deb822.paragraphs().next().unwrap();
4866
4867 let value = "\nblah\nmore";
4869 let result = para.try_set("Depends", value);
4870
4871 assert!(
4872 result.is_ok(),
4873 "try_set() should succeed for values with empty first line. Got: {:?}",
4874 result
4875 );
4876}
4877
4878#[test]
4879fn test_field_with_value_then_empty_continuation() {
4880 let text = "Foo: bar\n \n";
4882 let parsed = Deb822::parse(text);
4883
4884 assert!(
4886 !parsed.errors().is_empty(),
4887 "Field with value then empty continuation line should be rejected"
4888 );
4889
4890 let has_empty_line_error = parsed
4892 .errors()
4893 .iter()
4894 .any(|e| e.contains("empty continuation line"));
4895 assert!(
4896 has_empty_line_error,
4897 "Should have error about empty continuation line"
4898 );
4899}
4900
4901#[test]
4902fn test_line_col() {
4903 let text = r#"Source: foo
4904Maintainer: Foo Bar <jelmer@jelmer.uk>
4905Section: net
4906
4907Package: foo
4908Architecture: all
4909Depends: libc6
4910Description: This is a description
4911 With details
4912"#;
4913 let deb822 = text.parse::<Deb822>().unwrap();
4914
4915 let paras: Vec<_> = deb822.paragraphs().collect();
4917 assert_eq!(paras.len(), 2);
4918
4919 assert_eq!(paras[0].line(), 0);
4921 assert_eq!(paras[0].column(), 0);
4922
4923 assert_eq!(paras[1].line(), 4);
4925 assert_eq!(paras[1].column(), 0);
4926
4927 let entries: Vec<_> = paras[0].entries().collect();
4929 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));
4939 assert_eq!(entries[0].line_col(), (0, 0));
4940
4941 let second_para_entries: Vec<_> = paras[1].entries().collect();
4943 assert_eq!(second_para_entries[3].line(), 7); }
4945
4946#[test]
4947fn test_deb822_snapshot_independence() {
4948 let text = r#"Source: foo
4950Maintainer: Joe <joe@example.com>
4951
4952Package: foo
4953Architecture: all
4954"#;
4955 let deb822 = text.parse::<Deb822>().unwrap();
4956 let snapshot = deb822.snapshot();
4957
4958 let mut para = deb822.paragraphs().next().unwrap();
4960 para.set("Source", "modified");
4961
4962 let snapshot_para = snapshot.paragraphs().next().unwrap();
4964 assert_eq!(snapshot_para.get("Source").as_deref(), Some("foo"));
4965
4966 let mut snapshot_para = snapshot.paragraphs().next().unwrap();
4968 snapshot_para.set("Source", "snapshot-modified");
4969
4970 let para = deb822.paragraphs().next().unwrap();
4972 assert_eq!(para.get("Source").as_deref(), Some("modified"));
4973}
4974
4975#[test]
4976fn test_paragraph_snapshot_independence() {
4977 let text = "Package: foo\nArchitecture: all\n";
4979 let deb822 = text.parse::<Deb822>().unwrap();
4980 let mut para = deb822.paragraphs().next().unwrap();
4981 let mut snapshot = para.snapshot();
4982
4983 para.set("Package", "modified");
4985
4986 assert_eq!(snapshot.get("Package").as_deref(), Some("foo"));
4988
4989 snapshot.set("Package", "snapshot-modified");
4991
4992 assert_eq!(para.get("Package").as_deref(), Some("modified"));
4994}
4995
4996#[test]
4997fn test_entry_snapshot_independence() {
4998 let text = "Package: foo\n";
5000 let deb822 = text.parse::<Deb822>().unwrap();
5001 let mut para = deb822.paragraphs().next().unwrap();
5002 let entry = para.entries().next().unwrap();
5003 let snapshot = entry.snapshot();
5004
5005 let original_value = entry.value();
5007 let snapshot_value = snapshot.value();
5008
5009 assert_eq!(original_value, "foo");
5011 assert_eq!(snapshot_value, "foo");
5012
5013 para.set("Package", "modified");
5015
5016 let entry_after = para.entries().next().unwrap();
5018 assert_eq!(entry_after.value(), "modified");
5019
5020 assert_eq!(snapshot.value(), "foo");
5023}
5024
5025#[test]
5026fn test_snapshot_preserves_structure() {
5027 let text = r#"# Comment
5029Source: foo
5030## Another comment
5031Maintainer: Joe <joe@example.com>
5032
5033Package: foo
5034Architecture: all
5035"#;
5036 let deb822 = text.parse::<Deb822>().unwrap();
5037 let snapshot = deb822.snapshot();
5038
5039 assert_eq!(deb822.to_string(), snapshot.to_string());
5041
5042 let mut snapshot_para = snapshot.paragraphs().next().unwrap();
5044 snapshot_para.set("Source", "modified");
5045
5046 let original_para = deb822.paragraphs().next().unwrap();
5047 assert_eq!(original_para.get("Source").as_deref(), Some("foo"));
5048}