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 pub fn paragraphs_in_range(
883 &self,
884 range: rowan::TextRange,
885 ) -> impl Iterator<Item = Paragraph> + '_ {
886 self.paragraphs().filter(move |p| {
887 let para_range = p.text_range();
888 para_range.start() < range.end() && para_range.end() > range.start()
890 })
891 }
892
893 pub fn paragraph_at_position(&self, offset: rowan::TextSize) -> Option<Paragraph> {
916 self.paragraphs().find(|p| {
917 let range = p.text_range();
918 range.contains(offset)
919 })
920 }
921
922 pub fn paragraph_at_line(&self, line: usize) -> Option<Paragraph> {
945 self.paragraphs().find(|p| {
946 let start_line = p.line();
947 let range = p.text_range();
948 let text_str = self.0.text().to_string();
949 let text_before_end = &text_str[..range.end().into()];
950 let end_line = text_before_end.lines().count().saturating_sub(1);
951 line >= start_line && line <= end_line
952 })
953 }
954
955 pub fn entry_at_line_col(&self, line: usize, col: usize) -> Option<Entry> {
979 let text_str = self.0.text().to_string();
981 let offset: usize = text_str.lines().take(line).map(|l| l.len() + 1).sum();
982 let position = rowan::TextSize::from((offset + col) as u32);
983
984 for para in self.paragraphs() {
986 for entry in para.entries() {
987 let range = entry.text_range();
988 if range.contains(position) {
989 return Some(entry);
990 }
991 }
992 }
993 None
994 }
995
996 fn convert_index(&self, index: usize) -> Option<usize> {
998 let mut current_pos = 0usize;
999 if index == 0 {
1000 return Some(0);
1001 }
1002 for (i, node) in self.0.children_with_tokens().enumerate() {
1003 if node.kind() == PARAGRAPH {
1004 if current_pos == index {
1005 return Some(i);
1006 }
1007 current_pos += 1;
1008 }
1009 }
1010
1011 None
1012 }
1013
1014 fn delete_trailing_space(&self, start: usize) {
1016 for (i, node) in self.0.children_with_tokens().enumerate() {
1017 if i < start {
1018 continue;
1019 }
1020 if node.kind() != EMPTY_LINE {
1021 return;
1022 }
1023 self.0.splice_children(start..start + 1, []);
1026 }
1027 }
1028
1029 fn insert_empty_paragraph(&mut self, index: Option<usize>) -> Paragraph {
1031 let paragraph = Paragraph::new();
1032 let mut to_insert = vec![];
1033 if self.0.children().count() > 0 {
1034 let mut builder = GreenNodeBuilder::new();
1035 builder.start_node(EMPTY_LINE.into());
1036 builder.token(NEWLINE.into(), "\n");
1037 builder.finish_node();
1038 to_insert.push(SyntaxNode::new_root_mut(builder.finish()).into());
1039 }
1040 to_insert.push(paragraph.0.clone().into());
1041 let insertion_point = match index {
1042 Some(i) => {
1043 if to_insert.len() > 1 {
1044 to_insert.swap(0, 1);
1045 }
1046 i
1047 }
1048 None => self.0.children().count(),
1049 };
1050 self.0
1051 .splice_children(insertion_point..insertion_point, to_insert);
1052 paragraph
1053 }
1054
1055 pub fn insert_paragraph(&mut self, index: usize) -> Paragraph {
1075 self.insert_empty_paragraph(self.convert_index(index))
1076 }
1077
1078 pub fn remove_paragraph(&mut self, index: usize) {
1096 if let Some(index) = self.convert_index(index) {
1097 self.0.splice_children(index..index + 1, []);
1098 self.delete_trailing_space(index);
1099 }
1100 }
1101
1102 pub fn move_paragraph(&mut self, from_index: usize, to_index: usize) {
1122 if from_index == to_index {
1123 return;
1124 }
1125
1126 let paragraph_count = self.paragraphs().count();
1128 if from_index >= paragraph_count || to_index >= paragraph_count {
1129 return;
1130 }
1131
1132 let paragraph_to_move = self.paragraphs().nth(from_index).unwrap().0.clone();
1134
1135 let from_physical = self.convert_index(from_index).unwrap();
1137
1138 let mut start_idx = from_physical;
1140 if from_physical > 0 {
1141 if let Some(prev_node) = self.0.children_with_tokens().nth(from_physical - 1) {
1142 if prev_node.kind() == EMPTY_LINE {
1143 start_idx = from_physical - 1;
1144 }
1145 }
1146 }
1147
1148 self.0.splice_children(start_idx..from_physical + 1, []);
1150 self.delete_trailing_space(start_idx);
1151
1152 let insert_at = if to_index > from_index {
1156 let target_idx = to_index - 1;
1159 if let Some(target_physical) = self.convert_index(target_idx) {
1160 target_physical + 1
1161 } else {
1162 self.0.children().count()
1164 }
1165 } else {
1166 if let Some(target_physical) = self.convert_index(to_index) {
1169 target_physical
1170 } else {
1171 self.0.children().count()
1172 }
1173 };
1174
1175 let mut to_insert = vec![];
1177
1178 let needs_empty_line_before = if insert_at == 0 {
1180 false
1182 } else if insert_at > 0 {
1183 if let Some(node_at_insert) = self.0.children_with_tokens().nth(insert_at - 1) {
1185 node_at_insert.kind() != EMPTY_LINE
1186 } else {
1187 false
1188 }
1189 } else {
1190 false
1191 };
1192
1193 if needs_empty_line_before {
1194 let mut builder = GreenNodeBuilder::new();
1195 builder.start_node(EMPTY_LINE.into());
1196 builder.token(NEWLINE.into(), "\n");
1197 builder.finish_node();
1198 to_insert.push(SyntaxNode::new_root_mut(builder.finish()).into());
1199 }
1200
1201 to_insert.push(paragraph_to_move.into());
1202
1203 let needs_empty_line_after = if insert_at < self.0.children().count() {
1205 if let Some(node_after) = self.0.children_with_tokens().nth(insert_at) {
1207 node_after.kind() != EMPTY_LINE
1208 } else {
1209 false
1210 }
1211 } else {
1212 false
1213 };
1214
1215 if needs_empty_line_after {
1216 let mut builder = GreenNodeBuilder::new();
1217 builder.start_node(EMPTY_LINE.into());
1218 builder.token(NEWLINE.into(), "\n");
1219 builder.finish_node();
1220 to_insert.push(SyntaxNode::new_root_mut(builder.finish()).into());
1221 }
1222
1223 self.0.splice_children(insert_at..insert_at, to_insert);
1225 }
1226
1227 pub fn add_paragraph(&mut self) -> Paragraph {
1229 self.insert_empty_paragraph(None)
1230 }
1231
1232 pub fn swap_paragraphs(&mut self, index1: usize, index2: usize) {
1262 if index1 == index2 {
1263 return;
1264 }
1265
1266 let mut children: Vec<_> = self.0.children().map(|n| n.clone().into()).collect();
1268
1269 let mut para_child_indices = vec![];
1271 for (child_idx, child) in self.0.children().enumerate() {
1272 if child.kind() == PARAGRAPH {
1273 para_child_indices.push(child_idx);
1274 }
1275 }
1276
1277 if index1 >= para_child_indices.len() {
1279 panic!("index1 {} out of bounds", index1);
1280 }
1281 if index2 >= para_child_indices.len() {
1282 panic!("index2 {} out of bounds", index2);
1283 }
1284
1285 let child_idx1 = para_child_indices[index1];
1286 let child_idx2 = para_child_indices[index2];
1287
1288 children.swap(child_idx1, child_idx2);
1290
1291 let num_children = children.len();
1293 self.0.splice_children(0..num_children, children);
1294 }
1295
1296 pub fn from_file(path: impl AsRef<Path>) -> Result<Self, Error> {
1298 let text = std::fs::read_to_string(path)?;
1299 Ok(Self::from_str(&text)?)
1300 }
1301
1302 pub fn from_file_relaxed(
1304 path: impl AsRef<Path>,
1305 ) -> Result<(Self, Vec<String>), std::io::Error> {
1306 let text = std::fs::read_to_string(path)?;
1307 Ok(Self::from_str_relaxed(&text))
1308 }
1309
1310 pub fn from_str_relaxed(s: &str) -> (Self, Vec<String>) {
1312 let parsed = parse(s);
1313 (parsed.root_mut(), parsed.errors)
1314 }
1315
1316 pub fn read<R: std::io::Read>(mut r: R) -> Result<Self, Error> {
1318 let mut buf = String::new();
1319 r.read_to_string(&mut buf)?;
1320 Ok(Self::from_str(&buf)?)
1321 }
1322
1323 pub fn read_relaxed<R: std::io::Read>(mut r: R) -> Result<(Self, Vec<String>), std::io::Error> {
1325 let mut buf = String::new();
1326 r.read_to_string(&mut buf)?;
1327 Ok(Self::from_str_relaxed(&buf))
1328 }
1329}
1330
1331fn inject(builder: &mut GreenNodeBuilder, node: SyntaxNode) {
1332 builder.start_node(node.kind().into());
1333 for child in node.children_with_tokens() {
1334 match child {
1335 rowan::NodeOrToken::Node(child) => {
1336 inject(builder, child);
1337 }
1338 rowan::NodeOrToken::Token(token) => {
1339 builder.token(token.kind().into(), token.text());
1340 }
1341 }
1342 }
1343 builder.finish_node();
1344}
1345
1346impl FromIterator<Paragraph> for Deb822 {
1347 fn from_iter<T: IntoIterator<Item = Paragraph>>(iter: T) -> Self {
1348 let mut builder = GreenNodeBuilder::new();
1349 builder.start_node(ROOT.into());
1350 for (i, paragraph) in iter.into_iter().enumerate() {
1351 if i > 0 {
1352 builder.start_node(EMPTY_LINE.into());
1353 builder.token(NEWLINE.into(), "\n");
1354 builder.finish_node();
1355 }
1356 inject(&mut builder, paragraph.0);
1357 }
1358 builder.finish_node();
1359 Self(SyntaxNode::new_root_mut(builder.finish()))
1360 }
1361}
1362
1363impl From<Vec<(String, String)>> for Paragraph {
1364 fn from(v: Vec<(String, String)>) -> Self {
1365 v.into_iter().collect()
1366 }
1367}
1368
1369impl From<Vec<(&str, &str)>> for Paragraph {
1370 fn from(v: Vec<(&str, &str)>) -> Self {
1371 v.into_iter().collect()
1372 }
1373}
1374
1375impl FromIterator<(String, String)> for Paragraph {
1376 fn from_iter<T: IntoIterator<Item = (String, String)>>(iter: T) -> Self {
1377 let mut builder = GreenNodeBuilder::new();
1378 builder.start_node(PARAGRAPH.into());
1379 for (key, value) in iter {
1380 builder.start_node(ENTRY.into());
1381 builder.token(KEY.into(), &key);
1382 builder.token(COLON.into(), ":");
1383 builder.token(WHITESPACE.into(), " ");
1384 for (i, line) in value.split('\n').enumerate() {
1385 if i > 0 {
1386 builder.token(INDENT.into(), " ");
1387 }
1388 builder.token(VALUE.into(), line);
1389 builder.token(NEWLINE.into(), "\n");
1390 }
1391 builder.finish_node();
1392 }
1393 builder.finish_node();
1394 Self(SyntaxNode::new_root_mut(builder.finish()))
1395 }
1396}
1397
1398impl<'a> FromIterator<(&'a str, &'a str)> for Paragraph {
1399 fn from_iter<T: IntoIterator<Item = (&'a str, &'a str)>>(iter: T) -> Self {
1400 let mut builder = GreenNodeBuilder::new();
1401 builder.start_node(PARAGRAPH.into());
1402 for (key, value) in iter {
1403 builder.start_node(ENTRY.into());
1404 builder.token(KEY.into(), key);
1405 builder.token(COLON.into(), ":");
1406 builder.token(WHITESPACE.into(), " ");
1407 for (i, line) in value.split('\n').enumerate() {
1408 if i > 0 {
1409 builder.token(INDENT.into(), " ");
1410 }
1411 builder.token(VALUE.into(), line);
1412 builder.token(NEWLINE.into(), "\n");
1413 }
1414 builder.finish_node();
1415 }
1416 builder.finish_node();
1417 Self(SyntaxNode::new_root_mut(builder.finish()))
1418 }
1419}
1420
1421#[derive(Debug, Clone, PartialEq, Eq)]
1423pub enum IndentPattern {
1424 Fixed(usize),
1426 FieldNameLength,
1428}
1429
1430impl IndentPattern {
1431 fn to_string(&self, field_name: &str) -> String {
1433 match self {
1434 IndentPattern::Fixed(spaces) => " ".repeat(*spaces),
1435 IndentPattern::FieldNameLength => " ".repeat(field_name.len() + 2),
1436 }
1437 }
1438}
1439
1440impl Paragraph {
1441 pub fn new() -> Paragraph {
1443 let mut builder = GreenNodeBuilder::new();
1444
1445 builder.start_node(PARAGRAPH.into());
1446 builder.finish_node();
1447 Paragraph(SyntaxNode::new_root_mut(builder.finish()))
1448 }
1449
1450 pub fn snapshot(&self) -> Self {
1459 Paragraph(SyntaxNode::new_root_mut(self.0.green().into_owned()))
1460 }
1461
1462 pub fn text_range(&self) -> rowan::TextRange {
1464 self.0.text_range()
1465 }
1466
1467 pub fn entries_in_range(&self, range: rowan::TextRange) -> impl Iterator<Item = Entry> + '_ {
1494 self.entries().filter(move |e| {
1495 let entry_range = e.text_range();
1496 entry_range.start() < range.end() && entry_range.end() > range.start()
1498 })
1499 }
1500
1501 pub fn entry_at_position(&self, offset: rowan::TextSize) -> Option<Entry> {
1525 self.entries().find(|e| {
1526 let range = e.text_range();
1527 range.contains(offset)
1528 })
1529 }
1530
1531 #[must_use]
1543 pub fn wrap_and_sort(
1544 &self,
1545 indentation: Indentation,
1546 immediate_empty_line: bool,
1547 max_line_length_one_liner: Option<usize>,
1548 sort_entries: Option<&dyn Fn(&Entry, &Entry) -> std::cmp::Ordering>,
1549 format_value: Option<&dyn Fn(&str, &str) -> String>,
1550 ) -> Paragraph {
1551 let mut builder = GreenNodeBuilder::new();
1552
1553 let mut current = vec![];
1554 let mut entries = vec![];
1555
1556 builder.start_node(PARAGRAPH.into());
1557 for c in self.0.children_with_tokens() {
1558 match c.kind() {
1559 ENTRY => {
1560 entries.push((current, Entry::cast(c.as_node().unwrap().clone()).unwrap()));
1561 current = vec![];
1562 }
1563 ERROR | COMMENT => {
1564 current.push(c);
1565 }
1566 _ => {}
1567 }
1568 }
1569
1570 if let Some(sort_entry) = sort_entries {
1571 entries.sort_by(|a, b| {
1572 let a_key = &a.1;
1573 let b_key = &b.1;
1574 sort_entry(a_key, b_key)
1575 });
1576 }
1577
1578 for (pre, entry) in entries.into_iter() {
1579 for c in pre.into_iter() {
1580 builder.token(c.kind().into(), c.as_token().unwrap().text());
1581 }
1582
1583 inject(
1584 &mut builder,
1585 entry
1586 .wrap_and_sort(
1587 indentation,
1588 immediate_empty_line,
1589 max_line_length_one_liner,
1590 format_value,
1591 )
1592 .0,
1593 );
1594 }
1595
1596 for c in current {
1597 builder.token(c.kind().into(), c.as_token().unwrap().text());
1598 }
1599
1600 builder.finish_node();
1601 Self(SyntaxNode::new_root_mut(builder.finish()))
1602 }
1603
1604 pub fn normalize_field_spacing(&mut self) -> bool {
1624 let mut any_changed = false;
1625
1626 let mut entries: Vec<_> = self.entries().collect();
1628
1629 for entry in &mut entries {
1631 if entry.normalize_field_spacing() {
1632 any_changed = true;
1633 }
1634 }
1635
1636 any_changed
1637 }
1638
1639 pub fn get(&self, key: &str) -> Option<String> {
1643 self.entries()
1644 .find(|e| {
1645 e.key()
1646 .as_deref()
1647 .is_some_and(|k| k.eq_ignore_ascii_case(key))
1648 })
1649 .map(|e| e.value())
1650 }
1651
1652 pub fn get_entry(&self, key: &str) -> Option<Entry> {
1656 self.entries().find(|e| {
1657 e.key()
1658 .as_deref()
1659 .is_some_and(|k| k.eq_ignore_ascii_case(key))
1660 })
1661 }
1662
1663 pub fn get_with_indent(&self, key: &str, indent_pattern: &IndentPattern) -> Option<String> {
1690 use crate::lex::SyntaxKind::{INDENT, VALUE};
1691
1692 self.entries()
1693 .find(|e| {
1694 e.key()
1695 .as_deref()
1696 .is_some_and(|k| k.eq_ignore_ascii_case(key))
1697 })
1698 .and_then(|e| {
1699 let field_key = e.key()?;
1700 let expected_indent = indent_pattern.to_string(&field_key);
1701 let expected_len = expected_indent.len();
1702
1703 let mut result = String::new();
1704 let mut first = true;
1705 let mut last_indent: Option<String> = None;
1706
1707 for token in e.0.children_with_tokens().filter_map(|it| it.into_token()) {
1708 match token.kind() {
1709 INDENT => {
1710 last_indent = Some(token.text().to_string());
1711 }
1712 VALUE => {
1713 if !first {
1714 result.push('\n');
1715 if let Some(ref indent_text) = last_indent {
1717 if indent_text.len() > expected_len {
1718 result.push_str(&indent_text[expected_len..]);
1719 }
1720 }
1721 }
1722 result.push_str(token.text());
1723 first = false;
1724 last_indent = None;
1725 }
1726 _ => {}
1727 }
1728 }
1729
1730 Some(result)
1731 })
1732 }
1733
1734 pub fn get_multiline(&self, key: &str) -> Option<String> {
1761 self.get_with_indent(key, &IndentPattern::Fixed(1))
1762 }
1763
1764 pub fn set_multiline(
1790 &mut self,
1791 key: &str,
1792 value: &str,
1793 field_order: Option<&[&str]>,
1794 ) -> Result<(), Error> {
1795 self.try_set_with_forced_indent(key, value, &IndentPattern::Fixed(1), field_order)
1796 }
1797
1798 pub fn contains_key(&self, key: &str) -> bool {
1800 self.get(key).is_some()
1801 }
1802
1803 pub fn entries(&self) -> impl Iterator<Item = Entry> + '_ {
1805 self.0.children().filter_map(Entry::cast)
1806 }
1807
1808 pub fn items(&self) -> impl Iterator<Item = (String, String)> + '_ {
1810 self.entries()
1811 .filter_map(|e| e.key().map(|k| (k, e.value())))
1812 }
1813
1814 pub fn get_all<'a>(&'a self, key: &'a str) -> impl Iterator<Item = String> + 'a {
1818 self.items().filter_map(move |(k, v)| {
1819 if k.eq_ignore_ascii_case(key) {
1820 Some(v)
1821 } else {
1822 None
1823 }
1824 })
1825 }
1826
1827 pub fn keys(&self) -> impl Iterator<Item = String> + '_ {
1829 self.entries().filter_map(|e| e.key())
1830 }
1831
1832 pub fn remove(&mut self, key: &str) {
1836 for mut entry in self.entries() {
1837 if entry
1838 .key()
1839 .as_deref()
1840 .is_some_and(|k| k.eq_ignore_ascii_case(key))
1841 {
1842 entry.detach();
1843 }
1844 }
1845 }
1846
1847 pub fn insert(&mut self, key: &str, value: &str) {
1849 let entry = Entry::new(key, value);
1850 let count = self.0.children_with_tokens().count();
1851 self.0.splice_children(count..count, vec![entry.0.into()]);
1852 }
1853
1854 pub fn insert_comment_before(&mut self, comment: &str) {
1873 use rowan::GreenNodeBuilder;
1874
1875 let mut builder = GreenNodeBuilder::new();
1878 builder.start_node(EMPTY_LINE.into());
1879 builder.token(COMMENT.into(), &format!("# {}", comment));
1880 builder.token(NEWLINE.into(), "\n");
1881 builder.finish_node();
1882 let green = builder.finish();
1883
1884 let comment_node = SyntaxNode::new_root_mut(green);
1886
1887 let index = self.0.index();
1888 let parent = self.0.parent().expect("Paragraph must have a parent");
1889 parent.splice_children(index..index, vec![comment_node.into()]);
1890 }
1891
1892 fn detect_indent_pattern(&self) -> IndentPattern {
1900 let indent_data: Vec<(String, usize)> = self
1902 .entries()
1903 .filter_map(|entry| {
1904 let field_key = entry.key()?;
1905 let indent = entry.get_indent()?;
1906 Some((field_key, indent.len()))
1907 })
1908 .collect();
1909
1910 if indent_data.is_empty() {
1911 return IndentPattern::FieldNameLength;
1913 }
1914
1915 let first_indent_len = indent_data[0].1;
1917 let all_same = indent_data.iter().all(|(_, len)| *len == first_indent_len);
1918
1919 if all_same {
1920 return IndentPattern::Fixed(first_indent_len);
1922 }
1923
1924 let all_match_field_length = indent_data
1926 .iter()
1927 .all(|(field_key, indent_len)| *indent_len == field_key.len() + 2);
1928
1929 if all_match_field_length {
1930 return IndentPattern::FieldNameLength;
1932 }
1933
1934 IndentPattern::FieldNameLength
1936 }
1937
1938 pub fn try_set(&mut self, key: &str, value: &str) -> Result<(), Error> {
1943 self.try_set_with_indent_pattern(key, value, None, None)
1944 }
1945
1946 pub fn set(&mut self, key: &str, value: &str) {
1951 self.try_set(key, value)
1952 .expect("Invalid value: empty continuation line")
1953 }
1954
1955 pub fn set_with_field_order(&mut self, key: &str, value: &str, field_order: &[&str]) {
1957 self.try_set_with_indent_pattern(key, value, None, Some(field_order))
1958 .expect("Invalid value: empty continuation line")
1959 }
1960
1961 pub fn try_set_with_indent_pattern(
1978 &mut self,
1979 key: &str,
1980 value: &str,
1981 default_indent_pattern: Option<&IndentPattern>,
1982 field_order: Option<&[&str]>,
1983 ) -> Result<(), Error> {
1984 let existing_entry = self.entries().find(|entry| {
1986 entry
1987 .key()
1988 .as_deref()
1989 .is_some_and(|k| k.eq_ignore_ascii_case(key))
1990 });
1991
1992 let indent = existing_entry
1994 .as_ref()
1995 .and_then(|entry| entry.get_indent())
1996 .unwrap_or_else(|| {
1997 if let Some(pattern) = default_indent_pattern {
1999 pattern.to_string(key)
2000 } else {
2001 self.detect_indent_pattern().to_string(key)
2002 }
2003 });
2004
2005 let post_colon_ws = existing_entry
2006 .as_ref()
2007 .and_then(|entry| entry.get_post_colon_whitespace())
2008 .unwrap_or_else(|| " ".to_string());
2009
2010 let actual_key = existing_entry
2012 .as_ref()
2013 .and_then(|e| e.key())
2014 .unwrap_or_else(|| key.to_string());
2015
2016 let new_entry = Entry::try_with_formatting(&actual_key, value, &post_colon_ws, &indent)?;
2017
2018 for entry in self.entries() {
2020 if entry
2021 .key()
2022 .as_deref()
2023 .is_some_and(|k| k.eq_ignore_ascii_case(key))
2024 {
2025 self.0.splice_children(
2026 entry.0.index()..entry.0.index() + 1,
2027 vec![new_entry.0.into()],
2028 );
2029 return Ok(());
2030 }
2031 }
2032
2033 if let Some(order) = field_order {
2035 let insertion_index = self.find_insertion_index(key, order);
2036 self.0
2037 .splice_children(insertion_index..insertion_index, vec![new_entry.0.into()]);
2038 } else {
2039 let insertion_index = self.0.children_with_tokens().count();
2041 self.0
2042 .splice_children(insertion_index..insertion_index, vec![new_entry.0.into()]);
2043 }
2044 Ok(())
2045 }
2046
2047 pub fn set_with_indent_pattern(
2064 &mut self,
2065 key: &str,
2066 value: &str,
2067 default_indent_pattern: Option<&IndentPattern>,
2068 field_order: Option<&[&str]>,
2069 ) {
2070 self.try_set_with_indent_pattern(key, value, default_indent_pattern, field_order)
2071 .expect("Invalid value: empty continuation line")
2072 }
2073
2074 pub fn try_set_with_forced_indent(
2088 &mut self,
2089 key: &str,
2090 value: &str,
2091 indent_pattern: &IndentPattern,
2092 field_order: Option<&[&str]>,
2093 ) -> Result<(), Error> {
2094 let existing_entry = self.entries().find(|entry| {
2096 entry
2097 .key()
2098 .as_deref()
2099 .is_some_and(|k| k.eq_ignore_ascii_case(key))
2100 });
2101
2102 let post_colon_ws = existing_entry
2104 .as_ref()
2105 .and_then(|entry| entry.get_post_colon_whitespace())
2106 .unwrap_or_else(|| " ".to_string());
2107
2108 let actual_key = existing_entry
2110 .as_ref()
2111 .and_then(|e| e.key())
2112 .unwrap_or_else(|| key.to_string());
2113
2114 let indent = indent_pattern.to_string(&actual_key);
2116 let new_entry = Entry::try_with_formatting(&actual_key, value, &post_colon_ws, &indent)?;
2117
2118 for entry in self.entries() {
2120 if entry
2121 .key()
2122 .as_deref()
2123 .is_some_and(|k| k.eq_ignore_ascii_case(key))
2124 {
2125 self.0.splice_children(
2126 entry.0.index()..entry.0.index() + 1,
2127 vec![new_entry.0.into()],
2128 );
2129 return Ok(());
2130 }
2131 }
2132
2133 if let Some(order) = field_order {
2135 let insertion_index = self.find_insertion_index(key, order);
2136 self.0
2137 .splice_children(insertion_index..insertion_index, vec![new_entry.0.into()]);
2138 } else {
2139 let insertion_index = self.0.children_with_tokens().count();
2141 self.0
2142 .splice_children(insertion_index..insertion_index, vec![new_entry.0.into()]);
2143 }
2144 Ok(())
2145 }
2146
2147 pub fn set_with_forced_indent(
2161 &mut self,
2162 key: &str,
2163 value: &str,
2164 indent_pattern: &IndentPattern,
2165 field_order: Option<&[&str]>,
2166 ) {
2167 self.try_set_with_forced_indent(key, value, indent_pattern, field_order)
2168 .expect("Invalid value: empty continuation line")
2169 }
2170
2171 pub fn change_field_indent(
2187 &mut self,
2188 key: &str,
2189 indent_pattern: &IndentPattern,
2190 ) -> Result<bool, Error> {
2191 let existing_entry = self.entries().find(|entry| {
2193 entry
2194 .key()
2195 .as_deref()
2196 .is_some_and(|k| k.eq_ignore_ascii_case(key))
2197 });
2198
2199 if let Some(entry) = existing_entry {
2200 let value = entry.value();
2201 let actual_key = entry.key().unwrap_or_else(|| key.to_string());
2202
2203 let post_colon_ws = entry
2205 .get_post_colon_whitespace()
2206 .unwrap_or_else(|| " ".to_string());
2207
2208 let indent = indent_pattern.to_string(&actual_key);
2210 let new_entry =
2211 Entry::try_with_formatting(&actual_key, &value, &post_colon_ws, &indent)?;
2212
2213 self.0.splice_children(
2215 entry.0.index()..entry.0.index() + 1,
2216 vec![new_entry.0.into()],
2217 );
2218 Ok(true)
2219 } else {
2220 Ok(false)
2221 }
2222 }
2223
2224 fn find_insertion_index(&self, key: &str, field_order: &[&str]) -> usize {
2226 let new_field_position = field_order
2228 .iter()
2229 .position(|&field| field.eq_ignore_ascii_case(key));
2230
2231 let mut insertion_index = self.0.children_with_tokens().count();
2232
2233 for (i, child) in self.0.children_with_tokens().enumerate() {
2235 if let Some(node) = child.as_node() {
2236 if let Some(entry) = Entry::cast(node.clone()) {
2237 if let Some(existing_key) = entry.key() {
2238 let existing_position = field_order
2239 .iter()
2240 .position(|&field| field.eq_ignore_ascii_case(&existing_key));
2241
2242 match (new_field_position, existing_position) {
2243 (Some(new_pos), Some(existing_pos)) => {
2245 if new_pos < existing_pos {
2246 insertion_index = i;
2247 break;
2248 }
2249 }
2250 (Some(_), None) => {
2252 }
2254 (None, Some(_)) => {
2256 }
2258 (None, None) => {
2260 if key < existing_key.as_str() {
2261 insertion_index = i;
2262 break;
2263 }
2264 }
2265 }
2266 }
2267 }
2268 }
2269 }
2270
2271 if new_field_position.is_some() && insertion_index == self.0.children_with_tokens().count()
2274 {
2275 let children: Vec<_> = self.0.children_with_tokens().enumerate().collect();
2277 for (i, child) in children.into_iter().rev() {
2278 if let Some(node) = child.as_node() {
2279 if let Some(entry) = Entry::cast(node.clone()) {
2280 if let Some(existing_key) = entry.key() {
2281 if field_order
2282 .iter()
2283 .any(|&f| f.eq_ignore_ascii_case(&existing_key))
2284 {
2285 insertion_index = i + 1;
2287 break;
2288 }
2289 }
2290 }
2291 }
2292 }
2293 }
2294
2295 insertion_index
2296 }
2297
2298 pub fn rename(&mut self, old_key: &str, new_key: &str) -> bool {
2302 for entry in self.entries() {
2303 if entry
2304 .key()
2305 .as_deref()
2306 .is_some_and(|k| k.eq_ignore_ascii_case(old_key))
2307 {
2308 self.0.splice_children(
2309 entry.0.index()..entry.0.index() + 1,
2310 vec![Entry::new(new_key, entry.value().as_str()).0.into()],
2311 );
2312 return true;
2313 }
2314 }
2315 false
2316 }
2317}
2318
2319impl Default for Paragraph {
2320 fn default() -> Self {
2321 Self::new()
2322 }
2323}
2324
2325impl std::str::FromStr for Paragraph {
2326 type Err = ParseError;
2327
2328 fn from_str(text: &str) -> Result<Self, Self::Err> {
2329 let deb822 = Deb822::from_str(text)?;
2330
2331 let mut paragraphs = deb822.paragraphs();
2332
2333 paragraphs
2334 .next()
2335 .ok_or_else(|| ParseError(vec!["no paragraphs".to_string()]))
2336 }
2337}
2338
2339#[cfg(feature = "python-debian")]
2340impl<'py> pyo3::IntoPyObject<'py> for Paragraph {
2341 type Target = pyo3::PyAny;
2342 type Output = pyo3::Bound<'py, Self::Target>;
2343 type Error = pyo3::PyErr;
2344
2345 fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
2346 use pyo3::prelude::*;
2347 let d = pyo3::types::PyDict::new(py);
2348 for (k, v) in self.items() {
2349 d.set_item(k, v)?;
2350 }
2351 let m = py.import("debian.deb822")?;
2352 let cls = m.getattr("Deb822")?;
2353 cls.call1((d,))
2354 }
2355}
2356
2357#[cfg(feature = "python-debian")]
2358impl<'py> pyo3::IntoPyObject<'py> for &Paragraph {
2359 type Target = pyo3::PyAny;
2360 type Output = pyo3::Bound<'py, Self::Target>;
2361 type Error = pyo3::PyErr;
2362
2363 fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
2364 use pyo3::prelude::*;
2365 let d = pyo3::types::PyDict::new(py);
2366 for (k, v) in self.items() {
2367 d.set_item(k, v)?;
2368 }
2369 let m = py.import("debian.deb822")?;
2370 let cls = m.getattr("Deb822")?;
2371 cls.call1((d,))
2372 }
2373}
2374
2375#[cfg(feature = "python-debian")]
2376impl<'py> pyo3::FromPyObject<'_, 'py> for Paragraph {
2377 type Error = pyo3::PyErr;
2378
2379 fn extract(obj: pyo3::Borrowed<'_, 'py, pyo3::PyAny>) -> Result<Self, Self::Error> {
2380 use pyo3::types::PyAnyMethods;
2381 let d = obj.call_method0("__str__")?.extract::<String>()?;
2382 Paragraph::from_str(&d)
2383 .map_err(|e| pyo3::exceptions::PyValueError::new_err((e.to_string(),)))
2384 }
2385}
2386
2387impl Entry {
2388 pub fn snapshot(&self) -> Self {
2397 Entry(SyntaxNode::new_root_mut(self.0.green().into_owned()))
2398 }
2399
2400 pub fn text_range(&self) -> rowan::TextRange {
2402 self.0.text_range()
2403 }
2404
2405 pub fn key_range(&self) -> Option<rowan::TextRange> {
2407 self.0
2408 .children_with_tokens()
2409 .filter_map(|it| it.into_token())
2410 .find(|it| it.kind() == KEY)
2411 .map(|it| it.text_range())
2412 }
2413
2414 pub fn colon_range(&self) -> Option<rowan::TextRange> {
2416 self.0
2417 .children_with_tokens()
2418 .filter_map(|it| it.into_token())
2419 .find(|it| it.kind() == COLON)
2420 .map(|it| it.text_range())
2421 }
2422
2423 pub fn value_range(&self) -> Option<rowan::TextRange> {
2426 let value_tokens: Vec<_> = self
2427 .0
2428 .children_with_tokens()
2429 .filter_map(|it| it.into_token())
2430 .filter(|it| it.kind() == VALUE)
2431 .collect();
2432
2433 if value_tokens.is_empty() {
2434 return None;
2435 }
2436
2437 let first = value_tokens.first().unwrap();
2438 let last = value_tokens.last().unwrap();
2439 Some(rowan::TextRange::new(
2440 first.text_range().start(),
2441 last.text_range().end(),
2442 ))
2443 }
2444
2445 pub fn value_line_ranges(&self) -> Vec<rowan::TextRange> {
2448 self.0
2449 .children_with_tokens()
2450 .filter_map(|it| it.into_token())
2451 .filter(|it| it.kind() == VALUE)
2452 .map(|it| it.text_range())
2453 .collect()
2454 }
2455
2456 pub fn new(key: &str, value: &str) -> Entry {
2458 Self::with_indentation(key, value, " ")
2459 }
2460
2461 pub fn with_indentation(key: &str, value: &str, indent: &str) -> Entry {
2468 Entry::with_formatting(key, value, " ", indent)
2469 }
2470
2471 pub fn try_with_formatting(
2482 key: &str,
2483 value: &str,
2484 post_colon_ws: &str,
2485 indent: &str,
2486 ) -> Result<Entry, Error> {
2487 let mut builder = GreenNodeBuilder::new();
2488
2489 builder.start_node(ENTRY.into());
2490 builder.token(KEY.into(), key);
2491 builder.token(COLON.into(), ":");
2492
2493 let mut i = 0;
2495 while i < post_colon_ws.len() {
2496 if post_colon_ws[i..].starts_with('\n') {
2497 builder.token(NEWLINE.into(), "\n");
2498 i += 1;
2499 } else {
2500 let start = i;
2502 while i < post_colon_ws.len() && !post_colon_ws[i..].starts_with('\n') {
2503 i += post_colon_ws[i..].chars().next().unwrap().len_utf8();
2504 }
2505 builder.token(WHITESPACE.into(), &post_colon_ws[start..i]);
2506 }
2507 }
2508
2509 for (line_idx, line) in value.split('\n').enumerate() {
2510 if line_idx > 0 {
2511 if line.trim().is_empty() {
2514 return Err(Error::InvalidValue(format!(
2515 "empty continuation line (line with only whitespace) at line {}",
2516 line_idx + 1
2517 )));
2518 }
2519 builder.token(INDENT.into(), indent);
2520 }
2521 builder.token(VALUE.into(), line);
2522 builder.token(NEWLINE.into(), "\n");
2523 }
2524 builder.finish_node();
2525 Ok(Entry(SyntaxNode::new_root_mut(builder.finish())))
2526 }
2527
2528 pub fn with_formatting(key: &str, value: &str, post_colon_ws: &str, indent: &str) -> Entry {
2539 Self::try_with_formatting(key, value, post_colon_ws, indent)
2540 .expect("Invalid value: empty continuation line")
2541 }
2542
2543 #[must_use]
2544 pub fn wrap_and_sort(
2557 &self,
2558 mut indentation: Indentation,
2559 immediate_empty_line: bool,
2560 max_line_length_one_liner: Option<usize>,
2561 format_value: Option<&dyn Fn(&str, &str) -> String>,
2562 ) -> Entry {
2563 let mut builder = GreenNodeBuilder::new();
2564
2565 let mut content = vec![];
2566 builder.start_node(ENTRY.into());
2567 for c in self.0.children_with_tokens() {
2568 let text = c.as_token().map(|t| t.text());
2569 match c.kind() {
2570 KEY => {
2571 builder.token(KEY.into(), text.unwrap());
2572 if indentation == Indentation::FieldNameLength {
2573 indentation = Indentation::Spaces(text.unwrap().len() as u32);
2574 }
2575 }
2576 COLON => {
2577 builder.token(COLON.into(), ":");
2578 }
2579 INDENT => {
2580 }
2582 ERROR | COMMENT | VALUE | WHITESPACE | NEWLINE => {
2583 content.push(c);
2584 }
2585 EMPTY_LINE | ENTRY | ROOT | PARAGRAPH => unreachable!(),
2586 }
2587 }
2588
2589 let indentation = if let crate::Indentation::Spaces(i) = indentation {
2590 i
2591 } else {
2592 1
2593 };
2594
2595 assert!(indentation > 0);
2596
2597 while let Some(c) = content.last() {
2599 if c.kind() == NEWLINE || c.kind() == WHITESPACE {
2600 content.pop();
2601 } else {
2602 break;
2603 }
2604 }
2605
2606 let tokens = if let Some(ref format_value) = format_value {
2609 if !content
2610 .iter()
2611 .any(|c| c.kind() == ERROR || c.kind() == COMMENT)
2612 {
2613 let concat = content
2614 .iter()
2615 .filter_map(|c| c.as_token().map(|t| t.text()))
2616 .collect::<String>();
2617 let formatted = format_value(self.key().as_ref().unwrap(), &concat);
2618 crate::lex::lex_inline(&formatted)
2619 .map(|(k, t)| (k, t.to_string()))
2620 .collect::<Vec<_>>()
2621 } else {
2622 content
2623 .into_iter()
2624 .map(|n| n.into_token().unwrap())
2625 .map(|i| (i.kind(), i.text().to_string()))
2626 .collect::<Vec<_>>()
2627 }
2628 } else {
2629 content
2630 .into_iter()
2631 .map(|n| n.into_token().unwrap())
2632 .map(|i| (i.kind(), i.text().to_string()))
2633 .collect::<Vec<_>>()
2634 };
2635
2636 rebuild_value(
2637 &mut builder,
2638 tokens,
2639 self.key().map_or(0, |k| k.len()),
2640 indentation,
2641 immediate_empty_line,
2642 max_line_length_one_liner,
2643 );
2644
2645 builder.finish_node();
2646 Self(SyntaxNode::new_root_mut(builder.finish()))
2647 }
2648
2649 pub fn key(&self) -> Option<String> {
2651 self.0
2652 .children_with_tokens()
2653 .filter_map(|it| it.into_token())
2654 .find(|it| it.kind() == KEY)
2655 .map(|it| it.text().to_string())
2656 }
2657
2658 pub fn value(&self) -> String {
2660 let mut parts = self
2661 .0
2662 .children_with_tokens()
2663 .filter_map(|it| it.into_token())
2664 .filter(|it| it.kind() == VALUE)
2665 .map(|it| it.text().to_string());
2666
2667 match parts.next() {
2668 None => String::new(),
2669 Some(first) => {
2670 let mut result = first;
2671 for part in parts {
2672 result.push('\n');
2673 result.push_str(&part);
2674 }
2675 result
2676 }
2677 }
2678 }
2679
2680 fn get_indent(&self) -> Option<String> {
2683 self.0
2684 .children_with_tokens()
2685 .filter_map(|it| it.into_token())
2686 .find(|it| it.kind() == INDENT)
2687 .map(|it| it.text().to_string())
2688 }
2689
2690 fn get_post_colon_whitespace(&self) -> Option<String> {
2694 let mut found_colon = false;
2695 let mut whitespace = String::new();
2696
2697 for token in self
2698 .0
2699 .children_with_tokens()
2700 .filter_map(|it| it.into_token())
2701 {
2702 if token.kind() == COLON {
2703 found_colon = true;
2704 continue;
2705 }
2706
2707 if found_colon {
2708 if token.kind() == WHITESPACE || token.kind() == NEWLINE || token.kind() == INDENT {
2709 whitespace.push_str(token.text());
2710 } else {
2711 break;
2713 }
2714 }
2715 }
2716
2717 if whitespace.is_empty() {
2718 None
2719 } else {
2720 Some(whitespace)
2721 }
2722 }
2723
2724 pub fn normalize_field_spacing(&mut self) -> bool {
2745 use rowan::GreenNodeBuilder;
2746
2747 let original_text = self.0.text().to_string();
2749
2750 let mut builder = GreenNodeBuilder::new();
2752 builder.start_node(ENTRY.into());
2753
2754 let mut seen_colon = false;
2755 let mut skip_whitespace = false;
2756
2757 for child in self.0.children_with_tokens() {
2758 match child.kind() {
2759 KEY => {
2760 builder.token(KEY.into(), child.as_token().unwrap().text());
2761 }
2762 COLON => {
2763 builder.token(COLON.into(), ":");
2764 seen_colon = true;
2765 skip_whitespace = true;
2766 }
2767 WHITESPACE if skip_whitespace => {
2768 continue;
2770 }
2771 VALUE if skip_whitespace => {
2772 builder.token(WHITESPACE.into(), " ");
2774 builder.token(VALUE.into(), child.as_token().unwrap().text());
2775 skip_whitespace = false;
2776 }
2777 NEWLINE if skip_whitespace && seen_colon => {
2778 builder.token(NEWLINE.into(), "\n");
2781 skip_whitespace = false;
2782 }
2783 _ => {
2784 if let Some(token) = child.as_token() {
2786 builder.token(token.kind().into(), token.text());
2787 }
2788 }
2789 }
2790 }
2791
2792 builder.finish_node();
2793 let normalized_green = builder.finish();
2794 let normalized = SyntaxNode::new_root_mut(normalized_green);
2795
2796 let changed = original_text != normalized.text().to_string();
2798
2799 if changed {
2800 if let Some(parent) = self.0.parent() {
2802 let index = self.0.index();
2803 parent.splice_children(index..index + 1, vec![normalized.into()]);
2804 }
2805 }
2806
2807 changed
2808 }
2809
2810 pub fn detach(&mut self) {
2812 self.0.detach();
2813 }
2814}
2815
2816impl FromStr for Deb822 {
2817 type Err = ParseError;
2818
2819 fn from_str(s: &str) -> Result<Self, Self::Err> {
2820 Deb822::parse(s).to_result()
2821 }
2822}
2823
2824#[test]
2825fn test_parse_simple() {
2826 const CONTROLV1: &str = r#"Source: foo
2827Maintainer: Foo Bar <foo@example.com>
2828Section: net
2829
2830# This is a comment
2831
2832Package: foo
2833Architecture: all
2834Depends:
2835 bar,
2836 blah
2837Description: This is a description
2838 And it is
2839 .
2840 multiple
2841 lines
2842"#;
2843 let parsed = parse(CONTROLV1);
2844 let node = parsed.syntax();
2845 assert_eq!(
2846 format!("{:#?}", node),
2847 r###"ROOT@0..203
2848 PARAGRAPH@0..63
2849 ENTRY@0..12
2850 KEY@0..6 "Source"
2851 COLON@6..7 ":"
2852 WHITESPACE@7..8 " "
2853 VALUE@8..11 "foo"
2854 NEWLINE@11..12 "\n"
2855 ENTRY@12..50
2856 KEY@12..22 "Maintainer"
2857 COLON@22..23 ":"
2858 WHITESPACE@23..24 " "
2859 VALUE@24..49 "Foo Bar <foo@example. ..."
2860 NEWLINE@49..50 "\n"
2861 ENTRY@50..63
2862 KEY@50..57 "Section"
2863 COLON@57..58 ":"
2864 WHITESPACE@58..59 " "
2865 VALUE@59..62 "net"
2866 NEWLINE@62..63 "\n"
2867 EMPTY_LINE@63..64
2868 NEWLINE@63..64 "\n"
2869 EMPTY_LINE@64..84
2870 COMMENT@64..83 "# This is a comment"
2871 NEWLINE@83..84 "\n"
2872 EMPTY_LINE@84..85
2873 NEWLINE@84..85 "\n"
2874 PARAGRAPH@85..203
2875 ENTRY@85..98
2876 KEY@85..92 "Package"
2877 COLON@92..93 ":"
2878 WHITESPACE@93..94 " "
2879 VALUE@94..97 "foo"
2880 NEWLINE@97..98 "\n"
2881 ENTRY@98..116
2882 KEY@98..110 "Architecture"
2883 COLON@110..111 ":"
2884 WHITESPACE@111..112 " "
2885 VALUE@112..115 "all"
2886 NEWLINE@115..116 "\n"
2887 ENTRY@116..137
2888 KEY@116..123 "Depends"
2889 COLON@123..124 ":"
2890 NEWLINE@124..125 "\n"
2891 INDENT@125..126 " "
2892 VALUE@126..130 "bar,"
2893 NEWLINE@130..131 "\n"
2894 INDENT@131..132 " "
2895 VALUE@132..136 "blah"
2896 NEWLINE@136..137 "\n"
2897 ENTRY@137..203
2898 KEY@137..148 "Description"
2899 COLON@148..149 ":"
2900 WHITESPACE@149..150 " "
2901 VALUE@150..171 "This is a description"
2902 NEWLINE@171..172 "\n"
2903 INDENT@172..173 " "
2904 VALUE@173..182 "And it is"
2905 NEWLINE@182..183 "\n"
2906 INDENT@183..184 " "
2907 VALUE@184..185 "."
2908 NEWLINE@185..186 "\n"
2909 INDENT@186..187 " "
2910 VALUE@187..195 "multiple"
2911 NEWLINE@195..196 "\n"
2912 INDENT@196..197 " "
2913 VALUE@197..202 "lines"
2914 NEWLINE@202..203 "\n"
2915"###
2916 );
2917 assert_eq!(parsed.errors, Vec::<String>::new());
2918
2919 let root = parsed.root_mut();
2920 assert_eq!(root.paragraphs().count(), 2);
2921 let source = root.paragraphs().next().unwrap();
2922 assert_eq!(
2923 source.keys().collect::<Vec<_>>(),
2924 vec!["Source", "Maintainer", "Section"]
2925 );
2926 assert_eq!(source.get("Source").as_deref(), Some("foo"));
2927 assert_eq!(
2928 source.get("Maintainer").as_deref(),
2929 Some("Foo Bar <foo@example.com>")
2930 );
2931 assert_eq!(source.get("Section").as_deref(), Some("net"));
2932 assert_eq!(
2933 source.items().collect::<Vec<_>>(),
2934 vec![
2935 ("Source".into(), "foo".into()),
2936 ("Maintainer".into(), "Foo Bar <foo@example.com>".into()),
2937 ("Section".into(), "net".into()),
2938 ]
2939 );
2940
2941 let binary = root.paragraphs().nth(1).unwrap();
2942 assert_eq!(
2943 binary.keys().collect::<Vec<_>>(),
2944 vec!["Package", "Architecture", "Depends", "Description"]
2945 );
2946 assert_eq!(binary.get("Package").as_deref(), Some("foo"));
2947 assert_eq!(binary.get("Architecture").as_deref(), Some("all"));
2948 assert_eq!(binary.get("Depends").as_deref(), Some("bar,\nblah"));
2949 assert_eq!(
2950 binary.get("Description").as_deref(),
2951 Some("This is a description\nAnd it is\n.\nmultiple\nlines")
2952 );
2953
2954 assert_eq!(node.text(), CONTROLV1);
2955}
2956
2957#[test]
2958fn test_with_trailing_whitespace() {
2959 const CONTROLV1: &str = r#"Source: foo
2960Maintainer: Foo Bar <foo@example.com>
2961
2962
2963"#;
2964 let parsed = parse(CONTROLV1);
2965 let node = parsed.syntax();
2966 assert_eq!(
2967 format!("{:#?}", node),
2968 r###"ROOT@0..52
2969 PARAGRAPH@0..50
2970 ENTRY@0..12
2971 KEY@0..6 "Source"
2972 COLON@6..7 ":"
2973 WHITESPACE@7..8 " "
2974 VALUE@8..11 "foo"
2975 NEWLINE@11..12 "\n"
2976 ENTRY@12..50
2977 KEY@12..22 "Maintainer"
2978 COLON@22..23 ":"
2979 WHITESPACE@23..24 " "
2980 VALUE@24..49 "Foo Bar <foo@example. ..."
2981 NEWLINE@49..50 "\n"
2982 EMPTY_LINE@50..51
2983 NEWLINE@50..51 "\n"
2984 EMPTY_LINE@51..52
2985 NEWLINE@51..52 "\n"
2986"###
2987 );
2988 assert_eq!(parsed.errors, Vec::<String>::new());
2989
2990 let root = parsed.root_mut();
2991 assert_eq!(root.paragraphs().count(), 1);
2992 let source = root.paragraphs().next().unwrap();
2993 assert_eq!(
2994 source.items().collect::<Vec<_>>(),
2995 vec![
2996 ("Source".into(), "foo".into()),
2997 ("Maintainer".into(), "Foo Bar <foo@example.com>".into()),
2998 ]
2999 );
3000}
3001
3002fn rebuild_value(
3003 builder: &mut GreenNodeBuilder,
3004 mut tokens: Vec<(SyntaxKind, String)>,
3005 key_len: usize,
3006 indentation: u32,
3007 immediate_empty_line: bool,
3008 max_line_length_one_liner: Option<usize>,
3009) {
3010 let first_line_len = tokens
3011 .iter()
3012 .take_while(|(k, _t)| *k != NEWLINE)
3013 .map(|(_k, t)| t.len())
3014 .sum::<usize>() + key_len + 2 ;
3015
3016 let has_newline = tokens.iter().any(|(k, _t)| *k == NEWLINE);
3017
3018 let mut last_was_newline = false;
3019 if max_line_length_one_liner
3020 .map(|mll| first_line_len <= mll)
3021 .unwrap_or(false)
3022 && !has_newline
3023 {
3024 for (k, t) in tokens {
3026 builder.token(k.into(), &t);
3027 }
3028 } else {
3029 if immediate_empty_line && has_newline {
3031 builder.token(NEWLINE.into(), "\n");
3032 last_was_newline = true;
3033 } else {
3034 builder.token(WHITESPACE.into(), " ");
3035 }
3036 let mut start_idx = 0;
3038 while start_idx < tokens.len() {
3039 if tokens[start_idx].0 == NEWLINE || tokens[start_idx].0 == WHITESPACE {
3040 start_idx += 1;
3041 } else {
3042 break;
3043 }
3044 }
3045 tokens.drain(..start_idx);
3046 let indent_str = " ".repeat(indentation as usize);
3048 for (k, t) in tokens {
3049 if last_was_newline {
3050 builder.token(INDENT.into(), &indent_str);
3051 }
3052 builder.token(k.into(), &t);
3053 last_was_newline = k == NEWLINE;
3054 }
3055 }
3056
3057 if !last_was_newline {
3058 builder.token(NEWLINE.into(), "\n");
3059 }
3060}
3061
3062#[cfg(test)]
3063mod tests {
3064 use super::*;
3065 #[test]
3066 fn test_parse() {
3067 let d: super::Deb822 = r#"Source: foo
3068Maintainer: Foo Bar <jelmer@jelmer.uk>
3069Section: net
3070
3071Package: foo
3072Architecture: all
3073Depends: libc6
3074Description: This is a description
3075 With details
3076"#
3077 .parse()
3078 .unwrap();
3079 let mut ps = d.paragraphs();
3080 let p = ps.next().unwrap();
3081
3082 assert_eq!(p.get("Source").as_deref(), Some("foo"));
3083 assert_eq!(
3084 p.get("Maintainer").as_deref(),
3085 Some("Foo Bar <jelmer@jelmer.uk>")
3086 );
3087 assert_eq!(p.get("Section").as_deref(), Some("net"));
3088
3089 let b = ps.next().unwrap();
3090 assert_eq!(b.get("Package").as_deref(), Some("foo"));
3091 }
3092
3093 #[test]
3094 fn test_after_multi_line() {
3095 let d: super::Deb822 = r#"Source: golang-github-blah-blah
3096Section: devel
3097Priority: optional
3098Standards-Version: 4.2.0
3099Maintainer: Some Maintainer <example@example.com>
3100Build-Depends: debhelper (>= 11~),
3101 dh-golang,
3102 golang-any
3103Homepage: https://github.com/j-keck/arping
3104"#
3105 .parse()
3106 .unwrap();
3107 let mut ps = d.paragraphs();
3108 let p = ps.next().unwrap();
3109 assert_eq!(p.get("Source").as_deref(), Some("golang-github-blah-blah"));
3110 assert_eq!(p.get("Section").as_deref(), Some("devel"));
3111 assert_eq!(p.get("Priority").as_deref(), Some("optional"));
3112 assert_eq!(p.get("Standards-Version").as_deref(), Some("4.2.0"));
3113 assert_eq!(
3114 p.get("Maintainer").as_deref(),
3115 Some("Some Maintainer <example@example.com>")
3116 );
3117 assert_eq!(
3118 p.get("Build-Depends").as_deref(),
3119 Some("debhelper (>= 11~),\ndh-golang,\ngolang-any")
3120 );
3121 assert_eq!(
3122 p.get("Homepage").as_deref(),
3123 Some("https://github.com/j-keck/arping")
3124 );
3125 }
3126
3127 #[test]
3128 fn test_remove_field() {
3129 let d: super::Deb822 = r#"Source: foo
3130# Comment
3131Maintainer: Foo Bar <jelmer@jelmer.uk>
3132Section: net
3133
3134Package: foo
3135Architecture: all
3136Depends: libc6
3137Description: This is a description
3138 With details
3139"#
3140 .parse()
3141 .unwrap();
3142 let mut ps = d.paragraphs();
3143 let mut p = ps.next().unwrap();
3144 p.set("Foo", "Bar");
3145 p.remove("Section");
3146 p.remove("Nonexistent");
3147 assert_eq!(p.get("Foo").as_deref(), Some("Bar"));
3148 assert_eq!(
3149 p.to_string(),
3150 r#"Source: foo
3151# Comment
3152Maintainer: Foo Bar <jelmer@jelmer.uk>
3153Foo: Bar
3154"#
3155 );
3156 }
3157
3158 #[test]
3159 fn test_rename_field() {
3160 let d: super::Deb822 = r#"Source: foo
3161Vcs-Browser: https://salsa.debian.org/debian/foo
3162"#
3163 .parse()
3164 .unwrap();
3165 let mut ps = d.paragraphs();
3166 let mut p = ps.next().unwrap();
3167 assert!(p.rename("Vcs-Browser", "Homepage"));
3168 assert_eq!(
3169 p.to_string(),
3170 r#"Source: foo
3171Homepage: https://salsa.debian.org/debian/foo
3172"#
3173 );
3174
3175 assert_eq!(
3176 p.get("Homepage").as_deref(),
3177 Some("https://salsa.debian.org/debian/foo")
3178 );
3179 assert_eq!(p.get("Vcs-Browser").as_deref(), None);
3180
3181 assert!(!p.rename("Nonexistent", "Homepage"));
3183 }
3184
3185 #[test]
3186 fn test_set_field() {
3187 let d: super::Deb822 = r#"Source: foo
3188Maintainer: Foo Bar <joe@example.com>
3189"#
3190 .parse()
3191 .unwrap();
3192 let mut ps = d.paragraphs();
3193 let mut p = ps.next().unwrap();
3194 p.set("Maintainer", "Somebody Else <jane@example.com>");
3195 assert_eq!(
3196 p.get("Maintainer").as_deref(),
3197 Some("Somebody Else <jane@example.com>")
3198 );
3199 assert_eq!(
3200 p.to_string(),
3201 r#"Source: foo
3202Maintainer: Somebody Else <jane@example.com>
3203"#
3204 );
3205 }
3206
3207 #[test]
3208 fn test_set_new_field() {
3209 let d: super::Deb822 = r#"Source: foo
3210"#
3211 .parse()
3212 .unwrap();
3213 let mut ps = d.paragraphs();
3214 let mut p = ps.next().unwrap();
3215 p.set("Maintainer", "Somebody <joe@example.com>");
3216 assert_eq!(
3217 p.get("Maintainer").as_deref(),
3218 Some("Somebody <joe@example.com>")
3219 );
3220 assert_eq!(
3221 p.to_string(),
3222 r#"Source: foo
3223Maintainer: Somebody <joe@example.com>
3224"#
3225 );
3226 }
3227
3228 #[test]
3229 fn test_add_paragraph() {
3230 let mut d = super::Deb822::new();
3231 let mut p = d.add_paragraph();
3232 p.set("Foo", "Bar");
3233 assert_eq!(p.get("Foo").as_deref(), Some("Bar"));
3234 assert_eq!(
3235 p.to_string(),
3236 r#"Foo: Bar
3237"#
3238 );
3239 assert_eq!(
3240 d.to_string(),
3241 r#"Foo: Bar
3242"#
3243 );
3244
3245 let mut p = d.add_paragraph();
3246 p.set("Foo", "Blah");
3247 assert_eq!(p.get("Foo").as_deref(), Some("Blah"));
3248 assert_eq!(
3249 d.to_string(),
3250 r#"Foo: Bar
3251
3252Foo: Blah
3253"#
3254 );
3255 }
3256
3257 #[test]
3258 fn test_crud_paragraph() {
3259 let mut d = super::Deb822::new();
3260 let mut p = d.insert_paragraph(0);
3261 p.set("Foo", "Bar");
3262 assert_eq!(p.get("Foo").as_deref(), Some("Bar"));
3263 assert_eq!(
3264 d.to_string(),
3265 r#"Foo: Bar
3266"#
3267 );
3268
3269 let mut p = d.insert_paragraph(0);
3271 p.set("Foo", "Blah");
3272 assert_eq!(p.get("Foo").as_deref(), Some("Blah"));
3273 assert_eq!(
3274 d.to_string(),
3275 r#"Foo: Blah
3276
3277Foo: Bar
3278"#
3279 );
3280
3281 d.remove_paragraph(1);
3283 assert_eq!(d.to_string(), "Foo: Blah\n\n");
3284
3285 p.set("Foo", "Baz");
3287 assert_eq!(d.to_string(), "Foo: Baz\n\n");
3288
3289 d.remove_paragraph(0);
3291 assert_eq!(d.to_string(), "");
3292 }
3293
3294 #[test]
3295 fn test_swap_paragraphs() {
3296 let mut d: super::Deb822 = vec![
3298 vec![("Foo", "Bar")].into_iter().collect(),
3299 vec![("A", "B")].into_iter().collect(),
3300 vec![("X", "Y")].into_iter().collect(),
3301 ]
3302 .into_iter()
3303 .collect();
3304
3305 d.swap_paragraphs(0, 2);
3306 assert_eq!(d.to_string(), "X: Y\n\nA: B\n\nFoo: Bar\n");
3307
3308 d.swap_paragraphs(0, 2);
3310 assert_eq!(d.to_string(), "Foo: Bar\n\nA: B\n\nX: Y\n");
3311
3312 d.swap_paragraphs(0, 1);
3314 assert_eq!(d.to_string(), "A: B\n\nFoo: Bar\n\nX: Y\n");
3315
3316 let before = d.to_string();
3318 d.swap_paragraphs(1, 1);
3319 assert_eq!(d.to_string(), before);
3320 }
3321
3322 #[test]
3323 fn test_swap_paragraphs_preserves_content() {
3324 let mut d: super::Deb822 = vec![
3326 vec![("Field1", "Value1"), ("Field2", "Value2")]
3327 .into_iter()
3328 .collect(),
3329 vec![("FieldA", "ValueA"), ("FieldB", "ValueB")]
3330 .into_iter()
3331 .collect(),
3332 ]
3333 .into_iter()
3334 .collect();
3335
3336 d.swap_paragraphs(0, 1);
3337
3338 let mut paras = d.paragraphs();
3339 let p1 = paras.next().unwrap();
3340 assert_eq!(p1.get("FieldA").as_deref(), Some("ValueA"));
3341 assert_eq!(p1.get("FieldB").as_deref(), Some("ValueB"));
3342
3343 let p2 = paras.next().unwrap();
3344 assert_eq!(p2.get("Field1").as_deref(), Some("Value1"));
3345 assert_eq!(p2.get("Field2").as_deref(), Some("Value2"));
3346 }
3347
3348 #[test]
3349 #[should_panic(expected = "out of bounds")]
3350 fn test_swap_paragraphs_out_of_bounds() {
3351 let mut d: super::Deb822 = vec![
3352 vec![("Foo", "Bar")].into_iter().collect(),
3353 vec![("A", "B")].into_iter().collect(),
3354 ]
3355 .into_iter()
3356 .collect();
3357
3358 d.swap_paragraphs(0, 5);
3359 }
3360
3361 #[test]
3362 fn test_multiline_entry() {
3363 use super::SyntaxKind::*;
3364 use rowan::ast::AstNode;
3365
3366 let entry = super::Entry::new("foo", "bar\nbaz");
3367 let tokens: Vec<_> = entry
3368 .syntax()
3369 .descendants_with_tokens()
3370 .filter_map(|tok| tok.into_token())
3371 .collect();
3372
3373 assert_eq!("foo: bar\n baz\n", entry.to_string());
3374 assert_eq!("bar\nbaz", entry.value());
3375
3376 assert_eq!(
3377 vec![
3378 (KEY, "foo"),
3379 (COLON, ":"),
3380 (WHITESPACE, " "),
3381 (VALUE, "bar"),
3382 (NEWLINE, "\n"),
3383 (INDENT, " "),
3384 (VALUE, "baz"),
3385 (NEWLINE, "\n"),
3386 ],
3387 tokens
3388 .iter()
3389 .map(|token| (token.kind(), token.text()))
3390 .collect::<Vec<_>>()
3391 );
3392 }
3393
3394 #[test]
3395 fn test_apt_entry() {
3396 let text = r#"Package: cvsd
3397Binary: cvsd
3398Version: 1.0.24
3399Maintainer: Arthur de Jong <adejong@debian.org>
3400Build-Depends: debhelper (>= 9), po-debconf
3401Architecture: any
3402Standards-Version: 3.9.3
3403Format: 3.0 (native)
3404Files:
3405 b7a7d67a02974c52c408fdb5e118406d 890 cvsd_1.0.24.dsc
3406 b73ee40774c3086cb8490cdbb96ac883 258139 cvsd_1.0.24.tar.gz
3407Vcs-Browser: http://arthurdejong.org/viewvc/cvsd/
3408Vcs-Cvs: :pserver:anonymous@arthurdejong.org:/arthur/
3409Checksums-Sha256:
3410 a7bb7a3aacee19cd14ce5c26cb86e348b1608e6f1f6e97c6ea7c58efa440ac43 890 cvsd_1.0.24.dsc
3411 46bc517760c1070ae408693b89603986b53e6f068ae6bdc744e2e830e46b8cba 258139 cvsd_1.0.24.tar.gz
3412Homepage: http://arthurdejong.org/cvsd/
3413Package-List:
3414 cvsd deb vcs optional
3415Directory: pool/main/c/cvsd
3416Priority: source
3417Section: vcs
3418
3419"#;
3420 let d: super::Deb822 = text.parse().unwrap();
3421 let p = d.paragraphs().next().unwrap();
3422 assert_eq!(p.get("Binary").as_deref(), Some("cvsd"));
3423 assert_eq!(p.get("Version").as_deref(), Some("1.0.24"));
3424 assert_eq!(
3425 p.get("Maintainer").as_deref(),
3426 Some("Arthur de Jong <adejong@debian.org>")
3427 );
3428 }
3429
3430 #[test]
3431 fn test_format() {
3432 let d: super::Deb822 = r#"Source: foo
3433Maintainer: Foo Bar <foo@example.com>
3434Section: net
3435Blah: blah # comment
3436Multi-Line:
3437 Ahoi!
3438 Matey!
3439
3440"#
3441 .parse()
3442 .unwrap();
3443 let mut ps = d.paragraphs();
3444 let p = ps.next().unwrap();
3445 let result = p.wrap_and_sort(
3446 crate::Indentation::FieldNameLength,
3447 false,
3448 None,
3449 None::<&dyn Fn(&super::Entry, &super::Entry) -> std::cmp::Ordering>,
3450 None,
3451 );
3452 assert_eq!(
3453 result.to_string(),
3454 r#"Source: foo
3455Maintainer: Foo Bar <foo@example.com>
3456Section: net
3457Blah: blah # comment
3458Multi-Line: Ahoi!
3459 Matey!
3460"#
3461 );
3462 }
3463
3464 #[test]
3465 fn test_format_sort_paragraphs() {
3466 let d: super::Deb822 = r#"Source: foo
3467Maintainer: Foo Bar <foo@example.com>
3468
3469# This is a comment
3470Source: bar
3471Maintainer: Bar Foo <bar@example.com>
3472
3473"#
3474 .parse()
3475 .unwrap();
3476 let result = d.wrap_and_sort(
3477 Some(&|a: &super::Paragraph, b: &super::Paragraph| {
3478 a.get("Source").cmp(&b.get("Source"))
3479 }),
3480 Some(&|p| {
3481 p.wrap_and_sort(
3482 crate::Indentation::FieldNameLength,
3483 false,
3484 None,
3485 None::<&dyn Fn(&super::Entry, &super::Entry) -> std::cmp::Ordering>,
3486 None,
3487 )
3488 }),
3489 );
3490 assert_eq!(
3491 result.to_string(),
3492 r#"# This is a comment
3493Source: bar
3494Maintainer: Bar Foo <bar@example.com>
3495
3496Source: foo
3497Maintainer: Foo Bar <foo@example.com>
3498"#,
3499 );
3500 }
3501
3502 #[test]
3503 fn test_format_sort_fields() {
3504 let d: super::Deb822 = r#"Source: foo
3505Maintainer: Foo Bar <foo@example.com>
3506Build-Depends: debhelper (>= 9), po-debconf
3507Homepage: https://example.com/
3508
3509"#
3510 .parse()
3511 .unwrap();
3512 let result = d.wrap_and_sort(
3513 None,
3514 Some(&|p: &super::Paragraph| -> super::Paragraph {
3515 p.wrap_and_sort(
3516 crate::Indentation::FieldNameLength,
3517 false,
3518 None,
3519 Some(&|a: &super::Entry, b: &super::Entry| a.key().cmp(&b.key())),
3520 None,
3521 )
3522 }),
3523 );
3524 assert_eq!(
3525 result.to_string(),
3526 r#"Build-Depends: debhelper (>= 9), po-debconf
3527Homepage: https://example.com/
3528Maintainer: Foo Bar <foo@example.com>
3529Source: foo
3530"#
3531 );
3532 }
3533
3534 #[test]
3535 fn test_para_from_iter() {
3536 let p: super::Paragraph = vec![("Foo", "Bar"), ("Baz", "Qux")].into_iter().collect();
3537 assert_eq!(
3538 p.to_string(),
3539 r#"Foo: Bar
3540Baz: Qux
3541"#
3542 );
3543
3544 let p: super::Paragraph = vec![
3545 ("Foo".to_string(), "Bar".to_string()),
3546 ("Baz".to_string(), "Qux".to_string()),
3547 ]
3548 .into_iter()
3549 .collect();
3550
3551 assert_eq!(
3552 p.to_string(),
3553 r#"Foo: Bar
3554Baz: Qux
3555"#
3556 );
3557 }
3558
3559 #[test]
3560 fn test_deb822_from_iter() {
3561 let d: super::Deb822 = vec![
3562 vec![("Foo", "Bar"), ("Baz", "Qux")].into_iter().collect(),
3563 vec![("A", "B"), ("C", "D")].into_iter().collect(),
3564 ]
3565 .into_iter()
3566 .collect();
3567 assert_eq!(
3568 d.to_string(),
3569 r#"Foo: Bar
3570Baz: Qux
3571
3572A: B
3573C: D
3574"#
3575 );
3576 }
3577
3578 #[test]
3579 fn test_format_parse_error() {
3580 assert_eq!(ParseError(vec!["foo".to_string()]).to_string(), "foo\n");
3581 }
3582
3583 #[test]
3584 fn test_set_with_field_order() {
3585 let mut p = super::Paragraph::new();
3586 let custom_order = &["Foo", "Bar", "Baz"];
3587
3588 p.set_with_field_order("Baz", "3", custom_order);
3589 p.set_with_field_order("Foo", "1", custom_order);
3590 p.set_with_field_order("Bar", "2", custom_order);
3591 p.set_with_field_order("Unknown", "4", custom_order);
3592
3593 let keys: Vec<_> = p.keys().collect();
3594 assert_eq!(keys[0], "Foo");
3595 assert_eq!(keys[1], "Bar");
3596 assert_eq!(keys[2], "Baz");
3597 assert_eq!(keys[3], "Unknown");
3598 }
3599
3600 #[test]
3601 fn test_positioned_parse_error() {
3602 let error = PositionedParseError {
3603 message: "test error".to_string(),
3604 range: rowan::TextRange::new(rowan::TextSize::from(5), rowan::TextSize::from(10)),
3605 code: Some("test_code".to_string()),
3606 };
3607 assert_eq!(error.to_string(), "test error");
3608 assert_eq!(error.range.start(), rowan::TextSize::from(5));
3609 assert_eq!(error.range.end(), rowan::TextSize::from(10));
3610 assert_eq!(error.code, Some("test_code".to_string()));
3611 }
3612
3613 #[test]
3614 fn test_format_error() {
3615 assert_eq!(
3616 super::Error::ParseError(ParseError(vec!["foo".to_string()])).to_string(),
3617 "foo\n"
3618 );
3619 }
3620
3621 #[test]
3622 fn test_get_all() {
3623 let d: super::Deb822 = r#"Source: foo
3624Maintainer: Foo Bar <foo@example.com>
3625Maintainer: Bar Foo <bar@example.com>"#
3626 .parse()
3627 .unwrap();
3628 let p = d.paragraphs().next().unwrap();
3629 assert_eq!(
3630 p.get_all("Maintainer").collect::<Vec<_>>(),
3631 vec!["Foo Bar <foo@example.com>", "Bar Foo <bar@example.com>"]
3632 );
3633 }
3634
3635 #[test]
3636 fn test_get_with_indent_single_line() {
3637 let input = "Field: single line value\n";
3638 let deb = super::Deb822::from_str(input).unwrap();
3639 let para = deb.paragraphs().next().unwrap();
3640
3641 assert_eq!(
3643 para.get_with_indent("Field", &super::IndentPattern::Fixed(2)),
3644 Some("single line value".to_string())
3645 );
3646 assert_eq!(
3647 para.get_with_indent("Field", &super::IndentPattern::FieldNameLength),
3648 Some("single line value".to_string())
3649 );
3650 }
3651
3652 #[test]
3653 fn test_get_with_indent_fixed() {
3654 let input = "Field: First\n Second\n Third\n";
3655 let deb = super::Deb822::from_str(input).unwrap();
3656 let para = deb.paragraphs().next().unwrap();
3657
3658 let value = para
3660 .get_with_indent("Field", &super::IndentPattern::Fixed(2))
3661 .unwrap();
3662 assert_eq!(value, "First\n Second\n Third");
3663
3664 let value = para
3666 .get_with_indent("Field", &super::IndentPattern::Fixed(1))
3667 .unwrap();
3668 assert_eq!(value, "First\n Second\n Third");
3669
3670 let value = para
3672 .get_with_indent("Field", &super::IndentPattern::Fixed(3))
3673 .unwrap();
3674 assert_eq!(value, "First\nSecond\nThird");
3675 }
3676
3677 #[test]
3678 fn test_get_with_indent_field_name_length() {
3679 let input = "Description: First line\n Second line\n Third line\n";
3680 let deb = super::Deb822::from_str(input).unwrap();
3681 let para = deb.paragraphs().next().unwrap();
3682
3683 let value = para
3686 .get_with_indent("Description", &super::IndentPattern::FieldNameLength)
3687 .unwrap();
3688 assert_eq!(value, "First line\nSecond line\nThird line");
3689
3690 let value = para
3692 .get_with_indent("Description", &super::IndentPattern::Fixed(2))
3693 .unwrap();
3694 assert_eq!(
3695 value,
3696 "First line\n Second line\n Third line"
3697 );
3698 }
3699
3700 #[test]
3701 fn test_get_with_indent_nonexistent() {
3702 let input = "Field: value\n";
3703 let deb = super::Deb822::from_str(input).unwrap();
3704 let para = deb.paragraphs().next().unwrap();
3705
3706 assert_eq!(
3707 para.get_with_indent("NonExistent", &super::IndentPattern::Fixed(2)),
3708 None
3709 );
3710 }
3711
3712 #[test]
3713 fn test_get_entry() {
3714 let input = r#"Package: test-package
3715Maintainer: Test User <test@example.com>
3716Description: A simple test package
3717 with multiple lines
3718"#;
3719 let deb = super::Deb822::from_str(input).unwrap();
3720 let para = deb.paragraphs().next().unwrap();
3721
3722 let entry = para.get_entry("Package");
3724 assert!(entry.is_some());
3725 let entry = entry.unwrap();
3726 assert_eq!(entry.key(), Some("Package".to_string()));
3727 assert_eq!(entry.value(), "test-package");
3728
3729 let entry = para.get_entry("package");
3731 assert!(entry.is_some());
3732 assert_eq!(entry.unwrap().value(), "test-package");
3733
3734 let entry = para.get_entry("Description");
3736 assert!(entry.is_some());
3737 assert_eq!(
3738 entry.unwrap().value(),
3739 "A simple test package\nwith multiple lines"
3740 );
3741
3742 assert_eq!(para.get_entry("NonExistent"), None);
3744 }
3745
3746 #[test]
3747 fn test_entry_ranges() {
3748 let input = r#"Package: test-package
3749Maintainer: Test User <test@example.com>
3750Description: A simple test package
3751 with multiple lines
3752 of description text"#;
3753
3754 let deb822 = super::Deb822::from_str(input).unwrap();
3755 let paragraph = deb822.paragraphs().next().unwrap();
3756 let entries: Vec<_> = paragraph.entries().collect();
3757
3758 let package_entry = &entries[0];
3760 assert_eq!(package_entry.key(), Some("Package".to_string()));
3761
3762 let key_range = package_entry.key_range().unwrap();
3764 assert_eq!(
3765 &input[key_range.start().into()..key_range.end().into()],
3766 "Package"
3767 );
3768
3769 let colon_range = package_entry.colon_range().unwrap();
3771 assert_eq!(
3772 &input[colon_range.start().into()..colon_range.end().into()],
3773 ":"
3774 );
3775
3776 let value_range = package_entry.value_range().unwrap();
3778 assert_eq!(
3779 &input[value_range.start().into()..value_range.end().into()],
3780 "test-package"
3781 );
3782
3783 let text_range = package_entry.text_range();
3785 assert_eq!(
3786 &input[text_range.start().into()..text_range.end().into()],
3787 "Package: test-package\n"
3788 );
3789
3790 let value_lines = package_entry.value_line_ranges();
3792 assert_eq!(value_lines.len(), 1);
3793 assert_eq!(
3794 &input[value_lines[0].start().into()..value_lines[0].end().into()],
3795 "test-package"
3796 );
3797 }
3798
3799 #[test]
3800 fn test_multiline_entry_ranges() {
3801 let input = r#"Description: Short description
3802 Extended description line 1
3803 Extended description line 2"#;
3804
3805 let deb822 = super::Deb822::from_str(input).unwrap();
3806 let paragraph = deb822.paragraphs().next().unwrap();
3807 let entry = paragraph.entries().next().unwrap();
3808
3809 assert_eq!(entry.key(), Some("Description".to_string()));
3810
3811 let value_range = entry.value_range().unwrap();
3813 let full_value = &input[value_range.start().into()..value_range.end().into()];
3814 assert!(full_value.contains("Short description"));
3815 assert!(full_value.contains("Extended description line 1"));
3816 assert!(full_value.contains("Extended description line 2"));
3817
3818 let value_lines = entry.value_line_ranges();
3820 assert_eq!(value_lines.len(), 3);
3821
3822 assert_eq!(
3823 &input[value_lines[0].start().into()..value_lines[0].end().into()],
3824 "Short description"
3825 );
3826 assert_eq!(
3827 &input[value_lines[1].start().into()..value_lines[1].end().into()],
3828 "Extended description line 1"
3829 );
3830 assert_eq!(
3831 &input[value_lines[2].start().into()..value_lines[2].end().into()],
3832 "Extended description line 2"
3833 );
3834 }
3835
3836 #[test]
3837 fn test_entries_public_access() {
3838 let input = r#"Package: test
3839Version: 1.0"#;
3840
3841 let deb822 = super::Deb822::from_str(input).unwrap();
3842 let paragraph = deb822.paragraphs().next().unwrap();
3843
3844 let entries: Vec<_> = paragraph.entries().collect();
3846 assert_eq!(entries.len(), 2);
3847 assert_eq!(entries[0].key(), Some("Package".to_string()));
3848 assert_eq!(entries[1].key(), Some("Version".to_string()));
3849 }
3850
3851 #[test]
3852 fn test_empty_value_ranges() {
3853 let input = r#"EmptyField: "#;
3854
3855 let deb822 = super::Deb822::from_str(input).unwrap();
3856 let paragraph = deb822.paragraphs().next().unwrap();
3857 let entry = paragraph.entries().next().unwrap();
3858
3859 assert_eq!(entry.key(), Some("EmptyField".to_string()));
3860
3861 assert!(entry.key_range().is_some());
3863 assert!(entry.colon_range().is_some());
3864
3865 let value_lines = entry.value_line_ranges();
3867 assert!(value_lines.len() <= 1);
3870 }
3871
3872 #[test]
3873 fn test_range_ordering() {
3874 let input = r#"Field: value"#;
3875
3876 let deb822 = super::Deb822::from_str(input).unwrap();
3877 let paragraph = deb822.paragraphs().next().unwrap();
3878 let entry = paragraph.entries().next().unwrap();
3879
3880 let key_range = entry.key_range().unwrap();
3881 let colon_range = entry.colon_range().unwrap();
3882 let value_range = entry.value_range().unwrap();
3883 let text_range = entry.text_range();
3884
3885 assert!(key_range.end() <= colon_range.start());
3887 assert!(colon_range.end() <= value_range.start());
3888 assert!(key_range.start() >= text_range.start());
3889 assert!(value_range.end() <= text_range.end());
3890 }
3891
3892 #[test]
3893 fn test_error_recovery_missing_colon() {
3894 let input = r#"Source foo
3895Maintainer: Test User <test@example.com>
3896"#;
3897 let (deb822, errors) = super::Deb822::from_str_relaxed(input);
3898
3899 assert!(!errors.is_empty());
3901 assert!(errors.iter().any(|e| e.contains("missing colon")));
3902
3903 let paragraph = deb822.paragraphs().next().unwrap();
3905 assert_eq!(
3906 paragraph.get("Maintainer").as_deref(),
3907 Some("Test User <test@example.com>")
3908 );
3909 }
3910
3911 #[test]
3912 fn test_error_recovery_missing_field_name() {
3913 let input = r#": orphaned value
3914Package: test
3915"#;
3916
3917 let (deb822, errors) = super::Deb822::from_str_relaxed(input);
3918
3919 assert!(!errors.is_empty());
3921 assert!(errors
3922 .iter()
3923 .any(|e| e.contains("field name") || e.contains("missing")));
3924
3925 let paragraphs: Vec<_> = deb822.paragraphs().collect();
3927 let mut found_package = false;
3928 for paragraph in paragraphs.iter() {
3929 if paragraph.get("Package").is_some() {
3930 found_package = true;
3931 assert_eq!(paragraph.get("Package").as_deref(), Some("test"));
3932 }
3933 }
3934 assert!(found_package, "Package field not found in any paragraph");
3935 }
3936
3937 #[test]
3938 fn test_error_recovery_orphaned_text() {
3939 let input = r#"Package: test
3940some orphaned text without field name
3941Version: 1.0
3942"#;
3943 let (deb822, errors) = super::Deb822::from_str_relaxed(input);
3944
3945 assert!(!errors.is_empty());
3947 assert!(errors.iter().any(|e| e.contains("orphaned")
3948 || e.contains("unexpected")
3949 || e.contains("field name")));
3950
3951 let mut all_fields = std::collections::HashMap::new();
3953 for paragraph in deb822.paragraphs() {
3954 for (key, value) in paragraph.items() {
3955 all_fields.insert(key, value);
3956 }
3957 }
3958
3959 assert_eq!(all_fields.get("Package"), Some(&"test".to_string()));
3960 assert_eq!(all_fields.get("Version"), Some(&"1.0".to_string()));
3961 }
3962
3963 #[test]
3964 fn test_error_recovery_consecutive_field_names() {
3965 let input = r#"Package: test
3966Description
3967Maintainer: Another field without proper value
3968Version: 1.0
3969"#;
3970 let (deb822, errors) = super::Deb822::from_str_relaxed(input);
3971
3972 assert!(!errors.is_empty());
3974 assert!(errors.iter().any(|e| e.contains("consecutive")
3975 || e.contains("missing")
3976 || e.contains("incomplete")));
3977
3978 let mut all_fields = std::collections::HashMap::new();
3980 for paragraph in deb822.paragraphs() {
3981 for (key, value) in paragraph.items() {
3982 all_fields.insert(key, value);
3983 }
3984 }
3985
3986 assert_eq!(all_fields.get("Package"), Some(&"test".to_string()));
3987 assert_eq!(
3988 all_fields.get("Maintainer"),
3989 Some(&"Another field without proper value".to_string())
3990 );
3991 assert_eq!(all_fields.get("Version"), Some(&"1.0".to_string()));
3992 }
3993
3994 #[test]
3995 fn test_error_recovery_malformed_multiline() {
3996 let input = r#"Package: test
3997Description: Short desc
3998 Proper continuation
3999invalid continuation without indent
4000 Another proper continuation
4001Version: 1.0
4002"#;
4003 let (deb822, errors) = super::Deb822::from_str_relaxed(input);
4004
4005 assert!(!errors.is_empty());
4007
4008 let paragraph = deb822.paragraphs().next().unwrap();
4010 assert_eq!(paragraph.get("Package").as_deref(), Some("test"));
4011 assert_eq!(paragraph.get("Version").as_deref(), Some("1.0"));
4012 }
4013
4014 #[test]
4015 fn test_error_recovery_mixed_errors() {
4016 let input = r#"Package test without colon
4017: orphaned colon
4018Description: Valid field
4019some orphaned text
4020Another-Field: Valid too
4021"#;
4022 let (deb822, errors) = super::Deb822::from_str_relaxed(input);
4023
4024 assert!(!errors.is_empty());
4026 assert!(errors.len() >= 2);
4027
4028 let paragraph = deb822.paragraphs().next().unwrap();
4030 assert_eq!(paragraph.get("Description").as_deref(), Some("Valid field"));
4031 assert_eq!(paragraph.get("Another-Field").as_deref(), Some("Valid too"));
4032 }
4033
4034 #[test]
4035 fn test_error_recovery_paragraph_boundary() {
4036 let input = r#"Package: first-package
4037Description: First paragraph
4038
4039corrupted data here
4040: more corruption
4041completely broken line
4042
4043Package: second-package
4044Version: 1.0
4045"#;
4046 let (deb822, errors) = super::Deb822::from_str_relaxed(input);
4047
4048 assert!(!errors.is_empty());
4050
4051 let paragraphs: Vec<_> = deb822.paragraphs().collect();
4053 assert_eq!(paragraphs.len(), 2);
4054
4055 assert_eq!(
4056 paragraphs[0].get("Package").as_deref(),
4057 Some("first-package")
4058 );
4059 assert_eq!(
4060 paragraphs[1].get("Package").as_deref(),
4061 Some("second-package")
4062 );
4063 assert_eq!(paragraphs[1].get("Version").as_deref(), Some("1.0"));
4064 }
4065
4066 #[test]
4067 fn test_error_recovery_with_positioned_errors() {
4068 let input = r#"Package test
4069Description: Valid
4070"#;
4071 let parsed = super::parse(input);
4072
4073 assert!(!parsed.positioned_errors.is_empty());
4075
4076 let first_error = &parsed.positioned_errors[0];
4077 assert!(!first_error.message.is_empty());
4078 assert!(first_error.range.start() <= first_error.range.end());
4079 assert!(first_error.code.is_some());
4080
4081 let error_text = &input[first_error.range.start().into()..first_error.range.end().into()];
4083 assert!(!error_text.is_empty());
4084 }
4085
4086 #[test]
4087 fn test_error_recovery_preserves_whitespace() {
4088 let input = r#"Source: package
4089Maintainer Test User <test@example.com>
4090Section: utils
4091
4092"#;
4093 let (deb822, errors) = super::Deb822::from_str_relaxed(input);
4094
4095 assert!(!errors.is_empty());
4097
4098 let output = deb822.to_string();
4100 assert!(output.contains("Section: utils"));
4101
4102 let paragraph = deb822.paragraphs().next().unwrap();
4104 assert_eq!(paragraph.get("Source").as_deref(), Some("package"));
4105 assert_eq!(paragraph.get("Section").as_deref(), Some("utils"));
4106 }
4107
4108 #[test]
4109 fn test_error_recovery_empty_fields() {
4110 let input = r#"Package: test
4111Description:
4112Maintainer: Valid User
4113EmptyField:
4114Version: 1.0
4115"#;
4116 let (deb822, _errors) = super::Deb822::from_str_relaxed(input);
4117
4118 let mut all_fields = std::collections::HashMap::new();
4120 for paragraph in deb822.paragraphs() {
4121 for (key, value) in paragraph.items() {
4122 all_fields.insert(key, value);
4123 }
4124 }
4125
4126 assert_eq!(all_fields.get("Package"), Some(&"test".to_string()));
4127 assert_eq!(all_fields.get("Description"), Some(&"".to_string()));
4128 assert_eq!(
4129 all_fields.get("Maintainer"),
4130 Some(&"Valid User".to_string())
4131 );
4132 assert_eq!(all_fields.get("EmptyField"), Some(&"".to_string()));
4133 assert_eq!(all_fields.get("Version"), Some(&"1.0".to_string()));
4134 }
4135
4136 #[test]
4137 fn test_insert_comment_before() {
4138 let d: super::Deb822 = vec![
4139 vec![("Source", "foo"), ("Maintainer", "Bar <bar@example.com>")]
4140 .into_iter()
4141 .collect(),
4142 vec![("Package", "foo"), ("Architecture", "all")]
4143 .into_iter()
4144 .collect(),
4145 ]
4146 .into_iter()
4147 .collect();
4148
4149 let mut p1 = d.paragraphs().next().unwrap();
4151 p1.insert_comment_before("This is the source paragraph");
4152
4153 let mut p2 = d.paragraphs().nth(1).unwrap();
4155 p2.insert_comment_before("This is the binary paragraph");
4156
4157 let output = d.to_string();
4158 assert_eq!(
4159 output,
4160 r#"# This is the source paragraph
4161Source: foo
4162Maintainer: Bar <bar@example.com>
4163
4164# This is the binary paragraph
4165Package: foo
4166Architecture: all
4167"#
4168 );
4169 }
4170
4171 #[test]
4172 fn test_parse_continuation_with_colon() {
4173 let input = "Package: test\nDescription: short\n line: with colon\n";
4175 let result = input.parse::<Deb822>();
4176 assert!(result.is_ok());
4177
4178 let deb822 = result.unwrap();
4179 let para = deb822.paragraphs().next().unwrap();
4180 assert_eq!(para.get("Package").as_deref(), Some("test"));
4181 assert_eq!(
4182 para.get("Description").as_deref(),
4183 Some("short\nline: with colon")
4184 );
4185 }
4186
4187 #[test]
4188 fn test_parse_continuation_starting_with_colon() {
4189 let input = "Package: test\nDescription: short\n :value\n";
4191 let result = input.parse::<Deb822>();
4192 assert!(result.is_ok());
4193
4194 let deb822 = result.unwrap();
4195 let para = deb822.paragraphs().next().unwrap();
4196 assert_eq!(para.get("Package").as_deref(), Some("test"));
4197 assert_eq!(para.get("Description").as_deref(), Some("short\n:value"));
4198 }
4199
4200 #[test]
4201 fn test_normalize_field_spacing_single_space() {
4202 let input = "Field: value\n";
4204 let deb822 = input.parse::<Deb822>().unwrap();
4205 let mut para = deb822.paragraphs().next().unwrap();
4206
4207 para.normalize_field_spacing();
4208 assert_eq!(para.to_string(), "Field: value\n");
4209 }
4210
4211 #[test]
4212 fn test_normalize_field_spacing_extra_spaces() {
4213 let input = "Field: value\n";
4215 let deb822 = input.parse::<Deb822>().unwrap();
4216 let mut para = deb822.paragraphs().next().unwrap();
4217
4218 para.normalize_field_spacing();
4219 assert_eq!(para.to_string(), "Field: value\n");
4220 }
4221
4222 #[test]
4223 fn test_normalize_field_spacing_no_space() {
4224 let input = "Field:value\n";
4226 let deb822 = input.parse::<Deb822>().unwrap();
4227 let mut para = deb822.paragraphs().next().unwrap();
4228
4229 para.normalize_field_spacing();
4230 assert_eq!(para.to_string(), "Field: value\n");
4231 }
4232
4233 #[test]
4234 fn test_normalize_field_spacing_multiple_fields() {
4235 let input = "Field1: value1\nField2:value2\nField3: value3\n";
4237 let deb822 = input.parse::<Deb822>().unwrap();
4238 let mut para = deb822.paragraphs().next().unwrap();
4239
4240 para.normalize_field_spacing();
4241 assert_eq!(
4242 para.to_string(),
4243 "Field1: value1\nField2: value2\nField3: value3\n"
4244 );
4245 }
4246
4247 #[test]
4248 fn test_normalize_field_spacing_multiline_value() {
4249 let input = "Description: short\n continuation line\n . \n final line\n";
4251 let deb822 = input.parse::<Deb822>().unwrap();
4252 let mut para = deb822.paragraphs().next().unwrap();
4253
4254 para.normalize_field_spacing();
4255 assert_eq!(
4256 para.to_string(),
4257 "Description: short\n continuation line\n . \n final line\n"
4258 );
4259 }
4260
4261 #[test]
4262 fn test_normalize_field_spacing_empty_value_with_whitespace() {
4263 let input = "Field: \n";
4265 let deb822 = input.parse::<Deb822>().unwrap();
4266 let mut para = deb822.paragraphs().next().unwrap();
4267
4268 para.normalize_field_spacing();
4269 assert_eq!(para.to_string(), "Field:\n");
4271 }
4272
4273 #[test]
4274 fn test_normalize_field_spacing_no_value() {
4275 let input = "Depends:\n";
4277 let deb822 = input.parse::<Deb822>().unwrap();
4278 let mut para = deb822.paragraphs().next().unwrap();
4279
4280 para.normalize_field_spacing();
4281 assert_eq!(para.to_string(), "Depends:\n");
4283 }
4284
4285 #[test]
4286 fn test_normalize_field_spacing_multiple_paragraphs() {
4287 let input = "Field1: value1\n\nField2: value2\n";
4289 let mut deb822 = input.parse::<Deb822>().unwrap();
4290
4291 deb822.normalize_field_spacing();
4292 assert_eq!(deb822.to_string(), "Field1: value1\n\nField2: value2\n");
4293 }
4294
4295 #[test]
4296 fn test_normalize_field_spacing_preserves_comments() {
4297 let input = "# Comment\nField: value\n";
4299 let mut deb822 = input.parse::<Deb822>().unwrap();
4300
4301 deb822.normalize_field_spacing();
4302 assert_eq!(deb822.to_string(), "# Comment\nField: value\n");
4303 }
4304
4305 #[test]
4306 fn test_normalize_field_spacing_preserves_values() {
4307 let input = "Source: foo-bar\nMaintainer:Foo Bar <test@example.com>\n";
4309 let deb822 = input.parse::<Deb822>().unwrap();
4310 let mut para = deb822.paragraphs().next().unwrap();
4311
4312 para.normalize_field_spacing();
4313
4314 assert_eq!(para.get("Source").as_deref(), Some("foo-bar"));
4315 assert_eq!(
4316 para.get("Maintainer").as_deref(),
4317 Some("Foo Bar <test@example.com>")
4318 );
4319 }
4320
4321 #[test]
4322 fn test_normalize_field_spacing_tab_after_colon() {
4323 let input = "Field:\tvalue\n";
4325 let deb822 = input.parse::<Deb822>().unwrap();
4326 let mut para = deb822.paragraphs().next().unwrap();
4327
4328 para.normalize_field_spacing();
4329 assert_eq!(para.to_string(), "Field: value\n");
4330 }
4331
4332 #[test]
4333 fn test_set_preserves_indentation() {
4334 let original = r#"Source: example
4336Build-Depends: foo,
4337 bar,
4338 baz
4339"#;
4340
4341 let mut para: super::Paragraph = original.parse().unwrap();
4342
4343 para.set("Build-Depends", "foo,\nbar,\nbaz");
4345
4346 let expected = r#"Source: example
4348Build-Depends: foo,
4349 bar,
4350 baz
4351"#;
4352 assert_eq!(para.to_string(), expected);
4353 }
4354
4355 #[test]
4356 fn test_set_new_field_detects_field_name_length_indent() {
4357 let original = r#"Source: example
4359Build-Depends: foo,
4360 bar,
4361 baz
4362Depends: lib1,
4363 lib2
4364"#;
4365
4366 let mut para: super::Paragraph = original.parse().unwrap();
4367
4368 para.set("Recommends", "pkg1,\npkg2,\npkg3");
4370
4371 assert!(para
4373 .to_string()
4374 .contains("Recommends: pkg1,\n pkg2,"));
4375 }
4376
4377 #[test]
4378 fn test_set_new_field_detects_fixed_indent() {
4379 let original = r#"Source: example
4381Build-Depends: foo,
4382 bar,
4383 baz
4384Depends: lib1,
4385 lib2
4386"#;
4387
4388 let mut para: super::Paragraph = original.parse().unwrap();
4389
4390 para.set("Recommends", "pkg1,\npkg2,\npkg3");
4392
4393 assert!(para
4395 .to_string()
4396 .contains("Recommends: pkg1,\n pkg2,\n pkg3\n"));
4397 }
4398
4399 #[test]
4400 fn test_set_new_field_no_multiline_fields() {
4401 let original = r#"Source: example
4403Maintainer: Test <test@example.com>
4404"#;
4405
4406 let mut para: super::Paragraph = original.parse().unwrap();
4407
4408 para.set("Depends", "foo,\nbar,\nbaz");
4410
4411 let expected = r#"Source: example
4413Maintainer: Test <test@example.com>
4414Depends: foo,
4415 bar,
4416 baz
4417"#;
4418 assert_eq!(para.to_string(), expected);
4419 }
4420
4421 #[test]
4422 fn test_set_new_field_mixed_indentation() {
4423 let original = r#"Source: example
4425Build-Depends: foo,
4426 bar
4427Depends: lib1,
4428 lib2
4429"#;
4430
4431 let mut para: super::Paragraph = original.parse().unwrap();
4432
4433 para.set("Recommends", "pkg1,\npkg2");
4435
4436 assert!(para
4438 .to_string()
4439 .contains("Recommends: pkg1,\n pkg2\n"));
4440 }
4441
4442 #[test]
4443 fn test_entry_with_indentation() {
4444 let entry = super::Entry::with_indentation("Test-Field", "value1\nvalue2\nvalue3", " ");
4446
4447 assert_eq!(
4448 entry.to_string(),
4449 "Test-Field: value1\n value2\n value3\n"
4450 );
4451 }
4452
4453 #[test]
4454 fn test_set_with_indent_pattern_fixed() {
4455 let original = r#"Source: example
4457Maintainer: Test <test@example.com>
4458"#;
4459
4460 let mut para: super::Paragraph = original.parse().unwrap();
4461
4462 para.set_with_indent_pattern(
4464 "Depends",
4465 "foo,\nbar,\nbaz",
4466 Some(&super::IndentPattern::Fixed(4)),
4467 None,
4468 );
4469
4470 let expected = r#"Source: example
4472Maintainer: Test <test@example.com>
4473Depends: foo,
4474 bar,
4475 baz
4476"#;
4477 assert_eq!(para.to_string(), expected);
4478 }
4479
4480 #[test]
4481 fn test_set_with_indent_pattern_field_name_length() {
4482 let original = r#"Source: example
4484Maintainer: Test <test@example.com>
4485"#;
4486
4487 let mut para: super::Paragraph = original.parse().unwrap();
4488
4489 para.set_with_indent_pattern(
4491 "Build-Depends",
4492 "libfoo,\nlibbar,\nlibbaz",
4493 Some(&super::IndentPattern::FieldNameLength),
4494 None,
4495 );
4496
4497 let expected = r#"Source: example
4499Maintainer: Test <test@example.com>
4500Build-Depends: libfoo,
4501 libbar,
4502 libbaz
4503"#;
4504 assert_eq!(para.to_string(), expected);
4505 }
4506
4507 #[test]
4508 fn test_set_with_indent_pattern_override_auto_detection() {
4509 let original = r#"Source: example
4511Build-Depends: foo,
4512 bar,
4513 baz
4514"#;
4515
4516 let mut para: super::Paragraph = original.parse().unwrap();
4517
4518 para.set_with_indent_pattern(
4520 "Depends",
4521 "lib1,\nlib2,\nlib3",
4522 Some(&super::IndentPattern::Fixed(2)),
4523 None,
4524 );
4525
4526 let expected = r#"Source: example
4528Build-Depends: foo,
4529 bar,
4530 baz
4531Depends: lib1,
4532 lib2,
4533 lib3
4534"#;
4535 assert_eq!(para.to_string(), expected);
4536 }
4537
4538 #[test]
4539 fn test_set_with_indent_pattern_none_auto_detects() {
4540 let original = r#"Source: example
4542Build-Depends: foo,
4543 bar,
4544 baz
4545"#;
4546
4547 let mut para: super::Paragraph = original.parse().unwrap();
4548
4549 para.set_with_indent_pattern("Depends", "lib1,\nlib2", None, None);
4551
4552 let expected = r#"Source: example
4554Build-Depends: foo,
4555 bar,
4556 baz
4557Depends: lib1,
4558 lib2
4559"#;
4560 assert_eq!(para.to_string(), expected);
4561 }
4562
4563 #[test]
4564 fn test_set_with_indent_pattern_with_field_order() {
4565 let original = r#"Source: example
4567Maintainer: Test <test@example.com>
4568"#;
4569
4570 let mut para: super::Paragraph = original.parse().unwrap();
4571
4572 para.set_with_indent_pattern(
4574 "Priority",
4575 "optional",
4576 Some(&super::IndentPattern::Fixed(4)),
4577 Some(&["Source", "Priority", "Maintainer"]),
4578 );
4579
4580 let expected = r#"Source: example
4582Priority: optional
4583Maintainer: Test <test@example.com>
4584"#;
4585 assert_eq!(para.to_string(), expected);
4586 }
4587
4588 #[test]
4589 fn test_set_with_indent_pattern_replace_existing() {
4590 let original = r#"Source: example
4592Depends: foo,
4593 bar
4594"#;
4595
4596 let mut para: super::Paragraph = original.parse().unwrap();
4597
4598 para.set_with_indent_pattern(
4600 "Depends",
4601 "lib1,\nlib2,\nlib3",
4602 Some(&super::IndentPattern::Fixed(3)),
4603 None,
4604 );
4605
4606 let expected = r#"Source: example
4608Depends: lib1,
4609 lib2,
4610 lib3
4611"#;
4612 assert_eq!(para.to_string(), expected);
4613 }
4614
4615 #[test]
4616 fn test_change_field_indent() {
4617 let original = r#"Source: example
4619Depends: foo,
4620 bar,
4621 baz
4622"#;
4623 let mut para: super::Paragraph = original.parse().unwrap();
4624
4625 let result = para
4627 .change_field_indent("Depends", &super::IndentPattern::Fixed(2))
4628 .unwrap();
4629 assert!(result, "Field should have been found and updated");
4630
4631 let expected = r#"Source: example
4632Depends: foo,
4633 bar,
4634 baz
4635"#;
4636 assert_eq!(para.to_string(), expected);
4637 }
4638
4639 #[test]
4640 fn test_change_field_indent_nonexistent() {
4641 let original = r#"Source: example
4643"#;
4644 let mut para: super::Paragraph = original.parse().unwrap();
4645
4646 let result = para
4648 .change_field_indent("Depends", &super::IndentPattern::Fixed(2))
4649 .unwrap();
4650 assert!(!result, "Should return false for non-existent field");
4651
4652 assert_eq!(para.to_string(), original);
4654 }
4655
4656 #[test]
4657 fn test_change_field_indent_case_insensitive() {
4658 let original = r#"Build-Depends: foo,
4660 bar
4661"#;
4662 let mut para: super::Paragraph = original.parse().unwrap();
4663
4664 let result = para
4666 .change_field_indent("build-depends", &super::IndentPattern::Fixed(1))
4667 .unwrap();
4668 assert!(result, "Should find field case-insensitively");
4669
4670 let expected = r#"Build-Depends: foo,
4671 bar
4672"#;
4673 assert_eq!(para.to_string(), expected);
4674 }
4675
4676 #[test]
4677 fn test_entry_get_indent() {
4678 let original = r#"Build-Depends: foo,
4680 bar,
4681 baz
4682"#;
4683 let para: super::Paragraph = original.parse().unwrap();
4684 let entry = para.entries().next().unwrap();
4685
4686 assert_eq!(entry.get_indent(), Some(" ".to_string()));
4687 }
4688
4689 #[test]
4690 fn test_entry_get_indent_single_line() {
4691 let original = r#"Source: example
4693"#;
4694 let para: super::Paragraph = original.parse().unwrap();
4695 let entry = para.entries().next().unwrap();
4696
4697 assert_eq!(entry.get_indent(), None);
4698 }
4699}
4700
4701#[test]
4702fn test_move_paragraph_forward() {
4703 let mut d: Deb822 = vec![
4704 vec![("Foo", "Bar"), ("Baz", "Qux")].into_iter().collect(),
4705 vec![("A", "B"), ("C", "D")].into_iter().collect(),
4706 vec![("X", "Y"), ("Z", "W")].into_iter().collect(),
4707 ]
4708 .into_iter()
4709 .collect();
4710 d.move_paragraph(0, 2);
4711 assert_eq!(
4712 d.to_string(),
4713 "A: B\nC: D\n\nX: Y\nZ: W\n\nFoo: Bar\nBaz: Qux\n"
4714 );
4715}
4716
4717#[test]
4718fn test_move_paragraph_backward() {
4719 let mut d: Deb822 = vec![
4720 vec![("Foo", "Bar"), ("Baz", "Qux")].into_iter().collect(),
4721 vec![("A", "B"), ("C", "D")].into_iter().collect(),
4722 vec![("X", "Y"), ("Z", "W")].into_iter().collect(),
4723 ]
4724 .into_iter()
4725 .collect();
4726 d.move_paragraph(2, 0);
4727 assert_eq!(
4728 d.to_string(),
4729 "X: Y\nZ: W\n\nFoo: Bar\nBaz: Qux\n\nA: B\nC: D\n"
4730 );
4731}
4732
4733#[test]
4734fn test_move_paragraph_middle() {
4735 let mut d: Deb822 = vec![
4736 vec![("Foo", "Bar"), ("Baz", "Qux")].into_iter().collect(),
4737 vec![("A", "B"), ("C", "D")].into_iter().collect(),
4738 vec![("X", "Y"), ("Z", "W")].into_iter().collect(),
4739 ]
4740 .into_iter()
4741 .collect();
4742 d.move_paragraph(2, 1);
4743 assert_eq!(
4744 d.to_string(),
4745 "Foo: Bar\nBaz: Qux\n\nX: Y\nZ: W\n\nA: B\nC: D\n"
4746 );
4747}
4748
4749#[test]
4750fn test_move_paragraph_same_index() {
4751 let mut d: Deb822 = vec![
4752 vec![("Foo", "Bar"), ("Baz", "Qux")].into_iter().collect(),
4753 vec![("A", "B"), ("C", "D")].into_iter().collect(),
4754 ]
4755 .into_iter()
4756 .collect();
4757 let original = d.to_string();
4758 d.move_paragraph(1, 1);
4759 assert_eq!(d.to_string(), original);
4760}
4761
4762#[test]
4763fn test_move_paragraph_single() {
4764 let mut d: Deb822 = vec![vec![("Foo", "Bar")].into_iter().collect()]
4765 .into_iter()
4766 .collect();
4767 let original = d.to_string();
4768 d.move_paragraph(0, 0);
4769 assert_eq!(d.to_string(), original);
4770}
4771
4772#[test]
4773fn test_move_paragraph_invalid_index() {
4774 let mut d: Deb822 = vec![
4775 vec![("Foo", "Bar")].into_iter().collect(),
4776 vec![("A", "B")].into_iter().collect(),
4777 ]
4778 .into_iter()
4779 .collect();
4780 let original = d.to_string();
4781 d.move_paragraph(0, 5);
4782 assert_eq!(d.to_string(), original);
4783}
4784
4785#[test]
4786fn test_move_paragraph_with_comments() {
4787 let text = r#"Foo: Bar
4788
4789# This is a comment
4790
4791A: B
4792
4793X: Y
4794"#;
4795 let mut d: Deb822 = text.parse().unwrap();
4796 d.move_paragraph(0, 2);
4797 assert_eq!(
4798 d.to_string(),
4799 "# This is a comment\n\nA: B\n\nX: Y\n\nFoo: Bar\n"
4800 );
4801}
4802
4803#[test]
4804fn test_case_insensitive_get() {
4805 let text = "Package: test\nVersion: 1.0\n";
4806 let d: Deb822 = text.parse().unwrap();
4807 let p = d.paragraphs().next().unwrap();
4808
4809 assert_eq!(p.get("Package").as_deref(), Some("test"));
4811 assert_eq!(p.get("package").as_deref(), Some("test"));
4812 assert_eq!(p.get("PACKAGE").as_deref(), Some("test"));
4813 assert_eq!(p.get("PaCkAgE").as_deref(), Some("test"));
4814
4815 assert_eq!(p.get("Version").as_deref(), Some("1.0"));
4816 assert_eq!(p.get("version").as_deref(), Some("1.0"));
4817 assert_eq!(p.get("VERSION").as_deref(), Some("1.0"));
4818}
4819
4820#[test]
4821fn test_case_insensitive_set() {
4822 let text = "Package: test\n";
4823 let d: Deb822 = text.parse().unwrap();
4824 let mut p = d.paragraphs().next().unwrap();
4825
4826 p.set("package", "updated");
4828 assert_eq!(p.get("Package").as_deref(), Some("updated"));
4829 assert_eq!(p.get("package").as_deref(), Some("updated"));
4830
4831 p.set("PACKAGE", "updated2");
4833 assert_eq!(p.get("Package").as_deref(), Some("updated2"));
4834
4835 assert_eq!(p.keys().count(), 1);
4837}
4838
4839#[test]
4840fn test_case_insensitive_remove() {
4841 let text = "Package: test\nVersion: 1.0\n";
4842 let d: Deb822 = text.parse().unwrap();
4843 let mut p = d.paragraphs().next().unwrap();
4844
4845 p.remove("package");
4847 assert_eq!(p.get("Package"), None);
4848 assert_eq!(p.get("Version").as_deref(), Some("1.0"));
4849
4850 p.remove("VERSION");
4852 assert_eq!(p.get("Version"), None);
4853
4854 assert_eq!(p.keys().count(), 0);
4856}
4857
4858#[test]
4859fn test_case_preservation() {
4860 let text = "Package: test\n";
4861 let d: Deb822 = text.parse().unwrap();
4862 let mut p = d.paragraphs().next().unwrap();
4863
4864 let original_text = d.to_string();
4866 assert_eq!(original_text, "Package: test\n");
4867
4868 p.set("package", "updated");
4870
4871 let updated_text = d.to_string();
4873 assert_eq!(updated_text, "Package: updated\n");
4874}
4875
4876#[test]
4877fn test_case_insensitive_contains_key() {
4878 let text = "Package: test\n";
4879 let d: Deb822 = text.parse().unwrap();
4880 let p = d.paragraphs().next().unwrap();
4881
4882 assert!(p.contains_key("Package"));
4883 assert!(p.contains_key("package"));
4884 assert!(p.contains_key("PACKAGE"));
4885 assert!(!p.contains_key("NonExistent"));
4886}
4887
4888#[test]
4889fn test_case_insensitive_get_all() {
4890 let text = "Package: test1\npackage: test2\n";
4891 let d: Deb822 = text.parse().unwrap();
4892 let p = d.paragraphs().next().unwrap();
4893
4894 let values: Vec<String> = p.get_all("PACKAGE").collect();
4895 assert_eq!(values, vec!["test1", "test2"]);
4896}
4897
4898#[test]
4899fn test_case_insensitive_rename() {
4900 let text = "Package: test\n";
4901 let d: Deb822 = text.parse().unwrap();
4902 let mut p = d.paragraphs().next().unwrap();
4903
4904 assert!(p.rename("package", "NewName"));
4906 assert_eq!(p.get("NewName").as_deref(), Some("test"));
4907 assert_eq!(p.get("Package"), None);
4908}
4909
4910#[test]
4911fn test_rename_changes_case() {
4912 let text = "Package: test\n";
4913 let d: Deb822 = text.parse().unwrap();
4914 let mut p = d.paragraphs().next().unwrap();
4915
4916 assert!(p.rename("package", "PACKAGE"));
4918
4919 let updated_text = d.to_string();
4921 assert_eq!(updated_text, "PACKAGE: test\n");
4922
4923 assert_eq!(p.get("package").as_deref(), Some("test"));
4925 assert_eq!(p.get("Package").as_deref(), Some("test"));
4926 assert_eq!(p.get("PACKAGE").as_deref(), Some("test"));
4927}
4928
4929#[test]
4930fn test_reject_whitespace_only_continuation_line() {
4931 let text = "Build-Depends:\n \ndebhelper\n";
4937 let parsed = Deb822::parse(text);
4938
4939 assert!(
4942 !parsed.errors().is_empty(),
4943 "Expected parse errors for whitespace-only continuation line"
4944 );
4945}
4946
4947#[test]
4948fn test_reject_empty_continuation_line_in_multiline_field() {
4949 let text = "Depends: foo,\n bar,\n \n baz\n";
4951 let parsed = Deb822::parse(text);
4952
4953 assert!(
4955 !parsed.errors().is_empty(),
4956 "Empty continuation line should generate parse errors"
4957 );
4958
4959 let has_empty_line_error = parsed
4961 .errors()
4962 .iter()
4963 .any(|e| e.contains("empty continuation line"));
4964 assert!(
4965 has_empty_line_error,
4966 "Should have an error about empty continuation line"
4967 );
4968}
4969
4970#[test]
4971#[should_panic(expected = "empty continuation line")]
4972fn test_set_rejects_empty_continuation_lines() {
4973 let text = "Package: test\n";
4975 let deb822 = text.parse::<Deb822>().unwrap();
4976 let mut para = deb822.paragraphs().next().unwrap();
4977
4978 let value_with_empty_line = "foo\n \nbar";
4981 para.set("Depends", value_with_empty_line);
4982}
4983
4984#[test]
4985fn test_try_set_returns_error_for_empty_continuation_lines() {
4986 let text = "Package: test\n";
4988 let deb822 = text.parse::<Deb822>().unwrap();
4989 let mut para = deb822.paragraphs().next().unwrap();
4990
4991 let value_with_empty_line = "foo\n \nbar";
4993 let result = para.try_set("Depends", value_with_empty_line);
4994
4995 assert!(
4997 result.is_err(),
4998 "try_set() should return an error for empty continuation lines"
4999 );
5000
5001 match result {
5003 Err(Error::InvalidValue(msg)) => {
5004 assert!(
5005 msg.contains("empty continuation line"),
5006 "Error message should mention empty continuation line"
5007 );
5008 }
5009 _ => panic!("Expected InvalidValue error"),
5010 }
5011}
5012
5013#[test]
5014fn test_try_set_with_indent_pattern_returns_error() {
5015 let text = "Package: test\n";
5017 let deb822 = text.parse::<Deb822>().unwrap();
5018 let mut para = deb822.paragraphs().next().unwrap();
5019
5020 let value_with_empty_line = "foo\n \nbar";
5021 let result = para.try_set_with_indent_pattern(
5022 "Depends",
5023 value_with_empty_line,
5024 Some(&IndentPattern::Fixed(2)),
5025 None,
5026 );
5027
5028 assert!(
5029 result.is_err(),
5030 "try_set_with_indent_pattern() should return an error"
5031 );
5032}
5033
5034#[test]
5035fn test_try_set_succeeds_for_valid_value() {
5036 let text = "Package: test\n";
5038 let deb822 = text.parse::<Deb822>().unwrap();
5039 let mut para = deb822.paragraphs().next().unwrap();
5040
5041 let valid_value = "foo\nbar";
5043 let result = para.try_set("Depends", valid_value);
5044
5045 assert!(result.is_ok(), "try_set() should succeed for valid values");
5046 assert_eq!(para.get("Depends").as_deref(), Some("foo\nbar"));
5047}
5048
5049#[test]
5050fn test_field_with_empty_first_line() {
5051 let text = "Foo:\n blah\n blah\n";
5054 let parsed = Deb822::parse(text);
5055
5056 assert!(
5058 parsed.errors().is_empty(),
5059 "Empty first line should be valid. Got errors: {:?}",
5060 parsed.errors()
5061 );
5062
5063 let deb822 = parsed.tree();
5064 let para = deb822.paragraphs().next().unwrap();
5065 assert_eq!(para.get("Foo").as_deref(), Some("blah\nblah"));
5066}
5067
5068#[test]
5069fn test_try_set_with_empty_first_line() {
5070 let text = "Package: test\n";
5072 let deb822 = text.parse::<Deb822>().unwrap();
5073 let mut para = deb822.paragraphs().next().unwrap();
5074
5075 let value = "\nblah\nmore";
5077 let result = para.try_set("Depends", value);
5078
5079 assert!(
5080 result.is_ok(),
5081 "try_set() should succeed for values with empty first line. Got: {:?}",
5082 result
5083 );
5084}
5085
5086#[test]
5087fn test_field_with_value_then_empty_continuation() {
5088 let text = "Foo: bar\n \n";
5090 let parsed = Deb822::parse(text);
5091
5092 assert!(
5094 !parsed.errors().is_empty(),
5095 "Field with value then empty continuation line should be rejected"
5096 );
5097
5098 let has_empty_line_error = parsed
5100 .errors()
5101 .iter()
5102 .any(|e| e.contains("empty continuation line"));
5103 assert!(
5104 has_empty_line_error,
5105 "Should have error about empty continuation line"
5106 );
5107}
5108
5109#[test]
5110fn test_line_col() {
5111 let text = r#"Source: foo
5112Maintainer: Foo Bar <jelmer@jelmer.uk>
5113Section: net
5114
5115Package: foo
5116Architecture: all
5117Depends: libc6
5118Description: This is a description
5119 With details
5120"#;
5121 let deb822 = text.parse::<Deb822>().unwrap();
5122
5123 let paras: Vec<_> = deb822.paragraphs().collect();
5125 assert_eq!(paras.len(), 2);
5126
5127 assert_eq!(paras[0].line(), 0);
5129 assert_eq!(paras[0].column(), 0);
5130
5131 assert_eq!(paras[1].line(), 4);
5133 assert_eq!(paras[1].column(), 0);
5134
5135 let entries: Vec<_> = paras[0].entries().collect();
5137 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));
5147 assert_eq!(entries[0].line_col(), (0, 0));
5148
5149 let second_para_entries: Vec<_> = paras[1].entries().collect();
5151 assert_eq!(second_para_entries[3].line(), 7); }
5153
5154#[test]
5155fn test_deb822_snapshot_independence() {
5156 let text = r#"Source: foo
5158Maintainer: Joe <joe@example.com>
5159
5160Package: foo
5161Architecture: all
5162"#;
5163 let deb822 = text.parse::<Deb822>().unwrap();
5164 let snapshot = deb822.snapshot();
5165
5166 let mut para = deb822.paragraphs().next().unwrap();
5168 para.set("Source", "modified");
5169
5170 let snapshot_para = snapshot.paragraphs().next().unwrap();
5172 assert_eq!(snapshot_para.get("Source").as_deref(), Some("foo"));
5173
5174 let mut snapshot_para = snapshot.paragraphs().next().unwrap();
5176 snapshot_para.set("Source", "snapshot-modified");
5177
5178 let para = deb822.paragraphs().next().unwrap();
5180 assert_eq!(para.get("Source").as_deref(), Some("modified"));
5181}
5182
5183#[test]
5184fn test_paragraph_snapshot_independence() {
5185 let text = "Package: foo\nArchitecture: all\n";
5187 let deb822 = text.parse::<Deb822>().unwrap();
5188 let mut para = deb822.paragraphs().next().unwrap();
5189 let mut snapshot = para.snapshot();
5190
5191 para.set("Package", "modified");
5193
5194 assert_eq!(snapshot.get("Package").as_deref(), Some("foo"));
5196
5197 snapshot.set("Package", "snapshot-modified");
5199
5200 assert_eq!(para.get("Package").as_deref(), Some("modified"));
5202}
5203
5204#[test]
5205fn test_entry_snapshot_independence() {
5206 let text = "Package: foo\n";
5208 let deb822 = text.parse::<Deb822>().unwrap();
5209 let mut para = deb822.paragraphs().next().unwrap();
5210 let entry = para.entries().next().unwrap();
5211 let snapshot = entry.snapshot();
5212
5213 let original_value = entry.value();
5215 let snapshot_value = snapshot.value();
5216
5217 assert_eq!(original_value, "foo");
5219 assert_eq!(snapshot_value, "foo");
5220
5221 para.set("Package", "modified");
5223
5224 let entry_after = para.entries().next().unwrap();
5226 assert_eq!(entry_after.value(), "modified");
5227
5228 assert_eq!(snapshot.value(), "foo");
5231}
5232
5233#[test]
5234fn test_snapshot_preserves_structure() {
5235 let text = r#"# Comment
5237Source: foo
5238## Another comment
5239Maintainer: Joe <joe@example.com>
5240
5241Package: foo
5242Architecture: all
5243"#;
5244 let deb822 = text.parse::<Deb822>().unwrap();
5245 let snapshot = deb822.snapshot();
5246
5247 assert_eq!(deb822.to_string(), snapshot.to_string());
5249
5250 let mut snapshot_para = snapshot.paragraphs().next().unwrap();
5252 snapshot_para.set("Source", "modified");
5253
5254 let original_para = deb822.paragraphs().next().unwrap();
5255 assert_eq!(original_para.get("Source").as_deref(), Some("foo"));
5256}
5257
5258#[test]
5259fn test_paragraph_text_range() {
5260 let text = r#"Source: foo
5262Maintainer: Joe <joe@example.com>
5263
5264Package: foo
5265Architecture: all
5266"#;
5267 let deb822 = text.parse::<Deb822>().unwrap();
5268 let paras: Vec<_> = deb822.paragraphs().collect();
5269
5270 let range1 = paras[0].text_range();
5272 let para1_text = &text[range1.start().into()..range1.end().into()];
5273 assert_eq!(
5274 para1_text,
5275 "Source: foo\nMaintainer: Joe <joe@example.com>\n"
5276 );
5277
5278 let range2 = paras[1].text_range();
5280 let para2_text = &text[range2.start().into()..range2.end().into()];
5281 assert_eq!(para2_text, "Package: foo\nArchitecture: all\n");
5282}
5283
5284#[test]
5285fn test_paragraphs_in_range_single() {
5286 let text = r#"Source: foo
5288
5289Package: bar
5290
5291Package: baz
5292"#;
5293 let deb822 = text.parse::<Deb822>().unwrap();
5294
5295 let first_para = deb822.paragraphs().next().unwrap();
5297 let range = first_para.text_range();
5298
5299 let paras: Vec<_> = deb822.paragraphs_in_range(range).collect();
5301 assert_eq!(paras.len(), 1);
5302 assert_eq!(paras[0].get("Source").as_deref(), Some("foo"));
5303}
5304
5305#[test]
5306fn test_paragraphs_in_range_multiple() {
5307 let text = r#"Source: foo
5309
5310Package: bar
5311
5312Package: baz
5313"#;
5314 let deb822 = text.parse::<Deb822>().unwrap();
5315
5316 let range = rowan::TextRange::new(0.into(), 25.into());
5318
5319 let paras: Vec<_> = deb822.paragraphs_in_range(range).collect();
5321 assert_eq!(paras.len(), 2);
5322 assert_eq!(paras[0].get("Source").as_deref(), Some("foo"));
5323 assert_eq!(paras[1].get("Package").as_deref(), Some("bar"));
5324}
5325
5326#[test]
5327fn test_paragraphs_in_range_partial_overlap() {
5328 let text = r#"Source: foo
5330
5331Package: bar
5332
5333Package: baz
5334"#;
5335 let deb822 = text.parse::<Deb822>().unwrap();
5336
5337 let range = rowan::TextRange::new(15.into(), 30.into());
5339
5340 let paras: Vec<_> = deb822.paragraphs_in_range(range).collect();
5342 assert!(paras.len() >= 1);
5343 assert!(paras
5344 .iter()
5345 .any(|p| p.get("Package").as_deref() == Some("bar")));
5346}
5347
5348#[test]
5349fn test_paragraphs_in_range_no_match() {
5350 let text = r#"Source: foo
5352
5353Package: bar
5354"#;
5355 let deb822 = text.parse::<Deb822>().unwrap();
5356
5357 let range = rowan::TextRange::new(1000.into(), 2000.into());
5359
5360 let paras: Vec<_> = deb822.paragraphs_in_range(range).collect();
5362 assert_eq!(paras.len(), 0);
5363}
5364
5365#[test]
5366fn test_paragraphs_in_range_all() {
5367 let text = r#"Source: foo
5369
5370Package: bar
5371
5372Package: baz
5373"#;
5374 let deb822 = text.parse::<Deb822>().unwrap();
5375
5376 let range = rowan::TextRange::new(0.into(), text.len().try_into().unwrap());
5378
5379 let paras: Vec<_> = deb822.paragraphs_in_range(range).collect();
5381 assert_eq!(paras.len(), 3);
5382}
5383
5384#[test]
5385fn test_paragraph_at_position() {
5386 let text = r#"Package: foo
5388Version: 1.0
5389
5390Package: bar
5391Architecture: all
5392"#;
5393 let deb822 = text.parse::<Deb822>().unwrap();
5394
5395 let para = deb822.paragraph_at_position(rowan::TextSize::from(5));
5397 assert!(para.is_some());
5398 assert_eq!(para.unwrap().get("Package").as_deref(), Some("foo"));
5399
5400 let para = deb822.paragraph_at_position(rowan::TextSize::from(30));
5402 assert!(para.is_some());
5403 assert_eq!(para.unwrap().get("Package").as_deref(), Some("bar"));
5404
5405 let para = deb822.paragraph_at_position(rowan::TextSize::from(1000));
5407 assert!(para.is_none());
5408}
5409
5410#[test]
5411fn test_paragraph_at_line() {
5412 let text = r#"Package: foo
5414Version: 1.0
5415
5416Package: bar
5417Architecture: all
5418"#;
5419 let deb822 = text.parse::<Deb822>().unwrap();
5420
5421 let para = deb822.paragraph_at_line(0);
5423 assert!(para.is_some());
5424 assert_eq!(para.unwrap().get("Package").as_deref(), Some("foo"));
5425
5426 let para = deb822.paragraph_at_line(1);
5428 assert!(para.is_some());
5429 assert_eq!(para.unwrap().get("Package").as_deref(), Some("foo"));
5430
5431 let para = deb822.paragraph_at_line(3);
5433 assert!(para.is_some());
5434 assert_eq!(para.unwrap().get("Package").as_deref(), Some("bar"));
5435
5436 let para = deb822.paragraph_at_line(100);
5438 assert!(para.is_none());
5439}
5440
5441#[test]
5442fn test_entry_at_line_col() {
5443 let text = r#"Package: foo
5445Version: 1.0
5446Architecture: all
5447"#;
5448 let deb822 = text.parse::<Deb822>().unwrap();
5449
5450 let entry = deb822.entry_at_line_col(0, 0);
5452 assert!(entry.is_some());
5453 assert_eq!(entry.unwrap().key(), Some("Package".to_string()));
5454
5455 let entry = deb822.entry_at_line_col(1, 0);
5457 assert!(entry.is_some());
5458 assert_eq!(entry.unwrap().key(), Some("Version".to_string()));
5459
5460 let entry = deb822.entry_at_line_col(2, 5);
5462 assert!(entry.is_some());
5463 assert_eq!(entry.unwrap().key(), Some("Architecture".to_string()));
5464
5465 let entry = deb822.entry_at_line_col(100, 0);
5467 assert!(entry.is_none());
5468}
5469
5470#[test]
5471fn test_entry_at_line_col_multiline() {
5472 let text = r#"Package: foo
5474Description: A package
5475 with a long
5476 description
5477Version: 1.0
5478"#;
5479 let deb822 = text.parse::<Deb822>().unwrap();
5480
5481 let entry = deb822.entry_at_line_col(1, 0);
5483 assert!(entry.is_some());
5484 assert_eq!(entry.unwrap().key(), Some("Description".to_string()));
5485
5486 let entry = deb822.entry_at_line_col(2, 1);
5488 assert!(entry.is_some());
5489 assert_eq!(entry.unwrap().key(), Some("Description".to_string()));
5490
5491 let entry = deb822.entry_at_line_col(3, 1);
5493 assert!(entry.is_some());
5494 assert_eq!(entry.unwrap().key(), Some("Description".to_string()));
5495
5496 let entry = deb822.entry_at_line_col(4, 0);
5498 assert!(entry.is_some());
5499 assert_eq!(entry.unwrap().key(), Some("Version".to_string()));
5500}
5501
5502#[test]
5503fn test_entries_in_range() {
5504 let text = r#"Package: foo
5506Version: 1.0
5507Architecture: all
5508"#;
5509 let deb822 = text.parse::<Deb822>().unwrap();
5510 let para = deb822.paragraphs().next().unwrap();
5511
5512 let first_entry = para.entries().next().unwrap();
5514 let range = first_entry.text_range();
5515
5516 let entries: Vec<_> = para.entries_in_range(range).collect();
5518 assert_eq!(entries.len(), 1);
5519 assert_eq!(entries[0].key(), Some("Package".to_string()));
5520
5521 let range = rowan::TextRange::new(0.into(), 25.into());
5523 let entries: Vec<_> = para.entries_in_range(range).collect();
5524 assert_eq!(entries.len(), 2);
5525 assert_eq!(entries[0].key(), Some("Package".to_string()));
5526 assert_eq!(entries[1].key(), Some("Version".to_string()));
5527}
5528
5529#[test]
5530fn test_entries_in_range_partial_overlap() {
5531 let text = r#"Package: foo
5533Version: 1.0
5534Architecture: all
5535"#;
5536 let deb822 = text.parse::<Deb822>().unwrap();
5537 let para = deb822.paragraphs().next().unwrap();
5538
5539 let range = rowan::TextRange::new(15.into(), 30.into());
5541
5542 let entries: Vec<_> = para.entries_in_range(range).collect();
5543 assert!(entries.len() >= 1);
5544 assert!(entries
5545 .iter()
5546 .any(|e| e.key() == Some("Version".to_string())));
5547}
5548
5549#[test]
5550fn test_entries_in_range_no_match() {
5551 let text = "Package: foo\n";
5553 let deb822 = text.parse::<Deb822>().unwrap();
5554 let para = deb822.paragraphs().next().unwrap();
5555
5556 let range = rowan::TextRange::new(1000.into(), 2000.into());
5558 let entries: Vec<_> = para.entries_in_range(range).collect();
5559 assert_eq!(entries.len(), 0);
5560}
5561
5562#[test]
5563fn test_entry_at_position() {
5564 let text = r#"Package: foo
5566Version: 1.0
5567Architecture: all
5568"#;
5569 let deb822 = text.parse::<Deb822>().unwrap();
5570 let para = deb822.paragraphs().next().unwrap();
5571
5572 let entry = para.entry_at_position(rowan::TextSize::from(5));
5574 assert!(entry.is_some());
5575 assert_eq!(entry.unwrap().key(), Some("Package".to_string()));
5576
5577 let entry = para.entry_at_position(rowan::TextSize::from(15));
5579 assert!(entry.is_some());
5580 assert_eq!(entry.unwrap().key(), Some("Version".to_string()));
5581
5582 let entry = para.entry_at_position(rowan::TextSize::from(1000));
5584 assert!(entry.is_none());
5585}
5586
5587#[test]
5588fn test_entry_at_position_multiline() {
5589 let text = r#"Description: A package
5591 with a long
5592 description
5593"#;
5594 let deb822 = text.parse::<Deb822>().unwrap();
5595 let para = deb822.paragraphs().next().unwrap();
5596
5597 let entry = para.entry_at_position(rowan::TextSize::from(5));
5599 assert!(entry.is_some());
5600 assert_eq!(entry.unwrap().key(), Some("Description".to_string()));
5601
5602 let entry = para.entry_at_position(rowan::TextSize::from(30));
5604 assert!(entry.is_some());
5605 assert_eq!(entry.unwrap().key(), Some("Description".to_string()));
5606}
5607
5608#[test]
5609fn test_paragraph_at_position_at_boundary() {
5610 let text = "Package: foo\n\nPackage: bar\n";
5612 let deb822 = text.parse::<Deb822>().unwrap();
5613
5614 let para = deb822.paragraph_at_position(rowan::TextSize::from(0));
5616 assert!(para.is_some());
5617 assert_eq!(para.unwrap().get("Package").as_deref(), Some("foo"));
5618
5619 let para = deb822.paragraph_at_position(rowan::TextSize::from(15));
5621 assert!(para.is_some());
5622 assert_eq!(para.unwrap().get("Package").as_deref(), Some("bar"));
5623}