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 line_col_at_offset(node: &SyntaxNode, offset: rowan::TextSize) -> (usize, usize) {
605 let root = node.ancestors().last().unwrap_or_else(|| node.clone());
606 let mut line = 0;
607 let mut last_newline_offset = rowan::TextSize::from(0);
608
609 for element in root.preorder_with_tokens() {
610 if let rowan::WalkEvent::Enter(rowan::NodeOrToken::Token(token)) = element {
611 if token.text_range().start() >= offset {
612 break;
613 }
614
615 for (idx, _) in token.text().match_indices('\n') {
617 line += 1;
618 last_newline_offset =
619 token.text_range().start() + rowan::TextSize::from((idx + 1) as u32);
620 }
621 }
622 }
623
624 let column: usize = (offset - last_newline_offset).into();
625 (line, column)
626}
627
628macro_rules! ast_node {
629 ($ast:ident, $kind:ident) => {
630 #[doc = "An AST node representing a `"]
631 #[doc = stringify!($ast)]
632 #[doc = "`."]
633 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
634 #[repr(transparent)]
635 pub struct $ast(SyntaxNode);
636 impl $ast {
637 #[allow(unused)]
638 fn cast(node: SyntaxNode) -> Option<Self> {
639 if node.kind() == $kind {
640 Some(Self(node))
641 } else {
642 None
643 }
644 }
645
646 pub fn line(&self) -> usize {
648 line_col_at_offset(&self.0, self.0.text_range().start()).0
649 }
650
651 pub fn column(&self) -> usize {
653 line_col_at_offset(&self.0, self.0.text_range().start()).1
654 }
655
656 pub fn line_col(&self) -> (usize, usize) {
659 line_col_at_offset(&self.0, self.0.text_range().start())
660 }
661 }
662
663 impl AstNode for $ast {
664 type Language = Lang;
665
666 fn can_cast(kind: SyntaxKind) -> bool {
667 kind == $kind
668 }
669
670 fn cast(syntax: SyntaxNode) -> Option<Self> {
671 Self::cast(syntax)
672 }
673
674 fn syntax(&self) -> &SyntaxNode {
675 &self.0
676 }
677 }
678
679 impl std::fmt::Display for $ast {
680 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
681 write!(f, "{}", self.0.text())
682 }
683 }
684 };
685}
686
687ast_node!(Deb822, ROOT);
688ast_node!(Paragraph, PARAGRAPH);
689ast_node!(Entry, ENTRY);
690
691impl Default for Deb822 {
692 fn default() -> Self {
693 Self::new()
694 }
695}
696
697impl Deb822 {
698 pub fn snapshot(&self) -> Self {
723 Deb822(SyntaxNode::new_root_mut(self.0.green().into_owned()))
724 }
725
726 pub fn new() -> Deb822 {
728 let mut builder = GreenNodeBuilder::new();
729
730 builder.start_node(ROOT.into());
731 builder.finish_node();
732 Deb822(SyntaxNode::new_root_mut(builder.finish()))
733 }
734
735 pub fn parse(text: &str) -> crate::Parse<Deb822> {
737 crate::Parse::parse_deb822(text)
738 }
739
740 #[must_use]
756 pub fn wrap_and_sort(
757 &self,
758 sort_paragraphs: Option<&dyn Fn(&Paragraph, &Paragraph) -> std::cmp::Ordering>,
759 wrap_and_sort_paragraph: Option<&dyn Fn(&Paragraph) -> Paragraph>,
760 ) -> Deb822 {
761 let mut builder = GreenNodeBuilder::new();
762 builder.start_node(ROOT.into());
763 let mut current = vec![];
764 let mut paragraphs = vec![];
765 for c in self.0.children_with_tokens() {
766 match c.kind() {
767 PARAGRAPH => {
768 paragraphs.push((
769 current,
770 Paragraph::cast(c.as_node().unwrap().clone()).unwrap(),
771 ));
772 current = vec![];
773 }
774 COMMENT | ERROR => {
775 current.push(c);
776 }
777 EMPTY_LINE => {
778 current.extend(
779 c.as_node()
780 .unwrap()
781 .children_with_tokens()
782 .skip_while(|c| matches!(c.kind(), EMPTY_LINE | NEWLINE | WHITESPACE)),
783 );
784 }
785 _ => {}
786 }
787 }
788 if let Some(sort_paragraph) = sort_paragraphs {
789 paragraphs.sort_by(|a, b| {
790 let a_key = &a.1;
791 let b_key = &b.1;
792 sort_paragraph(a_key, b_key)
793 });
794 }
795
796 for (i, paragraph) in paragraphs.into_iter().enumerate() {
797 if i > 0 {
798 builder.start_node(EMPTY_LINE.into());
799 builder.token(NEWLINE.into(), "\n");
800 builder.finish_node();
801 }
802 for c in paragraph.0.into_iter() {
803 builder.token(c.kind().into(), c.as_token().unwrap().text());
804 }
805 let new_paragraph = if let Some(ref ws) = wrap_and_sort_paragraph {
806 ws(¶graph.1)
807 } else {
808 paragraph.1
809 };
810 inject(&mut builder, new_paragraph.0);
811 }
812
813 for c in current {
814 builder.token(c.kind().into(), c.as_token().unwrap().text());
815 }
816
817 builder.finish_node();
818 Self(SyntaxNode::new_root_mut(builder.finish()))
819 }
820
821 pub fn normalize_field_spacing(&mut self) -> bool {
840 let mut any_changed = false;
841
842 let mut paragraphs: Vec<_> = self.paragraphs().collect();
844
845 for para in &mut paragraphs {
847 if para.normalize_field_spacing() {
848 any_changed = true;
849 }
850 }
851
852 any_changed
853 }
854
855 pub fn paragraphs(&self) -> impl Iterator<Item = Paragraph> {
857 self.0.children().filter_map(Paragraph::cast)
858 }
859
860 pub fn paragraphs_in_range(
886 &self,
887 range: rowan::TextRange,
888 ) -> impl Iterator<Item = Paragraph> + '_ {
889 self.paragraphs().filter(move |p| {
890 let para_range = p.text_range();
891 para_range.start() < range.end() && para_range.end() > range.start()
893 })
894 }
895
896 pub fn paragraph_at_position(&self, offset: rowan::TextSize) -> Option<Paragraph> {
919 self.paragraphs().find(|p| {
920 let range = p.text_range();
921 range.contains(offset)
922 })
923 }
924
925 pub fn paragraph_at_line(&self, line: usize) -> Option<Paragraph> {
948 self.paragraphs().find(|p| {
949 let start_line = p.line();
950 let range = p.text_range();
951 let text_str = self.0.text().to_string();
952 let text_before_end = &text_str[..range.end().into()];
953 let end_line = text_before_end.lines().count().saturating_sub(1);
954 line >= start_line && line <= end_line
955 })
956 }
957
958 pub fn entry_at_line_col(&self, line: usize, col: usize) -> Option<Entry> {
982 let text_str = self.0.text().to_string();
984 let offset: usize = text_str.lines().take(line).map(|l| l.len() + 1).sum();
985 let position = rowan::TextSize::from((offset + col) as u32);
986
987 for para in self.paragraphs() {
989 for entry in para.entries() {
990 let range = entry.text_range();
991 if range.contains(position) {
992 return Some(entry);
993 }
994 }
995 }
996 None
997 }
998
999 fn convert_index(&self, index: usize) -> Option<usize> {
1001 let mut current_pos = 0usize;
1002 if index == 0 {
1003 return Some(0);
1004 }
1005 for (i, node) in self.0.children_with_tokens().enumerate() {
1006 if node.kind() == PARAGRAPH {
1007 if current_pos == index {
1008 return Some(i);
1009 }
1010 current_pos += 1;
1011 }
1012 }
1013
1014 None
1015 }
1016
1017 fn delete_trailing_space(&self, start: usize) {
1019 for (i, node) in self.0.children_with_tokens().enumerate() {
1020 if i < start {
1021 continue;
1022 }
1023 if node.kind() != EMPTY_LINE {
1024 return;
1025 }
1026 self.0.splice_children(start..start + 1, []);
1029 }
1030 }
1031
1032 fn insert_empty_paragraph(&mut self, index: Option<usize>) -> Paragraph {
1034 let paragraph = Paragraph::new();
1035 let mut to_insert = vec![];
1036 if self.0.children().count() > 0 {
1037 let mut builder = GreenNodeBuilder::new();
1038 builder.start_node(EMPTY_LINE.into());
1039 builder.token(NEWLINE.into(), "\n");
1040 builder.finish_node();
1041 to_insert.push(SyntaxNode::new_root_mut(builder.finish()).into());
1042 }
1043 to_insert.push(paragraph.0.clone().into());
1044 let insertion_point = match index {
1045 Some(i) => {
1046 if to_insert.len() > 1 {
1047 to_insert.swap(0, 1);
1048 }
1049 i
1050 }
1051 None => self.0.children().count(),
1052 };
1053 self.0
1054 .splice_children(insertion_point..insertion_point, to_insert);
1055 paragraph
1056 }
1057
1058 pub fn insert_paragraph(&mut self, index: usize) -> Paragraph {
1078 self.insert_empty_paragraph(self.convert_index(index))
1079 }
1080
1081 pub fn remove_paragraph(&mut self, index: usize) {
1099 if let Some(index) = self.convert_index(index) {
1100 self.0.splice_children(index..index + 1, []);
1101 self.delete_trailing_space(index);
1102 }
1103 }
1104
1105 pub fn move_paragraph(&mut self, from_index: usize, to_index: usize) {
1125 if from_index == to_index {
1126 return;
1127 }
1128
1129 let paragraph_count = self.paragraphs().count();
1131 if from_index >= paragraph_count || to_index >= paragraph_count {
1132 return;
1133 }
1134
1135 let paragraph_to_move = self.paragraphs().nth(from_index).unwrap().0.clone();
1137
1138 let from_physical = self.convert_index(from_index).unwrap();
1140
1141 let mut start_idx = from_physical;
1143 if from_physical > 0 {
1144 if let Some(prev_node) = self.0.children_with_tokens().nth(from_physical - 1) {
1145 if prev_node.kind() == EMPTY_LINE {
1146 start_idx = from_physical - 1;
1147 }
1148 }
1149 }
1150
1151 self.0.splice_children(start_idx..from_physical + 1, []);
1153 self.delete_trailing_space(start_idx);
1154
1155 let insert_at = if to_index > from_index {
1159 let target_idx = to_index - 1;
1162 if let Some(target_physical) = self.convert_index(target_idx) {
1163 target_physical + 1
1164 } else {
1165 self.0.children().count()
1167 }
1168 } else {
1169 if let Some(target_physical) = self.convert_index(to_index) {
1172 target_physical
1173 } else {
1174 self.0.children().count()
1175 }
1176 };
1177
1178 let mut to_insert = vec![];
1180
1181 let needs_empty_line_before = if insert_at == 0 {
1183 false
1185 } else if insert_at > 0 {
1186 if let Some(node_at_insert) = self.0.children_with_tokens().nth(insert_at - 1) {
1188 node_at_insert.kind() != EMPTY_LINE
1189 } else {
1190 false
1191 }
1192 } else {
1193 false
1194 };
1195
1196 if needs_empty_line_before {
1197 let mut builder = GreenNodeBuilder::new();
1198 builder.start_node(EMPTY_LINE.into());
1199 builder.token(NEWLINE.into(), "\n");
1200 builder.finish_node();
1201 to_insert.push(SyntaxNode::new_root_mut(builder.finish()).into());
1202 }
1203
1204 to_insert.push(paragraph_to_move.into());
1205
1206 let needs_empty_line_after = if insert_at < self.0.children().count() {
1208 if let Some(node_after) = self.0.children_with_tokens().nth(insert_at) {
1210 node_after.kind() != EMPTY_LINE
1211 } else {
1212 false
1213 }
1214 } else {
1215 false
1216 };
1217
1218 if needs_empty_line_after {
1219 let mut builder = GreenNodeBuilder::new();
1220 builder.start_node(EMPTY_LINE.into());
1221 builder.token(NEWLINE.into(), "\n");
1222 builder.finish_node();
1223 to_insert.push(SyntaxNode::new_root_mut(builder.finish()).into());
1224 }
1225
1226 self.0.splice_children(insert_at..insert_at, to_insert);
1228 }
1229
1230 pub fn add_paragraph(&mut self) -> Paragraph {
1232 self.insert_empty_paragraph(None)
1233 }
1234
1235 pub fn swap_paragraphs(&mut self, index1: usize, index2: usize) {
1265 if index1 == index2 {
1266 return;
1267 }
1268
1269 let mut children: Vec<_> = self.0.children().map(|n| n.clone().into()).collect();
1271
1272 let mut para_child_indices = vec![];
1274 for (child_idx, child) in self.0.children().enumerate() {
1275 if child.kind() == PARAGRAPH {
1276 para_child_indices.push(child_idx);
1277 }
1278 }
1279
1280 if index1 >= para_child_indices.len() {
1282 panic!("index1 {} out of bounds", index1);
1283 }
1284 if index2 >= para_child_indices.len() {
1285 panic!("index2 {} out of bounds", index2);
1286 }
1287
1288 let child_idx1 = para_child_indices[index1];
1289 let child_idx2 = para_child_indices[index2];
1290
1291 children.swap(child_idx1, child_idx2);
1293
1294 let num_children = children.len();
1296 self.0.splice_children(0..num_children, children);
1297 }
1298
1299 pub fn from_file(path: impl AsRef<Path>) -> Result<Self, Error> {
1301 let text = std::fs::read_to_string(path)?;
1302 Ok(Self::from_str(&text)?)
1303 }
1304
1305 pub fn from_file_relaxed(
1307 path: impl AsRef<Path>,
1308 ) -> Result<(Self, Vec<String>), std::io::Error> {
1309 let text = std::fs::read_to_string(path)?;
1310 Ok(Self::from_str_relaxed(&text))
1311 }
1312
1313 pub fn from_str_relaxed(s: &str) -> (Self, Vec<String>) {
1315 let parsed = parse(s);
1316 (parsed.root_mut(), parsed.errors)
1317 }
1318
1319 pub fn read<R: std::io::Read>(mut r: R) -> Result<Self, Error> {
1321 let mut buf = String::new();
1322 r.read_to_string(&mut buf)?;
1323 Ok(Self::from_str(&buf)?)
1324 }
1325
1326 pub fn read_relaxed<R: std::io::Read>(mut r: R) -> Result<(Self, Vec<String>), std::io::Error> {
1328 let mut buf = String::new();
1329 r.read_to_string(&mut buf)?;
1330 Ok(Self::from_str_relaxed(&buf))
1331 }
1332}
1333
1334fn inject(builder: &mut GreenNodeBuilder, node: SyntaxNode) {
1335 builder.start_node(node.kind().into());
1336 for child in node.children_with_tokens() {
1337 match child {
1338 rowan::NodeOrToken::Node(child) => {
1339 inject(builder, child);
1340 }
1341 rowan::NodeOrToken::Token(token) => {
1342 builder.token(token.kind().into(), token.text());
1343 }
1344 }
1345 }
1346 builder.finish_node();
1347}
1348
1349impl FromIterator<Paragraph> for Deb822 {
1350 fn from_iter<T: IntoIterator<Item = Paragraph>>(iter: T) -> Self {
1351 let mut builder = GreenNodeBuilder::new();
1352 builder.start_node(ROOT.into());
1353 for (i, paragraph) in iter.into_iter().enumerate() {
1354 if i > 0 {
1355 builder.start_node(EMPTY_LINE.into());
1356 builder.token(NEWLINE.into(), "\n");
1357 builder.finish_node();
1358 }
1359 inject(&mut builder, paragraph.0);
1360 }
1361 builder.finish_node();
1362 Self(SyntaxNode::new_root_mut(builder.finish()))
1363 }
1364}
1365
1366impl From<Vec<(String, String)>> for Paragraph {
1367 fn from(v: Vec<(String, String)>) -> Self {
1368 v.into_iter().collect()
1369 }
1370}
1371
1372impl From<Vec<(&str, &str)>> for Paragraph {
1373 fn from(v: Vec<(&str, &str)>) -> Self {
1374 v.into_iter().collect()
1375 }
1376}
1377
1378impl FromIterator<(String, String)> for Paragraph {
1379 fn from_iter<T: IntoIterator<Item = (String, String)>>(iter: T) -> Self {
1380 let mut builder = GreenNodeBuilder::new();
1381 builder.start_node(PARAGRAPH.into());
1382 for (key, value) in iter {
1383 builder.start_node(ENTRY.into());
1384 builder.token(KEY.into(), &key);
1385 builder.token(COLON.into(), ":");
1386 builder.token(WHITESPACE.into(), " ");
1387 for (i, line) in value.split('\n').enumerate() {
1388 if i > 0 {
1389 builder.token(INDENT.into(), " ");
1390 }
1391 builder.token(VALUE.into(), line);
1392 builder.token(NEWLINE.into(), "\n");
1393 }
1394 builder.finish_node();
1395 }
1396 builder.finish_node();
1397 Self(SyntaxNode::new_root_mut(builder.finish()))
1398 }
1399}
1400
1401impl<'a> FromIterator<(&'a str, &'a str)> for Paragraph {
1402 fn from_iter<T: IntoIterator<Item = (&'a str, &'a str)>>(iter: T) -> Self {
1403 let mut builder = GreenNodeBuilder::new();
1404 builder.start_node(PARAGRAPH.into());
1405 for (key, value) in iter {
1406 builder.start_node(ENTRY.into());
1407 builder.token(KEY.into(), key);
1408 builder.token(COLON.into(), ":");
1409 builder.token(WHITESPACE.into(), " ");
1410 for (i, line) in value.split('\n').enumerate() {
1411 if i > 0 {
1412 builder.token(INDENT.into(), " ");
1413 }
1414 builder.token(VALUE.into(), line);
1415 builder.token(NEWLINE.into(), "\n");
1416 }
1417 builder.finish_node();
1418 }
1419 builder.finish_node();
1420 Self(SyntaxNode::new_root_mut(builder.finish()))
1421 }
1422}
1423
1424#[derive(Debug, Clone, PartialEq, Eq)]
1426pub enum IndentPattern {
1427 Fixed(usize),
1429 FieldNameLength,
1431}
1432
1433impl IndentPattern {
1434 fn to_string(&self, field_name: &str) -> String {
1436 match self {
1437 IndentPattern::Fixed(spaces) => " ".repeat(*spaces),
1438 IndentPattern::FieldNameLength => " ".repeat(field_name.len() + 2),
1439 }
1440 }
1441}
1442
1443impl Paragraph {
1444 pub fn new() -> Paragraph {
1446 let mut builder = GreenNodeBuilder::new();
1447
1448 builder.start_node(PARAGRAPH.into());
1449 builder.finish_node();
1450 Paragraph(SyntaxNode::new_root_mut(builder.finish()))
1451 }
1452
1453 pub fn snapshot(&self) -> Self {
1462 Paragraph(SyntaxNode::new_root_mut(self.0.green().into_owned()))
1463 }
1464
1465 pub fn text_range(&self) -> rowan::TextRange {
1467 self.0.text_range()
1468 }
1469
1470 pub fn entries_in_range(&self, range: rowan::TextRange) -> impl Iterator<Item = Entry> + '_ {
1497 self.entries().filter(move |e| {
1498 let entry_range = e.text_range();
1499 entry_range.start() < range.end() && entry_range.end() > range.start()
1501 })
1502 }
1503
1504 pub fn entry_at_position(&self, offset: rowan::TextSize) -> Option<Entry> {
1528 self.entries().find(|e| {
1529 let range = e.text_range();
1530 range.contains(offset)
1531 })
1532 }
1533
1534 #[must_use]
1546 pub fn wrap_and_sort(
1547 &self,
1548 indentation: Indentation,
1549 immediate_empty_line: bool,
1550 max_line_length_one_liner: Option<usize>,
1551 sort_entries: Option<&dyn Fn(&Entry, &Entry) -> std::cmp::Ordering>,
1552 format_value: Option<&dyn Fn(&str, &str) -> String>,
1553 ) -> Paragraph {
1554 let mut builder = GreenNodeBuilder::new();
1555
1556 let mut current = vec![];
1557 let mut entries = vec![];
1558
1559 builder.start_node(PARAGRAPH.into());
1560 for c in self.0.children_with_tokens() {
1561 match c.kind() {
1562 ENTRY => {
1563 entries.push((current, Entry::cast(c.as_node().unwrap().clone()).unwrap()));
1564 current = vec![];
1565 }
1566 ERROR | COMMENT => {
1567 current.push(c);
1568 }
1569 _ => {}
1570 }
1571 }
1572
1573 if let Some(sort_entry) = sort_entries {
1574 entries.sort_by(|a, b| {
1575 let a_key = &a.1;
1576 let b_key = &b.1;
1577 sort_entry(a_key, b_key)
1578 });
1579 }
1580
1581 for (pre, entry) in entries.into_iter() {
1582 for c in pre.into_iter() {
1583 builder.token(c.kind().into(), c.as_token().unwrap().text());
1584 }
1585
1586 inject(
1587 &mut builder,
1588 entry
1589 .wrap_and_sort(
1590 indentation,
1591 immediate_empty_line,
1592 max_line_length_one_liner,
1593 format_value,
1594 )
1595 .0,
1596 );
1597 }
1598
1599 for c in current {
1600 builder.token(c.kind().into(), c.as_token().unwrap().text());
1601 }
1602
1603 builder.finish_node();
1604 Self(SyntaxNode::new_root_mut(builder.finish()))
1605 }
1606
1607 pub fn normalize_field_spacing(&mut self) -> bool {
1627 let mut any_changed = false;
1628
1629 let mut entries: Vec<_> = self.entries().collect();
1631
1632 for entry in &mut entries {
1634 if entry.normalize_field_spacing() {
1635 any_changed = true;
1636 }
1637 }
1638
1639 any_changed
1640 }
1641
1642 pub fn get(&self, key: &str) -> Option<String> {
1646 self.entries()
1647 .find(|e| {
1648 e.key()
1649 .as_deref()
1650 .is_some_and(|k| k.eq_ignore_ascii_case(key))
1651 })
1652 .map(|e| e.value())
1653 }
1654
1655 pub fn get_with_comments(&self, key: &str) -> Option<String> {
1663 self.entries()
1664 .find(|e| {
1665 e.key()
1666 .as_deref()
1667 .is_some_and(|k| k.eq_ignore_ascii_case(key))
1668 })
1669 .map(|e| e.value_with_comments())
1670 }
1671
1672 pub fn get_entry(&self, key: &str) -> Option<Entry> {
1676 self.entries().find(|e| {
1677 e.key()
1678 .as_deref()
1679 .is_some_and(|k| k.eq_ignore_ascii_case(key))
1680 })
1681 }
1682
1683 pub fn get_with_indent(&self, key: &str, indent_pattern: &IndentPattern) -> Option<String> {
1710 use crate::lex::SyntaxKind::{INDENT, VALUE};
1711
1712 self.entries()
1713 .find(|e| {
1714 e.key()
1715 .as_deref()
1716 .is_some_and(|k| k.eq_ignore_ascii_case(key))
1717 })
1718 .and_then(|e| {
1719 let field_key = e.key()?;
1720 let expected_indent = indent_pattern.to_string(&field_key);
1721 let expected_len = expected_indent.len();
1722
1723 let mut result = String::new();
1724 let mut first = true;
1725 let mut last_indent: Option<String> = None;
1726
1727 for token in e.0.children_with_tokens().filter_map(|it| it.into_token()) {
1728 match token.kind() {
1729 INDENT => {
1730 last_indent = Some(token.text().to_string());
1731 }
1732 VALUE => {
1733 if !first {
1734 result.push('\n');
1735 if let Some(ref indent_text) = last_indent {
1737 if indent_text.len() > expected_len {
1738 result.push_str(&indent_text[expected_len..]);
1739 }
1740 }
1741 }
1742 result.push_str(token.text());
1743 first = false;
1744 last_indent = None;
1745 }
1746 _ => {}
1747 }
1748 }
1749
1750 Some(result)
1751 })
1752 }
1753
1754 pub fn get_multiline(&self, key: &str) -> Option<String> {
1781 self.get_with_indent(key, &IndentPattern::Fixed(1))
1782 }
1783
1784 pub fn set_multiline(
1810 &mut self,
1811 key: &str,
1812 value: &str,
1813 field_order: Option<&[&str]>,
1814 ) -> Result<(), Error> {
1815 self.try_set_with_forced_indent(key, value, &IndentPattern::Fixed(1), field_order)
1816 }
1817
1818 pub fn contains_key(&self, key: &str) -> bool {
1820 self.get(key).is_some()
1821 }
1822
1823 pub fn entries(&self) -> impl Iterator<Item = Entry> + '_ {
1825 self.0.children().filter_map(Entry::cast)
1826 }
1827
1828 pub fn items(&self) -> impl Iterator<Item = (String, String)> + '_ {
1830 self.entries()
1831 .filter_map(|e| e.key().map(|k| (k, e.value())))
1832 }
1833
1834 pub fn get_all<'a>(&'a self, key: &'a str) -> impl Iterator<Item = String> + 'a {
1838 self.items().filter_map(move |(k, v)| {
1839 if k.eq_ignore_ascii_case(key) {
1840 Some(v)
1841 } else {
1842 None
1843 }
1844 })
1845 }
1846
1847 pub fn keys(&self) -> impl Iterator<Item = String> + '_ {
1849 self.entries().filter_map(|e| e.key())
1850 }
1851
1852 pub fn remove(&mut self, key: &str) {
1856 for mut entry in self.entries() {
1857 if entry
1858 .key()
1859 .as_deref()
1860 .is_some_and(|k| k.eq_ignore_ascii_case(key))
1861 {
1862 entry.detach();
1863 }
1864 }
1865 }
1866
1867 pub fn insert(&mut self, key: &str, value: &str) {
1869 let entry = Entry::new(key, value);
1870 let count = self.0.children_with_tokens().count();
1871 self.0.splice_children(count..count, vec![entry.0.into()]);
1872 }
1873
1874 pub fn insert_comment_before(&mut self, comment: &str) {
1893 use rowan::GreenNodeBuilder;
1894
1895 let mut builder = GreenNodeBuilder::new();
1898 builder.start_node(EMPTY_LINE.into());
1899 builder.token(COMMENT.into(), &format!("# {}", comment));
1900 builder.token(NEWLINE.into(), "\n");
1901 builder.finish_node();
1902 let green = builder.finish();
1903
1904 let comment_node = SyntaxNode::new_root_mut(green);
1906
1907 let index = self.0.index();
1908 let parent = self.0.parent().expect("Paragraph must have a parent");
1909 parent.splice_children(index..index, vec![comment_node.into()]);
1910 }
1911
1912 fn detect_indent_pattern(&self) -> IndentPattern {
1920 let indent_data: Vec<(String, usize)> = self
1922 .entries()
1923 .filter_map(|entry| {
1924 let field_key = entry.key()?;
1925 let indent = entry.get_indent()?;
1926 Some((field_key, indent.len()))
1927 })
1928 .collect();
1929
1930 if indent_data.is_empty() {
1931 return IndentPattern::FieldNameLength;
1933 }
1934
1935 let first_indent_len = indent_data[0].1;
1937 let all_same = indent_data.iter().all(|(_, len)| *len == first_indent_len);
1938
1939 if all_same {
1940 return IndentPattern::Fixed(first_indent_len);
1942 }
1943
1944 let all_match_field_length = indent_data
1946 .iter()
1947 .all(|(field_key, indent_len)| *indent_len == field_key.len() + 2);
1948
1949 if all_match_field_length {
1950 return IndentPattern::FieldNameLength;
1952 }
1953
1954 IndentPattern::FieldNameLength
1956 }
1957
1958 pub fn try_set(&mut self, key: &str, value: &str) -> Result<(), Error> {
1963 self.try_set_with_indent_pattern(key, value, None, None)
1964 }
1965
1966 pub fn set(&mut self, key: &str, value: &str) {
1971 self.try_set(key, value)
1972 .expect("Invalid value: empty continuation line")
1973 }
1974
1975 pub fn set_with_field_order(&mut self, key: &str, value: &str, field_order: &[&str]) {
1977 self.try_set_with_indent_pattern(key, value, None, Some(field_order))
1978 .expect("Invalid value: empty continuation line")
1979 }
1980
1981 pub fn try_set_with_indent_pattern(
1998 &mut self,
1999 key: &str,
2000 value: &str,
2001 default_indent_pattern: Option<&IndentPattern>,
2002 field_order: Option<&[&str]>,
2003 ) -> Result<(), Error> {
2004 let existing_entry = self.entries().find(|entry| {
2006 entry
2007 .key()
2008 .as_deref()
2009 .is_some_and(|k| k.eq_ignore_ascii_case(key))
2010 });
2011
2012 let indent = existing_entry
2014 .as_ref()
2015 .and_then(|entry| entry.get_indent())
2016 .unwrap_or_else(|| {
2017 if let Some(pattern) = default_indent_pattern {
2019 pattern.to_string(key)
2020 } else {
2021 self.detect_indent_pattern().to_string(key)
2022 }
2023 });
2024
2025 let post_colon_ws = existing_entry
2026 .as_ref()
2027 .and_then(|entry| entry.get_post_colon_whitespace())
2028 .unwrap_or_else(|| " ".to_string());
2029
2030 let actual_key = existing_entry
2032 .as_ref()
2033 .and_then(|e| e.key())
2034 .unwrap_or_else(|| key.to_string());
2035
2036 let new_entry = Entry::try_with_formatting(&actual_key, value, &post_colon_ws, &indent)?;
2037
2038 for entry in self.entries() {
2040 if entry
2041 .key()
2042 .as_deref()
2043 .is_some_and(|k| k.eq_ignore_ascii_case(key))
2044 {
2045 self.0.splice_children(
2046 entry.0.index()..entry.0.index() + 1,
2047 vec![new_entry.0.into()],
2048 );
2049 return Ok(());
2050 }
2051 }
2052
2053 if let Some(order) = field_order {
2055 let insertion_index = self.find_insertion_index(key, order);
2056 self.0
2057 .splice_children(insertion_index..insertion_index, vec![new_entry.0.into()]);
2058 } else {
2059 let insertion_index = self.0.children_with_tokens().count();
2061 self.0
2062 .splice_children(insertion_index..insertion_index, vec![new_entry.0.into()]);
2063 }
2064 Ok(())
2065 }
2066
2067 pub fn set_with_indent_pattern(
2084 &mut self,
2085 key: &str,
2086 value: &str,
2087 default_indent_pattern: Option<&IndentPattern>,
2088 field_order: Option<&[&str]>,
2089 ) {
2090 self.try_set_with_indent_pattern(key, value, default_indent_pattern, field_order)
2091 .expect("Invalid value: empty continuation line")
2092 }
2093
2094 pub fn try_set_with_forced_indent(
2108 &mut self,
2109 key: &str,
2110 value: &str,
2111 indent_pattern: &IndentPattern,
2112 field_order: Option<&[&str]>,
2113 ) -> Result<(), Error> {
2114 let existing_entry = self.entries().find(|entry| {
2116 entry
2117 .key()
2118 .as_deref()
2119 .is_some_and(|k| k.eq_ignore_ascii_case(key))
2120 });
2121
2122 let post_colon_ws = existing_entry
2124 .as_ref()
2125 .and_then(|entry| entry.get_post_colon_whitespace())
2126 .unwrap_or_else(|| " ".to_string());
2127
2128 let actual_key = existing_entry
2130 .as_ref()
2131 .and_then(|e| e.key())
2132 .unwrap_or_else(|| key.to_string());
2133
2134 let indent = indent_pattern.to_string(&actual_key);
2136 let new_entry = Entry::try_with_formatting(&actual_key, value, &post_colon_ws, &indent)?;
2137
2138 for entry in self.entries() {
2140 if entry
2141 .key()
2142 .as_deref()
2143 .is_some_and(|k| k.eq_ignore_ascii_case(key))
2144 {
2145 self.0.splice_children(
2146 entry.0.index()..entry.0.index() + 1,
2147 vec![new_entry.0.into()],
2148 );
2149 return Ok(());
2150 }
2151 }
2152
2153 if let Some(order) = field_order {
2155 let insertion_index = self.find_insertion_index(key, order);
2156 self.0
2157 .splice_children(insertion_index..insertion_index, vec![new_entry.0.into()]);
2158 } else {
2159 let insertion_index = self.0.children_with_tokens().count();
2161 self.0
2162 .splice_children(insertion_index..insertion_index, vec![new_entry.0.into()]);
2163 }
2164 Ok(())
2165 }
2166
2167 pub fn set_with_forced_indent(
2181 &mut self,
2182 key: &str,
2183 value: &str,
2184 indent_pattern: &IndentPattern,
2185 field_order: Option<&[&str]>,
2186 ) {
2187 self.try_set_with_forced_indent(key, value, indent_pattern, field_order)
2188 .expect("Invalid value: empty continuation line")
2189 }
2190
2191 pub fn change_field_indent(
2207 &mut self,
2208 key: &str,
2209 indent_pattern: &IndentPattern,
2210 ) -> Result<bool, Error> {
2211 let existing_entry = self.entries().find(|entry| {
2213 entry
2214 .key()
2215 .as_deref()
2216 .is_some_and(|k| k.eq_ignore_ascii_case(key))
2217 });
2218
2219 if let Some(entry) = existing_entry {
2220 let value = entry.value();
2221 let actual_key = entry.key().unwrap_or_else(|| key.to_string());
2222
2223 let post_colon_ws = entry
2225 .get_post_colon_whitespace()
2226 .unwrap_or_else(|| " ".to_string());
2227
2228 let indent = indent_pattern.to_string(&actual_key);
2230 let new_entry =
2231 Entry::try_with_formatting(&actual_key, &value, &post_colon_ws, &indent)?;
2232
2233 self.0.splice_children(
2235 entry.0.index()..entry.0.index() + 1,
2236 vec![new_entry.0.into()],
2237 );
2238 Ok(true)
2239 } else {
2240 Ok(false)
2241 }
2242 }
2243
2244 fn find_insertion_index(&self, key: &str, field_order: &[&str]) -> usize {
2246 let new_field_position = field_order
2248 .iter()
2249 .position(|&field| field.eq_ignore_ascii_case(key));
2250
2251 let mut insertion_index = self.0.children_with_tokens().count();
2252
2253 for (i, child) in self.0.children_with_tokens().enumerate() {
2255 if let Some(node) = child.as_node() {
2256 if let Some(entry) = Entry::cast(node.clone()) {
2257 if let Some(existing_key) = entry.key() {
2258 let existing_position = field_order
2259 .iter()
2260 .position(|&field| field.eq_ignore_ascii_case(&existing_key));
2261
2262 match (new_field_position, existing_position) {
2263 (Some(new_pos), Some(existing_pos)) => {
2265 if new_pos < existing_pos {
2266 insertion_index = i;
2267 break;
2268 }
2269 }
2270 (Some(_), None) => {
2272 }
2274 (None, Some(_)) => {
2276 }
2278 (None, None) => {
2280 if key < existing_key.as_str() {
2281 insertion_index = i;
2282 break;
2283 }
2284 }
2285 }
2286 }
2287 }
2288 }
2289 }
2290
2291 if new_field_position.is_some() && insertion_index == self.0.children_with_tokens().count()
2294 {
2295 let children: Vec<_> = self.0.children_with_tokens().enumerate().collect();
2297 for (i, child) in children.into_iter().rev() {
2298 if let Some(node) = child.as_node() {
2299 if let Some(entry) = Entry::cast(node.clone()) {
2300 if let Some(existing_key) = entry.key() {
2301 if field_order
2302 .iter()
2303 .any(|&f| f.eq_ignore_ascii_case(&existing_key))
2304 {
2305 insertion_index = i + 1;
2307 break;
2308 }
2309 }
2310 }
2311 }
2312 }
2313 }
2314
2315 insertion_index
2316 }
2317
2318 pub fn rename(&mut self, old_key: &str, new_key: &str) -> bool {
2324 for entry in self.entries() {
2325 if entry
2326 .key()
2327 .as_deref()
2328 .is_some_and(|k| k.eq_ignore_ascii_case(old_key))
2329 {
2330 let key_index = entry
2331 .0
2332 .children_with_tokens()
2333 .position(|it| it.as_token().is_some_and(|t| t.kind() == KEY));
2334 if let Some(key_index) = key_index {
2335 let new_token =
2336 rowan::NodeOrToken::Token(rowan::GreenToken::new(KEY.into(), new_key));
2337 let new_green = entry
2338 .0
2339 .green()
2340 .splice_children(key_index..key_index + 1, vec![new_token]);
2341 let parent = entry.0.parent().expect("Entry must have a parent");
2342 parent.splice_children(
2343 entry.0.index()..entry.0.index() + 1,
2344 vec![SyntaxNode::new_root_mut(new_green).into()],
2345 );
2346 return true;
2347 }
2348 }
2349 }
2350 false
2351 }
2352}
2353
2354impl Default for Paragraph {
2355 fn default() -> Self {
2356 Self::new()
2357 }
2358}
2359
2360impl std::str::FromStr for Paragraph {
2361 type Err = ParseError;
2362
2363 fn from_str(text: &str) -> Result<Self, Self::Err> {
2364 let deb822 = Deb822::from_str(text)?;
2365
2366 let mut paragraphs = deb822.paragraphs();
2367
2368 paragraphs
2369 .next()
2370 .ok_or_else(|| ParseError(vec!["no paragraphs".to_string()]))
2371 }
2372}
2373
2374#[cfg(feature = "python-debian")]
2375impl<'py> pyo3::IntoPyObject<'py> for Paragraph {
2376 type Target = pyo3::PyAny;
2377 type Output = pyo3::Bound<'py, Self::Target>;
2378 type Error = pyo3::PyErr;
2379
2380 fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
2381 use pyo3::prelude::*;
2382 let d = pyo3::types::PyDict::new(py);
2383 for (k, v) in self.items() {
2384 d.set_item(k, v)?;
2385 }
2386 let m = py.import("debian.deb822")?;
2387 let cls = m.getattr("Deb822")?;
2388 cls.call1((d,))
2389 }
2390}
2391
2392#[cfg(feature = "python-debian")]
2393impl<'py> pyo3::IntoPyObject<'py> for &Paragraph {
2394 type Target = pyo3::PyAny;
2395 type Output = pyo3::Bound<'py, Self::Target>;
2396 type Error = pyo3::PyErr;
2397
2398 fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
2399 use pyo3::prelude::*;
2400 let d = pyo3::types::PyDict::new(py);
2401 for (k, v) in self.items() {
2402 d.set_item(k, v)?;
2403 }
2404 let m = py.import("debian.deb822")?;
2405 let cls = m.getattr("Deb822")?;
2406 cls.call1((d,))
2407 }
2408}
2409
2410#[cfg(feature = "python-debian")]
2411impl<'py> pyo3::FromPyObject<'_, 'py> for Paragraph {
2412 type Error = pyo3::PyErr;
2413
2414 fn extract(obj: pyo3::Borrowed<'_, 'py, pyo3::PyAny>) -> Result<Self, Self::Error> {
2415 use pyo3::types::PyAnyMethods;
2416 let d = obj.call_method0("__str__")?.extract::<String>()?;
2417 Paragraph::from_str(&d)
2418 .map_err(|e| pyo3::exceptions::PyValueError::new_err((e.to_string(),)))
2419 }
2420}
2421
2422impl Entry {
2423 pub fn snapshot(&self) -> Self {
2432 Entry(SyntaxNode::new_root_mut(self.0.green().into_owned()))
2433 }
2434
2435 pub fn text_range(&self) -> rowan::TextRange {
2437 self.0.text_range()
2438 }
2439
2440 pub fn key_range(&self) -> Option<rowan::TextRange> {
2442 self.0
2443 .children_with_tokens()
2444 .filter_map(|it| it.into_token())
2445 .find(|it| it.kind() == KEY)
2446 .map(|it| it.text_range())
2447 }
2448
2449 pub fn colon_range(&self) -> Option<rowan::TextRange> {
2451 self.0
2452 .children_with_tokens()
2453 .filter_map(|it| it.into_token())
2454 .find(|it| it.kind() == COLON)
2455 .map(|it| it.text_range())
2456 }
2457
2458 pub fn value_range(&self) -> Option<rowan::TextRange> {
2461 let value_tokens: Vec<_> = self
2462 .0
2463 .children_with_tokens()
2464 .filter_map(|it| it.into_token())
2465 .filter(|it| it.kind() == VALUE)
2466 .collect();
2467
2468 if value_tokens.is_empty() {
2469 return None;
2470 }
2471
2472 let first = value_tokens.first().unwrap();
2473 let last = value_tokens.last().unwrap();
2474 Some(rowan::TextRange::new(
2475 first.text_range().start(),
2476 last.text_range().end(),
2477 ))
2478 }
2479
2480 pub fn value_line_ranges(&self) -> Vec<rowan::TextRange> {
2483 self.0
2484 .children_with_tokens()
2485 .filter_map(|it| it.into_token())
2486 .filter(|it| it.kind() == VALUE)
2487 .map(|it| it.text_range())
2488 .collect()
2489 }
2490
2491 pub fn new(key: &str, value: &str) -> Entry {
2493 Self::with_indentation(key, value, " ")
2494 }
2495
2496 pub fn with_indentation(key: &str, value: &str, indent: &str) -> Entry {
2503 Entry::with_formatting(key, value, " ", indent)
2504 }
2505
2506 pub fn try_with_formatting(
2517 key: &str,
2518 value: &str,
2519 post_colon_ws: &str,
2520 indent: &str,
2521 ) -> Result<Entry, Error> {
2522 let mut builder = GreenNodeBuilder::new();
2523
2524 builder.start_node(ENTRY.into());
2525 builder.token(KEY.into(), key);
2526 builder.token(COLON.into(), ":");
2527
2528 let mut i = 0;
2530 while i < post_colon_ws.len() {
2531 if post_colon_ws[i..].starts_with('\n') {
2532 builder.token(NEWLINE.into(), "\n");
2533 i += 1;
2534 } else {
2535 let start = i;
2537 while i < post_colon_ws.len() && !post_colon_ws[i..].starts_with('\n') {
2538 i += post_colon_ws[i..].chars().next().unwrap().len_utf8();
2539 }
2540 builder.token(WHITESPACE.into(), &post_colon_ws[start..i]);
2541 }
2542 }
2543
2544 for (line_idx, line) in value.split('\n').enumerate() {
2545 if line_idx > 0 {
2546 if line.trim().is_empty() {
2549 return Err(Error::InvalidValue(format!(
2550 "empty continuation line (line with only whitespace) at line {}",
2551 line_idx + 1
2552 )));
2553 }
2554 builder.token(INDENT.into(), indent);
2555 }
2556 builder.token(VALUE.into(), line);
2557 builder.token(NEWLINE.into(), "\n");
2558 }
2559 builder.finish_node();
2560 Ok(Entry(SyntaxNode::new_root_mut(builder.finish())))
2561 }
2562
2563 pub fn with_formatting(key: &str, value: &str, post_colon_ws: &str, indent: &str) -> Entry {
2574 Self::try_with_formatting(key, value, post_colon_ws, indent)
2575 .expect("Invalid value: empty continuation line")
2576 }
2577
2578 #[must_use]
2579 pub fn wrap_and_sort(
2592 &self,
2593 mut indentation: Indentation,
2594 immediate_empty_line: bool,
2595 max_line_length_one_liner: Option<usize>,
2596 format_value: Option<&dyn Fn(&str, &str) -> String>,
2597 ) -> Entry {
2598 let mut builder = GreenNodeBuilder::new();
2599
2600 let mut content = vec![];
2601 builder.start_node(ENTRY.into());
2602 for c in self.0.children_with_tokens() {
2603 let text = c.as_token().map(|t| t.text());
2604 match c.kind() {
2605 KEY => {
2606 builder.token(KEY.into(), text.unwrap());
2607 if indentation == Indentation::FieldNameLength {
2608 indentation = Indentation::Spaces(text.unwrap().len() as u32);
2609 }
2610 }
2611 COLON => {
2612 builder.token(COLON.into(), ":");
2613 }
2614 INDENT => {
2615 }
2617 ERROR | COMMENT | VALUE | WHITESPACE | NEWLINE => {
2618 content.push(c);
2619 }
2620 EMPTY_LINE | ENTRY | ROOT | PARAGRAPH => unreachable!(),
2621 }
2622 }
2623
2624 let indentation = if let crate::Indentation::Spaces(i) = indentation {
2625 i
2626 } else {
2627 1
2628 };
2629
2630 assert!(indentation > 0);
2631
2632 while let Some(c) = content.last() {
2634 if c.kind() == NEWLINE || c.kind() == WHITESPACE {
2635 content.pop();
2636 } else {
2637 break;
2638 }
2639 }
2640
2641 let tokens = if let Some(ref format_value) = format_value {
2644 if !content
2645 .iter()
2646 .any(|c| c.kind() == ERROR || c.kind() == COMMENT)
2647 {
2648 let concat = content
2649 .iter()
2650 .filter_map(|c| c.as_token().map(|t| t.text()))
2651 .collect::<String>();
2652 let formatted = format_value(self.key().as_ref().unwrap(), &concat);
2653 crate::lex::lex_inline(&formatted)
2654 .map(|(k, t)| (k, t.to_string()))
2655 .collect::<Vec<_>>()
2656 } else {
2657 content
2658 .into_iter()
2659 .map(|n| n.into_token().unwrap())
2660 .map(|i| (i.kind(), i.text().to_string()))
2661 .collect::<Vec<_>>()
2662 }
2663 } else {
2664 content
2665 .into_iter()
2666 .map(|n| n.into_token().unwrap())
2667 .map(|i| (i.kind(), i.text().to_string()))
2668 .collect::<Vec<_>>()
2669 };
2670
2671 rebuild_value(
2672 &mut builder,
2673 tokens,
2674 self.key().map_or(0, |k| k.len()),
2675 indentation,
2676 immediate_empty_line,
2677 max_line_length_one_liner,
2678 );
2679
2680 builder.finish_node();
2681 Self(SyntaxNode::new_root_mut(builder.finish()))
2682 }
2683
2684 pub fn key(&self) -> Option<String> {
2686 self.0
2687 .children_with_tokens()
2688 .filter_map(|it| it.into_token())
2689 .find(|it| it.kind() == KEY)
2690 .map(|it| it.text().to_string())
2691 }
2692
2693 pub fn value(&self) -> String {
2695 let mut parts = self
2696 .0
2697 .children_with_tokens()
2698 .filter_map(|it| it.into_token())
2699 .filter(|it| it.kind() == VALUE)
2700 .map(|it| it.text().to_string());
2701
2702 match parts.next() {
2703 None => String::new(),
2704 Some(first) => {
2705 let mut result = first;
2706 for part in parts {
2707 result.push('\n');
2708 result.push_str(&part);
2709 }
2710 result
2711 }
2712 }
2713 }
2714
2715 pub fn value_with_comments(&self) -> String {
2722 let mut parts = self
2723 .0
2724 .children_with_tokens()
2725 .filter_map(|it| it.into_token())
2726 .filter(|it| it.kind() == VALUE || it.kind() == COMMENT)
2727 .map(|it| it.text().to_string());
2728
2729 match parts.next() {
2730 None => String::new(),
2731 Some(first) => {
2732 let mut result = first;
2733 for part in parts {
2734 result.push('\n');
2735 result.push_str(&part);
2736 }
2737 result
2738 }
2739 }
2740 }
2741
2742 fn get_indent(&self) -> Option<String> {
2745 self.0
2746 .children_with_tokens()
2747 .filter_map(|it| it.into_token())
2748 .find(|it| it.kind() == INDENT)
2749 .map(|it| it.text().to_string())
2750 }
2751
2752 fn get_post_colon_whitespace(&self) -> Option<String> {
2756 let mut found_colon = false;
2757 let mut whitespace = String::new();
2758
2759 for token in self
2760 .0
2761 .children_with_tokens()
2762 .filter_map(|it| it.into_token())
2763 {
2764 if token.kind() == COLON {
2765 found_colon = true;
2766 continue;
2767 }
2768
2769 if found_colon {
2770 if token.kind() == WHITESPACE || token.kind() == NEWLINE || token.kind() == INDENT {
2771 whitespace.push_str(token.text());
2772 } else {
2773 break;
2775 }
2776 }
2777 }
2778
2779 if whitespace.is_empty() {
2780 None
2781 } else {
2782 Some(whitespace)
2783 }
2784 }
2785
2786 pub fn normalize_field_spacing(&mut self) -> bool {
2807 use rowan::GreenNodeBuilder;
2808
2809 let original_text = self.0.text().to_string();
2811
2812 let mut builder = GreenNodeBuilder::new();
2814 builder.start_node(ENTRY.into());
2815
2816 let mut seen_colon = false;
2817 let mut skip_whitespace = false;
2818
2819 for child in self.0.children_with_tokens() {
2820 match child.kind() {
2821 KEY => {
2822 builder.token(KEY.into(), child.as_token().unwrap().text());
2823 }
2824 COLON => {
2825 builder.token(COLON.into(), ":");
2826 seen_colon = true;
2827 skip_whitespace = true;
2828 }
2829 WHITESPACE if skip_whitespace => {
2830 continue;
2832 }
2833 VALUE if skip_whitespace => {
2834 builder.token(WHITESPACE.into(), " ");
2836 builder.token(VALUE.into(), child.as_token().unwrap().text());
2837 skip_whitespace = false;
2838 }
2839 NEWLINE if skip_whitespace && seen_colon => {
2840 builder.token(NEWLINE.into(), "\n");
2843 skip_whitespace = false;
2844 }
2845 _ => {
2846 if let Some(token) = child.as_token() {
2848 builder.token(token.kind().into(), token.text());
2849 }
2850 }
2851 }
2852 }
2853
2854 builder.finish_node();
2855 let normalized_green = builder.finish();
2856 let normalized = SyntaxNode::new_root_mut(normalized_green);
2857
2858 let changed = original_text != normalized.text().to_string();
2860
2861 if changed {
2862 if let Some(parent) = self.0.parent() {
2864 let index = self.0.index();
2865 parent.splice_children(index..index + 1, vec![normalized.into()]);
2866 }
2867 }
2868
2869 changed
2870 }
2871
2872 pub fn detach(&mut self) {
2874 self.0.detach();
2875 }
2876}
2877
2878impl FromStr for Deb822 {
2879 type Err = ParseError;
2880
2881 fn from_str(s: &str) -> Result<Self, Self::Err> {
2882 Deb822::parse(s).to_result()
2883 }
2884}
2885
2886#[test]
2887fn test_parse_simple() {
2888 const CONTROLV1: &str = r#"Source: foo
2889Maintainer: Foo Bar <foo@example.com>
2890Section: net
2891
2892# This is a comment
2893
2894Package: foo
2895Architecture: all
2896Depends:
2897 bar,
2898 blah
2899Description: This is a description
2900 And it is
2901 .
2902 multiple
2903 lines
2904"#;
2905 let parsed = parse(CONTROLV1);
2906 let node = parsed.syntax();
2907 assert_eq!(
2908 format!("{:#?}", node),
2909 r###"ROOT@0..203
2910 PARAGRAPH@0..63
2911 ENTRY@0..12
2912 KEY@0..6 "Source"
2913 COLON@6..7 ":"
2914 WHITESPACE@7..8 " "
2915 VALUE@8..11 "foo"
2916 NEWLINE@11..12 "\n"
2917 ENTRY@12..50
2918 KEY@12..22 "Maintainer"
2919 COLON@22..23 ":"
2920 WHITESPACE@23..24 " "
2921 VALUE@24..49 "Foo Bar <foo@example. ..."
2922 NEWLINE@49..50 "\n"
2923 ENTRY@50..63
2924 KEY@50..57 "Section"
2925 COLON@57..58 ":"
2926 WHITESPACE@58..59 " "
2927 VALUE@59..62 "net"
2928 NEWLINE@62..63 "\n"
2929 EMPTY_LINE@63..64
2930 NEWLINE@63..64 "\n"
2931 EMPTY_LINE@64..84
2932 COMMENT@64..83 "# This is a comment"
2933 NEWLINE@83..84 "\n"
2934 EMPTY_LINE@84..85
2935 NEWLINE@84..85 "\n"
2936 PARAGRAPH@85..203
2937 ENTRY@85..98
2938 KEY@85..92 "Package"
2939 COLON@92..93 ":"
2940 WHITESPACE@93..94 " "
2941 VALUE@94..97 "foo"
2942 NEWLINE@97..98 "\n"
2943 ENTRY@98..116
2944 KEY@98..110 "Architecture"
2945 COLON@110..111 ":"
2946 WHITESPACE@111..112 " "
2947 VALUE@112..115 "all"
2948 NEWLINE@115..116 "\n"
2949 ENTRY@116..137
2950 KEY@116..123 "Depends"
2951 COLON@123..124 ":"
2952 NEWLINE@124..125 "\n"
2953 INDENT@125..126 " "
2954 VALUE@126..130 "bar,"
2955 NEWLINE@130..131 "\n"
2956 INDENT@131..132 " "
2957 VALUE@132..136 "blah"
2958 NEWLINE@136..137 "\n"
2959 ENTRY@137..203
2960 KEY@137..148 "Description"
2961 COLON@148..149 ":"
2962 WHITESPACE@149..150 " "
2963 VALUE@150..171 "This is a description"
2964 NEWLINE@171..172 "\n"
2965 INDENT@172..173 " "
2966 VALUE@173..182 "And it is"
2967 NEWLINE@182..183 "\n"
2968 INDENT@183..184 " "
2969 VALUE@184..185 "."
2970 NEWLINE@185..186 "\n"
2971 INDENT@186..187 " "
2972 VALUE@187..195 "multiple"
2973 NEWLINE@195..196 "\n"
2974 INDENT@196..197 " "
2975 VALUE@197..202 "lines"
2976 NEWLINE@202..203 "\n"
2977"###
2978 );
2979 assert_eq!(parsed.errors, Vec::<String>::new());
2980
2981 let root = parsed.root_mut();
2982 assert_eq!(root.paragraphs().count(), 2);
2983 let source = root.paragraphs().next().unwrap();
2984 assert_eq!(
2985 source.keys().collect::<Vec<_>>(),
2986 vec!["Source", "Maintainer", "Section"]
2987 );
2988 assert_eq!(source.get("Source").as_deref(), Some("foo"));
2989 assert_eq!(
2990 source.get("Maintainer").as_deref(),
2991 Some("Foo Bar <foo@example.com>")
2992 );
2993 assert_eq!(source.get("Section").as_deref(), Some("net"));
2994 assert_eq!(
2995 source.items().collect::<Vec<_>>(),
2996 vec![
2997 ("Source".into(), "foo".into()),
2998 ("Maintainer".into(), "Foo Bar <foo@example.com>".into()),
2999 ("Section".into(), "net".into()),
3000 ]
3001 );
3002
3003 let binary = root.paragraphs().nth(1).unwrap();
3004 assert_eq!(
3005 binary.keys().collect::<Vec<_>>(),
3006 vec!["Package", "Architecture", "Depends", "Description"]
3007 );
3008 assert_eq!(binary.get("Package").as_deref(), Some("foo"));
3009 assert_eq!(binary.get("Architecture").as_deref(), Some("all"));
3010 assert_eq!(binary.get("Depends").as_deref(), Some("bar,\nblah"));
3011 assert_eq!(
3012 binary.get("Description").as_deref(),
3013 Some("This is a description\nAnd it is\n.\nmultiple\nlines")
3014 );
3015
3016 assert_eq!(node.text(), CONTROLV1);
3017}
3018
3019#[test]
3020fn test_with_trailing_whitespace() {
3021 const CONTROLV1: &str = r#"Source: foo
3022Maintainer: Foo Bar <foo@example.com>
3023
3024
3025"#;
3026 let parsed = parse(CONTROLV1);
3027 let node = parsed.syntax();
3028 assert_eq!(
3029 format!("{:#?}", node),
3030 r###"ROOT@0..52
3031 PARAGRAPH@0..50
3032 ENTRY@0..12
3033 KEY@0..6 "Source"
3034 COLON@6..7 ":"
3035 WHITESPACE@7..8 " "
3036 VALUE@8..11 "foo"
3037 NEWLINE@11..12 "\n"
3038 ENTRY@12..50
3039 KEY@12..22 "Maintainer"
3040 COLON@22..23 ":"
3041 WHITESPACE@23..24 " "
3042 VALUE@24..49 "Foo Bar <foo@example. ..."
3043 NEWLINE@49..50 "\n"
3044 EMPTY_LINE@50..51
3045 NEWLINE@50..51 "\n"
3046 EMPTY_LINE@51..52
3047 NEWLINE@51..52 "\n"
3048"###
3049 );
3050 assert_eq!(parsed.errors, Vec::<String>::new());
3051
3052 let root = parsed.root_mut();
3053 assert_eq!(root.paragraphs().count(), 1);
3054 let source = root.paragraphs().next().unwrap();
3055 assert_eq!(
3056 source.items().collect::<Vec<_>>(),
3057 vec![
3058 ("Source".into(), "foo".into()),
3059 ("Maintainer".into(), "Foo Bar <foo@example.com>".into()),
3060 ]
3061 );
3062}
3063
3064fn rebuild_value(
3065 builder: &mut GreenNodeBuilder,
3066 mut tokens: Vec<(SyntaxKind, String)>,
3067 key_len: usize,
3068 indentation: u32,
3069 immediate_empty_line: bool,
3070 max_line_length_one_liner: Option<usize>,
3071) {
3072 let first_line_len = tokens
3073 .iter()
3074 .take_while(|(k, _t)| *k != NEWLINE)
3075 .map(|(_k, t)| t.len())
3076 .sum::<usize>() + key_len + 2 ;
3077
3078 let has_newline = tokens.iter().any(|(k, _t)| *k == NEWLINE);
3079
3080 let mut last_was_newline = false;
3081 if max_line_length_one_liner
3082 .map(|mll| first_line_len <= mll)
3083 .unwrap_or(false)
3084 && !has_newline
3085 {
3086 for (k, t) in tokens {
3088 builder.token(k.into(), &t);
3089 }
3090 } else {
3091 if immediate_empty_line && has_newline {
3093 builder.token(NEWLINE.into(), "\n");
3094 last_was_newline = true;
3095 } else {
3096 builder.token(WHITESPACE.into(), " ");
3097 }
3098 let mut start_idx = 0;
3100 while start_idx < tokens.len() {
3101 if tokens[start_idx].0 == NEWLINE || tokens[start_idx].0 == WHITESPACE {
3102 start_idx += 1;
3103 } else {
3104 break;
3105 }
3106 }
3107 tokens.drain(..start_idx);
3108 let indent_str = " ".repeat(indentation as usize);
3110 for (k, t) in tokens {
3111 if last_was_newline {
3112 builder.token(INDENT.into(), &indent_str);
3113 }
3114 builder.token(k.into(), &t);
3115 last_was_newline = k == NEWLINE;
3116 }
3117 }
3118
3119 if !last_was_newline {
3120 builder.token(NEWLINE.into(), "\n");
3121 }
3122}
3123
3124#[cfg(test)]
3125mod tests {
3126 use super::*;
3127 #[test]
3128 fn test_parse() {
3129 let d: super::Deb822 = r#"Source: foo
3130Maintainer: Foo Bar <jelmer@jelmer.uk>
3131Section: net
3132
3133Package: foo
3134Architecture: all
3135Depends: libc6
3136Description: This is a description
3137 With details
3138"#
3139 .parse()
3140 .unwrap();
3141 let mut ps = d.paragraphs();
3142 let p = ps.next().unwrap();
3143
3144 assert_eq!(p.get("Source").as_deref(), Some("foo"));
3145 assert_eq!(
3146 p.get("Maintainer").as_deref(),
3147 Some("Foo Bar <jelmer@jelmer.uk>")
3148 );
3149 assert_eq!(p.get("Section").as_deref(), Some("net"));
3150
3151 let b = ps.next().unwrap();
3152 assert_eq!(b.get("Package").as_deref(), Some("foo"));
3153 }
3154
3155 #[test]
3156 fn test_after_multi_line() {
3157 let d: super::Deb822 = r#"Source: golang-github-blah-blah
3158Section: devel
3159Priority: optional
3160Standards-Version: 4.2.0
3161Maintainer: Some Maintainer <example@example.com>
3162Build-Depends: debhelper (>= 11~),
3163 dh-golang,
3164 golang-any
3165Homepage: https://github.com/j-keck/arping
3166"#
3167 .parse()
3168 .unwrap();
3169 let mut ps = d.paragraphs();
3170 let p = ps.next().unwrap();
3171 assert_eq!(p.get("Source").as_deref(), Some("golang-github-blah-blah"));
3172 assert_eq!(p.get("Section").as_deref(), Some("devel"));
3173 assert_eq!(p.get("Priority").as_deref(), Some("optional"));
3174 assert_eq!(p.get("Standards-Version").as_deref(), Some("4.2.0"));
3175 assert_eq!(
3176 p.get("Maintainer").as_deref(),
3177 Some("Some Maintainer <example@example.com>")
3178 );
3179 assert_eq!(
3180 p.get("Build-Depends").as_deref(),
3181 Some("debhelper (>= 11~),\ndh-golang,\ngolang-any")
3182 );
3183 assert_eq!(
3184 p.get("Homepage").as_deref(),
3185 Some("https://github.com/j-keck/arping")
3186 );
3187 }
3188
3189 #[test]
3190 fn test_remove_field() {
3191 let d: super::Deb822 = r#"Source: foo
3192# Comment
3193Maintainer: Foo Bar <jelmer@jelmer.uk>
3194Section: net
3195
3196Package: foo
3197Architecture: all
3198Depends: libc6
3199Description: This is a description
3200 With details
3201"#
3202 .parse()
3203 .unwrap();
3204 let mut ps = d.paragraphs();
3205 let mut p = ps.next().unwrap();
3206 p.set("Foo", "Bar");
3207 p.remove("Section");
3208 p.remove("Nonexistent");
3209 assert_eq!(p.get("Foo").as_deref(), Some("Bar"));
3210 assert_eq!(
3211 p.to_string(),
3212 r#"Source: foo
3213# Comment
3214Maintainer: Foo Bar <jelmer@jelmer.uk>
3215Foo: Bar
3216"#
3217 );
3218 }
3219
3220 #[test]
3221 fn test_rename_field() {
3222 let d: super::Deb822 = r#"Source: foo
3223Vcs-Browser: https://salsa.debian.org/debian/foo
3224"#
3225 .parse()
3226 .unwrap();
3227 let mut ps = d.paragraphs();
3228 let mut p = ps.next().unwrap();
3229 assert!(p.rename("Vcs-Browser", "Homepage"));
3230 assert_eq!(
3231 p.to_string(),
3232 r#"Source: foo
3233Homepage: https://salsa.debian.org/debian/foo
3234"#
3235 );
3236
3237 assert_eq!(
3238 p.get("Homepage").as_deref(),
3239 Some("https://salsa.debian.org/debian/foo")
3240 );
3241 assert_eq!(p.get("Vcs-Browser").as_deref(), None);
3242
3243 assert!(!p.rename("Nonexistent", "Homepage"));
3245 }
3246
3247 #[test]
3248 fn test_set_field() {
3249 let d: super::Deb822 = r#"Source: foo
3250Maintainer: Foo Bar <joe@example.com>
3251"#
3252 .parse()
3253 .unwrap();
3254 let mut ps = d.paragraphs();
3255 let mut p = ps.next().unwrap();
3256 p.set("Maintainer", "Somebody Else <jane@example.com>");
3257 assert_eq!(
3258 p.get("Maintainer").as_deref(),
3259 Some("Somebody Else <jane@example.com>")
3260 );
3261 assert_eq!(
3262 p.to_string(),
3263 r#"Source: foo
3264Maintainer: Somebody Else <jane@example.com>
3265"#
3266 );
3267 }
3268
3269 #[test]
3270 fn test_set_new_field() {
3271 let d: super::Deb822 = r#"Source: foo
3272"#
3273 .parse()
3274 .unwrap();
3275 let mut ps = d.paragraphs();
3276 let mut p = ps.next().unwrap();
3277 p.set("Maintainer", "Somebody <joe@example.com>");
3278 assert_eq!(
3279 p.get("Maintainer").as_deref(),
3280 Some("Somebody <joe@example.com>")
3281 );
3282 assert_eq!(
3283 p.to_string(),
3284 r#"Source: foo
3285Maintainer: Somebody <joe@example.com>
3286"#
3287 );
3288 }
3289
3290 #[test]
3291 fn test_add_paragraph() {
3292 let mut d = super::Deb822::new();
3293 let mut p = d.add_paragraph();
3294 p.set("Foo", "Bar");
3295 assert_eq!(p.get("Foo").as_deref(), Some("Bar"));
3296 assert_eq!(
3297 p.to_string(),
3298 r#"Foo: Bar
3299"#
3300 );
3301 assert_eq!(
3302 d.to_string(),
3303 r#"Foo: Bar
3304"#
3305 );
3306
3307 let mut p = d.add_paragraph();
3308 p.set("Foo", "Blah");
3309 assert_eq!(p.get("Foo").as_deref(), Some("Blah"));
3310 assert_eq!(
3311 d.to_string(),
3312 r#"Foo: Bar
3313
3314Foo: Blah
3315"#
3316 );
3317 }
3318
3319 #[test]
3320 fn test_crud_paragraph() {
3321 let mut d = super::Deb822::new();
3322 let mut p = d.insert_paragraph(0);
3323 p.set("Foo", "Bar");
3324 assert_eq!(p.get("Foo").as_deref(), Some("Bar"));
3325 assert_eq!(
3326 d.to_string(),
3327 r#"Foo: Bar
3328"#
3329 );
3330
3331 let mut p = d.insert_paragraph(0);
3333 p.set("Foo", "Blah");
3334 assert_eq!(p.get("Foo").as_deref(), Some("Blah"));
3335 assert_eq!(
3336 d.to_string(),
3337 r#"Foo: Blah
3338
3339Foo: Bar
3340"#
3341 );
3342
3343 d.remove_paragraph(1);
3345 assert_eq!(d.to_string(), "Foo: Blah\n\n");
3346
3347 p.set("Foo", "Baz");
3349 assert_eq!(d.to_string(), "Foo: Baz\n\n");
3350
3351 d.remove_paragraph(0);
3353 assert_eq!(d.to_string(), "");
3354 }
3355
3356 #[test]
3357 fn test_swap_paragraphs() {
3358 let mut d: super::Deb822 = vec![
3360 vec![("Foo", "Bar")].into_iter().collect(),
3361 vec![("A", "B")].into_iter().collect(),
3362 vec![("X", "Y")].into_iter().collect(),
3363 ]
3364 .into_iter()
3365 .collect();
3366
3367 d.swap_paragraphs(0, 2);
3368 assert_eq!(d.to_string(), "X: Y\n\nA: B\n\nFoo: Bar\n");
3369
3370 d.swap_paragraphs(0, 2);
3372 assert_eq!(d.to_string(), "Foo: Bar\n\nA: B\n\nX: Y\n");
3373
3374 d.swap_paragraphs(0, 1);
3376 assert_eq!(d.to_string(), "A: B\n\nFoo: Bar\n\nX: Y\n");
3377
3378 let before = d.to_string();
3380 d.swap_paragraphs(1, 1);
3381 assert_eq!(d.to_string(), before);
3382 }
3383
3384 #[test]
3385 fn test_swap_paragraphs_preserves_content() {
3386 let mut d: super::Deb822 = vec![
3388 vec![("Field1", "Value1"), ("Field2", "Value2")]
3389 .into_iter()
3390 .collect(),
3391 vec![("FieldA", "ValueA"), ("FieldB", "ValueB")]
3392 .into_iter()
3393 .collect(),
3394 ]
3395 .into_iter()
3396 .collect();
3397
3398 d.swap_paragraphs(0, 1);
3399
3400 let mut paras = d.paragraphs();
3401 let p1 = paras.next().unwrap();
3402 assert_eq!(p1.get("FieldA").as_deref(), Some("ValueA"));
3403 assert_eq!(p1.get("FieldB").as_deref(), Some("ValueB"));
3404
3405 let p2 = paras.next().unwrap();
3406 assert_eq!(p2.get("Field1").as_deref(), Some("Value1"));
3407 assert_eq!(p2.get("Field2").as_deref(), Some("Value2"));
3408 }
3409
3410 #[test]
3411 #[should_panic(expected = "out of bounds")]
3412 fn test_swap_paragraphs_out_of_bounds() {
3413 let mut d: super::Deb822 = vec![
3414 vec![("Foo", "Bar")].into_iter().collect(),
3415 vec![("A", "B")].into_iter().collect(),
3416 ]
3417 .into_iter()
3418 .collect();
3419
3420 d.swap_paragraphs(0, 5);
3421 }
3422
3423 #[test]
3424 fn test_multiline_entry() {
3425 use super::SyntaxKind::*;
3426 use rowan::ast::AstNode;
3427
3428 let entry = super::Entry::new("foo", "bar\nbaz");
3429 let tokens: Vec<_> = entry
3430 .syntax()
3431 .descendants_with_tokens()
3432 .filter_map(|tok| tok.into_token())
3433 .collect();
3434
3435 assert_eq!("foo: bar\n baz\n", entry.to_string());
3436 assert_eq!("bar\nbaz", entry.value());
3437
3438 assert_eq!(
3439 vec![
3440 (KEY, "foo"),
3441 (COLON, ":"),
3442 (WHITESPACE, " "),
3443 (VALUE, "bar"),
3444 (NEWLINE, "\n"),
3445 (INDENT, " "),
3446 (VALUE, "baz"),
3447 (NEWLINE, "\n"),
3448 ],
3449 tokens
3450 .iter()
3451 .map(|token| (token.kind(), token.text()))
3452 .collect::<Vec<_>>()
3453 );
3454 }
3455
3456 #[test]
3457 fn test_apt_entry() {
3458 let text = r#"Package: cvsd
3459Binary: cvsd
3460Version: 1.0.24
3461Maintainer: Arthur de Jong <adejong@debian.org>
3462Build-Depends: debhelper (>= 9), po-debconf
3463Architecture: any
3464Standards-Version: 3.9.3
3465Format: 3.0 (native)
3466Files:
3467 b7a7d67a02974c52c408fdb5e118406d 890 cvsd_1.0.24.dsc
3468 b73ee40774c3086cb8490cdbb96ac883 258139 cvsd_1.0.24.tar.gz
3469Vcs-Browser: http://arthurdejong.org/viewvc/cvsd/
3470Vcs-Cvs: :pserver:anonymous@arthurdejong.org:/arthur/
3471Checksums-Sha256:
3472 a7bb7a3aacee19cd14ce5c26cb86e348b1608e6f1f6e97c6ea7c58efa440ac43 890 cvsd_1.0.24.dsc
3473 46bc517760c1070ae408693b89603986b53e6f068ae6bdc744e2e830e46b8cba 258139 cvsd_1.0.24.tar.gz
3474Homepage: http://arthurdejong.org/cvsd/
3475Package-List:
3476 cvsd deb vcs optional
3477Directory: pool/main/c/cvsd
3478Priority: source
3479Section: vcs
3480
3481"#;
3482 let d: super::Deb822 = text.parse().unwrap();
3483 let p = d.paragraphs().next().unwrap();
3484 assert_eq!(p.get("Binary").as_deref(), Some("cvsd"));
3485 assert_eq!(p.get("Version").as_deref(), Some("1.0.24"));
3486 assert_eq!(
3487 p.get("Maintainer").as_deref(),
3488 Some("Arthur de Jong <adejong@debian.org>")
3489 );
3490 }
3491
3492 #[test]
3493 fn test_format() {
3494 let d: super::Deb822 = r#"Source: foo
3495Maintainer: Foo Bar <foo@example.com>
3496Section: net
3497Blah: blah # comment
3498Multi-Line:
3499 Ahoi!
3500 Matey!
3501
3502"#
3503 .parse()
3504 .unwrap();
3505 let mut ps = d.paragraphs();
3506 let p = ps.next().unwrap();
3507 let result = p.wrap_and_sort(
3508 crate::Indentation::FieldNameLength,
3509 false,
3510 None,
3511 None::<&dyn Fn(&super::Entry, &super::Entry) -> std::cmp::Ordering>,
3512 None,
3513 );
3514 assert_eq!(
3515 result.to_string(),
3516 r#"Source: foo
3517Maintainer: Foo Bar <foo@example.com>
3518Section: net
3519Blah: blah # comment
3520Multi-Line: Ahoi!
3521 Matey!
3522"#
3523 );
3524 }
3525
3526 #[test]
3527 fn test_format_sort_paragraphs() {
3528 let d: super::Deb822 = r#"Source: foo
3529Maintainer: Foo Bar <foo@example.com>
3530
3531# This is a comment
3532Source: bar
3533Maintainer: Bar Foo <bar@example.com>
3534
3535"#
3536 .parse()
3537 .unwrap();
3538 let result = d.wrap_and_sort(
3539 Some(&|a: &super::Paragraph, b: &super::Paragraph| {
3540 a.get("Source").cmp(&b.get("Source"))
3541 }),
3542 Some(&|p| {
3543 p.wrap_and_sort(
3544 crate::Indentation::FieldNameLength,
3545 false,
3546 None,
3547 None::<&dyn Fn(&super::Entry, &super::Entry) -> std::cmp::Ordering>,
3548 None,
3549 )
3550 }),
3551 );
3552 assert_eq!(
3553 result.to_string(),
3554 r#"# This is a comment
3555Source: bar
3556Maintainer: Bar Foo <bar@example.com>
3557
3558Source: foo
3559Maintainer: Foo Bar <foo@example.com>
3560"#,
3561 );
3562 }
3563
3564 #[test]
3565 fn test_format_sort_fields() {
3566 let d: super::Deb822 = r#"Source: foo
3567Maintainer: Foo Bar <foo@example.com>
3568Build-Depends: debhelper (>= 9), po-debconf
3569Homepage: https://example.com/
3570
3571"#
3572 .parse()
3573 .unwrap();
3574 let result = d.wrap_and_sort(
3575 None,
3576 Some(&|p: &super::Paragraph| -> super::Paragraph {
3577 p.wrap_and_sort(
3578 crate::Indentation::FieldNameLength,
3579 false,
3580 None,
3581 Some(&|a: &super::Entry, b: &super::Entry| a.key().cmp(&b.key())),
3582 None,
3583 )
3584 }),
3585 );
3586 assert_eq!(
3587 result.to_string(),
3588 r#"Build-Depends: debhelper (>= 9), po-debconf
3589Homepage: https://example.com/
3590Maintainer: Foo Bar <foo@example.com>
3591Source: foo
3592"#
3593 );
3594 }
3595
3596 #[test]
3597 fn test_para_from_iter() {
3598 let p: super::Paragraph = vec![("Foo", "Bar"), ("Baz", "Qux")].into_iter().collect();
3599 assert_eq!(
3600 p.to_string(),
3601 r#"Foo: Bar
3602Baz: Qux
3603"#
3604 );
3605
3606 let p: super::Paragraph = vec![
3607 ("Foo".to_string(), "Bar".to_string()),
3608 ("Baz".to_string(), "Qux".to_string()),
3609 ]
3610 .into_iter()
3611 .collect();
3612
3613 assert_eq!(
3614 p.to_string(),
3615 r#"Foo: Bar
3616Baz: Qux
3617"#
3618 );
3619 }
3620
3621 #[test]
3622 fn test_deb822_from_iter() {
3623 let d: super::Deb822 = vec![
3624 vec![("Foo", "Bar"), ("Baz", "Qux")].into_iter().collect(),
3625 vec![("A", "B"), ("C", "D")].into_iter().collect(),
3626 ]
3627 .into_iter()
3628 .collect();
3629 assert_eq!(
3630 d.to_string(),
3631 r#"Foo: Bar
3632Baz: Qux
3633
3634A: B
3635C: D
3636"#
3637 );
3638 }
3639
3640 #[test]
3641 fn test_format_parse_error() {
3642 assert_eq!(ParseError(vec!["foo".to_string()]).to_string(), "foo\n");
3643 }
3644
3645 #[test]
3646 fn test_set_with_field_order() {
3647 let mut p = super::Paragraph::new();
3648 let custom_order = &["Foo", "Bar", "Baz"];
3649
3650 p.set_with_field_order("Baz", "3", custom_order);
3651 p.set_with_field_order("Foo", "1", custom_order);
3652 p.set_with_field_order("Bar", "2", custom_order);
3653 p.set_with_field_order("Unknown", "4", custom_order);
3654
3655 let keys: Vec<_> = p.keys().collect();
3656 assert_eq!(keys[0], "Foo");
3657 assert_eq!(keys[1], "Bar");
3658 assert_eq!(keys[2], "Baz");
3659 assert_eq!(keys[3], "Unknown");
3660 }
3661
3662 #[test]
3663 fn test_positioned_parse_error() {
3664 let error = PositionedParseError {
3665 message: "test error".to_string(),
3666 range: rowan::TextRange::new(rowan::TextSize::from(5), rowan::TextSize::from(10)),
3667 code: Some("test_code".to_string()),
3668 };
3669 assert_eq!(error.to_string(), "test error");
3670 assert_eq!(error.range.start(), rowan::TextSize::from(5));
3671 assert_eq!(error.range.end(), rowan::TextSize::from(10));
3672 assert_eq!(error.code, Some("test_code".to_string()));
3673 }
3674
3675 #[test]
3676 fn test_format_error() {
3677 assert_eq!(
3678 super::Error::ParseError(ParseError(vec!["foo".to_string()])).to_string(),
3679 "foo\n"
3680 );
3681 }
3682
3683 #[test]
3684 fn test_get_all() {
3685 let d: super::Deb822 = r#"Source: foo
3686Maintainer: Foo Bar <foo@example.com>
3687Maintainer: Bar Foo <bar@example.com>"#
3688 .parse()
3689 .unwrap();
3690 let p = d.paragraphs().next().unwrap();
3691 assert_eq!(
3692 p.get_all("Maintainer").collect::<Vec<_>>(),
3693 vec!["Foo Bar <foo@example.com>", "Bar Foo <bar@example.com>"]
3694 );
3695 }
3696
3697 #[test]
3698 fn test_get_with_indent_single_line() {
3699 let input = "Field: single line value\n";
3700 let deb = super::Deb822::from_str(input).unwrap();
3701 let para = deb.paragraphs().next().unwrap();
3702
3703 assert_eq!(
3705 para.get_with_indent("Field", &super::IndentPattern::Fixed(2)),
3706 Some("single line value".to_string())
3707 );
3708 assert_eq!(
3709 para.get_with_indent("Field", &super::IndentPattern::FieldNameLength),
3710 Some("single line value".to_string())
3711 );
3712 }
3713
3714 #[test]
3715 fn test_get_with_indent_fixed() {
3716 let input = "Field: First\n Second\n Third\n";
3717 let deb = super::Deb822::from_str(input).unwrap();
3718 let para = deb.paragraphs().next().unwrap();
3719
3720 let value = para
3722 .get_with_indent("Field", &super::IndentPattern::Fixed(2))
3723 .unwrap();
3724 assert_eq!(value, "First\n Second\n Third");
3725
3726 let value = para
3728 .get_with_indent("Field", &super::IndentPattern::Fixed(1))
3729 .unwrap();
3730 assert_eq!(value, "First\n Second\n Third");
3731
3732 let value = para
3734 .get_with_indent("Field", &super::IndentPattern::Fixed(3))
3735 .unwrap();
3736 assert_eq!(value, "First\nSecond\nThird");
3737 }
3738
3739 #[test]
3740 fn test_get_with_indent_field_name_length() {
3741 let input = "Description: First line\n Second line\n Third line\n";
3742 let deb = super::Deb822::from_str(input).unwrap();
3743 let para = deb.paragraphs().next().unwrap();
3744
3745 let value = para
3748 .get_with_indent("Description", &super::IndentPattern::FieldNameLength)
3749 .unwrap();
3750 assert_eq!(value, "First line\nSecond line\nThird line");
3751
3752 let value = para
3754 .get_with_indent("Description", &super::IndentPattern::Fixed(2))
3755 .unwrap();
3756 assert_eq!(
3757 value,
3758 "First line\n Second line\n Third line"
3759 );
3760 }
3761
3762 #[test]
3763 fn test_get_with_indent_nonexistent() {
3764 let input = "Field: value\n";
3765 let deb = super::Deb822::from_str(input).unwrap();
3766 let para = deb.paragraphs().next().unwrap();
3767
3768 assert_eq!(
3769 para.get_with_indent("NonExistent", &super::IndentPattern::Fixed(2)),
3770 None
3771 );
3772 }
3773
3774 #[test]
3775 fn test_get_entry() {
3776 let input = r#"Package: test-package
3777Maintainer: Test User <test@example.com>
3778Description: A simple test package
3779 with multiple lines
3780"#;
3781 let deb = super::Deb822::from_str(input).unwrap();
3782 let para = deb.paragraphs().next().unwrap();
3783
3784 let entry = para.get_entry("Package");
3786 assert!(entry.is_some());
3787 let entry = entry.unwrap();
3788 assert_eq!(entry.key(), Some("Package".to_string()));
3789 assert_eq!(entry.value(), "test-package");
3790
3791 let entry = para.get_entry("package");
3793 assert!(entry.is_some());
3794 assert_eq!(entry.unwrap().value(), "test-package");
3795
3796 let entry = para.get_entry("Description");
3798 assert!(entry.is_some());
3799 assert_eq!(
3800 entry.unwrap().value(),
3801 "A simple test package\nwith multiple lines"
3802 );
3803
3804 assert_eq!(para.get_entry("NonExistent"), None);
3806 }
3807
3808 #[test]
3809 fn test_entry_ranges() {
3810 let input = r#"Package: test-package
3811Maintainer: Test User <test@example.com>
3812Description: A simple test package
3813 with multiple lines
3814 of description text"#;
3815
3816 let deb822 = super::Deb822::from_str(input).unwrap();
3817 let paragraph = deb822.paragraphs().next().unwrap();
3818 let entries: Vec<_> = paragraph.entries().collect();
3819
3820 let package_entry = &entries[0];
3822 assert_eq!(package_entry.key(), Some("Package".to_string()));
3823
3824 let key_range = package_entry.key_range().unwrap();
3826 assert_eq!(
3827 &input[key_range.start().into()..key_range.end().into()],
3828 "Package"
3829 );
3830
3831 let colon_range = package_entry.colon_range().unwrap();
3833 assert_eq!(
3834 &input[colon_range.start().into()..colon_range.end().into()],
3835 ":"
3836 );
3837
3838 let value_range = package_entry.value_range().unwrap();
3840 assert_eq!(
3841 &input[value_range.start().into()..value_range.end().into()],
3842 "test-package"
3843 );
3844
3845 let text_range = package_entry.text_range();
3847 assert_eq!(
3848 &input[text_range.start().into()..text_range.end().into()],
3849 "Package: test-package\n"
3850 );
3851
3852 let value_lines = package_entry.value_line_ranges();
3854 assert_eq!(value_lines.len(), 1);
3855 assert_eq!(
3856 &input[value_lines[0].start().into()..value_lines[0].end().into()],
3857 "test-package"
3858 );
3859 }
3860
3861 #[test]
3862 fn test_multiline_entry_ranges() {
3863 let input = r#"Description: Short description
3864 Extended description line 1
3865 Extended description line 2"#;
3866
3867 let deb822 = super::Deb822::from_str(input).unwrap();
3868 let paragraph = deb822.paragraphs().next().unwrap();
3869 let entry = paragraph.entries().next().unwrap();
3870
3871 assert_eq!(entry.key(), Some("Description".to_string()));
3872
3873 let value_range = entry.value_range().unwrap();
3875 let full_value = &input[value_range.start().into()..value_range.end().into()];
3876 assert!(full_value.contains("Short description"));
3877 assert!(full_value.contains("Extended description line 1"));
3878 assert!(full_value.contains("Extended description line 2"));
3879
3880 let value_lines = entry.value_line_ranges();
3882 assert_eq!(value_lines.len(), 3);
3883
3884 assert_eq!(
3885 &input[value_lines[0].start().into()..value_lines[0].end().into()],
3886 "Short description"
3887 );
3888 assert_eq!(
3889 &input[value_lines[1].start().into()..value_lines[1].end().into()],
3890 "Extended description line 1"
3891 );
3892 assert_eq!(
3893 &input[value_lines[2].start().into()..value_lines[2].end().into()],
3894 "Extended description line 2"
3895 );
3896 }
3897
3898 #[test]
3899 fn test_entries_public_access() {
3900 let input = r#"Package: test
3901Version: 1.0"#;
3902
3903 let deb822 = super::Deb822::from_str(input).unwrap();
3904 let paragraph = deb822.paragraphs().next().unwrap();
3905
3906 let entries: Vec<_> = paragraph.entries().collect();
3908 assert_eq!(entries.len(), 2);
3909 assert_eq!(entries[0].key(), Some("Package".to_string()));
3910 assert_eq!(entries[1].key(), Some("Version".to_string()));
3911 }
3912
3913 #[test]
3914 fn test_empty_value_ranges() {
3915 let input = r#"EmptyField: "#;
3916
3917 let deb822 = super::Deb822::from_str(input).unwrap();
3918 let paragraph = deb822.paragraphs().next().unwrap();
3919 let entry = paragraph.entries().next().unwrap();
3920
3921 assert_eq!(entry.key(), Some("EmptyField".to_string()));
3922
3923 assert!(entry.key_range().is_some());
3925 assert!(entry.colon_range().is_some());
3926
3927 let value_lines = entry.value_line_ranges();
3929 assert!(value_lines.len() <= 1);
3932 }
3933
3934 #[test]
3935 fn test_range_ordering() {
3936 let input = r#"Field: value"#;
3937
3938 let deb822 = super::Deb822::from_str(input).unwrap();
3939 let paragraph = deb822.paragraphs().next().unwrap();
3940 let entry = paragraph.entries().next().unwrap();
3941
3942 let key_range = entry.key_range().unwrap();
3943 let colon_range = entry.colon_range().unwrap();
3944 let value_range = entry.value_range().unwrap();
3945 let text_range = entry.text_range();
3946
3947 assert!(key_range.end() <= colon_range.start());
3949 assert!(colon_range.end() <= value_range.start());
3950 assert!(key_range.start() >= text_range.start());
3951 assert!(value_range.end() <= text_range.end());
3952 }
3953
3954 #[test]
3955 fn test_error_recovery_missing_colon() {
3956 let input = r#"Source foo
3957Maintainer: Test User <test@example.com>
3958"#;
3959 let (deb822, errors) = super::Deb822::from_str_relaxed(input);
3960
3961 assert!(!errors.is_empty());
3963 assert!(errors.iter().any(|e| e.contains("missing colon")));
3964
3965 let paragraph = deb822.paragraphs().next().unwrap();
3967 assert_eq!(
3968 paragraph.get("Maintainer").as_deref(),
3969 Some("Test User <test@example.com>")
3970 );
3971 }
3972
3973 #[test]
3974 fn test_error_recovery_missing_field_name() {
3975 let input = r#": orphaned value
3976Package: test
3977"#;
3978
3979 let (deb822, errors) = super::Deb822::from_str_relaxed(input);
3980
3981 assert!(!errors.is_empty());
3983 assert!(errors
3984 .iter()
3985 .any(|e| e.contains("field name") || e.contains("missing")));
3986
3987 let paragraphs: Vec<_> = deb822.paragraphs().collect();
3989 let mut found_package = false;
3990 for paragraph in paragraphs.iter() {
3991 if paragraph.get("Package").is_some() {
3992 found_package = true;
3993 assert_eq!(paragraph.get("Package").as_deref(), Some("test"));
3994 }
3995 }
3996 assert!(found_package, "Package field not found in any paragraph");
3997 }
3998
3999 #[test]
4000 fn test_error_recovery_orphaned_text() {
4001 let input = r#"Package: test
4002some orphaned text without field name
4003Version: 1.0
4004"#;
4005 let (deb822, errors) = super::Deb822::from_str_relaxed(input);
4006
4007 assert!(!errors.is_empty());
4009 assert!(errors.iter().any(|e| e.contains("orphaned")
4010 || e.contains("unexpected")
4011 || e.contains("field name")));
4012
4013 let mut all_fields = std::collections::HashMap::new();
4015 for paragraph in deb822.paragraphs() {
4016 for (key, value) in paragraph.items() {
4017 all_fields.insert(key, value);
4018 }
4019 }
4020
4021 assert_eq!(all_fields.get("Package"), Some(&"test".to_string()));
4022 assert_eq!(all_fields.get("Version"), Some(&"1.0".to_string()));
4023 }
4024
4025 #[test]
4026 fn test_error_recovery_consecutive_field_names() {
4027 let input = r#"Package: test
4028Description
4029Maintainer: Another field without proper value
4030Version: 1.0
4031"#;
4032 let (deb822, errors) = super::Deb822::from_str_relaxed(input);
4033
4034 assert!(!errors.is_empty());
4036 assert!(errors.iter().any(|e| e.contains("consecutive")
4037 || e.contains("missing")
4038 || e.contains("incomplete")));
4039
4040 let mut all_fields = std::collections::HashMap::new();
4042 for paragraph in deb822.paragraphs() {
4043 for (key, value) in paragraph.items() {
4044 all_fields.insert(key, value);
4045 }
4046 }
4047
4048 assert_eq!(all_fields.get("Package"), Some(&"test".to_string()));
4049 assert_eq!(
4050 all_fields.get("Maintainer"),
4051 Some(&"Another field without proper value".to_string())
4052 );
4053 assert_eq!(all_fields.get("Version"), Some(&"1.0".to_string()));
4054 }
4055
4056 #[test]
4057 fn test_error_recovery_malformed_multiline() {
4058 let input = r#"Package: test
4059Description: Short desc
4060 Proper continuation
4061invalid continuation without indent
4062 Another proper continuation
4063Version: 1.0
4064"#;
4065 let (deb822, errors) = super::Deb822::from_str_relaxed(input);
4066
4067 assert!(!errors.is_empty());
4069
4070 let paragraph = deb822.paragraphs().next().unwrap();
4072 assert_eq!(paragraph.get("Package").as_deref(), Some("test"));
4073 assert_eq!(paragraph.get("Version").as_deref(), Some("1.0"));
4074 }
4075
4076 #[test]
4077 fn test_error_recovery_mixed_errors() {
4078 let input = r#"Package test without colon
4079: orphaned colon
4080Description: Valid field
4081some orphaned text
4082Another-Field: Valid too
4083"#;
4084 let (deb822, errors) = super::Deb822::from_str_relaxed(input);
4085
4086 assert!(!errors.is_empty());
4088 assert!(errors.len() >= 2);
4089
4090 let paragraph = deb822.paragraphs().next().unwrap();
4092 assert_eq!(paragraph.get("Description").as_deref(), Some("Valid field"));
4093 assert_eq!(paragraph.get("Another-Field").as_deref(), Some("Valid too"));
4094 }
4095
4096 #[test]
4097 fn test_error_recovery_paragraph_boundary() {
4098 let input = r#"Package: first-package
4099Description: First paragraph
4100
4101corrupted data here
4102: more corruption
4103completely broken line
4104
4105Package: second-package
4106Version: 1.0
4107"#;
4108 let (deb822, errors) = super::Deb822::from_str_relaxed(input);
4109
4110 assert!(!errors.is_empty());
4112
4113 let paragraphs: Vec<_> = deb822.paragraphs().collect();
4115 assert_eq!(paragraphs.len(), 2);
4116
4117 assert_eq!(
4118 paragraphs[0].get("Package").as_deref(),
4119 Some("first-package")
4120 );
4121 assert_eq!(
4122 paragraphs[1].get("Package").as_deref(),
4123 Some("second-package")
4124 );
4125 assert_eq!(paragraphs[1].get("Version").as_deref(), Some("1.0"));
4126 }
4127
4128 #[test]
4129 fn test_error_recovery_with_positioned_errors() {
4130 let input = r#"Package test
4131Description: Valid
4132"#;
4133 let parsed = super::parse(input);
4134
4135 assert!(!parsed.positioned_errors.is_empty());
4137
4138 let first_error = &parsed.positioned_errors[0];
4139 assert!(!first_error.message.is_empty());
4140 assert!(first_error.range.start() <= first_error.range.end());
4141 assert!(first_error.code.is_some());
4142
4143 let error_text = &input[first_error.range.start().into()..first_error.range.end().into()];
4145 assert!(!error_text.is_empty());
4146 }
4147
4148 #[test]
4149 fn test_positioned_error_points_to_correct_token() {
4150 let input = "Package test\nDescription: Valid\n";
4151 let parsed = super::parse(input);
4152
4153 assert_eq!(parsed.positioned_errors.len(), 1);
4154
4155 let first_error = &parsed.positioned_errors[0];
4156 assert_eq!(first_error.message, "missing colon ':' after field name");
4157 assert_eq!(first_error.code.as_deref(), Some("missing_colon"));
4158
4159 let start: usize = first_error.range.start().into();
4160 let end: usize = first_error.range.end().into();
4161 assert_eq!(start, 8);
4162 assert_eq!(end, 12);
4163 assert_eq!(&input[start..end], "test");
4164 }
4165
4166 #[test]
4167 fn test_error_recovery_preserves_whitespace() {
4168 let input = r#"Source: package
4169Maintainer Test User <test@example.com>
4170Section: utils
4171
4172"#;
4173 let (deb822, errors) = super::Deb822::from_str_relaxed(input);
4174
4175 assert!(!errors.is_empty());
4177
4178 let output = deb822.to_string();
4180 assert!(output.contains("Section: utils"));
4181
4182 let paragraph = deb822.paragraphs().next().unwrap();
4184 assert_eq!(paragraph.get("Source").as_deref(), Some("package"));
4185 assert_eq!(paragraph.get("Section").as_deref(), Some("utils"));
4186 }
4187
4188 #[test]
4189 fn test_error_recovery_empty_fields() {
4190 let input = r#"Package: test
4191Description:
4192Maintainer: Valid User
4193EmptyField:
4194Version: 1.0
4195"#;
4196 let (deb822, _errors) = super::Deb822::from_str_relaxed(input);
4197
4198 let mut all_fields = std::collections::HashMap::new();
4200 for paragraph in deb822.paragraphs() {
4201 for (key, value) in paragraph.items() {
4202 all_fields.insert(key, value);
4203 }
4204 }
4205
4206 assert_eq!(all_fields.get("Package"), Some(&"test".to_string()));
4207 assert_eq!(all_fields.get("Description"), Some(&"".to_string()));
4208 assert_eq!(
4209 all_fields.get("Maintainer"),
4210 Some(&"Valid User".to_string())
4211 );
4212 assert_eq!(all_fields.get("EmptyField"), Some(&"".to_string()));
4213 assert_eq!(all_fields.get("Version"), Some(&"1.0".to_string()));
4214 }
4215
4216 #[test]
4217 fn test_insert_comment_before() {
4218 let d: super::Deb822 = vec![
4219 vec![("Source", "foo"), ("Maintainer", "Bar <bar@example.com>")]
4220 .into_iter()
4221 .collect(),
4222 vec![("Package", "foo"), ("Architecture", "all")]
4223 .into_iter()
4224 .collect(),
4225 ]
4226 .into_iter()
4227 .collect();
4228
4229 let mut p1 = d.paragraphs().next().unwrap();
4231 p1.insert_comment_before("This is the source paragraph");
4232
4233 let mut p2 = d.paragraphs().nth(1).unwrap();
4235 p2.insert_comment_before("This is the binary paragraph");
4236
4237 let output = d.to_string();
4238 assert_eq!(
4239 output,
4240 r#"# This is the source paragraph
4241Source: foo
4242Maintainer: Bar <bar@example.com>
4243
4244# This is the binary paragraph
4245Package: foo
4246Architecture: all
4247"#
4248 );
4249 }
4250
4251 #[test]
4252 fn test_parse_continuation_with_colon() {
4253 let input = "Package: test\nDescription: short\n line: with colon\n";
4255 let result = input.parse::<Deb822>();
4256 assert!(result.is_ok());
4257
4258 let deb822 = result.unwrap();
4259 let para = deb822.paragraphs().next().unwrap();
4260 assert_eq!(para.get("Package").as_deref(), Some("test"));
4261 assert_eq!(
4262 para.get("Description").as_deref(),
4263 Some("short\nline: with colon")
4264 );
4265 }
4266
4267 #[test]
4268 fn test_parse_continuation_starting_with_colon() {
4269 let input = "Package: test\nDescription: short\n :value\n";
4271 let result = input.parse::<Deb822>();
4272 assert!(result.is_ok());
4273
4274 let deb822 = result.unwrap();
4275 let para = deb822.paragraphs().next().unwrap();
4276 assert_eq!(para.get("Package").as_deref(), Some("test"));
4277 assert_eq!(para.get("Description").as_deref(), Some("short\n:value"));
4278 }
4279
4280 #[test]
4281 fn test_normalize_field_spacing_single_space() {
4282 let input = "Field: value\n";
4284 let deb822 = input.parse::<Deb822>().unwrap();
4285 let mut para = deb822.paragraphs().next().unwrap();
4286
4287 para.normalize_field_spacing();
4288 assert_eq!(para.to_string(), "Field: value\n");
4289 }
4290
4291 #[test]
4292 fn test_normalize_field_spacing_extra_spaces() {
4293 let input = "Field: value\n";
4295 let deb822 = input.parse::<Deb822>().unwrap();
4296 let mut para = deb822.paragraphs().next().unwrap();
4297
4298 para.normalize_field_spacing();
4299 assert_eq!(para.to_string(), "Field: value\n");
4300 }
4301
4302 #[test]
4303 fn test_normalize_field_spacing_no_space() {
4304 let input = "Field:value\n";
4306 let deb822 = input.parse::<Deb822>().unwrap();
4307 let mut para = deb822.paragraphs().next().unwrap();
4308
4309 para.normalize_field_spacing();
4310 assert_eq!(para.to_string(), "Field: value\n");
4311 }
4312
4313 #[test]
4314 fn test_normalize_field_spacing_multiple_fields() {
4315 let input = "Field1: value1\nField2:value2\nField3: value3\n";
4317 let deb822 = input.parse::<Deb822>().unwrap();
4318 let mut para = deb822.paragraphs().next().unwrap();
4319
4320 para.normalize_field_spacing();
4321 assert_eq!(
4322 para.to_string(),
4323 "Field1: value1\nField2: value2\nField3: value3\n"
4324 );
4325 }
4326
4327 #[test]
4328 fn test_normalize_field_spacing_multiline_value() {
4329 let input = "Description: short\n continuation line\n . \n final line\n";
4331 let deb822 = input.parse::<Deb822>().unwrap();
4332 let mut para = deb822.paragraphs().next().unwrap();
4333
4334 para.normalize_field_spacing();
4335 assert_eq!(
4336 para.to_string(),
4337 "Description: short\n continuation line\n . \n final line\n"
4338 );
4339 }
4340
4341 #[test]
4342 fn test_normalize_field_spacing_empty_value_with_whitespace() {
4343 let input = "Field: \n";
4345 let deb822 = input.parse::<Deb822>().unwrap();
4346 let mut para = deb822.paragraphs().next().unwrap();
4347
4348 para.normalize_field_spacing();
4349 assert_eq!(para.to_string(), "Field:\n");
4351 }
4352
4353 #[test]
4354 fn test_normalize_field_spacing_no_value() {
4355 let input = "Depends:\n";
4357 let deb822 = input.parse::<Deb822>().unwrap();
4358 let mut para = deb822.paragraphs().next().unwrap();
4359
4360 para.normalize_field_spacing();
4361 assert_eq!(para.to_string(), "Depends:\n");
4363 }
4364
4365 #[test]
4366 fn test_normalize_field_spacing_multiple_paragraphs() {
4367 let input = "Field1: value1\n\nField2: value2\n";
4369 let mut deb822 = input.parse::<Deb822>().unwrap();
4370
4371 deb822.normalize_field_spacing();
4372 assert_eq!(deb822.to_string(), "Field1: value1\n\nField2: value2\n");
4373 }
4374
4375 #[test]
4376 fn test_normalize_field_spacing_preserves_comments() {
4377 let input = "# Comment\nField: value\n";
4379 let mut deb822 = input.parse::<Deb822>().unwrap();
4380
4381 deb822.normalize_field_spacing();
4382 assert_eq!(deb822.to_string(), "# Comment\nField: value\n");
4383 }
4384
4385 #[test]
4386 fn test_normalize_field_spacing_preserves_values() {
4387 let input = "Source: foo-bar\nMaintainer:Foo Bar <test@example.com>\n";
4389 let deb822 = input.parse::<Deb822>().unwrap();
4390 let mut para = deb822.paragraphs().next().unwrap();
4391
4392 para.normalize_field_spacing();
4393
4394 assert_eq!(para.get("Source").as_deref(), Some("foo-bar"));
4395 assert_eq!(
4396 para.get("Maintainer").as_deref(),
4397 Some("Foo Bar <test@example.com>")
4398 );
4399 }
4400
4401 #[test]
4402 fn test_normalize_field_spacing_tab_after_colon() {
4403 let input = "Field:\tvalue\n";
4405 let deb822 = input.parse::<Deb822>().unwrap();
4406 let mut para = deb822.paragraphs().next().unwrap();
4407
4408 para.normalize_field_spacing();
4409 assert_eq!(para.to_string(), "Field: value\n");
4410 }
4411
4412 #[test]
4413 fn test_set_preserves_indentation() {
4414 let original = r#"Source: example
4416Build-Depends: foo,
4417 bar,
4418 baz
4419"#;
4420
4421 let mut para: super::Paragraph = original.parse().unwrap();
4422
4423 para.set("Build-Depends", "foo,\nbar,\nbaz");
4425
4426 let expected = r#"Source: example
4428Build-Depends: foo,
4429 bar,
4430 baz
4431"#;
4432 assert_eq!(para.to_string(), expected);
4433 }
4434
4435 #[test]
4436 fn test_set_new_field_detects_field_name_length_indent() {
4437 let original = r#"Source: example
4439Build-Depends: foo,
4440 bar,
4441 baz
4442Depends: lib1,
4443 lib2
4444"#;
4445
4446 let mut para: super::Paragraph = original.parse().unwrap();
4447
4448 para.set("Recommends", "pkg1,\npkg2,\npkg3");
4450
4451 assert!(para
4453 .to_string()
4454 .contains("Recommends: pkg1,\n pkg2,"));
4455 }
4456
4457 #[test]
4458 fn test_set_new_field_detects_fixed_indent() {
4459 let original = r#"Source: example
4461Build-Depends: foo,
4462 bar,
4463 baz
4464Depends: lib1,
4465 lib2
4466"#;
4467
4468 let mut para: super::Paragraph = original.parse().unwrap();
4469
4470 para.set("Recommends", "pkg1,\npkg2,\npkg3");
4472
4473 assert!(para
4475 .to_string()
4476 .contains("Recommends: pkg1,\n pkg2,\n pkg3\n"));
4477 }
4478
4479 #[test]
4480 fn test_set_new_field_no_multiline_fields() {
4481 let original = r#"Source: example
4483Maintainer: Test <test@example.com>
4484"#;
4485
4486 let mut para: super::Paragraph = original.parse().unwrap();
4487
4488 para.set("Depends", "foo,\nbar,\nbaz");
4490
4491 let expected = r#"Source: example
4493Maintainer: Test <test@example.com>
4494Depends: foo,
4495 bar,
4496 baz
4497"#;
4498 assert_eq!(para.to_string(), expected);
4499 }
4500
4501 #[test]
4502 fn test_set_new_field_mixed_indentation() {
4503 let original = r#"Source: example
4505Build-Depends: foo,
4506 bar
4507Depends: lib1,
4508 lib2
4509"#;
4510
4511 let mut para: super::Paragraph = original.parse().unwrap();
4512
4513 para.set("Recommends", "pkg1,\npkg2");
4515
4516 assert!(para
4518 .to_string()
4519 .contains("Recommends: pkg1,\n pkg2\n"));
4520 }
4521
4522 #[test]
4523 fn test_entry_with_indentation() {
4524 let entry = super::Entry::with_indentation("Test-Field", "value1\nvalue2\nvalue3", " ");
4526
4527 assert_eq!(
4528 entry.to_string(),
4529 "Test-Field: value1\n value2\n value3\n"
4530 );
4531 }
4532
4533 #[test]
4534 fn test_set_with_indent_pattern_fixed() {
4535 let original = r#"Source: example
4537Maintainer: Test <test@example.com>
4538"#;
4539
4540 let mut para: super::Paragraph = original.parse().unwrap();
4541
4542 para.set_with_indent_pattern(
4544 "Depends",
4545 "foo,\nbar,\nbaz",
4546 Some(&super::IndentPattern::Fixed(4)),
4547 None,
4548 );
4549
4550 let expected = r#"Source: example
4552Maintainer: Test <test@example.com>
4553Depends: foo,
4554 bar,
4555 baz
4556"#;
4557 assert_eq!(para.to_string(), expected);
4558 }
4559
4560 #[test]
4561 fn test_set_with_indent_pattern_field_name_length() {
4562 let original = r#"Source: example
4564Maintainer: Test <test@example.com>
4565"#;
4566
4567 let mut para: super::Paragraph = original.parse().unwrap();
4568
4569 para.set_with_indent_pattern(
4571 "Build-Depends",
4572 "libfoo,\nlibbar,\nlibbaz",
4573 Some(&super::IndentPattern::FieldNameLength),
4574 None,
4575 );
4576
4577 let expected = r#"Source: example
4579Maintainer: Test <test@example.com>
4580Build-Depends: libfoo,
4581 libbar,
4582 libbaz
4583"#;
4584 assert_eq!(para.to_string(), expected);
4585 }
4586
4587 #[test]
4588 fn test_set_with_indent_pattern_override_auto_detection() {
4589 let original = r#"Source: example
4591Build-Depends: foo,
4592 bar,
4593 baz
4594"#;
4595
4596 let mut para: super::Paragraph = original.parse().unwrap();
4597
4598 para.set_with_indent_pattern(
4600 "Depends",
4601 "lib1,\nlib2,\nlib3",
4602 Some(&super::IndentPattern::Fixed(2)),
4603 None,
4604 );
4605
4606 let expected = r#"Source: example
4608Build-Depends: foo,
4609 bar,
4610 baz
4611Depends: lib1,
4612 lib2,
4613 lib3
4614"#;
4615 assert_eq!(para.to_string(), expected);
4616 }
4617
4618 #[test]
4619 fn test_set_with_indent_pattern_none_auto_detects() {
4620 let original = r#"Source: example
4622Build-Depends: foo,
4623 bar,
4624 baz
4625"#;
4626
4627 let mut para: super::Paragraph = original.parse().unwrap();
4628
4629 para.set_with_indent_pattern("Depends", "lib1,\nlib2", None, None);
4631
4632 let expected = r#"Source: example
4634Build-Depends: foo,
4635 bar,
4636 baz
4637Depends: lib1,
4638 lib2
4639"#;
4640 assert_eq!(para.to_string(), expected);
4641 }
4642
4643 #[test]
4644 fn test_set_with_indent_pattern_with_field_order() {
4645 let original = r#"Source: example
4647Maintainer: Test <test@example.com>
4648"#;
4649
4650 let mut para: super::Paragraph = original.parse().unwrap();
4651
4652 para.set_with_indent_pattern(
4654 "Priority",
4655 "optional",
4656 Some(&super::IndentPattern::Fixed(4)),
4657 Some(&["Source", "Priority", "Maintainer"]),
4658 );
4659
4660 let expected = r#"Source: example
4662Priority: optional
4663Maintainer: Test <test@example.com>
4664"#;
4665 assert_eq!(para.to_string(), expected);
4666 }
4667
4668 #[test]
4669 fn test_set_with_indent_pattern_replace_existing() {
4670 let original = r#"Source: example
4672Depends: foo,
4673 bar
4674"#;
4675
4676 let mut para: super::Paragraph = original.parse().unwrap();
4677
4678 para.set_with_indent_pattern(
4680 "Depends",
4681 "lib1,\nlib2,\nlib3",
4682 Some(&super::IndentPattern::Fixed(3)),
4683 None,
4684 );
4685
4686 let expected = r#"Source: example
4688Depends: lib1,
4689 lib2,
4690 lib3
4691"#;
4692 assert_eq!(para.to_string(), expected);
4693 }
4694
4695 #[test]
4696 fn test_change_field_indent() {
4697 let original = r#"Source: example
4699Depends: foo,
4700 bar,
4701 baz
4702"#;
4703 let mut para: super::Paragraph = original.parse().unwrap();
4704
4705 let result = para
4707 .change_field_indent("Depends", &super::IndentPattern::Fixed(2))
4708 .unwrap();
4709 assert!(result, "Field should have been found and updated");
4710
4711 let expected = r#"Source: example
4712Depends: foo,
4713 bar,
4714 baz
4715"#;
4716 assert_eq!(para.to_string(), expected);
4717 }
4718
4719 #[test]
4720 fn test_change_field_indent_nonexistent() {
4721 let original = r#"Source: example
4723"#;
4724 let mut para: super::Paragraph = original.parse().unwrap();
4725
4726 let result = para
4728 .change_field_indent("Depends", &super::IndentPattern::Fixed(2))
4729 .unwrap();
4730 assert!(!result, "Should return false for non-existent field");
4731
4732 assert_eq!(para.to_string(), original);
4734 }
4735
4736 #[test]
4737 fn test_change_field_indent_case_insensitive() {
4738 let original = r#"Build-Depends: foo,
4740 bar
4741"#;
4742 let mut para: super::Paragraph = original.parse().unwrap();
4743
4744 let result = para
4746 .change_field_indent("build-depends", &super::IndentPattern::Fixed(1))
4747 .unwrap();
4748 assert!(result, "Should find field case-insensitively");
4749
4750 let expected = r#"Build-Depends: foo,
4751 bar
4752"#;
4753 assert_eq!(para.to_string(), expected);
4754 }
4755
4756 #[test]
4757 fn test_entry_get_indent() {
4758 let original = r#"Build-Depends: foo,
4760 bar,
4761 baz
4762"#;
4763 let para: super::Paragraph = original.parse().unwrap();
4764 let entry = para.entries().next().unwrap();
4765
4766 assert_eq!(entry.get_indent(), Some(" ".to_string()));
4767 }
4768
4769 #[test]
4770 fn test_entry_get_indent_single_line() {
4771 let original = r#"Source: example
4773"#;
4774 let para: super::Paragraph = original.parse().unwrap();
4775 let entry = para.entries().next().unwrap();
4776
4777 assert_eq!(entry.get_indent(), None);
4778 }
4779}
4780
4781#[test]
4782fn test_move_paragraph_forward() {
4783 let mut d: Deb822 = vec![
4784 vec![("Foo", "Bar"), ("Baz", "Qux")].into_iter().collect(),
4785 vec![("A", "B"), ("C", "D")].into_iter().collect(),
4786 vec![("X", "Y"), ("Z", "W")].into_iter().collect(),
4787 ]
4788 .into_iter()
4789 .collect();
4790 d.move_paragraph(0, 2);
4791 assert_eq!(
4792 d.to_string(),
4793 "A: B\nC: D\n\nX: Y\nZ: W\n\nFoo: Bar\nBaz: Qux\n"
4794 );
4795}
4796
4797#[test]
4798fn test_move_paragraph_backward() {
4799 let mut d: Deb822 = vec![
4800 vec![("Foo", "Bar"), ("Baz", "Qux")].into_iter().collect(),
4801 vec![("A", "B"), ("C", "D")].into_iter().collect(),
4802 vec![("X", "Y"), ("Z", "W")].into_iter().collect(),
4803 ]
4804 .into_iter()
4805 .collect();
4806 d.move_paragraph(2, 0);
4807 assert_eq!(
4808 d.to_string(),
4809 "X: Y\nZ: W\n\nFoo: Bar\nBaz: Qux\n\nA: B\nC: D\n"
4810 );
4811}
4812
4813#[test]
4814fn test_move_paragraph_middle() {
4815 let mut d: Deb822 = vec![
4816 vec![("Foo", "Bar"), ("Baz", "Qux")].into_iter().collect(),
4817 vec![("A", "B"), ("C", "D")].into_iter().collect(),
4818 vec![("X", "Y"), ("Z", "W")].into_iter().collect(),
4819 ]
4820 .into_iter()
4821 .collect();
4822 d.move_paragraph(2, 1);
4823 assert_eq!(
4824 d.to_string(),
4825 "Foo: Bar\nBaz: Qux\n\nX: Y\nZ: W\n\nA: B\nC: D\n"
4826 );
4827}
4828
4829#[test]
4830fn test_move_paragraph_same_index() {
4831 let mut d: Deb822 = vec![
4832 vec![("Foo", "Bar"), ("Baz", "Qux")].into_iter().collect(),
4833 vec![("A", "B"), ("C", "D")].into_iter().collect(),
4834 ]
4835 .into_iter()
4836 .collect();
4837 let original = d.to_string();
4838 d.move_paragraph(1, 1);
4839 assert_eq!(d.to_string(), original);
4840}
4841
4842#[test]
4843fn test_move_paragraph_single() {
4844 let mut d: Deb822 = vec![vec![("Foo", "Bar")].into_iter().collect()]
4845 .into_iter()
4846 .collect();
4847 let original = d.to_string();
4848 d.move_paragraph(0, 0);
4849 assert_eq!(d.to_string(), original);
4850}
4851
4852#[test]
4853fn test_move_paragraph_invalid_index() {
4854 let mut d: Deb822 = vec![
4855 vec![("Foo", "Bar")].into_iter().collect(),
4856 vec![("A", "B")].into_iter().collect(),
4857 ]
4858 .into_iter()
4859 .collect();
4860 let original = d.to_string();
4861 d.move_paragraph(0, 5);
4862 assert_eq!(d.to_string(), original);
4863}
4864
4865#[test]
4866fn test_move_paragraph_with_comments() {
4867 let text = r#"Foo: Bar
4868
4869# This is a comment
4870
4871A: B
4872
4873X: Y
4874"#;
4875 let mut d: Deb822 = text.parse().unwrap();
4876 d.move_paragraph(0, 2);
4877 assert_eq!(
4878 d.to_string(),
4879 "# This is a comment\n\nA: B\n\nX: Y\n\nFoo: Bar\n"
4880 );
4881}
4882
4883#[test]
4884fn test_case_insensitive_get() {
4885 let text = "Package: test\nVersion: 1.0\n";
4886 let d: Deb822 = text.parse().unwrap();
4887 let p = d.paragraphs().next().unwrap();
4888
4889 assert_eq!(p.get("Package").as_deref(), Some("test"));
4891 assert_eq!(p.get("package").as_deref(), Some("test"));
4892 assert_eq!(p.get("PACKAGE").as_deref(), Some("test"));
4893 assert_eq!(p.get("PaCkAgE").as_deref(), Some("test"));
4894
4895 assert_eq!(p.get("Version").as_deref(), Some("1.0"));
4896 assert_eq!(p.get("version").as_deref(), Some("1.0"));
4897 assert_eq!(p.get("VERSION").as_deref(), Some("1.0"));
4898}
4899
4900#[test]
4901fn test_case_insensitive_set() {
4902 let text = "Package: test\n";
4903 let d: Deb822 = text.parse().unwrap();
4904 let mut p = d.paragraphs().next().unwrap();
4905
4906 p.set("package", "updated");
4908 assert_eq!(p.get("Package").as_deref(), Some("updated"));
4909 assert_eq!(p.get("package").as_deref(), Some("updated"));
4910
4911 p.set("PACKAGE", "updated2");
4913 assert_eq!(p.get("Package").as_deref(), Some("updated2"));
4914
4915 assert_eq!(p.keys().count(), 1);
4917}
4918
4919#[test]
4920fn test_case_insensitive_remove() {
4921 let text = "Package: test\nVersion: 1.0\n";
4922 let d: Deb822 = text.parse().unwrap();
4923 let mut p = d.paragraphs().next().unwrap();
4924
4925 p.remove("package");
4927 assert_eq!(p.get("Package"), None);
4928 assert_eq!(p.get("Version").as_deref(), Some("1.0"));
4929
4930 p.remove("VERSION");
4932 assert_eq!(p.get("Version"), None);
4933
4934 assert_eq!(p.keys().count(), 0);
4936}
4937
4938#[test]
4939fn test_case_preservation() {
4940 let text = "Package: test\n";
4941 let d: Deb822 = text.parse().unwrap();
4942 let mut p = d.paragraphs().next().unwrap();
4943
4944 let original_text = d.to_string();
4946 assert_eq!(original_text, "Package: test\n");
4947
4948 p.set("package", "updated");
4950
4951 let updated_text = d.to_string();
4953 assert_eq!(updated_text, "Package: updated\n");
4954}
4955
4956#[test]
4957fn test_case_insensitive_contains_key() {
4958 let text = "Package: test\n";
4959 let d: Deb822 = text.parse().unwrap();
4960 let p = d.paragraphs().next().unwrap();
4961
4962 assert!(p.contains_key("Package"));
4963 assert!(p.contains_key("package"));
4964 assert!(p.contains_key("PACKAGE"));
4965 assert!(!p.contains_key("NonExistent"));
4966}
4967
4968#[test]
4969fn test_case_insensitive_get_all() {
4970 let text = "Package: test1\npackage: test2\n";
4971 let d: Deb822 = text.parse().unwrap();
4972 let p = d.paragraphs().next().unwrap();
4973
4974 let values: Vec<String> = p.get_all("PACKAGE").collect();
4975 assert_eq!(values, vec!["test1", "test2"]);
4976}
4977
4978#[test]
4979fn test_case_insensitive_rename() {
4980 let text = "Package: test\n";
4981 let d: Deb822 = text.parse().unwrap();
4982 let mut p = d.paragraphs().next().unwrap();
4983
4984 assert!(p.rename("package", "NewName"));
4986 assert_eq!(p.get("NewName").as_deref(), Some("test"));
4987 assert_eq!(p.get("Package"), None);
4988}
4989
4990#[test]
4991fn test_rename_changes_case() {
4992 let text = "Package: test\n";
4993 let d: Deb822 = text.parse().unwrap();
4994 let mut p = d.paragraphs().next().unwrap();
4995
4996 assert!(p.rename("package", "PACKAGE"));
4998
4999 let updated_text = d.to_string();
5001 assert_eq!(updated_text, "PACKAGE: test\n");
5002
5003 assert_eq!(p.get("package").as_deref(), Some("test"));
5005 assert_eq!(p.get("Package").as_deref(), Some("test"));
5006 assert_eq!(p.get("PACKAGE").as_deref(), Some("test"));
5007}
5008
5009#[test]
5010fn test_rename_preserves_indentation_and_whitespace() {
5011 let text =
5014 "Comments: Exceptions\n 1997-1999, 2003 MIT\n License terms\n";
5015 let d: Deb822 = text.parse().unwrap();
5016 let mut p = d.paragraphs().next().unwrap();
5017
5018 assert!(p.rename("Comments", "Comment"));
5019 assert_eq!(
5020 d.to_string(),
5021 "Comment: Exceptions\n 1997-1999, 2003 MIT\n License terms\n"
5022 );
5023}
5024
5025#[test]
5026fn test_rename_in_multi_field_paragraph() {
5027 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";
5030 let d: Deb822 = text.parse().unwrap();
5031 let mut p = d.paragraphs().next().unwrap();
5032
5033 assert!(p.rename("Comments", "Comment"));
5034 assert_eq!(d.to_string(), text.replace("Comments:", "Comment:"));
5035}
5036
5037#[test]
5038fn test_rename_preserves_post_colon_whitespace() {
5039 let text = "Files: install_GUI.sh\n";
5041 let d: Deb822 = text.parse().unwrap();
5042 let mut p = d.paragraphs().next().unwrap();
5043
5044 assert!(p.rename("Files", "File"));
5045 assert_eq!(d.to_string(), "File: install_GUI.sh\n");
5046}
5047
5048#[test]
5049fn test_reject_whitespace_only_continuation_line() {
5050 let text = "Build-Depends:\n \ndebhelper\n";
5056 let parsed = Deb822::parse(text);
5057
5058 assert!(
5061 !parsed.errors().is_empty(),
5062 "Expected parse errors for whitespace-only continuation line"
5063 );
5064}
5065
5066#[test]
5067fn test_reject_empty_continuation_line_in_multiline_field() {
5068 let text = "Depends: foo,\n bar,\n \n baz\n";
5070 let parsed = Deb822::parse(text);
5071
5072 assert!(
5074 !parsed.errors().is_empty(),
5075 "Empty continuation line should generate parse errors"
5076 );
5077
5078 let has_empty_line_error = parsed
5080 .errors()
5081 .iter()
5082 .any(|e| e.contains("empty continuation line"));
5083 assert!(
5084 has_empty_line_error,
5085 "Should have an error about empty continuation line"
5086 );
5087}
5088
5089#[test]
5090#[should_panic(expected = "empty continuation line")]
5091fn test_set_rejects_empty_continuation_lines() {
5092 let text = "Package: test\n";
5094 let deb822 = text.parse::<Deb822>().unwrap();
5095 let mut para = deb822.paragraphs().next().unwrap();
5096
5097 let value_with_empty_line = "foo\n \nbar";
5100 para.set("Depends", value_with_empty_line);
5101}
5102
5103#[test]
5104fn test_try_set_returns_error_for_empty_continuation_lines() {
5105 let text = "Package: test\n";
5107 let deb822 = text.parse::<Deb822>().unwrap();
5108 let mut para = deb822.paragraphs().next().unwrap();
5109
5110 let value_with_empty_line = "foo\n \nbar";
5112 let result = para.try_set("Depends", value_with_empty_line);
5113
5114 assert!(
5116 result.is_err(),
5117 "try_set() should return an error for empty continuation lines"
5118 );
5119
5120 match result {
5122 Err(Error::InvalidValue(msg)) => {
5123 assert!(
5124 msg.contains("empty continuation line"),
5125 "Error message should mention empty continuation line"
5126 );
5127 }
5128 _ => panic!("Expected InvalidValue error"),
5129 }
5130}
5131
5132#[test]
5133fn test_try_set_with_indent_pattern_returns_error() {
5134 let text = "Package: test\n";
5136 let deb822 = text.parse::<Deb822>().unwrap();
5137 let mut para = deb822.paragraphs().next().unwrap();
5138
5139 let value_with_empty_line = "foo\n \nbar";
5140 let result = para.try_set_with_indent_pattern(
5141 "Depends",
5142 value_with_empty_line,
5143 Some(&IndentPattern::Fixed(2)),
5144 None,
5145 );
5146
5147 assert!(
5148 result.is_err(),
5149 "try_set_with_indent_pattern() should return an error"
5150 );
5151}
5152
5153#[test]
5154fn test_try_set_succeeds_for_valid_value() {
5155 let text = "Package: test\n";
5157 let deb822 = text.parse::<Deb822>().unwrap();
5158 let mut para = deb822.paragraphs().next().unwrap();
5159
5160 let valid_value = "foo\nbar";
5162 let result = para.try_set("Depends", valid_value);
5163
5164 assert!(result.is_ok(), "try_set() should succeed for valid values");
5165 assert_eq!(para.get("Depends").as_deref(), Some("foo\nbar"));
5166}
5167
5168#[test]
5169fn test_field_with_empty_first_line() {
5170 let text = "Foo:\n blah\n blah\n";
5173 let parsed = Deb822::parse(text);
5174
5175 assert!(
5177 parsed.errors().is_empty(),
5178 "Empty first line should be valid. Got errors: {:?}",
5179 parsed.errors()
5180 );
5181
5182 let deb822 = parsed.tree();
5183 let para = deb822.paragraphs().next().unwrap();
5184 assert_eq!(para.get("Foo").as_deref(), Some("blah\nblah"));
5185}
5186
5187#[test]
5188fn test_try_set_with_empty_first_line() {
5189 let text = "Package: test\n";
5191 let deb822 = text.parse::<Deb822>().unwrap();
5192 let mut para = deb822.paragraphs().next().unwrap();
5193
5194 let value = "\nblah\nmore";
5196 let result = para.try_set("Depends", value);
5197
5198 assert!(
5199 result.is_ok(),
5200 "try_set() should succeed for values with empty first line. Got: {:?}",
5201 result
5202 );
5203}
5204
5205#[test]
5206fn test_field_with_value_then_empty_continuation() {
5207 let text = "Foo: bar\n \n";
5209 let parsed = Deb822::parse(text);
5210
5211 assert!(
5213 !parsed.errors().is_empty(),
5214 "Field with value then empty continuation line should be rejected"
5215 );
5216
5217 let has_empty_line_error = parsed
5219 .errors()
5220 .iter()
5221 .any(|e| e.contains("empty continuation line"));
5222 assert!(
5223 has_empty_line_error,
5224 "Should have error about empty continuation line"
5225 );
5226}
5227
5228#[test]
5229fn test_substvar_continuation_line() {
5230 let text = "\
5231Package: python3-cryptography
5232Architecture: any
5233Depends: python3-bcrypt,
5234 ${misc:Depends},
5235 ${python3:Depends},
5236 ${shlibs:Depends},
5237Suggests: python-cryptography-doc,
5238 python3-cryptography-vectors,
5239Description: Python library exposing cryptographic recipes and primitives
5240 The cryptography library is designed to be a \"one-stop-shop\" for
5241 all your cryptographic needs in Python.
5242 .
5243 As an alternative to the libraries that came before it, cryptography
5244 tries to address some of the issues with those libraries:
5245 - Lack of PyPy and Python 3 support.
5246 - Lack of maintenance.
5247 - Use of poor implementations of algorithms (i.e. ones with known
5248 side-channel attacks).
5249 - Lack of high level, \"Cryptography for humans\", APIs.
5250 - Absence of algorithms such as AES-GCM.
5251 - Poor introspectability, and thus poor testability.
5252 - Extremely error prone APIs, and bad defaults.
5253";
5254 let parsed = Deb822::parse(text);
5255 for e in parsed.positioned_errors() {
5256 eprintln!("error at {:?}: {}", e.range, e.message);
5257 }
5258 assert!(
5259 parsed.errors().is_empty(),
5260 "Should not produce errors: {:?}",
5261 parsed.errors()
5262 );
5263 assert!(
5264 parsed.positioned_errors().is_empty(),
5265 "Should not produce positioned errors: {:?}",
5266 parsed.positioned_errors()
5267 );
5268}
5269
5270#[test]
5271fn test_line_col() {
5272 let text = r#"Source: foo
5273Maintainer: Foo Bar <jelmer@jelmer.uk>
5274Section: net
5275
5276Package: foo
5277Architecture: all
5278Depends: libc6
5279Description: This is a description
5280 With details
5281"#;
5282 let deb822 = text.parse::<Deb822>().unwrap();
5283
5284 let paras: Vec<_> = deb822.paragraphs().collect();
5286 assert_eq!(paras.len(), 2);
5287
5288 assert_eq!(paras[0].line(), 0);
5290 assert_eq!(paras[0].column(), 0);
5291
5292 assert_eq!(paras[1].line(), 4);
5294 assert_eq!(paras[1].column(), 0);
5295
5296 let entries: Vec<_> = paras[0].entries().collect();
5298 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));
5308 assert_eq!(entries[0].line_col(), (0, 0));
5309
5310 let second_para_entries: Vec<_> = paras[1].entries().collect();
5312 assert_eq!(second_para_entries[3].line(), 7); }
5314
5315#[test]
5316fn test_deb822_snapshot_independence() {
5317 let text = r#"Source: foo
5319Maintainer: Joe <joe@example.com>
5320
5321Package: foo
5322Architecture: all
5323"#;
5324 let deb822 = text.parse::<Deb822>().unwrap();
5325 let snapshot = deb822.snapshot();
5326
5327 let mut para = deb822.paragraphs().next().unwrap();
5329 para.set("Source", "modified");
5330
5331 let snapshot_para = snapshot.paragraphs().next().unwrap();
5333 assert_eq!(snapshot_para.get("Source").as_deref(), Some("foo"));
5334
5335 let mut snapshot_para = snapshot.paragraphs().next().unwrap();
5337 snapshot_para.set("Source", "snapshot-modified");
5338
5339 let para = deb822.paragraphs().next().unwrap();
5341 assert_eq!(para.get("Source").as_deref(), Some("modified"));
5342}
5343
5344#[test]
5345fn test_paragraph_snapshot_independence() {
5346 let text = "Package: foo\nArchitecture: all\n";
5348 let deb822 = text.parse::<Deb822>().unwrap();
5349 let mut para = deb822.paragraphs().next().unwrap();
5350 let mut snapshot = para.snapshot();
5351
5352 para.set("Package", "modified");
5354
5355 assert_eq!(snapshot.get("Package").as_deref(), Some("foo"));
5357
5358 snapshot.set("Package", "snapshot-modified");
5360
5361 assert_eq!(para.get("Package").as_deref(), Some("modified"));
5363}
5364
5365#[test]
5366fn test_entry_snapshot_independence() {
5367 let text = "Package: foo\n";
5369 let deb822 = text.parse::<Deb822>().unwrap();
5370 let mut para = deb822.paragraphs().next().unwrap();
5371 let entry = para.entries().next().unwrap();
5372 let snapshot = entry.snapshot();
5373
5374 let original_value = entry.value();
5376 let snapshot_value = snapshot.value();
5377
5378 assert_eq!(original_value, "foo");
5380 assert_eq!(snapshot_value, "foo");
5381
5382 para.set("Package", "modified");
5384
5385 let entry_after = para.entries().next().unwrap();
5387 assert_eq!(entry_after.value(), "modified");
5388
5389 assert_eq!(snapshot.value(), "foo");
5392}
5393
5394#[test]
5395fn test_snapshot_preserves_structure() {
5396 let text = r#"# Comment
5398Source: foo
5399## Another comment
5400Maintainer: Joe <joe@example.com>
5401
5402Package: foo
5403Architecture: all
5404"#;
5405 let deb822 = text.parse::<Deb822>().unwrap();
5406 let snapshot = deb822.snapshot();
5407
5408 assert_eq!(deb822.to_string(), snapshot.to_string());
5410
5411 let mut snapshot_para = snapshot.paragraphs().next().unwrap();
5413 snapshot_para.set("Source", "modified");
5414
5415 let original_para = deb822.paragraphs().next().unwrap();
5416 assert_eq!(original_para.get("Source").as_deref(), Some("foo"));
5417}
5418
5419#[test]
5420fn test_paragraph_text_range() {
5421 let text = r#"Source: foo
5423Maintainer: Joe <joe@example.com>
5424
5425Package: foo
5426Architecture: all
5427"#;
5428 let deb822 = text.parse::<Deb822>().unwrap();
5429 let paras: Vec<_> = deb822.paragraphs().collect();
5430
5431 let range1 = paras[0].text_range();
5433 let para1_text = &text[range1.start().into()..range1.end().into()];
5434 assert_eq!(
5435 para1_text,
5436 "Source: foo\nMaintainer: Joe <joe@example.com>\n"
5437 );
5438
5439 let range2 = paras[1].text_range();
5441 let para2_text = &text[range2.start().into()..range2.end().into()];
5442 assert_eq!(para2_text, "Package: foo\nArchitecture: all\n");
5443}
5444
5445#[test]
5446fn test_paragraphs_in_range_single() {
5447 let text = r#"Source: foo
5449
5450Package: bar
5451
5452Package: baz
5453"#;
5454 let deb822 = text.parse::<Deb822>().unwrap();
5455
5456 let first_para = deb822.paragraphs().next().unwrap();
5458 let range = first_para.text_range();
5459
5460 let paras: Vec<_> = deb822.paragraphs_in_range(range).collect();
5462 assert_eq!(paras.len(), 1);
5463 assert_eq!(paras[0].get("Source").as_deref(), Some("foo"));
5464}
5465
5466#[test]
5467fn test_paragraphs_in_range_multiple() {
5468 let text = r#"Source: foo
5470
5471Package: bar
5472
5473Package: baz
5474"#;
5475 let deb822 = text.parse::<Deb822>().unwrap();
5476
5477 let range = rowan::TextRange::new(0.into(), 25.into());
5479
5480 let paras: Vec<_> = deb822.paragraphs_in_range(range).collect();
5482 assert_eq!(paras.len(), 2);
5483 assert_eq!(paras[0].get("Source").as_deref(), Some("foo"));
5484 assert_eq!(paras[1].get("Package").as_deref(), Some("bar"));
5485}
5486
5487#[test]
5488fn test_paragraphs_in_range_partial_overlap() {
5489 let text = r#"Source: foo
5491
5492Package: bar
5493
5494Package: baz
5495"#;
5496 let deb822 = text.parse::<Deb822>().unwrap();
5497
5498 let range = rowan::TextRange::new(15.into(), 30.into());
5500
5501 let paras: Vec<_> = deb822.paragraphs_in_range(range).collect();
5503 assert!(paras.len() >= 1);
5504 assert!(paras
5505 .iter()
5506 .any(|p| p.get("Package").as_deref() == Some("bar")));
5507}
5508
5509#[test]
5510fn test_paragraphs_in_range_no_match() {
5511 let text = r#"Source: foo
5513
5514Package: bar
5515"#;
5516 let deb822 = text.parse::<Deb822>().unwrap();
5517
5518 let range = rowan::TextRange::new(1000.into(), 2000.into());
5520
5521 let paras: Vec<_> = deb822.paragraphs_in_range(range).collect();
5523 assert_eq!(paras.len(), 0);
5524}
5525
5526#[test]
5527fn test_paragraphs_in_range_all() {
5528 let text = r#"Source: foo
5530
5531Package: bar
5532
5533Package: baz
5534"#;
5535 let deb822 = text.parse::<Deb822>().unwrap();
5536
5537 let range = rowan::TextRange::new(0.into(), text.len().try_into().unwrap());
5539
5540 let paras: Vec<_> = deb822.paragraphs_in_range(range).collect();
5542 assert_eq!(paras.len(), 3);
5543}
5544
5545#[test]
5546fn test_paragraph_at_position() {
5547 let text = r#"Package: foo
5549Version: 1.0
5550
5551Package: bar
5552Architecture: all
5553"#;
5554 let deb822 = text.parse::<Deb822>().unwrap();
5555
5556 let para = deb822.paragraph_at_position(rowan::TextSize::from(5));
5558 assert!(para.is_some());
5559 assert_eq!(para.unwrap().get("Package").as_deref(), Some("foo"));
5560
5561 let para = deb822.paragraph_at_position(rowan::TextSize::from(30));
5563 assert!(para.is_some());
5564 assert_eq!(para.unwrap().get("Package").as_deref(), Some("bar"));
5565
5566 let para = deb822.paragraph_at_position(rowan::TextSize::from(1000));
5568 assert!(para.is_none());
5569}
5570
5571#[test]
5572fn test_paragraph_at_line() {
5573 let text = r#"Package: foo
5575Version: 1.0
5576
5577Package: bar
5578Architecture: all
5579"#;
5580 let deb822 = text.parse::<Deb822>().unwrap();
5581
5582 let para = deb822.paragraph_at_line(0);
5584 assert!(para.is_some());
5585 assert_eq!(para.unwrap().get("Package").as_deref(), Some("foo"));
5586
5587 let para = deb822.paragraph_at_line(1);
5589 assert!(para.is_some());
5590 assert_eq!(para.unwrap().get("Package").as_deref(), Some("foo"));
5591
5592 let para = deb822.paragraph_at_line(3);
5594 assert!(para.is_some());
5595 assert_eq!(para.unwrap().get("Package").as_deref(), Some("bar"));
5596
5597 let para = deb822.paragraph_at_line(100);
5599 assert!(para.is_none());
5600}
5601
5602#[test]
5603fn test_entry_at_line_col() {
5604 let text = r#"Package: foo
5606Version: 1.0
5607Architecture: all
5608"#;
5609 let deb822 = text.parse::<Deb822>().unwrap();
5610
5611 let entry = deb822.entry_at_line_col(0, 0);
5613 assert!(entry.is_some());
5614 assert_eq!(entry.unwrap().key(), Some("Package".to_string()));
5615
5616 let entry = deb822.entry_at_line_col(1, 0);
5618 assert!(entry.is_some());
5619 assert_eq!(entry.unwrap().key(), Some("Version".to_string()));
5620
5621 let entry = deb822.entry_at_line_col(2, 5);
5623 assert!(entry.is_some());
5624 assert_eq!(entry.unwrap().key(), Some("Architecture".to_string()));
5625
5626 let entry = deb822.entry_at_line_col(100, 0);
5628 assert!(entry.is_none());
5629}
5630
5631#[test]
5632fn test_entry_at_line_col_multiline() {
5633 let text = r#"Package: foo
5635Description: A package
5636 with a long
5637 description
5638Version: 1.0
5639"#;
5640 let deb822 = text.parse::<Deb822>().unwrap();
5641
5642 let entry = deb822.entry_at_line_col(1, 0);
5644 assert!(entry.is_some());
5645 assert_eq!(entry.unwrap().key(), Some("Description".to_string()));
5646
5647 let entry = deb822.entry_at_line_col(2, 1);
5649 assert!(entry.is_some());
5650 assert_eq!(entry.unwrap().key(), Some("Description".to_string()));
5651
5652 let entry = deb822.entry_at_line_col(3, 1);
5654 assert!(entry.is_some());
5655 assert_eq!(entry.unwrap().key(), Some("Description".to_string()));
5656
5657 let entry = deb822.entry_at_line_col(4, 0);
5659 assert!(entry.is_some());
5660 assert_eq!(entry.unwrap().key(), Some("Version".to_string()));
5661}
5662
5663#[test]
5664fn test_entries_in_range() {
5665 let text = r#"Package: foo
5667Version: 1.0
5668Architecture: all
5669"#;
5670 let deb822 = text.parse::<Deb822>().unwrap();
5671 let para = deb822.paragraphs().next().unwrap();
5672
5673 let first_entry = para.entries().next().unwrap();
5675 let range = first_entry.text_range();
5676
5677 let entries: Vec<_> = para.entries_in_range(range).collect();
5679 assert_eq!(entries.len(), 1);
5680 assert_eq!(entries[0].key(), Some("Package".to_string()));
5681
5682 let range = rowan::TextRange::new(0.into(), 25.into());
5684 let entries: Vec<_> = para.entries_in_range(range).collect();
5685 assert_eq!(entries.len(), 2);
5686 assert_eq!(entries[0].key(), Some("Package".to_string()));
5687 assert_eq!(entries[1].key(), Some("Version".to_string()));
5688}
5689
5690#[test]
5691fn test_entries_in_range_partial_overlap() {
5692 let text = r#"Package: foo
5694Version: 1.0
5695Architecture: all
5696"#;
5697 let deb822 = text.parse::<Deb822>().unwrap();
5698 let para = deb822.paragraphs().next().unwrap();
5699
5700 let range = rowan::TextRange::new(15.into(), 30.into());
5702
5703 let entries: Vec<_> = para.entries_in_range(range).collect();
5704 assert!(entries.len() >= 1);
5705 assert!(entries
5706 .iter()
5707 .any(|e| e.key() == Some("Version".to_string())));
5708}
5709
5710#[test]
5711fn test_entries_in_range_no_match() {
5712 let text = "Package: foo\n";
5714 let deb822 = text.parse::<Deb822>().unwrap();
5715 let para = deb822.paragraphs().next().unwrap();
5716
5717 let range = rowan::TextRange::new(1000.into(), 2000.into());
5719 let entries: Vec<_> = para.entries_in_range(range).collect();
5720 assert_eq!(entries.len(), 0);
5721}
5722
5723#[test]
5724fn test_entry_at_position() {
5725 let text = r#"Package: foo
5727Version: 1.0
5728Architecture: all
5729"#;
5730 let deb822 = text.parse::<Deb822>().unwrap();
5731 let para = deb822.paragraphs().next().unwrap();
5732
5733 let entry = para.entry_at_position(rowan::TextSize::from(5));
5735 assert!(entry.is_some());
5736 assert_eq!(entry.unwrap().key(), Some("Package".to_string()));
5737
5738 let entry = para.entry_at_position(rowan::TextSize::from(15));
5740 assert!(entry.is_some());
5741 assert_eq!(entry.unwrap().key(), Some("Version".to_string()));
5742
5743 let entry = para.entry_at_position(rowan::TextSize::from(1000));
5745 assert!(entry.is_none());
5746}
5747
5748#[test]
5749fn test_entry_at_position_multiline() {
5750 let text = r#"Description: A package
5752 with a long
5753 description
5754"#;
5755 let deb822 = text.parse::<Deb822>().unwrap();
5756 let para = deb822.paragraphs().next().unwrap();
5757
5758 let entry = para.entry_at_position(rowan::TextSize::from(5));
5760 assert!(entry.is_some());
5761 assert_eq!(entry.unwrap().key(), Some("Description".to_string()));
5762
5763 let entry = para.entry_at_position(rowan::TextSize::from(30));
5765 assert!(entry.is_some());
5766 assert_eq!(entry.unwrap().key(), Some("Description".to_string()));
5767}
5768
5769#[test]
5770fn test_paragraph_at_position_at_boundary() {
5771 let text = "Package: foo\n\nPackage: bar\n";
5773 let deb822 = text.parse::<Deb822>().unwrap();
5774
5775 let para = deb822.paragraph_at_position(rowan::TextSize::from(0));
5777 assert!(para.is_some());
5778 assert_eq!(para.unwrap().get("Package").as_deref(), Some("foo"));
5779
5780 let para = deb822.paragraph_at_position(rowan::TextSize::from(15));
5782 assert!(para.is_some());
5783 assert_eq!(para.unwrap().get("Package").as_deref(), Some("bar"));
5784}
5785
5786#[test]
5787fn test_comment_in_multiline_value() {
5788 let text = "\
5791Build-Depends: dh-python,
5792 libsvn-dev,
5793# python-all-dbg (>= 2.6.6-3),
5794 python3-all-dev,
5795# python3-all-dbg,
5796 python3-docutils
5797Standards-Version: 4.7.0
5798";
5799 let deb822 = text.parse::<Deb822>().unwrap();
5800 let para = deb822.paragraphs().next().unwrap();
5801 assert_eq!(
5803 para.get("Build-Depends").as_deref(),
5804 Some("dh-python,\nlibsvn-dev,\npython3-all-dev,\npython3-docutils")
5805 );
5806 assert_eq!(
5808 para.get_with_comments("Build-Depends").as_deref(),
5809 Some("dh-python,\nlibsvn-dev,\n# python-all-dbg (>= 2.6.6-3),\npython3-all-dev,\n# python3-all-dbg,\npython3-docutils")
5810 );
5811 assert_eq!(para.get("Standards-Version").as_deref(), Some("4.7.0"));
5812 assert_eq!(deb822.to_string(), text);
5814}