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 self.current_token_index += 1;
503 }
504 fn current(&self) -> Option<SyntaxKind> {
506 self.tokens.last().map(|(kind, _)| *kind)
507 }
508
509 fn add_positioned_error(&mut self, message: String, code: Option<String>) {
511 let range = if self.current_token_index < self.token_positions.len() {
512 let (_, start, end) = self.token_positions[self.current_token_index];
513 rowan::TextRange::new(start, end)
514 } else {
515 let end = self
517 .token_positions
518 .last()
519 .map(|(_, _, end)| *end)
520 .unwrap_or_else(|| rowan::TextSize::from(0));
521 rowan::TextRange::new(end, end)
522 };
523
524 self.positioned_errors.push(PositionedParseError {
525 message: message.clone(),
526 range,
527 code,
528 });
529 self.errors.push(message);
530 }
531 fn skip_ws(&mut self) {
532 while self.current() == Some(WHITESPACE) || self.current() == Some(COMMENT) {
533 self.bump()
534 }
535 }
536 fn skip_ws_and_newlines(&mut self) {
537 while self.current() == Some(WHITESPACE)
538 || self.current() == Some(COMMENT)
539 || self.current() == Some(NEWLINE)
540 {
541 self.builder.start_node(EMPTY_LINE.into());
542 while self.current() != Some(NEWLINE) && self.current().is_some() {
543 self.bump();
544 }
545 if self.current() == Some(NEWLINE) {
546 self.bump();
547 }
548 self.builder.finish_node();
549 }
550 }
551 }
552
553 let mut tokens = lex(text).collect::<Vec<_>>();
554
555 let mut token_positions = Vec::new();
557 let mut position = rowan::TextSize::from(0);
558 for (kind, text) in &tokens {
559 let start = position;
560 let end = start + rowan::TextSize::of(*text);
561 token_positions.push((*kind, start, end));
562 position = end;
563 }
564
565 tokens.reverse();
567 let current_token_index = 0;
568
569 Parser {
570 tokens,
571 builder: GreenNodeBuilder::new(),
572 errors: Vec::new(),
573 positioned_errors: Vec::new(),
574 token_positions,
575 current_token_index,
576 }
577 .parse()
578}
579
580type SyntaxNode = rowan::SyntaxNode<Lang>;
586#[allow(unused)]
587type SyntaxToken = rowan::SyntaxToken<Lang>;
588#[allow(unused)]
589type SyntaxElement = rowan::NodeOrToken<SyntaxNode, SyntaxToken>;
590
591impl Parse {
592 #[cfg(test)]
593 fn syntax(&self) -> SyntaxNode {
594 SyntaxNode::new_root(self.green_node.clone())
595 }
596
597 fn root_mut(&self) -> Deb822 {
598 Deb822::cast(SyntaxNode::new_root_mut(self.green_node.clone())).unwrap()
599 }
600}
601
602fn green_eq(a: &SyntaxNode, b: &SyntaxNode) -> bool {
611 let a_green = a.green();
612 let b_green = b.green();
613 let a_ref: &rowan::GreenNodeData = &a_green;
614 let b_ref: &rowan::GreenNodeData = &b_green;
615 std::ptr::eq(a_ref as *const _, b_ref as *const _) || a_ref == b_ref
616}
617
618fn line_col_at_offset(node: &SyntaxNode, offset: rowan::TextSize) -> (usize, usize) {
621 let root = node.ancestors().last().unwrap_or_else(|| node.clone());
622 let mut line = 0;
623 let mut last_newline_offset = rowan::TextSize::from(0);
624
625 for element in root.preorder_with_tokens() {
626 if let rowan::WalkEvent::Enter(rowan::NodeOrToken::Token(token)) = element {
627 if token.text_range().start() >= offset {
628 break;
629 }
630
631 for (idx, _) in token.text().match_indices('\n') {
633 line += 1;
634 last_newline_offset =
635 token.text_range().start() + rowan::TextSize::from((idx + 1) as u32);
636 }
637 }
638 }
639
640 let column: usize = (offset - last_newline_offset).into();
641 (line, column)
642}
643
644macro_rules! ast_node {
645 ($ast:ident, $kind:ident) => {
646 #[doc = "An AST node representing a `"]
647 #[doc = stringify!($ast)]
648 #[doc = "`."]
649 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
650 #[repr(transparent)]
651 pub struct $ast(SyntaxNode);
652 impl $ast {
653 #[allow(unused)]
654 fn cast(node: SyntaxNode) -> Option<Self> {
655 if node.kind() == $kind {
656 Some(Self(node))
657 } else {
658 None
659 }
660 }
661
662 pub fn line(&self) -> usize {
664 line_col_at_offset(&self.0, self.0.text_range().start()).0
665 }
666
667 pub fn column(&self) -> usize {
669 line_col_at_offset(&self.0, self.0.text_range().start()).1
670 }
671
672 pub fn line_col(&self) -> (usize, usize) {
675 line_col_at_offset(&self.0, self.0.text_range().start())
676 }
677 }
678
679 impl AstNode for $ast {
680 type Language = Lang;
681
682 fn can_cast(kind: SyntaxKind) -> bool {
683 kind == $kind
684 }
685
686 fn cast(syntax: SyntaxNode) -> Option<Self> {
687 Self::cast(syntax)
688 }
689
690 fn syntax(&self) -> &SyntaxNode {
691 &self.0
692 }
693 }
694
695 impl std::fmt::Display for $ast {
696 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
697 write!(f, "{}", self.0.text())
698 }
699 }
700 };
701}
702
703ast_node!(Deb822, ROOT);
704ast_node!(Paragraph, PARAGRAPH);
705ast_node!(Entry, ENTRY);
706
707impl Default for Deb822 {
708 fn default() -> Self {
709 Self::new()
710 }
711}
712
713impl Deb822 {
714 pub fn snapshot(&self) -> Self {
737 Deb822(SyntaxNode::new_root_mut(self.0.green().into_owned()))
738 }
739
740 pub fn tree_eq(&self, other: &Self) -> bool {
751 green_eq(&self.0, &other.0)
752 }
753
754 pub fn new() -> Deb822 {
756 let mut builder = GreenNodeBuilder::new();
757
758 builder.start_node(ROOT.into());
759 builder.finish_node();
760 Deb822(SyntaxNode::new_root_mut(builder.finish()))
761 }
762
763 pub fn parse(text: &str) -> crate::Parse<Deb822> {
765 crate::Parse::parse_deb822(text)
766 }
767
768 #[must_use]
784 pub fn wrap_and_sort(
785 &self,
786 sort_paragraphs: Option<&dyn Fn(&Paragraph, &Paragraph) -> std::cmp::Ordering>,
787 wrap_and_sort_paragraph: Option<&dyn Fn(&Paragraph) -> Paragraph>,
788 ) -> Deb822 {
789 let mut builder = GreenNodeBuilder::new();
790 builder.start_node(ROOT.into());
791 let mut current = vec![];
792 let mut paragraphs = vec![];
793 for c in self.0.children_with_tokens() {
794 match c.kind() {
795 PARAGRAPH => {
796 paragraphs.push((
797 current,
798 Paragraph::cast(c.as_node().unwrap().clone()).unwrap(),
799 ));
800 current = vec![];
801 }
802 COMMENT | ERROR => {
803 current.push(c);
804 }
805 EMPTY_LINE => {
806 current.extend(
807 c.as_node()
808 .unwrap()
809 .children_with_tokens()
810 .skip_while(|c| matches!(c.kind(), EMPTY_LINE | NEWLINE | WHITESPACE)),
811 );
812 }
813 _ => {}
814 }
815 }
816 if let Some(sort_paragraph) = sort_paragraphs {
817 paragraphs.sort_by(|a, b| {
818 let a_key = &a.1;
819 let b_key = &b.1;
820 sort_paragraph(a_key, b_key)
821 });
822 }
823
824 for (i, paragraph) in paragraphs.into_iter().enumerate() {
825 if i > 0 {
826 builder.start_node(EMPTY_LINE.into());
827 builder.token(NEWLINE.into(), "\n");
828 builder.finish_node();
829 }
830 for c in paragraph.0.into_iter() {
831 builder.token(c.kind().into(), c.as_token().unwrap().text());
832 }
833 let new_paragraph = if let Some(ref ws) = wrap_and_sort_paragraph {
834 ws(¶graph.1)
835 } else {
836 paragraph.1
837 };
838 inject(&mut builder, new_paragraph.0);
839 }
840
841 for c in current {
842 builder.token(c.kind().into(), c.as_token().unwrap().text());
843 }
844
845 builder.finish_node();
846 Self(SyntaxNode::new_root_mut(builder.finish()))
847 }
848
849 pub fn normalize_field_spacing(&mut self) -> bool {
868 let mut any_changed = false;
869
870 let mut paragraphs: Vec<_> = self.paragraphs().collect();
872
873 for para in &mut paragraphs {
875 if para.normalize_field_spacing() {
876 any_changed = true;
877 }
878 }
879
880 any_changed
881 }
882
883 pub fn paragraphs(&self) -> impl Iterator<Item = Paragraph> {
885 self.0.children().filter_map(Paragraph::cast)
886 }
887
888 pub fn paragraphs_in_range(
914 &self,
915 range: rowan::TextRange,
916 ) -> impl Iterator<Item = Paragraph> + '_ {
917 self.paragraphs().filter(move |p| {
918 let para_range = p.text_range();
919 para_range.start() < range.end() && para_range.end() > range.start()
921 })
922 }
923
924 pub fn paragraph_at_position(&self, offset: rowan::TextSize) -> Option<Paragraph> {
947 self.paragraphs().find(|p| {
948 let range = p.text_range();
949 range.contains(offset)
950 })
951 }
952
953 pub fn paragraph_at_line(&self, line: usize) -> Option<Paragraph> {
976 self.paragraphs().find(|p| {
977 let start_line = p.line();
978 let range = p.text_range();
979 let text_str = self.0.text().to_string();
980 let text_before_end = &text_str[..range.end().into()];
981 let end_line = text_before_end.lines().count().saturating_sub(1);
982 line >= start_line && line <= end_line
983 })
984 }
985
986 pub fn entry_at_line_col(&self, line: usize, col: usize) -> Option<Entry> {
1010 let text_str = self.0.text().to_string();
1012 let offset: usize = text_str.lines().take(line).map(|l| l.len() + 1).sum();
1013 let position = rowan::TextSize::from((offset + col) as u32);
1014
1015 for para in self.paragraphs() {
1017 for entry in para.entries() {
1018 let range = entry.text_range();
1019 if range.contains(position) {
1020 return Some(entry);
1021 }
1022 }
1023 }
1024 None
1025 }
1026
1027 fn convert_index(&self, index: usize) -> Option<usize> {
1029 let mut current_pos = 0usize;
1030 if index == 0 {
1031 return Some(0);
1032 }
1033 for (i, node) in self.0.children_with_tokens().enumerate() {
1034 if node.kind() == PARAGRAPH {
1035 if current_pos == index {
1036 return Some(i);
1037 }
1038 current_pos += 1;
1039 }
1040 }
1041
1042 None
1043 }
1044
1045 fn delete_trailing_space(&self, start: usize) {
1047 for (i, node) in self.0.children_with_tokens().enumerate() {
1048 if i < start {
1049 continue;
1050 }
1051 if node.kind() != EMPTY_LINE {
1052 return;
1053 }
1054 self.0.splice_children(start..start + 1, []);
1057 }
1058 }
1059
1060 fn insert_empty_paragraph(&mut self, index: Option<usize>) -> Paragraph {
1062 let paragraph = Paragraph::new();
1063 let mut to_insert = vec![];
1064 if self.0.children().count() > 0 {
1065 let mut builder = GreenNodeBuilder::new();
1066 builder.start_node(EMPTY_LINE.into());
1067 builder.token(NEWLINE.into(), "\n");
1068 builder.finish_node();
1069 to_insert.push(SyntaxNode::new_root_mut(builder.finish()).into());
1070 }
1071 to_insert.push(paragraph.0.clone().into());
1072 let insertion_point = match index {
1073 Some(i) => {
1074 if to_insert.len() > 1 {
1075 to_insert.swap(0, 1);
1076 }
1077 i
1078 }
1079 None => self.0.children().count(),
1080 };
1081 self.0
1082 .splice_children(insertion_point..insertion_point, to_insert);
1083 paragraph
1084 }
1085
1086 pub fn insert_paragraph(&mut self, index: usize) -> Paragraph {
1106 self.insert_empty_paragraph(self.convert_index(index))
1107 }
1108
1109 pub fn remove_paragraph(&mut self, index: usize) {
1127 if let Some(index) = self.convert_index(index) {
1128 self.0.splice_children(index..index + 1, []);
1129 self.delete_trailing_space(index);
1130 }
1131 }
1132
1133 pub fn move_paragraph(&mut self, from_index: usize, to_index: usize) {
1153 if from_index == to_index {
1154 return;
1155 }
1156
1157 let paragraph_count = self.paragraphs().count();
1159 if from_index >= paragraph_count || to_index >= paragraph_count {
1160 return;
1161 }
1162
1163 let paragraph_to_move = self.paragraphs().nth(from_index).unwrap().0.clone();
1165
1166 let from_physical = self.convert_index(from_index).unwrap();
1168
1169 let mut start_idx = from_physical;
1171 if from_physical > 0 {
1172 if let Some(prev_node) = self.0.children_with_tokens().nth(from_physical - 1) {
1173 if prev_node.kind() == EMPTY_LINE {
1174 start_idx = from_physical - 1;
1175 }
1176 }
1177 }
1178
1179 self.0.splice_children(start_idx..from_physical + 1, []);
1181 self.delete_trailing_space(start_idx);
1182
1183 let insert_at = if to_index > from_index {
1187 let target_idx = to_index - 1;
1190 if let Some(target_physical) = self.convert_index(target_idx) {
1191 target_physical + 1
1192 } else {
1193 self.0.children().count()
1195 }
1196 } else {
1197 if let Some(target_physical) = self.convert_index(to_index) {
1200 target_physical
1201 } else {
1202 self.0.children().count()
1203 }
1204 };
1205
1206 let mut to_insert = vec![];
1208
1209 let needs_empty_line_before = if insert_at == 0 {
1211 false
1213 } else if insert_at > 0 {
1214 if let Some(node_at_insert) = self.0.children_with_tokens().nth(insert_at - 1) {
1216 node_at_insert.kind() != EMPTY_LINE
1217 } else {
1218 false
1219 }
1220 } else {
1221 false
1222 };
1223
1224 if needs_empty_line_before {
1225 let mut builder = GreenNodeBuilder::new();
1226 builder.start_node(EMPTY_LINE.into());
1227 builder.token(NEWLINE.into(), "\n");
1228 builder.finish_node();
1229 to_insert.push(SyntaxNode::new_root_mut(builder.finish()).into());
1230 }
1231
1232 to_insert.push(paragraph_to_move.into());
1233
1234 let needs_empty_line_after = if insert_at < self.0.children().count() {
1236 if let Some(node_after) = self.0.children_with_tokens().nth(insert_at) {
1238 node_after.kind() != EMPTY_LINE
1239 } else {
1240 false
1241 }
1242 } else {
1243 false
1244 };
1245
1246 if needs_empty_line_after {
1247 let mut builder = GreenNodeBuilder::new();
1248 builder.start_node(EMPTY_LINE.into());
1249 builder.token(NEWLINE.into(), "\n");
1250 builder.finish_node();
1251 to_insert.push(SyntaxNode::new_root_mut(builder.finish()).into());
1252 }
1253
1254 self.0.splice_children(insert_at..insert_at, to_insert);
1256 }
1257
1258 pub fn add_paragraph(&mut self) -> Paragraph {
1260 self.insert_empty_paragraph(None)
1261 }
1262
1263 pub fn swap_paragraphs(&mut self, index1: usize, index2: usize) {
1293 if index1 == index2 {
1294 return;
1295 }
1296
1297 let mut children: Vec<_> = self.0.children().map(|n| n.clone().into()).collect();
1299
1300 let mut para_child_indices = vec![];
1302 for (child_idx, child) in self.0.children().enumerate() {
1303 if child.kind() == PARAGRAPH {
1304 para_child_indices.push(child_idx);
1305 }
1306 }
1307
1308 if index1 >= para_child_indices.len() {
1310 panic!("index1 {} out of bounds", index1);
1311 }
1312 if index2 >= para_child_indices.len() {
1313 panic!("index2 {} out of bounds", index2);
1314 }
1315
1316 let child_idx1 = para_child_indices[index1];
1317 let child_idx2 = para_child_indices[index2];
1318
1319 children.swap(child_idx1, child_idx2);
1321
1322 let num_children = children.len();
1324 self.0.splice_children(0..num_children, children);
1325 }
1326
1327 pub fn from_file(path: impl AsRef<Path>) -> Result<Self, Error> {
1329 let text = std::fs::read_to_string(path)?;
1330 Ok(Self::from_str(&text)?)
1331 }
1332
1333 pub fn from_file_relaxed(
1335 path: impl AsRef<Path>,
1336 ) -> Result<(Self, Vec<String>), std::io::Error> {
1337 let text = std::fs::read_to_string(path)?;
1338 Ok(Self::from_str_relaxed(&text))
1339 }
1340
1341 pub fn from_str_relaxed(s: &str) -> (Self, Vec<String>) {
1343 let parsed = parse(s);
1344 (parsed.root_mut(), parsed.errors)
1345 }
1346
1347 pub fn read<R: std::io::Read>(mut r: R) -> Result<Self, Error> {
1349 let mut buf = String::new();
1350 r.read_to_string(&mut buf)?;
1351 Ok(Self::from_str(&buf)?)
1352 }
1353
1354 pub fn read_relaxed<R: std::io::Read>(mut r: R) -> Result<(Self, Vec<String>), std::io::Error> {
1356 let mut buf = String::new();
1357 r.read_to_string(&mut buf)?;
1358 Ok(Self::from_str_relaxed(&buf))
1359 }
1360}
1361
1362fn inject(builder: &mut GreenNodeBuilder, node: SyntaxNode) {
1363 builder.start_node(node.kind().into());
1364 for child in node.children_with_tokens() {
1365 match child {
1366 rowan::NodeOrToken::Node(child) => {
1367 inject(builder, child);
1368 }
1369 rowan::NodeOrToken::Token(token) => {
1370 builder.token(token.kind().into(), token.text());
1371 }
1372 }
1373 }
1374 builder.finish_node();
1375}
1376
1377impl FromIterator<Paragraph> for Deb822 {
1378 fn from_iter<T: IntoIterator<Item = Paragraph>>(iter: T) -> Self {
1379 let mut builder = GreenNodeBuilder::new();
1380 builder.start_node(ROOT.into());
1381 for (i, paragraph) in iter.into_iter().enumerate() {
1382 if i > 0 {
1383 builder.start_node(EMPTY_LINE.into());
1384 builder.token(NEWLINE.into(), "\n");
1385 builder.finish_node();
1386 }
1387 inject(&mut builder, paragraph.0);
1388 }
1389 builder.finish_node();
1390 Self(SyntaxNode::new_root_mut(builder.finish()))
1391 }
1392}
1393
1394impl From<Vec<(String, String)>> for Paragraph {
1395 fn from(v: Vec<(String, String)>) -> Self {
1396 v.into_iter().collect()
1397 }
1398}
1399
1400impl From<Vec<(&str, &str)>> for Paragraph {
1401 fn from(v: Vec<(&str, &str)>) -> Self {
1402 v.into_iter().collect()
1403 }
1404}
1405
1406impl FromIterator<(String, String)> for Paragraph {
1407 fn from_iter<T: IntoIterator<Item = (String, String)>>(iter: T) -> Self {
1408 let mut builder = GreenNodeBuilder::new();
1409 builder.start_node(PARAGRAPH.into());
1410 for (key, value) in iter {
1411 builder.start_node(ENTRY.into());
1412 builder.token(KEY.into(), &key);
1413 builder.token(COLON.into(), ":");
1414 builder.token(WHITESPACE.into(), " ");
1415 for (i, line) in value.split('\n').enumerate() {
1416 if i > 0 {
1417 builder.token(INDENT.into(), " ");
1418 }
1419 builder.token(VALUE.into(), line);
1420 builder.token(NEWLINE.into(), "\n");
1421 }
1422 builder.finish_node();
1423 }
1424 builder.finish_node();
1425 Self(SyntaxNode::new_root_mut(builder.finish()))
1426 }
1427}
1428
1429impl<'a> FromIterator<(&'a str, &'a str)> for Paragraph {
1430 fn from_iter<T: IntoIterator<Item = (&'a str, &'a str)>>(iter: T) -> Self {
1431 let mut builder = GreenNodeBuilder::new();
1432 builder.start_node(PARAGRAPH.into());
1433 for (key, value) in iter {
1434 builder.start_node(ENTRY.into());
1435 builder.token(KEY.into(), key);
1436 builder.token(COLON.into(), ":");
1437 builder.token(WHITESPACE.into(), " ");
1438 for (i, line) in value.split('\n').enumerate() {
1439 if i > 0 {
1440 builder.token(INDENT.into(), " ");
1441 }
1442 builder.token(VALUE.into(), line);
1443 builder.token(NEWLINE.into(), "\n");
1444 }
1445 builder.finish_node();
1446 }
1447 builder.finish_node();
1448 Self(SyntaxNode::new_root_mut(builder.finish()))
1449 }
1450}
1451
1452#[derive(Debug, Clone, PartialEq, Eq)]
1454pub enum IndentPattern {
1455 Fixed(usize),
1457 FieldNameLength,
1459}
1460
1461impl IndentPattern {
1462 fn to_string(&self, field_name: &str) -> String {
1464 match self {
1465 IndentPattern::Fixed(spaces) => " ".repeat(*spaces),
1466 IndentPattern::FieldNameLength => " ".repeat(field_name.len() + 2),
1467 }
1468 }
1469}
1470
1471impl Paragraph {
1472 pub fn new() -> Paragraph {
1474 let mut builder = GreenNodeBuilder::new();
1475
1476 builder.start_node(PARAGRAPH.into());
1477 builder.finish_node();
1478 Paragraph(SyntaxNode::new_root_mut(builder.finish()))
1479 }
1480
1481 pub fn snapshot(&self) -> Self {
1485 Paragraph(SyntaxNode::new_root_mut(self.0.green().into_owned()))
1486 }
1487
1488 pub fn tree_eq(&self, other: &Self) -> bool {
1491 green_eq(&self.0, &other.0)
1492 }
1493
1494 pub fn text_range(&self) -> rowan::TextRange {
1496 self.0.text_range()
1497 }
1498
1499 pub fn entries_in_range(&self, range: rowan::TextRange) -> impl Iterator<Item = Entry> + '_ {
1526 self.entries().filter(move |e| {
1527 let entry_range = e.text_range();
1528 entry_range.start() < range.end() && entry_range.end() > range.start()
1530 })
1531 }
1532
1533 pub fn entry_at_position(&self, offset: rowan::TextSize) -> Option<Entry> {
1557 self.entries().find(|e| {
1558 let range = e.text_range();
1559 range.contains(offset)
1560 })
1561 }
1562
1563 #[must_use]
1575 pub fn wrap_and_sort(
1576 &self,
1577 indentation: Indentation,
1578 immediate_empty_line: bool,
1579 max_line_length_one_liner: Option<usize>,
1580 sort_entries: Option<&dyn Fn(&Entry, &Entry) -> std::cmp::Ordering>,
1581 format_value: Option<&dyn Fn(&str, &str) -> String>,
1582 ) -> Paragraph {
1583 let mut builder = GreenNodeBuilder::new();
1584
1585 let mut current = vec![];
1586 let mut entries = vec![];
1587
1588 builder.start_node(PARAGRAPH.into());
1589 for c in self.0.children_with_tokens() {
1590 match c.kind() {
1591 ENTRY => {
1592 entries.push((current, Entry::cast(c.as_node().unwrap().clone()).unwrap()));
1593 current = vec![];
1594 }
1595 ERROR | COMMENT => {
1596 current.push(c);
1597 }
1598 _ => {}
1599 }
1600 }
1601
1602 if let Some(sort_entry) = sort_entries {
1603 entries.sort_by(|a, b| {
1604 let a_key = &a.1;
1605 let b_key = &b.1;
1606 sort_entry(a_key, b_key)
1607 });
1608 }
1609
1610 for (pre, entry) in entries.into_iter() {
1611 for c in pre.into_iter() {
1612 builder.token(c.kind().into(), c.as_token().unwrap().text());
1613 }
1614
1615 inject(
1616 &mut builder,
1617 entry
1618 .wrap_and_sort(
1619 indentation,
1620 immediate_empty_line,
1621 max_line_length_one_liner,
1622 format_value,
1623 )
1624 .0,
1625 );
1626 }
1627
1628 for c in current {
1629 builder.token(c.kind().into(), c.as_token().unwrap().text());
1630 }
1631
1632 builder.finish_node();
1633 Self(SyntaxNode::new_root_mut(builder.finish()))
1634 }
1635
1636 pub fn normalize_field_spacing(&mut self) -> bool {
1656 let mut any_changed = false;
1657
1658 let mut entries: Vec<_> = self.entries().collect();
1660
1661 for entry in &mut entries {
1663 if entry.normalize_field_spacing() {
1664 any_changed = true;
1665 }
1666 }
1667
1668 any_changed
1669 }
1670
1671 pub fn get(&self, key: &str) -> Option<String> {
1675 self.entries()
1676 .find(|e| {
1677 e.key()
1678 .as_deref()
1679 .is_some_and(|k| k.eq_ignore_ascii_case(key))
1680 })
1681 .map(|e| e.value())
1682 }
1683
1684 pub fn get_with_comments(&self, key: &str) -> Option<String> {
1692 self.entries()
1693 .find(|e| {
1694 e.key()
1695 .as_deref()
1696 .is_some_and(|k| k.eq_ignore_ascii_case(key))
1697 })
1698 .map(|e| e.value_with_comments())
1699 }
1700
1701 pub fn get_entry(&self, key: &str) -> Option<Entry> {
1705 self.entries().find(|e| {
1706 e.key()
1707 .as_deref()
1708 .is_some_and(|k| k.eq_ignore_ascii_case(key))
1709 })
1710 }
1711
1712 pub fn get_with_indent(&self, key: &str, indent_pattern: &IndentPattern) -> Option<String> {
1739 use crate::lex::SyntaxKind::{INDENT, VALUE};
1740
1741 self.entries()
1742 .find(|e| {
1743 e.key()
1744 .as_deref()
1745 .is_some_and(|k| k.eq_ignore_ascii_case(key))
1746 })
1747 .and_then(|e| {
1748 let field_key = e.key()?;
1749 let expected_indent = indent_pattern.to_string(&field_key);
1750 let expected_len = expected_indent.len();
1751
1752 let mut result = String::new();
1753 let mut first = true;
1754 let mut last_indent: Option<String> = None;
1755
1756 for token in e.0.children_with_tokens().filter_map(|it| it.into_token()) {
1757 match token.kind() {
1758 INDENT => {
1759 last_indent = Some(token.text().to_string());
1760 }
1761 VALUE => {
1762 if !first {
1763 result.push('\n');
1764 if let Some(ref indent_text) = last_indent {
1766 if indent_text.len() > expected_len {
1767 result.push_str(&indent_text[expected_len..]);
1768 }
1769 }
1770 }
1771 result.push_str(token.text());
1772 first = false;
1773 last_indent = None;
1774 }
1775 _ => {}
1776 }
1777 }
1778
1779 Some(result)
1780 })
1781 }
1782
1783 pub fn get_multiline(&self, key: &str) -> Option<String> {
1810 self.get_with_indent(key, &IndentPattern::Fixed(1))
1811 }
1812
1813 pub fn set_multiline(
1839 &mut self,
1840 key: &str,
1841 value: &str,
1842 field_order: Option<&[&str]>,
1843 ) -> Result<(), Error> {
1844 self.try_set_with_forced_indent(key, value, &IndentPattern::Fixed(1), field_order)
1845 }
1846
1847 pub fn contains_key(&self, key: &str) -> bool {
1849 self.get(key).is_some()
1850 }
1851
1852 pub fn entries(&self) -> impl Iterator<Item = Entry> + '_ {
1854 self.0.children().filter_map(Entry::cast)
1855 }
1856
1857 pub fn items(&self) -> impl Iterator<Item = (String, String)> + '_ {
1859 self.entries()
1860 .filter_map(|e| e.key().map(|k| (k, e.value())))
1861 }
1862
1863 pub fn get_all<'a>(&'a self, key: &'a str) -> impl Iterator<Item = String> + 'a {
1867 self.items().filter_map(move |(k, v)| {
1868 if k.eq_ignore_ascii_case(key) {
1869 Some(v)
1870 } else {
1871 None
1872 }
1873 })
1874 }
1875
1876 pub fn keys(&self) -> impl Iterator<Item = String> + '_ {
1878 self.entries().filter_map(|e| e.key())
1879 }
1880
1881 pub fn remove(&mut self, key: &str) {
1885 for mut entry in self.entries() {
1886 if entry
1887 .key()
1888 .as_deref()
1889 .is_some_and(|k| k.eq_ignore_ascii_case(key))
1890 {
1891 entry.detach();
1892 }
1893 }
1894 }
1895
1896 pub fn insert(&mut self, key: &str, value: &str) {
1898 let entry = Entry::new(key, value);
1899 let count = self.0.children_with_tokens().count();
1900 self.0.splice_children(count..count, vec![entry.0.into()]);
1901 }
1902
1903 pub fn insert_comment_before(&mut self, comment: &str) {
1922 use rowan::GreenNodeBuilder;
1923
1924 let mut builder = GreenNodeBuilder::new();
1927 builder.start_node(EMPTY_LINE.into());
1928 builder.token(COMMENT.into(), &format!("# {}", comment));
1929 builder.token(NEWLINE.into(), "\n");
1930 builder.finish_node();
1931 let green = builder.finish();
1932
1933 let comment_node = SyntaxNode::new_root_mut(green);
1935
1936 let index = self.0.index();
1937 let parent = self.0.parent().expect("Paragraph must have a parent");
1938 parent.splice_children(index..index, vec![comment_node.into()]);
1939 }
1940
1941 fn detect_indent_pattern(&self) -> IndentPattern {
1949 let indent_data: Vec<(String, usize)> = self
1951 .entries()
1952 .filter_map(|entry| {
1953 let field_key = entry.key()?;
1954 let indent = entry.get_indent()?;
1955 Some((field_key, indent.len()))
1956 })
1957 .collect();
1958
1959 if indent_data.is_empty() {
1960 return IndentPattern::FieldNameLength;
1962 }
1963
1964 let first_indent_len = indent_data[0].1;
1966 let all_same = indent_data.iter().all(|(_, len)| *len == first_indent_len);
1967
1968 if all_same {
1969 return IndentPattern::Fixed(first_indent_len);
1971 }
1972
1973 let all_match_field_length = indent_data
1975 .iter()
1976 .all(|(field_key, indent_len)| *indent_len == field_key.len() + 2);
1977
1978 if all_match_field_length {
1979 return IndentPattern::FieldNameLength;
1981 }
1982
1983 IndentPattern::FieldNameLength
1985 }
1986
1987 pub fn try_set(&mut self, key: &str, value: &str) -> Result<(), Error> {
1992 self.try_set_with_indent_pattern(key, value, None, None)
1993 }
1994
1995 pub fn set(&mut self, key: &str, value: &str) {
2000 self.try_set(key, value)
2001 .expect("Invalid value: empty continuation line")
2002 }
2003
2004 pub fn set_with_field_order(&mut self, key: &str, value: &str, field_order: &[&str]) {
2006 self.try_set_with_indent_pattern(key, value, None, Some(field_order))
2007 .expect("Invalid value: empty continuation line")
2008 }
2009
2010 pub fn try_set_with_indent_pattern(
2027 &mut self,
2028 key: &str,
2029 value: &str,
2030 default_indent_pattern: Option<&IndentPattern>,
2031 field_order: Option<&[&str]>,
2032 ) -> Result<(), Error> {
2033 let existing_entry = self.entries().find(|entry| {
2035 entry
2036 .key()
2037 .as_deref()
2038 .is_some_and(|k| k.eq_ignore_ascii_case(key))
2039 });
2040
2041 let indent = existing_entry
2043 .as_ref()
2044 .and_then(|entry| entry.get_indent())
2045 .unwrap_or_else(|| {
2046 if let Some(pattern) = default_indent_pattern {
2048 pattern.to_string(key)
2049 } else {
2050 self.detect_indent_pattern().to_string(key)
2051 }
2052 });
2053
2054 let post_colon_ws = existing_entry
2055 .as_ref()
2056 .and_then(|entry| entry.get_post_colon_whitespace())
2057 .unwrap_or_else(|| " ".to_string());
2058
2059 let actual_key = existing_entry
2061 .as_ref()
2062 .and_then(|e| e.key())
2063 .unwrap_or_else(|| key.to_string());
2064
2065 let new_entry = Entry::try_with_formatting(&actual_key, value, &post_colon_ws, &indent)?;
2066
2067 for entry in self.entries() {
2069 if entry
2070 .key()
2071 .as_deref()
2072 .is_some_and(|k| k.eq_ignore_ascii_case(key))
2073 {
2074 self.0.splice_children(
2075 entry.0.index()..entry.0.index() + 1,
2076 vec![new_entry.0.into()],
2077 );
2078 return Ok(());
2079 }
2080 }
2081
2082 if let Some(order) = field_order {
2084 let insertion_index = self.find_insertion_index(key, order);
2085 self.0
2086 .splice_children(insertion_index..insertion_index, vec![new_entry.0.into()]);
2087 } else {
2088 let insertion_index = self.0.children_with_tokens().count();
2090 self.0
2091 .splice_children(insertion_index..insertion_index, vec![new_entry.0.into()]);
2092 }
2093 Ok(())
2094 }
2095
2096 pub fn set_with_indent_pattern(
2113 &mut self,
2114 key: &str,
2115 value: &str,
2116 default_indent_pattern: Option<&IndentPattern>,
2117 field_order: Option<&[&str]>,
2118 ) {
2119 self.try_set_with_indent_pattern(key, value, default_indent_pattern, field_order)
2120 .expect("Invalid value: empty continuation line")
2121 }
2122
2123 pub fn try_set_with_forced_indent(
2137 &mut self,
2138 key: &str,
2139 value: &str,
2140 indent_pattern: &IndentPattern,
2141 field_order: Option<&[&str]>,
2142 ) -> Result<(), Error> {
2143 let existing_entry = self.entries().find(|entry| {
2145 entry
2146 .key()
2147 .as_deref()
2148 .is_some_and(|k| k.eq_ignore_ascii_case(key))
2149 });
2150
2151 let post_colon_ws = existing_entry
2153 .as_ref()
2154 .and_then(|entry| entry.get_post_colon_whitespace())
2155 .unwrap_or_else(|| " ".to_string());
2156
2157 let actual_key = existing_entry
2159 .as_ref()
2160 .and_then(|e| e.key())
2161 .unwrap_or_else(|| key.to_string());
2162
2163 let indent = indent_pattern.to_string(&actual_key);
2165 let new_entry = Entry::try_with_formatting(&actual_key, value, &post_colon_ws, &indent)?;
2166
2167 for entry in self.entries() {
2169 if entry
2170 .key()
2171 .as_deref()
2172 .is_some_and(|k| k.eq_ignore_ascii_case(key))
2173 {
2174 self.0.splice_children(
2175 entry.0.index()..entry.0.index() + 1,
2176 vec![new_entry.0.into()],
2177 );
2178 return Ok(());
2179 }
2180 }
2181
2182 if let Some(order) = field_order {
2184 let insertion_index = self.find_insertion_index(key, order);
2185 self.0
2186 .splice_children(insertion_index..insertion_index, vec![new_entry.0.into()]);
2187 } else {
2188 let insertion_index = self.0.children_with_tokens().count();
2190 self.0
2191 .splice_children(insertion_index..insertion_index, vec![new_entry.0.into()]);
2192 }
2193 Ok(())
2194 }
2195
2196 pub fn set_with_forced_indent(
2210 &mut self,
2211 key: &str,
2212 value: &str,
2213 indent_pattern: &IndentPattern,
2214 field_order: Option<&[&str]>,
2215 ) {
2216 self.try_set_with_forced_indent(key, value, indent_pattern, field_order)
2217 .expect("Invalid value: empty continuation line")
2218 }
2219
2220 pub fn change_field_indent(
2236 &mut self,
2237 key: &str,
2238 indent_pattern: &IndentPattern,
2239 ) -> Result<bool, Error> {
2240 let existing_entry = self.entries().find(|entry| {
2242 entry
2243 .key()
2244 .as_deref()
2245 .is_some_and(|k| k.eq_ignore_ascii_case(key))
2246 });
2247
2248 if let Some(entry) = existing_entry {
2249 let value = entry.value();
2250 let actual_key = entry.key().unwrap_or_else(|| key.to_string());
2251
2252 let post_colon_ws = entry
2254 .get_post_colon_whitespace()
2255 .unwrap_or_else(|| " ".to_string());
2256
2257 let indent = indent_pattern.to_string(&actual_key);
2259 let new_entry =
2260 Entry::try_with_formatting(&actual_key, &value, &post_colon_ws, &indent)?;
2261
2262 self.0.splice_children(
2264 entry.0.index()..entry.0.index() + 1,
2265 vec![new_entry.0.into()],
2266 );
2267 Ok(true)
2268 } else {
2269 Ok(false)
2270 }
2271 }
2272
2273 fn find_insertion_index(&self, key: &str, field_order: &[&str]) -> usize {
2275 let new_field_position = field_order
2277 .iter()
2278 .position(|&field| field.eq_ignore_ascii_case(key));
2279
2280 let mut insertion_index = self.0.children_with_tokens().count();
2281
2282 for (i, child) in self.0.children_with_tokens().enumerate() {
2284 if let Some(node) = child.as_node() {
2285 if let Some(entry) = Entry::cast(node.clone()) {
2286 if let Some(existing_key) = entry.key() {
2287 let existing_position = field_order
2288 .iter()
2289 .position(|&field| field.eq_ignore_ascii_case(&existing_key));
2290
2291 match (new_field_position, existing_position) {
2292 (Some(new_pos), Some(existing_pos)) => {
2294 if new_pos < existing_pos {
2295 insertion_index = i;
2296 break;
2297 }
2298 }
2299 (Some(_), None) => {
2301 }
2303 (None, Some(_)) => {
2305 }
2307 (None, None) => {
2309 if key < existing_key.as_str() {
2310 insertion_index = i;
2311 break;
2312 }
2313 }
2314 }
2315 }
2316 }
2317 }
2318 }
2319
2320 if new_field_position.is_some() && insertion_index == self.0.children_with_tokens().count()
2323 {
2324 let children: Vec<_> = self.0.children_with_tokens().enumerate().collect();
2326 for (i, child) in children.into_iter().rev() {
2327 if let Some(node) = child.as_node() {
2328 if let Some(entry) = Entry::cast(node.clone()) {
2329 if let Some(existing_key) = entry.key() {
2330 if field_order
2331 .iter()
2332 .any(|&f| f.eq_ignore_ascii_case(&existing_key))
2333 {
2334 insertion_index = i + 1;
2336 break;
2337 }
2338 }
2339 }
2340 }
2341 }
2342 }
2343
2344 insertion_index
2345 }
2346
2347 pub fn rename(&mut self, old_key: &str, new_key: &str) -> bool {
2353 for entry in self.entries() {
2354 if entry
2355 .key()
2356 .as_deref()
2357 .is_some_and(|k| k.eq_ignore_ascii_case(old_key))
2358 {
2359 let key_index = entry
2360 .0
2361 .children_with_tokens()
2362 .position(|it| it.as_token().is_some_and(|t| t.kind() == KEY));
2363 if let Some(key_index) = key_index {
2364 let new_token =
2365 rowan::NodeOrToken::Token(rowan::GreenToken::new(KEY.into(), new_key));
2366 let new_green = entry
2367 .0
2368 .green()
2369 .splice_children(key_index..key_index + 1, vec![new_token]);
2370 let parent = entry.0.parent().expect("Entry must have a parent");
2371 parent.splice_children(
2372 entry.0.index()..entry.0.index() + 1,
2373 vec![SyntaxNode::new_root_mut(new_green).into()],
2374 );
2375 return true;
2376 }
2377 }
2378 }
2379 false
2380 }
2381}
2382
2383impl Default for Paragraph {
2384 fn default() -> Self {
2385 Self::new()
2386 }
2387}
2388
2389impl std::str::FromStr for Paragraph {
2390 type Err = ParseError;
2391
2392 fn from_str(text: &str) -> Result<Self, Self::Err> {
2393 let deb822 = Deb822::from_str(text)?;
2394
2395 let mut paragraphs = deb822.paragraphs();
2396
2397 paragraphs
2398 .next()
2399 .ok_or_else(|| ParseError(vec!["no paragraphs".to_string()]))
2400 }
2401}
2402
2403#[cfg(feature = "python-debian")]
2404impl<'py> pyo3::IntoPyObject<'py> for Paragraph {
2405 type Target = pyo3::PyAny;
2406 type Output = pyo3::Bound<'py, Self::Target>;
2407 type Error = pyo3::PyErr;
2408
2409 fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
2410 use pyo3::prelude::*;
2411 let d = pyo3::types::PyDict::new(py);
2412 for (k, v) in self.items() {
2413 d.set_item(k, v)?;
2414 }
2415 let m = py.import("debian.deb822")?;
2416 let cls = m.getattr("Deb822")?;
2417 cls.call1((d,))
2418 }
2419}
2420
2421#[cfg(feature = "python-debian")]
2422impl<'py> pyo3::IntoPyObject<'py> for &Paragraph {
2423 type Target = pyo3::PyAny;
2424 type Output = pyo3::Bound<'py, Self::Target>;
2425 type Error = pyo3::PyErr;
2426
2427 fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
2428 use pyo3::prelude::*;
2429 let d = pyo3::types::PyDict::new(py);
2430 for (k, v) in self.items() {
2431 d.set_item(k, v)?;
2432 }
2433 let m = py.import("debian.deb822")?;
2434 let cls = m.getattr("Deb822")?;
2435 cls.call1((d,))
2436 }
2437}
2438
2439#[cfg(feature = "python-debian")]
2440impl<'py> pyo3::FromPyObject<'_, 'py> for Paragraph {
2441 type Error = pyo3::PyErr;
2442
2443 fn extract(obj: pyo3::Borrowed<'_, 'py, pyo3::PyAny>) -> Result<Self, Self::Error> {
2444 use pyo3::types::PyAnyMethods;
2445 let d = obj.call_method0("__str__")?.extract::<String>()?;
2446 Paragraph::from_str(&d)
2447 .map_err(|e| pyo3::exceptions::PyValueError::new_err((e.to_string(),)))
2448 }
2449}
2450
2451impl Entry {
2452 pub fn snapshot(&self) -> Self {
2456 Entry(SyntaxNode::new_root_mut(self.0.green().into_owned()))
2457 }
2458
2459 pub fn tree_eq(&self, other: &Self) -> bool {
2462 green_eq(&self.0, &other.0)
2463 }
2464
2465 pub fn text_range(&self) -> rowan::TextRange {
2467 self.0.text_range()
2468 }
2469
2470 pub fn key_range(&self) -> Option<rowan::TextRange> {
2472 self.0
2473 .children_with_tokens()
2474 .filter_map(|it| it.into_token())
2475 .find(|it| it.kind() == KEY)
2476 .map(|it| it.text_range())
2477 }
2478
2479 pub fn colon_range(&self) -> Option<rowan::TextRange> {
2481 self.0
2482 .children_with_tokens()
2483 .filter_map(|it| it.into_token())
2484 .find(|it| it.kind() == COLON)
2485 .map(|it| it.text_range())
2486 }
2487
2488 pub fn value_range(&self) -> Option<rowan::TextRange> {
2491 let value_tokens: Vec<_> = self
2492 .0
2493 .children_with_tokens()
2494 .filter_map(|it| it.into_token())
2495 .filter(|it| it.kind() == VALUE)
2496 .collect();
2497
2498 if value_tokens.is_empty() {
2499 return None;
2500 }
2501
2502 let first = value_tokens.first().unwrap();
2503 let last = value_tokens.last().unwrap();
2504 Some(rowan::TextRange::new(
2505 first.text_range().start(),
2506 last.text_range().end(),
2507 ))
2508 }
2509
2510 pub fn value_line_ranges(&self) -> Vec<rowan::TextRange> {
2513 self.0
2514 .children_with_tokens()
2515 .filter_map(|it| it.into_token())
2516 .filter(|it| it.kind() == VALUE)
2517 .map(|it| it.text_range())
2518 .collect()
2519 }
2520
2521 pub fn value_token_range(&self) -> Option<rowan::TextRange> {
2530 let value_range = self.value_range()?;
2531 let first = self
2532 .0
2533 .children_with_tokens()
2534 .filter_map(|it| it.into_token())
2535 .find(|it| it.kind() == VALUE)?;
2536 let text = first.text();
2537 let leading_ws = text.len() - text.trim_start().len();
2538 let token_len = text[leading_ws..]
2539 .find(char::is_whitespace)
2540 .unwrap_or(text.len() - leading_ws);
2541 if token_len == 0 {
2542 return None;
2543 }
2544 let start = first.text_range().start() + rowan::TextSize::from(leading_ws as u32);
2545 let end = start + rowan::TextSize::from(token_len as u32);
2546 if start < value_range.start() || end > value_range.end() {
2548 return None;
2549 }
2550 Some(rowan::TextRange::new(start, end))
2551 }
2552
2553 pub fn new(key: &str, value: &str) -> Entry {
2555 Self::with_indentation(key, value, " ")
2556 }
2557
2558 pub fn with_indentation(key: &str, value: &str, indent: &str) -> Entry {
2565 Entry::with_formatting(key, value, " ", indent)
2566 }
2567
2568 pub fn try_with_formatting(
2579 key: &str,
2580 value: &str,
2581 post_colon_ws: &str,
2582 indent: &str,
2583 ) -> Result<Entry, Error> {
2584 let mut builder = GreenNodeBuilder::new();
2585
2586 builder.start_node(ENTRY.into());
2587 builder.token(KEY.into(), key);
2588 builder.token(COLON.into(), ":");
2589
2590 let mut i = 0;
2592 while i < post_colon_ws.len() {
2593 if post_colon_ws[i..].starts_with('\n') {
2594 builder.token(NEWLINE.into(), "\n");
2595 i += 1;
2596 } else {
2597 let start = i;
2599 while i < post_colon_ws.len() && !post_colon_ws[i..].starts_with('\n') {
2600 i += post_colon_ws[i..].chars().next().unwrap().len_utf8();
2601 }
2602 builder.token(WHITESPACE.into(), &post_colon_ws[start..i]);
2603 }
2604 }
2605
2606 for (line_idx, line) in value.split('\n').enumerate() {
2607 if line_idx > 0 {
2608 if line.trim().is_empty() {
2611 return Err(Error::InvalidValue(format!(
2612 "empty continuation line (line with only whitespace) at line {}",
2613 line_idx + 1
2614 )));
2615 }
2616 builder.token(INDENT.into(), indent);
2617 }
2618 builder.token(VALUE.into(), line);
2619 builder.token(NEWLINE.into(), "\n");
2620 }
2621 builder.finish_node();
2622 Ok(Entry(SyntaxNode::new_root_mut(builder.finish())))
2623 }
2624
2625 pub fn with_formatting(key: &str, value: &str, post_colon_ws: &str, indent: &str) -> Entry {
2636 Self::try_with_formatting(key, value, post_colon_ws, indent)
2637 .expect("Invalid value: empty continuation line")
2638 }
2639
2640 #[must_use]
2641 pub fn wrap_and_sort(
2654 &self,
2655 mut indentation: Indentation,
2656 immediate_empty_line: bool,
2657 max_line_length_one_liner: Option<usize>,
2658 format_value: Option<&dyn Fn(&str, &str) -> String>,
2659 ) -> Entry {
2660 let mut builder = GreenNodeBuilder::new();
2661
2662 let mut content = vec![];
2663 builder.start_node(ENTRY.into());
2664 for c in self.0.children_with_tokens() {
2665 let text = c.as_token().map(|t| t.text());
2666 match c.kind() {
2667 KEY => {
2668 builder.token(KEY.into(), text.unwrap());
2669 if indentation == Indentation::FieldNameLength {
2670 indentation = Indentation::Spaces(text.unwrap().len() as u32);
2671 }
2672 }
2673 COLON => {
2674 builder.token(COLON.into(), ":");
2675 }
2676 INDENT => {
2677 }
2679 ERROR | COMMENT | VALUE | WHITESPACE | NEWLINE => {
2680 content.push(c);
2681 }
2682 EMPTY_LINE | ENTRY | ROOT | PARAGRAPH => unreachable!(),
2683 }
2684 }
2685
2686 let indentation = if let crate::Indentation::Spaces(i) = indentation {
2687 i
2688 } else {
2689 1
2690 };
2691
2692 assert!(indentation > 0);
2693
2694 while let Some(c) = content.last() {
2696 if c.kind() == NEWLINE || c.kind() == WHITESPACE {
2697 content.pop();
2698 } else {
2699 break;
2700 }
2701 }
2702
2703 let tokens = if let Some(ref format_value) = format_value {
2706 if !content
2707 .iter()
2708 .any(|c| c.kind() == ERROR || c.kind() == COMMENT)
2709 {
2710 let concat = content
2711 .iter()
2712 .filter_map(|c| c.as_token().map(|t| t.text()))
2713 .collect::<String>();
2714 let formatted = format_value(self.key().as_ref().unwrap(), &concat);
2715 crate::lex::lex_inline(&formatted)
2716 .map(|(k, t)| (k, t.to_string()))
2717 .collect::<Vec<_>>()
2718 } else {
2719 content
2720 .into_iter()
2721 .map(|n| n.into_token().unwrap())
2722 .map(|i| (i.kind(), i.text().to_string()))
2723 .collect::<Vec<_>>()
2724 }
2725 } else {
2726 content
2727 .into_iter()
2728 .map(|n| n.into_token().unwrap())
2729 .map(|i| (i.kind(), i.text().to_string()))
2730 .collect::<Vec<_>>()
2731 };
2732
2733 rebuild_value(
2734 &mut builder,
2735 tokens,
2736 self.key().map_or(0, |k| k.len()),
2737 indentation,
2738 immediate_empty_line,
2739 max_line_length_one_liner,
2740 );
2741
2742 builder.finish_node();
2743 Self(SyntaxNode::new_root_mut(builder.finish()))
2744 }
2745
2746 pub fn key(&self) -> Option<String> {
2748 self.0
2749 .children_with_tokens()
2750 .filter_map(|it| it.into_token())
2751 .find(|it| it.kind() == KEY)
2752 .map(|it| it.text().to_string())
2753 }
2754
2755 pub fn value(&self) -> String {
2757 let mut parts = self
2758 .0
2759 .children_with_tokens()
2760 .filter_map(|it| it.into_token())
2761 .filter(|it| it.kind() == VALUE)
2762 .map(|it| it.text().to_string());
2763
2764 match parts.next() {
2765 None => String::new(),
2766 Some(first) => {
2767 let mut result = first;
2768 for part in parts {
2769 result.push('\n');
2770 result.push_str(&part);
2771 }
2772 result
2773 }
2774 }
2775 }
2776
2777 pub fn value_with_comments(&self) -> String {
2784 let mut parts = self
2785 .0
2786 .children_with_tokens()
2787 .filter_map(|it| it.into_token())
2788 .filter(|it| it.kind() == VALUE || it.kind() == COMMENT)
2789 .map(|it| it.text().to_string());
2790
2791 match parts.next() {
2792 None => String::new(),
2793 Some(first) => {
2794 let mut result = first;
2795 for part in parts {
2796 result.push('\n');
2797 result.push_str(&part);
2798 }
2799 result
2800 }
2801 }
2802 }
2803
2804 fn get_indent(&self) -> Option<String> {
2807 self.0
2808 .children_with_tokens()
2809 .filter_map(|it| it.into_token())
2810 .find(|it| it.kind() == INDENT)
2811 .map(|it| it.text().to_string())
2812 }
2813
2814 fn get_post_colon_whitespace(&self) -> Option<String> {
2818 let mut found_colon = false;
2819 let mut whitespace = String::new();
2820
2821 for token in self
2822 .0
2823 .children_with_tokens()
2824 .filter_map(|it| it.into_token())
2825 {
2826 if token.kind() == COLON {
2827 found_colon = true;
2828 continue;
2829 }
2830
2831 if found_colon {
2832 if token.kind() == WHITESPACE || token.kind() == NEWLINE || token.kind() == INDENT {
2833 whitespace.push_str(token.text());
2834 } else {
2835 break;
2837 }
2838 }
2839 }
2840
2841 if whitespace.is_empty() {
2842 None
2843 } else {
2844 Some(whitespace)
2845 }
2846 }
2847
2848 pub fn normalize_field_spacing(&mut self) -> bool {
2869 use rowan::GreenNodeBuilder;
2870
2871 let original_text = self.0.text().to_string();
2873
2874 let mut builder = GreenNodeBuilder::new();
2876 builder.start_node(ENTRY.into());
2877
2878 let mut seen_colon = false;
2879 let mut skip_whitespace = false;
2880
2881 for child in self.0.children_with_tokens() {
2882 match child.kind() {
2883 KEY => {
2884 builder.token(KEY.into(), child.as_token().unwrap().text());
2885 }
2886 COLON => {
2887 builder.token(COLON.into(), ":");
2888 seen_colon = true;
2889 skip_whitespace = true;
2890 }
2891 WHITESPACE if skip_whitespace => {
2892 continue;
2894 }
2895 VALUE if skip_whitespace => {
2896 builder.token(WHITESPACE.into(), " ");
2898 builder.token(VALUE.into(), child.as_token().unwrap().text());
2899 skip_whitespace = false;
2900 }
2901 NEWLINE if skip_whitespace && seen_colon => {
2902 builder.token(NEWLINE.into(), "\n");
2905 skip_whitespace = false;
2906 }
2907 _ => {
2908 if let Some(token) = child.as_token() {
2910 builder.token(token.kind().into(), token.text());
2911 }
2912 }
2913 }
2914 }
2915
2916 builder.finish_node();
2917 let normalized_green = builder.finish();
2918 let normalized = SyntaxNode::new_root_mut(normalized_green);
2919
2920 let changed = original_text != normalized.text().to_string();
2922
2923 if changed {
2924 if let Some(parent) = self.0.parent() {
2926 let index = self.0.index();
2927 parent.splice_children(index..index + 1, vec![normalized.into()]);
2928 }
2929 }
2930
2931 changed
2932 }
2933
2934 pub fn detach(&mut self) {
2936 self.0.detach();
2937 }
2938}
2939
2940impl FromStr for Deb822 {
2941 type Err = ParseError;
2942
2943 fn from_str(s: &str) -> Result<Self, Self::Err> {
2944 Deb822::parse(s).to_result()
2945 }
2946}
2947
2948#[test]
2949fn test_parse_simple() {
2950 const CONTROLV1: &str = r#"Source: foo
2951Maintainer: Foo Bar <foo@example.com>
2952Section: net
2953
2954# This is a comment
2955
2956Package: foo
2957Architecture: all
2958Depends:
2959 bar,
2960 blah
2961Description: This is a description
2962 And it is
2963 .
2964 multiple
2965 lines
2966"#;
2967 let parsed = parse(CONTROLV1);
2968 let node = parsed.syntax();
2969 assert_eq!(
2970 format!("{:#?}", node),
2971 r###"ROOT@0..203
2972 PARAGRAPH@0..63
2973 ENTRY@0..12
2974 KEY@0..6 "Source"
2975 COLON@6..7 ":"
2976 WHITESPACE@7..8 " "
2977 VALUE@8..11 "foo"
2978 NEWLINE@11..12 "\n"
2979 ENTRY@12..50
2980 KEY@12..22 "Maintainer"
2981 COLON@22..23 ":"
2982 WHITESPACE@23..24 " "
2983 VALUE@24..49 "Foo Bar <foo@example. ..."
2984 NEWLINE@49..50 "\n"
2985 ENTRY@50..63
2986 KEY@50..57 "Section"
2987 COLON@57..58 ":"
2988 WHITESPACE@58..59 " "
2989 VALUE@59..62 "net"
2990 NEWLINE@62..63 "\n"
2991 EMPTY_LINE@63..64
2992 NEWLINE@63..64 "\n"
2993 EMPTY_LINE@64..84
2994 COMMENT@64..83 "# This is a comment"
2995 NEWLINE@83..84 "\n"
2996 EMPTY_LINE@84..85
2997 NEWLINE@84..85 "\n"
2998 PARAGRAPH@85..203
2999 ENTRY@85..98
3000 KEY@85..92 "Package"
3001 COLON@92..93 ":"
3002 WHITESPACE@93..94 " "
3003 VALUE@94..97 "foo"
3004 NEWLINE@97..98 "\n"
3005 ENTRY@98..116
3006 KEY@98..110 "Architecture"
3007 COLON@110..111 ":"
3008 WHITESPACE@111..112 " "
3009 VALUE@112..115 "all"
3010 NEWLINE@115..116 "\n"
3011 ENTRY@116..137
3012 KEY@116..123 "Depends"
3013 COLON@123..124 ":"
3014 NEWLINE@124..125 "\n"
3015 INDENT@125..126 " "
3016 VALUE@126..130 "bar,"
3017 NEWLINE@130..131 "\n"
3018 INDENT@131..132 " "
3019 VALUE@132..136 "blah"
3020 NEWLINE@136..137 "\n"
3021 ENTRY@137..203
3022 KEY@137..148 "Description"
3023 COLON@148..149 ":"
3024 WHITESPACE@149..150 " "
3025 VALUE@150..171 "This is a description"
3026 NEWLINE@171..172 "\n"
3027 INDENT@172..173 " "
3028 VALUE@173..182 "And it is"
3029 NEWLINE@182..183 "\n"
3030 INDENT@183..184 " "
3031 VALUE@184..185 "."
3032 NEWLINE@185..186 "\n"
3033 INDENT@186..187 " "
3034 VALUE@187..195 "multiple"
3035 NEWLINE@195..196 "\n"
3036 INDENT@196..197 " "
3037 VALUE@197..202 "lines"
3038 NEWLINE@202..203 "\n"
3039"###
3040 );
3041 assert_eq!(parsed.errors, Vec::<String>::new());
3042
3043 let root = parsed.root_mut();
3044 assert_eq!(root.paragraphs().count(), 2);
3045 let source = root.paragraphs().next().unwrap();
3046 assert_eq!(
3047 source.keys().collect::<Vec<_>>(),
3048 vec!["Source", "Maintainer", "Section"]
3049 );
3050 assert_eq!(source.get("Source").as_deref(), Some("foo"));
3051 assert_eq!(
3052 source.get("Maintainer").as_deref(),
3053 Some("Foo Bar <foo@example.com>")
3054 );
3055 assert_eq!(source.get("Section").as_deref(), Some("net"));
3056 assert_eq!(
3057 source.items().collect::<Vec<_>>(),
3058 vec![
3059 ("Source".into(), "foo".into()),
3060 ("Maintainer".into(), "Foo Bar <foo@example.com>".into()),
3061 ("Section".into(), "net".into()),
3062 ]
3063 );
3064
3065 let binary = root.paragraphs().nth(1).unwrap();
3066 assert_eq!(
3067 binary.keys().collect::<Vec<_>>(),
3068 vec!["Package", "Architecture", "Depends", "Description"]
3069 );
3070 assert_eq!(binary.get("Package").as_deref(), Some("foo"));
3071 assert_eq!(binary.get("Architecture").as_deref(), Some("all"));
3072 assert_eq!(binary.get("Depends").as_deref(), Some("bar,\nblah"));
3073 assert_eq!(
3074 binary.get("Description").as_deref(),
3075 Some("This is a description\nAnd it is\n.\nmultiple\nlines")
3076 );
3077
3078 assert_eq!(node.text(), CONTROLV1);
3079}
3080
3081#[test]
3082fn test_with_trailing_whitespace() {
3083 const CONTROLV1: &str = r#"Source: foo
3084Maintainer: Foo Bar <foo@example.com>
3085
3086
3087"#;
3088 let parsed = parse(CONTROLV1);
3089 let node = parsed.syntax();
3090 assert_eq!(
3091 format!("{:#?}", node),
3092 r###"ROOT@0..52
3093 PARAGRAPH@0..50
3094 ENTRY@0..12
3095 KEY@0..6 "Source"
3096 COLON@6..7 ":"
3097 WHITESPACE@7..8 " "
3098 VALUE@8..11 "foo"
3099 NEWLINE@11..12 "\n"
3100 ENTRY@12..50
3101 KEY@12..22 "Maintainer"
3102 COLON@22..23 ":"
3103 WHITESPACE@23..24 " "
3104 VALUE@24..49 "Foo Bar <foo@example. ..."
3105 NEWLINE@49..50 "\n"
3106 EMPTY_LINE@50..51
3107 NEWLINE@50..51 "\n"
3108 EMPTY_LINE@51..52
3109 NEWLINE@51..52 "\n"
3110"###
3111 );
3112 assert_eq!(parsed.errors, Vec::<String>::new());
3113
3114 let root = parsed.root_mut();
3115 assert_eq!(root.paragraphs().count(), 1);
3116 let source = root.paragraphs().next().unwrap();
3117 assert_eq!(
3118 source.items().collect::<Vec<_>>(),
3119 vec![
3120 ("Source".into(), "foo".into()),
3121 ("Maintainer".into(), "Foo Bar <foo@example.com>".into()),
3122 ]
3123 );
3124}
3125
3126fn rebuild_value(
3127 builder: &mut GreenNodeBuilder,
3128 mut tokens: Vec<(SyntaxKind, String)>,
3129 key_len: usize,
3130 indentation: u32,
3131 immediate_empty_line: bool,
3132 max_line_length_one_liner: Option<usize>,
3133) {
3134 let first_line_len = tokens
3135 .iter()
3136 .take_while(|(k, _t)| *k != NEWLINE)
3137 .map(|(_k, t)| t.len())
3138 .sum::<usize>() + key_len + 2 ;
3139
3140 let has_newline = tokens.iter().any(|(k, _t)| *k == NEWLINE);
3141
3142 let mut last_was_newline = false;
3143 if max_line_length_one_liner
3144 .map(|mll| first_line_len <= mll)
3145 .unwrap_or(false)
3146 && !has_newline
3147 {
3148 for (k, t) in tokens {
3150 builder.token(k.into(), &t);
3151 }
3152 } else {
3153 if immediate_empty_line && has_newline {
3155 builder.token(NEWLINE.into(), "\n");
3156 last_was_newline = true;
3157 } else {
3158 builder.token(WHITESPACE.into(), " ");
3159 }
3160 let mut start_idx = 0;
3162 while start_idx < tokens.len() {
3163 if tokens[start_idx].0 == NEWLINE || tokens[start_idx].0 == WHITESPACE {
3164 start_idx += 1;
3165 } else {
3166 break;
3167 }
3168 }
3169 tokens.drain(..start_idx);
3170 let indent_str = " ".repeat(indentation as usize);
3172 for (k, t) in tokens {
3173 if last_was_newline {
3174 builder.token(INDENT.into(), &indent_str);
3175 }
3176 builder.token(k.into(), &t);
3177 last_was_newline = k == NEWLINE;
3178 }
3179 }
3180
3181 if !last_was_newline {
3182 builder.token(NEWLINE.into(), "\n");
3183 }
3184}
3185
3186#[cfg(test)]
3187mod tests {
3188 use super::*;
3189 #[test]
3190 fn test_parse() {
3191 let d: super::Deb822 = r#"Source: foo
3192Maintainer: Foo Bar <jelmer@jelmer.uk>
3193Section: net
3194
3195Package: foo
3196Architecture: all
3197Depends: libc6
3198Description: This is a description
3199 With details
3200"#
3201 .parse()
3202 .unwrap();
3203 let mut ps = d.paragraphs();
3204 let p = ps.next().unwrap();
3205
3206 assert_eq!(p.get("Source").as_deref(), Some("foo"));
3207 assert_eq!(
3208 p.get("Maintainer").as_deref(),
3209 Some("Foo Bar <jelmer@jelmer.uk>")
3210 );
3211 assert_eq!(p.get("Section").as_deref(), Some("net"));
3212
3213 let b = ps.next().unwrap();
3214 assert_eq!(b.get("Package").as_deref(), Some("foo"));
3215 }
3216
3217 #[test]
3218 fn test_after_multi_line() {
3219 let d: super::Deb822 = r#"Source: golang-github-blah-blah
3220Section: devel
3221Priority: optional
3222Standards-Version: 4.2.0
3223Maintainer: Some Maintainer <example@example.com>
3224Build-Depends: debhelper (>= 11~),
3225 dh-golang,
3226 golang-any
3227Homepage: https://github.com/j-keck/arping
3228"#
3229 .parse()
3230 .unwrap();
3231 let mut ps = d.paragraphs();
3232 let p = ps.next().unwrap();
3233 assert_eq!(p.get("Source").as_deref(), Some("golang-github-blah-blah"));
3234 assert_eq!(p.get("Section").as_deref(), Some("devel"));
3235 assert_eq!(p.get("Priority").as_deref(), Some("optional"));
3236 assert_eq!(p.get("Standards-Version").as_deref(), Some("4.2.0"));
3237 assert_eq!(
3238 p.get("Maintainer").as_deref(),
3239 Some("Some Maintainer <example@example.com>")
3240 );
3241 assert_eq!(
3242 p.get("Build-Depends").as_deref(),
3243 Some("debhelper (>= 11~),\ndh-golang,\ngolang-any")
3244 );
3245 assert_eq!(
3246 p.get("Homepage").as_deref(),
3247 Some("https://github.com/j-keck/arping")
3248 );
3249 }
3250
3251 #[test]
3252 fn test_remove_field() {
3253 let d: super::Deb822 = r#"Source: foo
3254# Comment
3255Maintainer: Foo Bar <jelmer@jelmer.uk>
3256Section: net
3257
3258Package: foo
3259Architecture: all
3260Depends: libc6
3261Description: This is a description
3262 With details
3263"#
3264 .parse()
3265 .unwrap();
3266 let mut ps = d.paragraphs();
3267 let mut p = ps.next().unwrap();
3268 p.set("Foo", "Bar");
3269 p.remove("Section");
3270 p.remove("Nonexistent");
3271 assert_eq!(p.get("Foo").as_deref(), Some("Bar"));
3272 assert_eq!(
3273 p.to_string(),
3274 r#"Source: foo
3275# Comment
3276Maintainer: Foo Bar <jelmer@jelmer.uk>
3277Foo: Bar
3278"#
3279 );
3280 }
3281
3282 #[test]
3283 fn test_rename_field() {
3284 let d: super::Deb822 = r#"Source: foo
3285Vcs-Browser: https://salsa.debian.org/debian/foo
3286"#
3287 .parse()
3288 .unwrap();
3289 let mut ps = d.paragraphs();
3290 let mut p = ps.next().unwrap();
3291 assert!(p.rename("Vcs-Browser", "Homepage"));
3292 assert_eq!(
3293 p.to_string(),
3294 r#"Source: foo
3295Homepage: https://salsa.debian.org/debian/foo
3296"#
3297 );
3298
3299 assert_eq!(
3300 p.get("Homepage").as_deref(),
3301 Some("https://salsa.debian.org/debian/foo")
3302 );
3303 assert_eq!(p.get("Vcs-Browser").as_deref(), None);
3304
3305 assert!(!p.rename("Nonexistent", "Homepage"));
3307 }
3308
3309 #[test]
3310 fn test_set_field() {
3311 let d: super::Deb822 = r#"Source: foo
3312Maintainer: Foo Bar <joe@example.com>
3313"#
3314 .parse()
3315 .unwrap();
3316 let mut ps = d.paragraphs();
3317 let mut p = ps.next().unwrap();
3318 p.set("Maintainer", "Somebody Else <jane@example.com>");
3319 assert_eq!(
3320 p.get("Maintainer").as_deref(),
3321 Some("Somebody Else <jane@example.com>")
3322 );
3323 assert_eq!(
3324 p.to_string(),
3325 r#"Source: foo
3326Maintainer: Somebody Else <jane@example.com>
3327"#
3328 );
3329 }
3330
3331 #[test]
3332 fn test_set_new_field() {
3333 let d: super::Deb822 = r#"Source: foo
3334"#
3335 .parse()
3336 .unwrap();
3337 let mut ps = d.paragraphs();
3338 let mut p = ps.next().unwrap();
3339 p.set("Maintainer", "Somebody <joe@example.com>");
3340 assert_eq!(
3341 p.get("Maintainer").as_deref(),
3342 Some("Somebody <joe@example.com>")
3343 );
3344 assert_eq!(
3345 p.to_string(),
3346 r#"Source: foo
3347Maintainer: Somebody <joe@example.com>
3348"#
3349 );
3350 }
3351
3352 #[test]
3353 fn test_add_paragraph() {
3354 let mut d = super::Deb822::new();
3355 let mut p = d.add_paragraph();
3356 p.set("Foo", "Bar");
3357 assert_eq!(p.get("Foo").as_deref(), Some("Bar"));
3358 assert_eq!(
3359 p.to_string(),
3360 r#"Foo: Bar
3361"#
3362 );
3363 assert_eq!(
3364 d.to_string(),
3365 r#"Foo: Bar
3366"#
3367 );
3368
3369 let mut p = d.add_paragraph();
3370 p.set("Foo", "Blah");
3371 assert_eq!(p.get("Foo").as_deref(), Some("Blah"));
3372 assert_eq!(
3373 d.to_string(),
3374 r#"Foo: Bar
3375
3376Foo: Blah
3377"#
3378 );
3379 }
3380
3381 #[test]
3382 fn test_crud_paragraph() {
3383 let mut d = super::Deb822::new();
3384 let mut p = d.insert_paragraph(0);
3385 p.set("Foo", "Bar");
3386 assert_eq!(p.get("Foo").as_deref(), Some("Bar"));
3387 assert_eq!(
3388 d.to_string(),
3389 r#"Foo: Bar
3390"#
3391 );
3392
3393 let mut p = d.insert_paragraph(0);
3395 p.set("Foo", "Blah");
3396 assert_eq!(p.get("Foo").as_deref(), Some("Blah"));
3397 assert_eq!(
3398 d.to_string(),
3399 r#"Foo: Blah
3400
3401Foo: Bar
3402"#
3403 );
3404
3405 d.remove_paragraph(1);
3407 assert_eq!(d.to_string(), "Foo: Blah\n\n");
3408
3409 p.set("Foo", "Baz");
3411 assert_eq!(d.to_string(), "Foo: Baz\n\n");
3412
3413 d.remove_paragraph(0);
3415 assert_eq!(d.to_string(), "");
3416 }
3417
3418 #[test]
3419 fn test_swap_paragraphs() {
3420 let mut d: super::Deb822 = vec![
3422 vec![("Foo", "Bar")].into_iter().collect(),
3423 vec![("A", "B")].into_iter().collect(),
3424 vec![("X", "Y")].into_iter().collect(),
3425 ]
3426 .into_iter()
3427 .collect();
3428
3429 d.swap_paragraphs(0, 2);
3430 assert_eq!(d.to_string(), "X: Y\n\nA: B\n\nFoo: Bar\n");
3431
3432 d.swap_paragraphs(0, 2);
3434 assert_eq!(d.to_string(), "Foo: Bar\n\nA: B\n\nX: Y\n");
3435
3436 d.swap_paragraphs(0, 1);
3438 assert_eq!(d.to_string(), "A: B\n\nFoo: Bar\n\nX: Y\n");
3439
3440 let before = d.to_string();
3442 d.swap_paragraphs(1, 1);
3443 assert_eq!(d.to_string(), before);
3444 }
3445
3446 #[test]
3447 fn test_swap_paragraphs_preserves_content() {
3448 let mut d: super::Deb822 = vec![
3450 vec![("Field1", "Value1"), ("Field2", "Value2")]
3451 .into_iter()
3452 .collect(),
3453 vec![("FieldA", "ValueA"), ("FieldB", "ValueB")]
3454 .into_iter()
3455 .collect(),
3456 ]
3457 .into_iter()
3458 .collect();
3459
3460 d.swap_paragraphs(0, 1);
3461
3462 let mut paras = d.paragraphs();
3463 let p1 = paras.next().unwrap();
3464 assert_eq!(p1.get("FieldA").as_deref(), Some("ValueA"));
3465 assert_eq!(p1.get("FieldB").as_deref(), Some("ValueB"));
3466
3467 let p2 = paras.next().unwrap();
3468 assert_eq!(p2.get("Field1").as_deref(), Some("Value1"));
3469 assert_eq!(p2.get("Field2").as_deref(), Some("Value2"));
3470 }
3471
3472 #[test]
3473 #[should_panic(expected = "out of bounds")]
3474 fn test_swap_paragraphs_out_of_bounds() {
3475 let mut d: super::Deb822 = vec![
3476 vec![("Foo", "Bar")].into_iter().collect(),
3477 vec![("A", "B")].into_iter().collect(),
3478 ]
3479 .into_iter()
3480 .collect();
3481
3482 d.swap_paragraphs(0, 5);
3483 }
3484
3485 #[test]
3486 fn test_multiline_entry() {
3487 use super::SyntaxKind::*;
3488 use rowan::ast::AstNode;
3489
3490 let entry = super::Entry::new("foo", "bar\nbaz");
3491 let tokens: Vec<_> = entry
3492 .syntax()
3493 .descendants_with_tokens()
3494 .filter_map(|tok| tok.into_token())
3495 .collect();
3496
3497 assert_eq!("foo: bar\n baz\n", entry.to_string());
3498 assert_eq!("bar\nbaz", entry.value());
3499
3500 assert_eq!(
3501 vec![
3502 (KEY, "foo"),
3503 (COLON, ":"),
3504 (WHITESPACE, " "),
3505 (VALUE, "bar"),
3506 (NEWLINE, "\n"),
3507 (INDENT, " "),
3508 (VALUE, "baz"),
3509 (NEWLINE, "\n"),
3510 ],
3511 tokens
3512 .iter()
3513 .map(|token| (token.kind(), token.text()))
3514 .collect::<Vec<_>>()
3515 );
3516 }
3517
3518 #[test]
3519 fn test_apt_entry() {
3520 let text = r#"Package: cvsd
3521Binary: cvsd
3522Version: 1.0.24
3523Maintainer: Arthur de Jong <adejong@debian.org>
3524Build-Depends: debhelper (>= 9), po-debconf
3525Architecture: any
3526Standards-Version: 3.9.3
3527Format: 3.0 (native)
3528Files:
3529 b7a7d67a02974c52c408fdb5e118406d 890 cvsd_1.0.24.dsc
3530 b73ee40774c3086cb8490cdbb96ac883 258139 cvsd_1.0.24.tar.gz
3531Vcs-Browser: http://arthurdejong.org/viewvc/cvsd/
3532Vcs-Cvs: :pserver:anonymous@arthurdejong.org:/arthur/
3533Checksums-Sha256:
3534 a7bb7a3aacee19cd14ce5c26cb86e348b1608e6f1f6e97c6ea7c58efa440ac43 890 cvsd_1.0.24.dsc
3535 46bc517760c1070ae408693b89603986b53e6f068ae6bdc744e2e830e46b8cba 258139 cvsd_1.0.24.tar.gz
3536Homepage: http://arthurdejong.org/cvsd/
3537Package-List:
3538 cvsd deb vcs optional
3539Directory: pool/main/c/cvsd
3540Priority: source
3541Section: vcs
3542
3543"#;
3544 let d: super::Deb822 = text.parse().unwrap();
3545 let p = d.paragraphs().next().unwrap();
3546 assert_eq!(p.get("Binary").as_deref(), Some("cvsd"));
3547 assert_eq!(p.get("Version").as_deref(), Some("1.0.24"));
3548 assert_eq!(
3549 p.get("Maintainer").as_deref(),
3550 Some("Arthur de Jong <adejong@debian.org>")
3551 );
3552 }
3553
3554 #[test]
3555 fn test_format() {
3556 let d: super::Deb822 = r#"Source: foo
3557Maintainer: Foo Bar <foo@example.com>
3558Section: net
3559Blah: blah # comment
3560Multi-Line:
3561 Ahoi!
3562 Matey!
3563
3564"#
3565 .parse()
3566 .unwrap();
3567 let mut ps = d.paragraphs();
3568 let p = ps.next().unwrap();
3569 let result = p.wrap_and_sort(
3570 crate::Indentation::FieldNameLength,
3571 false,
3572 None,
3573 None::<&dyn Fn(&super::Entry, &super::Entry) -> std::cmp::Ordering>,
3574 None,
3575 );
3576 assert_eq!(
3577 result.to_string(),
3578 r#"Source: foo
3579Maintainer: Foo Bar <foo@example.com>
3580Section: net
3581Blah: blah # comment
3582Multi-Line: Ahoi!
3583 Matey!
3584"#
3585 );
3586 }
3587
3588 #[test]
3589 fn test_format_sort_paragraphs() {
3590 let d: super::Deb822 = r#"Source: foo
3591Maintainer: Foo Bar <foo@example.com>
3592
3593# This is a comment
3594Source: bar
3595Maintainer: Bar Foo <bar@example.com>
3596
3597"#
3598 .parse()
3599 .unwrap();
3600 let result = d.wrap_and_sort(
3601 Some(&|a: &super::Paragraph, b: &super::Paragraph| {
3602 a.get("Source").cmp(&b.get("Source"))
3603 }),
3604 Some(&|p| {
3605 p.wrap_and_sort(
3606 crate::Indentation::FieldNameLength,
3607 false,
3608 None,
3609 None::<&dyn Fn(&super::Entry, &super::Entry) -> std::cmp::Ordering>,
3610 None,
3611 )
3612 }),
3613 );
3614 assert_eq!(
3615 result.to_string(),
3616 r#"# This is a comment
3617Source: bar
3618Maintainer: Bar Foo <bar@example.com>
3619
3620Source: foo
3621Maintainer: Foo Bar <foo@example.com>
3622"#,
3623 );
3624 }
3625
3626 #[test]
3627 fn test_format_sort_fields() {
3628 let d: super::Deb822 = r#"Source: foo
3629Maintainer: Foo Bar <foo@example.com>
3630Build-Depends: debhelper (>= 9), po-debconf
3631Homepage: https://example.com/
3632
3633"#
3634 .parse()
3635 .unwrap();
3636 let result = d.wrap_and_sort(
3637 None,
3638 Some(&|p: &super::Paragraph| -> super::Paragraph {
3639 p.wrap_and_sort(
3640 crate::Indentation::FieldNameLength,
3641 false,
3642 None,
3643 Some(&|a: &super::Entry, b: &super::Entry| a.key().cmp(&b.key())),
3644 None,
3645 )
3646 }),
3647 );
3648 assert_eq!(
3649 result.to_string(),
3650 r#"Build-Depends: debhelper (>= 9), po-debconf
3651Homepage: https://example.com/
3652Maintainer: Foo Bar <foo@example.com>
3653Source: foo
3654"#
3655 );
3656 }
3657
3658 #[test]
3659 fn test_para_from_iter() {
3660 let p: super::Paragraph = vec![("Foo", "Bar"), ("Baz", "Qux")].into_iter().collect();
3661 assert_eq!(
3662 p.to_string(),
3663 r#"Foo: Bar
3664Baz: Qux
3665"#
3666 );
3667
3668 let p: super::Paragraph = vec![
3669 ("Foo".to_string(), "Bar".to_string()),
3670 ("Baz".to_string(), "Qux".to_string()),
3671 ]
3672 .into_iter()
3673 .collect();
3674
3675 assert_eq!(
3676 p.to_string(),
3677 r#"Foo: Bar
3678Baz: Qux
3679"#
3680 );
3681 }
3682
3683 #[test]
3684 fn test_deb822_from_iter() {
3685 let d: super::Deb822 = vec![
3686 vec![("Foo", "Bar"), ("Baz", "Qux")].into_iter().collect(),
3687 vec![("A", "B"), ("C", "D")].into_iter().collect(),
3688 ]
3689 .into_iter()
3690 .collect();
3691 assert_eq!(
3692 d.to_string(),
3693 r#"Foo: Bar
3694Baz: Qux
3695
3696A: B
3697C: D
3698"#
3699 );
3700 }
3701
3702 #[test]
3703 fn test_format_parse_error() {
3704 assert_eq!(ParseError(vec!["foo".to_string()]).to_string(), "foo\n");
3705 }
3706
3707 #[test]
3708 fn test_set_with_field_order() {
3709 let mut p = super::Paragraph::new();
3710 let custom_order = &["Foo", "Bar", "Baz"];
3711
3712 p.set_with_field_order("Baz", "3", custom_order);
3713 p.set_with_field_order("Foo", "1", custom_order);
3714 p.set_with_field_order("Bar", "2", custom_order);
3715 p.set_with_field_order("Unknown", "4", custom_order);
3716
3717 let keys: Vec<_> = p.keys().collect();
3718 assert_eq!(keys[0], "Foo");
3719 assert_eq!(keys[1], "Bar");
3720 assert_eq!(keys[2], "Baz");
3721 assert_eq!(keys[3], "Unknown");
3722 }
3723
3724 #[test]
3725 fn test_positioned_parse_error() {
3726 let error = PositionedParseError {
3727 message: "test error".to_string(),
3728 range: rowan::TextRange::new(rowan::TextSize::from(5), rowan::TextSize::from(10)),
3729 code: Some("test_code".to_string()),
3730 };
3731 assert_eq!(error.to_string(), "test error");
3732 assert_eq!(error.range.start(), rowan::TextSize::from(5));
3733 assert_eq!(error.range.end(), rowan::TextSize::from(10));
3734 assert_eq!(error.code, Some("test_code".to_string()));
3735 }
3736
3737 #[test]
3738 fn test_format_error() {
3739 assert_eq!(
3740 super::Error::ParseError(ParseError(vec!["foo".to_string()])).to_string(),
3741 "foo\n"
3742 );
3743 }
3744
3745 #[test]
3746 fn test_get_all() {
3747 let d: super::Deb822 = r#"Source: foo
3748Maintainer: Foo Bar <foo@example.com>
3749Maintainer: Bar Foo <bar@example.com>"#
3750 .parse()
3751 .unwrap();
3752 let p = d.paragraphs().next().unwrap();
3753 assert_eq!(
3754 p.get_all("Maintainer").collect::<Vec<_>>(),
3755 vec!["Foo Bar <foo@example.com>", "Bar Foo <bar@example.com>"]
3756 );
3757 }
3758
3759 #[test]
3760 fn test_get_with_indent_single_line() {
3761 let input = "Field: single line value\n";
3762 let deb = super::Deb822::from_str(input).unwrap();
3763 let para = deb.paragraphs().next().unwrap();
3764
3765 assert_eq!(
3767 para.get_with_indent("Field", &super::IndentPattern::Fixed(2)),
3768 Some("single line value".to_string())
3769 );
3770 assert_eq!(
3771 para.get_with_indent("Field", &super::IndentPattern::FieldNameLength),
3772 Some("single line value".to_string())
3773 );
3774 }
3775
3776 #[test]
3777 fn test_get_with_indent_fixed() {
3778 let input = "Field: First\n Second\n Third\n";
3779 let deb = super::Deb822::from_str(input).unwrap();
3780 let para = deb.paragraphs().next().unwrap();
3781
3782 let value = para
3784 .get_with_indent("Field", &super::IndentPattern::Fixed(2))
3785 .unwrap();
3786 assert_eq!(value, "First\n Second\n Third");
3787
3788 let value = para
3790 .get_with_indent("Field", &super::IndentPattern::Fixed(1))
3791 .unwrap();
3792 assert_eq!(value, "First\n Second\n Third");
3793
3794 let value = para
3796 .get_with_indent("Field", &super::IndentPattern::Fixed(3))
3797 .unwrap();
3798 assert_eq!(value, "First\nSecond\nThird");
3799 }
3800
3801 #[test]
3802 fn test_get_with_indent_field_name_length() {
3803 let input = "Description: First line\n Second line\n Third line\n";
3804 let deb = super::Deb822::from_str(input).unwrap();
3805 let para = deb.paragraphs().next().unwrap();
3806
3807 let value = para
3810 .get_with_indent("Description", &super::IndentPattern::FieldNameLength)
3811 .unwrap();
3812 assert_eq!(value, "First line\nSecond line\nThird line");
3813
3814 let value = para
3816 .get_with_indent("Description", &super::IndentPattern::Fixed(2))
3817 .unwrap();
3818 assert_eq!(
3819 value,
3820 "First line\n Second line\n Third line"
3821 );
3822 }
3823
3824 #[test]
3825 fn test_get_with_indent_nonexistent() {
3826 let input = "Field: value\n";
3827 let deb = super::Deb822::from_str(input).unwrap();
3828 let para = deb.paragraphs().next().unwrap();
3829
3830 assert_eq!(
3831 para.get_with_indent("NonExistent", &super::IndentPattern::Fixed(2)),
3832 None
3833 );
3834 }
3835
3836 #[test]
3837 fn test_get_entry() {
3838 let input = r#"Package: test-package
3839Maintainer: Test User <test@example.com>
3840Description: A simple test package
3841 with multiple lines
3842"#;
3843 let deb = super::Deb822::from_str(input).unwrap();
3844 let para = deb.paragraphs().next().unwrap();
3845
3846 let entry = para.get_entry("Package");
3848 assert!(entry.is_some());
3849 let entry = entry.unwrap();
3850 assert_eq!(entry.key(), Some("Package".to_string()));
3851 assert_eq!(entry.value(), "test-package");
3852
3853 let entry = para.get_entry("package");
3855 assert!(entry.is_some());
3856 assert_eq!(entry.unwrap().value(), "test-package");
3857
3858 let entry = para.get_entry("Description");
3860 assert!(entry.is_some());
3861 assert_eq!(
3862 entry.unwrap().value(),
3863 "A simple test package\nwith multiple lines"
3864 );
3865
3866 assert_eq!(para.get_entry("NonExistent"), None);
3868 }
3869
3870 #[test]
3871 fn test_entry_ranges() {
3872 let input = r#"Package: test-package
3873Maintainer: Test User <test@example.com>
3874Description: A simple test package
3875 with multiple lines
3876 of description text"#;
3877
3878 let deb822 = super::Deb822::from_str(input).unwrap();
3879 let paragraph = deb822.paragraphs().next().unwrap();
3880 let entries: Vec<_> = paragraph.entries().collect();
3881
3882 let package_entry = &entries[0];
3884 assert_eq!(package_entry.key(), Some("Package".to_string()));
3885
3886 let key_range = package_entry.key_range().unwrap();
3888 assert_eq!(
3889 &input[key_range.start().into()..key_range.end().into()],
3890 "Package"
3891 );
3892
3893 let colon_range = package_entry.colon_range().unwrap();
3895 assert_eq!(
3896 &input[colon_range.start().into()..colon_range.end().into()],
3897 ":"
3898 );
3899
3900 let value_range = package_entry.value_range().unwrap();
3902 assert_eq!(
3903 &input[value_range.start().into()..value_range.end().into()],
3904 "test-package"
3905 );
3906
3907 let text_range = package_entry.text_range();
3909 assert_eq!(
3910 &input[text_range.start().into()..text_range.end().into()],
3911 "Package: test-package\n"
3912 );
3913
3914 let value_lines = package_entry.value_line_ranges();
3916 assert_eq!(value_lines.len(), 1);
3917 assert_eq!(
3918 &input[value_lines[0].start().into()..value_lines[0].end().into()],
3919 "test-package"
3920 );
3921
3922 let token_range = package_entry.value_token_range().unwrap();
3924 assert_eq!(
3925 &input[token_range.start().into()..token_range.end().into()],
3926 "test-package"
3927 );
3928 }
3929
3930 #[test]
3931 fn test_value_token_range_multiword() {
3932 let input = "License: GPL-2+ with the autoconf exception\n";
3933 let para = Deb822::from_str(input)
3934 .unwrap()
3935 .paragraphs()
3936 .next()
3937 .unwrap();
3938 let entry = para.entries().next().unwrap();
3939 let token_range = entry.value_token_range().unwrap();
3940 assert_eq!(
3942 &input[token_range.start().into()..token_range.end().into()],
3943 "GPL-2+"
3944 );
3945 }
3946
3947 #[test]
3948 fn test_value_token_range_empty() {
3949 let input = "Description:\n";
3950 let para = Deb822::from_str(input)
3951 .unwrap()
3952 .paragraphs()
3953 .next()
3954 .unwrap();
3955 let entry = para.entries().next().unwrap();
3956 assert_eq!(entry.value_token_range(), None);
3957 }
3958
3959 #[test]
3960 fn test_multiline_entry_ranges() {
3961 let input = r#"Description: Short description
3962 Extended description line 1
3963 Extended description line 2"#;
3964
3965 let deb822 = super::Deb822::from_str(input).unwrap();
3966 let paragraph = deb822.paragraphs().next().unwrap();
3967 let entry = paragraph.entries().next().unwrap();
3968
3969 assert_eq!(entry.key(), Some("Description".to_string()));
3970
3971 let value_range = entry.value_range().unwrap();
3973 let full_value = &input[value_range.start().into()..value_range.end().into()];
3974 assert!(full_value.contains("Short description"));
3975 assert!(full_value.contains("Extended description line 1"));
3976 assert!(full_value.contains("Extended description line 2"));
3977
3978 let value_lines = entry.value_line_ranges();
3980 assert_eq!(value_lines.len(), 3);
3981
3982 assert_eq!(
3983 &input[value_lines[0].start().into()..value_lines[0].end().into()],
3984 "Short description"
3985 );
3986 assert_eq!(
3987 &input[value_lines[1].start().into()..value_lines[1].end().into()],
3988 "Extended description line 1"
3989 );
3990 assert_eq!(
3991 &input[value_lines[2].start().into()..value_lines[2].end().into()],
3992 "Extended description line 2"
3993 );
3994 }
3995
3996 #[test]
3997 fn test_entries_public_access() {
3998 let input = r#"Package: test
3999Version: 1.0"#;
4000
4001 let deb822 = super::Deb822::from_str(input).unwrap();
4002 let paragraph = deb822.paragraphs().next().unwrap();
4003
4004 let entries: Vec<_> = paragraph.entries().collect();
4006 assert_eq!(entries.len(), 2);
4007 assert_eq!(entries[0].key(), Some("Package".to_string()));
4008 assert_eq!(entries[1].key(), Some("Version".to_string()));
4009 }
4010
4011 #[test]
4012 fn test_empty_value_ranges() {
4013 let input = r#"EmptyField: "#;
4014
4015 let deb822 = super::Deb822::from_str(input).unwrap();
4016 let paragraph = deb822.paragraphs().next().unwrap();
4017 let entry = paragraph.entries().next().unwrap();
4018
4019 assert_eq!(entry.key(), Some("EmptyField".to_string()));
4020
4021 assert!(entry.key_range().is_some());
4023 assert!(entry.colon_range().is_some());
4024
4025 let value_lines = entry.value_line_ranges();
4027 assert!(value_lines.len() <= 1);
4030 }
4031
4032 #[test]
4033 fn test_range_ordering() {
4034 let input = r#"Field: value"#;
4035
4036 let deb822 = super::Deb822::from_str(input).unwrap();
4037 let paragraph = deb822.paragraphs().next().unwrap();
4038 let entry = paragraph.entries().next().unwrap();
4039
4040 let key_range = entry.key_range().unwrap();
4041 let colon_range = entry.colon_range().unwrap();
4042 let value_range = entry.value_range().unwrap();
4043 let text_range = entry.text_range();
4044
4045 assert!(key_range.end() <= colon_range.start());
4047 assert!(colon_range.end() <= value_range.start());
4048 assert!(key_range.start() >= text_range.start());
4049 assert!(value_range.end() <= text_range.end());
4050 }
4051
4052 #[test]
4053 fn test_error_recovery_missing_colon() {
4054 let input = r#"Source foo
4055Maintainer: Test User <test@example.com>
4056"#;
4057 let (deb822, errors) = super::Deb822::from_str_relaxed(input);
4058
4059 assert!(!errors.is_empty());
4061 assert!(errors.iter().any(|e| e.contains("missing colon")));
4062
4063 let paragraph = deb822.paragraphs().next().unwrap();
4065 assert_eq!(
4066 paragraph.get("Maintainer").as_deref(),
4067 Some("Test User <test@example.com>")
4068 );
4069 }
4070
4071 #[test]
4072 fn test_error_recovery_missing_field_name() {
4073 let input = r#": orphaned value
4074Package: test
4075"#;
4076
4077 let (deb822, errors) = super::Deb822::from_str_relaxed(input);
4078
4079 assert!(!errors.is_empty());
4081 assert!(errors
4082 .iter()
4083 .any(|e| e.contains("field name") || e.contains("missing")));
4084
4085 let paragraphs: Vec<_> = deb822.paragraphs().collect();
4087 let mut found_package = false;
4088 for paragraph in paragraphs.iter() {
4089 if paragraph.get("Package").is_some() {
4090 found_package = true;
4091 assert_eq!(paragraph.get("Package").as_deref(), Some("test"));
4092 }
4093 }
4094 assert!(found_package, "Package field not found in any paragraph");
4095 }
4096
4097 #[test]
4098 fn test_error_recovery_orphaned_text() {
4099 let input = r#"Package: test
4100some orphaned text without field name
4101Version: 1.0
4102"#;
4103 let (deb822, errors) = super::Deb822::from_str_relaxed(input);
4104
4105 assert!(!errors.is_empty());
4107 assert!(errors.iter().any(|e| e.contains("orphaned")
4108 || e.contains("unexpected")
4109 || e.contains("field name")));
4110
4111 let mut all_fields = std::collections::HashMap::new();
4113 for paragraph in deb822.paragraphs() {
4114 for (key, value) in paragraph.items() {
4115 all_fields.insert(key, value);
4116 }
4117 }
4118
4119 assert_eq!(all_fields.get("Package"), Some(&"test".to_string()));
4120 assert_eq!(all_fields.get("Version"), Some(&"1.0".to_string()));
4121 }
4122
4123 #[test]
4124 fn test_error_recovery_consecutive_field_names() {
4125 let input = r#"Package: test
4126Description
4127Maintainer: Another field without proper value
4128Version: 1.0
4129"#;
4130 let (deb822, errors) = super::Deb822::from_str_relaxed(input);
4131
4132 assert!(!errors.is_empty());
4134 assert!(errors.iter().any(|e| e.contains("consecutive")
4135 || e.contains("missing")
4136 || e.contains("incomplete")));
4137
4138 let mut all_fields = std::collections::HashMap::new();
4140 for paragraph in deb822.paragraphs() {
4141 for (key, value) in paragraph.items() {
4142 all_fields.insert(key, value);
4143 }
4144 }
4145
4146 assert_eq!(all_fields.get("Package"), Some(&"test".to_string()));
4147 assert_eq!(
4148 all_fields.get("Maintainer"),
4149 Some(&"Another field without proper value".to_string())
4150 );
4151 assert_eq!(all_fields.get("Version"), Some(&"1.0".to_string()));
4152 }
4153
4154 #[test]
4155 fn test_error_recovery_malformed_multiline() {
4156 let input = r#"Package: test
4157Description: Short desc
4158 Proper continuation
4159invalid continuation without indent
4160 Another proper continuation
4161Version: 1.0
4162"#;
4163 let (deb822, errors) = super::Deb822::from_str_relaxed(input);
4164
4165 assert!(!errors.is_empty());
4167
4168 let paragraph = deb822.paragraphs().next().unwrap();
4170 assert_eq!(paragraph.get("Package").as_deref(), Some("test"));
4171 assert_eq!(paragraph.get("Version").as_deref(), Some("1.0"));
4172 }
4173
4174 #[test]
4175 fn test_error_recovery_mixed_errors() {
4176 let input = r#"Package test without colon
4177: orphaned colon
4178Description: Valid field
4179some orphaned text
4180Another-Field: Valid too
4181"#;
4182 let (deb822, errors) = super::Deb822::from_str_relaxed(input);
4183
4184 assert!(!errors.is_empty());
4186 assert!(errors.len() >= 2);
4187
4188 let paragraph = deb822.paragraphs().next().unwrap();
4190 assert_eq!(paragraph.get("Description").as_deref(), Some("Valid field"));
4191 assert_eq!(paragraph.get("Another-Field").as_deref(), Some("Valid too"));
4192 }
4193
4194 #[test]
4195 fn test_error_recovery_paragraph_boundary() {
4196 let input = r#"Package: first-package
4197Description: First paragraph
4198
4199corrupted data here
4200: more corruption
4201completely broken line
4202
4203Package: second-package
4204Version: 1.0
4205"#;
4206 let (deb822, errors) = super::Deb822::from_str_relaxed(input);
4207
4208 assert!(!errors.is_empty());
4210
4211 let paragraphs: Vec<_> = deb822.paragraphs().collect();
4213 assert_eq!(paragraphs.len(), 2);
4214
4215 assert_eq!(
4216 paragraphs[0].get("Package").as_deref(),
4217 Some("first-package")
4218 );
4219 assert_eq!(
4220 paragraphs[1].get("Package").as_deref(),
4221 Some("second-package")
4222 );
4223 assert_eq!(paragraphs[1].get("Version").as_deref(), Some("1.0"));
4224 }
4225
4226 #[test]
4227 fn test_error_recovery_with_positioned_errors() {
4228 let input = r#"Package test
4229Description: Valid
4230"#;
4231 let parsed = super::parse(input);
4232
4233 assert!(!parsed.positioned_errors.is_empty());
4235
4236 let first_error = &parsed.positioned_errors[0];
4237 assert!(!first_error.message.is_empty());
4238 assert!(first_error.range.start() <= first_error.range.end());
4239 assert!(first_error.code.is_some());
4240
4241 let error_text = &input[first_error.range.start().into()..first_error.range.end().into()];
4243 assert!(!error_text.is_empty());
4244 }
4245
4246 #[test]
4247 fn test_positioned_error_points_to_correct_token() {
4248 let input = "Package test\nDescription: Valid\n";
4249 let parsed = super::parse(input);
4250
4251 assert_eq!(parsed.positioned_errors.len(), 1);
4252
4253 let first_error = &parsed.positioned_errors[0];
4254 assert_eq!(first_error.message, "missing colon ':' after field name");
4255 assert_eq!(first_error.code.as_deref(), Some("missing_colon"));
4256
4257 let start: usize = first_error.range.start().into();
4258 let end: usize = first_error.range.end().into();
4259 assert_eq!(start, 8);
4260 assert_eq!(end, 12);
4261 assert_eq!(&input[start..end], "test");
4262 }
4263
4264 #[test]
4265 fn test_error_recovery_preserves_whitespace() {
4266 let input = r#"Source: package
4267Maintainer Test User <test@example.com>
4268Section: utils
4269
4270"#;
4271 let (deb822, errors) = super::Deb822::from_str_relaxed(input);
4272
4273 assert!(!errors.is_empty());
4275
4276 let output = deb822.to_string();
4278 assert!(output.contains("Section: utils"));
4279
4280 let paragraph = deb822.paragraphs().next().unwrap();
4282 assert_eq!(paragraph.get("Source").as_deref(), Some("package"));
4283 assert_eq!(paragraph.get("Section").as_deref(), Some("utils"));
4284 }
4285
4286 #[test]
4287 fn test_error_recovery_empty_fields() {
4288 let input = r#"Package: test
4289Description:
4290Maintainer: Valid User
4291EmptyField:
4292Version: 1.0
4293"#;
4294 let (deb822, _errors) = super::Deb822::from_str_relaxed(input);
4295
4296 let mut all_fields = std::collections::HashMap::new();
4298 for paragraph in deb822.paragraphs() {
4299 for (key, value) in paragraph.items() {
4300 all_fields.insert(key, value);
4301 }
4302 }
4303
4304 assert_eq!(all_fields.get("Package"), Some(&"test".to_string()));
4305 assert_eq!(all_fields.get("Description"), Some(&"".to_string()));
4306 assert_eq!(
4307 all_fields.get("Maintainer"),
4308 Some(&"Valid User".to_string())
4309 );
4310 assert_eq!(all_fields.get("EmptyField"), Some(&"".to_string()));
4311 assert_eq!(all_fields.get("Version"), Some(&"1.0".to_string()));
4312 }
4313
4314 #[test]
4315 fn test_insert_comment_before() {
4316 let d: super::Deb822 = vec![
4317 vec![("Source", "foo"), ("Maintainer", "Bar <bar@example.com>")]
4318 .into_iter()
4319 .collect(),
4320 vec![("Package", "foo"), ("Architecture", "all")]
4321 .into_iter()
4322 .collect(),
4323 ]
4324 .into_iter()
4325 .collect();
4326
4327 let mut p1 = d.paragraphs().next().unwrap();
4329 p1.insert_comment_before("This is the source paragraph");
4330
4331 let mut p2 = d.paragraphs().nth(1).unwrap();
4333 p2.insert_comment_before("This is the binary paragraph");
4334
4335 let output = d.to_string();
4336 assert_eq!(
4337 output,
4338 r#"# This is the source paragraph
4339Source: foo
4340Maintainer: Bar <bar@example.com>
4341
4342# This is the binary paragraph
4343Package: foo
4344Architecture: all
4345"#
4346 );
4347 }
4348
4349 #[test]
4350 fn test_parse_continuation_with_colon() {
4351 let input = "Package: test\nDescription: short\n line: with colon\n";
4353 let result = input.parse::<Deb822>();
4354 assert!(result.is_ok());
4355
4356 let deb822 = result.unwrap();
4357 let para = deb822.paragraphs().next().unwrap();
4358 assert_eq!(para.get("Package").as_deref(), Some("test"));
4359 assert_eq!(
4360 para.get("Description").as_deref(),
4361 Some("short\nline: with colon")
4362 );
4363 }
4364
4365 #[test]
4366 fn test_parse_continuation_starting_with_colon() {
4367 let input = "Package: test\nDescription: short\n :value\n";
4369 let result = input.parse::<Deb822>();
4370 assert!(result.is_ok());
4371
4372 let deb822 = result.unwrap();
4373 let para = deb822.paragraphs().next().unwrap();
4374 assert_eq!(para.get("Package").as_deref(), Some("test"));
4375 assert_eq!(para.get("Description").as_deref(), Some("short\n:value"));
4376 }
4377
4378 #[test]
4379 fn test_normalize_field_spacing_single_space() {
4380 let input = "Field: value\n";
4382 let deb822 = input.parse::<Deb822>().unwrap();
4383 let mut para = deb822.paragraphs().next().unwrap();
4384
4385 para.normalize_field_spacing();
4386 assert_eq!(para.to_string(), "Field: value\n");
4387 }
4388
4389 #[test]
4390 fn test_normalize_field_spacing_extra_spaces() {
4391 let input = "Field: value\n";
4393 let deb822 = input.parse::<Deb822>().unwrap();
4394 let mut para = deb822.paragraphs().next().unwrap();
4395
4396 para.normalize_field_spacing();
4397 assert_eq!(para.to_string(), "Field: value\n");
4398 }
4399
4400 #[test]
4401 fn test_normalize_field_spacing_no_space() {
4402 let input = "Field:value\n";
4404 let deb822 = input.parse::<Deb822>().unwrap();
4405 let mut para = deb822.paragraphs().next().unwrap();
4406
4407 para.normalize_field_spacing();
4408 assert_eq!(para.to_string(), "Field: value\n");
4409 }
4410
4411 #[test]
4412 fn test_normalize_field_spacing_multiple_fields() {
4413 let input = "Field1: value1\nField2:value2\nField3: value3\n";
4415 let deb822 = input.parse::<Deb822>().unwrap();
4416 let mut para = deb822.paragraphs().next().unwrap();
4417
4418 para.normalize_field_spacing();
4419 assert_eq!(
4420 para.to_string(),
4421 "Field1: value1\nField2: value2\nField3: value3\n"
4422 );
4423 }
4424
4425 #[test]
4426 fn test_normalize_field_spacing_multiline_value() {
4427 let input = "Description: short\n continuation line\n . \n final line\n";
4429 let deb822 = input.parse::<Deb822>().unwrap();
4430 let mut para = deb822.paragraphs().next().unwrap();
4431
4432 para.normalize_field_spacing();
4433 assert_eq!(
4434 para.to_string(),
4435 "Description: short\n continuation line\n . \n final line\n"
4436 );
4437 }
4438
4439 #[test]
4440 fn test_normalize_field_spacing_empty_value_with_whitespace() {
4441 let input = "Field: \n";
4443 let deb822 = input.parse::<Deb822>().unwrap();
4444 let mut para = deb822.paragraphs().next().unwrap();
4445
4446 para.normalize_field_spacing();
4447 assert_eq!(para.to_string(), "Field:\n");
4449 }
4450
4451 #[test]
4452 fn test_normalize_field_spacing_no_value() {
4453 let input = "Depends:\n";
4455 let deb822 = input.parse::<Deb822>().unwrap();
4456 let mut para = deb822.paragraphs().next().unwrap();
4457
4458 para.normalize_field_spacing();
4459 assert_eq!(para.to_string(), "Depends:\n");
4461 }
4462
4463 #[test]
4464 fn test_normalize_field_spacing_multiple_paragraphs() {
4465 let input = "Field1: value1\n\nField2: value2\n";
4467 let mut deb822 = input.parse::<Deb822>().unwrap();
4468
4469 deb822.normalize_field_spacing();
4470 assert_eq!(deb822.to_string(), "Field1: value1\n\nField2: value2\n");
4471 }
4472
4473 #[test]
4474 fn test_normalize_field_spacing_preserves_comments() {
4475 let input = "# Comment\nField: value\n";
4477 let mut deb822 = input.parse::<Deb822>().unwrap();
4478
4479 deb822.normalize_field_spacing();
4480 assert_eq!(deb822.to_string(), "# Comment\nField: value\n");
4481 }
4482
4483 #[test]
4484 fn test_normalize_field_spacing_preserves_values() {
4485 let input = "Source: foo-bar\nMaintainer:Foo Bar <test@example.com>\n";
4487 let deb822 = input.parse::<Deb822>().unwrap();
4488 let mut para = deb822.paragraphs().next().unwrap();
4489
4490 para.normalize_field_spacing();
4491
4492 assert_eq!(para.get("Source").as_deref(), Some("foo-bar"));
4493 assert_eq!(
4494 para.get("Maintainer").as_deref(),
4495 Some("Foo Bar <test@example.com>")
4496 );
4497 }
4498
4499 #[test]
4500 fn test_normalize_field_spacing_tab_after_colon() {
4501 let input = "Field:\tvalue\n";
4503 let deb822 = input.parse::<Deb822>().unwrap();
4504 let mut para = deb822.paragraphs().next().unwrap();
4505
4506 para.normalize_field_spacing();
4507 assert_eq!(para.to_string(), "Field: value\n");
4508 }
4509
4510 #[test]
4511 fn test_set_preserves_indentation() {
4512 let original = r#"Source: example
4514Build-Depends: foo,
4515 bar,
4516 baz
4517"#;
4518
4519 let mut para: super::Paragraph = original.parse().unwrap();
4520
4521 para.set("Build-Depends", "foo,\nbar,\nbaz");
4523
4524 let expected = r#"Source: example
4526Build-Depends: foo,
4527 bar,
4528 baz
4529"#;
4530 assert_eq!(para.to_string(), expected);
4531 }
4532
4533 #[test]
4534 fn test_set_new_field_detects_field_name_length_indent() {
4535 let original = r#"Source: example
4537Build-Depends: foo,
4538 bar,
4539 baz
4540Depends: lib1,
4541 lib2
4542"#;
4543
4544 let mut para: super::Paragraph = original.parse().unwrap();
4545
4546 para.set("Recommends", "pkg1,\npkg2,\npkg3");
4548
4549 assert!(para
4551 .to_string()
4552 .contains("Recommends: pkg1,\n pkg2,"));
4553 }
4554
4555 #[test]
4556 fn test_set_new_field_detects_fixed_indent() {
4557 let original = r#"Source: example
4559Build-Depends: foo,
4560 bar,
4561 baz
4562Depends: lib1,
4563 lib2
4564"#;
4565
4566 let mut para: super::Paragraph = original.parse().unwrap();
4567
4568 para.set("Recommends", "pkg1,\npkg2,\npkg3");
4570
4571 assert!(para
4573 .to_string()
4574 .contains("Recommends: pkg1,\n pkg2,\n pkg3\n"));
4575 }
4576
4577 #[test]
4578 fn test_set_new_field_no_multiline_fields() {
4579 let original = r#"Source: example
4581Maintainer: Test <test@example.com>
4582"#;
4583
4584 let mut para: super::Paragraph = original.parse().unwrap();
4585
4586 para.set("Depends", "foo,\nbar,\nbaz");
4588
4589 let expected = r#"Source: example
4591Maintainer: Test <test@example.com>
4592Depends: foo,
4593 bar,
4594 baz
4595"#;
4596 assert_eq!(para.to_string(), expected);
4597 }
4598
4599 #[test]
4600 fn test_set_new_field_mixed_indentation() {
4601 let original = r#"Source: example
4603Build-Depends: foo,
4604 bar
4605Depends: lib1,
4606 lib2
4607"#;
4608
4609 let mut para: super::Paragraph = original.parse().unwrap();
4610
4611 para.set("Recommends", "pkg1,\npkg2");
4613
4614 assert!(para
4616 .to_string()
4617 .contains("Recommends: pkg1,\n pkg2\n"));
4618 }
4619
4620 #[test]
4621 fn test_entry_with_indentation() {
4622 let entry = super::Entry::with_indentation("Test-Field", "value1\nvalue2\nvalue3", " ");
4624
4625 assert_eq!(
4626 entry.to_string(),
4627 "Test-Field: value1\n value2\n value3\n"
4628 );
4629 }
4630
4631 #[test]
4632 fn test_set_with_indent_pattern_fixed() {
4633 let original = r#"Source: example
4635Maintainer: Test <test@example.com>
4636"#;
4637
4638 let mut para: super::Paragraph = original.parse().unwrap();
4639
4640 para.set_with_indent_pattern(
4642 "Depends",
4643 "foo,\nbar,\nbaz",
4644 Some(&super::IndentPattern::Fixed(4)),
4645 None,
4646 );
4647
4648 let expected = r#"Source: example
4650Maintainer: Test <test@example.com>
4651Depends: foo,
4652 bar,
4653 baz
4654"#;
4655 assert_eq!(para.to_string(), expected);
4656 }
4657
4658 #[test]
4659 fn test_set_with_indent_pattern_field_name_length() {
4660 let original = r#"Source: example
4662Maintainer: Test <test@example.com>
4663"#;
4664
4665 let mut para: super::Paragraph = original.parse().unwrap();
4666
4667 para.set_with_indent_pattern(
4669 "Build-Depends",
4670 "libfoo,\nlibbar,\nlibbaz",
4671 Some(&super::IndentPattern::FieldNameLength),
4672 None,
4673 );
4674
4675 let expected = r#"Source: example
4677Maintainer: Test <test@example.com>
4678Build-Depends: libfoo,
4679 libbar,
4680 libbaz
4681"#;
4682 assert_eq!(para.to_string(), expected);
4683 }
4684
4685 #[test]
4686 fn test_set_with_indent_pattern_override_auto_detection() {
4687 let original = r#"Source: example
4689Build-Depends: foo,
4690 bar,
4691 baz
4692"#;
4693
4694 let mut para: super::Paragraph = original.parse().unwrap();
4695
4696 para.set_with_indent_pattern(
4698 "Depends",
4699 "lib1,\nlib2,\nlib3",
4700 Some(&super::IndentPattern::Fixed(2)),
4701 None,
4702 );
4703
4704 let expected = r#"Source: example
4706Build-Depends: foo,
4707 bar,
4708 baz
4709Depends: lib1,
4710 lib2,
4711 lib3
4712"#;
4713 assert_eq!(para.to_string(), expected);
4714 }
4715
4716 #[test]
4717 fn test_set_with_indent_pattern_none_auto_detects() {
4718 let original = r#"Source: example
4720Build-Depends: foo,
4721 bar,
4722 baz
4723"#;
4724
4725 let mut para: super::Paragraph = original.parse().unwrap();
4726
4727 para.set_with_indent_pattern("Depends", "lib1,\nlib2", None, None);
4729
4730 let expected = r#"Source: example
4732Build-Depends: foo,
4733 bar,
4734 baz
4735Depends: lib1,
4736 lib2
4737"#;
4738 assert_eq!(para.to_string(), expected);
4739 }
4740
4741 #[test]
4742 fn test_set_with_indent_pattern_with_field_order() {
4743 let original = r#"Source: example
4745Maintainer: Test <test@example.com>
4746"#;
4747
4748 let mut para: super::Paragraph = original.parse().unwrap();
4749
4750 para.set_with_indent_pattern(
4752 "Priority",
4753 "optional",
4754 Some(&super::IndentPattern::Fixed(4)),
4755 Some(&["Source", "Priority", "Maintainer"]),
4756 );
4757
4758 let expected = r#"Source: example
4760Priority: optional
4761Maintainer: Test <test@example.com>
4762"#;
4763 assert_eq!(para.to_string(), expected);
4764 }
4765
4766 #[test]
4767 fn test_set_with_indent_pattern_replace_existing() {
4768 let original = r#"Source: example
4770Depends: foo,
4771 bar
4772"#;
4773
4774 let mut para: super::Paragraph = original.parse().unwrap();
4775
4776 para.set_with_indent_pattern(
4778 "Depends",
4779 "lib1,\nlib2,\nlib3",
4780 Some(&super::IndentPattern::Fixed(3)),
4781 None,
4782 );
4783
4784 let expected = r#"Source: example
4786Depends: lib1,
4787 lib2,
4788 lib3
4789"#;
4790 assert_eq!(para.to_string(), expected);
4791 }
4792
4793 #[test]
4794 fn test_change_field_indent() {
4795 let original = r#"Source: example
4797Depends: foo,
4798 bar,
4799 baz
4800"#;
4801 let mut para: super::Paragraph = original.parse().unwrap();
4802
4803 let result = para
4805 .change_field_indent("Depends", &super::IndentPattern::Fixed(2))
4806 .unwrap();
4807 assert!(result, "Field should have been found and updated");
4808
4809 let expected = r#"Source: example
4810Depends: foo,
4811 bar,
4812 baz
4813"#;
4814 assert_eq!(para.to_string(), expected);
4815 }
4816
4817 #[test]
4818 fn test_change_field_indent_nonexistent() {
4819 let original = r#"Source: example
4821"#;
4822 let mut para: super::Paragraph = original.parse().unwrap();
4823
4824 let result = para
4826 .change_field_indent("Depends", &super::IndentPattern::Fixed(2))
4827 .unwrap();
4828 assert!(!result, "Should return false for non-existent field");
4829
4830 assert_eq!(para.to_string(), original);
4832 }
4833
4834 #[test]
4835 fn test_change_field_indent_case_insensitive() {
4836 let original = r#"Build-Depends: foo,
4838 bar
4839"#;
4840 let mut para: super::Paragraph = original.parse().unwrap();
4841
4842 let result = para
4844 .change_field_indent("build-depends", &super::IndentPattern::Fixed(1))
4845 .unwrap();
4846 assert!(result, "Should find field case-insensitively");
4847
4848 let expected = r#"Build-Depends: foo,
4849 bar
4850"#;
4851 assert_eq!(para.to_string(), expected);
4852 }
4853
4854 #[test]
4855 fn test_entry_get_indent() {
4856 let original = r#"Build-Depends: foo,
4858 bar,
4859 baz
4860"#;
4861 let para: super::Paragraph = original.parse().unwrap();
4862 let entry = para.entries().next().unwrap();
4863
4864 assert_eq!(entry.get_indent(), Some(" ".to_string()));
4865 }
4866
4867 #[test]
4868 fn test_entry_get_indent_single_line() {
4869 let original = r#"Source: example
4871"#;
4872 let para: super::Paragraph = original.parse().unwrap();
4873 let entry = para.entries().next().unwrap();
4874
4875 assert_eq!(entry.get_indent(), None);
4876 }
4877}
4878
4879#[test]
4880fn test_move_paragraph_forward() {
4881 let mut d: Deb822 = vec![
4882 vec![("Foo", "Bar"), ("Baz", "Qux")].into_iter().collect(),
4883 vec![("A", "B"), ("C", "D")].into_iter().collect(),
4884 vec![("X", "Y"), ("Z", "W")].into_iter().collect(),
4885 ]
4886 .into_iter()
4887 .collect();
4888 d.move_paragraph(0, 2);
4889 assert_eq!(
4890 d.to_string(),
4891 "A: B\nC: D\n\nX: Y\nZ: W\n\nFoo: Bar\nBaz: Qux\n"
4892 );
4893}
4894
4895#[test]
4896fn test_move_paragraph_backward() {
4897 let mut d: Deb822 = vec![
4898 vec![("Foo", "Bar"), ("Baz", "Qux")].into_iter().collect(),
4899 vec![("A", "B"), ("C", "D")].into_iter().collect(),
4900 vec![("X", "Y"), ("Z", "W")].into_iter().collect(),
4901 ]
4902 .into_iter()
4903 .collect();
4904 d.move_paragraph(2, 0);
4905 assert_eq!(
4906 d.to_string(),
4907 "X: Y\nZ: W\n\nFoo: Bar\nBaz: Qux\n\nA: B\nC: D\n"
4908 );
4909}
4910
4911#[test]
4912fn test_move_paragraph_middle() {
4913 let mut d: Deb822 = vec![
4914 vec![("Foo", "Bar"), ("Baz", "Qux")].into_iter().collect(),
4915 vec![("A", "B"), ("C", "D")].into_iter().collect(),
4916 vec![("X", "Y"), ("Z", "W")].into_iter().collect(),
4917 ]
4918 .into_iter()
4919 .collect();
4920 d.move_paragraph(2, 1);
4921 assert_eq!(
4922 d.to_string(),
4923 "Foo: Bar\nBaz: Qux\n\nX: Y\nZ: W\n\nA: B\nC: D\n"
4924 );
4925}
4926
4927#[test]
4928fn test_move_paragraph_same_index() {
4929 let mut d: Deb822 = vec![
4930 vec![("Foo", "Bar"), ("Baz", "Qux")].into_iter().collect(),
4931 vec![("A", "B"), ("C", "D")].into_iter().collect(),
4932 ]
4933 .into_iter()
4934 .collect();
4935 let original = d.to_string();
4936 d.move_paragraph(1, 1);
4937 assert_eq!(d.to_string(), original);
4938}
4939
4940#[test]
4941fn test_move_paragraph_single() {
4942 let mut d: Deb822 = vec![vec![("Foo", "Bar")].into_iter().collect()]
4943 .into_iter()
4944 .collect();
4945 let original = d.to_string();
4946 d.move_paragraph(0, 0);
4947 assert_eq!(d.to_string(), original);
4948}
4949
4950#[test]
4951fn test_move_paragraph_invalid_index() {
4952 let mut d: Deb822 = vec![
4953 vec![("Foo", "Bar")].into_iter().collect(),
4954 vec![("A", "B")].into_iter().collect(),
4955 ]
4956 .into_iter()
4957 .collect();
4958 let original = d.to_string();
4959 d.move_paragraph(0, 5);
4960 assert_eq!(d.to_string(), original);
4961}
4962
4963#[test]
4964fn test_move_paragraph_with_comments() {
4965 let text = r#"Foo: Bar
4966
4967# This is a comment
4968
4969A: B
4970
4971X: Y
4972"#;
4973 let mut d: Deb822 = text.parse().unwrap();
4974 d.move_paragraph(0, 2);
4975 assert_eq!(
4976 d.to_string(),
4977 "# This is a comment\n\nA: B\n\nX: Y\n\nFoo: Bar\n"
4978 );
4979}
4980
4981#[test]
4982fn test_case_insensitive_get() {
4983 let text = "Package: test\nVersion: 1.0\n";
4984 let d: Deb822 = text.parse().unwrap();
4985 let p = d.paragraphs().next().unwrap();
4986
4987 assert_eq!(p.get("Package").as_deref(), Some("test"));
4989 assert_eq!(p.get("package").as_deref(), Some("test"));
4990 assert_eq!(p.get("PACKAGE").as_deref(), Some("test"));
4991 assert_eq!(p.get("PaCkAgE").as_deref(), Some("test"));
4992
4993 assert_eq!(p.get("Version").as_deref(), Some("1.0"));
4994 assert_eq!(p.get("version").as_deref(), Some("1.0"));
4995 assert_eq!(p.get("VERSION").as_deref(), Some("1.0"));
4996}
4997
4998#[test]
4999fn test_case_insensitive_set() {
5000 let text = "Package: test\n";
5001 let d: Deb822 = text.parse().unwrap();
5002 let mut p = d.paragraphs().next().unwrap();
5003
5004 p.set("package", "updated");
5006 assert_eq!(p.get("Package").as_deref(), Some("updated"));
5007 assert_eq!(p.get("package").as_deref(), Some("updated"));
5008
5009 p.set("PACKAGE", "updated2");
5011 assert_eq!(p.get("Package").as_deref(), Some("updated2"));
5012
5013 assert_eq!(p.keys().count(), 1);
5015}
5016
5017#[test]
5018fn test_case_insensitive_remove() {
5019 let text = "Package: test\nVersion: 1.0\n";
5020 let d: Deb822 = text.parse().unwrap();
5021 let mut p = d.paragraphs().next().unwrap();
5022
5023 p.remove("package");
5025 assert_eq!(p.get("Package"), None);
5026 assert_eq!(p.get("Version").as_deref(), Some("1.0"));
5027
5028 p.remove("VERSION");
5030 assert_eq!(p.get("Version"), None);
5031
5032 assert_eq!(p.keys().count(), 0);
5034}
5035
5036#[test]
5037fn test_case_preservation() {
5038 let text = "Package: test\n";
5039 let d: Deb822 = text.parse().unwrap();
5040 let mut p = d.paragraphs().next().unwrap();
5041
5042 let original_text = d.to_string();
5044 assert_eq!(original_text, "Package: test\n");
5045
5046 p.set("package", "updated");
5048
5049 let updated_text = d.to_string();
5051 assert_eq!(updated_text, "Package: updated\n");
5052}
5053
5054#[test]
5055fn test_case_insensitive_contains_key() {
5056 let text = "Package: test\n";
5057 let d: Deb822 = text.parse().unwrap();
5058 let p = d.paragraphs().next().unwrap();
5059
5060 assert!(p.contains_key("Package"));
5061 assert!(p.contains_key("package"));
5062 assert!(p.contains_key("PACKAGE"));
5063 assert!(!p.contains_key("NonExistent"));
5064}
5065
5066#[test]
5067fn test_case_insensitive_get_all() {
5068 let text = "Package: test1\npackage: test2\n";
5069 let d: Deb822 = text.parse().unwrap();
5070 let p = d.paragraphs().next().unwrap();
5071
5072 let values: Vec<String> = p.get_all("PACKAGE").collect();
5073 assert_eq!(values, vec!["test1", "test2"]);
5074}
5075
5076#[test]
5077fn test_case_insensitive_rename() {
5078 let text = "Package: test\n";
5079 let d: Deb822 = text.parse().unwrap();
5080 let mut p = d.paragraphs().next().unwrap();
5081
5082 assert!(p.rename("package", "NewName"));
5084 assert_eq!(p.get("NewName").as_deref(), Some("test"));
5085 assert_eq!(p.get("Package"), None);
5086}
5087
5088#[test]
5089fn test_rename_changes_case() {
5090 let text = "Package: test\n";
5091 let d: Deb822 = text.parse().unwrap();
5092 let mut p = d.paragraphs().next().unwrap();
5093
5094 assert!(p.rename("package", "PACKAGE"));
5096
5097 let updated_text = d.to_string();
5099 assert_eq!(updated_text, "PACKAGE: test\n");
5100
5101 assert_eq!(p.get("package").as_deref(), Some("test"));
5103 assert_eq!(p.get("Package").as_deref(), Some("test"));
5104 assert_eq!(p.get("PACKAGE").as_deref(), Some("test"));
5105}
5106
5107#[test]
5108fn test_rename_preserves_indentation_and_whitespace() {
5109 let text =
5112 "Comments: Exceptions\n 1997-1999, 2003 MIT\n License terms\n";
5113 let d: Deb822 = text.parse().unwrap();
5114 let mut p = d.paragraphs().next().unwrap();
5115
5116 assert!(p.rename("Comments", "Comment"));
5117 assert_eq!(
5118 d.to_string(),
5119 "Comment: Exceptions\n 1997-1999, 2003 MIT\n License terms\n"
5120 );
5121}
5122
5123#[test]
5124fn test_rename_in_multi_field_paragraph() {
5125 let text = "Files: *\nCopyright: 2017 Foo\nLicense: GPL-2+\nComments: Exceptions\n There are many files in the .rpm archives.\n 1997-1999, 2003 MIT\n";
5128 let d: Deb822 = text.parse().unwrap();
5129 let mut p = d.paragraphs().next().unwrap();
5130
5131 assert!(p.rename("Comments", "Comment"));
5132 assert_eq!(d.to_string(), text.replace("Comments:", "Comment:"));
5133}
5134
5135#[test]
5136fn test_rename_preserves_post_colon_whitespace() {
5137 let text = "Files: install_GUI.sh\n";
5139 let d: Deb822 = text.parse().unwrap();
5140 let mut p = d.paragraphs().next().unwrap();
5141
5142 assert!(p.rename("Files", "File"));
5143 assert_eq!(d.to_string(), "File: install_GUI.sh\n");
5144}
5145
5146#[test]
5147fn test_reject_whitespace_only_continuation_line() {
5148 let text = "Build-Depends:\n \ndebhelper\n";
5154 let parsed = Deb822::parse(text);
5155
5156 assert!(
5159 !parsed.errors().is_empty(),
5160 "Expected parse errors for whitespace-only continuation line"
5161 );
5162}
5163
5164#[test]
5165fn test_reject_empty_continuation_line_in_multiline_field() {
5166 let text = "Depends: foo,\n bar,\n \n baz\n";
5168 let parsed = Deb822::parse(text);
5169
5170 assert!(
5172 !parsed.errors().is_empty(),
5173 "Empty continuation line should generate parse errors"
5174 );
5175
5176 let has_empty_line_error = parsed
5178 .errors()
5179 .iter()
5180 .any(|e| e.contains("empty continuation line"));
5181 assert!(
5182 has_empty_line_error,
5183 "Should have an error about empty continuation line"
5184 );
5185}
5186
5187#[test]
5188#[should_panic(expected = "empty continuation line")]
5189fn test_set_rejects_empty_continuation_lines() {
5190 let text = "Package: test\n";
5192 let deb822 = text.parse::<Deb822>().unwrap();
5193 let mut para = deb822.paragraphs().next().unwrap();
5194
5195 let value_with_empty_line = "foo\n \nbar";
5198 para.set("Depends", value_with_empty_line);
5199}
5200
5201#[test]
5202fn test_try_set_returns_error_for_empty_continuation_lines() {
5203 let text = "Package: test\n";
5205 let deb822 = text.parse::<Deb822>().unwrap();
5206 let mut para = deb822.paragraphs().next().unwrap();
5207
5208 let value_with_empty_line = "foo\n \nbar";
5210 let result = para.try_set("Depends", value_with_empty_line);
5211
5212 assert!(
5214 result.is_err(),
5215 "try_set() should return an error for empty continuation lines"
5216 );
5217
5218 match result {
5220 Err(Error::InvalidValue(msg)) => {
5221 assert!(
5222 msg.contains("empty continuation line"),
5223 "Error message should mention empty continuation line"
5224 );
5225 }
5226 _ => panic!("Expected InvalidValue error"),
5227 }
5228}
5229
5230#[test]
5231fn test_try_set_with_indent_pattern_returns_error() {
5232 let text = "Package: test\n";
5234 let deb822 = text.parse::<Deb822>().unwrap();
5235 let mut para = deb822.paragraphs().next().unwrap();
5236
5237 let value_with_empty_line = "foo\n \nbar";
5238 let result = para.try_set_with_indent_pattern(
5239 "Depends",
5240 value_with_empty_line,
5241 Some(&IndentPattern::Fixed(2)),
5242 None,
5243 );
5244
5245 assert!(
5246 result.is_err(),
5247 "try_set_with_indent_pattern() should return an error"
5248 );
5249}
5250
5251#[test]
5252fn test_try_set_succeeds_for_valid_value() {
5253 let text = "Package: test\n";
5255 let deb822 = text.parse::<Deb822>().unwrap();
5256 let mut para = deb822.paragraphs().next().unwrap();
5257
5258 let valid_value = "foo\nbar";
5260 let result = para.try_set("Depends", valid_value);
5261
5262 assert!(result.is_ok(), "try_set() should succeed for valid values");
5263 assert_eq!(para.get("Depends").as_deref(), Some("foo\nbar"));
5264}
5265
5266#[test]
5267fn test_field_with_empty_first_line() {
5268 let text = "Foo:\n blah\n blah\n";
5271 let parsed = Deb822::parse(text);
5272
5273 assert!(
5275 parsed.errors().is_empty(),
5276 "Empty first line should be valid. Got errors: {:?}",
5277 parsed.errors()
5278 );
5279
5280 let deb822 = parsed.tree();
5281 let para = deb822.paragraphs().next().unwrap();
5282 assert_eq!(para.get("Foo").as_deref(), Some("blah\nblah"));
5283}
5284
5285#[test]
5286fn test_try_set_with_empty_first_line() {
5287 let text = "Package: test\n";
5289 let deb822 = text.parse::<Deb822>().unwrap();
5290 let mut para = deb822.paragraphs().next().unwrap();
5291
5292 let value = "\nblah\nmore";
5294 let result = para.try_set("Depends", value);
5295
5296 assert!(
5297 result.is_ok(),
5298 "try_set() should succeed for values with empty first line. Got: {:?}",
5299 result
5300 );
5301}
5302
5303#[test]
5304fn test_field_with_value_then_empty_continuation() {
5305 let text = "Foo: bar\n \n";
5307 let parsed = Deb822::parse(text);
5308
5309 assert!(
5311 !parsed.errors().is_empty(),
5312 "Field with value then empty continuation line should be rejected"
5313 );
5314
5315 let has_empty_line_error = parsed
5317 .errors()
5318 .iter()
5319 .any(|e| e.contains("empty continuation line"));
5320 assert!(
5321 has_empty_line_error,
5322 "Should have error about empty continuation line"
5323 );
5324}
5325
5326#[test]
5327fn test_substvar_continuation_line() {
5328 let text = "\
5329Package: python3-cryptography
5330Architecture: any
5331Depends: python3-bcrypt,
5332 ${misc:Depends},
5333 ${python3:Depends},
5334 ${shlibs:Depends},
5335Suggests: python-cryptography-doc,
5336 python3-cryptography-vectors,
5337Description: Python library exposing cryptographic recipes and primitives
5338 The cryptography library is designed to be a \"one-stop-shop\" for
5339 all your cryptographic needs in Python.
5340 .
5341 As an alternative to the libraries that came before it, cryptography
5342 tries to address some of the issues with those libraries:
5343 - Lack of PyPy and Python 3 support.
5344 - Lack of maintenance.
5345 - Use of poor implementations of algorithms (i.e. ones with known
5346 side-channel attacks).
5347 - Lack of high level, \"Cryptography for humans\", APIs.
5348 - Absence of algorithms such as AES-GCM.
5349 - Poor introspectability, and thus poor testability.
5350 - Extremely error prone APIs, and bad defaults.
5351";
5352 let parsed = Deb822::parse(text);
5353 for e in parsed.positioned_errors() {
5354 eprintln!("error at {:?}: {}", e.range, e.message);
5355 }
5356 assert!(
5357 parsed.errors().is_empty(),
5358 "Should not produce errors: {:?}",
5359 parsed.errors()
5360 );
5361 assert!(
5362 parsed.positioned_errors().is_empty(),
5363 "Should not produce positioned errors: {:?}",
5364 parsed.positioned_errors()
5365 );
5366}
5367
5368#[test]
5369fn test_line_col() {
5370 let text = r#"Source: foo
5371Maintainer: Foo Bar <jelmer@jelmer.uk>
5372Section: net
5373
5374Package: foo
5375Architecture: all
5376Depends: libc6
5377Description: This is a description
5378 With details
5379"#;
5380 let deb822 = text.parse::<Deb822>().unwrap();
5381
5382 let paras: Vec<_> = deb822.paragraphs().collect();
5384 assert_eq!(paras.len(), 2);
5385
5386 assert_eq!(paras[0].line(), 0);
5388 assert_eq!(paras[0].column(), 0);
5389
5390 assert_eq!(paras[1].line(), 4);
5392 assert_eq!(paras[1].column(), 0);
5393
5394 let entries: Vec<_> = paras[0].entries().collect();
5396 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));
5406 assert_eq!(entries[0].line_col(), (0, 0));
5407
5408 let second_para_entries: Vec<_> = paras[1].entries().collect();
5410 assert_eq!(second_para_entries[3].line(), 7); }
5412
5413#[test]
5414fn test_deb822_snapshot_independence() {
5415 let text = r#"Source: foo
5416Maintainer: Joe <joe@example.com>
5417
5418Package: foo
5419Architecture: all
5420"#;
5421 let deb822 = text.parse::<Deb822>().unwrap();
5422 let snap = deb822.snapshot();
5423 assert!(deb822.tree_eq(&snap));
5424
5425 let mut para = deb822.paragraphs().next().unwrap();
5426 para.set("Source", "modified");
5427
5428 let snap_para = snap.paragraphs().next().unwrap();
5430 assert_eq!(snap_para.get("Source").as_deref(), Some("foo"));
5431 assert!(!deb822.tree_eq(&snap));
5433}
5434
5435#[test]
5436fn test_paragraph_snapshot_independence() {
5437 let text = "Package: foo\nArchitecture: all\n";
5438 let deb822 = text.parse::<Deb822>().unwrap();
5439 let mut para = deb822.paragraphs().next().unwrap();
5440 let snap = para.snapshot();
5441 assert!(para.tree_eq(&snap));
5442
5443 para.set("Package", "modified");
5444 assert_eq!(snap.get("Package").as_deref(), Some("foo"));
5445 assert!(!para.tree_eq(&snap));
5446}
5447
5448#[test]
5449fn test_tree_eq_value_equivalence() {
5450 let text = "Package: foo\nArchitecture: all\n";
5453 let a = text.parse::<Deb822>().unwrap();
5454 let b = text.parse::<Deb822>().unwrap();
5455 assert!(a.tree_eq(&b));
5456 assert!(b.tree_eq(&a));
5457
5458 let c: Deb822 = "Package: bar\n".parse().unwrap();
5460 assert!(!a.tree_eq(&c));
5461}
5462
5463#[test]
5464fn test_entry_snapshot_independence() {
5465 let text = "Package: foo\n";
5466 let deb822 = text.parse::<Deb822>().unwrap();
5467 let mut para = deb822.paragraphs().next().unwrap();
5468 let entry = para.entries().next().unwrap();
5469 let snap = entry.snapshot();
5470 assert!(entry.tree_eq(&snap));
5471
5472 para.set("Package", "modified");
5473 assert_eq!(snap.value(), "foo");
5475}
5476
5477#[test]
5478fn test_paragraph_text_range() {
5479 let text = r#"Source: foo
5481Maintainer: Joe <joe@example.com>
5482
5483Package: foo
5484Architecture: all
5485"#;
5486 let deb822 = text.parse::<Deb822>().unwrap();
5487 let paras: Vec<_> = deb822.paragraphs().collect();
5488
5489 let range1 = paras[0].text_range();
5491 let para1_text = &text[range1.start().into()..range1.end().into()];
5492 assert_eq!(
5493 para1_text,
5494 "Source: foo\nMaintainer: Joe <joe@example.com>\n"
5495 );
5496
5497 let range2 = paras[1].text_range();
5499 let para2_text = &text[range2.start().into()..range2.end().into()];
5500 assert_eq!(para2_text, "Package: foo\nArchitecture: all\n");
5501}
5502
5503#[test]
5504fn test_paragraphs_in_range_single() {
5505 let text = r#"Source: foo
5507
5508Package: bar
5509
5510Package: baz
5511"#;
5512 let deb822 = text.parse::<Deb822>().unwrap();
5513
5514 let first_para = deb822.paragraphs().next().unwrap();
5516 let range = first_para.text_range();
5517
5518 let paras: Vec<_> = deb822.paragraphs_in_range(range).collect();
5520 assert_eq!(paras.len(), 1);
5521 assert_eq!(paras[0].get("Source").as_deref(), Some("foo"));
5522}
5523
5524#[test]
5525fn test_paragraphs_in_range_multiple() {
5526 let text = r#"Source: foo
5528
5529Package: bar
5530
5531Package: baz
5532"#;
5533 let deb822 = text.parse::<Deb822>().unwrap();
5534
5535 let range = rowan::TextRange::new(0.into(), 25.into());
5537
5538 let paras: Vec<_> = deb822.paragraphs_in_range(range).collect();
5540 assert_eq!(paras.len(), 2);
5541 assert_eq!(paras[0].get("Source").as_deref(), Some("foo"));
5542 assert_eq!(paras[1].get("Package").as_deref(), Some("bar"));
5543}
5544
5545#[test]
5546fn test_paragraphs_in_range_partial_overlap() {
5547 let text = r#"Source: foo
5549
5550Package: bar
5551
5552Package: baz
5553"#;
5554 let deb822 = text.parse::<Deb822>().unwrap();
5555
5556 let range = rowan::TextRange::new(15.into(), 30.into());
5558
5559 let paras: Vec<_> = deb822.paragraphs_in_range(range).collect();
5561 assert!(paras.len() >= 1);
5562 assert!(paras
5563 .iter()
5564 .any(|p| p.get("Package").as_deref() == Some("bar")));
5565}
5566
5567#[test]
5568fn test_paragraphs_in_range_no_match() {
5569 let text = r#"Source: foo
5571
5572Package: bar
5573"#;
5574 let deb822 = text.parse::<Deb822>().unwrap();
5575
5576 let range = rowan::TextRange::new(1000.into(), 2000.into());
5578
5579 let paras: Vec<_> = deb822.paragraphs_in_range(range).collect();
5581 assert_eq!(paras.len(), 0);
5582}
5583
5584#[test]
5585fn test_paragraphs_in_range_all() {
5586 let text = r#"Source: foo
5588
5589Package: bar
5590
5591Package: baz
5592"#;
5593 let deb822 = text.parse::<Deb822>().unwrap();
5594
5595 let range = rowan::TextRange::new(0.into(), text.len().try_into().unwrap());
5597
5598 let paras: Vec<_> = deb822.paragraphs_in_range(range).collect();
5600 assert_eq!(paras.len(), 3);
5601}
5602
5603#[test]
5604fn test_paragraph_at_position() {
5605 let text = r#"Package: foo
5607Version: 1.0
5608
5609Package: bar
5610Architecture: all
5611"#;
5612 let deb822 = text.parse::<Deb822>().unwrap();
5613
5614 let para = deb822.paragraph_at_position(rowan::TextSize::from(5));
5616 assert!(para.is_some());
5617 assert_eq!(para.unwrap().get("Package").as_deref(), Some("foo"));
5618
5619 let para = deb822.paragraph_at_position(rowan::TextSize::from(30));
5621 assert!(para.is_some());
5622 assert_eq!(para.unwrap().get("Package").as_deref(), Some("bar"));
5623
5624 let para = deb822.paragraph_at_position(rowan::TextSize::from(1000));
5626 assert!(para.is_none());
5627}
5628
5629#[test]
5630fn test_paragraph_at_line() {
5631 let text = r#"Package: foo
5633Version: 1.0
5634
5635Package: bar
5636Architecture: all
5637"#;
5638 let deb822 = text.parse::<Deb822>().unwrap();
5639
5640 let para = deb822.paragraph_at_line(0);
5642 assert!(para.is_some());
5643 assert_eq!(para.unwrap().get("Package").as_deref(), Some("foo"));
5644
5645 let para = deb822.paragraph_at_line(1);
5647 assert!(para.is_some());
5648 assert_eq!(para.unwrap().get("Package").as_deref(), Some("foo"));
5649
5650 let para = deb822.paragraph_at_line(3);
5652 assert!(para.is_some());
5653 assert_eq!(para.unwrap().get("Package").as_deref(), Some("bar"));
5654
5655 let para = deb822.paragraph_at_line(100);
5657 assert!(para.is_none());
5658}
5659
5660#[test]
5661fn test_entry_at_line_col() {
5662 let text = r#"Package: foo
5664Version: 1.0
5665Architecture: all
5666"#;
5667 let deb822 = text.parse::<Deb822>().unwrap();
5668
5669 let entry = deb822.entry_at_line_col(0, 0);
5671 assert!(entry.is_some());
5672 assert_eq!(entry.unwrap().key(), Some("Package".to_string()));
5673
5674 let entry = deb822.entry_at_line_col(1, 0);
5676 assert!(entry.is_some());
5677 assert_eq!(entry.unwrap().key(), Some("Version".to_string()));
5678
5679 let entry = deb822.entry_at_line_col(2, 5);
5681 assert!(entry.is_some());
5682 assert_eq!(entry.unwrap().key(), Some("Architecture".to_string()));
5683
5684 let entry = deb822.entry_at_line_col(100, 0);
5686 assert!(entry.is_none());
5687}
5688
5689#[test]
5690fn test_entry_at_line_col_multiline() {
5691 let text = r#"Package: foo
5693Description: A package
5694 with a long
5695 description
5696Version: 1.0
5697"#;
5698 let deb822 = text.parse::<Deb822>().unwrap();
5699
5700 let entry = deb822.entry_at_line_col(1, 0);
5702 assert!(entry.is_some());
5703 assert_eq!(entry.unwrap().key(), Some("Description".to_string()));
5704
5705 let entry = deb822.entry_at_line_col(2, 1);
5707 assert!(entry.is_some());
5708 assert_eq!(entry.unwrap().key(), Some("Description".to_string()));
5709
5710 let entry = deb822.entry_at_line_col(3, 1);
5712 assert!(entry.is_some());
5713 assert_eq!(entry.unwrap().key(), Some("Description".to_string()));
5714
5715 let entry = deb822.entry_at_line_col(4, 0);
5717 assert!(entry.is_some());
5718 assert_eq!(entry.unwrap().key(), Some("Version".to_string()));
5719}
5720
5721#[test]
5722fn test_entries_in_range() {
5723 let text = r#"Package: foo
5725Version: 1.0
5726Architecture: all
5727"#;
5728 let deb822 = text.parse::<Deb822>().unwrap();
5729 let para = deb822.paragraphs().next().unwrap();
5730
5731 let first_entry = para.entries().next().unwrap();
5733 let range = first_entry.text_range();
5734
5735 let entries: Vec<_> = para.entries_in_range(range).collect();
5737 assert_eq!(entries.len(), 1);
5738 assert_eq!(entries[0].key(), Some("Package".to_string()));
5739
5740 let range = rowan::TextRange::new(0.into(), 25.into());
5742 let entries: Vec<_> = para.entries_in_range(range).collect();
5743 assert_eq!(entries.len(), 2);
5744 assert_eq!(entries[0].key(), Some("Package".to_string()));
5745 assert_eq!(entries[1].key(), Some("Version".to_string()));
5746}
5747
5748#[test]
5749fn test_entries_in_range_partial_overlap() {
5750 let text = r#"Package: foo
5752Version: 1.0
5753Architecture: all
5754"#;
5755 let deb822 = text.parse::<Deb822>().unwrap();
5756 let para = deb822.paragraphs().next().unwrap();
5757
5758 let range = rowan::TextRange::new(15.into(), 30.into());
5760
5761 let entries: Vec<_> = para.entries_in_range(range).collect();
5762 assert!(entries.len() >= 1);
5763 assert!(entries
5764 .iter()
5765 .any(|e| e.key() == Some("Version".to_string())));
5766}
5767
5768#[test]
5769fn test_entries_in_range_no_match() {
5770 let text = "Package: foo\n";
5772 let deb822 = text.parse::<Deb822>().unwrap();
5773 let para = deb822.paragraphs().next().unwrap();
5774
5775 let range = rowan::TextRange::new(1000.into(), 2000.into());
5777 let entries: Vec<_> = para.entries_in_range(range).collect();
5778 assert_eq!(entries.len(), 0);
5779}
5780
5781#[test]
5782fn test_entry_at_position() {
5783 let text = r#"Package: foo
5785Version: 1.0
5786Architecture: all
5787"#;
5788 let deb822 = text.parse::<Deb822>().unwrap();
5789 let para = deb822.paragraphs().next().unwrap();
5790
5791 let entry = para.entry_at_position(rowan::TextSize::from(5));
5793 assert!(entry.is_some());
5794 assert_eq!(entry.unwrap().key(), Some("Package".to_string()));
5795
5796 let entry = para.entry_at_position(rowan::TextSize::from(15));
5798 assert!(entry.is_some());
5799 assert_eq!(entry.unwrap().key(), Some("Version".to_string()));
5800
5801 let entry = para.entry_at_position(rowan::TextSize::from(1000));
5803 assert!(entry.is_none());
5804}
5805
5806#[test]
5807fn test_entry_at_position_multiline() {
5808 let text = r#"Description: A package
5810 with a long
5811 description
5812"#;
5813 let deb822 = text.parse::<Deb822>().unwrap();
5814 let para = deb822.paragraphs().next().unwrap();
5815
5816 let entry = para.entry_at_position(rowan::TextSize::from(5));
5818 assert!(entry.is_some());
5819 assert_eq!(entry.unwrap().key(), Some("Description".to_string()));
5820
5821 let entry = para.entry_at_position(rowan::TextSize::from(30));
5823 assert!(entry.is_some());
5824 assert_eq!(entry.unwrap().key(), Some("Description".to_string()));
5825}
5826
5827#[test]
5828fn test_paragraph_at_position_at_boundary() {
5829 let text = "Package: foo\n\nPackage: bar\n";
5831 let deb822 = text.parse::<Deb822>().unwrap();
5832
5833 let para = deb822.paragraph_at_position(rowan::TextSize::from(0));
5835 assert!(para.is_some());
5836 assert_eq!(para.unwrap().get("Package").as_deref(), Some("foo"));
5837
5838 let para = deb822.paragraph_at_position(rowan::TextSize::from(15));
5840 assert!(para.is_some());
5841 assert_eq!(para.unwrap().get("Package").as_deref(), Some("bar"));
5842}
5843
5844#[test]
5845fn test_comment_in_multiline_value() {
5846 let text = "\
5849Build-Depends: dh-python,
5850 libsvn-dev,
5851# python-all-dbg (>= 2.6.6-3),
5852 python3-all-dev,
5853# python3-all-dbg,
5854 python3-docutils
5855Standards-Version: 4.7.0
5856";
5857 let deb822 = text.parse::<Deb822>().unwrap();
5858 let para = deb822.paragraphs().next().unwrap();
5859 assert_eq!(
5861 para.get("Build-Depends").as_deref(),
5862 Some("dh-python,\nlibsvn-dev,\npython3-all-dev,\npython3-docutils")
5863 );
5864 assert_eq!(
5866 para.get_with_comments("Build-Depends").as_deref(),
5867 Some("dh-python,\nlibsvn-dev,\n# python-all-dbg (>= 2.6.6-3),\npython3-all-dev,\n# python3-all-dbg,\npython3-docutils")
5868 );
5869 assert_eq!(para.get("Standards-Version").as_deref(), Some("4.7.0"));
5870 assert_eq!(deb822.to_string(), text);
5872}