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 if self.current() == Some(COMMENT) {
381 self.bump();
385 } else {
386 break;
387 }
388 }
389
390 self.builder.finish_node();
391
392 if entry_has_errors && !self.at_paragraph_start() && self.current().is_some() {
394 self.recover_entry();
395 }
396 }
397
398 fn parse_paragraph(&mut self) {
399 self.builder.start_node(PARAGRAPH.into());
400
401 let mut consecutive_errors = 0;
402 const MAX_CONSECUTIVE_ERRORS: usize = 5;
403
404 while self.current() != Some(NEWLINE) && self.current().is_some() {
405 let error_count_before = self.positioned_errors.len();
406
407 if self.current() == Some(KEY) || self.current() == Some(COMMENT) {
409 self.parse_entry();
410
411 if self.positioned_errors.len() == error_count_before {
413 consecutive_errors = 0;
414 } else {
415 consecutive_errors += 1;
416 }
417 } else {
418 consecutive_errors += 1;
420
421 self.builder.start_node(ERROR.into());
422 match self.current() {
423 Some(VALUE) => {
424 self.add_positioned_error(
425 "orphaned text without field name".to_string(),
426 Some("orphaned_text".to_string()),
427 );
428 while self.current() == Some(VALUE)
430 || self.current() == Some(WHITESPACE)
431 {
432 self.bump();
433 }
434 }
435 Some(COLON) => {
436 self.add_positioned_error(
437 "orphaned colon without field name".to_string(),
438 Some("orphaned_colon".to_string()),
439 );
440 self.bump();
441 }
442 Some(INDENT) => {
443 self.add_positioned_error(
444 "unexpected indentation without field".to_string(),
445 Some("unexpected_indent".to_string()),
446 );
447 self.bump();
448 }
449 _ => {
450 self.add_positioned_error(
451 format!(
452 "unexpected token at paragraph level: {:?}",
453 self.current()
454 ),
455 Some("unexpected_paragraph_token".to_string()),
456 );
457 self.bump();
458 }
459 }
460 self.builder.finish_node();
461 }
462
463 if consecutive_errors >= MAX_CONSECUTIVE_ERRORS {
465 self.add_positioned_error(
466 "too many consecutive parse errors, skipping to next paragraph".to_string(),
467 Some("parse_recovery".to_string()),
468 );
469 self.skip_to_paragraph_boundary();
470 break;
471 }
472 }
473
474 self.builder.finish_node();
475 }
476
477 fn parse(mut self) -> Parse {
478 self.builder.start_node(ROOT.into());
480 while self.current().is_some() {
481 self.skip_ws_and_newlines();
482 if self.current().is_some() {
483 self.parse_paragraph();
484 }
485 }
486 self.skip_ws_and_newlines();
488 self.builder.finish_node();
490
491 Parse {
493 green_node: self.builder.finish(),
494 errors: self.errors,
495 positioned_errors: self.positioned_errors,
496 }
497 }
498 fn bump(&mut self) {
500 let (kind, text) = self.tokens.pop().unwrap();
501 self.builder.token(kind.into(), text);
502 if self.current_token_index > 0 {
503 self.current_token_index -= 1;
504 }
505 }
506 fn current(&self) -> Option<SyntaxKind> {
508 self.tokens.last().map(|(kind, _)| *kind)
509 }
510
511 fn add_positioned_error(&mut self, message: String, code: Option<String>) {
513 let range = if self.current_token_index < self.token_positions.len() {
514 let (_, start, end) = self.token_positions[self.current_token_index];
515 rowan::TextRange::new(start, end)
516 } else {
517 let end = self
519 .token_positions
520 .last()
521 .map(|(_, _, end)| *end)
522 .unwrap_or_else(|| rowan::TextSize::from(0));
523 rowan::TextRange::new(end, end)
524 };
525
526 self.positioned_errors.push(PositionedParseError {
527 message: message.clone(),
528 range,
529 code,
530 });
531 self.errors.push(message);
532 }
533 fn skip_ws(&mut self) {
534 while self.current() == Some(WHITESPACE) || self.current() == Some(COMMENT) {
535 self.bump()
536 }
537 }
538 fn skip_ws_and_newlines(&mut self) {
539 while self.current() == Some(WHITESPACE)
540 || self.current() == Some(COMMENT)
541 || self.current() == Some(NEWLINE)
542 {
543 self.builder.start_node(EMPTY_LINE.into());
544 while self.current() != Some(NEWLINE) && self.current().is_some() {
545 self.bump();
546 }
547 if self.current() == Some(NEWLINE) {
548 self.bump();
549 }
550 self.builder.finish_node();
551 }
552 }
553 }
554
555 let mut tokens = lex(text).collect::<Vec<_>>();
556
557 let mut token_positions = Vec::new();
559 let mut position = rowan::TextSize::from(0);
560 for (kind, text) in &tokens {
561 let start = position;
562 let end = start + rowan::TextSize::of(*text);
563 token_positions.push((*kind, start, end));
564 position = end;
565 }
566
567 tokens.reverse();
569 let current_token_index = tokens.len().saturating_sub(1);
570
571 Parser {
572 tokens,
573 builder: GreenNodeBuilder::new(),
574 errors: Vec::new(),
575 positioned_errors: Vec::new(),
576 token_positions,
577 current_token_index,
578 }
579 .parse()
580}
581
582type SyntaxNode = rowan::SyntaxNode<Lang>;
588#[allow(unused)]
589type SyntaxToken = rowan::SyntaxToken<Lang>;
590#[allow(unused)]
591type SyntaxElement = rowan::NodeOrToken<SyntaxNode, SyntaxToken>;
592
593impl Parse {
594 #[cfg(test)]
595 fn syntax(&self) -> SyntaxNode {
596 SyntaxNode::new_root(self.green_node.clone())
597 }
598
599 fn root_mut(&self) -> Deb822 {
600 Deb822::cast(SyntaxNode::new_root_mut(self.green_node.clone())).unwrap()
601 }
602}
603
604fn line_col_at_offset(node: &SyntaxNode, offset: rowan::TextSize) -> (usize, usize) {
607 let root = node.ancestors().last().unwrap_or_else(|| node.clone());
608 let mut line = 0;
609 let mut last_newline_offset = rowan::TextSize::from(0);
610
611 for element in root.preorder_with_tokens() {
612 if let rowan::WalkEvent::Enter(rowan::NodeOrToken::Token(token)) = element {
613 if token.text_range().start() >= offset {
614 break;
615 }
616
617 for (idx, _) in token.text().match_indices('\n') {
619 line += 1;
620 last_newline_offset =
621 token.text_range().start() + rowan::TextSize::from((idx + 1) as u32);
622 }
623 }
624 }
625
626 let column: usize = (offset - last_newline_offset).into();
627 (line, column)
628}
629
630macro_rules! ast_node {
631 ($ast:ident, $kind:ident) => {
632 #[doc = "An AST node representing a `"]
633 #[doc = stringify!($ast)]
634 #[doc = "`."]
635 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
636 #[repr(transparent)]
637 pub struct $ast(SyntaxNode);
638 impl $ast {
639 #[allow(unused)]
640 fn cast(node: SyntaxNode) -> Option<Self> {
641 if node.kind() == $kind {
642 Some(Self(node))
643 } else {
644 None
645 }
646 }
647
648 pub fn line(&self) -> usize {
650 line_col_at_offset(&self.0, self.0.text_range().start()).0
651 }
652
653 pub fn column(&self) -> usize {
655 line_col_at_offset(&self.0, self.0.text_range().start()).1
656 }
657
658 pub fn line_col(&self) -> (usize, usize) {
661 line_col_at_offset(&self.0, self.0.text_range().start())
662 }
663 }
664
665 impl AstNode for $ast {
666 type Language = Lang;
667
668 fn can_cast(kind: SyntaxKind) -> bool {
669 kind == $kind
670 }
671
672 fn cast(syntax: SyntaxNode) -> Option<Self> {
673 Self::cast(syntax)
674 }
675
676 fn syntax(&self) -> &SyntaxNode {
677 &self.0
678 }
679 }
680
681 impl std::fmt::Display for $ast {
682 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
683 write!(f, "{}", self.0.text())
684 }
685 }
686 };
687}
688
689ast_node!(Deb822, ROOT);
690ast_node!(Paragraph, PARAGRAPH);
691ast_node!(Entry, ENTRY);
692
693impl Default for Deb822 {
694 fn default() -> Self {
695 Self::new()
696 }
697}
698
699impl Deb822 {
700 pub fn snapshot(&self) -> Self {
725 Deb822(SyntaxNode::new_root_mut(self.0.green().into_owned()))
726 }
727
728 pub fn new() -> Deb822 {
730 let mut builder = GreenNodeBuilder::new();
731
732 builder.start_node(ROOT.into());
733 builder.finish_node();
734 Deb822(SyntaxNode::new_root_mut(builder.finish()))
735 }
736
737 pub fn parse(text: &str) -> crate::Parse<Deb822> {
739 crate::Parse::parse_deb822(text)
740 }
741
742 #[must_use]
758 pub fn wrap_and_sort(
759 &self,
760 sort_paragraphs: Option<&dyn Fn(&Paragraph, &Paragraph) -> std::cmp::Ordering>,
761 wrap_and_sort_paragraph: Option<&dyn Fn(&Paragraph) -> Paragraph>,
762 ) -> Deb822 {
763 let mut builder = GreenNodeBuilder::new();
764 builder.start_node(ROOT.into());
765 let mut current = vec![];
766 let mut paragraphs = vec![];
767 for c in self.0.children_with_tokens() {
768 match c.kind() {
769 PARAGRAPH => {
770 paragraphs.push((
771 current,
772 Paragraph::cast(c.as_node().unwrap().clone()).unwrap(),
773 ));
774 current = vec![];
775 }
776 COMMENT | ERROR => {
777 current.push(c);
778 }
779 EMPTY_LINE => {
780 current.extend(
781 c.as_node()
782 .unwrap()
783 .children_with_tokens()
784 .skip_while(|c| matches!(c.kind(), EMPTY_LINE | NEWLINE | WHITESPACE)),
785 );
786 }
787 _ => {}
788 }
789 }
790 if let Some(sort_paragraph) = sort_paragraphs {
791 paragraphs.sort_by(|a, b| {
792 let a_key = &a.1;
793 let b_key = &b.1;
794 sort_paragraph(a_key, b_key)
795 });
796 }
797
798 for (i, paragraph) in paragraphs.into_iter().enumerate() {
799 if i > 0 {
800 builder.start_node(EMPTY_LINE.into());
801 builder.token(NEWLINE.into(), "\n");
802 builder.finish_node();
803 }
804 for c in paragraph.0.into_iter() {
805 builder.token(c.kind().into(), c.as_token().unwrap().text());
806 }
807 let new_paragraph = if let Some(ref ws) = wrap_and_sort_paragraph {
808 ws(¶graph.1)
809 } else {
810 paragraph.1
811 };
812 inject(&mut builder, new_paragraph.0);
813 }
814
815 for c in current {
816 builder.token(c.kind().into(), c.as_token().unwrap().text());
817 }
818
819 builder.finish_node();
820 Self(SyntaxNode::new_root_mut(builder.finish()))
821 }
822
823 pub fn normalize_field_spacing(&mut self) -> bool {
842 let mut any_changed = false;
843
844 let mut paragraphs: Vec<_> = self.paragraphs().collect();
846
847 for para in &mut paragraphs {
849 if para.normalize_field_spacing() {
850 any_changed = true;
851 }
852 }
853
854 any_changed
855 }
856
857 pub fn paragraphs(&self) -> impl Iterator<Item = Paragraph> {
859 self.0.children().filter_map(Paragraph::cast)
860 }
861
862 pub fn paragraphs_in_range(
888 &self,
889 range: rowan::TextRange,
890 ) -> impl Iterator<Item = Paragraph> + '_ {
891 self.paragraphs().filter(move |p| {
892 let para_range = p.text_range();
893 para_range.start() < range.end() && para_range.end() > range.start()
895 })
896 }
897
898 pub fn paragraph_at_position(&self, offset: rowan::TextSize) -> Option<Paragraph> {
921 self.paragraphs().find(|p| {
922 let range = p.text_range();
923 range.contains(offset)
924 })
925 }
926
927 pub fn paragraph_at_line(&self, line: usize) -> Option<Paragraph> {
950 self.paragraphs().find(|p| {
951 let start_line = p.line();
952 let range = p.text_range();
953 let text_str = self.0.text().to_string();
954 let text_before_end = &text_str[..range.end().into()];
955 let end_line = text_before_end.lines().count().saturating_sub(1);
956 line >= start_line && line <= end_line
957 })
958 }
959
960 pub fn entry_at_line_col(&self, line: usize, col: usize) -> Option<Entry> {
984 let text_str = self.0.text().to_string();
986 let offset: usize = text_str.lines().take(line).map(|l| l.len() + 1).sum();
987 let position = rowan::TextSize::from((offset + col) as u32);
988
989 for para in self.paragraphs() {
991 for entry in para.entries() {
992 let range = entry.text_range();
993 if range.contains(position) {
994 return Some(entry);
995 }
996 }
997 }
998 None
999 }
1000
1001 fn convert_index(&self, index: usize) -> Option<usize> {
1003 let mut current_pos = 0usize;
1004 if index == 0 {
1005 return Some(0);
1006 }
1007 for (i, node) in self.0.children_with_tokens().enumerate() {
1008 if node.kind() == PARAGRAPH {
1009 if current_pos == index {
1010 return Some(i);
1011 }
1012 current_pos += 1;
1013 }
1014 }
1015
1016 None
1017 }
1018
1019 fn delete_trailing_space(&self, start: usize) {
1021 for (i, node) in self.0.children_with_tokens().enumerate() {
1022 if i < start {
1023 continue;
1024 }
1025 if node.kind() != EMPTY_LINE {
1026 return;
1027 }
1028 self.0.splice_children(start..start + 1, []);
1031 }
1032 }
1033
1034 fn insert_empty_paragraph(&mut self, index: Option<usize>) -> Paragraph {
1036 let paragraph = Paragraph::new();
1037 let mut to_insert = vec![];
1038 if self.0.children().count() > 0 {
1039 let mut builder = GreenNodeBuilder::new();
1040 builder.start_node(EMPTY_LINE.into());
1041 builder.token(NEWLINE.into(), "\n");
1042 builder.finish_node();
1043 to_insert.push(SyntaxNode::new_root_mut(builder.finish()).into());
1044 }
1045 to_insert.push(paragraph.0.clone().into());
1046 let insertion_point = match index {
1047 Some(i) => {
1048 if to_insert.len() > 1 {
1049 to_insert.swap(0, 1);
1050 }
1051 i
1052 }
1053 None => self.0.children().count(),
1054 };
1055 self.0
1056 .splice_children(insertion_point..insertion_point, to_insert);
1057 paragraph
1058 }
1059
1060 pub fn insert_paragraph(&mut self, index: usize) -> Paragraph {
1080 self.insert_empty_paragraph(self.convert_index(index))
1081 }
1082
1083 pub fn remove_paragraph(&mut self, index: usize) {
1101 if let Some(index) = self.convert_index(index) {
1102 self.0.splice_children(index..index + 1, []);
1103 self.delete_trailing_space(index);
1104 }
1105 }
1106
1107 pub fn move_paragraph(&mut self, from_index: usize, to_index: usize) {
1127 if from_index == to_index {
1128 return;
1129 }
1130
1131 let paragraph_count = self.paragraphs().count();
1133 if from_index >= paragraph_count || to_index >= paragraph_count {
1134 return;
1135 }
1136
1137 let paragraph_to_move = self.paragraphs().nth(from_index).unwrap().0.clone();
1139
1140 let from_physical = self.convert_index(from_index).unwrap();
1142
1143 let mut start_idx = from_physical;
1145 if from_physical > 0 {
1146 if let Some(prev_node) = self.0.children_with_tokens().nth(from_physical - 1) {
1147 if prev_node.kind() == EMPTY_LINE {
1148 start_idx = from_physical - 1;
1149 }
1150 }
1151 }
1152
1153 self.0.splice_children(start_idx..from_physical + 1, []);
1155 self.delete_trailing_space(start_idx);
1156
1157 let insert_at = if to_index > from_index {
1161 let target_idx = to_index - 1;
1164 if let Some(target_physical) = self.convert_index(target_idx) {
1165 target_physical + 1
1166 } else {
1167 self.0.children().count()
1169 }
1170 } else {
1171 if let Some(target_physical) = self.convert_index(to_index) {
1174 target_physical
1175 } else {
1176 self.0.children().count()
1177 }
1178 };
1179
1180 let mut to_insert = vec![];
1182
1183 let needs_empty_line_before = if insert_at == 0 {
1185 false
1187 } else if insert_at > 0 {
1188 if let Some(node_at_insert) = self.0.children_with_tokens().nth(insert_at - 1) {
1190 node_at_insert.kind() != EMPTY_LINE
1191 } else {
1192 false
1193 }
1194 } else {
1195 false
1196 };
1197
1198 if needs_empty_line_before {
1199 let mut builder = GreenNodeBuilder::new();
1200 builder.start_node(EMPTY_LINE.into());
1201 builder.token(NEWLINE.into(), "\n");
1202 builder.finish_node();
1203 to_insert.push(SyntaxNode::new_root_mut(builder.finish()).into());
1204 }
1205
1206 to_insert.push(paragraph_to_move.into());
1207
1208 let needs_empty_line_after = if insert_at < self.0.children().count() {
1210 if let Some(node_after) = self.0.children_with_tokens().nth(insert_at) {
1212 node_after.kind() != EMPTY_LINE
1213 } else {
1214 false
1215 }
1216 } else {
1217 false
1218 };
1219
1220 if needs_empty_line_after {
1221 let mut builder = GreenNodeBuilder::new();
1222 builder.start_node(EMPTY_LINE.into());
1223 builder.token(NEWLINE.into(), "\n");
1224 builder.finish_node();
1225 to_insert.push(SyntaxNode::new_root_mut(builder.finish()).into());
1226 }
1227
1228 self.0.splice_children(insert_at..insert_at, to_insert);
1230 }
1231
1232 pub fn add_paragraph(&mut self) -> Paragraph {
1234 self.insert_empty_paragraph(None)
1235 }
1236
1237 pub fn swap_paragraphs(&mut self, index1: usize, index2: usize) {
1267 if index1 == index2 {
1268 return;
1269 }
1270
1271 let mut children: Vec<_> = self.0.children().map(|n| n.clone().into()).collect();
1273
1274 let mut para_child_indices = vec![];
1276 for (child_idx, child) in self.0.children().enumerate() {
1277 if child.kind() == PARAGRAPH {
1278 para_child_indices.push(child_idx);
1279 }
1280 }
1281
1282 if index1 >= para_child_indices.len() {
1284 panic!("index1 {} out of bounds", index1);
1285 }
1286 if index2 >= para_child_indices.len() {
1287 panic!("index2 {} out of bounds", index2);
1288 }
1289
1290 let child_idx1 = para_child_indices[index1];
1291 let child_idx2 = para_child_indices[index2];
1292
1293 children.swap(child_idx1, child_idx2);
1295
1296 let num_children = children.len();
1298 self.0.splice_children(0..num_children, children);
1299 }
1300
1301 pub fn from_file(path: impl AsRef<Path>) -> Result<Self, Error> {
1303 let text = std::fs::read_to_string(path)?;
1304 Ok(Self::from_str(&text)?)
1305 }
1306
1307 pub fn from_file_relaxed(
1309 path: impl AsRef<Path>,
1310 ) -> Result<(Self, Vec<String>), std::io::Error> {
1311 let text = std::fs::read_to_string(path)?;
1312 Ok(Self::from_str_relaxed(&text))
1313 }
1314
1315 pub fn from_str_relaxed(s: &str) -> (Self, Vec<String>) {
1317 let parsed = parse(s);
1318 (parsed.root_mut(), parsed.errors)
1319 }
1320
1321 pub fn read<R: std::io::Read>(mut r: R) -> Result<Self, Error> {
1323 let mut buf = String::new();
1324 r.read_to_string(&mut buf)?;
1325 Ok(Self::from_str(&buf)?)
1326 }
1327
1328 pub fn read_relaxed<R: std::io::Read>(mut r: R) -> Result<(Self, Vec<String>), std::io::Error> {
1330 let mut buf = String::new();
1331 r.read_to_string(&mut buf)?;
1332 Ok(Self::from_str_relaxed(&buf))
1333 }
1334}
1335
1336fn inject(builder: &mut GreenNodeBuilder, node: SyntaxNode) {
1337 builder.start_node(node.kind().into());
1338 for child in node.children_with_tokens() {
1339 match child {
1340 rowan::NodeOrToken::Node(child) => {
1341 inject(builder, child);
1342 }
1343 rowan::NodeOrToken::Token(token) => {
1344 builder.token(token.kind().into(), token.text());
1345 }
1346 }
1347 }
1348 builder.finish_node();
1349}
1350
1351impl FromIterator<Paragraph> for Deb822 {
1352 fn from_iter<T: IntoIterator<Item = Paragraph>>(iter: T) -> Self {
1353 let mut builder = GreenNodeBuilder::new();
1354 builder.start_node(ROOT.into());
1355 for (i, paragraph) in iter.into_iter().enumerate() {
1356 if i > 0 {
1357 builder.start_node(EMPTY_LINE.into());
1358 builder.token(NEWLINE.into(), "\n");
1359 builder.finish_node();
1360 }
1361 inject(&mut builder, paragraph.0);
1362 }
1363 builder.finish_node();
1364 Self(SyntaxNode::new_root_mut(builder.finish()))
1365 }
1366}
1367
1368impl From<Vec<(String, String)>> for Paragraph {
1369 fn from(v: Vec<(String, String)>) -> Self {
1370 v.into_iter().collect()
1371 }
1372}
1373
1374impl From<Vec<(&str, &str)>> for Paragraph {
1375 fn from(v: Vec<(&str, &str)>) -> Self {
1376 v.into_iter().collect()
1377 }
1378}
1379
1380impl FromIterator<(String, String)> for Paragraph {
1381 fn from_iter<T: IntoIterator<Item = (String, String)>>(iter: T) -> Self {
1382 let mut builder = GreenNodeBuilder::new();
1383 builder.start_node(PARAGRAPH.into());
1384 for (key, value) in iter {
1385 builder.start_node(ENTRY.into());
1386 builder.token(KEY.into(), &key);
1387 builder.token(COLON.into(), ":");
1388 builder.token(WHITESPACE.into(), " ");
1389 for (i, line) in value.split('\n').enumerate() {
1390 if i > 0 {
1391 builder.token(INDENT.into(), " ");
1392 }
1393 builder.token(VALUE.into(), line);
1394 builder.token(NEWLINE.into(), "\n");
1395 }
1396 builder.finish_node();
1397 }
1398 builder.finish_node();
1399 Self(SyntaxNode::new_root_mut(builder.finish()))
1400 }
1401}
1402
1403impl<'a> FromIterator<(&'a str, &'a str)> for Paragraph {
1404 fn from_iter<T: IntoIterator<Item = (&'a str, &'a str)>>(iter: T) -> Self {
1405 let mut builder = GreenNodeBuilder::new();
1406 builder.start_node(PARAGRAPH.into());
1407 for (key, value) in iter {
1408 builder.start_node(ENTRY.into());
1409 builder.token(KEY.into(), key);
1410 builder.token(COLON.into(), ":");
1411 builder.token(WHITESPACE.into(), " ");
1412 for (i, line) in value.split('\n').enumerate() {
1413 if i > 0 {
1414 builder.token(INDENT.into(), " ");
1415 }
1416 builder.token(VALUE.into(), line);
1417 builder.token(NEWLINE.into(), "\n");
1418 }
1419 builder.finish_node();
1420 }
1421 builder.finish_node();
1422 Self(SyntaxNode::new_root_mut(builder.finish()))
1423 }
1424}
1425
1426#[derive(Debug, Clone, PartialEq, Eq)]
1428pub enum IndentPattern {
1429 Fixed(usize),
1431 FieldNameLength,
1433}
1434
1435impl IndentPattern {
1436 fn to_string(&self, field_name: &str) -> String {
1438 match self {
1439 IndentPattern::Fixed(spaces) => " ".repeat(*spaces),
1440 IndentPattern::FieldNameLength => " ".repeat(field_name.len() + 2),
1441 }
1442 }
1443}
1444
1445impl Paragraph {
1446 pub fn new() -> Paragraph {
1448 let mut builder = GreenNodeBuilder::new();
1449
1450 builder.start_node(PARAGRAPH.into());
1451 builder.finish_node();
1452 Paragraph(SyntaxNode::new_root_mut(builder.finish()))
1453 }
1454
1455 pub fn snapshot(&self) -> Self {
1464 Paragraph(SyntaxNode::new_root_mut(self.0.green().into_owned()))
1465 }
1466
1467 pub fn text_range(&self) -> rowan::TextRange {
1469 self.0.text_range()
1470 }
1471
1472 pub fn entries_in_range(&self, range: rowan::TextRange) -> impl Iterator<Item = Entry> + '_ {
1499 self.entries().filter(move |e| {
1500 let entry_range = e.text_range();
1501 entry_range.start() < range.end() && entry_range.end() > range.start()
1503 })
1504 }
1505
1506 pub fn entry_at_position(&self, offset: rowan::TextSize) -> Option<Entry> {
1530 self.entries().find(|e| {
1531 let range = e.text_range();
1532 range.contains(offset)
1533 })
1534 }
1535
1536 #[must_use]
1548 pub fn wrap_and_sort(
1549 &self,
1550 indentation: Indentation,
1551 immediate_empty_line: bool,
1552 max_line_length_one_liner: Option<usize>,
1553 sort_entries: Option<&dyn Fn(&Entry, &Entry) -> std::cmp::Ordering>,
1554 format_value: Option<&dyn Fn(&str, &str) -> String>,
1555 ) -> Paragraph {
1556 let mut builder = GreenNodeBuilder::new();
1557
1558 let mut current = vec![];
1559 let mut entries = vec![];
1560
1561 builder.start_node(PARAGRAPH.into());
1562 for c in self.0.children_with_tokens() {
1563 match c.kind() {
1564 ENTRY => {
1565 entries.push((current, Entry::cast(c.as_node().unwrap().clone()).unwrap()));
1566 current = vec![];
1567 }
1568 ERROR | COMMENT => {
1569 current.push(c);
1570 }
1571 _ => {}
1572 }
1573 }
1574
1575 if let Some(sort_entry) = sort_entries {
1576 entries.sort_by(|a, b| {
1577 let a_key = &a.1;
1578 let b_key = &b.1;
1579 sort_entry(a_key, b_key)
1580 });
1581 }
1582
1583 for (pre, entry) in entries.into_iter() {
1584 for c in pre.into_iter() {
1585 builder.token(c.kind().into(), c.as_token().unwrap().text());
1586 }
1587
1588 inject(
1589 &mut builder,
1590 entry
1591 .wrap_and_sort(
1592 indentation,
1593 immediate_empty_line,
1594 max_line_length_one_liner,
1595 format_value,
1596 )
1597 .0,
1598 );
1599 }
1600
1601 for c in current {
1602 builder.token(c.kind().into(), c.as_token().unwrap().text());
1603 }
1604
1605 builder.finish_node();
1606 Self(SyntaxNode::new_root_mut(builder.finish()))
1607 }
1608
1609 pub fn normalize_field_spacing(&mut self) -> bool {
1629 let mut any_changed = false;
1630
1631 let mut entries: Vec<_> = self.entries().collect();
1633
1634 for entry in &mut entries {
1636 if entry.normalize_field_spacing() {
1637 any_changed = true;
1638 }
1639 }
1640
1641 any_changed
1642 }
1643
1644 pub fn get(&self, key: &str) -> Option<String> {
1648 self.entries()
1649 .find(|e| {
1650 e.key()
1651 .as_deref()
1652 .is_some_and(|k| k.eq_ignore_ascii_case(key))
1653 })
1654 .map(|e| e.value())
1655 }
1656
1657 pub fn get_with_comments(&self, key: &str) -> Option<String> {
1665 self.entries()
1666 .find(|e| {
1667 e.key()
1668 .as_deref()
1669 .is_some_and(|k| k.eq_ignore_ascii_case(key))
1670 })
1671 .map(|e| e.value_with_comments())
1672 }
1673
1674 pub fn get_entry(&self, key: &str) -> Option<Entry> {
1678 self.entries().find(|e| {
1679 e.key()
1680 .as_deref()
1681 .is_some_and(|k| k.eq_ignore_ascii_case(key))
1682 })
1683 }
1684
1685 pub fn get_with_indent(&self, key: &str, indent_pattern: &IndentPattern) -> Option<String> {
1712 use crate::lex::SyntaxKind::{INDENT, VALUE};
1713
1714 self.entries()
1715 .find(|e| {
1716 e.key()
1717 .as_deref()
1718 .is_some_and(|k| k.eq_ignore_ascii_case(key))
1719 })
1720 .and_then(|e| {
1721 let field_key = e.key()?;
1722 let expected_indent = indent_pattern.to_string(&field_key);
1723 let expected_len = expected_indent.len();
1724
1725 let mut result = String::new();
1726 let mut first = true;
1727 let mut last_indent: Option<String> = None;
1728
1729 for token in e.0.children_with_tokens().filter_map(|it| it.into_token()) {
1730 match token.kind() {
1731 INDENT => {
1732 last_indent = Some(token.text().to_string());
1733 }
1734 VALUE => {
1735 if !first {
1736 result.push('\n');
1737 if let Some(ref indent_text) = last_indent {
1739 if indent_text.len() > expected_len {
1740 result.push_str(&indent_text[expected_len..]);
1741 }
1742 }
1743 }
1744 result.push_str(token.text());
1745 first = false;
1746 last_indent = None;
1747 }
1748 _ => {}
1749 }
1750 }
1751
1752 Some(result)
1753 })
1754 }
1755
1756 pub fn get_multiline(&self, key: &str) -> Option<String> {
1783 self.get_with_indent(key, &IndentPattern::Fixed(1))
1784 }
1785
1786 pub fn set_multiline(
1812 &mut self,
1813 key: &str,
1814 value: &str,
1815 field_order: Option<&[&str]>,
1816 ) -> Result<(), Error> {
1817 self.try_set_with_forced_indent(key, value, &IndentPattern::Fixed(1), field_order)
1818 }
1819
1820 pub fn contains_key(&self, key: &str) -> bool {
1822 self.get(key).is_some()
1823 }
1824
1825 pub fn entries(&self) -> impl Iterator<Item = Entry> + '_ {
1827 self.0.children().filter_map(Entry::cast)
1828 }
1829
1830 pub fn items(&self) -> impl Iterator<Item = (String, String)> + '_ {
1832 self.entries()
1833 .filter_map(|e| e.key().map(|k| (k, e.value())))
1834 }
1835
1836 pub fn get_all<'a>(&'a self, key: &'a str) -> impl Iterator<Item = String> + 'a {
1840 self.items().filter_map(move |(k, v)| {
1841 if k.eq_ignore_ascii_case(key) {
1842 Some(v)
1843 } else {
1844 None
1845 }
1846 })
1847 }
1848
1849 pub fn keys(&self) -> impl Iterator<Item = String> + '_ {
1851 self.entries().filter_map(|e| e.key())
1852 }
1853
1854 pub fn remove(&mut self, key: &str) {
1858 for mut entry in self.entries() {
1859 if entry
1860 .key()
1861 .as_deref()
1862 .is_some_and(|k| k.eq_ignore_ascii_case(key))
1863 {
1864 entry.detach();
1865 }
1866 }
1867 }
1868
1869 pub fn insert(&mut self, key: &str, value: &str) {
1871 let entry = Entry::new(key, value);
1872 let count = self.0.children_with_tokens().count();
1873 self.0.splice_children(count..count, vec![entry.0.into()]);
1874 }
1875
1876 pub fn insert_comment_before(&mut self, comment: &str) {
1895 use rowan::GreenNodeBuilder;
1896
1897 let mut builder = GreenNodeBuilder::new();
1900 builder.start_node(EMPTY_LINE.into());
1901 builder.token(COMMENT.into(), &format!("# {}", comment));
1902 builder.token(NEWLINE.into(), "\n");
1903 builder.finish_node();
1904 let green = builder.finish();
1905
1906 let comment_node = SyntaxNode::new_root_mut(green);
1908
1909 let index = self.0.index();
1910 let parent = self.0.parent().expect("Paragraph must have a parent");
1911 parent.splice_children(index..index, vec![comment_node.into()]);
1912 }
1913
1914 fn detect_indent_pattern(&self) -> IndentPattern {
1922 let indent_data: Vec<(String, usize)> = self
1924 .entries()
1925 .filter_map(|entry| {
1926 let field_key = entry.key()?;
1927 let indent = entry.get_indent()?;
1928 Some((field_key, indent.len()))
1929 })
1930 .collect();
1931
1932 if indent_data.is_empty() {
1933 return IndentPattern::FieldNameLength;
1935 }
1936
1937 let first_indent_len = indent_data[0].1;
1939 let all_same = indent_data.iter().all(|(_, len)| *len == first_indent_len);
1940
1941 if all_same {
1942 return IndentPattern::Fixed(first_indent_len);
1944 }
1945
1946 let all_match_field_length = indent_data
1948 .iter()
1949 .all(|(field_key, indent_len)| *indent_len == field_key.len() + 2);
1950
1951 if all_match_field_length {
1952 return IndentPattern::FieldNameLength;
1954 }
1955
1956 IndentPattern::FieldNameLength
1958 }
1959
1960 pub fn try_set(&mut self, key: &str, value: &str) -> Result<(), Error> {
1965 self.try_set_with_indent_pattern(key, value, None, None)
1966 }
1967
1968 pub fn set(&mut self, key: &str, value: &str) {
1973 self.try_set(key, value)
1974 .expect("Invalid value: empty continuation line")
1975 }
1976
1977 pub fn set_with_field_order(&mut self, key: &str, value: &str, field_order: &[&str]) {
1979 self.try_set_with_indent_pattern(key, value, None, Some(field_order))
1980 .expect("Invalid value: empty continuation line")
1981 }
1982
1983 pub fn try_set_with_indent_pattern(
2000 &mut self,
2001 key: &str,
2002 value: &str,
2003 default_indent_pattern: Option<&IndentPattern>,
2004 field_order: Option<&[&str]>,
2005 ) -> Result<(), Error> {
2006 let existing_entry = self.entries().find(|entry| {
2008 entry
2009 .key()
2010 .as_deref()
2011 .is_some_and(|k| k.eq_ignore_ascii_case(key))
2012 });
2013
2014 let indent = existing_entry
2016 .as_ref()
2017 .and_then(|entry| entry.get_indent())
2018 .unwrap_or_else(|| {
2019 if let Some(pattern) = default_indent_pattern {
2021 pattern.to_string(key)
2022 } else {
2023 self.detect_indent_pattern().to_string(key)
2024 }
2025 });
2026
2027 let post_colon_ws = existing_entry
2028 .as_ref()
2029 .and_then(|entry| entry.get_post_colon_whitespace())
2030 .unwrap_or_else(|| " ".to_string());
2031
2032 let actual_key = existing_entry
2034 .as_ref()
2035 .and_then(|e| e.key())
2036 .unwrap_or_else(|| key.to_string());
2037
2038 let new_entry = Entry::try_with_formatting(&actual_key, value, &post_colon_ws, &indent)?;
2039
2040 for entry in self.entries() {
2042 if entry
2043 .key()
2044 .as_deref()
2045 .is_some_and(|k| k.eq_ignore_ascii_case(key))
2046 {
2047 self.0.splice_children(
2048 entry.0.index()..entry.0.index() + 1,
2049 vec![new_entry.0.into()],
2050 );
2051 return Ok(());
2052 }
2053 }
2054
2055 if let Some(order) = field_order {
2057 let insertion_index = self.find_insertion_index(key, order);
2058 self.0
2059 .splice_children(insertion_index..insertion_index, vec![new_entry.0.into()]);
2060 } else {
2061 let insertion_index = self.0.children_with_tokens().count();
2063 self.0
2064 .splice_children(insertion_index..insertion_index, vec![new_entry.0.into()]);
2065 }
2066 Ok(())
2067 }
2068
2069 pub fn set_with_indent_pattern(
2086 &mut self,
2087 key: &str,
2088 value: &str,
2089 default_indent_pattern: Option<&IndentPattern>,
2090 field_order: Option<&[&str]>,
2091 ) {
2092 self.try_set_with_indent_pattern(key, value, default_indent_pattern, field_order)
2093 .expect("Invalid value: empty continuation line")
2094 }
2095
2096 pub fn try_set_with_forced_indent(
2110 &mut self,
2111 key: &str,
2112 value: &str,
2113 indent_pattern: &IndentPattern,
2114 field_order: Option<&[&str]>,
2115 ) -> Result<(), Error> {
2116 let existing_entry = self.entries().find(|entry| {
2118 entry
2119 .key()
2120 .as_deref()
2121 .is_some_and(|k| k.eq_ignore_ascii_case(key))
2122 });
2123
2124 let post_colon_ws = existing_entry
2126 .as_ref()
2127 .and_then(|entry| entry.get_post_colon_whitespace())
2128 .unwrap_or_else(|| " ".to_string());
2129
2130 let actual_key = existing_entry
2132 .as_ref()
2133 .and_then(|e| e.key())
2134 .unwrap_or_else(|| key.to_string());
2135
2136 let indent = indent_pattern.to_string(&actual_key);
2138 let new_entry = Entry::try_with_formatting(&actual_key, value, &post_colon_ws, &indent)?;
2139
2140 for entry in self.entries() {
2142 if entry
2143 .key()
2144 .as_deref()
2145 .is_some_and(|k| k.eq_ignore_ascii_case(key))
2146 {
2147 self.0.splice_children(
2148 entry.0.index()..entry.0.index() + 1,
2149 vec![new_entry.0.into()],
2150 );
2151 return Ok(());
2152 }
2153 }
2154
2155 if let Some(order) = field_order {
2157 let insertion_index = self.find_insertion_index(key, order);
2158 self.0
2159 .splice_children(insertion_index..insertion_index, vec![new_entry.0.into()]);
2160 } else {
2161 let insertion_index = self.0.children_with_tokens().count();
2163 self.0
2164 .splice_children(insertion_index..insertion_index, vec![new_entry.0.into()]);
2165 }
2166 Ok(())
2167 }
2168
2169 pub fn set_with_forced_indent(
2183 &mut self,
2184 key: &str,
2185 value: &str,
2186 indent_pattern: &IndentPattern,
2187 field_order: Option<&[&str]>,
2188 ) {
2189 self.try_set_with_forced_indent(key, value, indent_pattern, field_order)
2190 .expect("Invalid value: empty continuation line")
2191 }
2192
2193 pub fn change_field_indent(
2209 &mut self,
2210 key: &str,
2211 indent_pattern: &IndentPattern,
2212 ) -> Result<bool, Error> {
2213 let existing_entry = self.entries().find(|entry| {
2215 entry
2216 .key()
2217 .as_deref()
2218 .is_some_and(|k| k.eq_ignore_ascii_case(key))
2219 });
2220
2221 if let Some(entry) = existing_entry {
2222 let value = entry.value();
2223 let actual_key = entry.key().unwrap_or_else(|| key.to_string());
2224
2225 let post_colon_ws = entry
2227 .get_post_colon_whitespace()
2228 .unwrap_or_else(|| " ".to_string());
2229
2230 let indent = indent_pattern.to_string(&actual_key);
2232 let new_entry =
2233 Entry::try_with_formatting(&actual_key, &value, &post_colon_ws, &indent)?;
2234
2235 self.0.splice_children(
2237 entry.0.index()..entry.0.index() + 1,
2238 vec![new_entry.0.into()],
2239 );
2240 Ok(true)
2241 } else {
2242 Ok(false)
2243 }
2244 }
2245
2246 fn find_insertion_index(&self, key: &str, field_order: &[&str]) -> usize {
2248 let new_field_position = field_order
2250 .iter()
2251 .position(|&field| field.eq_ignore_ascii_case(key));
2252
2253 let mut insertion_index = self.0.children_with_tokens().count();
2254
2255 for (i, child) in self.0.children_with_tokens().enumerate() {
2257 if let Some(node) = child.as_node() {
2258 if let Some(entry) = Entry::cast(node.clone()) {
2259 if let Some(existing_key) = entry.key() {
2260 let existing_position = field_order
2261 .iter()
2262 .position(|&field| field.eq_ignore_ascii_case(&existing_key));
2263
2264 match (new_field_position, existing_position) {
2265 (Some(new_pos), Some(existing_pos)) => {
2267 if new_pos < existing_pos {
2268 insertion_index = i;
2269 break;
2270 }
2271 }
2272 (Some(_), None) => {
2274 }
2276 (None, Some(_)) => {
2278 }
2280 (None, None) => {
2282 if key < existing_key.as_str() {
2283 insertion_index = i;
2284 break;
2285 }
2286 }
2287 }
2288 }
2289 }
2290 }
2291 }
2292
2293 if new_field_position.is_some() && insertion_index == self.0.children_with_tokens().count()
2296 {
2297 let children: Vec<_> = self.0.children_with_tokens().enumerate().collect();
2299 for (i, child) in children.into_iter().rev() {
2300 if let Some(node) = child.as_node() {
2301 if let Some(entry) = Entry::cast(node.clone()) {
2302 if let Some(existing_key) = entry.key() {
2303 if field_order
2304 .iter()
2305 .any(|&f| f.eq_ignore_ascii_case(&existing_key))
2306 {
2307 insertion_index = i + 1;
2309 break;
2310 }
2311 }
2312 }
2313 }
2314 }
2315 }
2316
2317 insertion_index
2318 }
2319
2320 pub fn rename(&mut self, old_key: &str, new_key: &str) -> bool {
2324 for entry in self.entries() {
2325 if entry
2326 .key()
2327 .as_deref()
2328 .is_some_and(|k| k.eq_ignore_ascii_case(old_key))
2329 {
2330 self.0.splice_children(
2331 entry.0.index()..entry.0.index() + 1,
2332 vec![Entry::new(new_key, entry.value().as_str()).0.into()],
2333 );
2334 return true;
2335 }
2336 }
2337 false
2338 }
2339}
2340
2341impl Default for Paragraph {
2342 fn default() -> Self {
2343 Self::new()
2344 }
2345}
2346
2347impl std::str::FromStr for Paragraph {
2348 type Err = ParseError;
2349
2350 fn from_str(text: &str) -> Result<Self, Self::Err> {
2351 let deb822 = Deb822::from_str(text)?;
2352
2353 let mut paragraphs = deb822.paragraphs();
2354
2355 paragraphs
2356 .next()
2357 .ok_or_else(|| ParseError(vec!["no paragraphs".to_string()]))
2358 }
2359}
2360
2361#[cfg(feature = "python-debian")]
2362impl<'py> pyo3::IntoPyObject<'py> for Paragraph {
2363 type Target = pyo3::PyAny;
2364 type Output = pyo3::Bound<'py, Self::Target>;
2365 type Error = pyo3::PyErr;
2366
2367 fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
2368 use pyo3::prelude::*;
2369 let d = pyo3::types::PyDict::new(py);
2370 for (k, v) in self.items() {
2371 d.set_item(k, v)?;
2372 }
2373 let m = py.import("debian.deb822")?;
2374 let cls = m.getattr("Deb822")?;
2375 cls.call1((d,))
2376 }
2377}
2378
2379#[cfg(feature = "python-debian")]
2380impl<'py> pyo3::IntoPyObject<'py> for &Paragraph {
2381 type Target = pyo3::PyAny;
2382 type Output = pyo3::Bound<'py, Self::Target>;
2383 type Error = pyo3::PyErr;
2384
2385 fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
2386 use pyo3::prelude::*;
2387 let d = pyo3::types::PyDict::new(py);
2388 for (k, v) in self.items() {
2389 d.set_item(k, v)?;
2390 }
2391 let m = py.import("debian.deb822")?;
2392 let cls = m.getattr("Deb822")?;
2393 cls.call1((d,))
2394 }
2395}
2396
2397#[cfg(feature = "python-debian")]
2398impl<'py> pyo3::FromPyObject<'_, 'py> for Paragraph {
2399 type Error = pyo3::PyErr;
2400
2401 fn extract(obj: pyo3::Borrowed<'_, 'py, pyo3::PyAny>) -> Result<Self, Self::Error> {
2402 use pyo3::types::PyAnyMethods;
2403 let d = obj.call_method0("__str__")?.extract::<String>()?;
2404 Paragraph::from_str(&d)
2405 .map_err(|e| pyo3::exceptions::PyValueError::new_err((e.to_string(),)))
2406 }
2407}
2408
2409impl Entry {
2410 pub fn snapshot(&self) -> Self {
2419 Entry(SyntaxNode::new_root_mut(self.0.green().into_owned()))
2420 }
2421
2422 pub fn text_range(&self) -> rowan::TextRange {
2424 self.0.text_range()
2425 }
2426
2427 pub fn key_range(&self) -> Option<rowan::TextRange> {
2429 self.0
2430 .children_with_tokens()
2431 .filter_map(|it| it.into_token())
2432 .find(|it| it.kind() == KEY)
2433 .map(|it| it.text_range())
2434 }
2435
2436 pub fn colon_range(&self) -> Option<rowan::TextRange> {
2438 self.0
2439 .children_with_tokens()
2440 .filter_map(|it| it.into_token())
2441 .find(|it| it.kind() == COLON)
2442 .map(|it| it.text_range())
2443 }
2444
2445 pub fn value_range(&self) -> Option<rowan::TextRange> {
2448 let value_tokens: Vec<_> = self
2449 .0
2450 .children_with_tokens()
2451 .filter_map(|it| it.into_token())
2452 .filter(|it| it.kind() == VALUE)
2453 .collect();
2454
2455 if value_tokens.is_empty() {
2456 return None;
2457 }
2458
2459 let first = value_tokens.first().unwrap();
2460 let last = value_tokens.last().unwrap();
2461 Some(rowan::TextRange::new(
2462 first.text_range().start(),
2463 last.text_range().end(),
2464 ))
2465 }
2466
2467 pub fn value_line_ranges(&self) -> Vec<rowan::TextRange> {
2470 self.0
2471 .children_with_tokens()
2472 .filter_map(|it| it.into_token())
2473 .filter(|it| it.kind() == VALUE)
2474 .map(|it| it.text_range())
2475 .collect()
2476 }
2477
2478 pub fn new(key: &str, value: &str) -> Entry {
2480 Self::with_indentation(key, value, " ")
2481 }
2482
2483 pub fn with_indentation(key: &str, value: &str, indent: &str) -> Entry {
2490 Entry::with_formatting(key, value, " ", indent)
2491 }
2492
2493 pub fn try_with_formatting(
2504 key: &str,
2505 value: &str,
2506 post_colon_ws: &str,
2507 indent: &str,
2508 ) -> Result<Entry, Error> {
2509 let mut builder = GreenNodeBuilder::new();
2510
2511 builder.start_node(ENTRY.into());
2512 builder.token(KEY.into(), key);
2513 builder.token(COLON.into(), ":");
2514
2515 let mut i = 0;
2517 while i < post_colon_ws.len() {
2518 if post_colon_ws[i..].starts_with('\n') {
2519 builder.token(NEWLINE.into(), "\n");
2520 i += 1;
2521 } else {
2522 let start = i;
2524 while i < post_colon_ws.len() && !post_colon_ws[i..].starts_with('\n') {
2525 i += post_colon_ws[i..].chars().next().unwrap().len_utf8();
2526 }
2527 builder.token(WHITESPACE.into(), &post_colon_ws[start..i]);
2528 }
2529 }
2530
2531 for (line_idx, line) in value.split('\n').enumerate() {
2532 if line_idx > 0 {
2533 if line.trim().is_empty() {
2536 return Err(Error::InvalidValue(format!(
2537 "empty continuation line (line with only whitespace) at line {}",
2538 line_idx + 1
2539 )));
2540 }
2541 builder.token(INDENT.into(), indent);
2542 }
2543 builder.token(VALUE.into(), line);
2544 builder.token(NEWLINE.into(), "\n");
2545 }
2546 builder.finish_node();
2547 Ok(Entry(SyntaxNode::new_root_mut(builder.finish())))
2548 }
2549
2550 pub fn with_formatting(key: &str, value: &str, post_colon_ws: &str, indent: &str) -> Entry {
2561 Self::try_with_formatting(key, value, post_colon_ws, indent)
2562 .expect("Invalid value: empty continuation line")
2563 }
2564
2565 #[must_use]
2566 pub fn wrap_and_sort(
2579 &self,
2580 mut indentation: Indentation,
2581 immediate_empty_line: bool,
2582 max_line_length_one_liner: Option<usize>,
2583 format_value: Option<&dyn Fn(&str, &str) -> String>,
2584 ) -> Entry {
2585 let mut builder = GreenNodeBuilder::new();
2586
2587 let mut content = vec![];
2588 builder.start_node(ENTRY.into());
2589 for c in self.0.children_with_tokens() {
2590 let text = c.as_token().map(|t| t.text());
2591 match c.kind() {
2592 KEY => {
2593 builder.token(KEY.into(), text.unwrap());
2594 if indentation == Indentation::FieldNameLength {
2595 indentation = Indentation::Spaces(text.unwrap().len() as u32);
2596 }
2597 }
2598 COLON => {
2599 builder.token(COLON.into(), ":");
2600 }
2601 INDENT => {
2602 }
2604 ERROR | COMMENT | VALUE | WHITESPACE | NEWLINE => {
2605 content.push(c);
2606 }
2607 EMPTY_LINE | ENTRY | ROOT | PARAGRAPH => unreachable!(),
2608 }
2609 }
2610
2611 let indentation = if let crate::Indentation::Spaces(i) = indentation {
2612 i
2613 } else {
2614 1
2615 };
2616
2617 assert!(indentation > 0);
2618
2619 while let Some(c) = content.last() {
2621 if c.kind() == NEWLINE || c.kind() == WHITESPACE {
2622 content.pop();
2623 } else {
2624 break;
2625 }
2626 }
2627
2628 let tokens = if let Some(ref format_value) = format_value {
2631 if !content
2632 .iter()
2633 .any(|c| c.kind() == ERROR || c.kind() == COMMENT)
2634 {
2635 let concat = content
2636 .iter()
2637 .filter_map(|c| c.as_token().map(|t| t.text()))
2638 .collect::<String>();
2639 let formatted = format_value(self.key().as_ref().unwrap(), &concat);
2640 crate::lex::lex_inline(&formatted)
2641 .map(|(k, t)| (k, t.to_string()))
2642 .collect::<Vec<_>>()
2643 } else {
2644 content
2645 .into_iter()
2646 .map(|n| n.into_token().unwrap())
2647 .map(|i| (i.kind(), i.text().to_string()))
2648 .collect::<Vec<_>>()
2649 }
2650 } else {
2651 content
2652 .into_iter()
2653 .map(|n| n.into_token().unwrap())
2654 .map(|i| (i.kind(), i.text().to_string()))
2655 .collect::<Vec<_>>()
2656 };
2657
2658 rebuild_value(
2659 &mut builder,
2660 tokens,
2661 self.key().map_or(0, |k| k.len()),
2662 indentation,
2663 immediate_empty_line,
2664 max_line_length_one_liner,
2665 );
2666
2667 builder.finish_node();
2668 Self(SyntaxNode::new_root_mut(builder.finish()))
2669 }
2670
2671 pub fn key(&self) -> Option<String> {
2673 self.0
2674 .children_with_tokens()
2675 .filter_map(|it| it.into_token())
2676 .find(|it| it.kind() == KEY)
2677 .map(|it| it.text().to_string())
2678 }
2679
2680 pub fn value(&self) -> String {
2682 let mut parts = self
2683 .0
2684 .children_with_tokens()
2685 .filter_map(|it| it.into_token())
2686 .filter(|it| it.kind() == VALUE)
2687 .map(|it| it.text().to_string());
2688
2689 match parts.next() {
2690 None => String::new(),
2691 Some(first) => {
2692 let mut result = first;
2693 for part in parts {
2694 result.push('\n');
2695 result.push_str(&part);
2696 }
2697 result
2698 }
2699 }
2700 }
2701
2702 pub fn value_with_comments(&self) -> String {
2709 let mut parts = self
2710 .0
2711 .children_with_tokens()
2712 .filter_map(|it| it.into_token())
2713 .filter(|it| it.kind() == VALUE || it.kind() == COMMENT)
2714 .map(|it| it.text().to_string());
2715
2716 match parts.next() {
2717 None => String::new(),
2718 Some(first) => {
2719 let mut result = first;
2720 for part in parts {
2721 result.push('\n');
2722 result.push_str(&part);
2723 }
2724 result
2725 }
2726 }
2727 }
2728
2729 fn get_indent(&self) -> Option<String> {
2732 self.0
2733 .children_with_tokens()
2734 .filter_map(|it| it.into_token())
2735 .find(|it| it.kind() == INDENT)
2736 .map(|it| it.text().to_string())
2737 }
2738
2739 fn get_post_colon_whitespace(&self) -> Option<String> {
2743 let mut found_colon = false;
2744 let mut whitespace = String::new();
2745
2746 for token in self
2747 .0
2748 .children_with_tokens()
2749 .filter_map(|it| it.into_token())
2750 {
2751 if token.kind() == COLON {
2752 found_colon = true;
2753 continue;
2754 }
2755
2756 if found_colon {
2757 if token.kind() == WHITESPACE || token.kind() == NEWLINE || token.kind() == INDENT {
2758 whitespace.push_str(token.text());
2759 } else {
2760 break;
2762 }
2763 }
2764 }
2765
2766 if whitespace.is_empty() {
2767 None
2768 } else {
2769 Some(whitespace)
2770 }
2771 }
2772
2773 pub fn normalize_field_spacing(&mut self) -> bool {
2794 use rowan::GreenNodeBuilder;
2795
2796 let original_text = self.0.text().to_string();
2798
2799 let mut builder = GreenNodeBuilder::new();
2801 builder.start_node(ENTRY.into());
2802
2803 let mut seen_colon = false;
2804 let mut skip_whitespace = false;
2805
2806 for child in self.0.children_with_tokens() {
2807 match child.kind() {
2808 KEY => {
2809 builder.token(KEY.into(), child.as_token().unwrap().text());
2810 }
2811 COLON => {
2812 builder.token(COLON.into(), ":");
2813 seen_colon = true;
2814 skip_whitespace = true;
2815 }
2816 WHITESPACE if skip_whitespace => {
2817 continue;
2819 }
2820 VALUE if skip_whitespace => {
2821 builder.token(WHITESPACE.into(), " ");
2823 builder.token(VALUE.into(), child.as_token().unwrap().text());
2824 skip_whitespace = false;
2825 }
2826 NEWLINE if skip_whitespace && seen_colon => {
2827 builder.token(NEWLINE.into(), "\n");
2830 skip_whitespace = false;
2831 }
2832 _ => {
2833 if let Some(token) = child.as_token() {
2835 builder.token(token.kind().into(), token.text());
2836 }
2837 }
2838 }
2839 }
2840
2841 builder.finish_node();
2842 let normalized_green = builder.finish();
2843 let normalized = SyntaxNode::new_root_mut(normalized_green);
2844
2845 let changed = original_text != normalized.text().to_string();
2847
2848 if changed {
2849 if let Some(parent) = self.0.parent() {
2851 let index = self.0.index();
2852 parent.splice_children(index..index + 1, vec![normalized.into()]);
2853 }
2854 }
2855
2856 changed
2857 }
2858
2859 pub fn detach(&mut self) {
2861 self.0.detach();
2862 }
2863}
2864
2865impl FromStr for Deb822 {
2866 type Err = ParseError;
2867
2868 fn from_str(s: &str) -> Result<Self, Self::Err> {
2869 Deb822::parse(s).to_result()
2870 }
2871}
2872
2873#[test]
2874fn test_parse_simple() {
2875 const CONTROLV1: &str = r#"Source: foo
2876Maintainer: Foo Bar <foo@example.com>
2877Section: net
2878
2879# This is a comment
2880
2881Package: foo
2882Architecture: all
2883Depends:
2884 bar,
2885 blah
2886Description: This is a description
2887 And it is
2888 .
2889 multiple
2890 lines
2891"#;
2892 let parsed = parse(CONTROLV1);
2893 let node = parsed.syntax();
2894 assert_eq!(
2895 format!("{:#?}", node),
2896 r###"ROOT@0..203
2897 PARAGRAPH@0..63
2898 ENTRY@0..12
2899 KEY@0..6 "Source"
2900 COLON@6..7 ":"
2901 WHITESPACE@7..8 " "
2902 VALUE@8..11 "foo"
2903 NEWLINE@11..12 "\n"
2904 ENTRY@12..50
2905 KEY@12..22 "Maintainer"
2906 COLON@22..23 ":"
2907 WHITESPACE@23..24 " "
2908 VALUE@24..49 "Foo Bar <foo@example. ..."
2909 NEWLINE@49..50 "\n"
2910 ENTRY@50..63
2911 KEY@50..57 "Section"
2912 COLON@57..58 ":"
2913 WHITESPACE@58..59 " "
2914 VALUE@59..62 "net"
2915 NEWLINE@62..63 "\n"
2916 EMPTY_LINE@63..64
2917 NEWLINE@63..64 "\n"
2918 EMPTY_LINE@64..84
2919 COMMENT@64..83 "# This is a comment"
2920 NEWLINE@83..84 "\n"
2921 EMPTY_LINE@84..85
2922 NEWLINE@84..85 "\n"
2923 PARAGRAPH@85..203
2924 ENTRY@85..98
2925 KEY@85..92 "Package"
2926 COLON@92..93 ":"
2927 WHITESPACE@93..94 " "
2928 VALUE@94..97 "foo"
2929 NEWLINE@97..98 "\n"
2930 ENTRY@98..116
2931 KEY@98..110 "Architecture"
2932 COLON@110..111 ":"
2933 WHITESPACE@111..112 " "
2934 VALUE@112..115 "all"
2935 NEWLINE@115..116 "\n"
2936 ENTRY@116..137
2937 KEY@116..123 "Depends"
2938 COLON@123..124 ":"
2939 NEWLINE@124..125 "\n"
2940 INDENT@125..126 " "
2941 VALUE@126..130 "bar,"
2942 NEWLINE@130..131 "\n"
2943 INDENT@131..132 " "
2944 VALUE@132..136 "blah"
2945 NEWLINE@136..137 "\n"
2946 ENTRY@137..203
2947 KEY@137..148 "Description"
2948 COLON@148..149 ":"
2949 WHITESPACE@149..150 " "
2950 VALUE@150..171 "This is a description"
2951 NEWLINE@171..172 "\n"
2952 INDENT@172..173 " "
2953 VALUE@173..182 "And it is"
2954 NEWLINE@182..183 "\n"
2955 INDENT@183..184 " "
2956 VALUE@184..185 "."
2957 NEWLINE@185..186 "\n"
2958 INDENT@186..187 " "
2959 VALUE@187..195 "multiple"
2960 NEWLINE@195..196 "\n"
2961 INDENT@196..197 " "
2962 VALUE@197..202 "lines"
2963 NEWLINE@202..203 "\n"
2964"###
2965 );
2966 assert_eq!(parsed.errors, Vec::<String>::new());
2967
2968 let root = parsed.root_mut();
2969 assert_eq!(root.paragraphs().count(), 2);
2970 let source = root.paragraphs().next().unwrap();
2971 assert_eq!(
2972 source.keys().collect::<Vec<_>>(),
2973 vec!["Source", "Maintainer", "Section"]
2974 );
2975 assert_eq!(source.get("Source").as_deref(), Some("foo"));
2976 assert_eq!(
2977 source.get("Maintainer").as_deref(),
2978 Some("Foo Bar <foo@example.com>")
2979 );
2980 assert_eq!(source.get("Section").as_deref(), Some("net"));
2981 assert_eq!(
2982 source.items().collect::<Vec<_>>(),
2983 vec![
2984 ("Source".into(), "foo".into()),
2985 ("Maintainer".into(), "Foo Bar <foo@example.com>".into()),
2986 ("Section".into(), "net".into()),
2987 ]
2988 );
2989
2990 let binary = root.paragraphs().nth(1).unwrap();
2991 assert_eq!(
2992 binary.keys().collect::<Vec<_>>(),
2993 vec!["Package", "Architecture", "Depends", "Description"]
2994 );
2995 assert_eq!(binary.get("Package").as_deref(), Some("foo"));
2996 assert_eq!(binary.get("Architecture").as_deref(), Some("all"));
2997 assert_eq!(binary.get("Depends").as_deref(), Some("bar,\nblah"));
2998 assert_eq!(
2999 binary.get("Description").as_deref(),
3000 Some("This is a description\nAnd it is\n.\nmultiple\nlines")
3001 );
3002
3003 assert_eq!(node.text(), CONTROLV1);
3004}
3005
3006#[test]
3007fn test_with_trailing_whitespace() {
3008 const CONTROLV1: &str = r#"Source: foo
3009Maintainer: Foo Bar <foo@example.com>
3010
3011
3012"#;
3013 let parsed = parse(CONTROLV1);
3014 let node = parsed.syntax();
3015 assert_eq!(
3016 format!("{:#?}", node),
3017 r###"ROOT@0..52
3018 PARAGRAPH@0..50
3019 ENTRY@0..12
3020 KEY@0..6 "Source"
3021 COLON@6..7 ":"
3022 WHITESPACE@7..8 " "
3023 VALUE@8..11 "foo"
3024 NEWLINE@11..12 "\n"
3025 ENTRY@12..50
3026 KEY@12..22 "Maintainer"
3027 COLON@22..23 ":"
3028 WHITESPACE@23..24 " "
3029 VALUE@24..49 "Foo Bar <foo@example. ..."
3030 NEWLINE@49..50 "\n"
3031 EMPTY_LINE@50..51
3032 NEWLINE@50..51 "\n"
3033 EMPTY_LINE@51..52
3034 NEWLINE@51..52 "\n"
3035"###
3036 );
3037 assert_eq!(parsed.errors, Vec::<String>::new());
3038
3039 let root = parsed.root_mut();
3040 assert_eq!(root.paragraphs().count(), 1);
3041 let source = root.paragraphs().next().unwrap();
3042 assert_eq!(
3043 source.items().collect::<Vec<_>>(),
3044 vec![
3045 ("Source".into(), "foo".into()),
3046 ("Maintainer".into(), "Foo Bar <foo@example.com>".into()),
3047 ]
3048 );
3049}
3050
3051fn rebuild_value(
3052 builder: &mut GreenNodeBuilder,
3053 mut tokens: Vec<(SyntaxKind, String)>,
3054 key_len: usize,
3055 indentation: u32,
3056 immediate_empty_line: bool,
3057 max_line_length_one_liner: Option<usize>,
3058) {
3059 let first_line_len = tokens
3060 .iter()
3061 .take_while(|(k, _t)| *k != NEWLINE)
3062 .map(|(_k, t)| t.len())
3063 .sum::<usize>() + key_len + 2 ;
3064
3065 let has_newline = tokens.iter().any(|(k, _t)| *k == NEWLINE);
3066
3067 let mut last_was_newline = false;
3068 if max_line_length_one_liner
3069 .map(|mll| first_line_len <= mll)
3070 .unwrap_or(false)
3071 && !has_newline
3072 {
3073 for (k, t) in tokens {
3075 builder.token(k.into(), &t);
3076 }
3077 } else {
3078 if immediate_empty_line && has_newline {
3080 builder.token(NEWLINE.into(), "\n");
3081 last_was_newline = true;
3082 } else {
3083 builder.token(WHITESPACE.into(), " ");
3084 }
3085 let mut start_idx = 0;
3087 while start_idx < tokens.len() {
3088 if tokens[start_idx].0 == NEWLINE || tokens[start_idx].0 == WHITESPACE {
3089 start_idx += 1;
3090 } else {
3091 break;
3092 }
3093 }
3094 tokens.drain(..start_idx);
3095 let indent_str = " ".repeat(indentation as usize);
3097 for (k, t) in tokens {
3098 if last_was_newline {
3099 builder.token(INDENT.into(), &indent_str);
3100 }
3101 builder.token(k.into(), &t);
3102 last_was_newline = k == NEWLINE;
3103 }
3104 }
3105
3106 if !last_was_newline {
3107 builder.token(NEWLINE.into(), "\n");
3108 }
3109}
3110
3111#[cfg(test)]
3112mod tests {
3113 use super::*;
3114 #[test]
3115 fn test_parse() {
3116 let d: super::Deb822 = r#"Source: foo
3117Maintainer: Foo Bar <jelmer@jelmer.uk>
3118Section: net
3119
3120Package: foo
3121Architecture: all
3122Depends: libc6
3123Description: This is a description
3124 With details
3125"#
3126 .parse()
3127 .unwrap();
3128 let mut ps = d.paragraphs();
3129 let p = ps.next().unwrap();
3130
3131 assert_eq!(p.get("Source").as_deref(), Some("foo"));
3132 assert_eq!(
3133 p.get("Maintainer").as_deref(),
3134 Some("Foo Bar <jelmer@jelmer.uk>")
3135 );
3136 assert_eq!(p.get("Section").as_deref(), Some("net"));
3137
3138 let b = ps.next().unwrap();
3139 assert_eq!(b.get("Package").as_deref(), Some("foo"));
3140 }
3141
3142 #[test]
3143 fn test_after_multi_line() {
3144 let d: super::Deb822 = r#"Source: golang-github-blah-blah
3145Section: devel
3146Priority: optional
3147Standards-Version: 4.2.0
3148Maintainer: Some Maintainer <example@example.com>
3149Build-Depends: debhelper (>= 11~),
3150 dh-golang,
3151 golang-any
3152Homepage: https://github.com/j-keck/arping
3153"#
3154 .parse()
3155 .unwrap();
3156 let mut ps = d.paragraphs();
3157 let p = ps.next().unwrap();
3158 assert_eq!(p.get("Source").as_deref(), Some("golang-github-blah-blah"));
3159 assert_eq!(p.get("Section").as_deref(), Some("devel"));
3160 assert_eq!(p.get("Priority").as_deref(), Some("optional"));
3161 assert_eq!(p.get("Standards-Version").as_deref(), Some("4.2.0"));
3162 assert_eq!(
3163 p.get("Maintainer").as_deref(),
3164 Some("Some Maintainer <example@example.com>")
3165 );
3166 assert_eq!(
3167 p.get("Build-Depends").as_deref(),
3168 Some("debhelper (>= 11~),\ndh-golang,\ngolang-any")
3169 );
3170 assert_eq!(
3171 p.get("Homepage").as_deref(),
3172 Some("https://github.com/j-keck/arping")
3173 );
3174 }
3175
3176 #[test]
3177 fn test_remove_field() {
3178 let d: super::Deb822 = r#"Source: foo
3179# Comment
3180Maintainer: Foo Bar <jelmer@jelmer.uk>
3181Section: net
3182
3183Package: foo
3184Architecture: all
3185Depends: libc6
3186Description: This is a description
3187 With details
3188"#
3189 .parse()
3190 .unwrap();
3191 let mut ps = d.paragraphs();
3192 let mut p = ps.next().unwrap();
3193 p.set("Foo", "Bar");
3194 p.remove("Section");
3195 p.remove("Nonexistent");
3196 assert_eq!(p.get("Foo").as_deref(), Some("Bar"));
3197 assert_eq!(
3198 p.to_string(),
3199 r#"Source: foo
3200# Comment
3201Maintainer: Foo Bar <jelmer@jelmer.uk>
3202Foo: Bar
3203"#
3204 );
3205 }
3206
3207 #[test]
3208 fn test_rename_field() {
3209 let d: super::Deb822 = r#"Source: foo
3210Vcs-Browser: https://salsa.debian.org/debian/foo
3211"#
3212 .parse()
3213 .unwrap();
3214 let mut ps = d.paragraphs();
3215 let mut p = ps.next().unwrap();
3216 assert!(p.rename("Vcs-Browser", "Homepage"));
3217 assert_eq!(
3218 p.to_string(),
3219 r#"Source: foo
3220Homepage: https://salsa.debian.org/debian/foo
3221"#
3222 );
3223
3224 assert_eq!(
3225 p.get("Homepage").as_deref(),
3226 Some("https://salsa.debian.org/debian/foo")
3227 );
3228 assert_eq!(p.get("Vcs-Browser").as_deref(), None);
3229
3230 assert!(!p.rename("Nonexistent", "Homepage"));
3232 }
3233
3234 #[test]
3235 fn test_set_field() {
3236 let d: super::Deb822 = r#"Source: foo
3237Maintainer: Foo Bar <joe@example.com>
3238"#
3239 .parse()
3240 .unwrap();
3241 let mut ps = d.paragraphs();
3242 let mut p = ps.next().unwrap();
3243 p.set("Maintainer", "Somebody Else <jane@example.com>");
3244 assert_eq!(
3245 p.get("Maintainer").as_deref(),
3246 Some("Somebody Else <jane@example.com>")
3247 );
3248 assert_eq!(
3249 p.to_string(),
3250 r#"Source: foo
3251Maintainer: Somebody Else <jane@example.com>
3252"#
3253 );
3254 }
3255
3256 #[test]
3257 fn test_set_new_field() {
3258 let d: super::Deb822 = r#"Source: foo
3259"#
3260 .parse()
3261 .unwrap();
3262 let mut ps = d.paragraphs();
3263 let mut p = ps.next().unwrap();
3264 p.set("Maintainer", "Somebody <joe@example.com>");
3265 assert_eq!(
3266 p.get("Maintainer").as_deref(),
3267 Some("Somebody <joe@example.com>")
3268 );
3269 assert_eq!(
3270 p.to_string(),
3271 r#"Source: foo
3272Maintainer: Somebody <joe@example.com>
3273"#
3274 );
3275 }
3276
3277 #[test]
3278 fn test_add_paragraph() {
3279 let mut d = super::Deb822::new();
3280 let mut p = d.add_paragraph();
3281 p.set("Foo", "Bar");
3282 assert_eq!(p.get("Foo").as_deref(), Some("Bar"));
3283 assert_eq!(
3284 p.to_string(),
3285 r#"Foo: Bar
3286"#
3287 );
3288 assert_eq!(
3289 d.to_string(),
3290 r#"Foo: Bar
3291"#
3292 );
3293
3294 let mut p = d.add_paragraph();
3295 p.set("Foo", "Blah");
3296 assert_eq!(p.get("Foo").as_deref(), Some("Blah"));
3297 assert_eq!(
3298 d.to_string(),
3299 r#"Foo: Bar
3300
3301Foo: Blah
3302"#
3303 );
3304 }
3305
3306 #[test]
3307 fn test_crud_paragraph() {
3308 let mut d = super::Deb822::new();
3309 let mut p = d.insert_paragraph(0);
3310 p.set("Foo", "Bar");
3311 assert_eq!(p.get("Foo").as_deref(), Some("Bar"));
3312 assert_eq!(
3313 d.to_string(),
3314 r#"Foo: Bar
3315"#
3316 );
3317
3318 let mut p = d.insert_paragraph(0);
3320 p.set("Foo", "Blah");
3321 assert_eq!(p.get("Foo").as_deref(), Some("Blah"));
3322 assert_eq!(
3323 d.to_string(),
3324 r#"Foo: Blah
3325
3326Foo: Bar
3327"#
3328 );
3329
3330 d.remove_paragraph(1);
3332 assert_eq!(d.to_string(), "Foo: Blah\n\n");
3333
3334 p.set("Foo", "Baz");
3336 assert_eq!(d.to_string(), "Foo: Baz\n\n");
3337
3338 d.remove_paragraph(0);
3340 assert_eq!(d.to_string(), "");
3341 }
3342
3343 #[test]
3344 fn test_swap_paragraphs() {
3345 let mut d: super::Deb822 = vec![
3347 vec![("Foo", "Bar")].into_iter().collect(),
3348 vec![("A", "B")].into_iter().collect(),
3349 vec![("X", "Y")].into_iter().collect(),
3350 ]
3351 .into_iter()
3352 .collect();
3353
3354 d.swap_paragraphs(0, 2);
3355 assert_eq!(d.to_string(), "X: Y\n\nA: B\n\nFoo: Bar\n");
3356
3357 d.swap_paragraphs(0, 2);
3359 assert_eq!(d.to_string(), "Foo: Bar\n\nA: B\n\nX: Y\n");
3360
3361 d.swap_paragraphs(0, 1);
3363 assert_eq!(d.to_string(), "A: B\n\nFoo: Bar\n\nX: Y\n");
3364
3365 let before = d.to_string();
3367 d.swap_paragraphs(1, 1);
3368 assert_eq!(d.to_string(), before);
3369 }
3370
3371 #[test]
3372 fn test_swap_paragraphs_preserves_content() {
3373 let mut d: super::Deb822 = vec![
3375 vec![("Field1", "Value1"), ("Field2", "Value2")]
3376 .into_iter()
3377 .collect(),
3378 vec![("FieldA", "ValueA"), ("FieldB", "ValueB")]
3379 .into_iter()
3380 .collect(),
3381 ]
3382 .into_iter()
3383 .collect();
3384
3385 d.swap_paragraphs(0, 1);
3386
3387 let mut paras = d.paragraphs();
3388 let p1 = paras.next().unwrap();
3389 assert_eq!(p1.get("FieldA").as_deref(), Some("ValueA"));
3390 assert_eq!(p1.get("FieldB").as_deref(), Some("ValueB"));
3391
3392 let p2 = paras.next().unwrap();
3393 assert_eq!(p2.get("Field1").as_deref(), Some("Value1"));
3394 assert_eq!(p2.get("Field2").as_deref(), Some("Value2"));
3395 }
3396
3397 #[test]
3398 #[should_panic(expected = "out of bounds")]
3399 fn test_swap_paragraphs_out_of_bounds() {
3400 let mut d: super::Deb822 = vec![
3401 vec![("Foo", "Bar")].into_iter().collect(),
3402 vec![("A", "B")].into_iter().collect(),
3403 ]
3404 .into_iter()
3405 .collect();
3406
3407 d.swap_paragraphs(0, 5);
3408 }
3409
3410 #[test]
3411 fn test_multiline_entry() {
3412 use super::SyntaxKind::*;
3413 use rowan::ast::AstNode;
3414
3415 let entry = super::Entry::new("foo", "bar\nbaz");
3416 let tokens: Vec<_> = entry
3417 .syntax()
3418 .descendants_with_tokens()
3419 .filter_map(|tok| tok.into_token())
3420 .collect();
3421
3422 assert_eq!("foo: bar\n baz\n", entry.to_string());
3423 assert_eq!("bar\nbaz", entry.value());
3424
3425 assert_eq!(
3426 vec![
3427 (KEY, "foo"),
3428 (COLON, ":"),
3429 (WHITESPACE, " "),
3430 (VALUE, "bar"),
3431 (NEWLINE, "\n"),
3432 (INDENT, " "),
3433 (VALUE, "baz"),
3434 (NEWLINE, "\n"),
3435 ],
3436 tokens
3437 .iter()
3438 .map(|token| (token.kind(), token.text()))
3439 .collect::<Vec<_>>()
3440 );
3441 }
3442
3443 #[test]
3444 fn test_apt_entry() {
3445 let text = r#"Package: cvsd
3446Binary: cvsd
3447Version: 1.0.24
3448Maintainer: Arthur de Jong <adejong@debian.org>
3449Build-Depends: debhelper (>= 9), po-debconf
3450Architecture: any
3451Standards-Version: 3.9.3
3452Format: 3.0 (native)
3453Files:
3454 b7a7d67a02974c52c408fdb5e118406d 890 cvsd_1.0.24.dsc
3455 b73ee40774c3086cb8490cdbb96ac883 258139 cvsd_1.0.24.tar.gz
3456Vcs-Browser: http://arthurdejong.org/viewvc/cvsd/
3457Vcs-Cvs: :pserver:anonymous@arthurdejong.org:/arthur/
3458Checksums-Sha256:
3459 a7bb7a3aacee19cd14ce5c26cb86e348b1608e6f1f6e97c6ea7c58efa440ac43 890 cvsd_1.0.24.dsc
3460 46bc517760c1070ae408693b89603986b53e6f068ae6bdc744e2e830e46b8cba 258139 cvsd_1.0.24.tar.gz
3461Homepage: http://arthurdejong.org/cvsd/
3462Package-List:
3463 cvsd deb vcs optional
3464Directory: pool/main/c/cvsd
3465Priority: source
3466Section: vcs
3467
3468"#;
3469 let d: super::Deb822 = text.parse().unwrap();
3470 let p = d.paragraphs().next().unwrap();
3471 assert_eq!(p.get("Binary").as_deref(), Some("cvsd"));
3472 assert_eq!(p.get("Version").as_deref(), Some("1.0.24"));
3473 assert_eq!(
3474 p.get("Maintainer").as_deref(),
3475 Some("Arthur de Jong <adejong@debian.org>")
3476 );
3477 }
3478
3479 #[test]
3480 fn test_format() {
3481 let d: super::Deb822 = r#"Source: foo
3482Maintainer: Foo Bar <foo@example.com>
3483Section: net
3484Blah: blah # comment
3485Multi-Line:
3486 Ahoi!
3487 Matey!
3488
3489"#
3490 .parse()
3491 .unwrap();
3492 let mut ps = d.paragraphs();
3493 let p = ps.next().unwrap();
3494 let result = p.wrap_and_sort(
3495 crate::Indentation::FieldNameLength,
3496 false,
3497 None,
3498 None::<&dyn Fn(&super::Entry, &super::Entry) -> std::cmp::Ordering>,
3499 None,
3500 );
3501 assert_eq!(
3502 result.to_string(),
3503 r#"Source: foo
3504Maintainer: Foo Bar <foo@example.com>
3505Section: net
3506Blah: blah # comment
3507Multi-Line: Ahoi!
3508 Matey!
3509"#
3510 );
3511 }
3512
3513 #[test]
3514 fn test_format_sort_paragraphs() {
3515 let d: super::Deb822 = r#"Source: foo
3516Maintainer: Foo Bar <foo@example.com>
3517
3518# This is a comment
3519Source: bar
3520Maintainer: Bar Foo <bar@example.com>
3521
3522"#
3523 .parse()
3524 .unwrap();
3525 let result = d.wrap_and_sort(
3526 Some(&|a: &super::Paragraph, b: &super::Paragraph| {
3527 a.get("Source").cmp(&b.get("Source"))
3528 }),
3529 Some(&|p| {
3530 p.wrap_and_sort(
3531 crate::Indentation::FieldNameLength,
3532 false,
3533 None,
3534 None::<&dyn Fn(&super::Entry, &super::Entry) -> std::cmp::Ordering>,
3535 None,
3536 )
3537 }),
3538 );
3539 assert_eq!(
3540 result.to_string(),
3541 r#"# This is a comment
3542Source: bar
3543Maintainer: Bar Foo <bar@example.com>
3544
3545Source: foo
3546Maintainer: Foo Bar <foo@example.com>
3547"#,
3548 );
3549 }
3550
3551 #[test]
3552 fn test_format_sort_fields() {
3553 let d: super::Deb822 = r#"Source: foo
3554Maintainer: Foo Bar <foo@example.com>
3555Build-Depends: debhelper (>= 9), po-debconf
3556Homepage: https://example.com/
3557
3558"#
3559 .parse()
3560 .unwrap();
3561 let result = d.wrap_and_sort(
3562 None,
3563 Some(&|p: &super::Paragraph| -> super::Paragraph {
3564 p.wrap_and_sort(
3565 crate::Indentation::FieldNameLength,
3566 false,
3567 None,
3568 Some(&|a: &super::Entry, b: &super::Entry| a.key().cmp(&b.key())),
3569 None,
3570 )
3571 }),
3572 );
3573 assert_eq!(
3574 result.to_string(),
3575 r#"Build-Depends: debhelper (>= 9), po-debconf
3576Homepage: https://example.com/
3577Maintainer: Foo Bar <foo@example.com>
3578Source: foo
3579"#
3580 );
3581 }
3582
3583 #[test]
3584 fn test_para_from_iter() {
3585 let p: super::Paragraph = vec![("Foo", "Bar"), ("Baz", "Qux")].into_iter().collect();
3586 assert_eq!(
3587 p.to_string(),
3588 r#"Foo: Bar
3589Baz: Qux
3590"#
3591 );
3592
3593 let p: super::Paragraph = vec![
3594 ("Foo".to_string(), "Bar".to_string()),
3595 ("Baz".to_string(), "Qux".to_string()),
3596 ]
3597 .into_iter()
3598 .collect();
3599
3600 assert_eq!(
3601 p.to_string(),
3602 r#"Foo: Bar
3603Baz: Qux
3604"#
3605 );
3606 }
3607
3608 #[test]
3609 fn test_deb822_from_iter() {
3610 let d: super::Deb822 = vec![
3611 vec![("Foo", "Bar"), ("Baz", "Qux")].into_iter().collect(),
3612 vec![("A", "B"), ("C", "D")].into_iter().collect(),
3613 ]
3614 .into_iter()
3615 .collect();
3616 assert_eq!(
3617 d.to_string(),
3618 r#"Foo: Bar
3619Baz: Qux
3620
3621A: B
3622C: D
3623"#
3624 );
3625 }
3626
3627 #[test]
3628 fn test_format_parse_error() {
3629 assert_eq!(ParseError(vec!["foo".to_string()]).to_string(), "foo\n");
3630 }
3631
3632 #[test]
3633 fn test_set_with_field_order() {
3634 let mut p = super::Paragraph::new();
3635 let custom_order = &["Foo", "Bar", "Baz"];
3636
3637 p.set_with_field_order("Baz", "3", custom_order);
3638 p.set_with_field_order("Foo", "1", custom_order);
3639 p.set_with_field_order("Bar", "2", custom_order);
3640 p.set_with_field_order("Unknown", "4", custom_order);
3641
3642 let keys: Vec<_> = p.keys().collect();
3643 assert_eq!(keys[0], "Foo");
3644 assert_eq!(keys[1], "Bar");
3645 assert_eq!(keys[2], "Baz");
3646 assert_eq!(keys[3], "Unknown");
3647 }
3648
3649 #[test]
3650 fn test_positioned_parse_error() {
3651 let error = PositionedParseError {
3652 message: "test error".to_string(),
3653 range: rowan::TextRange::new(rowan::TextSize::from(5), rowan::TextSize::from(10)),
3654 code: Some("test_code".to_string()),
3655 };
3656 assert_eq!(error.to_string(), "test error");
3657 assert_eq!(error.range.start(), rowan::TextSize::from(5));
3658 assert_eq!(error.range.end(), rowan::TextSize::from(10));
3659 assert_eq!(error.code, Some("test_code".to_string()));
3660 }
3661
3662 #[test]
3663 fn test_format_error() {
3664 assert_eq!(
3665 super::Error::ParseError(ParseError(vec!["foo".to_string()])).to_string(),
3666 "foo\n"
3667 );
3668 }
3669
3670 #[test]
3671 fn test_get_all() {
3672 let d: super::Deb822 = r#"Source: foo
3673Maintainer: Foo Bar <foo@example.com>
3674Maintainer: Bar Foo <bar@example.com>"#
3675 .parse()
3676 .unwrap();
3677 let p = d.paragraphs().next().unwrap();
3678 assert_eq!(
3679 p.get_all("Maintainer").collect::<Vec<_>>(),
3680 vec!["Foo Bar <foo@example.com>", "Bar Foo <bar@example.com>"]
3681 );
3682 }
3683
3684 #[test]
3685 fn test_get_with_indent_single_line() {
3686 let input = "Field: single line value\n";
3687 let deb = super::Deb822::from_str(input).unwrap();
3688 let para = deb.paragraphs().next().unwrap();
3689
3690 assert_eq!(
3692 para.get_with_indent("Field", &super::IndentPattern::Fixed(2)),
3693 Some("single line value".to_string())
3694 );
3695 assert_eq!(
3696 para.get_with_indent("Field", &super::IndentPattern::FieldNameLength),
3697 Some("single line value".to_string())
3698 );
3699 }
3700
3701 #[test]
3702 fn test_get_with_indent_fixed() {
3703 let input = "Field: First\n Second\n Third\n";
3704 let deb = super::Deb822::from_str(input).unwrap();
3705 let para = deb.paragraphs().next().unwrap();
3706
3707 let value = para
3709 .get_with_indent("Field", &super::IndentPattern::Fixed(2))
3710 .unwrap();
3711 assert_eq!(value, "First\n Second\n Third");
3712
3713 let value = para
3715 .get_with_indent("Field", &super::IndentPattern::Fixed(1))
3716 .unwrap();
3717 assert_eq!(value, "First\n Second\n Third");
3718
3719 let value = para
3721 .get_with_indent("Field", &super::IndentPattern::Fixed(3))
3722 .unwrap();
3723 assert_eq!(value, "First\nSecond\nThird");
3724 }
3725
3726 #[test]
3727 fn test_get_with_indent_field_name_length() {
3728 let input = "Description: First line\n Second line\n Third line\n";
3729 let deb = super::Deb822::from_str(input).unwrap();
3730 let para = deb.paragraphs().next().unwrap();
3731
3732 let value = para
3735 .get_with_indent("Description", &super::IndentPattern::FieldNameLength)
3736 .unwrap();
3737 assert_eq!(value, "First line\nSecond line\nThird line");
3738
3739 let value = para
3741 .get_with_indent("Description", &super::IndentPattern::Fixed(2))
3742 .unwrap();
3743 assert_eq!(
3744 value,
3745 "First line\n Second line\n Third line"
3746 );
3747 }
3748
3749 #[test]
3750 fn test_get_with_indent_nonexistent() {
3751 let input = "Field: value\n";
3752 let deb = super::Deb822::from_str(input).unwrap();
3753 let para = deb.paragraphs().next().unwrap();
3754
3755 assert_eq!(
3756 para.get_with_indent("NonExistent", &super::IndentPattern::Fixed(2)),
3757 None
3758 );
3759 }
3760
3761 #[test]
3762 fn test_get_entry() {
3763 let input = r#"Package: test-package
3764Maintainer: Test User <test@example.com>
3765Description: A simple test package
3766 with multiple lines
3767"#;
3768 let deb = super::Deb822::from_str(input).unwrap();
3769 let para = deb.paragraphs().next().unwrap();
3770
3771 let entry = para.get_entry("Package");
3773 assert!(entry.is_some());
3774 let entry = entry.unwrap();
3775 assert_eq!(entry.key(), Some("Package".to_string()));
3776 assert_eq!(entry.value(), "test-package");
3777
3778 let entry = para.get_entry("package");
3780 assert!(entry.is_some());
3781 assert_eq!(entry.unwrap().value(), "test-package");
3782
3783 let entry = para.get_entry("Description");
3785 assert!(entry.is_some());
3786 assert_eq!(
3787 entry.unwrap().value(),
3788 "A simple test package\nwith multiple lines"
3789 );
3790
3791 assert_eq!(para.get_entry("NonExistent"), None);
3793 }
3794
3795 #[test]
3796 fn test_entry_ranges() {
3797 let input = r#"Package: test-package
3798Maintainer: Test User <test@example.com>
3799Description: A simple test package
3800 with multiple lines
3801 of description text"#;
3802
3803 let deb822 = super::Deb822::from_str(input).unwrap();
3804 let paragraph = deb822.paragraphs().next().unwrap();
3805 let entries: Vec<_> = paragraph.entries().collect();
3806
3807 let package_entry = &entries[0];
3809 assert_eq!(package_entry.key(), Some("Package".to_string()));
3810
3811 let key_range = package_entry.key_range().unwrap();
3813 assert_eq!(
3814 &input[key_range.start().into()..key_range.end().into()],
3815 "Package"
3816 );
3817
3818 let colon_range = package_entry.colon_range().unwrap();
3820 assert_eq!(
3821 &input[colon_range.start().into()..colon_range.end().into()],
3822 ":"
3823 );
3824
3825 let value_range = package_entry.value_range().unwrap();
3827 assert_eq!(
3828 &input[value_range.start().into()..value_range.end().into()],
3829 "test-package"
3830 );
3831
3832 let text_range = package_entry.text_range();
3834 assert_eq!(
3835 &input[text_range.start().into()..text_range.end().into()],
3836 "Package: test-package\n"
3837 );
3838
3839 let value_lines = package_entry.value_line_ranges();
3841 assert_eq!(value_lines.len(), 1);
3842 assert_eq!(
3843 &input[value_lines[0].start().into()..value_lines[0].end().into()],
3844 "test-package"
3845 );
3846 }
3847
3848 #[test]
3849 fn test_multiline_entry_ranges() {
3850 let input = r#"Description: Short description
3851 Extended description line 1
3852 Extended description line 2"#;
3853
3854 let deb822 = super::Deb822::from_str(input).unwrap();
3855 let paragraph = deb822.paragraphs().next().unwrap();
3856 let entry = paragraph.entries().next().unwrap();
3857
3858 assert_eq!(entry.key(), Some("Description".to_string()));
3859
3860 let value_range = entry.value_range().unwrap();
3862 let full_value = &input[value_range.start().into()..value_range.end().into()];
3863 assert!(full_value.contains("Short description"));
3864 assert!(full_value.contains("Extended description line 1"));
3865 assert!(full_value.contains("Extended description line 2"));
3866
3867 let value_lines = entry.value_line_ranges();
3869 assert_eq!(value_lines.len(), 3);
3870
3871 assert_eq!(
3872 &input[value_lines[0].start().into()..value_lines[0].end().into()],
3873 "Short description"
3874 );
3875 assert_eq!(
3876 &input[value_lines[1].start().into()..value_lines[1].end().into()],
3877 "Extended description line 1"
3878 );
3879 assert_eq!(
3880 &input[value_lines[2].start().into()..value_lines[2].end().into()],
3881 "Extended description line 2"
3882 );
3883 }
3884
3885 #[test]
3886 fn test_entries_public_access() {
3887 let input = r#"Package: test
3888Version: 1.0"#;
3889
3890 let deb822 = super::Deb822::from_str(input).unwrap();
3891 let paragraph = deb822.paragraphs().next().unwrap();
3892
3893 let entries: Vec<_> = paragraph.entries().collect();
3895 assert_eq!(entries.len(), 2);
3896 assert_eq!(entries[0].key(), Some("Package".to_string()));
3897 assert_eq!(entries[1].key(), Some("Version".to_string()));
3898 }
3899
3900 #[test]
3901 fn test_empty_value_ranges() {
3902 let input = r#"EmptyField: "#;
3903
3904 let deb822 = super::Deb822::from_str(input).unwrap();
3905 let paragraph = deb822.paragraphs().next().unwrap();
3906 let entry = paragraph.entries().next().unwrap();
3907
3908 assert_eq!(entry.key(), Some("EmptyField".to_string()));
3909
3910 assert!(entry.key_range().is_some());
3912 assert!(entry.colon_range().is_some());
3913
3914 let value_lines = entry.value_line_ranges();
3916 assert!(value_lines.len() <= 1);
3919 }
3920
3921 #[test]
3922 fn test_range_ordering() {
3923 let input = r#"Field: value"#;
3924
3925 let deb822 = super::Deb822::from_str(input).unwrap();
3926 let paragraph = deb822.paragraphs().next().unwrap();
3927 let entry = paragraph.entries().next().unwrap();
3928
3929 let key_range = entry.key_range().unwrap();
3930 let colon_range = entry.colon_range().unwrap();
3931 let value_range = entry.value_range().unwrap();
3932 let text_range = entry.text_range();
3933
3934 assert!(key_range.end() <= colon_range.start());
3936 assert!(colon_range.end() <= value_range.start());
3937 assert!(key_range.start() >= text_range.start());
3938 assert!(value_range.end() <= text_range.end());
3939 }
3940
3941 #[test]
3942 fn test_error_recovery_missing_colon() {
3943 let input = r#"Source foo
3944Maintainer: Test User <test@example.com>
3945"#;
3946 let (deb822, errors) = super::Deb822::from_str_relaxed(input);
3947
3948 assert!(!errors.is_empty());
3950 assert!(errors.iter().any(|e| e.contains("missing colon")));
3951
3952 let paragraph = deb822.paragraphs().next().unwrap();
3954 assert_eq!(
3955 paragraph.get("Maintainer").as_deref(),
3956 Some("Test User <test@example.com>")
3957 );
3958 }
3959
3960 #[test]
3961 fn test_error_recovery_missing_field_name() {
3962 let input = r#": orphaned value
3963Package: test
3964"#;
3965
3966 let (deb822, errors) = super::Deb822::from_str_relaxed(input);
3967
3968 assert!(!errors.is_empty());
3970 assert!(errors
3971 .iter()
3972 .any(|e| e.contains("field name") || e.contains("missing")));
3973
3974 let paragraphs: Vec<_> = deb822.paragraphs().collect();
3976 let mut found_package = false;
3977 for paragraph in paragraphs.iter() {
3978 if paragraph.get("Package").is_some() {
3979 found_package = true;
3980 assert_eq!(paragraph.get("Package").as_deref(), Some("test"));
3981 }
3982 }
3983 assert!(found_package, "Package field not found in any paragraph");
3984 }
3985
3986 #[test]
3987 fn test_error_recovery_orphaned_text() {
3988 let input = r#"Package: test
3989some orphaned text without field name
3990Version: 1.0
3991"#;
3992 let (deb822, errors) = super::Deb822::from_str_relaxed(input);
3993
3994 assert!(!errors.is_empty());
3996 assert!(errors.iter().any(|e| e.contains("orphaned")
3997 || e.contains("unexpected")
3998 || e.contains("field name")));
3999
4000 let mut all_fields = std::collections::HashMap::new();
4002 for paragraph in deb822.paragraphs() {
4003 for (key, value) in paragraph.items() {
4004 all_fields.insert(key, value);
4005 }
4006 }
4007
4008 assert_eq!(all_fields.get("Package"), Some(&"test".to_string()));
4009 assert_eq!(all_fields.get("Version"), Some(&"1.0".to_string()));
4010 }
4011
4012 #[test]
4013 fn test_error_recovery_consecutive_field_names() {
4014 let input = r#"Package: test
4015Description
4016Maintainer: Another field without proper value
4017Version: 1.0
4018"#;
4019 let (deb822, errors) = super::Deb822::from_str_relaxed(input);
4020
4021 assert!(!errors.is_empty());
4023 assert!(errors.iter().any(|e| e.contains("consecutive")
4024 || e.contains("missing")
4025 || e.contains("incomplete")));
4026
4027 let mut all_fields = std::collections::HashMap::new();
4029 for paragraph in deb822.paragraphs() {
4030 for (key, value) in paragraph.items() {
4031 all_fields.insert(key, value);
4032 }
4033 }
4034
4035 assert_eq!(all_fields.get("Package"), Some(&"test".to_string()));
4036 assert_eq!(
4037 all_fields.get("Maintainer"),
4038 Some(&"Another field without proper value".to_string())
4039 );
4040 assert_eq!(all_fields.get("Version"), Some(&"1.0".to_string()));
4041 }
4042
4043 #[test]
4044 fn test_error_recovery_malformed_multiline() {
4045 let input = r#"Package: test
4046Description: Short desc
4047 Proper continuation
4048invalid continuation without indent
4049 Another proper continuation
4050Version: 1.0
4051"#;
4052 let (deb822, errors) = super::Deb822::from_str_relaxed(input);
4053
4054 assert!(!errors.is_empty());
4056
4057 let paragraph = deb822.paragraphs().next().unwrap();
4059 assert_eq!(paragraph.get("Package").as_deref(), Some("test"));
4060 assert_eq!(paragraph.get("Version").as_deref(), Some("1.0"));
4061 }
4062
4063 #[test]
4064 fn test_error_recovery_mixed_errors() {
4065 let input = r#"Package test without colon
4066: orphaned colon
4067Description: Valid field
4068some orphaned text
4069Another-Field: Valid too
4070"#;
4071 let (deb822, errors) = super::Deb822::from_str_relaxed(input);
4072
4073 assert!(!errors.is_empty());
4075 assert!(errors.len() >= 2);
4076
4077 let paragraph = deb822.paragraphs().next().unwrap();
4079 assert_eq!(paragraph.get("Description").as_deref(), Some("Valid field"));
4080 assert_eq!(paragraph.get("Another-Field").as_deref(), Some("Valid too"));
4081 }
4082
4083 #[test]
4084 fn test_error_recovery_paragraph_boundary() {
4085 let input = r#"Package: first-package
4086Description: First paragraph
4087
4088corrupted data here
4089: more corruption
4090completely broken line
4091
4092Package: second-package
4093Version: 1.0
4094"#;
4095 let (deb822, errors) = super::Deb822::from_str_relaxed(input);
4096
4097 assert!(!errors.is_empty());
4099
4100 let paragraphs: Vec<_> = deb822.paragraphs().collect();
4102 assert_eq!(paragraphs.len(), 2);
4103
4104 assert_eq!(
4105 paragraphs[0].get("Package").as_deref(),
4106 Some("first-package")
4107 );
4108 assert_eq!(
4109 paragraphs[1].get("Package").as_deref(),
4110 Some("second-package")
4111 );
4112 assert_eq!(paragraphs[1].get("Version").as_deref(), Some("1.0"));
4113 }
4114
4115 #[test]
4116 fn test_error_recovery_with_positioned_errors() {
4117 let input = r#"Package test
4118Description: Valid
4119"#;
4120 let parsed = super::parse(input);
4121
4122 assert!(!parsed.positioned_errors.is_empty());
4124
4125 let first_error = &parsed.positioned_errors[0];
4126 assert!(!first_error.message.is_empty());
4127 assert!(first_error.range.start() <= first_error.range.end());
4128 assert!(first_error.code.is_some());
4129
4130 let error_text = &input[first_error.range.start().into()..first_error.range.end().into()];
4132 assert!(!error_text.is_empty());
4133 }
4134
4135 #[test]
4136 fn test_error_recovery_preserves_whitespace() {
4137 let input = r#"Source: package
4138Maintainer Test User <test@example.com>
4139Section: utils
4140
4141"#;
4142 let (deb822, errors) = super::Deb822::from_str_relaxed(input);
4143
4144 assert!(!errors.is_empty());
4146
4147 let output = deb822.to_string();
4149 assert!(output.contains("Section: utils"));
4150
4151 let paragraph = deb822.paragraphs().next().unwrap();
4153 assert_eq!(paragraph.get("Source").as_deref(), Some("package"));
4154 assert_eq!(paragraph.get("Section").as_deref(), Some("utils"));
4155 }
4156
4157 #[test]
4158 fn test_error_recovery_empty_fields() {
4159 let input = r#"Package: test
4160Description:
4161Maintainer: Valid User
4162EmptyField:
4163Version: 1.0
4164"#;
4165 let (deb822, _errors) = super::Deb822::from_str_relaxed(input);
4166
4167 let mut all_fields = std::collections::HashMap::new();
4169 for paragraph in deb822.paragraphs() {
4170 for (key, value) in paragraph.items() {
4171 all_fields.insert(key, value);
4172 }
4173 }
4174
4175 assert_eq!(all_fields.get("Package"), Some(&"test".to_string()));
4176 assert_eq!(all_fields.get("Description"), Some(&"".to_string()));
4177 assert_eq!(
4178 all_fields.get("Maintainer"),
4179 Some(&"Valid User".to_string())
4180 );
4181 assert_eq!(all_fields.get("EmptyField"), Some(&"".to_string()));
4182 assert_eq!(all_fields.get("Version"), Some(&"1.0".to_string()));
4183 }
4184
4185 #[test]
4186 fn test_insert_comment_before() {
4187 let d: super::Deb822 = vec![
4188 vec![("Source", "foo"), ("Maintainer", "Bar <bar@example.com>")]
4189 .into_iter()
4190 .collect(),
4191 vec![("Package", "foo"), ("Architecture", "all")]
4192 .into_iter()
4193 .collect(),
4194 ]
4195 .into_iter()
4196 .collect();
4197
4198 let mut p1 = d.paragraphs().next().unwrap();
4200 p1.insert_comment_before("This is the source paragraph");
4201
4202 let mut p2 = d.paragraphs().nth(1).unwrap();
4204 p2.insert_comment_before("This is the binary paragraph");
4205
4206 let output = d.to_string();
4207 assert_eq!(
4208 output,
4209 r#"# This is the source paragraph
4210Source: foo
4211Maintainer: Bar <bar@example.com>
4212
4213# This is the binary paragraph
4214Package: foo
4215Architecture: all
4216"#
4217 );
4218 }
4219
4220 #[test]
4221 fn test_parse_continuation_with_colon() {
4222 let input = "Package: test\nDescription: short\n line: with colon\n";
4224 let result = input.parse::<Deb822>();
4225 assert!(result.is_ok());
4226
4227 let deb822 = result.unwrap();
4228 let para = deb822.paragraphs().next().unwrap();
4229 assert_eq!(para.get("Package").as_deref(), Some("test"));
4230 assert_eq!(
4231 para.get("Description").as_deref(),
4232 Some("short\nline: with colon")
4233 );
4234 }
4235
4236 #[test]
4237 fn test_parse_continuation_starting_with_colon() {
4238 let input = "Package: test\nDescription: short\n :value\n";
4240 let result = input.parse::<Deb822>();
4241 assert!(result.is_ok());
4242
4243 let deb822 = result.unwrap();
4244 let para = deb822.paragraphs().next().unwrap();
4245 assert_eq!(para.get("Package").as_deref(), Some("test"));
4246 assert_eq!(para.get("Description").as_deref(), Some("short\n:value"));
4247 }
4248
4249 #[test]
4250 fn test_normalize_field_spacing_single_space() {
4251 let input = "Field: value\n";
4253 let deb822 = input.parse::<Deb822>().unwrap();
4254 let mut para = deb822.paragraphs().next().unwrap();
4255
4256 para.normalize_field_spacing();
4257 assert_eq!(para.to_string(), "Field: value\n");
4258 }
4259
4260 #[test]
4261 fn test_normalize_field_spacing_extra_spaces() {
4262 let input = "Field: value\n";
4264 let deb822 = input.parse::<Deb822>().unwrap();
4265 let mut para = deb822.paragraphs().next().unwrap();
4266
4267 para.normalize_field_spacing();
4268 assert_eq!(para.to_string(), "Field: value\n");
4269 }
4270
4271 #[test]
4272 fn test_normalize_field_spacing_no_space() {
4273 let input = "Field:value\n";
4275 let deb822 = input.parse::<Deb822>().unwrap();
4276 let mut para = deb822.paragraphs().next().unwrap();
4277
4278 para.normalize_field_spacing();
4279 assert_eq!(para.to_string(), "Field: value\n");
4280 }
4281
4282 #[test]
4283 fn test_normalize_field_spacing_multiple_fields() {
4284 let input = "Field1: value1\nField2:value2\nField3: value3\n";
4286 let deb822 = input.parse::<Deb822>().unwrap();
4287 let mut para = deb822.paragraphs().next().unwrap();
4288
4289 para.normalize_field_spacing();
4290 assert_eq!(
4291 para.to_string(),
4292 "Field1: value1\nField2: value2\nField3: value3\n"
4293 );
4294 }
4295
4296 #[test]
4297 fn test_normalize_field_spacing_multiline_value() {
4298 let input = "Description: short\n continuation line\n . \n final line\n";
4300 let deb822 = input.parse::<Deb822>().unwrap();
4301 let mut para = deb822.paragraphs().next().unwrap();
4302
4303 para.normalize_field_spacing();
4304 assert_eq!(
4305 para.to_string(),
4306 "Description: short\n continuation line\n . \n final line\n"
4307 );
4308 }
4309
4310 #[test]
4311 fn test_normalize_field_spacing_empty_value_with_whitespace() {
4312 let input = "Field: \n";
4314 let deb822 = input.parse::<Deb822>().unwrap();
4315 let mut para = deb822.paragraphs().next().unwrap();
4316
4317 para.normalize_field_spacing();
4318 assert_eq!(para.to_string(), "Field:\n");
4320 }
4321
4322 #[test]
4323 fn test_normalize_field_spacing_no_value() {
4324 let input = "Depends:\n";
4326 let deb822 = input.parse::<Deb822>().unwrap();
4327 let mut para = deb822.paragraphs().next().unwrap();
4328
4329 para.normalize_field_spacing();
4330 assert_eq!(para.to_string(), "Depends:\n");
4332 }
4333
4334 #[test]
4335 fn test_normalize_field_spacing_multiple_paragraphs() {
4336 let input = "Field1: value1\n\nField2: value2\n";
4338 let mut deb822 = input.parse::<Deb822>().unwrap();
4339
4340 deb822.normalize_field_spacing();
4341 assert_eq!(deb822.to_string(), "Field1: value1\n\nField2: value2\n");
4342 }
4343
4344 #[test]
4345 fn test_normalize_field_spacing_preserves_comments() {
4346 let input = "# Comment\nField: value\n";
4348 let mut deb822 = input.parse::<Deb822>().unwrap();
4349
4350 deb822.normalize_field_spacing();
4351 assert_eq!(deb822.to_string(), "# Comment\nField: value\n");
4352 }
4353
4354 #[test]
4355 fn test_normalize_field_spacing_preserves_values() {
4356 let input = "Source: foo-bar\nMaintainer:Foo Bar <test@example.com>\n";
4358 let deb822 = input.parse::<Deb822>().unwrap();
4359 let mut para = deb822.paragraphs().next().unwrap();
4360
4361 para.normalize_field_spacing();
4362
4363 assert_eq!(para.get("Source").as_deref(), Some("foo-bar"));
4364 assert_eq!(
4365 para.get("Maintainer").as_deref(),
4366 Some("Foo Bar <test@example.com>")
4367 );
4368 }
4369
4370 #[test]
4371 fn test_normalize_field_spacing_tab_after_colon() {
4372 let input = "Field:\tvalue\n";
4374 let deb822 = input.parse::<Deb822>().unwrap();
4375 let mut para = deb822.paragraphs().next().unwrap();
4376
4377 para.normalize_field_spacing();
4378 assert_eq!(para.to_string(), "Field: value\n");
4379 }
4380
4381 #[test]
4382 fn test_set_preserves_indentation() {
4383 let original = r#"Source: example
4385Build-Depends: foo,
4386 bar,
4387 baz
4388"#;
4389
4390 let mut para: super::Paragraph = original.parse().unwrap();
4391
4392 para.set("Build-Depends", "foo,\nbar,\nbaz");
4394
4395 let expected = r#"Source: example
4397Build-Depends: foo,
4398 bar,
4399 baz
4400"#;
4401 assert_eq!(para.to_string(), expected);
4402 }
4403
4404 #[test]
4405 fn test_set_new_field_detects_field_name_length_indent() {
4406 let original = r#"Source: example
4408Build-Depends: foo,
4409 bar,
4410 baz
4411Depends: lib1,
4412 lib2
4413"#;
4414
4415 let mut para: super::Paragraph = original.parse().unwrap();
4416
4417 para.set("Recommends", "pkg1,\npkg2,\npkg3");
4419
4420 assert!(para
4422 .to_string()
4423 .contains("Recommends: pkg1,\n pkg2,"));
4424 }
4425
4426 #[test]
4427 fn test_set_new_field_detects_fixed_indent() {
4428 let original = r#"Source: example
4430Build-Depends: foo,
4431 bar,
4432 baz
4433Depends: lib1,
4434 lib2
4435"#;
4436
4437 let mut para: super::Paragraph = original.parse().unwrap();
4438
4439 para.set("Recommends", "pkg1,\npkg2,\npkg3");
4441
4442 assert!(para
4444 .to_string()
4445 .contains("Recommends: pkg1,\n pkg2,\n pkg3\n"));
4446 }
4447
4448 #[test]
4449 fn test_set_new_field_no_multiline_fields() {
4450 let original = r#"Source: example
4452Maintainer: Test <test@example.com>
4453"#;
4454
4455 let mut para: super::Paragraph = original.parse().unwrap();
4456
4457 para.set("Depends", "foo,\nbar,\nbaz");
4459
4460 let expected = r#"Source: example
4462Maintainer: Test <test@example.com>
4463Depends: foo,
4464 bar,
4465 baz
4466"#;
4467 assert_eq!(para.to_string(), expected);
4468 }
4469
4470 #[test]
4471 fn test_set_new_field_mixed_indentation() {
4472 let original = r#"Source: example
4474Build-Depends: foo,
4475 bar
4476Depends: lib1,
4477 lib2
4478"#;
4479
4480 let mut para: super::Paragraph = original.parse().unwrap();
4481
4482 para.set("Recommends", "pkg1,\npkg2");
4484
4485 assert!(para
4487 .to_string()
4488 .contains("Recommends: pkg1,\n pkg2\n"));
4489 }
4490
4491 #[test]
4492 fn test_entry_with_indentation() {
4493 let entry = super::Entry::with_indentation("Test-Field", "value1\nvalue2\nvalue3", " ");
4495
4496 assert_eq!(
4497 entry.to_string(),
4498 "Test-Field: value1\n value2\n value3\n"
4499 );
4500 }
4501
4502 #[test]
4503 fn test_set_with_indent_pattern_fixed() {
4504 let original = r#"Source: example
4506Maintainer: Test <test@example.com>
4507"#;
4508
4509 let mut para: super::Paragraph = original.parse().unwrap();
4510
4511 para.set_with_indent_pattern(
4513 "Depends",
4514 "foo,\nbar,\nbaz",
4515 Some(&super::IndentPattern::Fixed(4)),
4516 None,
4517 );
4518
4519 let expected = r#"Source: example
4521Maintainer: Test <test@example.com>
4522Depends: foo,
4523 bar,
4524 baz
4525"#;
4526 assert_eq!(para.to_string(), expected);
4527 }
4528
4529 #[test]
4530 fn test_set_with_indent_pattern_field_name_length() {
4531 let original = r#"Source: example
4533Maintainer: Test <test@example.com>
4534"#;
4535
4536 let mut para: super::Paragraph = original.parse().unwrap();
4537
4538 para.set_with_indent_pattern(
4540 "Build-Depends",
4541 "libfoo,\nlibbar,\nlibbaz",
4542 Some(&super::IndentPattern::FieldNameLength),
4543 None,
4544 );
4545
4546 let expected = r#"Source: example
4548Maintainer: Test <test@example.com>
4549Build-Depends: libfoo,
4550 libbar,
4551 libbaz
4552"#;
4553 assert_eq!(para.to_string(), expected);
4554 }
4555
4556 #[test]
4557 fn test_set_with_indent_pattern_override_auto_detection() {
4558 let original = r#"Source: example
4560Build-Depends: foo,
4561 bar,
4562 baz
4563"#;
4564
4565 let mut para: super::Paragraph = original.parse().unwrap();
4566
4567 para.set_with_indent_pattern(
4569 "Depends",
4570 "lib1,\nlib2,\nlib3",
4571 Some(&super::IndentPattern::Fixed(2)),
4572 None,
4573 );
4574
4575 let expected = r#"Source: example
4577Build-Depends: foo,
4578 bar,
4579 baz
4580Depends: lib1,
4581 lib2,
4582 lib3
4583"#;
4584 assert_eq!(para.to_string(), expected);
4585 }
4586
4587 #[test]
4588 fn test_set_with_indent_pattern_none_auto_detects() {
4589 let original = r#"Source: example
4591Build-Depends: foo,
4592 bar,
4593 baz
4594"#;
4595
4596 let mut para: super::Paragraph = original.parse().unwrap();
4597
4598 para.set_with_indent_pattern("Depends", "lib1,\nlib2", None, None);
4600
4601 let expected = r#"Source: example
4603Build-Depends: foo,
4604 bar,
4605 baz
4606Depends: lib1,
4607 lib2
4608"#;
4609 assert_eq!(para.to_string(), expected);
4610 }
4611
4612 #[test]
4613 fn test_set_with_indent_pattern_with_field_order() {
4614 let original = r#"Source: example
4616Maintainer: Test <test@example.com>
4617"#;
4618
4619 let mut para: super::Paragraph = original.parse().unwrap();
4620
4621 para.set_with_indent_pattern(
4623 "Priority",
4624 "optional",
4625 Some(&super::IndentPattern::Fixed(4)),
4626 Some(&["Source", "Priority", "Maintainer"]),
4627 );
4628
4629 let expected = r#"Source: example
4631Priority: optional
4632Maintainer: Test <test@example.com>
4633"#;
4634 assert_eq!(para.to_string(), expected);
4635 }
4636
4637 #[test]
4638 fn test_set_with_indent_pattern_replace_existing() {
4639 let original = r#"Source: example
4641Depends: foo,
4642 bar
4643"#;
4644
4645 let mut para: super::Paragraph = original.parse().unwrap();
4646
4647 para.set_with_indent_pattern(
4649 "Depends",
4650 "lib1,\nlib2,\nlib3",
4651 Some(&super::IndentPattern::Fixed(3)),
4652 None,
4653 );
4654
4655 let expected = r#"Source: example
4657Depends: lib1,
4658 lib2,
4659 lib3
4660"#;
4661 assert_eq!(para.to_string(), expected);
4662 }
4663
4664 #[test]
4665 fn test_change_field_indent() {
4666 let original = r#"Source: example
4668Depends: foo,
4669 bar,
4670 baz
4671"#;
4672 let mut para: super::Paragraph = original.parse().unwrap();
4673
4674 let result = para
4676 .change_field_indent("Depends", &super::IndentPattern::Fixed(2))
4677 .unwrap();
4678 assert!(result, "Field should have been found and updated");
4679
4680 let expected = r#"Source: example
4681Depends: foo,
4682 bar,
4683 baz
4684"#;
4685 assert_eq!(para.to_string(), expected);
4686 }
4687
4688 #[test]
4689 fn test_change_field_indent_nonexistent() {
4690 let original = r#"Source: example
4692"#;
4693 let mut para: super::Paragraph = original.parse().unwrap();
4694
4695 let result = para
4697 .change_field_indent("Depends", &super::IndentPattern::Fixed(2))
4698 .unwrap();
4699 assert!(!result, "Should return false for non-existent field");
4700
4701 assert_eq!(para.to_string(), original);
4703 }
4704
4705 #[test]
4706 fn test_change_field_indent_case_insensitive() {
4707 let original = r#"Build-Depends: foo,
4709 bar
4710"#;
4711 let mut para: super::Paragraph = original.parse().unwrap();
4712
4713 let result = para
4715 .change_field_indent("build-depends", &super::IndentPattern::Fixed(1))
4716 .unwrap();
4717 assert!(result, "Should find field case-insensitively");
4718
4719 let expected = r#"Build-Depends: foo,
4720 bar
4721"#;
4722 assert_eq!(para.to_string(), expected);
4723 }
4724
4725 #[test]
4726 fn test_entry_get_indent() {
4727 let original = r#"Build-Depends: foo,
4729 bar,
4730 baz
4731"#;
4732 let para: super::Paragraph = original.parse().unwrap();
4733 let entry = para.entries().next().unwrap();
4734
4735 assert_eq!(entry.get_indent(), Some(" ".to_string()));
4736 }
4737
4738 #[test]
4739 fn test_entry_get_indent_single_line() {
4740 let original = r#"Source: example
4742"#;
4743 let para: super::Paragraph = original.parse().unwrap();
4744 let entry = para.entries().next().unwrap();
4745
4746 assert_eq!(entry.get_indent(), None);
4747 }
4748}
4749
4750#[test]
4751fn test_move_paragraph_forward() {
4752 let mut d: Deb822 = vec![
4753 vec![("Foo", "Bar"), ("Baz", "Qux")].into_iter().collect(),
4754 vec![("A", "B"), ("C", "D")].into_iter().collect(),
4755 vec![("X", "Y"), ("Z", "W")].into_iter().collect(),
4756 ]
4757 .into_iter()
4758 .collect();
4759 d.move_paragraph(0, 2);
4760 assert_eq!(
4761 d.to_string(),
4762 "A: B\nC: D\n\nX: Y\nZ: W\n\nFoo: Bar\nBaz: Qux\n"
4763 );
4764}
4765
4766#[test]
4767fn test_move_paragraph_backward() {
4768 let mut d: Deb822 = vec![
4769 vec![("Foo", "Bar"), ("Baz", "Qux")].into_iter().collect(),
4770 vec![("A", "B"), ("C", "D")].into_iter().collect(),
4771 vec![("X", "Y"), ("Z", "W")].into_iter().collect(),
4772 ]
4773 .into_iter()
4774 .collect();
4775 d.move_paragraph(2, 0);
4776 assert_eq!(
4777 d.to_string(),
4778 "X: Y\nZ: W\n\nFoo: Bar\nBaz: Qux\n\nA: B\nC: D\n"
4779 );
4780}
4781
4782#[test]
4783fn test_move_paragraph_middle() {
4784 let mut d: Deb822 = vec![
4785 vec![("Foo", "Bar"), ("Baz", "Qux")].into_iter().collect(),
4786 vec![("A", "B"), ("C", "D")].into_iter().collect(),
4787 vec![("X", "Y"), ("Z", "W")].into_iter().collect(),
4788 ]
4789 .into_iter()
4790 .collect();
4791 d.move_paragraph(2, 1);
4792 assert_eq!(
4793 d.to_string(),
4794 "Foo: Bar\nBaz: Qux\n\nX: Y\nZ: W\n\nA: B\nC: D\n"
4795 );
4796}
4797
4798#[test]
4799fn test_move_paragraph_same_index() {
4800 let mut d: Deb822 = vec![
4801 vec![("Foo", "Bar"), ("Baz", "Qux")].into_iter().collect(),
4802 vec![("A", "B"), ("C", "D")].into_iter().collect(),
4803 ]
4804 .into_iter()
4805 .collect();
4806 let original = d.to_string();
4807 d.move_paragraph(1, 1);
4808 assert_eq!(d.to_string(), original);
4809}
4810
4811#[test]
4812fn test_move_paragraph_single() {
4813 let mut d: Deb822 = vec![vec![("Foo", "Bar")].into_iter().collect()]
4814 .into_iter()
4815 .collect();
4816 let original = d.to_string();
4817 d.move_paragraph(0, 0);
4818 assert_eq!(d.to_string(), original);
4819}
4820
4821#[test]
4822fn test_move_paragraph_invalid_index() {
4823 let mut d: Deb822 = vec![
4824 vec![("Foo", "Bar")].into_iter().collect(),
4825 vec![("A", "B")].into_iter().collect(),
4826 ]
4827 .into_iter()
4828 .collect();
4829 let original = d.to_string();
4830 d.move_paragraph(0, 5);
4831 assert_eq!(d.to_string(), original);
4832}
4833
4834#[test]
4835fn test_move_paragraph_with_comments() {
4836 let text = r#"Foo: Bar
4837
4838# This is a comment
4839
4840A: B
4841
4842X: Y
4843"#;
4844 let mut d: Deb822 = text.parse().unwrap();
4845 d.move_paragraph(0, 2);
4846 assert_eq!(
4847 d.to_string(),
4848 "# This is a comment\n\nA: B\n\nX: Y\n\nFoo: Bar\n"
4849 );
4850}
4851
4852#[test]
4853fn test_case_insensitive_get() {
4854 let text = "Package: test\nVersion: 1.0\n";
4855 let d: Deb822 = text.parse().unwrap();
4856 let p = d.paragraphs().next().unwrap();
4857
4858 assert_eq!(p.get("Package").as_deref(), Some("test"));
4860 assert_eq!(p.get("package").as_deref(), Some("test"));
4861 assert_eq!(p.get("PACKAGE").as_deref(), Some("test"));
4862 assert_eq!(p.get("PaCkAgE").as_deref(), Some("test"));
4863
4864 assert_eq!(p.get("Version").as_deref(), Some("1.0"));
4865 assert_eq!(p.get("version").as_deref(), Some("1.0"));
4866 assert_eq!(p.get("VERSION").as_deref(), Some("1.0"));
4867}
4868
4869#[test]
4870fn test_case_insensitive_set() {
4871 let text = "Package: test\n";
4872 let d: Deb822 = text.parse().unwrap();
4873 let mut p = d.paragraphs().next().unwrap();
4874
4875 p.set("package", "updated");
4877 assert_eq!(p.get("Package").as_deref(), Some("updated"));
4878 assert_eq!(p.get("package").as_deref(), Some("updated"));
4879
4880 p.set("PACKAGE", "updated2");
4882 assert_eq!(p.get("Package").as_deref(), Some("updated2"));
4883
4884 assert_eq!(p.keys().count(), 1);
4886}
4887
4888#[test]
4889fn test_case_insensitive_remove() {
4890 let text = "Package: test\nVersion: 1.0\n";
4891 let d: Deb822 = text.parse().unwrap();
4892 let mut p = d.paragraphs().next().unwrap();
4893
4894 p.remove("package");
4896 assert_eq!(p.get("Package"), None);
4897 assert_eq!(p.get("Version").as_deref(), Some("1.0"));
4898
4899 p.remove("VERSION");
4901 assert_eq!(p.get("Version"), None);
4902
4903 assert_eq!(p.keys().count(), 0);
4905}
4906
4907#[test]
4908fn test_case_preservation() {
4909 let text = "Package: test\n";
4910 let d: Deb822 = text.parse().unwrap();
4911 let mut p = d.paragraphs().next().unwrap();
4912
4913 let original_text = d.to_string();
4915 assert_eq!(original_text, "Package: test\n");
4916
4917 p.set("package", "updated");
4919
4920 let updated_text = d.to_string();
4922 assert_eq!(updated_text, "Package: updated\n");
4923}
4924
4925#[test]
4926fn test_case_insensitive_contains_key() {
4927 let text = "Package: test\n";
4928 let d: Deb822 = text.parse().unwrap();
4929 let p = d.paragraphs().next().unwrap();
4930
4931 assert!(p.contains_key("Package"));
4932 assert!(p.contains_key("package"));
4933 assert!(p.contains_key("PACKAGE"));
4934 assert!(!p.contains_key("NonExistent"));
4935}
4936
4937#[test]
4938fn test_case_insensitive_get_all() {
4939 let text = "Package: test1\npackage: test2\n";
4940 let d: Deb822 = text.parse().unwrap();
4941 let p = d.paragraphs().next().unwrap();
4942
4943 let values: Vec<String> = p.get_all("PACKAGE").collect();
4944 assert_eq!(values, vec!["test1", "test2"]);
4945}
4946
4947#[test]
4948fn test_case_insensitive_rename() {
4949 let text = "Package: test\n";
4950 let d: Deb822 = text.parse().unwrap();
4951 let mut p = d.paragraphs().next().unwrap();
4952
4953 assert!(p.rename("package", "NewName"));
4955 assert_eq!(p.get("NewName").as_deref(), Some("test"));
4956 assert_eq!(p.get("Package"), None);
4957}
4958
4959#[test]
4960fn test_rename_changes_case() {
4961 let text = "Package: test\n";
4962 let d: Deb822 = text.parse().unwrap();
4963 let mut p = d.paragraphs().next().unwrap();
4964
4965 assert!(p.rename("package", "PACKAGE"));
4967
4968 let updated_text = d.to_string();
4970 assert_eq!(updated_text, "PACKAGE: test\n");
4971
4972 assert_eq!(p.get("package").as_deref(), Some("test"));
4974 assert_eq!(p.get("Package").as_deref(), Some("test"));
4975 assert_eq!(p.get("PACKAGE").as_deref(), Some("test"));
4976}
4977
4978#[test]
4979fn test_reject_whitespace_only_continuation_line() {
4980 let text = "Build-Depends:\n \ndebhelper\n";
4986 let parsed = Deb822::parse(text);
4987
4988 assert!(
4991 !parsed.errors().is_empty(),
4992 "Expected parse errors for whitespace-only continuation line"
4993 );
4994}
4995
4996#[test]
4997fn test_reject_empty_continuation_line_in_multiline_field() {
4998 let text = "Depends: foo,\n bar,\n \n baz\n";
5000 let parsed = Deb822::parse(text);
5001
5002 assert!(
5004 !parsed.errors().is_empty(),
5005 "Empty continuation line should generate parse errors"
5006 );
5007
5008 let has_empty_line_error = parsed
5010 .errors()
5011 .iter()
5012 .any(|e| e.contains("empty continuation line"));
5013 assert!(
5014 has_empty_line_error,
5015 "Should have an error about empty continuation line"
5016 );
5017}
5018
5019#[test]
5020#[should_panic(expected = "empty continuation line")]
5021fn test_set_rejects_empty_continuation_lines() {
5022 let text = "Package: test\n";
5024 let deb822 = text.parse::<Deb822>().unwrap();
5025 let mut para = deb822.paragraphs().next().unwrap();
5026
5027 let value_with_empty_line = "foo\n \nbar";
5030 para.set("Depends", value_with_empty_line);
5031}
5032
5033#[test]
5034fn test_try_set_returns_error_for_empty_continuation_lines() {
5035 let text = "Package: test\n";
5037 let deb822 = text.parse::<Deb822>().unwrap();
5038 let mut para = deb822.paragraphs().next().unwrap();
5039
5040 let value_with_empty_line = "foo\n \nbar";
5042 let result = para.try_set("Depends", value_with_empty_line);
5043
5044 assert!(
5046 result.is_err(),
5047 "try_set() should return an error for empty continuation lines"
5048 );
5049
5050 match result {
5052 Err(Error::InvalidValue(msg)) => {
5053 assert!(
5054 msg.contains("empty continuation line"),
5055 "Error message should mention empty continuation line"
5056 );
5057 }
5058 _ => panic!("Expected InvalidValue error"),
5059 }
5060}
5061
5062#[test]
5063fn test_try_set_with_indent_pattern_returns_error() {
5064 let text = "Package: test\n";
5066 let deb822 = text.parse::<Deb822>().unwrap();
5067 let mut para = deb822.paragraphs().next().unwrap();
5068
5069 let value_with_empty_line = "foo\n \nbar";
5070 let result = para.try_set_with_indent_pattern(
5071 "Depends",
5072 value_with_empty_line,
5073 Some(&IndentPattern::Fixed(2)),
5074 None,
5075 );
5076
5077 assert!(
5078 result.is_err(),
5079 "try_set_with_indent_pattern() should return an error"
5080 );
5081}
5082
5083#[test]
5084fn test_try_set_succeeds_for_valid_value() {
5085 let text = "Package: test\n";
5087 let deb822 = text.parse::<Deb822>().unwrap();
5088 let mut para = deb822.paragraphs().next().unwrap();
5089
5090 let valid_value = "foo\nbar";
5092 let result = para.try_set("Depends", valid_value);
5093
5094 assert!(result.is_ok(), "try_set() should succeed for valid values");
5095 assert_eq!(para.get("Depends").as_deref(), Some("foo\nbar"));
5096}
5097
5098#[test]
5099fn test_field_with_empty_first_line() {
5100 let text = "Foo:\n blah\n blah\n";
5103 let parsed = Deb822::parse(text);
5104
5105 assert!(
5107 parsed.errors().is_empty(),
5108 "Empty first line should be valid. Got errors: {:?}",
5109 parsed.errors()
5110 );
5111
5112 let deb822 = parsed.tree();
5113 let para = deb822.paragraphs().next().unwrap();
5114 assert_eq!(para.get("Foo").as_deref(), Some("blah\nblah"));
5115}
5116
5117#[test]
5118fn test_try_set_with_empty_first_line() {
5119 let text = "Package: test\n";
5121 let deb822 = text.parse::<Deb822>().unwrap();
5122 let mut para = deb822.paragraphs().next().unwrap();
5123
5124 let value = "\nblah\nmore";
5126 let result = para.try_set("Depends", value);
5127
5128 assert!(
5129 result.is_ok(),
5130 "try_set() should succeed for values with empty first line. Got: {:?}",
5131 result
5132 );
5133}
5134
5135#[test]
5136fn test_field_with_value_then_empty_continuation() {
5137 let text = "Foo: bar\n \n";
5139 let parsed = Deb822::parse(text);
5140
5141 assert!(
5143 !parsed.errors().is_empty(),
5144 "Field with value then empty continuation line should be rejected"
5145 );
5146
5147 let has_empty_line_error = parsed
5149 .errors()
5150 .iter()
5151 .any(|e| e.contains("empty continuation line"));
5152 assert!(
5153 has_empty_line_error,
5154 "Should have error about empty continuation line"
5155 );
5156}
5157
5158#[test]
5159fn test_line_col() {
5160 let text = r#"Source: foo
5161Maintainer: Foo Bar <jelmer@jelmer.uk>
5162Section: net
5163
5164Package: foo
5165Architecture: all
5166Depends: libc6
5167Description: This is a description
5168 With details
5169"#;
5170 let deb822 = text.parse::<Deb822>().unwrap();
5171
5172 let paras: Vec<_> = deb822.paragraphs().collect();
5174 assert_eq!(paras.len(), 2);
5175
5176 assert_eq!(paras[0].line(), 0);
5178 assert_eq!(paras[0].column(), 0);
5179
5180 assert_eq!(paras[1].line(), 4);
5182 assert_eq!(paras[1].column(), 0);
5183
5184 let entries: Vec<_> = paras[0].entries().collect();
5186 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));
5196 assert_eq!(entries[0].line_col(), (0, 0));
5197
5198 let second_para_entries: Vec<_> = paras[1].entries().collect();
5200 assert_eq!(second_para_entries[3].line(), 7); }
5202
5203#[test]
5204fn test_deb822_snapshot_independence() {
5205 let text = r#"Source: foo
5207Maintainer: Joe <joe@example.com>
5208
5209Package: foo
5210Architecture: all
5211"#;
5212 let deb822 = text.parse::<Deb822>().unwrap();
5213 let snapshot = deb822.snapshot();
5214
5215 let mut para = deb822.paragraphs().next().unwrap();
5217 para.set("Source", "modified");
5218
5219 let snapshot_para = snapshot.paragraphs().next().unwrap();
5221 assert_eq!(snapshot_para.get("Source").as_deref(), Some("foo"));
5222
5223 let mut snapshot_para = snapshot.paragraphs().next().unwrap();
5225 snapshot_para.set("Source", "snapshot-modified");
5226
5227 let para = deb822.paragraphs().next().unwrap();
5229 assert_eq!(para.get("Source").as_deref(), Some("modified"));
5230}
5231
5232#[test]
5233fn test_paragraph_snapshot_independence() {
5234 let text = "Package: foo\nArchitecture: all\n";
5236 let deb822 = text.parse::<Deb822>().unwrap();
5237 let mut para = deb822.paragraphs().next().unwrap();
5238 let mut snapshot = para.snapshot();
5239
5240 para.set("Package", "modified");
5242
5243 assert_eq!(snapshot.get("Package").as_deref(), Some("foo"));
5245
5246 snapshot.set("Package", "snapshot-modified");
5248
5249 assert_eq!(para.get("Package").as_deref(), Some("modified"));
5251}
5252
5253#[test]
5254fn test_entry_snapshot_independence() {
5255 let text = "Package: foo\n";
5257 let deb822 = text.parse::<Deb822>().unwrap();
5258 let mut para = deb822.paragraphs().next().unwrap();
5259 let entry = para.entries().next().unwrap();
5260 let snapshot = entry.snapshot();
5261
5262 let original_value = entry.value();
5264 let snapshot_value = snapshot.value();
5265
5266 assert_eq!(original_value, "foo");
5268 assert_eq!(snapshot_value, "foo");
5269
5270 para.set("Package", "modified");
5272
5273 let entry_after = para.entries().next().unwrap();
5275 assert_eq!(entry_after.value(), "modified");
5276
5277 assert_eq!(snapshot.value(), "foo");
5280}
5281
5282#[test]
5283fn test_snapshot_preserves_structure() {
5284 let text = r#"# Comment
5286Source: foo
5287## Another comment
5288Maintainer: Joe <joe@example.com>
5289
5290Package: foo
5291Architecture: all
5292"#;
5293 let deb822 = text.parse::<Deb822>().unwrap();
5294 let snapshot = deb822.snapshot();
5295
5296 assert_eq!(deb822.to_string(), snapshot.to_string());
5298
5299 let mut snapshot_para = snapshot.paragraphs().next().unwrap();
5301 snapshot_para.set("Source", "modified");
5302
5303 let original_para = deb822.paragraphs().next().unwrap();
5304 assert_eq!(original_para.get("Source").as_deref(), Some("foo"));
5305}
5306
5307#[test]
5308fn test_paragraph_text_range() {
5309 let text = r#"Source: foo
5311Maintainer: Joe <joe@example.com>
5312
5313Package: foo
5314Architecture: all
5315"#;
5316 let deb822 = text.parse::<Deb822>().unwrap();
5317 let paras: Vec<_> = deb822.paragraphs().collect();
5318
5319 let range1 = paras[0].text_range();
5321 let para1_text = &text[range1.start().into()..range1.end().into()];
5322 assert_eq!(
5323 para1_text,
5324 "Source: foo\nMaintainer: Joe <joe@example.com>\n"
5325 );
5326
5327 let range2 = paras[1].text_range();
5329 let para2_text = &text[range2.start().into()..range2.end().into()];
5330 assert_eq!(para2_text, "Package: foo\nArchitecture: all\n");
5331}
5332
5333#[test]
5334fn test_paragraphs_in_range_single() {
5335 let text = r#"Source: foo
5337
5338Package: bar
5339
5340Package: baz
5341"#;
5342 let deb822 = text.parse::<Deb822>().unwrap();
5343
5344 let first_para = deb822.paragraphs().next().unwrap();
5346 let range = first_para.text_range();
5347
5348 let paras: Vec<_> = deb822.paragraphs_in_range(range).collect();
5350 assert_eq!(paras.len(), 1);
5351 assert_eq!(paras[0].get("Source").as_deref(), Some("foo"));
5352}
5353
5354#[test]
5355fn test_paragraphs_in_range_multiple() {
5356 let text = r#"Source: foo
5358
5359Package: bar
5360
5361Package: baz
5362"#;
5363 let deb822 = text.parse::<Deb822>().unwrap();
5364
5365 let range = rowan::TextRange::new(0.into(), 25.into());
5367
5368 let paras: Vec<_> = deb822.paragraphs_in_range(range).collect();
5370 assert_eq!(paras.len(), 2);
5371 assert_eq!(paras[0].get("Source").as_deref(), Some("foo"));
5372 assert_eq!(paras[1].get("Package").as_deref(), Some("bar"));
5373}
5374
5375#[test]
5376fn test_paragraphs_in_range_partial_overlap() {
5377 let text = r#"Source: foo
5379
5380Package: bar
5381
5382Package: baz
5383"#;
5384 let deb822 = text.parse::<Deb822>().unwrap();
5385
5386 let range = rowan::TextRange::new(15.into(), 30.into());
5388
5389 let paras: Vec<_> = deb822.paragraphs_in_range(range).collect();
5391 assert!(paras.len() >= 1);
5392 assert!(paras
5393 .iter()
5394 .any(|p| p.get("Package").as_deref() == Some("bar")));
5395}
5396
5397#[test]
5398fn test_paragraphs_in_range_no_match() {
5399 let text = r#"Source: foo
5401
5402Package: bar
5403"#;
5404 let deb822 = text.parse::<Deb822>().unwrap();
5405
5406 let range = rowan::TextRange::new(1000.into(), 2000.into());
5408
5409 let paras: Vec<_> = deb822.paragraphs_in_range(range).collect();
5411 assert_eq!(paras.len(), 0);
5412}
5413
5414#[test]
5415fn test_paragraphs_in_range_all() {
5416 let text = r#"Source: foo
5418
5419Package: bar
5420
5421Package: baz
5422"#;
5423 let deb822 = text.parse::<Deb822>().unwrap();
5424
5425 let range = rowan::TextRange::new(0.into(), text.len().try_into().unwrap());
5427
5428 let paras: Vec<_> = deb822.paragraphs_in_range(range).collect();
5430 assert_eq!(paras.len(), 3);
5431}
5432
5433#[test]
5434fn test_paragraph_at_position() {
5435 let text = r#"Package: foo
5437Version: 1.0
5438
5439Package: bar
5440Architecture: all
5441"#;
5442 let deb822 = text.parse::<Deb822>().unwrap();
5443
5444 let para = deb822.paragraph_at_position(rowan::TextSize::from(5));
5446 assert!(para.is_some());
5447 assert_eq!(para.unwrap().get("Package").as_deref(), Some("foo"));
5448
5449 let para = deb822.paragraph_at_position(rowan::TextSize::from(30));
5451 assert!(para.is_some());
5452 assert_eq!(para.unwrap().get("Package").as_deref(), Some("bar"));
5453
5454 let para = deb822.paragraph_at_position(rowan::TextSize::from(1000));
5456 assert!(para.is_none());
5457}
5458
5459#[test]
5460fn test_paragraph_at_line() {
5461 let text = r#"Package: foo
5463Version: 1.0
5464
5465Package: bar
5466Architecture: all
5467"#;
5468 let deb822 = text.parse::<Deb822>().unwrap();
5469
5470 let para = deb822.paragraph_at_line(0);
5472 assert!(para.is_some());
5473 assert_eq!(para.unwrap().get("Package").as_deref(), Some("foo"));
5474
5475 let para = deb822.paragraph_at_line(1);
5477 assert!(para.is_some());
5478 assert_eq!(para.unwrap().get("Package").as_deref(), Some("foo"));
5479
5480 let para = deb822.paragraph_at_line(3);
5482 assert!(para.is_some());
5483 assert_eq!(para.unwrap().get("Package").as_deref(), Some("bar"));
5484
5485 let para = deb822.paragraph_at_line(100);
5487 assert!(para.is_none());
5488}
5489
5490#[test]
5491fn test_entry_at_line_col() {
5492 let text = r#"Package: foo
5494Version: 1.0
5495Architecture: all
5496"#;
5497 let deb822 = text.parse::<Deb822>().unwrap();
5498
5499 let entry = deb822.entry_at_line_col(0, 0);
5501 assert!(entry.is_some());
5502 assert_eq!(entry.unwrap().key(), Some("Package".to_string()));
5503
5504 let entry = deb822.entry_at_line_col(1, 0);
5506 assert!(entry.is_some());
5507 assert_eq!(entry.unwrap().key(), Some("Version".to_string()));
5508
5509 let entry = deb822.entry_at_line_col(2, 5);
5511 assert!(entry.is_some());
5512 assert_eq!(entry.unwrap().key(), Some("Architecture".to_string()));
5513
5514 let entry = deb822.entry_at_line_col(100, 0);
5516 assert!(entry.is_none());
5517}
5518
5519#[test]
5520fn test_entry_at_line_col_multiline() {
5521 let text = r#"Package: foo
5523Description: A package
5524 with a long
5525 description
5526Version: 1.0
5527"#;
5528 let deb822 = text.parse::<Deb822>().unwrap();
5529
5530 let entry = deb822.entry_at_line_col(1, 0);
5532 assert!(entry.is_some());
5533 assert_eq!(entry.unwrap().key(), Some("Description".to_string()));
5534
5535 let entry = deb822.entry_at_line_col(2, 1);
5537 assert!(entry.is_some());
5538 assert_eq!(entry.unwrap().key(), Some("Description".to_string()));
5539
5540 let entry = deb822.entry_at_line_col(3, 1);
5542 assert!(entry.is_some());
5543 assert_eq!(entry.unwrap().key(), Some("Description".to_string()));
5544
5545 let entry = deb822.entry_at_line_col(4, 0);
5547 assert!(entry.is_some());
5548 assert_eq!(entry.unwrap().key(), Some("Version".to_string()));
5549}
5550
5551#[test]
5552fn test_entries_in_range() {
5553 let text = r#"Package: foo
5555Version: 1.0
5556Architecture: all
5557"#;
5558 let deb822 = text.parse::<Deb822>().unwrap();
5559 let para = deb822.paragraphs().next().unwrap();
5560
5561 let first_entry = para.entries().next().unwrap();
5563 let range = first_entry.text_range();
5564
5565 let entries: Vec<_> = para.entries_in_range(range).collect();
5567 assert_eq!(entries.len(), 1);
5568 assert_eq!(entries[0].key(), Some("Package".to_string()));
5569
5570 let range = rowan::TextRange::new(0.into(), 25.into());
5572 let entries: Vec<_> = para.entries_in_range(range).collect();
5573 assert_eq!(entries.len(), 2);
5574 assert_eq!(entries[0].key(), Some("Package".to_string()));
5575 assert_eq!(entries[1].key(), Some("Version".to_string()));
5576}
5577
5578#[test]
5579fn test_entries_in_range_partial_overlap() {
5580 let text = r#"Package: foo
5582Version: 1.0
5583Architecture: all
5584"#;
5585 let deb822 = text.parse::<Deb822>().unwrap();
5586 let para = deb822.paragraphs().next().unwrap();
5587
5588 let range = rowan::TextRange::new(15.into(), 30.into());
5590
5591 let entries: Vec<_> = para.entries_in_range(range).collect();
5592 assert!(entries.len() >= 1);
5593 assert!(entries
5594 .iter()
5595 .any(|e| e.key() == Some("Version".to_string())));
5596}
5597
5598#[test]
5599fn test_entries_in_range_no_match() {
5600 let text = "Package: foo\n";
5602 let deb822 = text.parse::<Deb822>().unwrap();
5603 let para = deb822.paragraphs().next().unwrap();
5604
5605 let range = rowan::TextRange::new(1000.into(), 2000.into());
5607 let entries: Vec<_> = para.entries_in_range(range).collect();
5608 assert_eq!(entries.len(), 0);
5609}
5610
5611#[test]
5612fn test_entry_at_position() {
5613 let text = r#"Package: foo
5615Version: 1.0
5616Architecture: all
5617"#;
5618 let deb822 = text.parse::<Deb822>().unwrap();
5619 let para = deb822.paragraphs().next().unwrap();
5620
5621 let entry = para.entry_at_position(rowan::TextSize::from(5));
5623 assert!(entry.is_some());
5624 assert_eq!(entry.unwrap().key(), Some("Package".to_string()));
5625
5626 let entry = para.entry_at_position(rowan::TextSize::from(15));
5628 assert!(entry.is_some());
5629 assert_eq!(entry.unwrap().key(), Some("Version".to_string()));
5630
5631 let entry = para.entry_at_position(rowan::TextSize::from(1000));
5633 assert!(entry.is_none());
5634}
5635
5636#[test]
5637fn test_entry_at_position_multiline() {
5638 let text = r#"Description: A package
5640 with a long
5641 description
5642"#;
5643 let deb822 = text.parse::<Deb822>().unwrap();
5644 let para = deb822.paragraphs().next().unwrap();
5645
5646 let entry = para.entry_at_position(rowan::TextSize::from(5));
5648 assert!(entry.is_some());
5649 assert_eq!(entry.unwrap().key(), Some("Description".to_string()));
5650
5651 let entry = para.entry_at_position(rowan::TextSize::from(30));
5653 assert!(entry.is_some());
5654 assert_eq!(entry.unwrap().key(), Some("Description".to_string()));
5655}
5656
5657#[test]
5658fn test_paragraph_at_position_at_boundary() {
5659 let text = "Package: foo\n\nPackage: bar\n";
5661 let deb822 = text.parse::<Deb822>().unwrap();
5662
5663 let para = deb822.paragraph_at_position(rowan::TextSize::from(0));
5665 assert!(para.is_some());
5666 assert_eq!(para.unwrap().get("Package").as_deref(), Some("foo"));
5667
5668 let para = deb822.paragraph_at_position(rowan::TextSize::from(15));
5670 assert!(para.is_some());
5671 assert_eq!(para.unwrap().get("Package").as_deref(), Some("bar"));
5672}
5673
5674#[test]
5675fn test_comment_in_multiline_value() {
5676 let text = "\
5679Build-Depends: dh-python,
5680 libsvn-dev,
5681# python-all-dbg (>= 2.6.6-3),
5682 python3-all-dev,
5683# python3-all-dbg,
5684 python3-docutils
5685Standards-Version: 4.7.0
5686";
5687 let deb822 = text.parse::<Deb822>().unwrap();
5688 let para = deb822.paragraphs().next().unwrap();
5689 assert_eq!(
5691 para.get("Build-Depends").as_deref(),
5692 Some("dh-python,\nlibsvn-dev,\npython3-all-dev,\npython3-docutils")
5693 );
5694 assert_eq!(
5696 para.get_with_comments("Build-Depends").as_deref(),
5697 Some("dh-python,\nlibsvn-dev,\n# python-all-dbg (>= 2.6.6-3),\npython3-all-dev,\n# python3-all-dbg,\npython3-docutils")
5698 );
5699 assert_eq!(para.get("Standards-Version").as_deref(), Some("4.7.0"));
5700 assert_eq!(deb822.to_string(), text);
5702}