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 new(key: &str, value: &str) -> Entry {
2523 Self::with_indentation(key, value, " ")
2524 }
2525
2526 pub fn with_indentation(key: &str, value: &str, indent: &str) -> Entry {
2533 Entry::with_formatting(key, value, " ", indent)
2534 }
2535
2536 pub fn try_with_formatting(
2547 key: &str,
2548 value: &str,
2549 post_colon_ws: &str,
2550 indent: &str,
2551 ) -> Result<Entry, Error> {
2552 let mut builder = GreenNodeBuilder::new();
2553
2554 builder.start_node(ENTRY.into());
2555 builder.token(KEY.into(), key);
2556 builder.token(COLON.into(), ":");
2557
2558 let mut i = 0;
2560 while i < post_colon_ws.len() {
2561 if post_colon_ws[i..].starts_with('\n') {
2562 builder.token(NEWLINE.into(), "\n");
2563 i += 1;
2564 } else {
2565 let start = i;
2567 while i < post_colon_ws.len() && !post_colon_ws[i..].starts_with('\n') {
2568 i += post_colon_ws[i..].chars().next().unwrap().len_utf8();
2569 }
2570 builder.token(WHITESPACE.into(), &post_colon_ws[start..i]);
2571 }
2572 }
2573
2574 for (line_idx, line) in value.split('\n').enumerate() {
2575 if line_idx > 0 {
2576 if line.trim().is_empty() {
2579 return Err(Error::InvalidValue(format!(
2580 "empty continuation line (line with only whitespace) at line {}",
2581 line_idx + 1
2582 )));
2583 }
2584 builder.token(INDENT.into(), indent);
2585 }
2586 builder.token(VALUE.into(), line);
2587 builder.token(NEWLINE.into(), "\n");
2588 }
2589 builder.finish_node();
2590 Ok(Entry(SyntaxNode::new_root_mut(builder.finish())))
2591 }
2592
2593 pub fn with_formatting(key: &str, value: &str, post_colon_ws: &str, indent: &str) -> Entry {
2604 Self::try_with_formatting(key, value, post_colon_ws, indent)
2605 .expect("Invalid value: empty continuation line")
2606 }
2607
2608 #[must_use]
2609 pub fn wrap_and_sort(
2622 &self,
2623 mut indentation: Indentation,
2624 immediate_empty_line: bool,
2625 max_line_length_one_liner: Option<usize>,
2626 format_value: Option<&dyn Fn(&str, &str) -> String>,
2627 ) -> Entry {
2628 let mut builder = GreenNodeBuilder::new();
2629
2630 let mut content = vec![];
2631 builder.start_node(ENTRY.into());
2632 for c in self.0.children_with_tokens() {
2633 let text = c.as_token().map(|t| t.text());
2634 match c.kind() {
2635 KEY => {
2636 builder.token(KEY.into(), text.unwrap());
2637 if indentation == Indentation::FieldNameLength {
2638 indentation = Indentation::Spaces(text.unwrap().len() as u32);
2639 }
2640 }
2641 COLON => {
2642 builder.token(COLON.into(), ":");
2643 }
2644 INDENT => {
2645 }
2647 ERROR | COMMENT | VALUE | WHITESPACE | NEWLINE => {
2648 content.push(c);
2649 }
2650 EMPTY_LINE | ENTRY | ROOT | PARAGRAPH => unreachable!(),
2651 }
2652 }
2653
2654 let indentation = if let crate::Indentation::Spaces(i) = indentation {
2655 i
2656 } else {
2657 1
2658 };
2659
2660 assert!(indentation > 0);
2661
2662 while let Some(c) = content.last() {
2664 if c.kind() == NEWLINE || c.kind() == WHITESPACE {
2665 content.pop();
2666 } else {
2667 break;
2668 }
2669 }
2670
2671 let tokens = if let Some(ref format_value) = format_value {
2674 if !content
2675 .iter()
2676 .any(|c| c.kind() == ERROR || c.kind() == COMMENT)
2677 {
2678 let concat = content
2679 .iter()
2680 .filter_map(|c| c.as_token().map(|t| t.text()))
2681 .collect::<String>();
2682 let formatted = format_value(self.key().as_ref().unwrap(), &concat);
2683 crate::lex::lex_inline(&formatted)
2684 .map(|(k, t)| (k, t.to_string()))
2685 .collect::<Vec<_>>()
2686 } else {
2687 content
2688 .into_iter()
2689 .map(|n| n.into_token().unwrap())
2690 .map(|i| (i.kind(), i.text().to_string()))
2691 .collect::<Vec<_>>()
2692 }
2693 } else {
2694 content
2695 .into_iter()
2696 .map(|n| n.into_token().unwrap())
2697 .map(|i| (i.kind(), i.text().to_string()))
2698 .collect::<Vec<_>>()
2699 };
2700
2701 rebuild_value(
2702 &mut builder,
2703 tokens,
2704 self.key().map_or(0, |k| k.len()),
2705 indentation,
2706 immediate_empty_line,
2707 max_line_length_one_liner,
2708 );
2709
2710 builder.finish_node();
2711 Self(SyntaxNode::new_root_mut(builder.finish()))
2712 }
2713
2714 pub fn key(&self) -> Option<String> {
2716 self.0
2717 .children_with_tokens()
2718 .filter_map(|it| it.into_token())
2719 .find(|it| it.kind() == KEY)
2720 .map(|it| it.text().to_string())
2721 }
2722
2723 pub fn value(&self) -> String {
2725 let mut parts = self
2726 .0
2727 .children_with_tokens()
2728 .filter_map(|it| it.into_token())
2729 .filter(|it| it.kind() == VALUE)
2730 .map(|it| it.text().to_string());
2731
2732 match parts.next() {
2733 None => String::new(),
2734 Some(first) => {
2735 let mut result = first;
2736 for part in parts {
2737 result.push('\n');
2738 result.push_str(&part);
2739 }
2740 result
2741 }
2742 }
2743 }
2744
2745 pub fn value_with_comments(&self) -> String {
2752 let mut parts = self
2753 .0
2754 .children_with_tokens()
2755 .filter_map(|it| it.into_token())
2756 .filter(|it| it.kind() == VALUE || it.kind() == COMMENT)
2757 .map(|it| it.text().to_string());
2758
2759 match parts.next() {
2760 None => String::new(),
2761 Some(first) => {
2762 let mut result = first;
2763 for part in parts {
2764 result.push('\n');
2765 result.push_str(&part);
2766 }
2767 result
2768 }
2769 }
2770 }
2771
2772 fn get_indent(&self) -> Option<String> {
2775 self.0
2776 .children_with_tokens()
2777 .filter_map(|it| it.into_token())
2778 .find(|it| it.kind() == INDENT)
2779 .map(|it| it.text().to_string())
2780 }
2781
2782 fn get_post_colon_whitespace(&self) -> Option<String> {
2786 let mut found_colon = false;
2787 let mut whitespace = String::new();
2788
2789 for token in self
2790 .0
2791 .children_with_tokens()
2792 .filter_map(|it| it.into_token())
2793 {
2794 if token.kind() == COLON {
2795 found_colon = true;
2796 continue;
2797 }
2798
2799 if found_colon {
2800 if token.kind() == WHITESPACE || token.kind() == NEWLINE || token.kind() == INDENT {
2801 whitespace.push_str(token.text());
2802 } else {
2803 break;
2805 }
2806 }
2807 }
2808
2809 if whitespace.is_empty() {
2810 None
2811 } else {
2812 Some(whitespace)
2813 }
2814 }
2815
2816 pub fn normalize_field_spacing(&mut self) -> bool {
2837 use rowan::GreenNodeBuilder;
2838
2839 let original_text = self.0.text().to_string();
2841
2842 let mut builder = GreenNodeBuilder::new();
2844 builder.start_node(ENTRY.into());
2845
2846 let mut seen_colon = false;
2847 let mut skip_whitespace = false;
2848
2849 for child in self.0.children_with_tokens() {
2850 match child.kind() {
2851 KEY => {
2852 builder.token(KEY.into(), child.as_token().unwrap().text());
2853 }
2854 COLON => {
2855 builder.token(COLON.into(), ":");
2856 seen_colon = true;
2857 skip_whitespace = true;
2858 }
2859 WHITESPACE if skip_whitespace => {
2860 continue;
2862 }
2863 VALUE if skip_whitespace => {
2864 builder.token(WHITESPACE.into(), " ");
2866 builder.token(VALUE.into(), child.as_token().unwrap().text());
2867 skip_whitespace = false;
2868 }
2869 NEWLINE if skip_whitespace && seen_colon => {
2870 builder.token(NEWLINE.into(), "\n");
2873 skip_whitespace = false;
2874 }
2875 _ => {
2876 if let Some(token) = child.as_token() {
2878 builder.token(token.kind().into(), token.text());
2879 }
2880 }
2881 }
2882 }
2883
2884 builder.finish_node();
2885 let normalized_green = builder.finish();
2886 let normalized = SyntaxNode::new_root_mut(normalized_green);
2887
2888 let changed = original_text != normalized.text().to_string();
2890
2891 if changed {
2892 if let Some(parent) = self.0.parent() {
2894 let index = self.0.index();
2895 parent.splice_children(index..index + 1, vec![normalized.into()]);
2896 }
2897 }
2898
2899 changed
2900 }
2901
2902 pub fn detach(&mut self) {
2904 self.0.detach();
2905 }
2906}
2907
2908impl FromStr for Deb822 {
2909 type Err = ParseError;
2910
2911 fn from_str(s: &str) -> Result<Self, Self::Err> {
2912 Deb822::parse(s).to_result()
2913 }
2914}
2915
2916#[test]
2917fn test_parse_simple() {
2918 const CONTROLV1: &str = r#"Source: foo
2919Maintainer: Foo Bar <foo@example.com>
2920Section: net
2921
2922# This is a comment
2923
2924Package: foo
2925Architecture: all
2926Depends:
2927 bar,
2928 blah
2929Description: This is a description
2930 And it is
2931 .
2932 multiple
2933 lines
2934"#;
2935 let parsed = parse(CONTROLV1);
2936 let node = parsed.syntax();
2937 assert_eq!(
2938 format!("{:#?}", node),
2939 r###"ROOT@0..203
2940 PARAGRAPH@0..63
2941 ENTRY@0..12
2942 KEY@0..6 "Source"
2943 COLON@6..7 ":"
2944 WHITESPACE@7..8 " "
2945 VALUE@8..11 "foo"
2946 NEWLINE@11..12 "\n"
2947 ENTRY@12..50
2948 KEY@12..22 "Maintainer"
2949 COLON@22..23 ":"
2950 WHITESPACE@23..24 " "
2951 VALUE@24..49 "Foo Bar <foo@example. ..."
2952 NEWLINE@49..50 "\n"
2953 ENTRY@50..63
2954 KEY@50..57 "Section"
2955 COLON@57..58 ":"
2956 WHITESPACE@58..59 " "
2957 VALUE@59..62 "net"
2958 NEWLINE@62..63 "\n"
2959 EMPTY_LINE@63..64
2960 NEWLINE@63..64 "\n"
2961 EMPTY_LINE@64..84
2962 COMMENT@64..83 "# This is a comment"
2963 NEWLINE@83..84 "\n"
2964 EMPTY_LINE@84..85
2965 NEWLINE@84..85 "\n"
2966 PARAGRAPH@85..203
2967 ENTRY@85..98
2968 KEY@85..92 "Package"
2969 COLON@92..93 ":"
2970 WHITESPACE@93..94 " "
2971 VALUE@94..97 "foo"
2972 NEWLINE@97..98 "\n"
2973 ENTRY@98..116
2974 KEY@98..110 "Architecture"
2975 COLON@110..111 ":"
2976 WHITESPACE@111..112 " "
2977 VALUE@112..115 "all"
2978 NEWLINE@115..116 "\n"
2979 ENTRY@116..137
2980 KEY@116..123 "Depends"
2981 COLON@123..124 ":"
2982 NEWLINE@124..125 "\n"
2983 INDENT@125..126 " "
2984 VALUE@126..130 "bar,"
2985 NEWLINE@130..131 "\n"
2986 INDENT@131..132 " "
2987 VALUE@132..136 "blah"
2988 NEWLINE@136..137 "\n"
2989 ENTRY@137..203
2990 KEY@137..148 "Description"
2991 COLON@148..149 ":"
2992 WHITESPACE@149..150 " "
2993 VALUE@150..171 "This is a description"
2994 NEWLINE@171..172 "\n"
2995 INDENT@172..173 " "
2996 VALUE@173..182 "And it is"
2997 NEWLINE@182..183 "\n"
2998 INDENT@183..184 " "
2999 VALUE@184..185 "."
3000 NEWLINE@185..186 "\n"
3001 INDENT@186..187 " "
3002 VALUE@187..195 "multiple"
3003 NEWLINE@195..196 "\n"
3004 INDENT@196..197 " "
3005 VALUE@197..202 "lines"
3006 NEWLINE@202..203 "\n"
3007"###
3008 );
3009 assert_eq!(parsed.errors, Vec::<String>::new());
3010
3011 let root = parsed.root_mut();
3012 assert_eq!(root.paragraphs().count(), 2);
3013 let source = root.paragraphs().next().unwrap();
3014 assert_eq!(
3015 source.keys().collect::<Vec<_>>(),
3016 vec!["Source", "Maintainer", "Section"]
3017 );
3018 assert_eq!(source.get("Source").as_deref(), Some("foo"));
3019 assert_eq!(
3020 source.get("Maintainer").as_deref(),
3021 Some("Foo Bar <foo@example.com>")
3022 );
3023 assert_eq!(source.get("Section").as_deref(), Some("net"));
3024 assert_eq!(
3025 source.items().collect::<Vec<_>>(),
3026 vec![
3027 ("Source".into(), "foo".into()),
3028 ("Maintainer".into(), "Foo Bar <foo@example.com>".into()),
3029 ("Section".into(), "net".into()),
3030 ]
3031 );
3032
3033 let binary = root.paragraphs().nth(1).unwrap();
3034 assert_eq!(
3035 binary.keys().collect::<Vec<_>>(),
3036 vec!["Package", "Architecture", "Depends", "Description"]
3037 );
3038 assert_eq!(binary.get("Package").as_deref(), Some("foo"));
3039 assert_eq!(binary.get("Architecture").as_deref(), Some("all"));
3040 assert_eq!(binary.get("Depends").as_deref(), Some("bar,\nblah"));
3041 assert_eq!(
3042 binary.get("Description").as_deref(),
3043 Some("This is a description\nAnd it is\n.\nmultiple\nlines")
3044 );
3045
3046 assert_eq!(node.text(), CONTROLV1);
3047}
3048
3049#[test]
3050fn test_with_trailing_whitespace() {
3051 const CONTROLV1: &str = r#"Source: foo
3052Maintainer: Foo Bar <foo@example.com>
3053
3054
3055"#;
3056 let parsed = parse(CONTROLV1);
3057 let node = parsed.syntax();
3058 assert_eq!(
3059 format!("{:#?}", node),
3060 r###"ROOT@0..52
3061 PARAGRAPH@0..50
3062 ENTRY@0..12
3063 KEY@0..6 "Source"
3064 COLON@6..7 ":"
3065 WHITESPACE@7..8 " "
3066 VALUE@8..11 "foo"
3067 NEWLINE@11..12 "\n"
3068 ENTRY@12..50
3069 KEY@12..22 "Maintainer"
3070 COLON@22..23 ":"
3071 WHITESPACE@23..24 " "
3072 VALUE@24..49 "Foo Bar <foo@example. ..."
3073 NEWLINE@49..50 "\n"
3074 EMPTY_LINE@50..51
3075 NEWLINE@50..51 "\n"
3076 EMPTY_LINE@51..52
3077 NEWLINE@51..52 "\n"
3078"###
3079 );
3080 assert_eq!(parsed.errors, Vec::<String>::new());
3081
3082 let root = parsed.root_mut();
3083 assert_eq!(root.paragraphs().count(), 1);
3084 let source = root.paragraphs().next().unwrap();
3085 assert_eq!(
3086 source.items().collect::<Vec<_>>(),
3087 vec![
3088 ("Source".into(), "foo".into()),
3089 ("Maintainer".into(), "Foo Bar <foo@example.com>".into()),
3090 ]
3091 );
3092}
3093
3094fn rebuild_value(
3095 builder: &mut GreenNodeBuilder,
3096 mut tokens: Vec<(SyntaxKind, String)>,
3097 key_len: usize,
3098 indentation: u32,
3099 immediate_empty_line: bool,
3100 max_line_length_one_liner: Option<usize>,
3101) {
3102 let first_line_len = tokens
3103 .iter()
3104 .take_while(|(k, _t)| *k != NEWLINE)
3105 .map(|(_k, t)| t.len())
3106 .sum::<usize>() + key_len + 2 ;
3107
3108 let has_newline = tokens.iter().any(|(k, _t)| *k == NEWLINE);
3109
3110 let mut last_was_newline = false;
3111 if max_line_length_one_liner
3112 .map(|mll| first_line_len <= mll)
3113 .unwrap_or(false)
3114 && !has_newline
3115 {
3116 for (k, t) in tokens {
3118 builder.token(k.into(), &t);
3119 }
3120 } else {
3121 if immediate_empty_line && has_newline {
3123 builder.token(NEWLINE.into(), "\n");
3124 last_was_newline = true;
3125 } else {
3126 builder.token(WHITESPACE.into(), " ");
3127 }
3128 let mut start_idx = 0;
3130 while start_idx < tokens.len() {
3131 if tokens[start_idx].0 == NEWLINE || tokens[start_idx].0 == WHITESPACE {
3132 start_idx += 1;
3133 } else {
3134 break;
3135 }
3136 }
3137 tokens.drain(..start_idx);
3138 let indent_str = " ".repeat(indentation as usize);
3140 for (k, t) in tokens {
3141 if last_was_newline {
3142 builder.token(INDENT.into(), &indent_str);
3143 }
3144 builder.token(k.into(), &t);
3145 last_was_newline = k == NEWLINE;
3146 }
3147 }
3148
3149 if !last_was_newline {
3150 builder.token(NEWLINE.into(), "\n");
3151 }
3152}
3153
3154#[cfg(test)]
3155mod tests {
3156 use super::*;
3157 #[test]
3158 fn test_parse() {
3159 let d: super::Deb822 = r#"Source: foo
3160Maintainer: Foo Bar <jelmer@jelmer.uk>
3161Section: net
3162
3163Package: foo
3164Architecture: all
3165Depends: libc6
3166Description: This is a description
3167 With details
3168"#
3169 .parse()
3170 .unwrap();
3171 let mut ps = d.paragraphs();
3172 let p = ps.next().unwrap();
3173
3174 assert_eq!(p.get("Source").as_deref(), Some("foo"));
3175 assert_eq!(
3176 p.get("Maintainer").as_deref(),
3177 Some("Foo Bar <jelmer@jelmer.uk>")
3178 );
3179 assert_eq!(p.get("Section").as_deref(), Some("net"));
3180
3181 let b = ps.next().unwrap();
3182 assert_eq!(b.get("Package").as_deref(), Some("foo"));
3183 }
3184
3185 #[test]
3186 fn test_after_multi_line() {
3187 let d: super::Deb822 = r#"Source: golang-github-blah-blah
3188Section: devel
3189Priority: optional
3190Standards-Version: 4.2.0
3191Maintainer: Some Maintainer <example@example.com>
3192Build-Depends: debhelper (>= 11~),
3193 dh-golang,
3194 golang-any
3195Homepage: https://github.com/j-keck/arping
3196"#
3197 .parse()
3198 .unwrap();
3199 let mut ps = d.paragraphs();
3200 let p = ps.next().unwrap();
3201 assert_eq!(p.get("Source").as_deref(), Some("golang-github-blah-blah"));
3202 assert_eq!(p.get("Section").as_deref(), Some("devel"));
3203 assert_eq!(p.get("Priority").as_deref(), Some("optional"));
3204 assert_eq!(p.get("Standards-Version").as_deref(), Some("4.2.0"));
3205 assert_eq!(
3206 p.get("Maintainer").as_deref(),
3207 Some("Some Maintainer <example@example.com>")
3208 );
3209 assert_eq!(
3210 p.get("Build-Depends").as_deref(),
3211 Some("debhelper (>= 11~),\ndh-golang,\ngolang-any")
3212 );
3213 assert_eq!(
3214 p.get("Homepage").as_deref(),
3215 Some("https://github.com/j-keck/arping")
3216 );
3217 }
3218
3219 #[test]
3220 fn test_remove_field() {
3221 let d: super::Deb822 = r#"Source: foo
3222# Comment
3223Maintainer: Foo Bar <jelmer@jelmer.uk>
3224Section: net
3225
3226Package: foo
3227Architecture: all
3228Depends: libc6
3229Description: This is a description
3230 With details
3231"#
3232 .parse()
3233 .unwrap();
3234 let mut ps = d.paragraphs();
3235 let mut p = ps.next().unwrap();
3236 p.set("Foo", "Bar");
3237 p.remove("Section");
3238 p.remove("Nonexistent");
3239 assert_eq!(p.get("Foo").as_deref(), Some("Bar"));
3240 assert_eq!(
3241 p.to_string(),
3242 r#"Source: foo
3243# Comment
3244Maintainer: Foo Bar <jelmer@jelmer.uk>
3245Foo: Bar
3246"#
3247 );
3248 }
3249
3250 #[test]
3251 fn test_rename_field() {
3252 let d: super::Deb822 = r#"Source: foo
3253Vcs-Browser: https://salsa.debian.org/debian/foo
3254"#
3255 .parse()
3256 .unwrap();
3257 let mut ps = d.paragraphs();
3258 let mut p = ps.next().unwrap();
3259 assert!(p.rename("Vcs-Browser", "Homepage"));
3260 assert_eq!(
3261 p.to_string(),
3262 r#"Source: foo
3263Homepage: https://salsa.debian.org/debian/foo
3264"#
3265 );
3266
3267 assert_eq!(
3268 p.get("Homepage").as_deref(),
3269 Some("https://salsa.debian.org/debian/foo")
3270 );
3271 assert_eq!(p.get("Vcs-Browser").as_deref(), None);
3272
3273 assert!(!p.rename("Nonexistent", "Homepage"));
3275 }
3276
3277 #[test]
3278 fn test_set_field() {
3279 let d: super::Deb822 = r#"Source: foo
3280Maintainer: Foo Bar <joe@example.com>
3281"#
3282 .parse()
3283 .unwrap();
3284 let mut ps = d.paragraphs();
3285 let mut p = ps.next().unwrap();
3286 p.set("Maintainer", "Somebody Else <jane@example.com>");
3287 assert_eq!(
3288 p.get("Maintainer").as_deref(),
3289 Some("Somebody Else <jane@example.com>")
3290 );
3291 assert_eq!(
3292 p.to_string(),
3293 r#"Source: foo
3294Maintainer: Somebody Else <jane@example.com>
3295"#
3296 );
3297 }
3298
3299 #[test]
3300 fn test_set_new_field() {
3301 let d: super::Deb822 = r#"Source: foo
3302"#
3303 .parse()
3304 .unwrap();
3305 let mut ps = d.paragraphs();
3306 let mut p = ps.next().unwrap();
3307 p.set("Maintainer", "Somebody <joe@example.com>");
3308 assert_eq!(
3309 p.get("Maintainer").as_deref(),
3310 Some("Somebody <joe@example.com>")
3311 );
3312 assert_eq!(
3313 p.to_string(),
3314 r#"Source: foo
3315Maintainer: Somebody <joe@example.com>
3316"#
3317 );
3318 }
3319
3320 #[test]
3321 fn test_add_paragraph() {
3322 let mut d = super::Deb822::new();
3323 let mut p = d.add_paragraph();
3324 p.set("Foo", "Bar");
3325 assert_eq!(p.get("Foo").as_deref(), Some("Bar"));
3326 assert_eq!(
3327 p.to_string(),
3328 r#"Foo: Bar
3329"#
3330 );
3331 assert_eq!(
3332 d.to_string(),
3333 r#"Foo: Bar
3334"#
3335 );
3336
3337 let mut p = d.add_paragraph();
3338 p.set("Foo", "Blah");
3339 assert_eq!(p.get("Foo").as_deref(), Some("Blah"));
3340 assert_eq!(
3341 d.to_string(),
3342 r#"Foo: Bar
3343
3344Foo: Blah
3345"#
3346 );
3347 }
3348
3349 #[test]
3350 fn test_crud_paragraph() {
3351 let mut d = super::Deb822::new();
3352 let mut p = d.insert_paragraph(0);
3353 p.set("Foo", "Bar");
3354 assert_eq!(p.get("Foo").as_deref(), Some("Bar"));
3355 assert_eq!(
3356 d.to_string(),
3357 r#"Foo: Bar
3358"#
3359 );
3360
3361 let mut p = d.insert_paragraph(0);
3363 p.set("Foo", "Blah");
3364 assert_eq!(p.get("Foo").as_deref(), Some("Blah"));
3365 assert_eq!(
3366 d.to_string(),
3367 r#"Foo: Blah
3368
3369Foo: Bar
3370"#
3371 );
3372
3373 d.remove_paragraph(1);
3375 assert_eq!(d.to_string(), "Foo: Blah\n\n");
3376
3377 p.set("Foo", "Baz");
3379 assert_eq!(d.to_string(), "Foo: Baz\n\n");
3380
3381 d.remove_paragraph(0);
3383 assert_eq!(d.to_string(), "");
3384 }
3385
3386 #[test]
3387 fn test_swap_paragraphs() {
3388 let mut d: super::Deb822 = vec![
3390 vec![("Foo", "Bar")].into_iter().collect(),
3391 vec![("A", "B")].into_iter().collect(),
3392 vec![("X", "Y")].into_iter().collect(),
3393 ]
3394 .into_iter()
3395 .collect();
3396
3397 d.swap_paragraphs(0, 2);
3398 assert_eq!(d.to_string(), "X: Y\n\nA: B\n\nFoo: Bar\n");
3399
3400 d.swap_paragraphs(0, 2);
3402 assert_eq!(d.to_string(), "Foo: Bar\n\nA: B\n\nX: Y\n");
3403
3404 d.swap_paragraphs(0, 1);
3406 assert_eq!(d.to_string(), "A: B\n\nFoo: Bar\n\nX: Y\n");
3407
3408 let before = d.to_string();
3410 d.swap_paragraphs(1, 1);
3411 assert_eq!(d.to_string(), before);
3412 }
3413
3414 #[test]
3415 fn test_swap_paragraphs_preserves_content() {
3416 let mut d: super::Deb822 = vec![
3418 vec![("Field1", "Value1"), ("Field2", "Value2")]
3419 .into_iter()
3420 .collect(),
3421 vec![("FieldA", "ValueA"), ("FieldB", "ValueB")]
3422 .into_iter()
3423 .collect(),
3424 ]
3425 .into_iter()
3426 .collect();
3427
3428 d.swap_paragraphs(0, 1);
3429
3430 let mut paras = d.paragraphs();
3431 let p1 = paras.next().unwrap();
3432 assert_eq!(p1.get("FieldA").as_deref(), Some("ValueA"));
3433 assert_eq!(p1.get("FieldB").as_deref(), Some("ValueB"));
3434
3435 let p2 = paras.next().unwrap();
3436 assert_eq!(p2.get("Field1").as_deref(), Some("Value1"));
3437 assert_eq!(p2.get("Field2").as_deref(), Some("Value2"));
3438 }
3439
3440 #[test]
3441 #[should_panic(expected = "out of bounds")]
3442 fn test_swap_paragraphs_out_of_bounds() {
3443 let mut d: super::Deb822 = vec![
3444 vec![("Foo", "Bar")].into_iter().collect(),
3445 vec![("A", "B")].into_iter().collect(),
3446 ]
3447 .into_iter()
3448 .collect();
3449
3450 d.swap_paragraphs(0, 5);
3451 }
3452
3453 #[test]
3454 fn test_multiline_entry() {
3455 use super::SyntaxKind::*;
3456 use rowan::ast::AstNode;
3457
3458 let entry = super::Entry::new("foo", "bar\nbaz");
3459 let tokens: Vec<_> = entry
3460 .syntax()
3461 .descendants_with_tokens()
3462 .filter_map(|tok| tok.into_token())
3463 .collect();
3464
3465 assert_eq!("foo: bar\n baz\n", entry.to_string());
3466 assert_eq!("bar\nbaz", entry.value());
3467
3468 assert_eq!(
3469 vec![
3470 (KEY, "foo"),
3471 (COLON, ":"),
3472 (WHITESPACE, " "),
3473 (VALUE, "bar"),
3474 (NEWLINE, "\n"),
3475 (INDENT, " "),
3476 (VALUE, "baz"),
3477 (NEWLINE, "\n"),
3478 ],
3479 tokens
3480 .iter()
3481 .map(|token| (token.kind(), token.text()))
3482 .collect::<Vec<_>>()
3483 );
3484 }
3485
3486 #[test]
3487 fn test_apt_entry() {
3488 let text = r#"Package: cvsd
3489Binary: cvsd
3490Version: 1.0.24
3491Maintainer: Arthur de Jong <adejong@debian.org>
3492Build-Depends: debhelper (>= 9), po-debconf
3493Architecture: any
3494Standards-Version: 3.9.3
3495Format: 3.0 (native)
3496Files:
3497 b7a7d67a02974c52c408fdb5e118406d 890 cvsd_1.0.24.dsc
3498 b73ee40774c3086cb8490cdbb96ac883 258139 cvsd_1.0.24.tar.gz
3499Vcs-Browser: http://arthurdejong.org/viewvc/cvsd/
3500Vcs-Cvs: :pserver:anonymous@arthurdejong.org:/arthur/
3501Checksums-Sha256:
3502 a7bb7a3aacee19cd14ce5c26cb86e348b1608e6f1f6e97c6ea7c58efa440ac43 890 cvsd_1.0.24.dsc
3503 46bc517760c1070ae408693b89603986b53e6f068ae6bdc744e2e830e46b8cba 258139 cvsd_1.0.24.tar.gz
3504Homepage: http://arthurdejong.org/cvsd/
3505Package-List:
3506 cvsd deb vcs optional
3507Directory: pool/main/c/cvsd
3508Priority: source
3509Section: vcs
3510
3511"#;
3512 let d: super::Deb822 = text.parse().unwrap();
3513 let p = d.paragraphs().next().unwrap();
3514 assert_eq!(p.get("Binary").as_deref(), Some("cvsd"));
3515 assert_eq!(p.get("Version").as_deref(), Some("1.0.24"));
3516 assert_eq!(
3517 p.get("Maintainer").as_deref(),
3518 Some("Arthur de Jong <adejong@debian.org>")
3519 );
3520 }
3521
3522 #[test]
3523 fn test_format() {
3524 let d: super::Deb822 = r#"Source: foo
3525Maintainer: Foo Bar <foo@example.com>
3526Section: net
3527Blah: blah # comment
3528Multi-Line:
3529 Ahoi!
3530 Matey!
3531
3532"#
3533 .parse()
3534 .unwrap();
3535 let mut ps = d.paragraphs();
3536 let p = ps.next().unwrap();
3537 let result = p.wrap_and_sort(
3538 crate::Indentation::FieldNameLength,
3539 false,
3540 None,
3541 None::<&dyn Fn(&super::Entry, &super::Entry) -> std::cmp::Ordering>,
3542 None,
3543 );
3544 assert_eq!(
3545 result.to_string(),
3546 r#"Source: foo
3547Maintainer: Foo Bar <foo@example.com>
3548Section: net
3549Blah: blah # comment
3550Multi-Line: Ahoi!
3551 Matey!
3552"#
3553 );
3554 }
3555
3556 #[test]
3557 fn test_format_sort_paragraphs() {
3558 let d: super::Deb822 = r#"Source: foo
3559Maintainer: Foo Bar <foo@example.com>
3560
3561# This is a comment
3562Source: bar
3563Maintainer: Bar Foo <bar@example.com>
3564
3565"#
3566 .parse()
3567 .unwrap();
3568 let result = d.wrap_and_sort(
3569 Some(&|a: &super::Paragraph, b: &super::Paragraph| {
3570 a.get("Source").cmp(&b.get("Source"))
3571 }),
3572 Some(&|p| {
3573 p.wrap_and_sort(
3574 crate::Indentation::FieldNameLength,
3575 false,
3576 None,
3577 None::<&dyn Fn(&super::Entry, &super::Entry) -> std::cmp::Ordering>,
3578 None,
3579 )
3580 }),
3581 );
3582 assert_eq!(
3583 result.to_string(),
3584 r#"# This is a comment
3585Source: bar
3586Maintainer: Bar Foo <bar@example.com>
3587
3588Source: foo
3589Maintainer: Foo Bar <foo@example.com>
3590"#,
3591 );
3592 }
3593
3594 #[test]
3595 fn test_format_sort_fields() {
3596 let d: super::Deb822 = r#"Source: foo
3597Maintainer: Foo Bar <foo@example.com>
3598Build-Depends: debhelper (>= 9), po-debconf
3599Homepage: https://example.com/
3600
3601"#
3602 .parse()
3603 .unwrap();
3604 let result = d.wrap_and_sort(
3605 None,
3606 Some(&|p: &super::Paragraph| -> super::Paragraph {
3607 p.wrap_and_sort(
3608 crate::Indentation::FieldNameLength,
3609 false,
3610 None,
3611 Some(&|a: &super::Entry, b: &super::Entry| a.key().cmp(&b.key())),
3612 None,
3613 )
3614 }),
3615 );
3616 assert_eq!(
3617 result.to_string(),
3618 r#"Build-Depends: debhelper (>= 9), po-debconf
3619Homepage: https://example.com/
3620Maintainer: Foo Bar <foo@example.com>
3621Source: foo
3622"#
3623 );
3624 }
3625
3626 #[test]
3627 fn test_para_from_iter() {
3628 let p: super::Paragraph = vec![("Foo", "Bar"), ("Baz", "Qux")].into_iter().collect();
3629 assert_eq!(
3630 p.to_string(),
3631 r#"Foo: Bar
3632Baz: Qux
3633"#
3634 );
3635
3636 let p: super::Paragraph = vec![
3637 ("Foo".to_string(), "Bar".to_string()),
3638 ("Baz".to_string(), "Qux".to_string()),
3639 ]
3640 .into_iter()
3641 .collect();
3642
3643 assert_eq!(
3644 p.to_string(),
3645 r#"Foo: Bar
3646Baz: Qux
3647"#
3648 );
3649 }
3650
3651 #[test]
3652 fn test_deb822_from_iter() {
3653 let d: super::Deb822 = vec![
3654 vec![("Foo", "Bar"), ("Baz", "Qux")].into_iter().collect(),
3655 vec![("A", "B"), ("C", "D")].into_iter().collect(),
3656 ]
3657 .into_iter()
3658 .collect();
3659 assert_eq!(
3660 d.to_string(),
3661 r#"Foo: Bar
3662Baz: Qux
3663
3664A: B
3665C: D
3666"#
3667 );
3668 }
3669
3670 #[test]
3671 fn test_format_parse_error() {
3672 assert_eq!(ParseError(vec!["foo".to_string()]).to_string(), "foo\n");
3673 }
3674
3675 #[test]
3676 fn test_set_with_field_order() {
3677 let mut p = super::Paragraph::new();
3678 let custom_order = &["Foo", "Bar", "Baz"];
3679
3680 p.set_with_field_order("Baz", "3", custom_order);
3681 p.set_with_field_order("Foo", "1", custom_order);
3682 p.set_with_field_order("Bar", "2", custom_order);
3683 p.set_with_field_order("Unknown", "4", custom_order);
3684
3685 let keys: Vec<_> = p.keys().collect();
3686 assert_eq!(keys[0], "Foo");
3687 assert_eq!(keys[1], "Bar");
3688 assert_eq!(keys[2], "Baz");
3689 assert_eq!(keys[3], "Unknown");
3690 }
3691
3692 #[test]
3693 fn test_positioned_parse_error() {
3694 let error = PositionedParseError {
3695 message: "test error".to_string(),
3696 range: rowan::TextRange::new(rowan::TextSize::from(5), rowan::TextSize::from(10)),
3697 code: Some("test_code".to_string()),
3698 };
3699 assert_eq!(error.to_string(), "test error");
3700 assert_eq!(error.range.start(), rowan::TextSize::from(5));
3701 assert_eq!(error.range.end(), rowan::TextSize::from(10));
3702 assert_eq!(error.code, Some("test_code".to_string()));
3703 }
3704
3705 #[test]
3706 fn test_format_error() {
3707 assert_eq!(
3708 super::Error::ParseError(ParseError(vec!["foo".to_string()])).to_string(),
3709 "foo\n"
3710 );
3711 }
3712
3713 #[test]
3714 fn test_get_all() {
3715 let d: super::Deb822 = r#"Source: foo
3716Maintainer: Foo Bar <foo@example.com>
3717Maintainer: Bar Foo <bar@example.com>"#
3718 .parse()
3719 .unwrap();
3720 let p = d.paragraphs().next().unwrap();
3721 assert_eq!(
3722 p.get_all("Maintainer").collect::<Vec<_>>(),
3723 vec!["Foo Bar <foo@example.com>", "Bar Foo <bar@example.com>"]
3724 );
3725 }
3726
3727 #[test]
3728 fn test_get_with_indent_single_line() {
3729 let input = "Field: single line value\n";
3730 let deb = super::Deb822::from_str(input).unwrap();
3731 let para = deb.paragraphs().next().unwrap();
3732
3733 assert_eq!(
3735 para.get_with_indent("Field", &super::IndentPattern::Fixed(2)),
3736 Some("single line value".to_string())
3737 );
3738 assert_eq!(
3739 para.get_with_indent("Field", &super::IndentPattern::FieldNameLength),
3740 Some("single line value".to_string())
3741 );
3742 }
3743
3744 #[test]
3745 fn test_get_with_indent_fixed() {
3746 let input = "Field: First\n Second\n Third\n";
3747 let deb = super::Deb822::from_str(input).unwrap();
3748 let para = deb.paragraphs().next().unwrap();
3749
3750 let value = para
3752 .get_with_indent("Field", &super::IndentPattern::Fixed(2))
3753 .unwrap();
3754 assert_eq!(value, "First\n Second\n Third");
3755
3756 let value = para
3758 .get_with_indent("Field", &super::IndentPattern::Fixed(1))
3759 .unwrap();
3760 assert_eq!(value, "First\n Second\n Third");
3761
3762 let value = para
3764 .get_with_indent("Field", &super::IndentPattern::Fixed(3))
3765 .unwrap();
3766 assert_eq!(value, "First\nSecond\nThird");
3767 }
3768
3769 #[test]
3770 fn test_get_with_indent_field_name_length() {
3771 let input = "Description: First line\n Second line\n Third line\n";
3772 let deb = super::Deb822::from_str(input).unwrap();
3773 let para = deb.paragraphs().next().unwrap();
3774
3775 let value = para
3778 .get_with_indent("Description", &super::IndentPattern::FieldNameLength)
3779 .unwrap();
3780 assert_eq!(value, "First line\nSecond line\nThird line");
3781
3782 let value = para
3784 .get_with_indent("Description", &super::IndentPattern::Fixed(2))
3785 .unwrap();
3786 assert_eq!(
3787 value,
3788 "First line\n Second line\n Third line"
3789 );
3790 }
3791
3792 #[test]
3793 fn test_get_with_indent_nonexistent() {
3794 let input = "Field: value\n";
3795 let deb = super::Deb822::from_str(input).unwrap();
3796 let para = deb.paragraphs().next().unwrap();
3797
3798 assert_eq!(
3799 para.get_with_indent("NonExistent", &super::IndentPattern::Fixed(2)),
3800 None
3801 );
3802 }
3803
3804 #[test]
3805 fn test_get_entry() {
3806 let input = r#"Package: test-package
3807Maintainer: Test User <test@example.com>
3808Description: A simple test package
3809 with multiple lines
3810"#;
3811 let deb = super::Deb822::from_str(input).unwrap();
3812 let para = deb.paragraphs().next().unwrap();
3813
3814 let entry = para.get_entry("Package");
3816 assert!(entry.is_some());
3817 let entry = entry.unwrap();
3818 assert_eq!(entry.key(), Some("Package".to_string()));
3819 assert_eq!(entry.value(), "test-package");
3820
3821 let entry = para.get_entry("package");
3823 assert!(entry.is_some());
3824 assert_eq!(entry.unwrap().value(), "test-package");
3825
3826 let entry = para.get_entry("Description");
3828 assert!(entry.is_some());
3829 assert_eq!(
3830 entry.unwrap().value(),
3831 "A simple test package\nwith multiple lines"
3832 );
3833
3834 assert_eq!(para.get_entry("NonExistent"), None);
3836 }
3837
3838 #[test]
3839 fn test_entry_ranges() {
3840 let input = r#"Package: test-package
3841Maintainer: Test User <test@example.com>
3842Description: A simple test package
3843 with multiple lines
3844 of description text"#;
3845
3846 let deb822 = super::Deb822::from_str(input).unwrap();
3847 let paragraph = deb822.paragraphs().next().unwrap();
3848 let entries: Vec<_> = paragraph.entries().collect();
3849
3850 let package_entry = &entries[0];
3852 assert_eq!(package_entry.key(), Some("Package".to_string()));
3853
3854 let key_range = package_entry.key_range().unwrap();
3856 assert_eq!(
3857 &input[key_range.start().into()..key_range.end().into()],
3858 "Package"
3859 );
3860
3861 let colon_range = package_entry.colon_range().unwrap();
3863 assert_eq!(
3864 &input[colon_range.start().into()..colon_range.end().into()],
3865 ":"
3866 );
3867
3868 let value_range = package_entry.value_range().unwrap();
3870 assert_eq!(
3871 &input[value_range.start().into()..value_range.end().into()],
3872 "test-package"
3873 );
3874
3875 let text_range = package_entry.text_range();
3877 assert_eq!(
3878 &input[text_range.start().into()..text_range.end().into()],
3879 "Package: test-package\n"
3880 );
3881
3882 let value_lines = package_entry.value_line_ranges();
3884 assert_eq!(value_lines.len(), 1);
3885 assert_eq!(
3886 &input[value_lines[0].start().into()..value_lines[0].end().into()],
3887 "test-package"
3888 );
3889 }
3890
3891 #[test]
3892 fn test_multiline_entry_ranges() {
3893 let input = r#"Description: Short description
3894 Extended description line 1
3895 Extended description line 2"#;
3896
3897 let deb822 = super::Deb822::from_str(input).unwrap();
3898 let paragraph = deb822.paragraphs().next().unwrap();
3899 let entry = paragraph.entries().next().unwrap();
3900
3901 assert_eq!(entry.key(), Some("Description".to_string()));
3902
3903 let value_range = entry.value_range().unwrap();
3905 let full_value = &input[value_range.start().into()..value_range.end().into()];
3906 assert!(full_value.contains("Short description"));
3907 assert!(full_value.contains("Extended description line 1"));
3908 assert!(full_value.contains("Extended description line 2"));
3909
3910 let value_lines = entry.value_line_ranges();
3912 assert_eq!(value_lines.len(), 3);
3913
3914 assert_eq!(
3915 &input[value_lines[0].start().into()..value_lines[0].end().into()],
3916 "Short description"
3917 );
3918 assert_eq!(
3919 &input[value_lines[1].start().into()..value_lines[1].end().into()],
3920 "Extended description line 1"
3921 );
3922 assert_eq!(
3923 &input[value_lines[2].start().into()..value_lines[2].end().into()],
3924 "Extended description line 2"
3925 );
3926 }
3927
3928 #[test]
3929 fn test_entries_public_access() {
3930 let input = r#"Package: test
3931Version: 1.0"#;
3932
3933 let deb822 = super::Deb822::from_str(input).unwrap();
3934 let paragraph = deb822.paragraphs().next().unwrap();
3935
3936 let entries: Vec<_> = paragraph.entries().collect();
3938 assert_eq!(entries.len(), 2);
3939 assert_eq!(entries[0].key(), Some("Package".to_string()));
3940 assert_eq!(entries[1].key(), Some("Version".to_string()));
3941 }
3942
3943 #[test]
3944 fn test_empty_value_ranges() {
3945 let input = r#"EmptyField: "#;
3946
3947 let deb822 = super::Deb822::from_str(input).unwrap();
3948 let paragraph = deb822.paragraphs().next().unwrap();
3949 let entry = paragraph.entries().next().unwrap();
3950
3951 assert_eq!(entry.key(), Some("EmptyField".to_string()));
3952
3953 assert!(entry.key_range().is_some());
3955 assert!(entry.colon_range().is_some());
3956
3957 let value_lines = entry.value_line_ranges();
3959 assert!(value_lines.len() <= 1);
3962 }
3963
3964 #[test]
3965 fn test_range_ordering() {
3966 let input = r#"Field: value"#;
3967
3968 let deb822 = super::Deb822::from_str(input).unwrap();
3969 let paragraph = deb822.paragraphs().next().unwrap();
3970 let entry = paragraph.entries().next().unwrap();
3971
3972 let key_range = entry.key_range().unwrap();
3973 let colon_range = entry.colon_range().unwrap();
3974 let value_range = entry.value_range().unwrap();
3975 let text_range = entry.text_range();
3976
3977 assert!(key_range.end() <= colon_range.start());
3979 assert!(colon_range.end() <= value_range.start());
3980 assert!(key_range.start() >= text_range.start());
3981 assert!(value_range.end() <= text_range.end());
3982 }
3983
3984 #[test]
3985 fn test_error_recovery_missing_colon() {
3986 let input = r#"Source foo
3987Maintainer: Test User <test@example.com>
3988"#;
3989 let (deb822, errors) = super::Deb822::from_str_relaxed(input);
3990
3991 assert!(!errors.is_empty());
3993 assert!(errors.iter().any(|e| e.contains("missing colon")));
3994
3995 let paragraph = deb822.paragraphs().next().unwrap();
3997 assert_eq!(
3998 paragraph.get("Maintainer").as_deref(),
3999 Some("Test User <test@example.com>")
4000 );
4001 }
4002
4003 #[test]
4004 fn test_error_recovery_missing_field_name() {
4005 let input = r#": orphaned value
4006Package: test
4007"#;
4008
4009 let (deb822, errors) = super::Deb822::from_str_relaxed(input);
4010
4011 assert!(!errors.is_empty());
4013 assert!(errors
4014 .iter()
4015 .any(|e| e.contains("field name") || e.contains("missing")));
4016
4017 let paragraphs: Vec<_> = deb822.paragraphs().collect();
4019 let mut found_package = false;
4020 for paragraph in paragraphs.iter() {
4021 if paragraph.get("Package").is_some() {
4022 found_package = true;
4023 assert_eq!(paragraph.get("Package").as_deref(), Some("test"));
4024 }
4025 }
4026 assert!(found_package, "Package field not found in any paragraph");
4027 }
4028
4029 #[test]
4030 fn test_error_recovery_orphaned_text() {
4031 let input = r#"Package: test
4032some orphaned text without field name
4033Version: 1.0
4034"#;
4035 let (deb822, errors) = super::Deb822::from_str_relaxed(input);
4036
4037 assert!(!errors.is_empty());
4039 assert!(errors.iter().any(|e| e.contains("orphaned")
4040 || e.contains("unexpected")
4041 || e.contains("field name")));
4042
4043 let mut all_fields = std::collections::HashMap::new();
4045 for paragraph in deb822.paragraphs() {
4046 for (key, value) in paragraph.items() {
4047 all_fields.insert(key, value);
4048 }
4049 }
4050
4051 assert_eq!(all_fields.get("Package"), Some(&"test".to_string()));
4052 assert_eq!(all_fields.get("Version"), Some(&"1.0".to_string()));
4053 }
4054
4055 #[test]
4056 fn test_error_recovery_consecutive_field_names() {
4057 let input = r#"Package: test
4058Description
4059Maintainer: Another field without proper value
4060Version: 1.0
4061"#;
4062 let (deb822, errors) = super::Deb822::from_str_relaxed(input);
4063
4064 assert!(!errors.is_empty());
4066 assert!(errors.iter().any(|e| e.contains("consecutive")
4067 || e.contains("missing")
4068 || e.contains("incomplete")));
4069
4070 let mut all_fields = std::collections::HashMap::new();
4072 for paragraph in deb822.paragraphs() {
4073 for (key, value) in paragraph.items() {
4074 all_fields.insert(key, value);
4075 }
4076 }
4077
4078 assert_eq!(all_fields.get("Package"), Some(&"test".to_string()));
4079 assert_eq!(
4080 all_fields.get("Maintainer"),
4081 Some(&"Another field without proper value".to_string())
4082 );
4083 assert_eq!(all_fields.get("Version"), Some(&"1.0".to_string()));
4084 }
4085
4086 #[test]
4087 fn test_error_recovery_malformed_multiline() {
4088 let input = r#"Package: test
4089Description: Short desc
4090 Proper continuation
4091invalid continuation without indent
4092 Another proper continuation
4093Version: 1.0
4094"#;
4095 let (deb822, errors) = super::Deb822::from_str_relaxed(input);
4096
4097 assert!(!errors.is_empty());
4099
4100 let paragraph = deb822.paragraphs().next().unwrap();
4102 assert_eq!(paragraph.get("Package").as_deref(), Some("test"));
4103 assert_eq!(paragraph.get("Version").as_deref(), Some("1.0"));
4104 }
4105
4106 #[test]
4107 fn test_error_recovery_mixed_errors() {
4108 let input = r#"Package test without colon
4109: orphaned colon
4110Description: Valid field
4111some orphaned text
4112Another-Field: Valid too
4113"#;
4114 let (deb822, errors) = super::Deb822::from_str_relaxed(input);
4115
4116 assert!(!errors.is_empty());
4118 assert!(errors.len() >= 2);
4119
4120 let paragraph = deb822.paragraphs().next().unwrap();
4122 assert_eq!(paragraph.get("Description").as_deref(), Some("Valid field"));
4123 assert_eq!(paragraph.get("Another-Field").as_deref(), Some("Valid too"));
4124 }
4125
4126 #[test]
4127 fn test_error_recovery_paragraph_boundary() {
4128 let input = r#"Package: first-package
4129Description: First paragraph
4130
4131corrupted data here
4132: more corruption
4133completely broken line
4134
4135Package: second-package
4136Version: 1.0
4137"#;
4138 let (deb822, errors) = super::Deb822::from_str_relaxed(input);
4139
4140 assert!(!errors.is_empty());
4142
4143 let paragraphs: Vec<_> = deb822.paragraphs().collect();
4145 assert_eq!(paragraphs.len(), 2);
4146
4147 assert_eq!(
4148 paragraphs[0].get("Package").as_deref(),
4149 Some("first-package")
4150 );
4151 assert_eq!(
4152 paragraphs[1].get("Package").as_deref(),
4153 Some("second-package")
4154 );
4155 assert_eq!(paragraphs[1].get("Version").as_deref(), Some("1.0"));
4156 }
4157
4158 #[test]
4159 fn test_error_recovery_with_positioned_errors() {
4160 let input = r#"Package test
4161Description: Valid
4162"#;
4163 let parsed = super::parse(input);
4164
4165 assert!(!parsed.positioned_errors.is_empty());
4167
4168 let first_error = &parsed.positioned_errors[0];
4169 assert!(!first_error.message.is_empty());
4170 assert!(first_error.range.start() <= first_error.range.end());
4171 assert!(first_error.code.is_some());
4172
4173 let error_text = &input[first_error.range.start().into()..first_error.range.end().into()];
4175 assert!(!error_text.is_empty());
4176 }
4177
4178 #[test]
4179 fn test_positioned_error_points_to_correct_token() {
4180 let input = "Package test\nDescription: Valid\n";
4181 let parsed = super::parse(input);
4182
4183 assert_eq!(parsed.positioned_errors.len(), 1);
4184
4185 let first_error = &parsed.positioned_errors[0];
4186 assert_eq!(first_error.message, "missing colon ':' after field name");
4187 assert_eq!(first_error.code.as_deref(), Some("missing_colon"));
4188
4189 let start: usize = first_error.range.start().into();
4190 let end: usize = first_error.range.end().into();
4191 assert_eq!(start, 8);
4192 assert_eq!(end, 12);
4193 assert_eq!(&input[start..end], "test");
4194 }
4195
4196 #[test]
4197 fn test_error_recovery_preserves_whitespace() {
4198 let input = r#"Source: package
4199Maintainer Test User <test@example.com>
4200Section: utils
4201
4202"#;
4203 let (deb822, errors) = super::Deb822::from_str_relaxed(input);
4204
4205 assert!(!errors.is_empty());
4207
4208 let output = deb822.to_string();
4210 assert!(output.contains("Section: utils"));
4211
4212 let paragraph = deb822.paragraphs().next().unwrap();
4214 assert_eq!(paragraph.get("Source").as_deref(), Some("package"));
4215 assert_eq!(paragraph.get("Section").as_deref(), Some("utils"));
4216 }
4217
4218 #[test]
4219 fn test_error_recovery_empty_fields() {
4220 let input = r#"Package: test
4221Description:
4222Maintainer: Valid User
4223EmptyField:
4224Version: 1.0
4225"#;
4226 let (deb822, _errors) = super::Deb822::from_str_relaxed(input);
4227
4228 let mut all_fields = std::collections::HashMap::new();
4230 for paragraph in deb822.paragraphs() {
4231 for (key, value) in paragraph.items() {
4232 all_fields.insert(key, value);
4233 }
4234 }
4235
4236 assert_eq!(all_fields.get("Package"), Some(&"test".to_string()));
4237 assert_eq!(all_fields.get("Description"), Some(&"".to_string()));
4238 assert_eq!(
4239 all_fields.get("Maintainer"),
4240 Some(&"Valid User".to_string())
4241 );
4242 assert_eq!(all_fields.get("EmptyField"), Some(&"".to_string()));
4243 assert_eq!(all_fields.get("Version"), Some(&"1.0".to_string()));
4244 }
4245
4246 #[test]
4247 fn test_insert_comment_before() {
4248 let d: super::Deb822 = vec![
4249 vec![("Source", "foo"), ("Maintainer", "Bar <bar@example.com>")]
4250 .into_iter()
4251 .collect(),
4252 vec![("Package", "foo"), ("Architecture", "all")]
4253 .into_iter()
4254 .collect(),
4255 ]
4256 .into_iter()
4257 .collect();
4258
4259 let mut p1 = d.paragraphs().next().unwrap();
4261 p1.insert_comment_before("This is the source paragraph");
4262
4263 let mut p2 = d.paragraphs().nth(1).unwrap();
4265 p2.insert_comment_before("This is the binary paragraph");
4266
4267 let output = d.to_string();
4268 assert_eq!(
4269 output,
4270 r#"# This is the source paragraph
4271Source: foo
4272Maintainer: Bar <bar@example.com>
4273
4274# This is the binary paragraph
4275Package: foo
4276Architecture: all
4277"#
4278 );
4279 }
4280
4281 #[test]
4282 fn test_parse_continuation_with_colon() {
4283 let input = "Package: test\nDescription: short\n line: with colon\n";
4285 let result = input.parse::<Deb822>();
4286 assert!(result.is_ok());
4287
4288 let deb822 = result.unwrap();
4289 let para = deb822.paragraphs().next().unwrap();
4290 assert_eq!(para.get("Package").as_deref(), Some("test"));
4291 assert_eq!(
4292 para.get("Description").as_deref(),
4293 Some("short\nline: with colon")
4294 );
4295 }
4296
4297 #[test]
4298 fn test_parse_continuation_starting_with_colon() {
4299 let input = "Package: test\nDescription: short\n :value\n";
4301 let result = input.parse::<Deb822>();
4302 assert!(result.is_ok());
4303
4304 let deb822 = result.unwrap();
4305 let para = deb822.paragraphs().next().unwrap();
4306 assert_eq!(para.get("Package").as_deref(), Some("test"));
4307 assert_eq!(para.get("Description").as_deref(), Some("short\n:value"));
4308 }
4309
4310 #[test]
4311 fn test_normalize_field_spacing_single_space() {
4312 let input = "Field: value\n";
4314 let deb822 = input.parse::<Deb822>().unwrap();
4315 let mut para = deb822.paragraphs().next().unwrap();
4316
4317 para.normalize_field_spacing();
4318 assert_eq!(para.to_string(), "Field: value\n");
4319 }
4320
4321 #[test]
4322 fn test_normalize_field_spacing_extra_spaces() {
4323 let input = "Field: value\n";
4325 let deb822 = input.parse::<Deb822>().unwrap();
4326 let mut para = deb822.paragraphs().next().unwrap();
4327
4328 para.normalize_field_spacing();
4329 assert_eq!(para.to_string(), "Field: value\n");
4330 }
4331
4332 #[test]
4333 fn test_normalize_field_spacing_no_space() {
4334 let input = "Field:value\n";
4336 let deb822 = input.parse::<Deb822>().unwrap();
4337 let mut para = deb822.paragraphs().next().unwrap();
4338
4339 para.normalize_field_spacing();
4340 assert_eq!(para.to_string(), "Field: value\n");
4341 }
4342
4343 #[test]
4344 fn test_normalize_field_spacing_multiple_fields() {
4345 let input = "Field1: value1\nField2:value2\nField3: value3\n";
4347 let deb822 = input.parse::<Deb822>().unwrap();
4348 let mut para = deb822.paragraphs().next().unwrap();
4349
4350 para.normalize_field_spacing();
4351 assert_eq!(
4352 para.to_string(),
4353 "Field1: value1\nField2: value2\nField3: value3\n"
4354 );
4355 }
4356
4357 #[test]
4358 fn test_normalize_field_spacing_multiline_value() {
4359 let input = "Description: short\n continuation line\n . \n final line\n";
4361 let deb822 = input.parse::<Deb822>().unwrap();
4362 let mut para = deb822.paragraphs().next().unwrap();
4363
4364 para.normalize_field_spacing();
4365 assert_eq!(
4366 para.to_string(),
4367 "Description: short\n continuation line\n . \n final line\n"
4368 );
4369 }
4370
4371 #[test]
4372 fn test_normalize_field_spacing_empty_value_with_whitespace() {
4373 let input = "Field: \n";
4375 let deb822 = input.parse::<Deb822>().unwrap();
4376 let mut para = deb822.paragraphs().next().unwrap();
4377
4378 para.normalize_field_spacing();
4379 assert_eq!(para.to_string(), "Field:\n");
4381 }
4382
4383 #[test]
4384 fn test_normalize_field_spacing_no_value() {
4385 let input = "Depends:\n";
4387 let deb822 = input.parse::<Deb822>().unwrap();
4388 let mut para = deb822.paragraphs().next().unwrap();
4389
4390 para.normalize_field_spacing();
4391 assert_eq!(para.to_string(), "Depends:\n");
4393 }
4394
4395 #[test]
4396 fn test_normalize_field_spacing_multiple_paragraphs() {
4397 let input = "Field1: value1\n\nField2: value2\n";
4399 let mut deb822 = input.parse::<Deb822>().unwrap();
4400
4401 deb822.normalize_field_spacing();
4402 assert_eq!(deb822.to_string(), "Field1: value1\n\nField2: value2\n");
4403 }
4404
4405 #[test]
4406 fn test_normalize_field_spacing_preserves_comments() {
4407 let input = "# Comment\nField: value\n";
4409 let mut deb822 = input.parse::<Deb822>().unwrap();
4410
4411 deb822.normalize_field_spacing();
4412 assert_eq!(deb822.to_string(), "# Comment\nField: value\n");
4413 }
4414
4415 #[test]
4416 fn test_normalize_field_spacing_preserves_values() {
4417 let input = "Source: foo-bar\nMaintainer:Foo Bar <test@example.com>\n";
4419 let deb822 = input.parse::<Deb822>().unwrap();
4420 let mut para = deb822.paragraphs().next().unwrap();
4421
4422 para.normalize_field_spacing();
4423
4424 assert_eq!(para.get("Source").as_deref(), Some("foo-bar"));
4425 assert_eq!(
4426 para.get("Maintainer").as_deref(),
4427 Some("Foo Bar <test@example.com>")
4428 );
4429 }
4430
4431 #[test]
4432 fn test_normalize_field_spacing_tab_after_colon() {
4433 let input = "Field:\tvalue\n";
4435 let deb822 = input.parse::<Deb822>().unwrap();
4436 let mut para = deb822.paragraphs().next().unwrap();
4437
4438 para.normalize_field_spacing();
4439 assert_eq!(para.to_string(), "Field: value\n");
4440 }
4441
4442 #[test]
4443 fn test_set_preserves_indentation() {
4444 let original = r#"Source: example
4446Build-Depends: foo,
4447 bar,
4448 baz
4449"#;
4450
4451 let mut para: super::Paragraph = original.parse().unwrap();
4452
4453 para.set("Build-Depends", "foo,\nbar,\nbaz");
4455
4456 let expected = r#"Source: example
4458Build-Depends: foo,
4459 bar,
4460 baz
4461"#;
4462 assert_eq!(para.to_string(), expected);
4463 }
4464
4465 #[test]
4466 fn test_set_new_field_detects_field_name_length_indent() {
4467 let original = r#"Source: example
4469Build-Depends: foo,
4470 bar,
4471 baz
4472Depends: lib1,
4473 lib2
4474"#;
4475
4476 let mut para: super::Paragraph = original.parse().unwrap();
4477
4478 para.set("Recommends", "pkg1,\npkg2,\npkg3");
4480
4481 assert!(para
4483 .to_string()
4484 .contains("Recommends: pkg1,\n pkg2,"));
4485 }
4486
4487 #[test]
4488 fn test_set_new_field_detects_fixed_indent() {
4489 let original = r#"Source: example
4491Build-Depends: foo,
4492 bar,
4493 baz
4494Depends: lib1,
4495 lib2
4496"#;
4497
4498 let mut para: super::Paragraph = original.parse().unwrap();
4499
4500 para.set("Recommends", "pkg1,\npkg2,\npkg3");
4502
4503 assert!(para
4505 .to_string()
4506 .contains("Recommends: pkg1,\n pkg2,\n pkg3\n"));
4507 }
4508
4509 #[test]
4510 fn test_set_new_field_no_multiline_fields() {
4511 let original = r#"Source: example
4513Maintainer: Test <test@example.com>
4514"#;
4515
4516 let mut para: super::Paragraph = original.parse().unwrap();
4517
4518 para.set("Depends", "foo,\nbar,\nbaz");
4520
4521 let expected = r#"Source: example
4523Maintainer: Test <test@example.com>
4524Depends: foo,
4525 bar,
4526 baz
4527"#;
4528 assert_eq!(para.to_string(), expected);
4529 }
4530
4531 #[test]
4532 fn test_set_new_field_mixed_indentation() {
4533 let original = r#"Source: example
4535Build-Depends: foo,
4536 bar
4537Depends: lib1,
4538 lib2
4539"#;
4540
4541 let mut para: super::Paragraph = original.parse().unwrap();
4542
4543 para.set("Recommends", "pkg1,\npkg2");
4545
4546 assert!(para
4548 .to_string()
4549 .contains("Recommends: pkg1,\n pkg2\n"));
4550 }
4551
4552 #[test]
4553 fn test_entry_with_indentation() {
4554 let entry = super::Entry::with_indentation("Test-Field", "value1\nvalue2\nvalue3", " ");
4556
4557 assert_eq!(
4558 entry.to_string(),
4559 "Test-Field: value1\n value2\n value3\n"
4560 );
4561 }
4562
4563 #[test]
4564 fn test_set_with_indent_pattern_fixed() {
4565 let original = r#"Source: example
4567Maintainer: Test <test@example.com>
4568"#;
4569
4570 let mut para: super::Paragraph = original.parse().unwrap();
4571
4572 para.set_with_indent_pattern(
4574 "Depends",
4575 "foo,\nbar,\nbaz",
4576 Some(&super::IndentPattern::Fixed(4)),
4577 None,
4578 );
4579
4580 let expected = r#"Source: example
4582Maintainer: Test <test@example.com>
4583Depends: foo,
4584 bar,
4585 baz
4586"#;
4587 assert_eq!(para.to_string(), expected);
4588 }
4589
4590 #[test]
4591 fn test_set_with_indent_pattern_field_name_length() {
4592 let original = r#"Source: example
4594Maintainer: Test <test@example.com>
4595"#;
4596
4597 let mut para: super::Paragraph = original.parse().unwrap();
4598
4599 para.set_with_indent_pattern(
4601 "Build-Depends",
4602 "libfoo,\nlibbar,\nlibbaz",
4603 Some(&super::IndentPattern::FieldNameLength),
4604 None,
4605 );
4606
4607 let expected = r#"Source: example
4609Maintainer: Test <test@example.com>
4610Build-Depends: libfoo,
4611 libbar,
4612 libbaz
4613"#;
4614 assert_eq!(para.to_string(), expected);
4615 }
4616
4617 #[test]
4618 fn test_set_with_indent_pattern_override_auto_detection() {
4619 let original = r#"Source: example
4621Build-Depends: foo,
4622 bar,
4623 baz
4624"#;
4625
4626 let mut para: super::Paragraph = original.parse().unwrap();
4627
4628 para.set_with_indent_pattern(
4630 "Depends",
4631 "lib1,\nlib2,\nlib3",
4632 Some(&super::IndentPattern::Fixed(2)),
4633 None,
4634 );
4635
4636 let expected = r#"Source: example
4638Build-Depends: foo,
4639 bar,
4640 baz
4641Depends: lib1,
4642 lib2,
4643 lib3
4644"#;
4645 assert_eq!(para.to_string(), expected);
4646 }
4647
4648 #[test]
4649 fn test_set_with_indent_pattern_none_auto_detects() {
4650 let original = r#"Source: example
4652Build-Depends: foo,
4653 bar,
4654 baz
4655"#;
4656
4657 let mut para: super::Paragraph = original.parse().unwrap();
4658
4659 para.set_with_indent_pattern("Depends", "lib1,\nlib2", None, None);
4661
4662 let expected = r#"Source: example
4664Build-Depends: foo,
4665 bar,
4666 baz
4667Depends: lib1,
4668 lib2
4669"#;
4670 assert_eq!(para.to_string(), expected);
4671 }
4672
4673 #[test]
4674 fn test_set_with_indent_pattern_with_field_order() {
4675 let original = r#"Source: example
4677Maintainer: Test <test@example.com>
4678"#;
4679
4680 let mut para: super::Paragraph = original.parse().unwrap();
4681
4682 para.set_with_indent_pattern(
4684 "Priority",
4685 "optional",
4686 Some(&super::IndentPattern::Fixed(4)),
4687 Some(&["Source", "Priority", "Maintainer"]),
4688 );
4689
4690 let expected = r#"Source: example
4692Priority: optional
4693Maintainer: Test <test@example.com>
4694"#;
4695 assert_eq!(para.to_string(), expected);
4696 }
4697
4698 #[test]
4699 fn test_set_with_indent_pattern_replace_existing() {
4700 let original = r#"Source: example
4702Depends: foo,
4703 bar
4704"#;
4705
4706 let mut para: super::Paragraph = original.parse().unwrap();
4707
4708 para.set_with_indent_pattern(
4710 "Depends",
4711 "lib1,\nlib2,\nlib3",
4712 Some(&super::IndentPattern::Fixed(3)),
4713 None,
4714 );
4715
4716 let expected = r#"Source: example
4718Depends: lib1,
4719 lib2,
4720 lib3
4721"#;
4722 assert_eq!(para.to_string(), expected);
4723 }
4724
4725 #[test]
4726 fn test_change_field_indent() {
4727 let original = r#"Source: example
4729Depends: foo,
4730 bar,
4731 baz
4732"#;
4733 let mut para: super::Paragraph = original.parse().unwrap();
4734
4735 let result = para
4737 .change_field_indent("Depends", &super::IndentPattern::Fixed(2))
4738 .unwrap();
4739 assert!(result, "Field should have been found and updated");
4740
4741 let expected = r#"Source: example
4742Depends: foo,
4743 bar,
4744 baz
4745"#;
4746 assert_eq!(para.to_string(), expected);
4747 }
4748
4749 #[test]
4750 fn test_change_field_indent_nonexistent() {
4751 let original = r#"Source: example
4753"#;
4754 let mut para: super::Paragraph = original.parse().unwrap();
4755
4756 let result = para
4758 .change_field_indent("Depends", &super::IndentPattern::Fixed(2))
4759 .unwrap();
4760 assert!(!result, "Should return false for non-existent field");
4761
4762 assert_eq!(para.to_string(), original);
4764 }
4765
4766 #[test]
4767 fn test_change_field_indent_case_insensitive() {
4768 let original = r#"Build-Depends: foo,
4770 bar
4771"#;
4772 let mut para: super::Paragraph = original.parse().unwrap();
4773
4774 let result = para
4776 .change_field_indent("build-depends", &super::IndentPattern::Fixed(1))
4777 .unwrap();
4778 assert!(result, "Should find field case-insensitively");
4779
4780 let expected = r#"Build-Depends: foo,
4781 bar
4782"#;
4783 assert_eq!(para.to_string(), expected);
4784 }
4785
4786 #[test]
4787 fn test_entry_get_indent() {
4788 let original = r#"Build-Depends: foo,
4790 bar,
4791 baz
4792"#;
4793 let para: super::Paragraph = original.parse().unwrap();
4794 let entry = para.entries().next().unwrap();
4795
4796 assert_eq!(entry.get_indent(), Some(" ".to_string()));
4797 }
4798
4799 #[test]
4800 fn test_entry_get_indent_single_line() {
4801 let original = r#"Source: example
4803"#;
4804 let para: super::Paragraph = original.parse().unwrap();
4805 let entry = para.entries().next().unwrap();
4806
4807 assert_eq!(entry.get_indent(), None);
4808 }
4809}
4810
4811#[test]
4812fn test_move_paragraph_forward() {
4813 let mut d: Deb822 = vec![
4814 vec![("Foo", "Bar"), ("Baz", "Qux")].into_iter().collect(),
4815 vec![("A", "B"), ("C", "D")].into_iter().collect(),
4816 vec![("X", "Y"), ("Z", "W")].into_iter().collect(),
4817 ]
4818 .into_iter()
4819 .collect();
4820 d.move_paragraph(0, 2);
4821 assert_eq!(
4822 d.to_string(),
4823 "A: B\nC: D\n\nX: Y\nZ: W\n\nFoo: Bar\nBaz: Qux\n"
4824 );
4825}
4826
4827#[test]
4828fn test_move_paragraph_backward() {
4829 let mut d: Deb822 = vec![
4830 vec![("Foo", "Bar"), ("Baz", "Qux")].into_iter().collect(),
4831 vec![("A", "B"), ("C", "D")].into_iter().collect(),
4832 vec![("X", "Y"), ("Z", "W")].into_iter().collect(),
4833 ]
4834 .into_iter()
4835 .collect();
4836 d.move_paragraph(2, 0);
4837 assert_eq!(
4838 d.to_string(),
4839 "X: Y\nZ: W\n\nFoo: Bar\nBaz: Qux\n\nA: B\nC: D\n"
4840 );
4841}
4842
4843#[test]
4844fn test_move_paragraph_middle() {
4845 let mut d: Deb822 = vec![
4846 vec![("Foo", "Bar"), ("Baz", "Qux")].into_iter().collect(),
4847 vec![("A", "B"), ("C", "D")].into_iter().collect(),
4848 vec![("X", "Y"), ("Z", "W")].into_iter().collect(),
4849 ]
4850 .into_iter()
4851 .collect();
4852 d.move_paragraph(2, 1);
4853 assert_eq!(
4854 d.to_string(),
4855 "Foo: Bar\nBaz: Qux\n\nX: Y\nZ: W\n\nA: B\nC: D\n"
4856 );
4857}
4858
4859#[test]
4860fn test_move_paragraph_same_index() {
4861 let mut d: Deb822 = vec![
4862 vec![("Foo", "Bar"), ("Baz", "Qux")].into_iter().collect(),
4863 vec![("A", "B"), ("C", "D")].into_iter().collect(),
4864 ]
4865 .into_iter()
4866 .collect();
4867 let original = d.to_string();
4868 d.move_paragraph(1, 1);
4869 assert_eq!(d.to_string(), original);
4870}
4871
4872#[test]
4873fn test_move_paragraph_single() {
4874 let mut d: Deb822 = vec![vec![("Foo", "Bar")].into_iter().collect()]
4875 .into_iter()
4876 .collect();
4877 let original = d.to_string();
4878 d.move_paragraph(0, 0);
4879 assert_eq!(d.to_string(), original);
4880}
4881
4882#[test]
4883fn test_move_paragraph_invalid_index() {
4884 let mut d: Deb822 = vec![
4885 vec![("Foo", "Bar")].into_iter().collect(),
4886 vec![("A", "B")].into_iter().collect(),
4887 ]
4888 .into_iter()
4889 .collect();
4890 let original = d.to_string();
4891 d.move_paragraph(0, 5);
4892 assert_eq!(d.to_string(), original);
4893}
4894
4895#[test]
4896fn test_move_paragraph_with_comments() {
4897 let text = r#"Foo: Bar
4898
4899# This is a comment
4900
4901A: B
4902
4903X: Y
4904"#;
4905 let mut d: Deb822 = text.parse().unwrap();
4906 d.move_paragraph(0, 2);
4907 assert_eq!(
4908 d.to_string(),
4909 "# This is a comment\n\nA: B\n\nX: Y\n\nFoo: Bar\n"
4910 );
4911}
4912
4913#[test]
4914fn test_case_insensitive_get() {
4915 let text = "Package: test\nVersion: 1.0\n";
4916 let d: Deb822 = text.parse().unwrap();
4917 let p = d.paragraphs().next().unwrap();
4918
4919 assert_eq!(p.get("Package").as_deref(), Some("test"));
4921 assert_eq!(p.get("package").as_deref(), Some("test"));
4922 assert_eq!(p.get("PACKAGE").as_deref(), Some("test"));
4923 assert_eq!(p.get("PaCkAgE").as_deref(), Some("test"));
4924
4925 assert_eq!(p.get("Version").as_deref(), Some("1.0"));
4926 assert_eq!(p.get("version").as_deref(), Some("1.0"));
4927 assert_eq!(p.get("VERSION").as_deref(), Some("1.0"));
4928}
4929
4930#[test]
4931fn test_case_insensitive_set() {
4932 let text = "Package: test\n";
4933 let d: Deb822 = text.parse().unwrap();
4934 let mut p = d.paragraphs().next().unwrap();
4935
4936 p.set("package", "updated");
4938 assert_eq!(p.get("Package").as_deref(), Some("updated"));
4939 assert_eq!(p.get("package").as_deref(), Some("updated"));
4940
4941 p.set("PACKAGE", "updated2");
4943 assert_eq!(p.get("Package").as_deref(), Some("updated2"));
4944
4945 assert_eq!(p.keys().count(), 1);
4947}
4948
4949#[test]
4950fn test_case_insensitive_remove() {
4951 let text = "Package: test\nVersion: 1.0\n";
4952 let d: Deb822 = text.parse().unwrap();
4953 let mut p = d.paragraphs().next().unwrap();
4954
4955 p.remove("package");
4957 assert_eq!(p.get("Package"), None);
4958 assert_eq!(p.get("Version").as_deref(), Some("1.0"));
4959
4960 p.remove("VERSION");
4962 assert_eq!(p.get("Version"), None);
4963
4964 assert_eq!(p.keys().count(), 0);
4966}
4967
4968#[test]
4969fn test_case_preservation() {
4970 let text = "Package: test\n";
4971 let d: Deb822 = text.parse().unwrap();
4972 let mut p = d.paragraphs().next().unwrap();
4973
4974 let original_text = d.to_string();
4976 assert_eq!(original_text, "Package: test\n");
4977
4978 p.set("package", "updated");
4980
4981 let updated_text = d.to_string();
4983 assert_eq!(updated_text, "Package: updated\n");
4984}
4985
4986#[test]
4987fn test_case_insensitive_contains_key() {
4988 let text = "Package: test\n";
4989 let d: Deb822 = text.parse().unwrap();
4990 let p = d.paragraphs().next().unwrap();
4991
4992 assert!(p.contains_key("Package"));
4993 assert!(p.contains_key("package"));
4994 assert!(p.contains_key("PACKAGE"));
4995 assert!(!p.contains_key("NonExistent"));
4996}
4997
4998#[test]
4999fn test_case_insensitive_get_all() {
5000 let text = "Package: test1\npackage: test2\n";
5001 let d: Deb822 = text.parse().unwrap();
5002 let p = d.paragraphs().next().unwrap();
5003
5004 let values: Vec<String> = p.get_all("PACKAGE").collect();
5005 assert_eq!(values, vec!["test1", "test2"]);
5006}
5007
5008#[test]
5009fn test_case_insensitive_rename() {
5010 let text = "Package: test\n";
5011 let d: Deb822 = text.parse().unwrap();
5012 let mut p = d.paragraphs().next().unwrap();
5013
5014 assert!(p.rename("package", "NewName"));
5016 assert_eq!(p.get("NewName").as_deref(), Some("test"));
5017 assert_eq!(p.get("Package"), None);
5018}
5019
5020#[test]
5021fn test_rename_changes_case() {
5022 let text = "Package: test\n";
5023 let d: Deb822 = text.parse().unwrap();
5024 let mut p = d.paragraphs().next().unwrap();
5025
5026 assert!(p.rename("package", "PACKAGE"));
5028
5029 let updated_text = d.to_string();
5031 assert_eq!(updated_text, "PACKAGE: test\n");
5032
5033 assert_eq!(p.get("package").as_deref(), Some("test"));
5035 assert_eq!(p.get("Package").as_deref(), Some("test"));
5036 assert_eq!(p.get("PACKAGE").as_deref(), Some("test"));
5037}
5038
5039#[test]
5040fn test_rename_preserves_indentation_and_whitespace() {
5041 let text =
5044 "Comments: Exceptions\n 1997-1999, 2003 MIT\n License terms\n";
5045 let d: Deb822 = text.parse().unwrap();
5046 let mut p = d.paragraphs().next().unwrap();
5047
5048 assert!(p.rename("Comments", "Comment"));
5049 assert_eq!(
5050 d.to_string(),
5051 "Comment: Exceptions\n 1997-1999, 2003 MIT\n License terms\n"
5052 );
5053}
5054
5055#[test]
5056fn test_rename_in_multi_field_paragraph() {
5057 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";
5060 let d: Deb822 = text.parse().unwrap();
5061 let mut p = d.paragraphs().next().unwrap();
5062
5063 assert!(p.rename("Comments", "Comment"));
5064 assert_eq!(d.to_string(), text.replace("Comments:", "Comment:"));
5065}
5066
5067#[test]
5068fn test_rename_preserves_post_colon_whitespace() {
5069 let text = "Files: install_GUI.sh\n";
5071 let d: Deb822 = text.parse().unwrap();
5072 let mut p = d.paragraphs().next().unwrap();
5073
5074 assert!(p.rename("Files", "File"));
5075 assert_eq!(d.to_string(), "File: install_GUI.sh\n");
5076}
5077
5078#[test]
5079fn test_reject_whitespace_only_continuation_line() {
5080 let text = "Build-Depends:\n \ndebhelper\n";
5086 let parsed = Deb822::parse(text);
5087
5088 assert!(
5091 !parsed.errors().is_empty(),
5092 "Expected parse errors for whitespace-only continuation line"
5093 );
5094}
5095
5096#[test]
5097fn test_reject_empty_continuation_line_in_multiline_field() {
5098 let text = "Depends: foo,\n bar,\n \n baz\n";
5100 let parsed = Deb822::parse(text);
5101
5102 assert!(
5104 !parsed.errors().is_empty(),
5105 "Empty continuation line should generate parse errors"
5106 );
5107
5108 let has_empty_line_error = parsed
5110 .errors()
5111 .iter()
5112 .any(|e| e.contains("empty continuation line"));
5113 assert!(
5114 has_empty_line_error,
5115 "Should have an error about empty continuation line"
5116 );
5117}
5118
5119#[test]
5120#[should_panic(expected = "empty continuation line")]
5121fn test_set_rejects_empty_continuation_lines() {
5122 let text = "Package: test\n";
5124 let deb822 = text.parse::<Deb822>().unwrap();
5125 let mut para = deb822.paragraphs().next().unwrap();
5126
5127 let value_with_empty_line = "foo\n \nbar";
5130 para.set("Depends", value_with_empty_line);
5131}
5132
5133#[test]
5134fn test_try_set_returns_error_for_empty_continuation_lines() {
5135 let text = "Package: test\n";
5137 let deb822 = text.parse::<Deb822>().unwrap();
5138 let mut para = deb822.paragraphs().next().unwrap();
5139
5140 let value_with_empty_line = "foo\n \nbar";
5142 let result = para.try_set("Depends", value_with_empty_line);
5143
5144 assert!(
5146 result.is_err(),
5147 "try_set() should return an error for empty continuation lines"
5148 );
5149
5150 match result {
5152 Err(Error::InvalidValue(msg)) => {
5153 assert!(
5154 msg.contains("empty continuation line"),
5155 "Error message should mention empty continuation line"
5156 );
5157 }
5158 _ => panic!("Expected InvalidValue error"),
5159 }
5160}
5161
5162#[test]
5163fn test_try_set_with_indent_pattern_returns_error() {
5164 let text = "Package: test\n";
5166 let deb822 = text.parse::<Deb822>().unwrap();
5167 let mut para = deb822.paragraphs().next().unwrap();
5168
5169 let value_with_empty_line = "foo\n \nbar";
5170 let result = para.try_set_with_indent_pattern(
5171 "Depends",
5172 value_with_empty_line,
5173 Some(&IndentPattern::Fixed(2)),
5174 None,
5175 );
5176
5177 assert!(
5178 result.is_err(),
5179 "try_set_with_indent_pattern() should return an error"
5180 );
5181}
5182
5183#[test]
5184fn test_try_set_succeeds_for_valid_value() {
5185 let text = "Package: test\n";
5187 let deb822 = text.parse::<Deb822>().unwrap();
5188 let mut para = deb822.paragraphs().next().unwrap();
5189
5190 let valid_value = "foo\nbar";
5192 let result = para.try_set("Depends", valid_value);
5193
5194 assert!(result.is_ok(), "try_set() should succeed for valid values");
5195 assert_eq!(para.get("Depends").as_deref(), Some("foo\nbar"));
5196}
5197
5198#[test]
5199fn test_field_with_empty_first_line() {
5200 let text = "Foo:\n blah\n blah\n";
5203 let parsed = Deb822::parse(text);
5204
5205 assert!(
5207 parsed.errors().is_empty(),
5208 "Empty first line should be valid. Got errors: {:?}",
5209 parsed.errors()
5210 );
5211
5212 let deb822 = parsed.tree();
5213 let para = deb822.paragraphs().next().unwrap();
5214 assert_eq!(para.get("Foo").as_deref(), Some("blah\nblah"));
5215}
5216
5217#[test]
5218fn test_try_set_with_empty_first_line() {
5219 let text = "Package: test\n";
5221 let deb822 = text.parse::<Deb822>().unwrap();
5222 let mut para = deb822.paragraphs().next().unwrap();
5223
5224 let value = "\nblah\nmore";
5226 let result = para.try_set("Depends", value);
5227
5228 assert!(
5229 result.is_ok(),
5230 "try_set() should succeed for values with empty first line. Got: {:?}",
5231 result
5232 );
5233}
5234
5235#[test]
5236fn test_field_with_value_then_empty_continuation() {
5237 let text = "Foo: bar\n \n";
5239 let parsed = Deb822::parse(text);
5240
5241 assert!(
5243 !parsed.errors().is_empty(),
5244 "Field with value then empty continuation line should be rejected"
5245 );
5246
5247 let has_empty_line_error = parsed
5249 .errors()
5250 .iter()
5251 .any(|e| e.contains("empty continuation line"));
5252 assert!(
5253 has_empty_line_error,
5254 "Should have error about empty continuation line"
5255 );
5256}
5257
5258#[test]
5259fn test_substvar_continuation_line() {
5260 let text = "\
5261Package: python3-cryptography
5262Architecture: any
5263Depends: python3-bcrypt,
5264 ${misc:Depends},
5265 ${python3:Depends},
5266 ${shlibs:Depends},
5267Suggests: python-cryptography-doc,
5268 python3-cryptography-vectors,
5269Description: Python library exposing cryptographic recipes and primitives
5270 The cryptography library is designed to be a \"one-stop-shop\" for
5271 all your cryptographic needs in Python.
5272 .
5273 As an alternative to the libraries that came before it, cryptography
5274 tries to address some of the issues with those libraries:
5275 - Lack of PyPy and Python 3 support.
5276 - Lack of maintenance.
5277 - Use of poor implementations of algorithms (i.e. ones with known
5278 side-channel attacks).
5279 - Lack of high level, \"Cryptography for humans\", APIs.
5280 - Absence of algorithms such as AES-GCM.
5281 - Poor introspectability, and thus poor testability.
5282 - Extremely error prone APIs, and bad defaults.
5283";
5284 let parsed = Deb822::parse(text);
5285 for e in parsed.positioned_errors() {
5286 eprintln!("error at {:?}: {}", e.range, e.message);
5287 }
5288 assert!(
5289 parsed.errors().is_empty(),
5290 "Should not produce errors: {:?}",
5291 parsed.errors()
5292 );
5293 assert!(
5294 parsed.positioned_errors().is_empty(),
5295 "Should not produce positioned errors: {:?}",
5296 parsed.positioned_errors()
5297 );
5298}
5299
5300#[test]
5301fn test_line_col() {
5302 let text = r#"Source: foo
5303Maintainer: Foo Bar <jelmer@jelmer.uk>
5304Section: net
5305
5306Package: foo
5307Architecture: all
5308Depends: libc6
5309Description: This is a description
5310 With details
5311"#;
5312 let deb822 = text.parse::<Deb822>().unwrap();
5313
5314 let paras: Vec<_> = deb822.paragraphs().collect();
5316 assert_eq!(paras.len(), 2);
5317
5318 assert_eq!(paras[0].line(), 0);
5320 assert_eq!(paras[0].column(), 0);
5321
5322 assert_eq!(paras[1].line(), 4);
5324 assert_eq!(paras[1].column(), 0);
5325
5326 let entries: Vec<_> = paras[0].entries().collect();
5328 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));
5338 assert_eq!(entries[0].line_col(), (0, 0));
5339
5340 let second_para_entries: Vec<_> = paras[1].entries().collect();
5342 assert_eq!(second_para_entries[3].line(), 7); }
5344
5345#[test]
5346fn test_deb822_snapshot_independence() {
5347 let text = r#"Source: foo
5348Maintainer: Joe <joe@example.com>
5349
5350Package: foo
5351Architecture: all
5352"#;
5353 let deb822 = text.parse::<Deb822>().unwrap();
5354 let snap = deb822.snapshot();
5355 assert!(deb822.tree_eq(&snap));
5356
5357 let mut para = deb822.paragraphs().next().unwrap();
5358 para.set("Source", "modified");
5359
5360 let snap_para = snap.paragraphs().next().unwrap();
5362 assert_eq!(snap_para.get("Source").as_deref(), Some("foo"));
5363 assert!(!deb822.tree_eq(&snap));
5365}
5366
5367#[test]
5368fn test_paragraph_snapshot_independence() {
5369 let text = "Package: foo\nArchitecture: all\n";
5370 let deb822 = text.parse::<Deb822>().unwrap();
5371 let mut para = deb822.paragraphs().next().unwrap();
5372 let snap = para.snapshot();
5373 assert!(para.tree_eq(&snap));
5374
5375 para.set("Package", "modified");
5376 assert_eq!(snap.get("Package").as_deref(), Some("foo"));
5377 assert!(!para.tree_eq(&snap));
5378}
5379
5380#[test]
5381fn test_tree_eq_value_equivalence() {
5382 let text = "Package: foo\nArchitecture: all\n";
5385 let a = text.parse::<Deb822>().unwrap();
5386 let b = text.parse::<Deb822>().unwrap();
5387 assert!(a.tree_eq(&b));
5388 assert!(b.tree_eq(&a));
5389
5390 let c: Deb822 = "Package: bar\n".parse().unwrap();
5392 assert!(!a.tree_eq(&c));
5393}
5394
5395#[test]
5396fn test_entry_snapshot_independence() {
5397 let text = "Package: foo\n";
5398 let deb822 = text.parse::<Deb822>().unwrap();
5399 let mut para = deb822.paragraphs().next().unwrap();
5400 let entry = para.entries().next().unwrap();
5401 let snap = entry.snapshot();
5402 assert!(entry.tree_eq(&snap));
5403
5404 para.set("Package", "modified");
5405 assert_eq!(snap.value(), "foo");
5407}
5408
5409#[test]
5410fn test_paragraph_text_range() {
5411 let text = r#"Source: foo
5413Maintainer: Joe <joe@example.com>
5414
5415Package: foo
5416Architecture: all
5417"#;
5418 let deb822 = text.parse::<Deb822>().unwrap();
5419 let paras: Vec<_> = deb822.paragraphs().collect();
5420
5421 let range1 = paras[0].text_range();
5423 let para1_text = &text[range1.start().into()..range1.end().into()];
5424 assert_eq!(
5425 para1_text,
5426 "Source: foo\nMaintainer: Joe <joe@example.com>\n"
5427 );
5428
5429 let range2 = paras[1].text_range();
5431 let para2_text = &text[range2.start().into()..range2.end().into()];
5432 assert_eq!(para2_text, "Package: foo\nArchitecture: all\n");
5433}
5434
5435#[test]
5436fn test_paragraphs_in_range_single() {
5437 let text = r#"Source: foo
5439
5440Package: bar
5441
5442Package: baz
5443"#;
5444 let deb822 = text.parse::<Deb822>().unwrap();
5445
5446 let first_para = deb822.paragraphs().next().unwrap();
5448 let range = first_para.text_range();
5449
5450 let paras: Vec<_> = deb822.paragraphs_in_range(range).collect();
5452 assert_eq!(paras.len(), 1);
5453 assert_eq!(paras[0].get("Source").as_deref(), Some("foo"));
5454}
5455
5456#[test]
5457fn test_paragraphs_in_range_multiple() {
5458 let text = r#"Source: foo
5460
5461Package: bar
5462
5463Package: baz
5464"#;
5465 let deb822 = text.parse::<Deb822>().unwrap();
5466
5467 let range = rowan::TextRange::new(0.into(), 25.into());
5469
5470 let paras: Vec<_> = deb822.paragraphs_in_range(range).collect();
5472 assert_eq!(paras.len(), 2);
5473 assert_eq!(paras[0].get("Source").as_deref(), Some("foo"));
5474 assert_eq!(paras[1].get("Package").as_deref(), Some("bar"));
5475}
5476
5477#[test]
5478fn test_paragraphs_in_range_partial_overlap() {
5479 let text = r#"Source: foo
5481
5482Package: bar
5483
5484Package: baz
5485"#;
5486 let deb822 = text.parse::<Deb822>().unwrap();
5487
5488 let range = rowan::TextRange::new(15.into(), 30.into());
5490
5491 let paras: Vec<_> = deb822.paragraphs_in_range(range).collect();
5493 assert!(paras.len() >= 1);
5494 assert!(paras
5495 .iter()
5496 .any(|p| p.get("Package").as_deref() == Some("bar")));
5497}
5498
5499#[test]
5500fn test_paragraphs_in_range_no_match() {
5501 let text = r#"Source: foo
5503
5504Package: bar
5505"#;
5506 let deb822 = text.parse::<Deb822>().unwrap();
5507
5508 let range = rowan::TextRange::new(1000.into(), 2000.into());
5510
5511 let paras: Vec<_> = deb822.paragraphs_in_range(range).collect();
5513 assert_eq!(paras.len(), 0);
5514}
5515
5516#[test]
5517fn test_paragraphs_in_range_all() {
5518 let text = r#"Source: foo
5520
5521Package: bar
5522
5523Package: baz
5524"#;
5525 let deb822 = text.parse::<Deb822>().unwrap();
5526
5527 let range = rowan::TextRange::new(0.into(), text.len().try_into().unwrap());
5529
5530 let paras: Vec<_> = deb822.paragraphs_in_range(range).collect();
5532 assert_eq!(paras.len(), 3);
5533}
5534
5535#[test]
5536fn test_paragraph_at_position() {
5537 let text = r#"Package: foo
5539Version: 1.0
5540
5541Package: bar
5542Architecture: all
5543"#;
5544 let deb822 = text.parse::<Deb822>().unwrap();
5545
5546 let para = deb822.paragraph_at_position(rowan::TextSize::from(5));
5548 assert!(para.is_some());
5549 assert_eq!(para.unwrap().get("Package").as_deref(), Some("foo"));
5550
5551 let para = deb822.paragraph_at_position(rowan::TextSize::from(30));
5553 assert!(para.is_some());
5554 assert_eq!(para.unwrap().get("Package").as_deref(), Some("bar"));
5555
5556 let para = deb822.paragraph_at_position(rowan::TextSize::from(1000));
5558 assert!(para.is_none());
5559}
5560
5561#[test]
5562fn test_paragraph_at_line() {
5563 let text = r#"Package: foo
5565Version: 1.0
5566
5567Package: bar
5568Architecture: all
5569"#;
5570 let deb822 = text.parse::<Deb822>().unwrap();
5571
5572 let para = deb822.paragraph_at_line(0);
5574 assert!(para.is_some());
5575 assert_eq!(para.unwrap().get("Package").as_deref(), Some("foo"));
5576
5577 let para = deb822.paragraph_at_line(1);
5579 assert!(para.is_some());
5580 assert_eq!(para.unwrap().get("Package").as_deref(), Some("foo"));
5581
5582 let para = deb822.paragraph_at_line(3);
5584 assert!(para.is_some());
5585 assert_eq!(para.unwrap().get("Package").as_deref(), Some("bar"));
5586
5587 let para = deb822.paragraph_at_line(100);
5589 assert!(para.is_none());
5590}
5591
5592#[test]
5593fn test_entry_at_line_col() {
5594 let text = r#"Package: foo
5596Version: 1.0
5597Architecture: all
5598"#;
5599 let deb822 = text.parse::<Deb822>().unwrap();
5600
5601 let entry = deb822.entry_at_line_col(0, 0);
5603 assert!(entry.is_some());
5604 assert_eq!(entry.unwrap().key(), Some("Package".to_string()));
5605
5606 let entry = deb822.entry_at_line_col(1, 0);
5608 assert!(entry.is_some());
5609 assert_eq!(entry.unwrap().key(), Some("Version".to_string()));
5610
5611 let entry = deb822.entry_at_line_col(2, 5);
5613 assert!(entry.is_some());
5614 assert_eq!(entry.unwrap().key(), Some("Architecture".to_string()));
5615
5616 let entry = deb822.entry_at_line_col(100, 0);
5618 assert!(entry.is_none());
5619}
5620
5621#[test]
5622fn test_entry_at_line_col_multiline() {
5623 let text = r#"Package: foo
5625Description: A package
5626 with a long
5627 description
5628Version: 1.0
5629"#;
5630 let deb822 = text.parse::<Deb822>().unwrap();
5631
5632 let entry = deb822.entry_at_line_col(1, 0);
5634 assert!(entry.is_some());
5635 assert_eq!(entry.unwrap().key(), Some("Description".to_string()));
5636
5637 let entry = deb822.entry_at_line_col(2, 1);
5639 assert!(entry.is_some());
5640 assert_eq!(entry.unwrap().key(), Some("Description".to_string()));
5641
5642 let entry = deb822.entry_at_line_col(3, 1);
5644 assert!(entry.is_some());
5645 assert_eq!(entry.unwrap().key(), Some("Description".to_string()));
5646
5647 let entry = deb822.entry_at_line_col(4, 0);
5649 assert!(entry.is_some());
5650 assert_eq!(entry.unwrap().key(), Some("Version".to_string()));
5651}
5652
5653#[test]
5654fn test_entries_in_range() {
5655 let text = r#"Package: foo
5657Version: 1.0
5658Architecture: all
5659"#;
5660 let deb822 = text.parse::<Deb822>().unwrap();
5661 let para = deb822.paragraphs().next().unwrap();
5662
5663 let first_entry = para.entries().next().unwrap();
5665 let range = first_entry.text_range();
5666
5667 let entries: Vec<_> = para.entries_in_range(range).collect();
5669 assert_eq!(entries.len(), 1);
5670 assert_eq!(entries[0].key(), Some("Package".to_string()));
5671
5672 let range = rowan::TextRange::new(0.into(), 25.into());
5674 let entries: Vec<_> = para.entries_in_range(range).collect();
5675 assert_eq!(entries.len(), 2);
5676 assert_eq!(entries[0].key(), Some("Package".to_string()));
5677 assert_eq!(entries[1].key(), Some("Version".to_string()));
5678}
5679
5680#[test]
5681fn test_entries_in_range_partial_overlap() {
5682 let text = r#"Package: foo
5684Version: 1.0
5685Architecture: all
5686"#;
5687 let deb822 = text.parse::<Deb822>().unwrap();
5688 let para = deb822.paragraphs().next().unwrap();
5689
5690 let range = rowan::TextRange::new(15.into(), 30.into());
5692
5693 let entries: Vec<_> = para.entries_in_range(range).collect();
5694 assert!(entries.len() >= 1);
5695 assert!(entries
5696 .iter()
5697 .any(|e| e.key() == Some("Version".to_string())));
5698}
5699
5700#[test]
5701fn test_entries_in_range_no_match() {
5702 let text = "Package: foo\n";
5704 let deb822 = text.parse::<Deb822>().unwrap();
5705 let para = deb822.paragraphs().next().unwrap();
5706
5707 let range = rowan::TextRange::new(1000.into(), 2000.into());
5709 let entries: Vec<_> = para.entries_in_range(range).collect();
5710 assert_eq!(entries.len(), 0);
5711}
5712
5713#[test]
5714fn test_entry_at_position() {
5715 let text = r#"Package: foo
5717Version: 1.0
5718Architecture: all
5719"#;
5720 let deb822 = text.parse::<Deb822>().unwrap();
5721 let para = deb822.paragraphs().next().unwrap();
5722
5723 let entry = para.entry_at_position(rowan::TextSize::from(5));
5725 assert!(entry.is_some());
5726 assert_eq!(entry.unwrap().key(), Some("Package".to_string()));
5727
5728 let entry = para.entry_at_position(rowan::TextSize::from(15));
5730 assert!(entry.is_some());
5731 assert_eq!(entry.unwrap().key(), Some("Version".to_string()));
5732
5733 let entry = para.entry_at_position(rowan::TextSize::from(1000));
5735 assert!(entry.is_none());
5736}
5737
5738#[test]
5739fn test_entry_at_position_multiline() {
5740 let text = r#"Description: A package
5742 with a long
5743 description
5744"#;
5745 let deb822 = text.parse::<Deb822>().unwrap();
5746 let para = deb822.paragraphs().next().unwrap();
5747
5748 let entry = para.entry_at_position(rowan::TextSize::from(5));
5750 assert!(entry.is_some());
5751 assert_eq!(entry.unwrap().key(), Some("Description".to_string()));
5752
5753 let entry = para.entry_at_position(rowan::TextSize::from(30));
5755 assert!(entry.is_some());
5756 assert_eq!(entry.unwrap().key(), Some("Description".to_string()));
5757}
5758
5759#[test]
5760fn test_paragraph_at_position_at_boundary() {
5761 let text = "Package: foo\n\nPackage: bar\n";
5763 let deb822 = text.parse::<Deb822>().unwrap();
5764
5765 let para = deb822.paragraph_at_position(rowan::TextSize::from(0));
5767 assert!(para.is_some());
5768 assert_eq!(para.unwrap().get("Package").as_deref(), Some("foo"));
5769
5770 let para = deb822.paragraph_at_position(rowan::TextSize::from(15));
5772 assert!(para.is_some());
5773 assert_eq!(para.unwrap().get("Package").as_deref(), Some("bar"));
5774}
5775
5776#[test]
5777fn test_comment_in_multiline_value() {
5778 let text = "\
5781Build-Depends: dh-python,
5782 libsvn-dev,
5783# python-all-dbg (>= 2.6.6-3),
5784 python3-all-dev,
5785# python3-all-dbg,
5786 python3-docutils
5787Standards-Version: 4.7.0
5788";
5789 let deb822 = text.parse::<Deb822>().unwrap();
5790 let para = deb822.paragraphs().next().unwrap();
5791 assert_eq!(
5793 para.get("Build-Depends").as_deref(),
5794 Some("dh-python,\nlibsvn-dev,\npython3-all-dev,\npython3-docutils")
5795 );
5796 assert_eq!(
5798 para.get_with_comments("Build-Depends").as_deref(),
5799 Some("dh-python,\nlibsvn-dev,\n# python-all-dbg (>= 2.6.6-3),\npython3-all-dev,\n# python3-all-dbg,\npython3-docutils")
5800 );
5801 assert_eq!(para.get("Standards-Version").as_deref(), Some("4.7.0"));
5802 assert_eq!(deb822.to_string(), text);
5804}