1use crate::relations::SyntaxKind::{self, *};
22use crate::relations::{BuildProfile, VersionConstraint};
23use debversion::Version;
24use rowan::{Direction, NodeOrToken};
25use std::collections::HashSet;
26
27#[derive(Debug, Clone, PartialEq, Eq, Hash)]
29pub struct ParseError(Vec<String>);
30
31impl std::fmt::Display for ParseError {
32 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
33 for err in &self.0 {
34 writeln!(f, "{}", err)?;
35 }
36 Ok(())
37 }
38}
39
40impl std::error::Error for ParseError {}
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
46enum Lang {}
47impl rowan::Language for Lang {
48 type Kind = SyntaxKind;
49 fn kind_from_raw(raw: rowan::SyntaxKind) -> Self::Kind {
50 unsafe { std::mem::transmute::<u16, SyntaxKind>(raw.0) }
51 }
52 fn kind_to_raw(kind: Self::Kind) -> rowan::SyntaxKind {
53 kind.into()
54 }
55}
56
57use rowan::{GreenNode, GreenToken};
60
61use rowan::GreenNodeBuilder;
65
66struct Parse {
69 green_node: GreenNode,
70 #[allow(unused)]
71 errors: Vec<String>,
72}
73
74fn parse(text: &str, allow_substvar: bool) -> Parse {
75 struct Parser {
76 tokens: Vec<(SyntaxKind, String)>,
79 builder: GreenNodeBuilder<'static>,
81 errors: Vec<String>,
84 allow_substvar: bool,
86 }
87
88 impl Parser {
89 fn parse_substvar(&mut self) {
90 self.builder.start_node(SyntaxKind::SUBSTVAR.into());
91 self.bump();
92 if self.current() != Some(L_CURLY) {
93 self.error(format!("expected {{ but got {:?}", self.current()).to_string());
94 } else {
95 self.bump();
96 }
97 loop {
98 match self.current() {
99 Some(IDENT) | Some(COLON) => {
100 self.bump();
101 }
102 Some(R_CURLY) => {
103 break;
104 }
105 e => {
106 self.error(format!("expected identifier or : but got {:?}", e).to_string());
107 }
108 }
109 }
110 if self.current() != Some(R_CURLY) {
111 self.error(format!("expected }} but got {:?}", self.current()).to_string());
112 } else {
113 self.bump();
114 }
115 self.builder.finish_node();
116 }
117
118 fn parse_entry(&mut self) {
119 self.skip_ws();
120 self.builder.start_node(SyntaxKind::ENTRY.into());
121 loop {
122 self.parse_relation();
123 match self.peek_past_ws() {
124 Some(COMMA) => {
125 break;
126 }
127 Some(PIPE) => {
128 self.skip_ws();
129 self.bump();
130 self.skip_ws();
131 }
132 None => {
133 self.skip_ws();
134 break;
135 }
136 _ => {
137 self.skip_ws();
138 self.builder.start_node(SyntaxKind::ERROR.into());
139 match self.tokens.pop() {
140 Some((k, t)) => {
141 self.builder.token(k.into(), t.as_str());
142 self.errors
143 .push(format!("Expected comma or pipe, not {:?}", (k, t)));
144 }
145 None => {
146 self.errors
147 .push("Expected comma or pipe, got end of file".to_string());
148 }
149 }
150 self.builder.finish_node();
151 }
152 }
153 }
154 self.builder.finish_node();
155 }
156
157 fn error(&mut self, error: String) {
158 self.errors.push(error);
159 self.builder.start_node(SyntaxKind::ERROR.into());
160 if self.current().is_some() {
161 self.bump();
162 }
163 self.builder.finish_node();
164 }
165
166 fn parse_relation(&mut self) {
167 self.builder.start_node(SyntaxKind::RELATION.into());
168 if self.current() == Some(IDENT) {
169 self.bump();
170 } else {
171 self.error("Expected package name".to_string());
172 }
173 match self.peek_past_ws() {
174 Some(COLON) => {
175 self.skip_ws();
176 self.builder.start_node(ARCHQUAL.into());
177 self.bump();
178 self.skip_ws();
179 if self.current() == Some(IDENT) {
180 self.bump();
181 } else {
182 self.error("Expected architecture name".to_string());
183 }
184 self.builder.finish_node();
185 self.skip_ws();
186 }
187 Some(PIPE) | Some(COMMA) => {}
188 None | Some(L_PARENS) | Some(L_BRACKET) | Some(L_ANGLE) => {
189 self.skip_ws();
190 }
191 e => {
192 self.skip_ws();
193 self.error(format!(
194 "Expected ':' or '|' or '[' or '<' or ',' but got {:?}",
195 e
196 ));
197 }
198 }
199
200 if self.peek_past_ws() == Some(L_PARENS) {
201 self.skip_ws();
202 self.builder.start_node(VERSION.into());
203 self.bump();
204 self.skip_ws();
205
206 self.builder.start_node(CONSTRAINT.into());
207
208 while self.current() == Some(L_ANGLE)
209 || self.current() == Some(R_ANGLE)
210 || self.current() == Some(EQUAL)
211 {
212 self.bump();
213 }
214
215 self.builder.finish_node();
216
217 self.skip_ws();
218
219 if self.current() == Some(IDENT) {
220 self.bump();
221 } else {
222 self.error("Expected version".to_string());
223 }
224
225 if self.current() == Some(R_PARENS) {
226 self.bump();
227 } else {
228 self.error("Expected ')'".to_string());
229 }
230
231 self.builder.finish_node();
232 }
233
234 if self.peek_past_ws() == Some(L_BRACKET) {
235 self.skip_ws();
236 self.builder.start_node(ARCHITECTURES.into());
237 self.bump();
238 loop {
239 self.skip_ws();
240 match self.current() {
241 Some(NOT) => {
242 self.bump();
243 }
244 Some(IDENT) => {
245 self.bump();
246 }
247 Some(R_BRACKET) => {
248 self.bump();
249 break;
250 }
251 _ => {
252 self.error("Expected architecture name or '!' or ']'".to_string());
253 }
254 }
255 }
256 self.builder.finish_node();
257 }
258
259 while self.peek_past_ws() == Some(L_ANGLE) {
260 self.skip_ws();
261 self.builder.start_node(PROFILES.into());
262 self.bump();
263
264 loop {
265 self.skip_ws();
266 match self.current() {
267 Some(IDENT) => {
268 self.bump();
269 }
270 Some(NOT) => {
271 self.bump();
272 self.skip_ws();
273 if self.current() == Some(IDENT) {
274 self.bump();
275 } else {
276 self.error("Expected profile".to_string());
277 }
278 }
279 Some(R_ANGLE) => {
280 self.bump();
281 break;
282 }
283 None => {
284 self.error("Expected profile or '>'".to_string());
285 break;
286 }
287 _ => {
288 self.error("Expected profile or '!' or '>'".to_string());
289 }
290 }
291 }
292
293 self.builder.finish_node();
294 }
295
296 self.builder.finish_node();
297 }
298
299 fn parse(mut self) -> Parse {
300 self.builder.start_node(SyntaxKind::ROOT.into());
301
302 self.skip_ws();
303
304 while self.current().is_some() {
305 match self.current() {
306 Some(IDENT) => self.parse_entry(),
307 Some(DOLLAR) => {
308 if self.allow_substvar {
309 self.parse_substvar()
310 } else {
311 self.error("Substvars are not allowed".to_string());
312 }
313 }
314 Some(COMMA) => {
315 }
317 Some(c) => {
318 self.error(format!("expected $ or identifier but got {:?}", c));
319 }
320 None => {
321 self.error("expected identifier but got end of file".to_string());
322 }
323 }
324
325 self.skip_ws();
326 match self.current() {
327 Some(COMMA) => {
328 self.bump();
329 }
330 None => {
331 break;
332 }
333 c => {
334 self.error(format!("expected comma or end of file but got {:?}", c));
335 }
336 }
337 self.skip_ws();
338 }
339
340 self.builder.finish_node();
341 Parse {
343 green_node: self.builder.finish(),
344 errors: self.errors,
345 }
346 }
347 fn bump(&mut self) {
349 let (kind, text) = self.tokens.pop().unwrap();
350 self.builder.token(kind.into(), text.as_str());
351 }
352 fn current(&self) -> Option<SyntaxKind> {
354 self.tokens.last().map(|(kind, _)| *kind)
355 }
356 fn skip_ws(&mut self) {
357 while self.current() == Some(WHITESPACE) || self.current() == Some(NEWLINE) {
358 self.bump()
359 }
360 }
361
362 fn peek_past_ws(&self) -> Option<SyntaxKind> {
363 let mut i = self.tokens.len();
364 while i > 0 {
365 i -= 1;
366 match self.tokens[i].0 {
367 WHITESPACE | NEWLINE => {}
368 _ => return Some(self.tokens[i].0),
369 }
370 }
371 None
372 }
373 }
374
375 let mut tokens = crate::relations::lex(text);
376 tokens.reverse();
377 Parser {
378 tokens,
379 builder: GreenNodeBuilder::new(),
380 errors: Vec::new(),
381 allow_substvar,
382 }
383 .parse()
384}
385
386type SyntaxNode = rowan::SyntaxNode<Lang>;
393#[allow(unused)]
394type SyntaxToken = rowan::SyntaxToken<Lang>;
395#[allow(unused)]
396type SyntaxElement = rowan::NodeOrToken<SyntaxNode, SyntaxToken>;
397
398impl Parse {
399 fn root_mut(&self) -> Relations {
400 Relations::cast(SyntaxNode::new_root_mut(self.green_node.clone())).unwrap()
401 }
402}
403
404macro_rules! ast_node {
405 ($ast:ident, $kind:ident) => {
406 #[repr(transparent)]
408 pub struct $ast(SyntaxNode);
409 impl $ast {
410 #[allow(unused)]
411 fn cast(node: SyntaxNode) -> Option<Self> {
412 if node.kind() == $kind {
413 Some(Self(node))
414 } else {
415 None
416 }
417 }
418 }
419
420 impl std::fmt::Display for $ast {
421 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
422 f.write_str(&self.0.text().to_string())
423 }
424 }
425 };
426}
427
428ast_node!(Relations, ROOT);
429ast_node!(Entry, ENTRY);
430ast_node!(Relation, RELATION);
431ast_node!(Substvar, SUBSTVAR);
432
433impl PartialEq for Relations {
434 fn eq(&self, other: &Self) -> bool {
435 self.entries().collect::<Vec<_>>() == other.entries().collect::<Vec<_>>()
436 }
437}
438
439impl PartialEq for Entry {
440 fn eq(&self, other: &Self) -> bool {
441 self.relations().collect::<Vec<_>>() == other.relations().collect::<Vec<_>>()
442 }
443}
444
445impl PartialEq for Relation {
446 fn eq(&self, other: &Self) -> bool {
447 self.name() == other.name()
448 && self.version() == other.version()
449 && self.archqual() == other.archqual()
450 && self.architectures().map(|x| x.collect::<HashSet<_>>())
451 == other.architectures().map(|x| x.collect::<HashSet<_>>())
452 && self.profiles().eq(other.profiles())
453 }
454}
455
456#[cfg(feature = "serde")]
457impl serde::Serialize for Relations {
458 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
459 let rep = self.to_string();
460 serializer.serialize_str(&rep)
461 }
462}
463
464#[cfg(feature = "serde")]
465impl<'de> serde::Deserialize<'de> for Relations {
466 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
467 let s = String::deserialize(deserializer)?;
468 let relations = s.parse().map_err(serde::de::Error::custom)?;
469 Ok(relations)
470 }
471}
472
473impl std::fmt::Debug for Relations {
474 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
475 let mut s = f.debug_struct("Relations");
476
477 for entry in self.entries() {
478 s.field("entry", &entry);
479 }
480
481 s.finish()
482 }
483}
484
485impl std::fmt::Debug for Entry {
486 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
487 let mut s = f.debug_struct("Entry");
488
489 for relation in self.relations() {
490 s.field("relation", &relation);
491 }
492
493 s.finish()
494 }
495}
496
497#[cfg(feature = "serde")]
498impl serde::Serialize for Entry {
499 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
500 let rep = self.to_string();
501 serializer.serialize_str(&rep)
502 }
503}
504
505#[cfg(feature = "serde")]
506impl<'de> serde::Deserialize<'de> for Entry {
507 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
508 let s = String::deserialize(deserializer)?;
509 let entry = s.parse().map_err(serde::de::Error::custom)?;
510 Ok(entry)
511 }
512}
513
514impl std::fmt::Debug for Relation {
515 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
516 let mut s = f.debug_struct("Relation");
517
518 s.field("name", &self.name());
519
520 if let Some((vc, version)) = self.version() {
521 s.field("version", &vc);
522 s.field("version", &version);
523 }
524
525 s.finish()
526 }
527}
528
529#[cfg(feature = "serde")]
530impl serde::Serialize for Relation {
531 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
532 let rep = self.to_string();
533 serializer.serialize_str(&rep)
534 }
535}
536
537#[cfg(feature = "serde")]
538impl<'de> serde::Deserialize<'de> for Relation {
539 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
540 let s = String::deserialize(deserializer)?;
541 let relation = s.parse().map_err(serde::de::Error::custom)?;
542 Ok(relation)
543 }
544}
545
546impl Default for Relations {
547 fn default() -> Self {
548 Self::new()
549 }
550}
551
552fn is_special_package_name(name: &str) -> bool {
557 if name.starts_with("${") && name.ends_with('}') {
559 return true;
560 }
561 if name.starts_with('@') && name.ends_with('@') {
563 return true;
564 }
565 false
566}
567
568pub trait SortingOrder {
570 fn lt(&self, name1: &str, name2: &str) -> bool;
574
575 fn ignore(&self, name: &str) -> bool;
577}
578
579#[derive(Debug, Clone, Copy, Default)]
581pub struct DefaultSortingOrder;
582
583impl SortingOrder for DefaultSortingOrder {
584 fn lt(&self, name1: &str, name2: &str) -> bool {
585 let special1 = is_special_package_name(name1);
586 let special2 = is_special_package_name(name2);
587
588 if special1 && !special2 {
590 return false;
591 }
592 if !special1 && special2 {
593 return true;
594 }
595 if special1 && special2 {
596 return false;
598 }
599
600 name1 < name2
602 }
603
604 fn ignore(&self, name: &str) -> bool {
605 is_special_package_name(name)
606 }
607}
608
609#[derive(Debug, Clone, Copy, Default)]
619pub struct WrapAndSortOrder;
620
621impl WrapAndSortOrder {
622 const BUILD_SYSTEMS: &'static [&'static str] = &[
624 "cdbs",
625 "debhelper-compat",
626 "debhelper",
627 "debputy",
628 "dpkg-build-api",
629 "dpkg-dev",
630 ];
631
632 fn get_sort_key<'a>(&self, name: &'a str) -> (i32, &'a str) {
633 if Self::BUILD_SYSTEMS.contains(&name) || name.starts_with("dh-") {
635 return (-1, name);
636 }
637
638 if name
640 .chars()
641 .next()
642 .is_some_and(|c| c.is_ascii_lowercase() || c.is_ascii_digit())
643 {
644 return (0, name);
645 }
646
647 (1, name)
649 }
650}
651
652impl SortingOrder for WrapAndSortOrder {
653 fn lt(&self, name1: &str, name2: &str) -> bool {
654 self.get_sort_key(name1) < self.get_sort_key(name2)
655 }
656
657 fn ignore(&self, _name: &str) -> bool {
658 false
660 }
661}
662
663impl Relations {
664 pub fn new() -> Self {
666 Self::from(vec![])
667 }
668
669 #[must_use]
671 pub fn wrap_and_sort(self) -> Self {
672 let mut entries = self
673 .entries()
674 .map(|e| e.wrap_and_sort())
675 .collect::<Vec<_>>();
676 entries.sort();
677 Self::from(entries)
679 }
680
681 pub fn entries(&self) -> impl Iterator<Item = Entry> + '_ {
683 self.0.children().filter_map(Entry::cast)
684 }
685
686 pub fn iter(&self) -> impl Iterator<Item = Entry> + '_ {
688 self.entries()
689 }
690
691 pub fn get_entry(&self, idx: usize) -> Option<Entry> {
693 self.entries().nth(idx)
694 }
695
696 pub fn remove_entry(&mut self, idx: usize) -> Entry {
698 let mut entry = self.get_entry(idx).unwrap();
699 entry.remove();
700 entry
701 }
702
703 fn collect_whitespace(start: Option<NodeOrToken<SyntaxNode, SyntaxToken>>) -> String {
705 let mut pattern = String::new();
706 let mut current = start;
707 while let Some(token) = current {
708 if matches!(token.kind(), WHITESPACE | NEWLINE) {
709 if let NodeOrToken::Token(t) = &token {
710 pattern.push_str(t.text());
711 }
712 current = token.next_sibling_or_token();
713 } else {
714 break;
715 }
716 }
717 pattern
718 }
719
720 fn to_green(node: &NodeOrToken<SyntaxNode, SyntaxToken>) -> NodeOrToken<GreenNode, GreenToken> {
722 match node {
723 NodeOrToken::Node(n) => NodeOrToken::Node(n.green().into()),
724 NodeOrToken::Token(t) => NodeOrToken::Token(t.green().to_owned()),
725 }
726 }
727
728 fn is_whitespace_token(token: &GreenToken) -> bool {
730 token.kind() == rowan::SyntaxKind(WHITESPACE as u16)
731 || token.kind() == rowan::SyntaxKind(NEWLINE as u16)
732 }
733
734 fn strip_trailing_ws_from_children(
736 mut children: Vec<NodeOrToken<GreenNode, GreenToken>>,
737 ) -> Vec<NodeOrToken<GreenNode, GreenToken>> {
738 while let Some(last) = children.last() {
739 if let NodeOrToken::Token(t) = last {
740 if Self::is_whitespace_token(t) {
741 children.pop();
742 } else {
743 break;
744 }
745 } else {
746 break;
747 }
748 }
749 children
750 }
751
752 fn strip_relation_trailing_ws(relation: &SyntaxNode) -> GreenNode {
754 let children: Vec<_> = relation
755 .children_with_tokens()
756 .map(|c| Self::to_green(&c))
757 .collect();
758 let stripped = Self::strip_trailing_ws_from_children(children);
759 GreenNode::new(relation.kind().into(), stripped)
760 }
761
762 fn build_odd_syntax_nodes(
764 before_ws: &str,
765 after_ws: &str,
766 ) -> Vec<NodeOrToken<GreenNode, GreenToken>> {
767 [
768 (!before_ws.is_empty())
769 .then(|| NodeOrToken::Token(GreenToken::new(WHITESPACE.into(), before_ws))),
770 Some(NodeOrToken::Token(GreenToken::new(COMMA.into(), ","))),
771 (!after_ws.is_empty())
772 .then(|| NodeOrToken::Token(GreenToken::new(WHITESPACE.into(), after_ws))),
773 ]
774 .into_iter()
775 .flatten()
776 .collect()
777 }
778
779 fn detect_odd_syntax(&self) -> Option<(String, String)> {
781 for entry_node in self.entries() {
782 let mut node = entry_node.0.next_sibling_or_token()?;
783
784 let mut before = String::new();
786 while matches!(node.kind(), WHITESPACE | NEWLINE) {
787 if let NodeOrToken::Token(t) = &node {
788 before.push_str(t.text());
789 }
790 node = node.next_sibling_or_token()?;
791 }
792
793 if node.kind() == COMMA && !before.is_empty() {
795 let after = Self::collect_whitespace(node.next_sibling_or_token());
796 return Some((before, after));
797 }
798 }
799 None
800 }
801
802 fn detect_whitespace_pattern(&self, default: &str) -> String {
810 use std::collections::HashMap;
811
812 let entries: Vec<_> = self.entries().collect();
813 let num_entries = entries.len();
814
815 if num_entries == 0 {
816 return String::from(""); }
818
819 if num_entries == 1 {
820 if let Some(node) = entries[0].0.next_sibling_or_token() {
822 if node.kind() == COMMA {
823 let pattern = Self::collect_whitespace(node.next_sibling_or_token());
824 if !pattern.is_empty() {
825 return pattern;
826 }
827 }
828 }
829 return default.to_string(); }
831
832 let mut whitespace_counts: HashMap<String, usize> = HashMap::new();
834
835 for (i, entry) in entries.iter().enumerate() {
836 if i == num_entries - 1 {
837 break; }
839
840 if let Some(mut node) = entry.0.next_sibling_or_token() {
842 while matches!(node.kind(), WHITESPACE | NEWLINE) {
844 if let Some(next) = node.next_sibling_or_token() {
845 node = next;
846 } else {
847 break;
848 }
849 }
850
851 if node.kind() == COMMA {
853 let pattern = Self::collect_whitespace(node.next_sibling_or_token());
854 if !pattern.is_empty() {
855 *whitespace_counts.entry(pattern).or_insert(0) += 1;
856 }
857 }
858 }
859 }
860
861 if whitespace_counts.len() == 1 {
863 if let Some((ws, _)) = whitespace_counts.iter().next() {
864 return ws.clone();
865 }
866 }
867
868 if let Some((ws, _)) = whitespace_counts.iter().max_by_key(|(_, count)| *count) {
870 return ws.clone();
871 }
872
873 default.to_string()
875 }
876
877 pub fn insert_with_separator(&mut self, idx: usize, entry: Entry, default_sep: Option<&str>) {
884 let is_empty = self.entries().next().is_none();
885 let whitespace = self.detect_whitespace_pattern(default_sep.unwrap_or(" "));
886
887 self.strip_trailing_whitespace();
889
890 let odd_syntax = self.detect_odd_syntax();
892
893 let (position, new_children) = if let Some(current_entry) = self.entries().nth(idx) {
894 let to_insert = if idx == 0 && is_empty {
895 vec![entry.0.green().into()]
896 } else if let Some((before_ws, after_ws)) = &odd_syntax {
897 let mut nodes = vec![entry.0.green().into()];
898 nodes.extend(Self::build_odd_syntax_nodes(before_ws, after_ws));
899 nodes
900 } else {
901 vec![
902 entry.0.green().into(),
903 NodeOrToken::Token(GreenToken::new(COMMA.into(), ",")),
904 NodeOrToken::Token(GreenToken::new(WHITESPACE.into(), whitespace.as_str())),
905 ]
906 };
907
908 (current_entry.0.index(), to_insert)
909 } else {
910 let child_count = self.0.children_with_tokens().count();
911 let to_insert = if idx == 0 {
912 vec![entry.0.green().into()]
913 } else if let Some((before_ws, after_ws)) = &odd_syntax {
914 let mut nodes = Self::build_odd_syntax_nodes(before_ws, after_ws);
915 nodes.push(entry.0.green().into());
916 nodes
917 } else {
918 vec![
919 NodeOrToken::Token(GreenToken::new(COMMA.into(), ",")),
920 NodeOrToken::Token(GreenToken::new(WHITESPACE.into(), whitespace.as_str())),
921 entry.0.green().into(),
922 ]
923 };
924
925 (child_count, to_insert)
926 };
927 self.0 = SyntaxNode::new_root_mut(
929 self.0.replace_with(
930 self.0
931 .green()
932 .splice_children(position..position, new_children),
933 ),
934 );
935 }
936
937 pub fn insert(&mut self, idx: usize, entry: Entry) {
939 self.insert_with_separator(idx, entry, None);
940 }
941
942 fn strip_entry_trailing_ws(entry: &SyntaxNode) -> GreenNode {
944 let mut children: Vec<_> = entry
945 .children_with_tokens()
946 .map(|c| Self::to_green(&c))
947 .collect();
948
949 if let Some(NodeOrToken::Node(last)) = children.last() {
951 if last.kind() == rowan::SyntaxKind(RELATION as u16) {
952 let relation_node = entry.children().last().unwrap();
954 children.pop();
955 children.push(NodeOrToken::Node(
956 Self::strip_relation_trailing_ws(&relation_node).into(),
957 ));
958 }
959 }
960
961 let stripped = Self::strip_trailing_ws_from_children(children);
963 GreenNode::new(ENTRY.into(), stripped)
964 }
965
966 fn strip_trailing_whitespace(&mut self) {
967 let mut children: Vec<_> = self
968 .0
969 .children_with_tokens()
970 .map(|c| Self::to_green(&c))
971 .collect();
972
973 if let Some(NodeOrToken::Node(last)) = children.last() {
975 if last.kind() == rowan::SyntaxKind(ENTRY as u16) {
976 let last_entry = self.0.children().last().unwrap();
977 children.pop();
978 children.push(NodeOrToken::Node(
979 Self::strip_entry_trailing_ws(&last_entry).into(),
980 ));
981 }
982 }
983
984 let stripped = Self::strip_trailing_ws_from_children(children);
986
987 let nc = self.0.children_with_tokens().count();
988 self.0 = SyntaxNode::new_root_mut(
989 self.0
990 .replace_with(self.0.green().splice_children(0..nc, stripped)),
991 );
992 }
993
994 pub fn replace(&mut self, idx: usize, entry: Entry) {
996 let current_entry = self.get_entry(idx).unwrap();
997 self.0.splice_children(
998 current_entry.0.index()..current_entry.0.index() + 1,
999 vec![entry.0.into()],
1000 );
1001 }
1002
1003 pub fn push(&mut self, entry: Entry) {
1005 let pos = self.entries().count();
1006 self.insert(pos, entry);
1007 }
1008
1009 pub fn substvars(&self) -> impl Iterator<Item = String> + '_ {
1011 self.0
1012 .children()
1013 .filter_map(Substvar::cast)
1014 .map(|s| s.to_string())
1015 }
1016
1017 pub fn parse_relaxed(s: &str, allow_substvar: bool) -> (Relations, Vec<String>) {
1019 let parse = parse(s, allow_substvar);
1020 (parse.root_mut(), parse.errors)
1021 }
1022
1023 pub fn satisfied_by(&self, package_version: impl crate::VersionLookup + Copy) -> bool {
1025 self.entries().all(|e| e.satisfied_by(package_version))
1026 }
1027
1028 pub fn is_empty(&self) -> bool {
1030 self.entries().count() == 0
1031 }
1032
1033 pub fn len(&self) -> usize {
1035 self.entries().count()
1036 }
1037
1038 pub fn ensure_minimum_version(&mut self, package: &str, minimum_version: &Version) {
1061 let mut found = false;
1062 let mut obsolete_indices = vec![];
1063 let mut update_idx = None;
1064
1065 let entries: Vec<_> = self.entries().collect();
1066 for (idx, entry) in entries.iter().enumerate() {
1067 let relations: Vec<_> = entry.relations().collect();
1068
1069 let names: Vec<_> = relations.iter().map(|r| r.name()).collect();
1071 if names.len() > 1 && names.contains(&package.to_string()) {
1072 let is_obsolete = relations.iter().any(|r| {
1074 if r.name() != package {
1075 return false;
1076 }
1077 if let Some((vc, ver)) = r.version() {
1078 matches!(vc, VersionConstraint::GreaterThan if &ver < minimum_version)
1079 || matches!(vc, VersionConstraint::GreaterThanEqual if &ver <= minimum_version)
1080 } else {
1081 false
1082 }
1083 });
1084 if is_obsolete {
1085 obsolete_indices.push(idx);
1086 }
1087 continue;
1088 }
1089
1090 if names.len() == 1 && names[0] == package {
1092 found = true;
1093 let relation = relations.into_iter().next().unwrap();
1094
1095 let should_update = if let Some((vc, ver)) = relation.version() {
1097 match vc {
1098 VersionConstraint::GreaterThanEqual | VersionConstraint::GreaterThan => {
1099 &ver < minimum_version
1100 }
1101 _ => false,
1102 }
1103 } else {
1104 true
1105 };
1106
1107 if should_update {
1108 update_idx = Some(idx);
1109 }
1110 break;
1111 }
1112 }
1113
1114 if let Some(idx) = update_idx {
1116 let relation = Relation::new(
1117 package,
1118 Some((VersionConstraint::GreaterThanEqual, minimum_version.clone())),
1119 );
1120 let mut entry = self.get_entry(idx).unwrap();
1122 entry.replace(0, relation);
1123 self.replace(idx, entry);
1124 }
1125
1126 for idx in obsolete_indices.into_iter().rev() {
1128 self.remove_entry(idx);
1129 }
1130
1131 if !found {
1133 let relation = Relation::new(
1134 package,
1135 Some((VersionConstraint::GreaterThanEqual, minimum_version.clone())),
1136 );
1137 self.push(Entry::from(relation));
1138 }
1139 }
1140
1141 pub fn ensure_exact_version(&mut self, package: &str, version: &Version) {
1156 let mut found = false;
1157 let mut update_idx = None;
1158
1159 let entries: Vec<_> = self.entries().collect();
1160 for (idx, entry) in entries.iter().enumerate() {
1161 let relations: Vec<_> = entry.relations().collect();
1162 let names: Vec<_> = relations.iter().map(|r| r.name()).collect();
1163
1164 if names.len() > 1 && names[0] == package {
1165 panic!("Complex rule for {}, aborting", package);
1166 }
1167
1168 if names.len() == 1 && names[0] == package {
1169 found = true;
1170 let relation = relations.into_iter().next().unwrap();
1171
1172 let should_update = if let Some((vc, ver)) = relation.version() {
1173 vc != VersionConstraint::Equal || &ver != version
1174 } else {
1175 true
1176 };
1177
1178 if should_update {
1179 update_idx = Some(idx);
1180 }
1181 break;
1182 }
1183 }
1184
1185 if let Some(idx) = update_idx {
1187 let relation =
1188 Relation::new(package, Some((VersionConstraint::Equal, version.clone())));
1189 let mut entry = self.get_entry(idx).unwrap();
1191 entry.replace(0, relation);
1192 self.replace(idx, entry);
1193 }
1194
1195 if !found {
1196 let relation =
1197 Relation::new(package, Some((VersionConstraint::Equal, version.clone())));
1198 self.push(Entry::from(relation));
1199 }
1200 }
1201
1202 pub fn ensure_some_version(&mut self, package: &str) {
1220 for entry in self.entries() {
1221 let relations: Vec<_> = entry.relations().collect();
1222 let names: Vec<_> = relations.iter().map(|r| r.name()).collect();
1223
1224 if names.len() > 1 && names[0] == package {
1225 panic!("Complex rule for {}, aborting", package);
1226 }
1227
1228 if names.len() == 1 && names[0] == package {
1229 return;
1231 }
1232 }
1233
1234 let relation = Relation::simple(package);
1236 self.push(Entry::from(relation));
1237 }
1238
1239 pub fn ensure_substvar(&mut self, substvar: &str) -> Result<(), String> {
1259 for existing in self.substvars() {
1261 if existing.trim() == substvar.trim() {
1262 return Ok(());
1263 }
1264 }
1265
1266 let (parsed, errors) = Relations::parse_relaxed(substvar, true);
1268 if !errors.is_empty() {
1269 return Err(errors.join("\n"));
1270 }
1271
1272 let whitespace = self.detect_whitespace_pattern(" ");
1274
1275 for substvar_node in parsed.0.children().filter(|n| n.kind() == SUBSTVAR) {
1277 let has_content = self.entries().next().is_some() || self.substvars().next().is_some();
1278
1279 let mut builder = GreenNodeBuilder::new();
1280 builder.start_node(ROOT.into());
1281
1282 for child in self.0.children_with_tokens() {
1284 match child {
1285 NodeOrToken::Node(n) => inject(&mut builder, n),
1286 NodeOrToken::Token(t) => builder.token(t.kind().into(), t.text()),
1287 }
1288 }
1289
1290 if has_content {
1292 builder.token(COMMA.into(), ",");
1293 builder.token(WHITESPACE.into(), whitespace.as_str());
1294 }
1295
1296 inject(&mut builder, substvar_node);
1298
1299 builder.finish_node();
1300 self.0 = SyntaxNode::new_root_mut(builder.finish());
1301 }
1302
1303 Ok(())
1304 }
1305
1306 pub fn drop_substvar(&mut self, substvar: &str) {
1323 let substvars_to_remove: Vec<_> = self
1325 .0
1326 .children()
1327 .filter_map(Substvar::cast)
1328 .filter(|s| s.to_string().trim() == substvar.trim())
1329 .collect();
1330
1331 for substvar_node in substvars_to_remove {
1332 let is_first = !substvar_node
1334 .0
1335 .siblings(Direction::Prev)
1336 .skip(1)
1337 .any(|n| n.kind() == ENTRY || n.kind() == SUBSTVAR);
1338
1339 let mut removed_comma = false;
1340
1341 while let Some(n) = substvar_node.0.next_sibling_or_token() {
1343 if n.kind() == WHITESPACE || n.kind() == NEWLINE {
1344 n.detach();
1345 } else if n.kind() == COMMA {
1346 n.detach();
1347 removed_comma = true;
1348 break;
1349 } else {
1350 break;
1351 }
1352 }
1353
1354 if !is_first {
1356 while let Some(n) = substvar_node.0.prev_sibling_or_token() {
1357 if n.kind() == WHITESPACE || n.kind() == NEWLINE {
1358 n.detach();
1359 } else if !removed_comma && n.kind() == COMMA {
1360 n.detach();
1361 break;
1362 } else {
1363 break;
1364 }
1365 }
1366 } else {
1367 while let Some(n) = substvar_node.0.next_sibling_or_token() {
1369 if n.kind() == WHITESPACE || n.kind() == NEWLINE {
1370 n.detach();
1371 } else {
1372 break;
1373 }
1374 }
1375 }
1376
1377 substvar_node.0.detach();
1379 }
1380 }
1381
1382 pub fn filter_entries<F>(&mut self, keep: F)
1398 where
1399 F: Fn(&Entry) -> bool,
1400 {
1401 let indices_to_remove: Vec<_> = self
1402 .entries()
1403 .enumerate()
1404 .filter_map(|(idx, entry)| if keep(&entry) { None } else { Some(idx) })
1405 .collect();
1406
1407 for idx in indices_to_remove.into_iter().rev() {
1409 self.remove_entry(idx);
1410 }
1411 }
1412
1413 pub fn is_sorted(&self, sorting_order: &impl SortingOrder) -> bool {
1429 let mut last_name: Option<String> = None;
1430 for entry in self.entries() {
1431 let mut relations = entry.relations();
1433 let Some(relation) = relations.next() else {
1434 continue;
1435 };
1436
1437 let name = relation.name();
1438
1439 if sorting_order.ignore(&name) {
1441 continue;
1442 }
1443
1444 if let Some(ref last) = last_name {
1446 if sorting_order.lt(&name, last) {
1447 return false;
1448 }
1449 }
1450
1451 last_name = Some(name);
1452 }
1453 true
1454 }
1455
1456 fn find_insert_position(&self, entry: &Entry) -> usize {
1468 let Some(relation) = entry.relations().next() else {
1470 return self.len();
1472 };
1473 let package_name = relation.name();
1474
1475 let count = self.entries().filter(|e| !e.is_empty()).count();
1477
1478 let sorting_order: Box<dyn SortingOrder> = if count < 2 {
1480 Box::new(WrapAndSortOrder)
1481 } else {
1482 if self.is_sorted(&WrapAndSortOrder) {
1485 Box::new(WrapAndSortOrder)
1486 } else if self.is_sorted(&DefaultSortingOrder) {
1487 Box::new(DefaultSortingOrder)
1488 } else {
1489 return self.len();
1491 }
1492 };
1493
1494 if sorting_order.ignore(&package_name) {
1496 return self.len();
1497 }
1498
1499 let mut position = 0;
1501 for (idx, existing_entry) in self.entries().enumerate() {
1502 let mut existing_relations = existing_entry.relations();
1503 let Some(existing_relation) = existing_relations.next() else {
1504 position += 1;
1506 continue;
1507 };
1508
1509 let existing_name = existing_relation.name();
1510
1511 if sorting_order.ignore(&existing_name) {
1513 position += 1;
1514 continue;
1515 }
1516
1517 if sorting_order.lt(&package_name, &existing_name) {
1519 return idx;
1520 }
1521 position += 1;
1522 }
1523
1524 position
1525 }
1526
1527 pub fn drop_dependency(&mut self, package: &str) -> bool {
1545 let indices_to_remove: Vec<_> = self
1546 .entries()
1547 .enumerate()
1548 .filter_map(|(idx, entry)| {
1549 let relations: Vec<_> = entry.relations().collect();
1550 let names: Vec<_> = relations.iter().map(|r| r.name()).collect();
1551 if names == vec![package] {
1552 Some(idx)
1553 } else {
1554 None
1555 }
1556 })
1557 .collect();
1558
1559 let found = !indices_to_remove.is_empty();
1560
1561 for idx in indices_to_remove.into_iter().rev() {
1563 self.remove_entry(idx);
1564 }
1565
1566 found
1567 }
1568
1569 pub fn add_dependency(&mut self, entry: Entry, position: Option<usize>) {
1590 let pos = position.unwrap_or_else(|| self.find_insert_position(&entry));
1591 self.insert(pos, entry);
1592 }
1593
1594 pub fn get_relation(&self, package: &str) -> Result<(usize, Entry), String> {
1618 for (idx, entry) in self.entries().enumerate() {
1619 let relations: Vec<_> = entry.relations().collect();
1620 let names: Vec<_> = relations.iter().map(|r| r.name()).collect();
1621
1622 if names.len() > 1 && names.contains(&package.to_string()) {
1623 return Err(format!("Complex rule for {}, aborting", package));
1624 }
1625
1626 if names.len() == 1 && names[0] == package {
1627 return Ok((idx, entry));
1628 }
1629 }
1630 Err(format!("Package {} not found", package))
1631 }
1632
1633 pub fn iter_relations_for(&self, package: &str) -> impl Iterator<Item = (usize, Entry)> + '_ {
1650 let package = package.to_string();
1651 self.entries().enumerate().filter(move |(_, entry)| {
1652 let names: Vec<_> = entry.relations().map(|r| r.name()).collect();
1653 names.contains(&package)
1654 })
1655 }
1656
1657 pub fn has_relation(&self, package: &str) -> bool {
1674 self.entries()
1675 .any(|entry| entry.relations().any(|r| r.name() == package))
1676 }
1677}
1678
1679impl From<Vec<Entry>> for Relations {
1680 fn from(entries: Vec<Entry>) -> Self {
1681 let mut builder = GreenNodeBuilder::new();
1682 builder.start_node(ROOT.into());
1683 for (i, entry) in entries.into_iter().enumerate() {
1684 if i > 0 {
1685 builder.token(COMMA.into(), ",");
1686 builder.token(WHITESPACE.into(), " ");
1687 }
1688 inject(&mut builder, entry.0);
1689 }
1690 builder.finish_node();
1691 Relations(SyntaxNode::new_root_mut(builder.finish()))
1692 }
1693}
1694
1695impl From<Entry> for Relations {
1696 fn from(entry: Entry) -> Self {
1697 Self::from(vec![entry])
1698 }
1699}
1700
1701impl Default for Entry {
1702 fn default() -> Self {
1703 Self::new()
1704 }
1705}
1706
1707impl PartialOrd for Entry {
1708 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
1709 let mut rels_a = self.relations();
1710 let mut rels_b = other.relations();
1711 while let (Some(a), Some(b)) = (rels_a.next(), rels_b.next()) {
1712 match a.cmp(&b) {
1713 std::cmp::Ordering::Equal => continue,
1714 x => return Some(x),
1715 }
1716 }
1717
1718 if rels_a.next().is_some() {
1719 return Some(std::cmp::Ordering::Greater);
1720 }
1721
1722 if rels_b.next().is_some() {
1723 return Some(std::cmp::Ordering::Less);
1724 }
1725
1726 Some(std::cmp::Ordering::Equal)
1727 }
1728}
1729
1730impl Eq for Entry {}
1731
1732impl Ord for Entry {
1733 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1734 self.partial_cmp(other).unwrap()
1735 }
1736}
1737
1738impl Entry {
1739 pub fn new() -> Self {
1741 let mut builder = GreenNodeBuilder::new();
1742 builder.start_node(SyntaxKind::ENTRY.into());
1743 builder.finish_node();
1744 Entry(SyntaxNode::new_root_mut(builder.finish()))
1745 }
1746
1747 pub fn replace(&mut self, idx: usize, relation: Relation) {
1749 let current_relation = self.get_relation(idx).unwrap();
1750
1751 let old_root = current_relation.0;
1752 let new_root = relation.0;
1753 let mut prev = new_root.first_child_or_token();
1755 let mut new_head_len = 0;
1756 while let Some(p) = prev {
1758 if p.kind() == WHITESPACE || p.kind() == NEWLINE {
1759 new_head_len += 1;
1760 prev = p.next_sibling_or_token();
1761 } else {
1762 break;
1763 }
1764 }
1765 let mut new_tail_len = 0;
1766 let mut next = new_root.last_child_or_token();
1767 while let Some(n) = next {
1768 if n.kind() == WHITESPACE || n.kind() == NEWLINE {
1769 new_tail_len += 1;
1770 next = n.prev_sibling_or_token();
1771 } else {
1772 break;
1773 }
1774 }
1775 let mut prev = old_root.first_child_or_token();
1777 let mut old_head = vec![];
1778 while let Some(p) = prev {
1779 if p.kind() == WHITESPACE || p.kind() == NEWLINE {
1780 old_head.push(p.clone());
1781 prev = p.next_sibling_or_token();
1782 } else {
1783 break;
1784 }
1785 }
1786 let mut old_tail = vec![];
1787 let mut next = old_root.last_child_or_token();
1788 while let Some(n) = next {
1789 if n.kind() == WHITESPACE || n.kind() == NEWLINE {
1790 old_tail.push(n.clone());
1791 next = n.prev_sibling_or_token();
1792 } else {
1793 break;
1794 }
1795 }
1796 new_root.splice_children(0..new_head_len, old_head);
1797 let tail_pos = new_root.children_with_tokens().count() - new_tail_len;
1798 new_root.splice_children(
1799 tail_pos - new_tail_len..tail_pos,
1800 old_tail.into_iter().rev(),
1801 );
1802 let index = old_root.index();
1803 self.0
1804 .splice_children(index..index + 1, vec![new_root.into()]);
1805 }
1806
1807 #[must_use]
1809 pub fn wrap_and_sort(&self) -> Self {
1810 let mut relations = self
1811 .relations()
1812 .map(|r| r.wrap_and_sort())
1813 .collect::<Vec<_>>();
1814 relations.sort();
1816 Self::from(relations)
1817 }
1818
1819 pub fn relations(&self) -> impl Iterator<Item = Relation> + '_ {
1821 self.0.children().filter_map(Relation::cast)
1822 }
1823
1824 pub fn iter(&self) -> impl Iterator<Item = Relation> + '_ {
1826 self.relations()
1827 }
1828
1829 pub fn get_relation(&self, idx: usize) -> Option<Relation> {
1831 self.relations().nth(idx)
1832 }
1833
1834 pub fn remove_relation(&self, idx: usize) -> Relation {
1847 let mut relation = self.get_relation(idx).unwrap();
1848 relation.remove();
1849 relation
1850 }
1851
1852 pub fn satisfied_by(&self, package_version: impl crate::VersionLookup + Copy) -> bool {
1868 self.relations().any(|r| {
1869 let actual = package_version.lookup_version(r.name().as_str());
1870 if let Some((vc, version)) = r.version() {
1871 if let Some(actual) = actual {
1872 match vc {
1873 VersionConstraint::GreaterThanEqual => *actual >= version,
1874 VersionConstraint::LessThanEqual => *actual <= version,
1875 VersionConstraint::Equal => *actual == version,
1876 VersionConstraint::GreaterThan => *actual > version,
1877 VersionConstraint::LessThan => *actual < version,
1878 }
1879 } else {
1880 false
1881 }
1882 } else {
1883 actual.is_some()
1884 }
1885 })
1886 }
1887
1888 pub fn remove(&mut self) {
1899 let mut removed_comma = false;
1900 let is_first = !self
1901 .0
1902 .siblings(Direction::Prev)
1903 .skip(1)
1904 .any(|n| n.kind() == ENTRY);
1905 while let Some(n) = self.0.next_sibling_or_token() {
1906 if n.kind() == WHITESPACE || n.kind() == NEWLINE {
1907 n.detach();
1908 } else if n.kind() == COMMA {
1909 n.detach();
1910 removed_comma = true;
1911 break;
1912 } else {
1913 panic!("Unexpected node: {:?}", n);
1914 }
1915 }
1916 if !is_first {
1917 while let Some(n) = self.0.prev_sibling_or_token() {
1918 if n.kind() == WHITESPACE || n.kind() == NEWLINE {
1919 n.detach();
1920 } else if !removed_comma && n.kind() == COMMA {
1921 n.detach();
1922 break;
1923 } else {
1924 break;
1925 }
1926 }
1927 } else {
1928 while let Some(n) = self.0.next_sibling_or_token() {
1929 if n.kind() == WHITESPACE || n.kind() == NEWLINE {
1930 n.detach();
1931 } else {
1932 break;
1933 }
1934 }
1935 }
1936 self.0.detach();
1937 }
1938
1939 pub fn is_empty(&self) -> bool {
1941 self.relations().count() == 0
1942 }
1943
1944 pub fn len(&self) -> usize {
1946 self.relations().count()
1947 }
1948
1949 pub fn push(&mut self, relation: Relation) {
1962 let is_empty = !self
1963 .0
1964 .children_with_tokens()
1965 .any(|n| n.kind() == PIPE || n.kind() == RELATION);
1966
1967 let (position, new_children) = if let Some(current_relation) = self.relations().last() {
1968 let to_insert: Vec<NodeOrToken<GreenNode, GreenToken>> = if is_empty {
1969 vec![relation.0.green().into()]
1970 } else {
1971 vec![
1972 NodeOrToken::Token(GreenToken::new(WHITESPACE.into(), " ")),
1973 NodeOrToken::Token(GreenToken::new(PIPE.into(), "|")),
1974 NodeOrToken::Token(GreenToken::new(WHITESPACE.into(), " ")),
1975 relation.0.green().into(),
1976 ]
1977 };
1978
1979 (current_relation.0.index() + 1, to_insert)
1980 } else {
1981 let child_count = self.0.children_with_tokens().count();
1982 (
1983 child_count,
1984 if is_empty {
1985 vec![relation.0.green().into()]
1986 } else {
1987 vec![
1988 NodeOrToken::Token(GreenToken::new(PIPE.into(), "|")),
1989 NodeOrToken::Token(GreenToken::new(WHITESPACE.into(), " ")),
1990 relation.0.green().into(),
1991 ]
1992 },
1993 )
1994 };
1995
1996 let new_root = SyntaxNode::new_root_mut(
1997 self.0.replace_with(
1998 self.0
1999 .green()
2000 .splice_children(position..position, new_children),
2001 ),
2002 );
2003
2004 if let Some(parent) = self.0.parent() {
2005 parent.splice_children(self.0.index()..self.0.index() + 1, vec![new_root.into()]);
2006 self.0 = parent
2007 .children_with_tokens()
2008 .nth(self.0.index())
2009 .unwrap()
2010 .clone()
2011 .into_node()
2012 .unwrap();
2013 } else {
2014 self.0 = new_root;
2015 }
2016 }
2017}
2018
2019fn inject(builder: &mut GreenNodeBuilder, node: SyntaxNode) {
2020 builder.start_node(node.kind().into());
2021 for child in node.children_with_tokens() {
2022 match child {
2023 rowan::NodeOrToken::Node(child) => {
2024 inject(builder, child);
2025 }
2026 rowan::NodeOrToken::Token(token) => {
2027 builder.token(token.kind().into(), token.text());
2028 }
2029 }
2030 }
2031 builder.finish_node();
2032}
2033
2034impl From<Vec<Relation>> for Entry {
2035 fn from(relations: Vec<Relation>) -> Self {
2036 let mut builder = GreenNodeBuilder::new();
2037 builder.start_node(SyntaxKind::ENTRY.into());
2038 for (i, relation) in relations.into_iter().enumerate() {
2039 if i > 0 {
2040 builder.token(WHITESPACE.into(), " ");
2041 builder.token(COMMA.into(), "|");
2042 builder.token(WHITESPACE.into(), " ");
2043 }
2044 inject(&mut builder, relation.0);
2045 }
2046 builder.finish_node();
2047 Entry(SyntaxNode::new_root_mut(builder.finish()))
2048 }
2049}
2050
2051impl From<Relation> for Entry {
2052 fn from(relation: Relation) -> Self {
2053 Self::from(vec![relation])
2054 }
2055}
2056
2057impl Relation {
2058 pub fn new(name: &str, version_constraint: Option<(VersionConstraint, Version)>) -> Self {
2072 let mut builder = GreenNodeBuilder::new();
2073 builder.start_node(SyntaxKind::RELATION.into());
2074 builder.token(IDENT.into(), name);
2075 if let Some((vc, version)) = version_constraint {
2076 builder.token(WHITESPACE.into(), " ");
2077 builder.start_node(SyntaxKind::VERSION.into());
2078 builder.token(L_PARENS.into(), "(");
2079 builder.start_node(SyntaxKind::CONSTRAINT.into());
2080 for c in vc.to_string().chars() {
2081 builder.token(
2082 match c {
2083 '>' => R_ANGLE.into(),
2084 '<' => L_ANGLE.into(),
2085 '=' => EQUAL.into(),
2086 _ => unreachable!(),
2087 },
2088 c.to_string().as_str(),
2089 );
2090 }
2091 builder.finish_node();
2092
2093 builder.token(WHITESPACE.into(), " ");
2094
2095 builder.token(IDENT.into(), version.to_string().as_str());
2096
2097 builder.token(R_PARENS.into(), ")");
2098
2099 builder.finish_node();
2100 }
2101
2102 builder.finish_node();
2103 Relation(SyntaxNode::new_root_mut(builder.finish()))
2104 }
2105
2106 #[must_use]
2115 pub fn wrap_and_sort(&self) -> Self {
2116 let mut builder = GreenNodeBuilder::new();
2117 builder.start_node(SyntaxKind::RELATION.into());
2118 builder.token(IDENT.into(), self.name().as_str());
2119 if let Some(archqual) = self.archqual() {
2120 builder.token(COLON.into(), ":");
2121 builder.token(IDENT.into(), archqual.as_str());
2122 }
2123 if let Some((vc, version)) = self.version() {
2124 builder.token(WHITESPACE.into(), " ");
2125 builder.start_node(SyntaxKind::VERSION.into());
2126 builder.token(L_PARENS.into(), "(");
2127 builder.start_node(SyntaxKind::CONSTRAINT.into());
2128 builder.token(
2129 match vc {
2130 VersionConstraint::GreaterThanEqual => R_ANGLE.into(),
2131 VersionConstraint::LessThanEqual => L_ANGLE.into(),
2132 VersionConstraint::Equal => EQUAL.into(),
2133 VersionConstraint::GreaterThan => R_ANGLE.into(),
2134 VersionConstraint::LessThan => L_ANGLE.into(),
2135 },
2136 vc.to_string().as_str(),
2137 );
2138 builder.finish_node();
2139 builder.token(WHITESPACE.into(), " ");
2140 builder.token(IDENT.into(), version.to_string().as_str());
2141 builder.token(R_PARENS.into(), ")");
2142 builder.finish_node();
2143 }
2144 if let Some(architectures) = self.architectures() {
2145 builder.token(WHITESPACE.into(), " ");
2146 builder.start_node(ARCHITECTURES.into());
2147 builder.token(L_BRACKET.into(), "[");
2148 for (i, arch) in architectures.enumerate() {
2149 if i > 0 {
2150 builder.token(WHITESPACE.into(), " ");
2151 }
2152 builder.token(IDENT.into(), arch.as_str());
2153 }
2154 builder.token(R_BRACKET.into(), "]");
2155 builder.finish_node();
2156 }
2157 for profiles in self.profiles() {
2158 builder.token(WHITESPACE.into(), " ");
2159 builder.start_node(PROFILES.into());
2160 builder.token(L_ANGLE.into(), "<");
2161 for (i, profile) in profiles.into_iter().enumerate() {
2162 if i > 0 {
2163 builder.token(WHITESPACE.into(), " ");
2164 }
2165 match profile {
2166 BuildProfile::Disabled(name) => {
2167 builder.token(NOT.into(), "!");
2168 builder.token(IDENT.into(), name.as_str());
2169 }
2170 BuildProfile::Enabled(name) => {
2171 builder.token(IDENT.into(), name.as_str());
2172 }
2173 }
2174 }
2175 builder.token(R_ANGLE.into(), ">");
2176 builder.finish_node();
2177 }
2178 builder.finish_node();
2179 Relation(SyntaxNode::new_root_mut(builder.finish()))
2180 }
2181
2182 pub fn simple(name: &str) -> Self {
2191 Self::new(name, None)
2192 }
2193
2194 pub fn drop_constraint(&mut self) -> bool {
2205 let version_token = self.0.children().find(|n| n.kind() == VERSION);
2206 if let Some(version_token) = version_token {
2207 while let Some(prev) = version_token.prev_sibling_or_token() {
2209 if prev.kind() == WHITESPACE || prev.kind() == NEWLINE {
2210 prev.detach();
2211 } else {
2212 break;
2213 }
2214 }
2215 version_token.detach();
2216 return true;
2217 }
2218
2219 false
2220 }
2221
2222 pub fn name(&self) -> String {
2231 self.0
2232 .children_with_tokens()
2233 .find_map(|it| match it {
2234 SyntaxElement::Token(token) if token.kind() == IDENT => Some(token),
2235 _ => None,
2236 })
2237 .unwrap()
2238 .text()
2239 .to_string()
2240 }
2241
2242 pub fn archqual(&self) -> Option<String> {
2251 let archqual = self.0.children().find(|n| n.kind() == ARCHQUAL);
2252 let node = if let Some(archqual) = archqual {
2253 archqual.children_with_tokens().find_map(|it| match it {
2254 SyntaxElement::Token(token) if token.kind() == IDENT => Some(token),
2255 _ => None,
2256 })
2257 } else {
2258 None
2259 };
2260 node.map(|n| n.text().to_string())
2261 }
2262
2263 pub fn set_archqual(&mut self, archqual: &str) {
2273 let mut builder = GreenNodeBuilder::new();
2274 builder.start_node(ARCHQUAL.into());
2275 builder.token(COLON.into(), ":");
2276 builder.token(IDENT.into(), archqual);
2277 builder.finish_node();
2278
2279 let node_archqual = self.0.children().find(|n| n.kind() == ARCHQUAL);
2280 if let Some(node_archqual) = node_archqual {
2281 self.0.splice_children(
2282 node_archqual.index()..node_archqual.index() + 1,
2283 vec![SyntaxNode::new_root_mut(builder.finish()).into()],
2284 );
2285 } else {
2286 let name_node = self.0.children_with_tokens().find(|n| n.kind() == IDENT);
2287 let idx = if let Some(name_node) = name_node {
2288 name_node.index() + 1
2289 } else {
2290 0
2291 };
2292 self.0.splice_children(
2293 idx..idx,
2294 vec![SyntaxNode::new_root_mut(builder.finish()).into()],
2295 );
2296 }
2297 }
2298
2299 pub fn version(&self) -> Option<(VersionConstraint, Version)> {
2301 let vc = self.0.children().find(|n| n.kind() == VERSION);
2302 let vc = vc.as_ref()?;
2303 let constraint = vc.children().find(|n| n.kind() == CONSTRAINT);
2304
2305 let version = vc.children_with_tokens().find_map(|it| match it {
2306 SyntaxElement::Token(token) if token.kind() == IDENT => Some(token),
2307 _ => None,
2308 });
2309
2310 if let (Some(constraint), Some(version)) = (constraint, version) {
2311 let vc: VersionConstraint = constraint.to_string().parse().unwrap();
2312 Some((vc, (version.text().to_string()).parse().unwrap()))
2313 } else {
2314 None
2315 }
2316 }
2317
2318 pub fn set_version(&mut self, version_constraint: Option<(VersionConstraint, Version)>) {
2329 let current_version = self.0.children().find(|n| n.kind() == VERSION);
2330 if let Some((vc, version)) = version_constraint {
2331 let mut builder = GreenNodeBuilder::new();
2332 builder.start_node(VERSION.into());
2333 builder.token(L_PARENS.into(), "(");
2334 builder.start_node(CONSTRAINT.into());
2335 match vc {
2336 VersionConstraint::GreaterThanEqual => {
2337 builder.token(R_ANGLE.into(), ">");
2338 builder.token(EQUAL.into(), "=");
2339 }
2340 VersionConstraint::LessThanEqual => {
2341 builder.token(L_ANGLE.into(), "<");
2342 builder.token(EQUAL.into(), "=");
2343 }
2344 VersionConstraint::Equal => {
2345 builder.token(EQUAL.into(), "=");
2346 }
2347 VersionConstraint::GreaterThan => {
2348 builder.token(R_ANGLE.into(), ">");
2349 }
2350 VersionConstraint::LessThan => {
2351 builder.token(L_ANGLE.into(), "<");
2352 }
2353 }
2354 builder.finish_node(); builder.token(WHITESPACE.into(), " ");
2356 builder.token(IDENT.into(), version.to_string().as_str());
2357 builder.token(R_PARENS.into(), ")");
2358 builder.finish_node(); if let Some(current_version) = current_version {
2361 self.0.splice_children(
2362 current_version.index()..current_version.index() + 1,
2363 vec![SyntaxNode::new_root_mut(builder.finish()).into()],
2364 );
2365 } else {
2366 let name_node = self.0.children_with_tokens().find(|n| n.kind() == IDENT);
2367 let idx = if let Some(name_node) = name_node {
2368 name_node.index() + 1
2369 } else {
2370 0
2371 };
2372 let new_children = vec![
2373 GreenToken::new(WHITESPACE.into(), " ").into(),
2374 builder.finish().into(),
2375 ];
2376 let new_root = SyntaxNode::new_root_mut(
2377 self.0.green().splice_children(idx..idx, new_children),
2378 );
2379 if let Some(parent) = self.0.parent() {
2380 parent
2381 .splice_children(self.0.index()..self.0.index() + 1, vec![new_root.into()]);
2382 self.0 = parent
2383 .children_with_tokens()
2384 .nth(self.0.index())
2385 .unwrap()
2386 .clone()
2387 .into_node()
2388 .unwrap();
2389 } else {
2390 self.0 = new_root;
2391 }
2392 }
2393 } else if let Some(current_version) = current_version {
2394 while let Some(prev) = current_version.prev_sibling_or_token() {
2396 if prev.kind() == WHITESPACE || prev.kind() == NEWLINE {
2397 prev.detach();
2398 } else {
2399 break;
2400 }
2401 }
2402 current_version.detach();
2403 }
2404 }
2405
2406 pub fn architectures(&self) -> Option<impl Iterator<Item = String> + '_> {
2415 let architectures = self.0.children().find(|n| n.kind() == ARCHITECTURES)?;
2416
2417 Some(architectures.children_with_tokens().filter_map(|node| {
2418 let token = node.as_token()?;
2419 if token.kind() == IDENT {
2420 Some(token.text().to_string())
2421 } else {
2422 None
2423 }
2424 }))
2425 }
2426
2427 pub fn profiles(&self) -> impl Iterator<Item = Vec<BuildProfile>> + '_ {
2437 let profiles = self.0.children().filter(|n| n.kind() == PROFILES);
2438
2439 profiles.map(|profile| {
2440 let mut ret = vec![];
2442 let mut current = vec![];
2443 for token in profile.children_with_tokens() {
2444 match token.kind() {
2445 WHITESPACE | NEWLINE => {
2446 if !current.is_empty() {
2447 ret.push(current.join("").parse::<BuildProfile>().unwrap());
2448 current = vec![];
2449 }
2450 }
2451 L_ANGLE | R_ANGLE => {}
2452 _ => {
2453 current.push(token.to_string());
2454 }
2455 }
2456 }
2457 if !current.is_empty() {
2458 ret.push(current.concat().parse().unwrap());
2459 }
2460 ret
2461 })
2462 }
2463
2464 pub fn remove(&mut self) {
2475 let is_first = !self
2476 .0
2477 .siblings(Direction::Prev)
2478 .skip(1)
2479 .any(|n| n.kind() == RELATION);
2480 if !is_first {
2481 while let Some(n) = self.0.prev_sibling_or_token() {
2484 if n.kind() == WHITESPACE || n.kind() == NEWLINE {
2485 n.detach();
2486 } else if n.kind() == PIPE {
2487 n.detach();
2488 break;
2489 } else {
2490 break;
2491 }
2492 }
2493 while let Some(n) = self.0.prev_sibling_or_token() {
2494 if n.kind() == WHITESPACE || n.kind() == NEWLINE {
2495 n.detach();
2496 } else {
2497 break;
2498 }
2499 }
2500 } else {
2501 while let Some(n) = self.0.next_sibling_or_token() {
2504 if n.kind() == WHITESPACE || n.kind() == NEWLINE {
2505 n.detach();
2506 } else if n.kind() == PIPE {
2507 n.detach();
2508 break;
2509 } else {
2510 panic!("Unexpected node: {:?}", n);
2511 }
2512 }
2513
2514 while let Some(n) = self.0.next_sibling_or_token() {
2515 if n.kind() == WHITESPACE || n.kind() == NEWLINE {
2516 n.detach();
2517 } else {
2518 break;
2519 }
2520 }
2521 }
2522 if let Some(mut parent) = self.0.parent().and_then(Entry::cast) {
2524 if parent.is_empty() {
2525 parent.remove();
2526 } else {
2527 self.0.detach();
2528 }
2529 } else {
2530 self.0.detach();
2531 }
2532 }
2533
2534 pub fn set_architectures<'a>(&mut self, architectures: impl Iterator<Item = &'a str>) {
2544 let mut builder = GreenNodeBuilder::new();
2545 builder.start_node(ARCHITECTURES.into());
2546 builder.token(L_BRACKET.into(), "[");
2547 for (i, arch) in architectures.enumerate() {
2548 if i > 0 {
2549 builder.token(WHITESPACE.into(), " ");
2550 }
2551 builder.token(IDENT.into(), arch);
2552 }
2553 builder.token(R_BRACKET.into(), "]");
2554 builder.finish_node();
2555
2556 let node_architectures = self.0.children().find(|n| n.kind() == ARCHITECTURES);
2557 if let Some(node_architectures) = node_architectures {
2558 let new_root = SyntaxNode::new_root_mut(builder.finish());
2559 self.0.splice_children(
2560 node_architectures.index()..node_architectures.index() + 1,
2561 vec![new_root.into()],
2562 );
2563 } else {
2564 let profiles = self.0.children().find(|n| n.kind() == PROFILES);
2565 let idx = if let Some(profiles) = profiles {
2566 profiles.index()
2567 } else {
2568 self.0.children_with_tokens().count()
2569 };
2570 let new_root = SyntaxNode::new_root(self.0.green().splice_children(
2571 idx..idx,
2572 vec![
2573 GreenToken::new(WHITESPACE.into(), " ").into(),
2574 builder.finish().into(),
2575 ],
2576 ));
2577 if let Some(parent) = self.0.parent() {
2578 parent.splice_children(self.0.index()..self.0.index() + 1, vec![new_root.into()]);
2579 self.0 = parent
2580 .children_with_tokens()
2581 .nth(self.0.index())
2582 .unwrap()
2583 .clone()
2584 .into_node()
2585 .unwrap();
2586 } else {
2587 self.0 = new_root;
2588 }
2589 }
2590 }
2591
2592 pub fn add_profile(&mut self, profile: &[BuildProfile]) {
2603 let mut builder = GreenNodeBuilder::new();
2604 builder.start_node(PROFILES.into());
2605 builder.token(L_ANGLE.into(), "<");
2606 for (i, profile) in profile.iter().enumerate() {
2607 if i > 0 {
2608 builder.token(WHITESPACE.into(), " ");
2609 }
2610 match profile {
2611 BuildProfile::Disabled(name) => {
2612 builder.token(NOT.into(), "!");
2613 builder.token(IDENT.into(), name.as_str());
2614 }
2615 BuildProfile::Enabled(name) => {
2616 builder.token(IDENT.into(), name.as_str());
2617 }
2618 }
2619 }
2620 builder.token(R_ANGLE.into(), ">");
2621 builder.finish_node();
2622
2623 let node_profiles = self.0.children().find(|n| n.kind() == PROFILES);
2624 if let Some(node_profiles) = node_profiles {
2625 let new_root = SyntaxNode::new_root_mut(builder.finish());
2626 self.0.splice_children(
2627 node_profiles.index()..node_profiles.index() + 1,
2628 vec![new_root.into()],
2629 );
2630 } else {
2631 let idx = self.0.children_with_tokens().count();
2632 let new_root = SyntaxNode::new_root(self.0.green().splice_children(
2633 idx..idx,
2634 vec![
2635 GreenToken::new(WHITESPACE.into(), " ").into(),
2636 builder.finish().into(),
2637 ],
2638 ));
2639 if let Some(parent) = self.0.parent() {
2640 parent.splice_children(self.0.index()..self.0.index() + 1, vec![new_root.into()]);
2641 self.0 = parent
2642 .children_with_tokens()
2643 .nth(self.0.index())
2644 .unwrap()
2645 .clone()
2646 .into_node()
2647 .unwrap();
2648 } else {
2649 self.0 = new_root;
2650 }
2651 }
2652 }
2653
2654 pub fn build(name: &str) -> RelationBuilder {
2656 RelationBuilder::new(name)
2657 }
2658}
2659
2660pub struct RelationBuilder {
2674 name: String,
2675 version_constraint: Option<(VersionConstraint, Version)>,
2676 archqual: Option<String>,
2677 architectures: Option<Vec<String>>,
2678 profiles: Vec<Vec<BuildProfile>>,
2679}
2680
2681impl RelationBuilder {
2682 fn new(name: &str) -> Self {
2684 Self {
2685 name: name.to_string(),
2686 version_constraint: None,
2687 archqual: None,
2688 architectures: None,
2689 profiles: vec![],
2690 }
2691 }
2692
2693 pub fn version_constraint(mut self, vc: VersionConstraint, version: Version) -> Self {
2695 self.version_constraint = Some((vc, version));
2696 self
2697 }
2698
2699 pub fn archqual(mut self, archqual: &str) -> Self {
2701 self.archqual = Some(archqual.to_string());
2702 self
2703 }
2704
2705 pub fn architectures(mut self, architectures: Vec<String>) -> Self {
2707 self.architectures = Some(architectures);
2708 self
2709 }
2710
2711 pub fn profiles(mut self, profiles: Vec<Vec<BuildProfile>>) -> Self {
2713 self.profiles = profiles;
2714 self
2715 }
2716
2717 pub fn add_profile(mut self, profile: Vec<BuildProfile>) -> Self {
2719 self.profiles.push(profile);
2720 self
2721 }
2722
2723 pub fn build(self) -> Relation {
2725 let mut relation = Relation::new(&self.name, self.version_constraint);
2726 if let Some(archqual) = &self.archqual {
2727 relation.set_archqual(archqual);
2728 }
2729 if let Some(architectures) = &self.architectures {
2730 relation.set_architectures(architectures.iter().map(|s| s.as_str()));
2731 }
2732 for profile in &self.profiles {
2733 relation.add_profile(profile);
2734 }
2735 relation
2736 }
2737}
2738
2739impl PartialOrd for Relation {
2740 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
2741 let name_cmp = self.name().cmp(&other.name());
2743 if name_cmp != std::cmp::Ordering::Equal {
2744 return Some(name_cmp);
2745 }
2746
2747 let self_version = self.version();
2748 let other_version = other.version();
2749
2750 match (self_version, other_version) {
2751 (Some((self_vc, self_version)), Some((other_vc, other_version))) => {
2752 let vc_cmp = self_vc.cmp(&other_vc);
2753 if vc_cmp != std::cmp::Ordering::Equal {
2754 return Some(vc_cmp);
2755 }
2756
2757 Some(self_version.cmp(&other_version))
2758 }
2759 (Some(_), None) => Some(std::cmp::Ordering::Greater),
2760 (None, Some(_)) => Some(std::cmp::Ordering::Less),
2761 (None, None) => Some(std::cmp::Ordering::Equal),
2762 }
2763 }
2764}
2765
2766impl Eq for Relation {}
2767
2768impl Ord for Relation {
2769 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
2770 self.partial_cmp(other).unwrap()
2771 }
2772}
2773
2774impl std::str::FromStr for Relations {
2775 type Err = String;
2776
2777 fn from_str(s: &str) -> Result<Self, Self::Err> {
2778 let parse = parse(s, false);
2779 if parse.errors.is_empty() {
2780 Ok(parse.root_mut())
2781 } else {
2782 Err(parse.errors.join("\n"))
2783 }
2784 }
2785}
2786
2787impl std::str::FromStr for Entry {
2788 type Err = String;
2789
2790 fn from_str(s: &str) -> Result<Self, Self::Err> {
2791 let root: Relations = s.parse()?;
2792
2793 let mut entries = root.entries();
2794 let entry = if let Some(entry) = entries.next() {
2795 entry
2796 } else {
2797 return Err("No entry found".to_string());
2798 };
2799
2800 if entries.next().is_some() {
2801 return Err("Multiple entries found".to_string());
2802 }
2803
2804 Ok(entry)
2805 }
2806}
2807
2808impl std::str::FromStr for Relation {
2809 type Err = String;
2810
2811 fn from_str(s: &str) -> Result<Self, Self::Err> {
2812 let entry: Entry = s.parse()?;
2813
2814 let mut relations = entry.relations();
2815 let relation = if let Some(relation) = relations.next() {
2816 relation
2817 } else {
2818 return Err("No relation found".to_string());
2819 };
2820
2821 if relations.next().is_some() {
2822 return Err("Multiple relations found".to_string());
2823 }
2824
2825 Ok(relation)
2826 }
2827}
2828
2829impl From<crate::lossy::Relation> for Relation {
2830 fn from(relation: crate::lossy::Relation) -> Self {
2831 let mut builder = Relation::build(&relation.name);
2832
2833 if let Some((vc, version)) = relation.version {
2834 builder = builder.version_constraint(vc, version);
2835 }
2836
2837 if let Some(archqual) = relation.archqual {
2838 builder = builder.archqual(&archqual);
2839 }
2840
2841 if let Some(architectures) = relation.architectures {
2842 builder = builder.architectures(architectures);
2843 }
2844
2845 builder = builder.profiles(relation.profiles);
2846
2847 builder.build()
2848 }
2849}
2850
2851impl From<Relation> for crate::lossy::Relation {
2852 fn from(relation: Relation) -> Self {
2853 crate::lossy::Relation {
2854 name: relation.name(),
2855 version: relation.version(),
2856 archqual: relation.archqual(),
2857 architectures: relation.architectures().map(|a| a.collect()),
2858 profiles: relation.profiles().collect(),
2859 }
2860 }
2861}
2862
2863impl From<Entry> for Vec<crate::lossy::Relation> {
2864 fn from(entry: Entry) -> Self {
2865 entry.relations().map(|r| r.into()).collect()
2866 }
2867}
2868
2869impl From<Vec<crate::lossy::Relation>> for Entry {
2870 fn from(relations: Vec<crate::lossy::Relation>) -> Self {
2871 let relations: Vec<Relation> = relations.into_iter().map(|r| r.into()).collect();
2872 Entry::from(relations)
2873 }
2874}
2875
2876#[cfg(test)]
2877mod tests {
2878 use super::*;
2879
2880 #[test]
2881 fn test_parse() {
2882 let input = "python3-dulwich";
2883 let parsed: Relations = input.parse().unwrap();
2884 assert_eq!(parsed.to_string(), input);
2885 assert_eq!(parsed.entries().count(), 1);
2886 let entry = parsed.entries().next().unwrap();
2887 assert_eq!(entry.to_string(), "python3-dulwich");
2888 assert_eq!(entry.relations().count(), 1);
2889 let relation = entry.relations().next().unwrap();
2890 assert_eq!(relation.to_string(), "python3-dulwich");
2891 assert_eq!(relation.version(), None);
2892
2893 let input = "python3-dulwich (>= 0.20.21)";
2894 let parsed: Relations = input.parse().unwrap();
2895 assert_eq!(parsed.to_string(), input);
2896 assert_eq!(parsed.entries().count(), 1);
2897 let entry = parsed.entries().next().unwrap();
2898 assert_eq!(entry.to_string(), "python3-dulwich (>= 0.20.21)");
2899 assert_eq!(entry.relations().count(), 1);
2900 let relation = entry.relations().next().unwrap();
2901 assert_eq!(relation.to_string(), "python3-dulwich (>= 0.20.21)");
2902 assert_eq!(
2903 relation.version(),
2904 Some((
2905 VersionConstraint::GreaterThanEqual,
2906 "0.20.21".parse().unwrap()
2907 ))
2908 );
2909 }
2910
2911 #[test]
2912 fn test_multiple() {
2913 let input = "python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)";
2914 let parsed: Relations = input.parse().unwrap();
2915 assert_eq!(parsed.to_string(), input);
2916 assert_eq!(parsed.entries().count(), 2);
2917 let entry = parsed.entries().next().unwrap();
2918 assert_eq!(entry.to_string(), "python3-dulwich (>= 0.20.21)");
2919 assert_eq!(entry.relations().count(), 1);
2920 let relation = entry.relations().next().unwrap();
2921 assert_eq!(relation.to_string(), "python3-dulwich (>= 0.20.21)");
2922 assert_eq!(
2923 relation.version(),
2924 Some((
2925 VersionConstraint::GreaterThanEqual,
2926 "0.20.21".parse().unwrap()
2927 ))
2928 );
2929 let entry = parsed.entries().nth(1).unwrap();
2930 assert_eq!(entry.to_string(), "python3-dulwich (<< 0.21)");
2931 assert_eq!(entry.relations().count(), 1);
2932 let relation = entry.relations().next().unwrap();
2933 assert_eq!(relation.to_string(), "python3-dulwich (<< 0.21)");
2934 assert_eq!(
2935 relation.version(),
2936 Some((VersionConstraint::LessThan, "0.21".parse().unwrap()))
2937 );
2938 }
2939
2940 #[test]
2941 fn test_architectures() {
2942 let input = "python3-dulwich [amd64 arm64 armhf i386 mips mips64el mipsel ppc64el s390x]";
2943 let parsed: Relations = input.parse().unwrap();
2944 assert_eq!(parsed.to_string(), input);
2945 assert_eq!(parsed.entries().count(), 1);
2946 let entry = parsed.entries().next().unwrap();
2947 assert_eq!(
2948 entry.to_string(),
2949 "python3-dulwich [amd64 arm64 armhf i386 mips mips64el mipsel ppc64el s390x]"
2950 );
2951 assert_eq!(entry.relations().count(), 1);
2952 let relation = entry.relations().next().unwrap();
2953 assert_eq!(
2954 relation.to_string(),
2955 "python3-dulwich [amd64 arm64 armhf i386 mips mips64el mipsel ppc64el s390x]"
2956 );
2957 assert_eq!(relation.version(), None);
2958 assert_eq!(
2959 relation.architectures().unwrap().collect::<Vec<_>>(),
2960 vec![
2961 "amd64", "arm64", "armhf", "i386", "mips", "mips64el", "mipsel", "ppc64el", "s390x"
2962 ]
2963 .into_iter()
2964 .map(|s| s.to_string())
2965 .collect::<Vec<_>>()
2966 );
2967 }
2968
2969 #[test]
2970 fn test_profiles() {
2971 let input = "foo (>= 1.0) [i386 arm] <!nocheck> <!cross>, bar";
2972 let parsed: Relations = input.parse().unwrap();
2973 assert_eq!(parsed.to_string(), input);
2974 assert_eq!(parsed.entries().count(), 2);
2975 let entry = parsed.entries().next().unwrap();
2976 assert_eq!(
2977 entry.to_string(),
2978 "foo (>= 1.0) [i386 arm] <!nocheck> <!cross>"
2979 );
2980 assert_eq!(entry.relations().count(), 1);
2981 let relation = entry.relations().next().unwrap();
2982 assert_eq!(
2983 relation.to_string(),
2984 "foo (>= 1.0) [i386 arm] <!nocheck> <!cross>"
2985 );
2986 assert_eq!(
2987 relation.version(),
2988 Some((VersionConstraint::GreaterThanEqual, "1.0".parse().unwrap()))
2989 );
2990 assert_eq!(
2991 relation.architectures().unwrap().collect::<Vec<_>>(),
2992 vec!["i386", "arm"]
2993 .into_iter()
2994 .map(|s| s.to_string())
2995 .collect::<Vec<_>>()
2996 );
2997 assert_eq!(
2998 relation.profiles().collect::<Vec<_>>(),
2999 vec![
3000 vec![BuildProfile::Disabled("nocheck".to_string())],
3001 vec![BuildProfile::Disabled("cross".to_string())]
3002 ]
3003 );
3004 }
3005
3006 #[test]
3007 fn test_substvar() {
3008 let input = "${shlibs:Depends}";
3009
3010 let (parsed, errors) = Relations::parse_relaxed(input, true);
3011 assert_eq!(errors, Vec::<String>::new());
3012 assert_eq!(parsed.to_string(), input);
3013 assert_eq!(parsed.entries().count(), 0);
3014
3015 assert_eq!(
3016 parsed.substvars().collect::<Vec<_>>(),
3017 vec!["${shlibs:Depends}"]
3018 );
3019 }
3020
3021 #[test]
3022 fn test_new() {
3023 let r = Relation::new(
3024 "samba",
3025 Some((VersionConstraint::GreaterThanEqual, "2.0".parse().unwrap())),
3026 );
3027
3028 assert_eq!(r.to_string(), "samba (>= 2.0)");
3029 }
3030
3031 #[test]
3032 fn test_drop_constraint() {
3033 let mut r = Relation::new(
3034 "samba",
3035 Some((VersionConstraint::GreaterThanEqual, "2.0".parse().unwrap())),
3036 );
3037
3038 r.drop_constraint();
3039
3040 assert_eq!(r.to_string(), "samba");
3041 }
3042
3043 #[test]
3044 fn test_simple() {
3045 let r = Relation::simple("samba");
3046
3047 assert_eq!(r.to_string(), "samba");
3048 }
3049
3050 #[test]
3051 fn test_remove_first_entry() {
3052 let mut rels: Relations = r#"python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)"#
3053 .parse()
3054 .unwrap();
3055 let removed = rels.remove_entry(0);
3056 assert_eq!(removed.to_string(), "python3-dulwich (>= 0.20.21)");
3057 assert_eq!(rels.to_string(), "python3-dulwich (<< 0.21)");
3058 }
3059
3060 #[test]
3061 fn test_remove_last_entry() {
3062 let mut rels: Relations = r#"python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)"#
3063 .parse()
3064 .unwrap();
3065 rels.remove_entry(1);
3066 assert_eq!(rels.to_string(), "python3-dulwich (>= 0.20.21)");
3067 }
3068
3069 #[test]
3070 fn test_remove_middle() {
3071 let mut rels: Relations =
3072 r#"python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21), python3-dulwich (<< 0.22)"#
3073 .parse()
3074 .unwrap();
3075 rels.remove_entry(1);
3076 assert_eq!(
3077 rels.to_string(),
3078 "python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.22)"
3079 );
3080 }
3081
3082 #[test]
3083 fn test_remove_added() {
3084 let mut rels: Relations = r#"python3-dulwich (>= 0.20.21)"#.parse().unwrap();
3085 let entry = Entry::from(vec![Relation::simple("python3-dulwich")]);
3086 rels.push(entry);
3087 rels.remove_entry(1);
3088 assert_eq!(rels.to_string(), "python3-dulwich (>= 0.20.21)");
3089 }
3090
3091 #[test]
3092 fn test_push() {
3093 let mut rels: Relations = r#"python3-dulwich (>= 0.20.21)"#.parse().unwrap();
3094 let entry = Entry::from(vec![Relation::simple("python3-dulwich")]);
3095 rels.push(entry);
3096 assert_eq!(
3097 rels.to_string(),
3098 "python3-dulwich (>= 0.20.21), python3-dulwich"
3099 );
3100 }
3101
3102 #[test]
3103 fn test_insert_with_custom_separator() {
3104 let mut rels: Relations = "python3".parse().unwrap();
3105 let entry = Entry::from(vec![Relation::simple("debhelper")]);
3106 rels.insert_with_separator(1, entry, Some("\n "));
3107 assert_eq!(rels.to_string(), "python3,\n debhelper");
3108 }
3109
3110 #[test]
3111 fn test_insert_with_wrap_and_sort_separator() {
3112 let mut rels: Relations = "python3".parse().unwrap();
3113 let entry = Entry::from(vec![Relation::simple("rustc")]);
3114 rels.insert_with_separator(1, entry, Some("\n "));
3116 assert_eq!(rels.to_string(), "python3,\n rustc");
3117 }
3118
3119 #[test]
3120 fn test_push_from_empty() {
3121 let mut rels: Relations = "".parse().unwrap();
3122 let entry = Entry::from(vec![Relation::simple("python3-dulwich")]);
3123 rels.push(entry);
3124 assert_eq!(rels.to_string(), "python3-dulwich");
3125 }
3126
3127 #[test]
3128 fn test_insert() {
3129 let mut rels: Relations = r#"python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)"#
3130 .parse()
3131 .unwrap();
3132 let entry = Entry::from(vec![Relation::simple("python3-dulwich")]);
3133 rels.insert(1, entry);
3134 assert_eq!(
3135 rels.to_string(),
3136 "python3-dulwich (>= 0.20.21), python3-dulwich, python3-dulwich (<< 0.21)"
3137 );
3138 }
3139
3140 #[test]
3141 fn test_insert_at_start() {
3142 let mut rels: Relations = r#"python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)"#
3143 .parse()
3144 .unwrap();
3145 let entry = Entry::from(vec![Relation::simple("python3-dulwich")]);
3146 rels.insert(0, entry);
3147 assert_eq!(
3148 rels.to_string(),
3149 "python3-dulwich, python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)"
3150 );
3151 }
3152
3153 #[test]
3154 fn test_insert_after_error() {
3155 let (mut rels, errors) = Relations::parse_relaxed("@foo@, debhelper (>= 1.0)", false);
3156 assert_eq!(
3157 errors,
3158 vec![
3159 "expected $ or identifier but got ERROR",
3160 "expected comma or end of file but got Some(IDENT)",
3161 "expected $ or identifier but got ERROR"
3162 ]
3163 );
3164 let entry = Entry::from(vec![Relation::simple("bar")]);
3165 rels.push(entry);
3166 assert_eq!(rels.to_string(), "@foo@, debhelper (>= 1.0), bar");
3167 }
3168
3169 #[test]
3170 fn test_insert_before_error() {
3171 let (mut rels, errors) = Relations::parse_relaxed("debhelper (>= 1.0), @foo@, bla", false);
3172 assert_eq!(
3173 errors,
3174 vec![
3175 "expected $ or identifier but got ERROR",
3176 "expected comma or end of file but got Some(IDENT)",
3177 "expected $ or identifier but got ERROR"
3178 ]
3179 );
3180 let entry = Entry::from(vec![Relation::simple("bar")]);
3181 rels.insert(0, entry);
3182 assert_eq!(rels.to_string(), "bar, debhelper (>= 1.0), @foo@, bla");
3183 }
3184
3185 #[test]
3186 fn test_replace() {
3187 let mut rels: Relations = r#"python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)"#
3188 .parse()
3189 .unwrap();
3190 let entry = Entry::from(vec![Relation::simple("python3-dulwich")]);
3191 rels.replace(1, entry);
3192 assert_eq!(
3193 rels.to_string(),
3194 "python3-dulwich (>= 0.20.21), python3-dulwich"
3195 );
3196 }
3197
3198 #[test]
3199 fn test_relation_from_entries() {
3200 let entries = vec![
3201 Entry::from(vec![Relation::simple("python3-dulwich")]),
3202 Entry::from(vec![Relation::simple("python3-breezy")]),
3203 ];
3204 let rels: Relations = entries.into();
3205 assert_eq!(rels.entries().count(), 2);
3206 assert_eq!(rels.to_string(), "python3-dulwich, python3-breezy");
3207 }
3208
3209 #[test]
3210 fn test_entry_from_relations() {
3211 let relations = vec![
3212 Relation::simple("python3-dulwich"),
3213 Relation::simple("python3-breezy"),
3214 ];
3215 let entry: Entry = relations.into();
3216 assert_eq!(entry.relations().count(), 2);
3217 assert_eq!(entry.to_string(), "python3-dulwich | python3-breezy");
3218 }
3219
3220 #[test]
3221 fn test_parse_entry() {
3222 let parsed: Entry = "python3-dulwich (>= 0.20.21) | bar".parse().unwrap();
3223 assert_eq!(parsed.to_string(), "python3-dulwich (>= 0.20.21) | bar");
3224 assert_eq!(parsed.relations().count(), 2);
3225
3226 assert_eq!(
3227 "foo, bar".parse::<Entry>().unwrap_err(),
3228 "Multiple entries found"
3229 );
3230 assert_eq!("".parse::<Entry>().unwrap_err(), "No entry found");
3231 }
3232
3233 #[test]
3234 fn test_parse_relation() {
3235 let parsed: Relation = "python3-dulwich (>= 0.20.21)".parse().unwrap();
3236 assert_eq!(parsed.to_string(), "python3-dulwich (>= 0.20.21)");
3237 assert_eq!(
3238 parsed.version(),
3239 Some((
3240 VersionConstraint::GreaterThanEqual,
3241 "0.20.21".parse().unwrap()
3242 ))
3243 );
3244 assert_eq!(
3245 "foo | bar".parse::<Relation>().unwrap_err(),
3246 "Multiple relations found"
3247 );
3248 assert_eq!("".parse::<Relation>().unwrap_err(), "No entry found");
3249 }
3250
3251 #[test]
3252 fn test_special() {
3253 let parsed: Relation = "librust-breezyshim+dirty-tracker-dev:amd64 (>= 0.1.138-~~)"
3254 .parse()
3255 .unwrap();
3256 assert_eq!(
3257 parsed.to_string(),
3258 "librust-breezyshim+dirty-tracker-dev:amd64 (>= 0.1.138-~~)"
3259 );
3260 assert_eq!(
3261 parsed.version(),
3262 Some((
3263 VersionConstraint::GreaterThanEqual,
3264 "0.1.138-~~".parse().unwrap()
3265 ))
3266 );
3267 assert_eq!(parsed.archqual(), Some("amd64".to_string()));
3268 assert_eq!(parsed.name(), "librust-breezyshim+dirty-tracker-dev");
3269 }
3270
3271 #[test]
3272 fn test_relations_satisfied_by() {
3273 let rels: Relations = "python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)"
3274 .parse()
3275 .unwrap();
3276 let satisfied = |name: &str| -> Option<debversion::Version> {
3277 match name {
3278 "python3-dulwich" => Some("0.20.21".parse().unwrap()),
3279 _ => None,
3280 }
3281 };
3282 assert!(rels.satisfied_by(satisfied));
3283
3284 let satisfied = |name: &str| match name {
3285 "python3-dulwich" => Some("0.21".parse().unwrap()),
3286 _ => None,
3287 };
3288 assert!(!rels.satisfied_by(satisfied));
3289
3290 let satisfied = |name: &str| match name {
3291 "python3-dulwich" => Some("0.20.20".parse().unwrap()),
3292 _ => None,
3293 };
3294 assert!(!rels.satisfied_by(satisfied));
3295 }
3296
3297 #[test]
3298 fn test_entry_satisfied_by() {
3299 let entry: Entry = "python3-dulwich (>= 0.20.21) | python3-dulwich (<< 0.18)"
3300 .parse()
3301 .unwrap();
3302 let satisfied = |name: &str| -> Option<debversion::Version> {
3303 match name {
3304 "python3-dulwich" => Some("0.20.21".parse().unwrap()),
3305 _ => None,
3306 }
3307 };
3308 assert!(entry.satisfied_by(satisfied));
3309 let satisfied = |name: &str| -> Option<debversion::Version> {
3310 match name {
3311 "python3-dulwich" => Some("0.18".parse().unwrap()),
3312 _ => None,
3313 }
3314 };
3315 assert!(!entry.satisfied_by(satisfied));
3316 }
3317
3318 #[test]
3319 fn test_wrap_and_sort_relation() {
3320 let relation: Relation = " python3-dulwich (>= 11) [ amd64 ] < lala>"
3321 .parse()
3322 .unwrap();
3323
3324 let wrapped = relation.wrap_and_sort();
3325
3326 assert_eq!(
3327 wrapped.to_string(),
3328 "python3-dulwich (>= 11) [amd64] <lala>"
3329 );
3330 }
3331
3332 #[test]
3333 fn test_wrap_and_sort_relations() {
3334 let entry: Relations =
3335 "python3-dulwich (>= 0.20.21) | bar, \n\n\n\npython3-dulwich (<< 0.21)"
3336 .parse()
3337 .unwrap();
3338
3339 let wrapped = entry.wrap_and_sort();
3340
3341 assert_eq!(
3342 wrapped.to_string(),
3343 "bar | python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)"
3344 );
3345 }
3346
3347 #[cfg(feature = "serde")]
3348 #[test]
3349 fn test_serialize_relations() {
3350 let relations: Relations = "python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)"
3351 .parse()
3352 .unwrap();
3353 let serialized = serde_json::to_string(&relations).unwrap();
3354 assert_eq!(
3355 serialized,
3356 r#""python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)""#
3357 );
3358 }
3359
3360 #[cfg(feature = "serde")]
3361 #[test]
3362 fn test_deserialize_relations() {
3363 let relations: Relations = "python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)"
3364 .parse()
3365 .unwrap();
3366 let serialized = serde_json::to_string(&relations).unwrap();
3367 let deserialized: Relations = serde_json::from_str(&serialized).unwrap();
3368 assert_eq!(deserialized.to_string(), relations.to_string());
3369 }
3370
3371 #[cfg(feature = "serde")]
3372 #[test]
3373 fn test_serialize_relation() {
3374 let relation: Relation = "python3-dulwich (>= 0.20.21)".parse().unwrap();
3375 let serialized = serde_json::to_string(&relation).unwrap();
3376 assert_eq!(serialized, r#""python3-dulwich (>= 0.20.21)""#);
3377 }
3378
3379 #[cfg(feature = "serde")]
3380 #[test]
3381 fn test_deserialize_relation() {
3382 let relation: Relation = "python3-dulwich (>= 0.20.21)".parse().unwrap();
3383 let serialized = serde_json::to_string(&relation).unwrap();
3384 let deserialized: Relation = serde_json::from_str(&serialized).unwrap();
3385 assert_eq!(deserialized.to_string(), relation.to_string());
3386 }
3387
3388 #[cfg(feature = "serde")]
3389 #[test]
3390 fn test_serialize_entry() {
3391 let entry: Entry = "python3-dulwich (>= 0.20.21) | python3-dulwich (<< 0.18)"
3392 .parse()
3393 .unwrap();
3394 let serialized = serde_json::to_string(&entry).unwrap();
3395 assert_eq!(
3396 serialized,
3397 r#""python3-dulwich (>= 0.20.21) | python3-dulwich (<< 0.18)""#
3398 );
3399 }
3400
3401 #[cfg(feature = "serde")]
3402 #[test]
3403 fn test_deserialize_entry() {
3404 let entry: Entry = "python3-dulwich (>= 0.20.21) | python3-dulwich (<< 0.18)"
3405 .parse()
3406 .unwrap();
3407 let serialized = serde_json::to_string(&entry).unwrap();
3408 let deserialized: Entry = serde_json::from_str(&serialized).unwrap();
3409 assert_eq!(deserialized.to_string(), entry.to_string());
3410 }
3411
3412 #[test]
3413 fn test_remove_first_relation() {
3414 let entry: Entry = "python3-dulwich (>= 0.20.21) | python3-dulwich (<< 0.18)"
3415 .parse()
3416 .unwrap();
3417 let mut rel = entry.relations().next().unwrap();
3418 rel.remove();
3419 assert_eq!(entry.to_string(), "python3-dulwich (<< 0.18)");
3420 }
3421
3422 #[test]
3423 fn test_remove_last_relation() {
3424 let entry: Entry = "python3-dulwich (>= 0.20.21) | python3-dulwich (<< 0.18)"
3425 .parse()
3426 .unwrap();
3427 let mut rel = entry.relations().nth(1).unwrap();
3428 rel.remove();
3429 assert_eq!(entry.to_string(), "python3-dulwich (>= 0.20.21)");
3430 }
3431
3432 #[test]
3433 fn test_remove_only_relation() {
3434 let entry: Entry = "python3-dulwich (>= 0.20.21)".parse().unwrap();
3435 let mut rel = entry.relations().next().unwrap();
3436 rel.remove();
3437 assert_eq!(entry.to_string(), "");
3438 }
3439
3440 #[test]
3441 fn test_relations_is_empty() {
3442 let entry: Relations = "python3-dulwich (>= 0.20.21)".parse().unwrap();
3443 assert!(!entry.is_empty());
3444 assert_eq!(1, entry.len());
3445 let mut rel = entry.entries().next().unwrap();
3446 rel.remove();
3447 assert!(entry.is_empty());
3448 assert_eq!(0, entry.len());
3449 }
3450
3451 #[test]
3452 fn test_entry_is_empty() {
3453 let entry: Entry = "python3-dulwich (>= 0.20.21) | python3-dulwich (<< 0.18)"
3454 .parse()
3455 .unwrap();
3456 assert!(!entry.is_empty());
3457 assert_eq!(2, entry.len());
3458 let mut rel = entry.relations().next().unwrap();
3459 rel.remove();
3460 assert!(!entry.is_empty());
3461 assert_eq!(1, entry.len());
3462 let mut rel = entry.relations().next().unwrap();
3463 rel.remove();
3464 assert!(entry.is_empty());
3465 assert_eq!(0, entry.len());
3466 }
3467
3468 #[test]
3469 fn test_relation_set_version() {
3470 let mut rel: Relation = "samba".parse().unwrap();
3471 rel.set_version(None);
3472 assert_eq!("samba", rel.to_string());
3473 rel.set_version(Some((
3474 VersionConstraint::GreaterThanEqual,
3475 "2.0".parse().unwrap(),
3476 )));
3477 assert_eq!("samba (>= 2.0)", rel.to_string());
3478 rel.set_version(None);
3479 assert_eq!("samba", rel.to_string());
3480 rel.set_version(Some((
3481 VersionConstraint::GreaterThanEqual,
3482 "2.0".parse().unwrap(),
3483 )));
3484 rel.set_version(Some((
3485 VersionConstraint::GreaterThanEqual,
3486 "1.1".parse().unwrap(),
3487 )));
3488 assert_eq!("samba (>= 1.1)", rel.to_string());
3489 }
3490
3491 #[test]
3492 fn test_replace_relation() {
3493 let mut entry: Entry = "python3-dulwich (>= 0.20.21) | python3-dulwich (<< 0.18)"
3494 .parse()
3495 .unwrap();
3496 let new_rel = Relation::simple("python3-breezy");
3497 entry.replace(0, new_rel);
3498 assert_eq!(
3499 entry.to_string(),
3500 "python3-breezy | python3-dulwich (<< 0.18)"
3501 );
3502 }
3503
3504 #[test]
3505 fn test_entry_push_relation() {
3506 let relations: Relations = "python3-dulwich (>= 0.20.21)".parse().unwrap();
3507 let new_rel = Relation::simple("python3-breezy");
3508 let mut entry = relations.entries().next().unwrap();
3509 entry.push(new_rel);
3510 assert_eq!(
3511 entry.to_string(),
3512 "python3-dulwich (>= 0.20.21) | python3-breezy"
3513 );
3514 assert_eq!(
3515 relations.to_string(),
3516 "python3-dulwich (>= 0.20.21) | python3-breezy"
3517 );
3518 }
3519
3520 #[test]
3521 fn test_relations_remove_empty_entry() {
3522 let (mut relations, errors) = Relations::parse_relaxed("foo, , bar, ", false);
3523 assert_eq!(errors, Vec::<String>::new());
3524 assert_eq!(relations.to_string(), "foo, , bar, ");
3525 assert_eq!(relations.len(), 2);
3526 assert_eq!(
3527 relations.entries().next().unwrap().to_string(),
3528 "foo".to_string()
3529 );
3530 assert_eq!(
3531 relations.entries().nth(1).unwrap().to_string(),
3532 "bar".to_string()
3533 );
3534 relations.remove_entry(1);
3535 assert_eq!(relations.to_string(), "foo, , ");
3536 }
3537
3538 #[test]
3539 fn test_entry_remove_relation() {
3540 let entry: Entry = "python3-dulwich | samba".parse().unwrap();
3541 let removed = entry.remove_relation(0);
3542 assert_eq!(removed.to_string(), "python3-dulwich");
3543 assert_eq!(entry.to_string(), "samba");
3544 }
3545
3546 #[test]
3547 fn test_wrap_and_sort_removes_empty_entries() {
3548 let relations: Relations = "foo, , bar, ".parse().unwrap();
3549 let wrapped = relations.wrap_and_sort();
3550 assert_eq!(wrapped.to_string(), "bar, foo");
3551 }
3552
3553 #[test]
3554 fn test_set_archqual() {
3555 let entry: Entry = "python3-dulwich | samba".parse().unwrap();
3556 let mut rel = entry.relations().next().unwrap();
3557 rel.set_archqual("amd64");
3558 assert_eq!(rel.to_string(), "python3-dulwich:amd64");
3559 assert_eq!(rel.archqual(), Some("amd64".to_string()));
3560 assert_eq!(entry.to_string(), "python3-dulwich:amd64 | samba");
3561 rel.set_archqual("i386");
3562 assert_eq!(rel.to_string(), "python3-dulwich:i386");
3563 assert_eq!(rel.archqual(), Some("i386".to_string()));
3564 assert_eq!(entry.to_string(), "python3-dulwich:i386 | samba");
3565 }
3566
3567 #[test]
3568 fn test_set_architectures() {
3569 let mut relation = Relation::simple("samba");
3570 relation.set_architectures(vec!["amd64", "i386"].into_iter());
3571 assert_eq!(relation.to_string(), "samba [amd64 i386]");
3572 }
3573
3574 #[test]
3575 fn test_relation_builder_no_architectures() {
3576 let relation = Relation::build("debhelper").build();
3578 assert_eq!(relation.to_string(), "debhelper");
3579 }
3580
3581 #[test]
3582 fn test_relation_builder_with_architectures() {
3583 let relation = Relation::build("samba")
3585 .architectures(vec!["amd64".to_string(), "i386".to_string()])
3586 .build();
3587 assert_eq!(relation.to_string(), "samba [amd64 i386]");
3588 }
3589
3590 #[test]
3591 fn test_ensure_minimum_version_add_new() {
3592 let mut relations: Relations = "python3".parse().unwrap();
3593 relations.ensure_minimum_version("debhelper", &"12".parse().unwrap());
3594 assert_eq!(relations.to_string(), "python3, debhelper (>= 12)");
3595 }
3596
3597 #[test]
3598 fn test_ensure_minimum_version_update() {
3599 let mut relations: Relations = "debhelper (>= 9)".parse().unwrap();
3600 relations.ensure_minimum_version("debhelper", &"12".parse().unwrap());
3601 assert_eq!(relations.to_string(), "debhelper (>= 12)");
3602 }
3603
3604 #[test]
3605 fn test_ensure_minimum_version_no_change() {
3606 let mut relations: Relations = "debhelper (>= 13)".parse().unwrap();
3607 relations.ensure_minimum_version("debhelper", &"12".parse().unwrap());
3608 assert_eq!(relations.to_string(), "debhelper (>= 13)");
3609 }
3610
3611 #[test]
3612 fn test_ensure_minimum_version_no_version() {
3613 let mut relations: Relations = "debhelper".parse().unwrap();
3614 relations.ensure_minimum_version("debhelper", &"12".parse().unwrap());
3615 assert_eq!(relations.to_string(), "debhelper (>= 12)");
3616 }
3617
3618 #[test]
3619 fn test_ensure_minimum_version_preserves_newline() {
3620 let input = "\n debhelper (>= 9),\n pkg-config,\n uuid-dev";
3626 let mut relations: Relations = input.parse().unwrap();
3627 relations.ensure_minimum_version("debhelper", &"12~".parse().unwrap());
3628 let result = relations.to_string();
3629
3630 assert!(
3632 result.starts_with('\n'),
3633 "Expected result to start with newline, got: {:?}",
3634 result
3635 );
3636 assert_eq!(result, "\n debhelper (>= 12~),\n pkg-config,\n uuid-dev");
3637 }
3638
3639 #[test]
3640 fn test_ensure_minimum_version_preserves_newline_in_control() {
3641 use crate::lossless::Control;
3643 use std::str::FromStr;
3644
3645 let input = r#"Source: f2fs-tools
3646Section: admin
3647Priority: optional
3648Maintainer: Test <test@example.com>
3649Build-Depends:
3650 debhelper (>= 9),
3651 pkg-config,
3652 uuid-dev
3653
3654Package: f2fs-tools
3655Description: test
3656"#;
3657
3658 let control = Control::from_str(input).unwrap();
3659 let mut source = control.source().unwrap();
3660 let mut build_depends = source.build_depends().unwrap();
3661
3662 let version = Version::from_str("12~").unwrap();
3663 build_depends.ensure_minimum_version("debhelper", &version);
3664
3665 source.set_build_depends(&build_depends);
3666
3667 let output = control.to_string();
3668
3669 assert!(
3671 output.contains("Build-Depends:\n debhelper (>= 12~)"),
3672 "Expected 'Build-Depends:\\n debhelper (>= 12~)' but got:\n{}",
3673 output
3674 );
3675 }
3676
3677 #[test]
3678 fn test_ensure_exact_version_add_new() {
3679 let mut relations: Relations = "python3".parse().unwrap();
3680 relations.ensure_exact_version("debhelper", &"12".parse().unwrap());
3681 assert_eq!(relations.to_string(), "python3, debhelper (= 12)");
3682 }
3683
3684 #[test]
3685 fn test_ensure_exact_version_update() {
3686 let mut relations: Relations = "debhelper (>= 9)".parse().unwrap();
3687 relations.ensure_exact_version("debhelper", &"12".parse().unwrap());
3688 assert_eq!(relations.to_string(), "debhelper (= 12)");
3689 }
3690
3691 #[test]
3692 fn test_ensure_exact_version_no_change() {
3693 let mut relations: Relations = "debhelper (= 12)".parse().unwrap();
3694 relations.ensure_exact_version("debhelper", &"12".parse().unwrap());
3695 assert_eq!(relations.to_string(), "debhelper (= 12)");
3696 }
3697
3698 #[test]
3699 fn test_ensure_some_version_add_new() {
3700 let mut relations: Relations = "python3".parse().unwrap();
3701 relations.ensure_some_version("debhelper");
3702 assert_eq!(relations.to_string(), "python3, debhelper");
3703 }
3704
3705 #[test]
3706 fn test_ensure_some_version_exists_with_version() {
3707 let mut relations: Relations = "debhelper (>= 12)".parse().unwrap();
3708 relations.ensure_some_version("debhelper");
3709 assert_eq!(relations.to_string(), "debhelper (>= 12)");
3710 }
3711
3712 #[test]
3713 fn test_ensure_some_version_exists_no_version() {
3714 let mut relations: Relations = "debhelper".parse().unwrap();
3715 relations.ensure_some_version("debhelper");
3716 assert_eq!(relations.to_string(), "debhelper");
3717 }
3718
3719 #[test]
3720 fn test_ensure_substvar() {
3721 let mut relations: Relations = "python3".parse().unwrap();
3722 relations.ensure_substvar("${misc:Depends}").unwrap();
3723 assert_eq!(relations.to_string(), "python3, ${misc:Depends}");
3724 }
3725
3726 #[test]
3727 fn test_ensure_substvar_already_exists() {
3728 let (mut relations, _) = Relations::parse_relaxed("python3, ${misc:Depends}", true);
3729 relations.ensure_substvar("${misc:Depends}").unwrap();
3730 assert_eq!(relations.to_string(), "python3, ${misc:Depends}");
3731 }
3732
3733 #[test]
3734 fn test_ensure_substvar_empty_relations() {
3735 let mut relations: Relations = Relations::new();
3736 relations.ensure_substvar("${misc:Depends}").unwrap();
3737 assert_eq!(relations.to_string(), "${misc:Depends}");
3738 }
3739
3740 #[test]
3741 fn test_ensure_substvar_preserves_whitespace() {
3742 let (mut relations, _) = Relations::parse_relaxed("python3, rustc", false);
3744 relations.ensure_substvar("${misc:Depends}").unwrap();
3745 assert_eq!(relations.to_string(), "python3, rustc, ${misc:Depends}");
3747 }
3748
3749 #[test]
3750 fn test_drop_substvar_basic() {
3751 let (mut relations, _) = Relations::parse_relaxed("python3, ${misc:Depends}", true);
3752 relations.drop_substvar("${misc:Depends}");
3753 assert_eq!(relations.to_string(), "python3");
3754 }
3755
3756 #[test]
3757 fn test_drop_substvar_first_position() {
3758 let (mut relations, _) = Relations::parse_relaxed("${misc:Depends}, python3", true);
3759 relations.drop_substvar("${misc:Depends}");
3760 assert_eq!(relations.to_string(), "python3");
3761 }
3762
3763 #[test]
3764 fn test_drop_substvar_middle_position() {
3765 let (mut relations, _) = Relations::parse_relaxed("python3, ${misc:Depends}, rustc", true);
3766 relations.drop_substvar("${misc:Depends}");
3767 assert_eq!(relations.to_string(), "python3, rustc");
3768 }
3769
3770 #[test]
3771 fn test_drop_substvar_only_substvar() {
3772 let (mut relations, _) = Relations::parse_relaxed("${misc:Depends}", true);
3773 relations.drop_substvar("${misc:Depends}");
3774 assert_eq!(relations.to_string(), "");
3775 }
3776
3777 #[test]
3778 fn test_drop_substvar_not_exists() {
3779 let (mut relations, _) = Relations::parse_relaxed("python3, rustc", false);
3780 relations.drop_substvar("${misc:Depends}");
3781 assert_eq!(relations.to_string(), "python3, rustc");
3782 }
3783
3784 #[test]
3785 fn test_drop_substvar_multiple_substvars() {
3786 let (mut relations, _) =
3787 Relations::parse_relaxed("python3, ${misc:Depends}, ${shlibs:Depends}", true);
3788 relations.drop_substvar("${misc:Depends}");
3789 assert_eq!(relations.to_string(), "python3, ${shlibs:Depends}");
3790 }
3791
3792 #[test]
3793 fn test_drop_substvar_preserves_whitespace() {
3794 let (mut relations, _) = Relations::parse_relaxed("python3, ${misc:Depends}", true);
3795 relations.drop_substvar("${misc:Depends}");
3796 assert_eq!(relations.to_string(), "python3");
3797 }
3798
3799 #[test]
3800 fn test_filter_entries_basic() {
3801 let mut relations: Relations = "python3, debhelper, rustc".parse().unwrap();
3802 relations.filter_entries(|entry| entry.relations().any(|r| r.name().starts_with("python")));
3803 assert_eq!(relations.to_string(), "python3");
3804 }
3805
3806 #[test]
3807 fn test_filter_entries_keep_all() {
3808 let mut relations: Relations = "python3, debhelper".parse().unwrap();
3809 relations.filter_entries(|_| true);
3810 assert_eq!(relations.to_string(), "python3, debhelper");
3811 }
3812
3813 #[test]
3814 fn test_filter_entries_remove_all() {
3815 let mut relations: Relations = "python3, debhelper".parse().unwrap();
3816 relations.filter_entries(|_| false);
3817 assert_eq!(relations.to_string(), "");
3818 }
3819
3820 #[test]
3821 fn test_filter_entries_keep_middle() {
3822 let mut relations: Relations = "aaa, bbb, ccc".parse().unwrap();
3823 relations.filter_entries(|entry| entry.relations().any(|r| r.name() == "bbb"));
3824 assert_eq!(relations.to_string(), "bbb");
3825 }
3826
3827 #[test]
3830 fn test_is_sorted_wrap_and_sort_order() {
3831 let relations: Relations = "debhelper, python3, rustc".parse().unwrap();
3833 assert!(relations.is_sorted(&WrapAndSortOrder));
3834
3835 let relations: Relations = "rustc, debhelper, python3".parse().unwrap();
3837 assert!(!relations.is_sorted(&WrapAndSortOrder));
3838
3839 let (relations, _) =
3841 Relations::parse_relaxed("cdbs, debhelper-compat, python3, ${misc:Depends}", true);
3842 assert!(relations.is_sorted(&WrapAndSortOrder));
3843 }
3844
3845 #[test]
3846 fn test_is_sorted_default_order() {
3847 let relations: Relations = "aaa, bbb, ccc".parse().unwrap();
3849 assert!(relations.is_sorted(&DefaultSortingOrder));
3850
3851 let relations: Relations = "ccc, aaa, bbb".parse().unwrap();
3853 assert!(!relations.is_sorted(&DefaultSortingOrder));
3854
3855 let (relations, _) = Relations::parse_relaxed("aaa, bbb, ${misc:Depends}", true);
3857 assert!(relations.is_sorted(&DefaultSortingOrder));
3858 }
3859
3860 #[test]
3861 fn test_is_sorted_with_substvars() {
3862 let (relations, _) = Relations::parse_relaxed("python3, ${misc:Depends}, rustc", true);
3864 assert!(relations.is_sorted(&DefaultSortingOrder));
3866 }
3867
3868 #[test]
3869 fn test_drop_dependency_exists() {
3870 let mut relations: Relations = "python3, debhelper, rustc".parse().unwrap();
3871 assert!(relations.drop_dependency("debhelper"));
3872 assert_eq!(relations.to_string(), "python3, rustc");
3873 }
3874
3875 #[test]
3876 fn test_drop_dependency_not_exists() {
3877 let mut relations: Relations = "python3, rustc".parse().unwrap();
3878 assert!(!relations.drop_dependency("nonexistent"));
3879 assert_eq!(relations.to_string(), "python3, rustc");
3880 }
3881
3882 #[test]
3883 fn test_drop_dependency_only_item() {
3884 let mut relations: Relations = "python3".parse().unwrap();
3885 assert!(relations.drop_dependency("python3"));
3886 assert_eq!(relations.to_string(), "");
3887 }
3888
3889 #[test]
3890 fn test_add_dependency_to_empty() {
3891 let mut relations: Relations = "".parse().unwrap();
3892 let entry = Entry::from(Relation::simple("python3"));
3893 relations.add_dependency(entry, None);
3894 assert_eq!(relations.to_string(), "python3");
3895 }
3896
3897 #[test]
3898 fn test_add_dependency_sorted_position() {
3899 let mut relations: Relations = "debhelper, rustc".parse().unwrap();
3900 let entry = Entry::from(Relation::simple("python3"));
3901 relations.add_dependency(entry, None);
3902 assert_eq!(relations.to_string(), "debhelper, python3, rustc");
3904 }
3905
3906 #[test]
3907 fn test_add_dependency_explicit_position() {
3908 let mut relations: Relations = "python3, rustc".parse().unwrap();
3909 let entry = Entry::from(Relation::simple("debhelper"));
3910 relations.add_dependency(entry, Some(0));
3911 assert_eq!(relations.to_string(), "debhelper, python3, rustc");
3912 }
3913
3914 #[test]
3915 fn test_add_dependency_build_system_first() {
3916 let mut relations: Relations = "python3, rustc".parse().unwrap();
3917 let entry = Entry::from(Relation::simple("debhelper-compat"));
3918 relations.add_dependency(entry, None);
3919 assert_eq!(relations.to_string(), "debhelper-compat, python3, rustc");
3921 }
3922
3923 #[test]
3924 fn test_add_dependency_at_end() {
3925 let mut relations: Relations = "debhelper, python3".parse().unwrap();
3926 let entry = Entry::from(Relation::simple("zzz-package"));
3927 relations.add_dependency(entry, None);
3928 assert_eq!(relations.to_string(), "debhelper, python3, zzz-package");
3930 }
3931
3932 #[test]
3933 fn test_add_dependency_to_single_entry() {
3934 let mut relations: Relations = "python3-dulwich".parse().unwrap();
3936 let entry: Entry = "debhelper-compat (= 12)".parse().unwrap();
3937 relations.add_dependency(entry, None);
3938 assert_eq!(
3940 relations.to_string(),
3941 "debhelper-compat (= 12), python3-dulwich"
3942 );
3943 }
3944
3945 #[test]
3946 fn test_get_relation_exists() {
3947 let relations: Relations = "python3, debhelper (>= 12), rustc".parse().unwrap();
3948 let result = relations.get_relation("debhelper");
3949 assert!(result.is_ok());
3950 let (idx, entry) = result.unwrap();
3951 assert_eq!(idx, 1);
3952 assert_eq!(entry.to_string(), "debhelper (>= 12)");
3953 }
3954
3955 #[test]
3956 fn test_get_relation_not_exists() {
3957 let relations: Relations = "python3, rustc".parse().unwrap();
3958 let result = relations.get_relation("nonexistent");
3959 assert_eq!(result, Err("Package nonexistent not found".to_string()));
3960 }
3961
3962 #[test]
3963 fn test_get_relation_complex_rule() {
3964 let relations: Relations = "python3 | python3-minimal, rustc".parse().unwrap();
3965 let result = relations.get_relation("python3");
3966 assert_eq!(
3967 result,
3968 Err("Complex rule for python3, aborting".to_string())
3969 );
3970 }
3971
3972 #[test]
3973 fn test_iter_relations_for_simple() {
3974 let relations: Relations = "python3, debhelper, python3-dev".parse().unwrap();
3975 let entries: Vec<_> = relations.iter_relations_for("python3").collect();
3976 assert_eq!(entries.len(), 1);
3977 assert_eq!(entries[0].0, 0);
3978 assert_eq!(entries[0].1.to_string(), "python3");
3979 }
3980
3981 #[test]
3982 fn test_iter_relations_for_alternatives() {
3983 let relations: Relations = "python3 | python3-minimal, python3-dev".parse().unwrap();
3984 let entries: Vec<_> = relations.iter_relations_for("python3").collect();
3985 assert_eq!(entries.len(), 1);
3987 assert_eq!(entries[0].0, 0);
3988 }
3989
3990 #[test]
3991 fn test_iter_relations_for_not_found() {
3992 let relations: Relations = "python3, rustc".parse().unwrap();
3993 let entries: Vec<_> = relations.iter_relations_for("debhelper").collect();
3994 assert_eq!(entries.len(), 0);
3995 }
3996
3997 #[test]
3998 fn test_has_relation_exists() {
3999 let relations: Relations = "python3, debhelper, rustc".parse().unwrap();
4000 assert!(relations.has_relation("debhelper"));
4001 assert!(relations.has_relation("python3"));
4002 assert!(relations.has_relation("rustc"));
4003 }
4004
4005 #[test]
4006 fn test_has_relation_not_exists() {
4007 let relations: Relations = "python3, rustc".parse().unwrap();
4008 assert!(!relations.has_relation("debhelper"));
4009 }
4010
4011 #[test]
4012 fn test_has_relation_in_alternative() {
4013 let relations: Relations = "python3 | python3-minimal".parse().unwrap();
4014 assert!(relations.has_relation("python3"));
4015 assert!(relations.has_relation("python3-minimal"));
4016 }
4017
4018 #[test]
4019 fn test_sorting_order_wrap_and_sort_build_systems() {
4020 let order = WrapAndSortOrder;
4021 assert!(order.lt("debhelper", "python3"));
4023 assert!(order.lt("debhelper-compat", "rustc"));
4024 assert!(order.lt("cdbs", "aaa"));
4025 assert!(order.lt("dh-python", "python3"));
4026 }
4027
4028 #[test]
4029 fn test_sorting_order_wrap_and_sort_regular_packages() {
4030 let order = WrapAndSortOrder;
4031 assert!(order.lt("aaa", "bbb"));
4033 assert!(order.lt("python3", "rustc"));
4034 assert!(!order.lt("rustc", "python3"));
4035 }
4036
4037 #[test]
4038 fn test_sorting_order_wrap_and_sort_substvars() {
4039 let order = WrapAndSortOrder;
4040 assert!(order.lt("python3", "${misc:Depends}"));
4042 assert!(!order.lt("${misc:Depends}", "python3"));
4043 assert!(!order.ignore("${misc:Depends}"));
4045 }
4046
4047 #[test]
4048 fn test_sorting_order_default_special_items() {
4049 let order = DefaultSortingOrder;
4050 assert!(order.lt("python3", "${misc:Depends}"));
4052 assert!(order.lt("aaa", "@cdbs@"));
4053 assert!(order.ignore("${misc:Depends}"));
4055 assert!(order.ignore("@cdbs@"));
4056 assert!(!order.ignore("python3"));
4057 }
4058
4059 #[test]
4060 fn test_is_special_package_name() {
4061 assert!(is_special_package_name("${misc:Depends}"));
4062 assert!(is_special_package_name("${shlibs:Depends}"));
4063 assert!(is_special_package_name("@cdbs@"));
4064 assert!(!is_special_package_name("python3"));
4065 assert!(!is_special_package_name("debhelper"));
4066 }
4067
4068 #[test]
4069 fn test_add_dependency_with_explicit_position() {
4070 let mut relations: Relations = "python3, rustc".parse().unwrap();
4072 let entry = Entry::from(Relation::simple("debhelper"));
4073 relations.add_dependency(entry, Some(1));
4074 assert_eq!(relations.to_string(), "python3, debhelper, rustc");
4076 }
4077
4078 #[test]
4079 fn test_whitespace_detection_single_space() {
4080 let mut relations: Relations = "python3, rustc".parse().unwrap();
4081 let entry = Entry::from(Relation::simple("debhelper"));
4082 relations.add_dependency(entry, Some(1));
4083 assert_eq!(relations.to_string(), "python3, debhelper, rustc");
4084 }
4085
4086 #[test]
4087 fn test_whitespace_detection_multiple_spaces() {
4088 let mut relations: Relations = "python3, rustc, gcc".parse().unwrap();
4089 let entry = Entry::from(Relation::simple("debhelper"));
4090 relations.add_dependency(entry, Some(1));
4091 assert_eq!(relations.to_string(), "python3, debhelper, rustc, gcc");
4093 }
4094
4095 #[test]
4096 fn test_whitespace_detection_mixed_patterns() {
4097 let mut relations: Relations = "a, b, c, d, e".parse().unwrap();
4099 let entry = Entry::from(Relation::simple("x"));
4100 relations.push(entry);
4101 assert_eq!(relations.to_string(), "a, b, c, d, e, x");
4104 }
4105
4106 #[test]
4107 fn test_whitespace_detection_newlines() {
4108 let mut relations: Relations = "python3,\n rustc".parse().unwrap();
4109 let entry = Entry::from(Relation::simple("debhelper"));
4110 relations.add_dependency(entry, Some(1));
4111 assert_eq!(relations.to_string(), "python3,\n debhelper,\n rustc");
4113 }
4114
4115 #[test]
4116 fn test_append_with_newline_no_trailing() {
4117 let mut relations: Relations = "foo,\n bar".parse().unwrap();
4118 let entry = Entry::from(Relation::simple("blah"));
4119 relations.add_dependency(entry, None);
4120 assert_eq!(relations.to_string(), "foo,\n bar,\n blah");
4121 }
4122
4123 #[test]
4124 fn test_append_with_trailing_newline() {
4125 let mut relations: Relations = "foo,\n bar\n".parse().unwrap();
4126 let entry = Entry::from(Relation::simple("blah"));
4127 relations.add_dependency(entry, None);
4128 assert_eq!(relations.to_string(), "foo,\n bar,\n blah");
4129 }
4130
4131 #[test]
4132 fn test_append_with_4_space_indent() {
4133 let mut relations: Relations = "foo,\n bar".parse().unwrap();
4134 let entry = Entry::from(Relation::simple("blah"));
4135 relations.add_dependency(entry, None);
4136 assert_eq!(relations.to_string(), "foo,\n bar,\n blah");
4137 }
4138
4139 #[test]
4140 fn test_append_with_4_space_and_trailing_newline() {
4141 let mut relations: Relations = "foo,\n bar\n".parse().unwrap();
4142 let entry = Entry::from(Relation::simple("blah"));
4143 relations.add_dependency(entry, None);
4144 assert_eq!(relations.to_string(), "foo,\n bar,\n blah");
4145 }
4146
4147 #[test]
4148 fn test_odd_syntax_append_no_trailing() {
4149 let mut relations: Relations = "\n foo\n , bar".parse().unwrap();
4150 let entry = Entry::from(Relation::simple("blah"));
4151 relations.add_dependency(entry, None);
4152 assert_eq!(relations.to_string(), "\n foo\n , bar\n , blah");
4153 }
4154
4155 #[test]
4156 fn test_odd_syntax_append_with_trailing() {
4157 let mut relations: Relations = "\n foo\n , bar\n".parse().unwrap();
4158 let entry = Entry::from(Relation::simple("blah"));
4159 relations.add_dependency(entry, None);
4160 assert_eq!(relations.to_string(), "\n foo\n , bar\n , blah");
4161 }
4162
4163 #[test]
4164 fn test_insert_at_1_no_trailing() {
4165 let mut relations: Relations = "foo,\n bar".parse().unwrap();
4166 let entry = Entry::from(Relation::simple("blah"));
4167 relations.add_dependency(entry, Some(1));
4168 assert_eq!(relations.to_string(), "foo,\n blah,\n bar");
4169 }
4170
4171 #[test]
4172 fn test_insert_at_1_with_trailing() {
4173 let mut relations: Relations = "foo,\n bar\n".parse().unwrap();
4174 let entry = Entry::from(Relation::simple("blah"));
4175 relations.add_dependency(entry, Some(1));
4176 assert_eq!(relations.to_string(), "foo,\n blah,\n bar");
4177 }
4178
4179 #[test]
4180 fn test_odd_syntax_insert_at_1() {
4181 let mut relations: Relations = "\n foo\n , bar\n".parse().unwrap();
4182 let entry = Entry::from(Relation::simple("blah"));
4183 relations.add_dependency(entry, Some(1));
4184 assert_eq!(relations.to_string(), "\n foo\n , blah\n , bar");
4185 }
4186
4187 #[test]
4188 fn test_relations_preserves_exact_whitespace() {
4189 let input =
4191 "debhelper (>= 10), quilt (>= 0.40),\n libsystemd-dev [linux-any], pkg-config";
4192
4193 let relations: Relations = input.parse().unwrap();
4194
4195 assert_eq!(
4197 relations.to_string(),
4198 input,
4199 "Relations should preserve exact whitespace from input"
4200 );
4201 }
4202
4203 #[test]
4204 fn test_remove_entry_preserves_indentation() {
4205 let input = "debhelper (>= 10), quilt (>= 0.40),\n libsystemd-dev [linux-any], dh-systemd (>= 1.5), pkg-config";
4207
4208 let mut relations: Relations = input.parse().unwrap();
4209
4210 let mut to_remove = Vec::new();
4212 for (idx, entry) in relations.entries().enumerate() {
4213 for relation in entry.relations() {
4214 if relation.name() == "dh-systemd" {
4215 to_remove.push(idx);
4216 break;
4217 }
4218 }
4219 }
4220
4221 for idx in to_remove.into_iter().rev() {
4222 relations.remove_entry(idx);
4223 }
4224
4225 let output = relations.to_string();
4226 println!("After removal: '{}'", output);
4227
4228 assert!(
4230 output.contains("\n libsystemd-dev"),
4231 "Expected 4-space indentation to be preserved, but got:\n'{}'",
4232 output
4233 );
4234 }
4235}