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)]
46pub enum 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 break;
108 }
109 }
110 }
111 if self.current() != Some(R_CURLY) {
112 self.error(format!("expected }} but got {:?}", self.current()).to_string());
113 } else {
114 self.bump();
115 }
116 self.builder.finish_node();
117 }
118
119 fn parse_entry(&mut self) {
120 self.skip_ws();
121 self.builder.start_node(SyntaxKind::ENTRY.into());
122 loop {
123 self.parse_relation();
124 match self.peek_past_ws() {
125 Some(COMMA) => {
126 break;
127 }
128 Some(PIPE) => {
129 self.skip_ws();
130 self.bump();
131 self.skip_ws();
132 }
133 None => {
134 self.skip_ws();
135 break;
136 }
137 _ => {
138 self.skip_ws();
139 self.builder.start_node(SyntaxKind::ERROR.into());
140 match self.tokens.pop() {
141 Some((k, t)) => {
142 self.builder.token(k.into(), t.as_str());
143 self.errors
144 .push(format!("Expected comma or pipe, not {:?}", (k, t)));
145 }
146 None => {
147 self.errors
148 .push("Expected comma or pipe, got end of file".to_string());
149 }
150 }
151 self.builder.finish_node();
152 }
153 }
154 }
155 self.builder.finish_node();
156 }
157
158 fn error(&mut self, error: String) {
159 self.errors.push(error);
160 self.builder.start_node(SyntaxKind::ERROR.into());
161 if self.current().is_some() {
162 self.bump();
163 }
164 self.builder.finish_node();
165 }
166
167 fn parse_relation(&mut self) {
168 self.builder.start_node(SyntaxKind::RELATION.into());
169 if self.current() == Some(IDENT) {
170 self.bump();
171 } else {
172 self.error("Expected package name".to_string());
173 }
174 match self.peek_past_ws() {
175 Some(COLON) => {
176 self.skip_ws();
177 self.builder.start_node(ARCHQUAL.into());
178 self.bump();
179 self.skip_ws();
180 if self.current() == Some(IDENT) {
181 self.bump();
182 } else {
183 self.error("Expected architecture name".to_string());
184 }
185 self.builder.finish_node();
186 self.skip_ws();
187 }
188 Some(PIPE) | Some(COMMA) => {}
189 None | Some(L_PARENS) | Some(L_BRACKET) | Some(L_ANGLE) => {
190 self.skip_ws();
191 }
192 e => {
193 self.skip_ws();
194 self.error(format!(
195 "Expected ':' or '|' or '[' or '<' or ',' but got {:?}",
196 e
197 ));
198 }
199 }
200
201 if self.peek_past_ws() == Some(L_PARENS) {
202 self.skip_ws();
203 self.builder.start_node(VERSION.into());
204 self.bump();
205 self.skip_ws();
206
207 self.builder.start_node(CONSTRAINT.into());
208
209 while self.current() == Some(L_ANGLE)
210 || self.current() == Some(R_ANGLE)
211 || self.current() == Some(EQUAL)
212 {
213 self.bump();
214 }
215
216 self.builder.finish_node();
217
218 self.skip_ws();
219
220 while matches!(self.current(), Some(IDENT) | Some(COLON)) {
223 self.bump();
224 }
225
226 if self.current() == Some(R_PARENS) {
227 self.bump();
228 } else {
229 self.error("Expected ')'".to_string());
230 }
231
232 self.builder.finish_node();
233 }
234
235 if self.peek_past_ws() == Some(L_BRACKET) {
236 self.skip_ws();
237 self.builder.start_node(ARCHITECTURES.into());
238 self.bump();
239 loop {
240 self.skip_ws();
241 match self.current() {
242 Some(NOT) => {
243 self.bump();
244 }
245 Some(IDENT) => {
246 self.bump();
247 }
248 Some(R_BRACKET) => {
249 self.bump();
250 break;
251 }
252 _ => {
253 self.error("Expected architecture name or '!' or ']'".to_string());
254 break;
255 }
256 }
257 }
258 self.builder.finish_node();
259 }
260
261 while self.peek_past_ws() == Some(L_ANGLE) {
262 self.skip_ws();
263 self.builder.start_node(PROFILES.into());
264 self.bump();
265
266 loop {
267 self.skip_ws();
268 match self.current() {
269 Some(IDENT) => {
270 self.bump();
271 }
272 Some(NOT) => {
273 self.bump();
274 self.skip_ws();
275 if self.current() == Some(IDENT) {
276 self.bump();
277 } else {
278 self.error("Expected profile".to_string());
279 }
280 }
281 Some(R_ANGLE) => {
282 self.bump();
283 break;
284 }
285 None => {
286 self.error("Expected profile or '>'".to_string());
287 break;
288 }
289 _ => {
290 self.error("Expected profile or '!' or '>'".to_string());
291 break;
292 }
293 }
294 }
295
296 self.builder.finish_node();
297 }
298
299 self.builder.finish_node();
300 }
301
302 fn parse(mut self) -> Parse {
303 self.builder.start_node(SyntaxKind::ROOT.into());
304
305 self.skip_ws();
306
307 while self.current().is_some() {
308 match self.current() {
309 Some(IDENT) => self.parse_entry(),
310 Some(DOLLAR) => {
311 if self.allow_substvar {
312 self.parse_substvar()
313 } else {
314 self.error("Substvars are not allowed".to_string());
315 }
316 }
317 Some(COMMA) => {
318 }
320 Some(c) => {
321 self.error(format!("expected $ or identifier but got {:?}", c));
322 }
323 None => {
324 self.error("expected identifier but got end of file".to_string());
325 }
326 }
327
328 self.skip_ws();
329 match self.current() {
330 Some(COMMA) => {
331 self.bump();
332 }
333 None => {
334 break;
335 }
336 c => {
337 self.error(format!("expected comma or end of file but got {:?}", c));
338 }
339 }
340 self.skip_ws();
341 }
342
343 self.builder.finish_node();
344 Parse {
346 green_node: self.builder.finish(),
347 errors: self.errors,
348 }
349 }
350 fn bump(&mut self) {
352 let (kind, text) = self.tokens.pop().unwrap();
353 self.builder.token(kind.into(), text.as_str());
354 }
355 fn current(&self) -> Option<SyntaxKind> {
357 self.tokens.last().map(|(kind, _)| *kind)
358 }
359 fn skip_ws(&mut self) {
360 while matches!(
361 self.current(),
362 Some(WHITESPACE) | Some(NEWLINE) | Some(COMMENT)
363 ) {
364 self.bump()
365 }
366 }
367
368 fn peek_past_ws(&self) -> Option<SyntaxKind> {
369 let mut i = self.tokens.len();
370 while i > 0 {
371 i -= 1;
372 match self.tokens[i].0 {
373 WHITESPACE | NEWLINE | COMMENT => {}
374 _ => return Some(self.tokens[i].0),
375 }
376 }
377 None
378 }
379 }
380
381 let mut tokens = crate::relations::lex(text);
382 tokens.reverse();
383 Parser {
384 tokens,
385 builder: GreenNodeBuilder::new(),
386 errors: Vec::new(),
387 allow_substvar,
388 }
389 .parse()
390}
391
392pub type SyntaxNode = rowan::SyntaxNode<Lang>;
400pub type SyntaxToken = rowan::SyntaxToken<Lang>;
402pub type SyntaxElement = rowan::NodeOrToken<SyntaxNode, SyntaxToken>;
404
405impl Parse {
406 fn root_mut(&self) -> Relations {
407 Relations::cast(SyntaxNode::new_root_mut(self.green_node.clone())).unwrap()
408 }
409}
410
411macro_rules! ast_node {
412 ($ast:ident, $kind:ident) => {
413 #[repr(transparent)]
415 pub struct $ast(SyntaxNode);
416 impl $ast {
417 #[allow(unused)]
418 fn cast(node: SyntaxNode) -> Option<Self> {
419 if node.kind() == $kind {
420 Some(Self(node))
421 } else {
422 None
423 }
424 }
425
426 pub fn syntax(&self) -> &rowan::SyntaxNode<Lang> {
428 &self.0
429 }
430 }
431
432 impl std::fmt::Display for $ast {
433 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
434 f.write_str(&self.0.text().to_string())
435 }
436 }
437 };
438}
439
440ast_node!(Relations, ROOT);
441ast_node!(Entry, ENTRY);
442ast_node!(Relation, RELATION);
443ast_node!(Substvar, SUBSTVAR);
444
445impl PartialEq for Relations {
446 fn eq(&self, other: &Self) -> bool {
447 self.entries().collect::<Vec<_>>() == other.entries().collect::<Vec<_>>()
448 }
449}
450
451impl PartialEq for Entry {
452 fn eq(&self, other: &Self) -> bool {
453 self.relations().collect::<Vec<_>>() == other.relations().collect::<Vec<_>>()
454 }
455}
456
457impl PartialEq for Relation {
458 fn eq(&self, other: &Self) -> bool {
459 self.name() == other.name()
460 && self.version() == other.version()
461 && self.archqual() == other.archqual()
462 && self.architectures().map(|x| x.collect::<HashSet<_>>())
463 == other.architectures().map(|x| x.collect::<HashSet<_>>())
464 && self.profiles().eq(other.profiles())
465 }
466}
467
468#[cfg(feature = "serde")]
469impl serde::Serialize for Relations {
470 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
471 let rep = self.to_string();
472 serializer.serialize_str(&rep)
473 }
474}
475
476#[cfg(feature = "serde")]
477impl<'de> serde::Deserialize<'de> for Relations {
478 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
479 let s = String::deserialize(deserializer)?;
480 let relations = s.parse().map_err(serde::de::Error::custom)?;
481 Ok(relations)
482 }
483}
484
485impl std::fmt::Debug for Relations {
486 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
487 let mut s = f.debug_struct("Relations");
488
489 for entry in self.entries() {
490 s.field("entry", &entry);
491 }
492
493 s.finish()
494 }
495}
496
497impl std::fmt::Debug for Entry {
498 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
499 let mut s = f.debug_struct("Entry");
500
501 for relation in self.relations() {
502 s.field("relation", &relation);
503 }
504
505 s.finish()
506 }
507}
508
509#[cfg(feature = "serde")]
510impl serde::Serialize for Entry {
511 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
512 let rep = self.to_string();
513 serializer.serialize_str(&rep)
514 }
515}
516
517#[cfg(feature = "serde")]
518impl<'de> serde::Deserialize<'de> for Entry {
519 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
520 let s = String::deserialize(deserializer)?;
521 let entry = s.parse().map_err(serde::de::Error::custom)?;
522 Ok(entry)
523 }
524}
525
526impl std::fmt::Debug for Relation {
527 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
528 let mut s = f.debug_struct("Relation");
529
530 s.field("name", &self.name());
531
532 if let Some((vc, version)) = self.version() {
533 s.field("version", &vc);
534 s.field("version", &version);
535 }
536
537 s.finish()
538 }
539}
540
541#[cfg(feature = "serde")]
542impl serde::Serialize for Relation {
543 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
544 let rep = self.to_string();
545 serializer.serialize_str(&rep)
546 }
547}
548
549#[cfg(feature = "serde")]
550impl<'de> serde::Deserialize<'de> for Relation {
551 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
552 let s = String::deserialize(deserializer)?;
553 let relation = s.parse().map_err(serde::de::Error::custom)?;
554 Ok(relation)
555 }
556}
557
558impl Default for Relations {
559 fn default() -> Self {
560 Self::new()
561 }
562}
563
564fn is_special_package_name(name: &str) -> bool {
569 if name.starts_with("${") && name.ends_with('}') {
571 return true;
572 }
573 if name.starts_with('@') && name.ends_with('@') {
575 return true;
576 }
577 false
578}
579
580pub trait SortingOrder {
582 fn lt(&self, name1: &str, name2: &str) -> bool;
586
587 fn ignore(&self, name: &str) -> bool;
589}
590
591#[derive(Debug, Clone, Copy, Default)]
593pub struct DefaultSortingOrder;
594
595impl SortingOrder for DefaultSortingOrder {
596 fn lt(&self, name1: &str, name2: &str) -> bool {
597 let special1 = is_special_package_name(name1);
598 let special2 = is_special_package_name(name2);
599
600 if special1 && !special2 {
602 return false;
603 }
604 if !special1 && special2 {
605 return true;
606 }
607 if special1 && special2 {
608 return false;
610 }
611
612 name1 < name2
614 }
615
616 fn ignore(&self, name: &str) -> bool {
617 is_special_package_name(name)
618 }
619}
620
621#[derive(Debug, Clone, Copy, Default)]
631pub struct WrapAndSortOrder;
632
633impl WrapAndSortOrder {
634 const BUILD_SYSTEMS: &'static [&'static str] = &[
636 "cdbs",
637 "debhelper-compat",
638 "debhelper",
639 "debputy",
640 "dpkg-build-api",
641 "dpkg-dev",
642 ];
643
644 fn get_sort_key<'a>(&self, name: &'a str) -> (i32, &'a str) {
645 if Self::BUILD_SYSTEMS.contains(&name) || name.starts_with("dh-") {
647 return (-1, name);
648 }
649
650 if name
652 .chars()
653 .next()
654 .is_some_and(|c| c.is_ascii_lowercase() || c.is_ascii_digit())
655 {
656 return (0, name);
657 }
658
659 (1, name)
661 }
662}
663
664impl SortingOrder for WrapAndSortOrder {
665 fn lt(&self, name1: &str, name2: &str) -> bool {
666 self.get_sort_key(name1) < self.get_sort_key(name2)
667 }
668
669 fn ignore(&self, _name: &str) -> bool {
670 false
672 }
673}
674
675impl Relations {
676 pub fn new() -> Self {
678 Self::from(vec![])
679 }
680
681 #[must_use]
683 pub fn wrap_and_sort(self) -> Self {
684 let mut entries = self
685 .entries()
686 .map(|e| e.wrap_and_sort())
687 .collect::<Vec<_>>();
688 entries.sort();
689 Self::from(entries)
691 }
692
693 pub fn entries(&self) -> impl Iterator<Item = Entry> + '_ {
695 self.0.children().filter_map(Entry::cast)
696 }
697
698 pub fn iter(&self) -> impl Iterator<Item = Entry> + '_ {
700 self.entries()
701 }
702
703 pub fn get_entry(&self, idx: usize) -> Option<Entry> {
705 self.entries().nth(idx)
706 }
707
708 pub fn remove_entry(&mut self, idx: usize) -> Entry {
710 let mut entry = self.get_entry(idx).unwrap();
711 entry.remove();
712 entry
713 }
714
715 fn collect_whitespace(start: Option<NodeOrToken<SyntaxNode, SyntaxToken>>) -> String {
717 let mut pattern = String::new();
718 let mut current = start;
719 while let Some(token) = current {
720 if matches!(token.kind(), WHITESPACE | NEWLINE | COMMENT) {
721 if let NodeOrToken::Token(t) = &token {
722 pattern.push_str(t.text());
723 }
724 current = token.next_sibling_or_token();
725 } else {
726 break;
727 }
728 }
729 pattern
730 }
731
732 fn to_green(node: &NodeOrToken<SyntaxNode, SyntaxToken>) -> NodeOrToken<GreenNode, GreenToken> {
734 match node {
735 NodeOrToken::Node(n) => NodeOrToken::Node(n.green().into()),
736 NodeOrToken::Token(t) => NodeOrToken::Token(t.green().to_owned()),
737 }
738 }
739
740 fn is_whitespace_token(token: &GreenToken) -> bool {
742 token.kind() == rowan::SyntaxKind(WHITESPACE as u16)
743 || token.kind() == rowan::SyntaxKind(NEWLINE as u16)
744 || token.kind() == rowan::SyntaxKind(COMMENT as u16)
745 }
746
747 fn strip_trailing_ws_from_children(
749 mut children: Vec<NodeOrToken<GreenNode, GreenToken>>,
750 ) -> Vec<NodeOrToken<GreenNode, GreenToken>> {
751 while let Some(last) = children.last() {
752 if let NodeOrToken::Token(t) = last {
753 if Self::is_whitespace_token(t) {
754 children.pop();
755 } else {
756 break;
757 }
758 } else {
759 break;
760 }
761 }
762 children
763 }
764
765 fn strip_relation_trailing_ws(relation: &SyntaxNode) -> GreenNode {
767 let children: Vec<_> = relation
768 .children_with_tokens()
769 .map(|c| Self::to_green(&c))
770 .collect();
771 let stripped = Self::strip_trailing_ws_from_children(children);
772 GreenNode::new(relation.kind().into(), stripped)
773 }
774
775 fn build_odd_syntax_nodes(
777 before_ws: &str,
778 after_ws: &str,
779 ) -> Vec<NodeOrToken<GreenNode, GreenToken>> {
780 [
781 (!before_ws.is_empty())
782 .then(|| NodeOrToken::Token(GreenToken::new(WHITESPACE.into(), before_ws))),
783 Some(NodeOrToken::Token(GreenToken::new(COMMA.into(), ","))),
784 (!after_ws.is_empty())
785 .then(|| NodeOrToken::Token(GreenToken::new(WHITESPACE.into(), after_ws))),
786 ]
787 .into_iter()
788 .flatten()
789 .collect()
790 }
791
792 fn detect_odd_syntax(&self) -> Option<(String, String)> {
794 for entry_node in self.entries() {
795 let mut node = entry_node.0.next_sibling_or_token()?;
796
797 let mut before = String::new();
799 while matches!(node.kind(), WHITESPACE | NEWLINE | COMMENT) {
800 if let NodeOrToken::Token(t) = &node {
801 before.push_str(t.text());
802 }
803 node = node.next_sibling_or_token()?;
804 }
805
806 if node.kind() == COMMA && !before.is_empty() {
808 let after = Self::collect_whitespace(node.next_sibling_or_token());
809 return Some((before, after));
810 }
811 }
812 None
813 }
814
815 fn detect_whitespace_pattern(&self, default: &str) -> String {
823 use std::collections::HashMap;
824
825 let entries: Vec<_> = self.entries().collect();
826 let num_entries = entries.len();
827
828 if num_entries == 0 {
829 if self.substvars().next().is_some() {
831 return default.to_string();
833 }
834 return String::from(""); }
836
837 if num_entries == 1 {
838 if let Some(node) = entries[0].0.next_sibling_or_token() {
840 if node.kind() == COMMA {
841 let pattern = Self::collect_whitespace(node.next_sibling_or_token());
842 if !pattern.is_empty() {
843 return pattern;
844 }
845 }
846 }
847 return default.to_string(); }
849
850 let mut whitespace_counts: HashMap<String, usize> = HashMap::new();
852
853 for (i, entry) in entries.iter().enumerate() {
854 if i == num_entries - 1 {
855 break; }
857
858 if let Some(mut node) = entry.0.next_sibling_or_token() {
860 while matches!(node.kind(), WHITESPACE | NEWLINE | COMMENT) {
862 if let Some(next) = node.next_sibling_or_token() {
863 node = next;
864 } else {
865 break;
866 }
867 }
868
869 if node.kind() == COMMA {
871 let pattern = Self::collect_whitespace(node.next_sibling_or_token());
872 if !pattern.is_empty() {
873 *whitespace_counts.entry(pattern).or_insert(0) += 1;
874 }
875 }
876 }
877 }
878
879 if whitespace_counts.len() == 1 {
881 if let Some((ws, _)) = whitespace_counts.iter().next() {
882 return ws.clone();
883 }
884 }
885
886 if let Some((ws, _)) = whitespace_counts.iter().max_by_key(|(_, count)| *count) {
888 return ws.clone();
889 }
890
891 default.to_string()
893 }
894
895 pub fn insert_with_separator(&mut self, idx: usize, entry: Entry, default_sep: Option<&str>) {
902 let is_empty = self.entries().next().is_none();
903 let whitespace = self.detect_whitespace_pattern(default_sep.unwrap_or(" "));
904
905 self.strip_trailing_whitespace();
907
908 let odd_syntax = self.detect_odd_syntax();
910
911 let (position, new_children) = if let Some(current_entry) = self.entries().nth(idx) {
912 let to_insert = if idx == 0 && is_empty {
913 vec![entry.0.green().into()]
914 } else if let Some((before_ws, after_ws)) = &odd_syntax {
915 let mut nodes = vec![entry.0.green().into()];
916 nodes.extend(Self::build_odd_syntax_nodes(before_ws, after_ws));
917 nodes
918 } else {
919 vec![
920 entry.0.green().into(),
921 NodeOrToken::Token(GreenToken::new(COMMA.into(), ",")),
922 NodeOrToken::Token(GreenToken::new(WHITESPACE.into(), whitespace.as_str())),
923 ]
924 };
925
926 (current_entry.0.index(), to_insert)
927 } else {
928 let child_count = self.0.children_with_tokens().count();
929 let to_insert = if idx == 0 {
930 vec![entry.0.green().into()]
931 } else if let Some((before_ws, after_ws)) = &odd_syntax {
932 let mut nodes = Self::build_odd_syntax_nodes(before_ws, after_ws);
933 nodes.push(entry.0.green().into());
934 nodes
935 } else {
936 vec![
937 NodeOrToken::Token(GreenToken::new(COMMA.into(), ",")),
938 NodeOrToken::Token(GreenToken::new(WHITESPACE.into(), whitespace.as_str())),
939 entry.0.green().into(),
940 ]
941 };
942
943 (child_count, to_insert)
944 };
945 self.0 = SyntaxNode::new_root_mut(
947 self.0.replace_with(
948 self.0
949 .green()
950 .splice_children(position..position, new_children),
951 ),
952 );
953 }
954
955 pub fn insert(&mut self, idx: usize, entry: Entry) {
957 self.insert_with_separator(idx, entry, None);
958 }
959
960 fn strip_entry_trailing_ws(entry: &SyntaxNode) -> GreenNode {
962 let mut children: Vec<_> = entry
963 .children_with_tokens()
964 .map(|c| Self::to_green(&c))
965 .collect();
966
967 if let Some(NodeOrToken::Node(last)) = children.last() {
969 if last.kind() == rowan::SyntaxKind(RELATION as u16) {
970 let relation_node = entry.children().last().unwrap();
972 children.pop();
973 children.push(NodeOrToken::Node(Self::strip_relation_trailing_ws(
974 &relation_node,
975 )));
976 }
977 }
978
979 let stripped = Self::strip_trailing_ws_from_children(children);
981 GreenNode::new(ENTRY.into(), stripped)
982 }
983
984 fn strip_trailing_whitespace(&mut self) {
985 let mut children: Vec<_> = self
986 .0
987 .children_with_tokens()
988 .map(|c| Self::to_green(&c))
989 .collect();
990
991 if let Some(NodeOrToken::Node(last)) = children.last() {
993 if last.kind() == rowan::SyntaxKind(ENTRY as u16) {
994 let last_entry = self.0.children().last().unwrap();
995 children.pop();
996 children.push(NodeOrToken::Node(Self::strip_entry_trailing_ws(
997 &last_entry,
998 )));
999 }
1000 }
1001
1002 let stripped = Self::strip_trailing_ws_from_children(children);
1004
1005 let nc = self.0.children_with_tokens().count();
1006 self.0 = SyntaxNode::new_root_mut(
1007 self.0
1008 .replace_with(self.0.green().splice_children(0..nc, stripped)),
1009 );
1010 }
1011
1012 pub fn replace(&mut self, idx: usize, entry: Entry) {
1014 let current_entry = self.get_entry(idx).unwrap();
1015 self.0.splice_children(
1016 current_entry.0.index()..current_entry.0.index() + 1,
1017 vec![entry.0.into()],
1018 );
1019 }
1020
1021 pub fn push(&mut self, entry: Entry) {
1023 let pos = self.entries().count();
1024 self.insert(pos, entry);
1025 }
1026
1027 pub fn substvars(&self) -> impl Iterator<Item = String> + '_ {
1029 self.0
1030 .children()
1031 .filter_map(Substvar::cast)
1032 .map(|s| s.to_string())
1033 }
1034
1035 pub fn parse_relaxed(s: &str, allow_substvar: bool) -> (Relations, Vec<String>) {
1037 let parse = parse(s, allow_substvar);
1038 (parse.root_mut(), parse.errors)
1039 }
1040
1041 pub fn satisfied_by(&self, package_version: impl crate::VersionLookup + Copy) -> bool {
1043 self.entries().all(|e| e.satisfied_by(package_version))
1044 }
1045
1046 pub fn is_empty(&self) -> bool {
1048 self.entries().count() == 0
1049 }
1050
1051 pub fn len(&self) -> usize {
1053 self.entries().count()
1054 }
1055
1056 pub fn ensure_minimum_version(&mut self, package: &str, minimum_version: &Version) {
1079 let mut found = false;
1080 let mut obsolete_indices = vec![];
1081 let mut update_idx = None;
1082
1083 let entries: Vec<_> = self.entries().collect();
1084 for (idx, entry) in entries.iter().enumerate() {
1085 let relations: Vec<_> = entry.relations().collect();
1086
1087 let names: Vec<_> = relations.iter().map(|r| r.name()).collect();
1089 if names.len() > 1 && names.contains(&package.to_string()) {
1090 let is_obsolete = relations.iter().any(|r| {
1092 if r.name() != package {
1093 return false;
1094 }
1095 if let Some((vc, ver)) = r.version() {
1096 matches!(vc, VersionConstraint::GreaterThan if &ver < minimum_version)
1097 || matches!(vc, VersionConstraint::GreaterThanEqual if &ver <= minimum_version)
1098 } else {
1099 false
1100 }
1101 });
1102 if is_obsolete {
1103 obsolete_indices.push(idx);
1104 }
1105 continue;
1106 }
1107
1108 if names.len() == 1 && names[0] == package {
1110 found = true;
1111 let relation = relations.into_iter().next().unwrap();
1112
1113 let should_update = if let Some((vc, ver)) = relation.version() {
1115 match vc {
1116 VersionConstraint::GreaterThanEqual | VersionConstraint::GreaterThan => {
1117 &ver < minimum_version
1118 }
1119 _ => false,
1120 }
1121 } else {
1122 true
1123 };
1124
1125 if should_update {
1126 update_idx = Some(idx);
1127 }
1128 break;
1129 }
1130 }
1131
1132 if let Some(idx) = update_idx {
1134 let relation = Relation::new(
1135 package,
1136 Some((VersionConstraint::GreaterThanEqual, minimum_version.clone())),
1137 );
1138 let mut entry = self.get_entry(idx).unwrap();
1140 entry.replace(0, relation);
1141 self.replace(idx, entry);
1142 }
1143
1144 for idx in obsolete_indices.into_iter().rev() {
1146 self.remove_entry(idx);
1147 }
1148
1149 if !found {
1151 let relation = Relation::new(
1152 package,
1153 Some((VersionConstraint::GreaterThanEqual, minimum_version.clone())),
1154 );
1155 self.push(Entry::from(relation));
1156 }
1157 }
1158
1159 pub fn ensure_exact_version(&mut self, package: &str, version: &Version) {
1174 let mut found = false;
1175 let mut update_idx = None;
1176
1177 let entries: Vec<_> = self.entries().collect();
1178 for (idx, entry) in entries.iter().enumerate() {
1179 let relations: Vec<_> = entry.relations().collect();
1180 let names: Vec<_> = relations.iter().map(|r| r.name()).collect();
1181
1182 if names.len() > 1 && names[0] == package {
1183 panic!("Complex rule for {}, aborting", package);
1184 }
1185
1186 if names.len() == 1 && names[0] == package {
1187 found = true;
1188 let relation = relations.into_iter().next().unwrap();
1189
1190 let should_update = if let Some((vc, ver)) = relation.version() {
1191 vc != VersionConstraint::Equal || &ver != version
1192 } else {
1193 true
1194 };
1195
1196 if should_update {
1197 update_idx = Some(idx);
1198 }
1199 break;
1200 }
1201 }
1202
1203 if let Some(idx) = update_idx {
1205 let relation =
1206 Relation::new(package, Some((VersionConstraint::Equal, version.clone())));
1207 let mut entry = self.get_entry(idx).unwrap();
1209 entry.replace(0, relation);
1210 self.replace(idx, entry);
1211 }
1212
1213 if !found {
1214 let relation =
1215 Relation::new(package, Some((VersionConstraint::Equal, version.clone())));
1216 self.push(Entry::from(relation));
1217 }
1218 }
1219
1220 pub fn ensure_some_version(&mut self, package: &str) {
1238 for entry in self.entries() {
1239 let relations: Vec<_> = entry.relations().collect();
1240 let names: Vec<_> = relations.iter().map(|r| r.name()).collect();
1241
1242 if names.len() > 1 && names[0] == package {
1243 panic!("Complex rule for {}, aborting", package);
1244 }
1245
1246 if names.len() == 1 && names[0] == package {
1247 return;
1249 }
1250 }
1251
1252 let relation = Relation::simple(package);
1254 self.push(Entry::from(relation));
1255 }
1256
1257 pub fn ensure_relation(&mut self, new_entry: Entry) -> bool {
1282 let mut to_replace: Vec<usize> = Vec::new();
1283 let mut to_remove: Vec<usize> = Vec::new();
1284 let mut already_satisfied = false;
1285
1286 for (idx, existing_entry) in self.entries().enumerate() {
1288 if new_entry.is_implied_by(&existing_entry) {
1289 already_satisfied = true;
1291 break;
1292 }
1293 if existing_entry.is_implied_by(&new_entry) {
1294 if to_replace.is_empty() {
1297 to_replace.push(idx);
1298 } else {
1299 to_remove.push(idx);
1300 }
1301 }
1302 }
1303
1304 if already_satisfied {
1305 return false;
1306 }
1307
1308 for idx in to_remove.into_iter().rev() {
1310 self.remove_entry(idx);
1311 }
1312
1313 if let Some(&idx) = to_replace.first() {
1315 self.replace(idx, new_entry);
1316 } else {
1317 self.add_dependency(new_entry, None);
1318 }
1319
1320 true
1321 }
1322
1323 pub fn ensure_substvar(&mut self, substvar: &str) -> Result<(), String> {
1343 for existing in self.substvars() {
1345 if existing.trim() == substvar.trim() {
1346 return Ok(());
1347 }
1348 }
1349
1350 let (parsed, errors) = Relations::parse_relaxed(substvar, true);
1352 if !errors.is_empty() {
1353 return Err(errors.join("\n"));
1354 }
1355
1356 let whitespace = self.detect_whitespace_pattern(" ");
1358
1359 for substvar_node in parsed.0.children().filter(|n| n.kind() == SUBSTVAR) {
1361 let has_content = self.entries().next().is_some() || self.substvars().next().is_some();
1362
1363 let mut builder = GreenNodeBuilder::new();
1364 builder.start_node(ROOT.into());
1365
1366 for child in self.0.children_with_tokens() {
1368 match child {
1369 NodeOrToken::Node(n) => inject(&mut builder, n),
1370 NodeOrToken::Token(t) => builder.token(t.kind().into(), t.text()),
1371 }
1372 }
1373
1374 if has_content {
1376 builder.token(COMMA.into(), ",");
1377 builder.token(WHITESPACE.into(), whitespace.as_str());
1378 }
1379
1380 inject(&mut builder, substvar_node);
1382
1383 builder.finish_node();
1384 self.0 = SyntaxNode::new_root_mut(builder.finish());
1385 }
1386
1387 Ok(())
1388 }
1389
1390 pub fn drop_substvar(&mut self, substvar: &str) {
1407 let substvars_to_remove: Vec<_> = self
1409 .0
1410 .children()
1411 .filter_map(Substvar::cast)
1412 .filter(|s| s.to_string().trim() == substvar.trim())
1413 .collect();
1414
1415 for substvar_node in substvars_to_remove {
1416 let is_first = !substvar_node
1418 .0
1419 .siblings(Direction::Prev)
1420 .skip(1)
1421 .any(|n| n.kind() == ENTRY || n.kind() == SUBSTVAR);
1422
1423 let mut removed_comma = false;
1424
1425 while let Some(n) = substvar_node.0.next_sibling_or_token() {
1427 if matches!(n.kind(), WHITESPACE | NEWLINE | COMMENT) {
1428 n.detach();
1429 } else if n.kind() == COMMA {
1430 n.detach();
1431 removed_comma = true;
1432 break;
1433 } else {
1434 break;
1435 }
1436 }
1437
1438 if !is_first {
1440 while let Some(n) = substvar_node.0.prev_sibling_or_token() {
1441 if matches!(n.kind(), WHITESPACE | NEWLINE | COMMENT) {
1442 n.detach();
1443 } else if !removed_comma && n.kind() == COMMA {
1444 n.detach();
1445 break;
1446 } else {
1447 break;
1448 }
1449 }
1450 } else {
1451 while let Some(n) = substvar_node.0.next_sibling_or_token() {
1453 if matches!(n.kind(), WHITESPACE | NEWLINE | COMMENT) {
1454 n.detach();
1455 } else {
1456 break;
1457 }
1458 }
1459 }
1460
1461 substvar_node.0.detach();
1463 }
1464 }
1465
1466 pub fn filter_entries<F>(&mut self, keep: F)
1482 where
1483 F: Fn(&Entry) -> bool,
1484 {
1485 let indices_to_remove: Vec<_> = self
1486 .entries()
1487 .enumerate()
1488 .filter_map(|(idx, entry)| if keep(&entry) { None } else { Some(idx) })
1489 .collect();
1490
1491 for idx in indices_to_remove.into_iter().rev() {
1493 self.remove_entry(idx);
1494 }
1495 }
1496
1497 pub fn is_sorted(&self, sorting_order: &impl SortingOrder) -> bool {
1513 let mut last_name: Option<String> = None;
1514 for entry in self.entries() {
1515 let mut relations = entry.relations();
1517 let Some(relation) = relations.next() else {
1518 continue;
1519 };
1520
1521 let name = relation.name();
1522
1523 if sorting_order.ignore(&name) {
1525 continue;
1526 }
1527
1528 if let Some(ref last) = last_name {
1530 if sorting_order.lt(&name, last) {
1531 return false;
1532 }
1533 }
1534
1535 last_name = Some(name);
1536 }
1537 true
1538 }
1539
1540 fn find_insert_position(&self, entry: &Entry) -> usize {
1552 let Some(relation) = entry.relations().next() else {
1554 return self.len();
1556 };
1557 let package_name = relation.name();
1558
1559 let count = self.entries().filter(|e| !e.is_empty()).count();
1561
1562 let sorting_order: Box<dyn SortingOrder> = if count < 2 {
1564 Box::new(WrapAndSortOrder)
1565 } else {
1566 if self.is_sorted(&WrapAndSortOrder) {
1569 Box::new(WrapAndSortOrder)
1570 } else if self.is_sorted(&DefaultSortingOrder) {
1571 Box::new(DefaultSortingOrder)
1572 } else {
1573 return self.len();
1575 }
1576 };
1577
1578 if sorting_order.ignore(&package_name) {
1580 return self.len();
1581 }
1582
1583 let mut position = 0;
1585 for (idx, existing_entry) in self.entries().enumerate() {
1586 let mut existing_relations = existing_entry.relations();
1587 let Some(existing_relation) = existing_relations.next() else {
1588 position += 1;
1590 continue;
1591 };
1592
1593 let existing_name = existing_relation.name();
1594
1595 if sorting_order.ignore(&existing_name) {
1597 position += 1;
1598 continue;
1599 }
1600
1601 if sorting_order.lt(&package_name, &existing_name) {
1603 return idx;
1604 }
1605 position += 1;
1606 }
1607
1608 position
1609 }
1610
1611 pub fn drop_dependency(&mut self, package: &str) -> bool {
1629 let indices_to_remove: Vec<_> = self
1630 .entries()
1631 .enumerate()
1632 .filter_map(|(idx, entry)| {
1633 let relations: Vec<_> = entry.relations().collect();
1634 let names: Vec<_> = relations.iter().map(|r| r.name()).collect();
1635 if names == vec![package] {
1636 Some(idx)
1637 } else {
1638 None
1639 }
1640 })
1641 .collect();
1642
1643 let found = !indices_to_remove.is_empty();
1644
1645 for idx in indices_to_remove.into_iter().rev() {
1647 self.remove_entry(idx);
1648 }
1649
1650 found
1651 }
1652
1653 pub fn add_dependency(&mut self, entry: Entry, position: Option<usize>) {
1674 let pos = position.unwrap_or_else(|| self.find_insert_position(&entry));
1675 self.insert(pos, entry);
1676 }
1677
1678 pub fn get_relation(&self, package: &str) -> Result<(usize, Entry), String> {
1702 for (idx, entry) in self.entries().enumerate() {
1703 let relations: Vec<_> = entry.relations().collect();
1704 let names: Vec<_> = relations.iter().map(|r| r.name()).collect();
1705
1706 if names.len() > 1 && names.contains(&package.to_string()) {
1707 return Err(format!("Complex rule for {}, aborting", package));
1708 }
1709
1710 if names.len() == 1 && names[0] == package {
1711 return Ok((idx, entry));
1712 }
1713 }
1714 Err(format!("Package {} not found", package))
1715 }
1716
1717 pub fn iter_relations_for(&self, package: &str) -> impl Iterator<Item = (usize, Entry)> + '_ {
1734 let package = package.to_string();
1735 self.entries().enumerate().filter(move |(_, entry)| {
1736 let names: Vec<_> = entry.relations().map(|r| r.name()).collect();
1737 names.contains(&package)
1738 })
1739 }
1740
1741 pub fn has_relation(&self, package: &str) -> bool {
1758 self.entries()
1759 .any(|entry| entry.relations().any(|r| r.name() == package))
1760 }
1761}
1762
1763impl From<Vec<Entry>> for Relations {
1764 fn from(entries: Vec<Entry>) -> Self {
1765 let mut builder = GreenNodeBuilder::new();
1766 builder.start_node(ROOT.into());
1767 for (i, entry) in entries.into_iter().enumerate() {
1768 if i > 0 {
1769 builder.token(COMMA.into(), ",");
1770 builder.token(WHITESPACE.into(), " ");
1771 }
1772 inject(&mut builder, entry.0);
1773 }
1774 builder.finish_node();
1775 Relations(SyntaxNode::new_root_mut(builder.finish()))
1776 }
1777}
1778
1779impl From<Entry> for Relations {
1780 fn from(entry: Entry) -> Self {
1781 Self::from(vec![entry])
1782 }
1783}
1784
1785impl Default for Entry {
1786 fn default() -> Self {
1787 Self::new()
1788 }
1789}
1790
1791impl PartialOrd for Entry {
1792 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
1793 Some(self.cmp(other))
1794 }
1795}
1796
1797impl Eq for Entry {}
1798
1799impl Ord for Entry {
1800 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1801 let mut rels_a = self.relations();
1802 let mut rels_b = other.relations();
1803 while let (Some(a), Some(b)) = (rels_a.next(), rels_b.next()) {
1804 match a.cmp(&b) {
1805 std::cmp::Ordering::Equal => continue,
1806 x => return x,
1807 }
1808 }
1809
1810 if rels_a.next().is_some() {
1811 return std::cmp::Ordering::Greater;
1812 }
1813
1814 if rels_b.next().is_some() {
1815 return std::cmp::Ordering::Less;
1816 }
1817
1818 std::cmp::Ordering::Equal
1819 }
1820}
1821
1822impl Entry {
1823 pub fn new() -> Self {
1825 let mut builder = GreenNodeBuilder::new();
1826 builder.start_node(SyntaxKind::ENTRY.into());
1827 builder.finish_node();
1828 Entry(SyntaxNode::new_root_mut(builder.finish()))
1829 }
1830
1831 pub fn replace(&mut self, idx: usize, relation: Relation) {
1833 let current_relation = self.get_relation(idx).unwrap();
1834
1835 let old_root = current_relation.0;
1836 let new_root = relation.0;
1837 let mut prev = new_root.first_child_or_token();
1839 let mut new_head_len = 0;
1840 while let Some(p) = prev {
1842 if p.kind() == WHITESPACE || p.kind() == NEWLINE {
1843 new_head_len += 1;
1844 prev = p.next_sibling_or_token();
1845 } else {
1846 break;
1847 }
1848 }
1849 let mut new_tail_len = 0;
1850 let mut next = new_root.last_child_or_token();
1851 while let Some(n) = next {
1852 if n.kind() == WHITESPACE || n.kind() == NEWLINE {
1853 new_tail_len += 1;
1854 next = n.prev_sibling_or_token();
1855 } else {
1856 break;
1857 }
1858 }
1859 let mut prev = old_root.first_child_or_token();
1861 let mut old_head = vec![];
1862 while let Some(p) = prev {
1863 if p.kind() == WHITESPACE || p.kind() == NEWLINE {
1864 old_head.push(p.clone());
1865 prev = p.next_sibling_or_token();
1866 } else {
1867 break;
1868 }
1869 }
1870 let mut old_tail = vec![];
1871 let mut next = old_root.last_child_or_token();
1872 while let Some(n) = next {
1873 if n.kind() == WHITESPACE || n.kind() == NEWLINE {
1874 old_tail.push(n.clone());
1875 next = n.prev_sibling_or_token();
1876 } else {
1877 break;
1878 }
1879 }
1880 new_root.splice_children(0..new_head_len, old_head);
1881 let tail_pos = new_root.children_with_tokens().count() - new_tail_len;
1882 new_root.splice_children(
1883 tail_pos - new_tail_len..tail_pos,
1884 old_tail.into_iter().rev(),
1885 );
1886 let index = old_root.index();
1887 self.0
1888 .splice_children(index..index + 1, vec![new_root.into()]);
1889 }
1890
1891 #[must_use]
1893 pub fn wrap_and_sort(&self) -> Self {
1894 let mut relations = self
1895 .relations()
1896 .map(|r| r.wrap_and_sort())
1897 .collect::<Vec<_>>();
1898 relations.sort();
1900 Self::from(relations)
1901 }
1902
1903 pub fn relations(&self) -> impl Iterator<Item = Relation> + '_ {
1905 self.0.children().filter_map(Relation::cast)
1906 }
1907
1908 pub fn iter(&self) -> impl Iterator<Item = Relation> + '_ {
1910 self.relations()
1911 }
1912
1913 pub fn get_relation(&self, idx: usize) -> Option<Relation> {
1915 self.relations().nth(idx)
1916 }
1917
1918 pub fn remove_relation(&self, idx: usize) -> Relation {
1931 let mut relation = self.get_relation(idx).unwrap();
1932 relation.remove();
1933 relation
1934 }
1935
1936 pub fn satisfied_by(&self, package_version: impl crate::VersionLookup + Copy) -> bool {
1952 self.relations().any(|r| {
1953 let actual = package_version.lookup_version(r.name().as_str());
1954 if let Some((vc, version)) = r.version() {
1955 if let Some(actual) = actual {
1956 match vc {
1957 VersionConstraint::GreaterThanEqual => *actual >= version,
1958 VersionConstraint::LessThanEqual => *actual <= version,
1959 VersionConstraint::Equal => *actual == version,
1960 VersionConstraint::GreaterThan => *actual > version,
1961 VersionConstraint::LessThan => *actual < version,
1962 }
1963 } else {
1964 false
1965 }
1966 } else {
1967 actual.is_some()
1968 }
1969 })
1970 }
1971
1972 pub fn remove(&mut self) {
1983 let mut removed_comma = false;
1984 let is_first = !self
1985 .0
1986 .siblings(Direction::Prev)
1987 .skip(1)
1988 .any(|n| n.kind() == ENTRY);
1989 while let Some(n) = self.0.next_sibling_or_token() {
1990 if matches!(n.kind(), WHITESPACE | NEWLINE | COMMENT) {
1991 n.detach();
1992 } else if n.kind() == COMMA {
1993 n.detach();
1994 removed_comma = true;
1995 break;
1996 } else {
1997 panic!("Unexpected node: {:?}", n);
1998 }
1999 }
2000 if !is_first {
2001 while let Some(n) = self.0.prev_sibling_or_token() {
2002 if matches!(n.kind(), WHITESPACE | NEWLINE | COMMENT) {
2003 n.detach();
2004 } else if !removed_comma && n.kind() == COMMA {
2005 n.detach();
2006 break;
2007 } else {
2008 break;
2009 }
2010 }
2011 } else {
2012 while let Some(n) = self.0.next_sibling_or_token() {
2013 if matches!(n.kind(), WHITESPACE | NEWLINE | COMMENT) {
2014 n.detach();
2015 } else {
2016 break;
2017 }
2018 }
2019 }
2020 self.0.detach();
2021 }
2022
2023 pub fn is_empty(&self) -> bool {
2025 self.relations().count() == 0
2026 }
2027
2028 pub fn len(&self) -> usize {
2030 self.relations().count()
2031 }
2032
2033 pub fn push(&mut self, relation: Relation) {
2046 let is_empty = !self
2047 .0
2048 .children_with_tokens()
2049 .any(|n| n.kind() == PIPE || n.kind() == RELATION);
2050
2051 let (position, new_children) = if let Some(current_relation) = self.relations().last() {
2052 let to_insert: Vec<NodeOrToken<GreenNode, GreenToken>> = if is_empty {
2053 vec![relation.0.green().into()]
2054 } else {
2055 vec![
2056 NodeOrToken::Token(GreenToken::new(WHITESPACE.into(), " ")),
2057 NodeOrToken::Token(GreenToken::new(PIPE.into(), "|")),
2058 NodeOrToken::Token(GreenToken::new(WHITESPACE.into(), " ")),
2059 relation.0.green().into(),
2060 ]
2061 };
2062
2063 (current_relation.0.index() + 1, to_insert)
2064 } else {
2065 let child_count = self.0.children_with_tokens().count();
2066 (
2067 child_count,
2068 if is_empty {
2069 vec![relation.0.green().into()]
2070 } else {
2071 vec![
2072 NodeOrToken::Token(GreenToken::new(PIPE.into(), "|")),
2073 NodeOrToken::Token(GreenToken::new(WHITESPACE.into(), " ")),
2074 relation.0.green().into(),
2075 ]
2076 },
2077 )
2078 };
2079
2080 let new_root = SyntaxNode::new_root_mut(
2081 self.0.replace_with(
2082 self.0
2083 .green()
2084 .splice_children(position..position, new_children),
2085 ),
2086 );
2087
2088 if let Some(parent) = self.0.parent() {
2089 parent.splice_children(self.0.index()..self.0.index() + 1, vec![new_root.into()]);
2090 self.0 = parent
2091 .children_with_tokens()
2092 .nth(self.0.index())
2093 .unwrap()
2094 .clone()
2095 .into_node()
2096 .unwrap();
2097 } else {
2098 self.0 = new_root;
2099 }
2100 }
2101
2102 pub fn is_implied_by(&self, outer: &Entry) -> bool {
2127 if self == outer {
2129 return true;
2130 }
2131
2132 for inner_rel in self.relations() {
2134 for outer_rel in outer.relations() {
2135 if inner_rel.is_implied_by(&outer_rel) {
2136 return true;
2137 }
2138 }
2139 }
2140
2141 false
2142 }
2143}
2144
2145fn inject(builder: &mut GreenNodeBuilder, node: SyntaxNode) {
2146 builder.start_node(node.kind().into());
2147 for child in node.children_with_tokens() {
2148 match child {
2149 rowan::NodeOrToken::Node(child) => {
2150 inject(builder, child);
2151 }
2152 rowan::NodeOrToken::Token(token) => {
2153 builder.token(token.kind().into(), token.text());
2154 }
2155 }
2156 }
2157 builder.finish_node();
2158}
2159
2160impl From<Vec<Relation>> for Entry {
2161 fn from(relations: Vec<Relation>) -> Self {
2162 let mut builder = GreenNodeBuilder::new();
2163 builder.start_node(SyntaxKind::ENTRY.into());
2164 for (i, relation) in relations.into_iter().enumerate() {
2165 if i > 0 {
2166 builder.token(WHITESPACE.into(), " ");
2167 builder.token(COMMA.into(), "|");
2168 builder.token(WHITESPACE.into(), " ");
2169 }
2170 inject(&mut builder, relation.0);
2171 }
2172 builder.finish_node();
2173 Entry(SyntaxNode::new_root_mut(builder.finish()))
2174 }
2175}
2176
2177impl From<Relation> for Entry {
2178 fn from(relation: Relation) -> Self {
2179 Self::from(vec![relation])
2180 }
2181}
2182
2183fn tokenize_version(builder: &mut GreenNodeBuilder, version: &Version) {
2186 let version_str = version.to_string();
2187
2188 if let Some(colon_pos) = version_str.find(':') {
2190 builder.token(IDENT.into(), &version_str[..colon_pos]);
2192 builder.token(COLON.into(), ":");
2193 builder.token(IDENT.into(), &version_str[colon_pos + 1..]);
2195 } else {
2196 builder.token(IDENT.into(), version_str.as_str());
2198 }
2199}
2200
2201impl Relation {
2202 pub fn new(name: &str, version_constraint: Option<(VersionConstraint, Version)>) -> Self {
2216 let mut builder = GreenNodeBuilder::new();
2217 builder.start_node(SyntaxKind::RELATION.into());
2218 builder.token(IDENT.into(), name);
2219 if let Some((vc, version)) = version_constraint {
2220 builder.token(WHITESPACE.into(), " ");
2221 builder.start_node(SyntaxKind::VERSION.into());
2222 builder.token(L_PARENS.into(), "(");
2223 builder.start_node(SyntaxKind::CONSTRAINT.into());
2224 for c in vc.to_string().chars() {
2225 builder.token(
2226 match c {
2227 '>' => R_ANGLE.into(),
2228 '<' => L_ANGLE.into(),
2229 '=' => EQUAL.into(),
2230 _ => unreachable!(),
2231 },
2232 c.to_string().as_str(),
2233 );
2234 }
2235 builder.finish_node();
2236
2237 builder.token(WHITESPACE.into(), " ");
2238
2239 tokenize_version(&mut builder, &version);
2240
2241 builder.token(R_PARENS.into(), ")");
2242
2243 builder.finish_node();
2244 }
2245
2246 builder.finish_node();
2247 Relation(SyntaxNode::new_root_mut(builder.finish()))
2248 }
2249
2250 #[must_use]
2259 pub fn wrap_and_sort(&self) -> Self {
2260 let mut builder = GreenNodeBuilder::new();
2261 builder.start_node(SyntaxKind::RELATION.into());
2262 builder.token(IDENT.into(), self.name().as_str());
2263 if let Some(archqual) = self.archqual() {
2264 builder.token(COLON.into(), ":");
2265 builder.token(IDENT.into(), archqual.as_str());
2266 }
2267 if let Some((vc, version)) = self.version() {
2268 builder.token(WHITESPACE.into(), " ");
2269 builder.start_node(SyntaxKind::VERSION.into());
2270 builder.token(L_PARENS.into(), "(");
2271 builder.start_node(SyntaxKind::CONSTRAINT.into());
2272 builder.token(
2273 match vc {
2274 VersionConstraint::GreaterThanEqual => R_ANGLE.into(),
2275 VersionConstraint::LessThanEqual => L_ANGLE.into(),
2276 VersionConstraint::Equal => EQUAL.into(),
2277 VersionConstraint::GreaterThan => R_ANGLE.into(),
2278 VersionConstraint::LessThan => L_ANGLE.into(),
2279 },
2280 vc.to_string().as_str(),
2281 );
2282 builder.finish_node();
2283 builder.token(WHITESPACE.into(), " ");
2284 tokenize_version(&mut builder, &version);
2285 builder.token(R_PARENS.into(), ")");
2286 builder.finish_node();
2287 }
2288 if let Some(architectures) = self.architectures() {
2289 builder.token(WHITESPACE.into(), " ");
2290 builder.start_node(ARCHITECTURES.into());
2291 builder.token(L_BRACKET.into(), "[");
2292 for (i, arch) in architectures.enumerate() {
2293 if i > 0 {
2294 builder.token(WHITESPACE.into(), " ");
2295 }
2296 builder.token(IDENT.into(), arch.as_str());
2297 }
2298 builder.token(R_BRACKET.into(), "]");
2299 builder.finish_node();
2300 }
2301 for profiles in self.profiles() {
2302 builder.token(WHITESPACE.into(), " ");
2303 builder.start_node(PROFILES.into());
2304 builder.token(L_ANGLE.into(), "<");
2305 for (i, profile) in profiles.into_iter().enumerate() {
2306 if i > 0 {
2307 builder.token(WHITESPACE.into(), " ");
2308 }
2309 match profile {
2310 BuildProfile::Disabled(name) => {
2311 builder.token(NOT.into(), "!");
2312 builder.token(IDENT.into(), name.as_str());
2313 }
2314 BuildProfile::Enabled(name) => {
2315 builder.token(IDENT.into(), name.as_str());
2316 }
2317 }
2318 }
2319 builder.token(R_ANGLE.into(), ">");
2320 builder.finish_node();
2321 }
2322 builder.finish_node();
2323 Relation(SyntaxNode::new_root_mut(builder.finish()))
2324 }
2325
2326 pub fn simple(name: &str) -> Self {
2335 Self::new(name, None)
2336 }
2337
2338 pub fn drop_constraint(&mut self) -> bool {
2349 let version_token = self.0.children().find(|n| n.kind() == VERSION);
2350 if let Some(version_token) = version_token {
2351 while let Some(prev) = version_token.prev_sibling_or_token() {
2353 if prev.kind() == WHITESPACE || prev.kind() == NEWLINE {
2354 prev.detach();
2355 } else {
2356 break;
2357 }
2358 }
2359 version_token.detach();
2360 return true;
2361 }
2362
2363 false
2364 }
2365
2366 pub fn name(&self) -> String {
2375 self.0
2376 .children_with_tokens()
2377 .find_map(|it| match it {
2378 SyntaxElement::Token(token) if token.kind() == IDENT => Some(token),
2379 _ => None,
2380 })
2381 .unwrap()
2382 .text()
2383 .to_string()
2384 }
2385
2386 pub fn archqual(&self) -> Option<String> {
2395 let archqual = self.0.children().find(|n| n.kind() == ARCHQUAL);
2396 let node = if let Some(archqual) = archqual {
2397 archqual.children_with_tokens().find_map(|it| match it {
2398 SyntaxElement::Token(token) if token.kind() == IDENT => Some(token),
2399 _ => None,
2400 })
2401 } else {
2402 None
2403 };
2404 node.map(|n| n.text().to_string())
2405 }
2406
2407 pub fn set_archqual(&mut self, archqual: &str) {
2417 let mut builder = GreenNodeBuilder::new();
2418 builder.start_node(ARCHQUAL.into());
2419 builder.token(COLON.into(), ":");
2420 builder.token(IDENT.into(), archqual);
2421 builder.finish_node();
2422
2423 let node_archqual = self.0.children().find(|n| n.kind() == ARCHQUAL);
2424 if let Some(node_archqual) = node_archqual {
2425 self.0.splice_children(
2426 node_archqual.index()..node_archqual.index() + 1,
2427 vec![SyntaxNode::new_root_mut(builder.finish()).into()],
2428 );
2429 } else {
2430 let name_node = self.0.children_with_tokens().find(|n| n.kind() == IDENT);
2431 let idx = if let Some(name_node) = name_node {
2432 name_node.index() + 1
2433 } else {
2434 0
2435 };
2436 self.0.splice_children(
2437 idx..idx,
2438 vec![SyntaxNode::new_root_mut(builder.finish()).into()],
2439 );
2440 }
2441 }
2442
2443 pub fn version(&self) -> Option<(VersionConstraint, Version)> {
2445 let vc = self.0.children().find(|n| n.kind() == VERSION);
2446 let vc = vc.as_ref()?;
2447 let constraint = vc.children().find(|n| n.kind() == CONSTRAINT);
2448
2449 let version_str: String = vc
2451 .children_with_tokens()
2452 .filter_map(|it| match it {
2453 SyntaxElement::Token(token) if token.kind() == IDENT || token.kind() == COLON => {
2454 Some(token.text().to_string())
2455 }
2456 _ => None,
2457 })
2458 .collect();
2459
2460 if let Some(constraint) = constraint {
2461 if !version_str.is_empty() {
2462 let vc: VersionConstraint = constraint.to_string().parse().unwrap();
2463 Some((vc, version_str.parse().unwrap()))
2464 } else {
2465 None
2466 }
2467 } else {
2468 None
2469 }
2470 }
2471
2472 pub fn set_version(&mut self, version_constraint: Option<(VersionConstraint, Version)>) {
2483 let current_version = self.0.children().find(|n| n.kind() == VERSION);
2484 if let Some((vc, version)) = version_constraint {
2485 let mut builder = GreenNodeBuilder::new();
2486 builder.start_node(VERSION.into());
2487 builder.token(L_PARENS.into(), "(");
2488 builder.start_node(CONSTRAINT.into());
2489 match vc {
2490 VersionConstraint::GreaterThanEqual => {
2491 builder.token(R_ANGLE.into(), ">");
2492 builder.token(EQUAL.into(), "=");
2493 }
2494 VersionConstraint::LessThanEqual => {
2495 builder.token(L_ANGLE.into(), "<");
2496 builder.token(EQUAL.into(), "=");
2497 }
2498 VersionConstraint::Equal => {
2499 builder.token(EQUAL.into(), "=");
2500 }
2501 VersionConstraint::GreaterThan => {
2502 builder.token(R_ANGLE.into(), ">");
2503 }
2504 VersionConstraint::LessThan => {
2505 builder.token(L_ANGLE.into(), "<");
2506 }
2507 }
2508 builder.finish_node(); builder.token(WHITESPACE.into(), " ");
2510 tokenize_version(&mut builder, &version);
2511 builder.token(R_PARENS.into(), ")");
2512 builder.finish_node(); if let Some(current_version) = current_version {
2515 self.0.splice_children(
2516 current_version.index()..current_version.index() + 1,
2517 vec![SyntaxNode::new_root_mut(builder.finish()).into()],
2518 );
2519 } else {
2520 let name_node = self.0.children_with_tokens().find(|n| n.kind() == IDENT);
2521 let idx = if let Some(name_node) = name_node {
2522 name_node.index() + 1
2523 } else {
2524 0
2525 };
2526 let new_children = vec![
2527 GreenToken::new(WHITESPACE.into(), " ").into(),
2528 builder.finish().into(),
2529 ];
2530 let new_root = SyntaxNode::new_root_mut(
2531 self.0.green().splice_children(idx..idx, new_children),
2532 );
2533 if let Some(parent) = self.0.parent() {
2534 parent
2535 .splice_children(self.0.index()..self.0.index() + 1, vec![new_root.into()]);
2536 self.0 = parent
2537 .children_with_tokens()
2538 .nth(self.0.index())
2539 .unwrap()
2540 .clone()
2541 .into_node()
2542 .unwrap();
2543 } else {
2544 self.0 = new_root;
2545 }
2546 }
2547 } else if let Some(current_version) = current_version {
2548 while let Some(prev) = current_version.prev_sibling_or_token() {
2550 if prev.kind() == WHITESPACE || prev.kind() == NEWLINE {
2551 prev.detach();
2552 } else {
2553 break;
2554 }
2555 }
2556 current_version.detach();
2557 }
2558 }
2559
2560 pub fn architectures(&self) -> Option<impl Iterator<Item = String> + '_> {
2569 let architectures = self.0.children().find(|n| n.kind() == ARCHITECTURES)?;
2570
2571 Some(architectures.children_with_tokens().filter_map(|node| {
2572 let token = node.as_token()?;
2573 if token.kind() == IDENT {
2574 Some(token.text().to_string())
2575 } else {
2576 None
2577 }
2578 }))
2579 }
2580
2581 pub fn profiles(&self) -> impl Iterator<Item = Vec<BuildProfile>> + '_ {
2591 let profiles = self.0.children().filter(|n| n.kind() == PROFILES);
2592
2593 profiles.map(|profile| {
2594 let mut ret = vec![];
2596 let mut current = vec![];
2597 for token in profile.children_with_tokens() {
2598 match token.kind() {
2599 WHITESPACE | NEWLINE => {
2600 if !current.is_empty() {
2601 ret.push(current.join("").parse::<BuildProfile>().unwrap());
2602 current = vec![];
2603 }
2604 }
2605 L_ANGLE | R_ANGLE => {}
2606 _ => {
2607 current.push(token.to_string());
2608 }
2609 }
2610 }
2611 if !current.is_empty() {
2612 ret.push(current.concat().parse().unwrap());
2613 }
2614 ret
2615 })
2616 }
2617
2618 pub fn remove(&mut self) {
2629 let is_first = !self
2630 .0
2631 .siblings(Direction::Prev)
2632 .skip(1)
2633 .any(|n| n.kind() == RELATION);
2634 if !is_first {
2635 while let Some(n) = self.0.prev_sibling_or_token() {
2638 if matches!(n.kind(), WHITESPACE | NEWLINE | COMMENT) {
2639 n.detach();
2640 } else if n.kind() == PIPE {
2641 n.detach();
2642 break;
2643 } else {
2644 break;
2645 }
2646 }
2647 while let Some(n) = self.0.prev_sibling_or_token() {
2648 if matches!(n.kind(), WHITESPACE | NEWLINE | COMMENT) {
2649 n.detach();
2650 } else {
2651 break;
2652 }
2653 }
2654 } else {
2655 while let Some(n) = self.0.next_sibling_or_token() {
2658 if matches!(n.kind(), WHITESPACE | NEWLINE | COMMENT) {
2659 n.detach();
2660 } else if n.kind() == PIPE {
2661 n.detach();
2662 break;
2663 } else {
2664 break;
2665 }
2666 }
2667
2668 while let Some(n) = self.0.next_sibling_or_token() {
2669 if matches!(n.kind(), WHITESPACE | NEWLINE | COMMENT) {
2670 n.detach();
2671 } else {
2672 break;
2673 }
2674 }
2675 }
2676 if let Some(mut parent) = self.0.parent().and_then(Entry::cast) {
2678 if parent.is_empty() {
2679 parent.remove();
2680 } else {
2681 self.0.detach();
2682 }
2683 } else {
2684 self.0.detach();
2685 }
2686 }
2687
2688 pub fn set_architectures<'a>(&mut self, architectures: impl Iterator<Item = &'a str>) {
2698 let mut builder = GreenNodeBuilder::new();
2699 builder.start_node(ARCHITECTURES.into());
2700 builder.token(L_BRACKET.into(), "[");
2701 for (i, arch) in architectures.enumerate() {
2702 if i > 0 {
2703 builder.token(WHITESPACE.into(), " ");
2704 }
2705 builder.token(IDENT.into(), arch);
2706 }
2707 builder.token(R_BRACKET.into(), "]");
2708 builder.finish_node();
2709
2710 let node_architectures = self.0.children().find(|n| n.kind() == ARCHITECTURES);
2711 if let Some(node_architectures) = node_architectures {
2712 let new_root = SyntaxNode::new_root_mut(builder.finish());
2713 self.0.splice_children(
2714 node_architectures.index()..node_architectures.index() + 1,
2715 vec![new_root.into()],
2716 );
2717 } else {
2718 let profiles = self.0.children().find(|n| n.kind() == PROFILES);
2719 let idx = if let Some(profiles) = profiles {
2720 profiles.index()
2721 } else {
2722 self.0.children_with_tokens().count()
2723 };
2724 let new_root = SyntaxNode::new_root(self.0.green().splice_children(
2725 idx..idx,
2726 vec![
2727 GreenToken::new(WHITESPACE.into(), " ").into(),
2728 builder.finish().into(),
2729 ],
2730 ));
2731 if let Some(parent) = self.0.parent() {
2732 parent.splice_children(self.0.index()..self.0.index() + 1, vec![new_root.into()]);
2733 self.0 = parent
2734 .children_with_tokens()
2735 .nth(self.0.index())
2736 .unwrap()
2737 .clone()
2738 .into_node()
2739 .unwrap();
2740 } else {
2741 self.0 = new_root;
2742 }
2743 }
2744 }
2745
2746 pub fn add_profile(&mut self, profile: &[BuildProfile]) {
2757 let mut builder = GreenNodeBuilder::new();
2758 builder.start_node(PROFILES.into());
2759 builder.token(L_ANGLE.into(), "<");
2760 for (i, profile) in profile.iter().enumerate() {
2761 if i > 0 {
2762 builder.token(WHITESPACE.into(), " ");
2763 }
2764 match profile {
2765 BuildProfile::Disabled(name) => {
2766 builder.token(NOT.into(), "!");
2767 builder.token(IDENT.into(), name.as_str());
2768 }
2769 BuildProfile::Enabled(name) => {
2770 builder.token(IDENT.into(), name.as_str());
2771 }
2772 }
2773 }
2774 builder.token(R_ANGLE.into(), ">");
2775 builder.finish_node();
2776
2777 let node_profiles = self.0.children().find(|n| n.kind() == PROFILES);
2778 if let Some(node_profiles) = node_profiles {
2779 let new_root = SyntaxNode::new_root_mut(builder.finish());
2780 self.0.splice_children(
2781 node_profiles.index()..node_profiles.index() + 1,
2782 vec![new_root.into()],
2783 );
2784 } else {
2785 let idx = self.0.children_with_tokens().count();
2786 let new_root = SyntaxNode::new_root(self.0.green().splice_children(
2787 idx..idx,
2788 vec![
2789 GreenToken::new(WHITESPACE.into(), " ").into(),
2790 builder.finish().into(),
2791 ],
2792 ));
2793 if let Some(parent) = self.0.parent() {
2794 parent.splice_children(self.0.index()..self.0.index() + 1, vec![new_root.into()]);
2795 self.0 = parent
2796 .children_with_tokens()
2797 .nth(self.0.index())
2798 .unwrap()
2799 .clone()
2800 .into_node()
2801 .unwrap();
2802 } else {
2803 self.0 = new_root;
2804 }
2805 }
2806 }
2807
2808 pub fn build(name: &str) -> RelationBuilder {
2810 RelationBuilder::new(name)
2811 }
2812
2813 pub fn is_implied_by(&self, outer: &Relation) -> bool {
2840 if self.name() != outer.name() {
2841 return false;
2842 }
2843
2844 let inner_version = self.version();
2845 let outer_version = outer.version();
2846
2847 if inner_version.is_none() {
2849 return true;
2850 }
2851
2852 if inner_version == outer_version {
2854 return true;
2855 }
2856
2857 if outer_version.is_none() {
2859 return false;
2860 }
2861
2862 let (inner_constraint, inner_ver) = inner_version.unwrap();
2863 let (outer_constraint, outer_ver) = outer_version.unwrap();
2864
2865 use VersionConstraint::*;
2866 match inner_constraint {
2867 GreaterThanEqual => match outer_constraint {
2868 GreaterThan => outer_ver > inner_ver,
2869 GreaterThanEqual | Equal => outer_ver >= inner_ver,
2870 LessThan | LessThanEqual => false,
2871 },
2872 Equal => match outer_constraint {
2873 Equal => outer_ver == inner_ver,
2874 _ => false,
2875 },
2876 LessThan => match outer_constraint {
2877 LessThan => outer_ver <= inner_ver,
2878 LessThanEqual | Equal => outer_ver < inner_ver,
2879 GreaterThan | GreaterThanEqual => false,
2880 },
2881 LessThanEqual => match outer_constraint {
2882 LessThanEqual | Equal | LessThan => outer_ver <= inner_ver,
2883 GreaterThan | GreaterThanEqual => false,
2884 },
2885 GreaterThan => match outer_constraint {
2886 GreaterThan => outer_ver >= inner_ver,
2887 Equal | GreaterThanEqual => outer_ver > inner_ver,
2888 LessThan | LessThanEqual => false,
2889 },
2890 }
2891 }
2892}
2893
2894pub struct RelationBuilder {
2908 name: String,
2909 version_constraint: Option<(VersionConstraint, Version)>,
2910 archqual: Option<String>,
2911 architectures: Option<Vec<String>>,
2912 profiles: Vec<Vec<BuildProfile>>,
2913}
2914
2915impl RelationBuilder {
2916 fn new(name: &str) -> Self {
2918 Self {
2919 name: name.to_string(),
2920 version_constraint: None,
2921 archqual: None,
2922 architectures: None,
2923 profiles: vec![],
2924 }
2925 }
2926
2927 pub fn version_constraint(mut self, vc: VersionConstraint, version: Version) -> Self {
2929 self.version_constraint = Some((vc, version));
2930 self
2931 }
2932
2933 pub fn archqual(mut self, archqual: &str) -> Self {
2935 self.archqual = Some(archqual.to_string());
2936 self
2937 }
2938
2939 pub fn architectures(mut self, architectures: Vec<String>) -> Self {
2941 self.architectures = Some(architectures);
2942 self
2943 }
2944
2945 pub fn profiles(mut self, profiles: Vec<Vec<BuildProfile>>) -> Self {
2947 self.profiles = profiles;
2948 self
2949 }
2950
2951 pub fn add_profile(mut self, profile: Vec<BuildProfile>) -> Self {
2953 self.profiles.push(profile);
2954 self
2955 }
2956
2957 pub fn build(self) -> Relation {
2959 let mut relation = Relation::new(&self.name, self.version_constraint);
2960 if let Some(archqual) = &self.archqual {
2961 relation.set_archqual(archqual);
2962 }
2963 if let Some(architectures) = &self.architectures {
2964 relation.set_architectures(architectures.iter().map(|s| s.as_str()));
2965 }
2966 for profile in &self.profiles {
2967 relation.add_profile(profile);
2968 }
2969 relation
2970 }
2971}
2972
2973impl PartialOrd for Relation {
2974 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
2975 Some(self.cmp(other))
2976 }
2977}
2978
2979impl Eq for Relation {}
2980
2981impl Ord for Relation {
2982 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
2983 let name_cmp = self.name().cmp(&other.name());
2985 if name_cmp != std::cmp::Ordering::Equal {
2986 return name_cmp;
2987 }
2988
2989 let self_version = self.version();
2990 let other_version = other.version();
2991
2992 match (self_version, other_version) {
2993 (Some((self_vc, self_version)), Some((other_vc, other_version))) => {
2994 let vc_cmp = self_vc.cmp(&other_vc);
2995 if vc_cmp != std::cmp::Ordering::Equal {
2996 return vc_cmp;
2997 }
2998
2999 self_version.cmp(&other_version)
3000 }
3001 (Some(_), None) => std::cmp::Ordering::Greater,
3002 (None, Some(_)) => std::cmp::Ordering::Less,
3003 (None, None) => std::cmp::Ordering::Equal,
3004 }
3005 }
3006}
3007
3008impl std::str::FromStr for Relations {
3009 type Err = String;
3010
3011 fn from_str(s: &str) -> Result<Self, Self::Err> {
3012 let parse = parse(s, false);
3013 if parse.errors.is_empty() {
3014 Ok(parse.root_mut())
3015 } else {
3016 Err(parse.errors.join("\n"))
3017 }
3018 }
3019}
3020
3021impl std::str::FromStr for Entry {
3022 type Err = String;
3023
3024 fn from_str(s: &str) -> Result<Self, Self::Err> {
3025 let root: Relations = s.parse()?;
3026
3027 let mut entries = root.entries();
3028 let entry = if let Some(entry) = entries.next() {
3029 entry
3030 } else {
3031 return Err("No entry found".to_string());
3032 };
3033
3034 if entries.next().is_some() {
3035 return Err("Multiple entries found".to_string());
3036 }
3037
3038 Ok(entry)
3039 }
3040}
3041
3042impl std::str::FromStr for Relation {
3043 type Err = String;
3044
3045 fn from_str(s: &str) -> Result<Self, Self::Err> {
3046 let entry: Entry = s.parse()?;
3047
3048 let mut relations = entry.relations();
3049 let relation = if let Some(relation) = relations.next() {
3050 relation
3051 } else {
3052 return Err("No relation found".to_string());
3053 };
3054
3055 if relations.next().is_some() {
3056 return Err("Multiple relations found".to_string());
3057 }
3058
3059 Ok(relation)
3060 }
3061}
3062
3063impl From<crate::lossy::Relation> for Relation {
3064 fn from(relation: crate::lossy::Relation) -> Self {
3065 let mut builder = Relation::build(&relation.name);
3066
3067 if let Some((vc, version)) = relation.version {
3068 builder = builder.version_constraint(vc, version);
3069 }
3070
3071 if let Some(archqual) = relation.archqual {
3072 builder = builder.archqual(&archqual);
3073 }
3074
3075 if let Some(architectures) = relation.architectures {
3076 builder = builder.architectures(architectures);
3077 }
3078
3079 builder = builder.profiles(relation.profiles);
3080
3081 builder.build()
3082 }
3083}
3084
3085impl From<Relation> for crate::lossy::Relation {
3086 fn from(relation: Relation) -> Self {
3087 crate::lossy::Relation {
3088 name: relation.name(),
3089 version: relation.version(),
3090 archqual: relation.archqual(),
3091 architectures: relation.architectures().map(|a| a.collect()),
3092 profiles: relation.profiles().collect(),
3093 }
3094 }
3095}
3096
3097impl From<Entry> for Vec<crate::lossy::Relation> {
3098 fn from(entry: Entry) -> Self {
3099 entry.relations().map(|r| r.into()).collect()
3100 }
3101}
3102
3103impl From<Vec<crate::lossy::Relation>> for Entry {
3104 fn from(relations: Vec<crate::lossy::Relation>) -> Self {
3105 let relations: Vec<Relation> = relations.into_iter().map(|r| r.into()).collect();
3106 Entry::from(relations)
3107 }
3108}
3109
3110#[cfg(test)]
3111mod tests {
3112 use super::*;
3113
3114 #[test]
3115 fn test_parse() {
3116 let input = "python3-dulwich";
3117 let parsed: Relations = input.parse().unwrap();
3118 assert_eq!(parsed.to_string(), input);
3119 assert_eq!(parsed.entries().count(), 1);
3120 let entry = parsed.entries().next().unwrap();
3121 assert_eq!(entry.to_string(), "python3-dulwich");
3122 assert_eq!(entry.relations().count(), 1);
3123 let relation = entry.relations().next().unwrap();
3124 assert_eq!(relation.to_string(), "python3-dulwich");
3125 assert_eq!(relation.version(), None);
3126
3127 let input = "python3-dulwich (>= 0.20.21)";
3128 let parsed: Relations = input.parse().unwrap();
3129 assert_eq!(parsed.to_string(), input);
3130 assert_eq!(parsed.entries().count(), 1);
3131 let entry = parsed.entries().next().unwrap();
3132 assert_eq!(entry.to_string(), "python3-dulwich (>= 0.20.21)");
3133 assert_eq!(entry.relations().count(), 1);
3134 let relation = entry.relations().next().unwrap();
3135 assert_eq!(relation.to_string(), "python3-dulwich (>= 0.20.21)");
3136 assert_eq!(
3137 relation.version(),
3138 Some((
3139 VersionConstraint::GreaterThanEqual,
3140 "0.20.21".parse().unwrap()
3141 ))
3142 );
3143 }
3144
3145 #[test]
3146 fn test_multiple() {
3147 let input = "python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)";
3148 let parsed: Relations = input.parse().unwrap();
3149 assert_eq!(parsed.to_string(), input);
3150 assert_eq!(parsed.entries().count(), 2);
3151 let entry = parsed.entries().next().unwrap();
3152 assert_eq!(entry.to_string(), "python3-dulwich (>= 0.20.21)");
3153 assert_eq!(entry.relations().count(), 1);
3154 let relation = entry.relations().next().unwrap();
3155 assert_eq!(relation.to_string(), "python3-dulwich (>= 0.20.21)");
3156 assert_eq!(
3157 relation.version(),
3158 Some((
3159 VersionConstraint::GreaterThanEqual,
3160 "0.20.21".parse().unwrap()
3161 ))
3162 );
3163 let entry = parsed.entries().nth(1).unwrap();
3164 assert_eq!(entry.to_string(), "python3-dulwich (<< 0.21)");
3165 assert_eq!(entry.relations().count(), 1);
3166 let relation = entry.relations().next().unwrap();
3167 assert_eq!(relation.to_string(), "python3-dulwich (<< 0.21)");
3168 assert_eq!(
3169 relation.version(),
3170 Some((VersionConstraint::LessThan, "0.21".parse().unwrap()))
3171 );
3172 }
3173
3174 #[test]
3175 fn test_architectures() {
3176 let input = "python3-dulwich [amd64 arm64 armhf i386 mips mips64el mipsel ppc64el s390x]";
3177 let parsed: Relations = input.parse().unwrap();
3178 assert_eq!(parsed.to_string(), input);
3179 assert_eq!(parsed.entries().count(), 1);
3180 let entry = parsed.entries().next().unwrap();
3181 assert_eq!(
3182 entry.to_string(),
3183 "python3-dulwich [amd64 arm64 armhf i386 mips mips64el mipsel ppc64el s390x]"
3184 );
3185 assert_eq!(entry.relations().count(), 1);
3186 let relation = entry.relations().next().unwrap();
3187 assert_eq!(
3188 relation.to_string(),
3189 "python3-dulwich [amd64 arm64 armhf i386 mips mips64el mipsel ppc64el s390x]"
3190 );
3191 assert_eq!(relation.version(), None);
3192 assert_eq!(
3193 relation.architectures().unwrap().collect::<Vec<_>>(),
3194 vec![
3195 "amd64", "arm64", "armhf", "i386", "mips", "mips64el", "mipsel", "ppc64el", "s390x"
3196 ]
3197 .into_iter()
3198 .map(|s| s.to_string())
3199 .collect::<Vec<_>>()
3200 );
3201 }
3202
3203 #[test]
3204 fn test_profiles() {
3205 let input = "foo (>= 1.0) [i386 arm] <!nocheck> <!cross>, bar";
3206 let parsed: Relations = input.parse().unwrap();
3207 assert_eq!(parsed.to_string(), input);
3208 assert_eq!(parsed.entries().count(), 2);
3209 let entry = parsed.entries().next().unwrap();
3210 assert_eq!(
3211 entry.to_string(),
3212 "foo (>= 1.0) [i386 arm] <!nocheck> <!cross>"
3213 );
3214 assert_eq!(entry.relations().count(), 1);
3215 let relation = entry.relations().next().unwrap();
3216 assert_eq!(
3217 relation.to_string(),
3218 "foo (>= 1.0) [i386 arm] <!nocheck> <!cross>"
3219 );
3220 assert_eq!(
3221 relation.version(),
3222 Some((VersionConstraint::GreaterThanEqual, "1.0".parse().unwrap()))
3223 );
3224 assert_eq!(
3225 relation.architectures().unwrap().collect::<Vec<_>>(),
3226 vec!["i386", "arm"]
3227 .into_iter()
3228 .map(|s| s.to_string())
3229 .collect::<Vec<_>>()
3230 );
3231 assert_eq!(
3232 relation.profiles().collect::<Vec<_>>(),
3233 vec![
3234 vec![BuildProfile::Disabled("nocheck".to_string())],
3235 vec![BuildProfile::Disabled("cross".to_string())]
3236 ]
3237 );
3238 }
3239
3240 #[test]
3241 fn test_substvar() {
3242 let input = "${shlibs:Depends}";
3243
3244 let (parsed, errors) = Relations::parse_relaxed(input, true);
3245 assert_eq!(errors, Vec::<String>::new());
3246 assert_eq!(parsed.to_string(), input);
3247 assert_eq!(parsed.entries().count(), 0);
3248
3249 assert_eq!(
3250 parsed.substvars().collect::<Vec<_>>(),
3251 vec!["${shlibs:Depends}"]
3252 );
3253 }
3254
3255 #[test]
3256 fn test_new() {
3257 let r = Relation::new(
3258 "samba",
3259 Some((VersionConstraint::GreaterThanEqual, "2.0".parse().unwrap())),
3260 );
3261
3262 assert_eq!(r.to_string(), "samba (>= 2.0)");
3263 }
3264
3265 #[test]
3266 fn test_drop_constraint() {
3267 let mut r = Relation::new(
3268 "samba",
3269 Some((VersionConstraint::GreaterThanEqual, "2.0".parse().unwrap())),
3270 );
3271
3272 r.drop_constraint();
3273
3274 assert_eq!(r.to_string(), "samba");
3275 }
3276
3277 #[test]
3278 fn test_simple() {
3279 let r = Relation::simple("samba");
3280
3281 assert_eq!(r.to_string(), "samba");
3282 }
3283
3284 #[test]
3285 fn test_remove_first_entry() {
3286 let mut rels: Relations = r#"python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)"#
3287 .parse()
3288 .unwrap();
3289 let removed = rels.remove_entry(0);
3290 assert_eq!(removed.to_string(), "python3-dulwich (>= 0.20.21)");
3291 assert_eq!(rels.to_string(), "python3-dulwich (<< 0.21)");
3292 }
3293
3294 #[test]
3295 fn test_remove_last_entry() {
3296 let mut rels: Relations = r#"python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)"#
3297 .parse()
3298 .unwrap();
3299 rels.remove_entry(1);
3300 assert_eq!(rels.to_string(), "python3-dulwich (>= 0.20.21)");
3301 }
3302
3303 #[test]
3304 fn test_remove_middle() {
3305 let mut rels: Relations =
3306 r#"python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21), python3-dulwich (<< 0.22)"#
3307 .parse()
3308 .unwrap();
3309 rels.remove_entry(1);
3310 assert_eq!(
3311 rels.to_string(),
3312 "python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.22)"
3313 );
3314 }
3315
3316 #[test]
3317 fn test_remove_added() {
3318 let mut rels: Relations = r#"python3-dulwich (>= 0.20.21)"#.parse().unwrap();
3319 let entry = Entry::from(vec![Relation::simple("python3-dulwich")]);
3320 rels.push(entry);
3321 rels.remove_entry(1);
3322 assert_eq!(rels.to_string(), "python3-dulwich (>= 0.20.21)");
3323 }
3324
3325 #[test]
3326 fn test_push() {
3327 let mut rels: Relations = r#"python3-dulwich (>= 0.20.21)"#.parse().unwrap();
3328 let entry = Entry::from(vec![Relation::simple("python3-dulwich")]);
3329 rels.push(entry);
3330 assert_eq!(
3331 rels.to_string(),
3332 "python3-dulwich (>= 0.20.21), python3-dulwich"
3333 );
3334 }
3335
3336 #[test]
3337 fn test_insert_with_custom_separator() {
3338 let mut rels: Relations = "python3".parse().unwrap();
3339 let entry = Entry::from(vec![Relation::simple("debhelper")]);
3340 rels.insert_with_separator(1, entry, Some("\n "));
3341 assert_eq!(rels.to_string(), "python3,\n debhelper");
3342 }
3343
3344 #[test]
3345 fn test_insert_with_wrap_and_sort_separator() {
3346 let mut rels: Relations = "python3".parse().unwrap();
3347 let entry = Entry::from(vec![Relation::simple("rustc")]);
3348 rels.insert_with_separator(1, entry, Some("\n "));
3350 assert_eq!(rels.to_string(), "python3,\n rustc");
3351 }
3352
3353 #[test]
3354 fn test_push_from_empty() {
3355 let mut rels: Relations = "".parse().unwrap();
3356 let entry = Entry::from(vec![Relation::simple("python3-dulwich")]);
3357 rels.push(entry);
3358 assert_eq!(rels.to_string(), "python3-dulwich");
3359 }
3360
3361 #[test]
3362 fn test_insert() {
3363 let mut rels: Relations = r#"python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)"#
3364 .parse()
3365 .unwrap();
3366 let entry = Entry::from(vec![Relation::simple("python3-dulwich")]);
3367 rels.insert(1, entry);
3368 assert_eq!(
3369 rels.to_string(),
3370 "python3-dulwich (>= 0.20.21), python3-dulwich, python3-dulwich (<< 0.21)"
3371 );
3372 }
3373
3374 #[test]
3375 fn test_insert_at_start() {
3376 let mut rels: Relations = r#"python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)"#
3377 .parse()
3378 .unwrap();
3379 let entry = Entry::from(vec![Relation::simple("python3-dulwich")]);
3380 rels.insert(0, entry);
3381 assert_eq!(
3382 rels.to_string(),
3383 "python3-dulwich, python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)"
3384 );
3385 }
3386
3387 #[test]
3388 fn test_insert_after_error() {
3389 let (mut rels, errors) = Relations::parse_relaxed("@foo@, debhelper (>= 1.0)", false);
3390 assert_eq!(
3391 errors,
3392 vec![
3393 "expected $ or identifier but got ERROR",
3394 "expected comma or end of file but got Some(IDENT)",
3395 "expected $ or identifier but got ERROR"
3396 ]
3397 );
3398 let entry = Entry::from(vec![Relation::simple("bar")]);
3399 rels.push(entry);
3400 assert_eq!(rels.to_string(), "@foo@, debhelper (>= 1.0), bar");
3401 }
3402
3403 #[test]
3404 fn test_insert_before_error() {
3405 let (mut rels, errors) = Relations::parse_relaxed("debhelper (>= 1.0), @foo@, bla", false);
3406 assert_eq!(
3407 errors,
3408 vec![
3409 "expected $ or identifier but got ERROR",
3410 "expected comma or end of file but got Some(IDENT)",
3411 "expected $ or identifier but got ERROR"
3412 ]
3413 );
3414 let entry = Entry::from(vec![Relation::simple("bar")]);
3415 rels.insert(0, entry);
3416 assert_eq!(rels.to_string(), "bar, debhelper (>= 1.0), @foo@, bla");
3417 }
3418
3419 #[test]
3420 fn test_replace() {
3421 let mut rels: Relations = r#"python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)"#
3422 .parse()
3423 .unwrap();
3424 let entry = Entry::from(vec![Relation::simple("python3-dulwich")]);
3425 rels.replace(1, entry);
3426 assert_eq!(
3427 rels.to_string(),
3428 "python3-dulwich (>= 0.20.21), python3-dulwich"
3429 );
3430 }
3431
3432 #[test]
3433 fn test_relation_from_entries() {
3434 let entries = vec![
3435 Entry::from(vec![Relation::simple("python3-dulwich")]),
3436 Entry::from(vec![Relation::simple("python3-breezy")]),
3437 ];
3438 let rels: Relations = entries.into();
3439 assert_eq!(rels.entries().count(), 2);
3440 assert_eq!(rels.to_string(), "python3-dulwich, python3-breezy");
3441 }
3442
3443 #[test]
3444 fn test_entry_from_relations() {
3445 let relations = vec![
3446 Relation::simple("python3-dulwich"),
3447 Relation::simple("python3-breezy"),
3448 ];
3449 let entry: Entry = relations.into();
3450 assert_eq!(entry.relations().count(), 2);
3451 assert_eq!(entry.to_string(), "python3-dulwich | python3-breezy");
3452 }
3453
3454 #[test]
3455 fn test_parse_entry() {
3456 let parsed: Entry = "python3-dulwich (>= 0.20.21) | bar".parse().unwrap();
3457 assert_eq!(parsed.to_string(), "python3-dulwich (>= 0.20.21) | bar");
3458 assert_eq!(parsed.relations().count(), 2);
3459
3460 assert_eq!(
3461 "foo, bar".parse::<Entry>().unwrap_err(),
3462 "Multiple entries found"
3463 );
3464 assert_eq!("".parse::<Entry>().unwrap_err(), "No entry found");
3465 }
3466
3467 #[test]
3468 fn test_parse_relation() {
3469 let parsed: Relation = "python3-dulwich (>= 0.20.21)".parse().unwrap();
3470 assert_eq!(parsed.to_string(), "python3-dulwich (>= 0.20.21)");
3471 assert_eq!(
3472 parsed.version(),
3473 Some((
3474 VersionConstraint::GreaterThanEqual,
3475 "0.20.21".parse().unwrap()
3476 ))
3477 );
3478 assert_eq!(
3479 "foo | bar".parse::<Relation>().unwrap_err(),
3480 "Multiple relations found"
3481 );
3482 assert_eq!("".parse::<Relation>().unwrap_err(), "No entry found");
3483 }
3484
3485 #[test]
3486 fn test_special() {
3487 let parsed: Relation = "librust-breezyshim+dirty-tracker-dev:amd64 (>= 0.1.138-~~)"
3488 .parse()
3489 .unwrap();
3490 assert_eq!(
3491 parsed.to_string(),
3492 "librust-breezyshim+dirty-tracker-dev:amd64 (>= 0.1.138-~~)"
3493 );
3494 assert_eq!(
3495 parsed.version(),
3496 Some((
3497 VersionConstraint::GreaterThanEqual,
3498 "0.1.138-~~".parse().unwrap()
3499 ))
3500 );
3501 assert_eq!(parsed.archqual(), Some("amd64".to_string()));
3502 assert_eq!(parsed.name(), "librust-breezyshim+dirty-tracker-dev");
3503 }
3504
3505 #[test]
3506 fn test_relations_satisfied_by() {
3507 let rels: Relations = "python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)"
3508 .parse()
3509 .unwrap();
3510 let satisfied = |name: &str| -> Option<debversion::Version> {
3511 match name {
3512 "python3-dulwich" => Some("0.20.21".parse().unwrap()),
3513 _ => None,
3514 }
3515 };
3516 assert!(rels.satisfied_by(satisfied));
3517
3518 let satisfied = |name: &str| match name {
3519 "python3-dulwich" => Some("0.21".parse().unwrap()),
3520 _ => None,
3521 };
3522 assert!(!rels.satisfied_by(satisfied));
3523
3524 let satisfied = |name: &str| match name {
3525 "python3-dulwich" => Some("0.20.20".parse().unwrap()),
3526 _ => None,
3527 };
3528 assert!(!rels.satisfied_by(satisfied));
3529 }
3530
3531 #[test]
3532 fn test_entry_satisfied_by() {
3533 let entry: Entry = "python3-dulwich (>= 0.20.21) | python3-dulwich (<< 0.18)"
3534 .parse()
3535 .unwrap();
3536 let satisfied = |name: &str| -> Option<debversion::Version> {
3537 match name {
3538 "python3-dulwich" => Some("0.20.21".parse().unwrap()),
3539 _ => None,
3540 }
3541 };
3542 assert!(entry.satisfied_by(satisfied));
3543 let satisfied = |name: &str| -> Option<debversion::Version> {
3544 match name {
3545 "python3-dulwich" => Some("0.18".parse().unwrap()),
3546 _ => None,
3547 }
3548 };
3549 assert!(!entry.satisfied_by(satisfied));
3550 }
3551
3552 #[test]
3553 fn test_wrap_and_sort_relation() {
3554 let relation: Relation = " python3-dulwich (>= 11) [ amd64 ] < lala>"
3555 .parse()
3556 .unwrap();
3557
3558 let wrapped = relation.wrap_and_sort();
3559
3560 assert_eq!(
3561 wrapped.to_string(),
3562 "python3-dulwich (>= 11) [amd64] <lala>"
3563 );
3564 }
3565
3566 #[test]
3567 fn test_wrap_and_sort_relations() {
3568 let entry: Relations =
3569 "python3-dulwich (>= 0.20.21) | bar, \n\n\n\npython3-dulwich (<< 0.21)"
3570 .parse()
3571 .unwrap();
3572
3573 let wrapped = entry.wrap_and_sort();
3574
3575 assert_eq!(
3576 wrapped.to_string(),
3577 "bar | python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)"
3578 );
3579 }
3580
3581 #[cfg(feature = "serde")]
3582 #[test]
3583 fn test_serialize_relations() {
3584 let relations: Relations = "python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)"
3585 .parse()
3586 .unwrap();
3587 let serialized = serde_json::to_string(&relations).unwrap();
3588 assert_eq!(
3589 serialized,
3590 r#""python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)""#
3591 );
3592 }
3593
3594 #[cfg(feature = "serde")]
3595 #[test]
3596 fn test_deserialize_relations() {
3597 let relations: Relations = "python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)"
3598 .parse()
3599 .unwrap();
3600 let serialized = serde_json::to_string(&relations).unwrap();
3601 let deserialized: Relations = serde_json::from_str(&serialized).unwrap();
3602 assert_eq!(deserialized.to_string(), relations.to_string());
3603 }
3604
3605 #[cfg(feature = "serde")]
3606 #[test]
3607 fn test_serialize_relation() {
3608 let relation: Relation = "python3-dulwich (>= 0.20.21)".parse().unwrap();
3609 let serialized = serde_json::to_string(&relation).unwrap();
3610 assert_eq!(serialized, r#""python3-dulwich (>= 0.20.21)""#);
3611 }
3612
3613 #[cfg(feature = "serde")]
3614 #[test]
3615 fn test_deserialize_relation() {
3616 let relation: Relation = "python3-dulwich (>= 0.20.21)".parse().unwrap();
3617 let serialized = serde_json::to_string(&relation).unwrap();
3618 let deserialized: Relation = serde_json::from_str(&serialized).unwrap();
3619 assert_eq!(deserialized.to_string(), relation.to_string());
3620 }
3621
3622 #[cfg(feature = "serde")]
3623 #[test]
3624 fn test_serialize_entry() {
3625 let entry: Entry = "python3-dulwich (>= 0.20.21) | python3-dulwich (<< 0.18)"
3626 .parse()
3627 .unwrap();
3628 let serialized = serde_json::to_string(&entry).unwrap();
3629 assert_eq!(
3630 serialized,
3631 r#""python3-dulwich (>= 0.20.21) | python3-dulwich (<< 0.18)""#
3632 );
3633 }
3634
3635 #[cfg(feature = "serde")]
3636 #[test]
3637 fn test_deserialize_entry() {
3638 let entry: Entry = "python3-dulwich (>= 0.20.21) | python3-dulwich (<< 0.18)"
3639 .parse()
3640 .unwrap();
3641 let serialized = serde_json::to_string(&entry).unwrap();
3642 let deserialized: Entry = serde_json::from_str(&serialized).unwrap();
3643 assert_eq!(deserialized.to_string(), entry.to_string());
3644 }
3645
3646 #[test]
3647 fn test_remove_first_relation() {
3648 let entry: Entry = "python3-dulwich (>= 0.20.21) | python3-dulwich (<< 0.18)"
3649 .parse()
3650 .unwrap();
3651 let mut rel = entry.relations().next().unwrap();
3652 rel.remove();
3653 assert_eq!(entry.to_string(), "python3-dulwich (<< 0.18)");
3654 }
3655
3656 #[test]
3657 fn test_remove_last_relation() {
3658 let entry: Entry = "python3-dulwich (>= 0.20.21) | python3-dulwich (<< 0.18)"
3659 .parse()
3660 .unwrap();
3661 let mut rel = entry.relations().nth(1).unwrap();
3662 rel.remove();
3663 assert_eq!(entry.to_string(), "python3-dulwich (>= 0.20.21)");
3664 }
3665
3666 #[test]
3667 fn test_remove_only_relation() {
3668 let entry: Entry = "python3-dulwich (>= 0.20.21)".parse().unwrap();
3669 let mut rel = entry.relations().next().unwrap();
3670 rel.remove();
3671 assert_eq!(entry.to_string(), "");
3672 }
3673
3674 #[test]
3675 fn test_relations_is_empty() {
3676 let entry: Relations = "python3-dulwich (>= 0.20.21)".parse().unwrap();
3677 assert!(!entry.is_empty());
3678 assert_eq!(1, entry.len());
3679 let mut rel = entry.entries().next().unwrap();
3680 rel.remove();
3681 assert!(entry.is_empty());
3682 assert_eq!(0, entry.len());
3683 }
3684
3685 #[test]
3686 fn test_entry_is_empty() {
3687 let entry: Entry = "python3-dulwich (>= 0.20.21) | python3-dulwich (<< 0.18)"
3688 .parse()
3689 .unwrap();
3690 assert!(!entry.is_empty());
3691 assert_eq!(2, entry.len());
3692 let mut rel = entry.relations().next().unwrap();
3693 rel.remove();
3694 assert!(!entry.is_empty());
3695 assert_eq!(1, entry.len());
3696 let mut rel = entry.relations().next().unwrap();
3697 rel.remove();
3698 assert!(entry.is_empty());
3699 assert_eq!(0, entry.len());
3700 }
3701
3702 #[test]
3703 fn test_relation_set_version() {
3704 let mut rel: Relation = "samba".parse().unwrap();
3705 rel.set_version(None);
3706 assert_eq!("samba", rel.to_string());
3707 rel.set_version(Some((
3708 VersionConstraint::GreaterThanEqual,
3709 "2.0".parse().unwrap(),
3710 )));
3711 assert_eq!("samba (>= 2.0)", rel.to_string());
3712 rel.set_version(None);
3713 assert_eq!("samba", rel.to_string());
3714 rel.set_version(Some((
3715 VersionConstraint::GreaterThanEqual,
3716 "2.0".parse().unwrap(),
3717 )));
3718 rel.set_version(Some((
3719 VersionConstraint::GreaterThanEqual,
3720 "1.1".parse().unwrap(),
3721 )));
3722 assert_eq!("samba (>= 1.1)", rel.to_string());
3723 }
3724
3725 #[test]
3726 fn test_replace_relation() {
3727 let mut entry: Entry = "python3-dulwich (>= 0.20.21) | python3-dulwich (<< 0.18)"
3728 .parse()
3729 .unwrap();
3730 let new_rel = Relation::simple("python3-breezy");
3731 entry.replace(0, new_rel);
3732 assert_eq!(
3733 entry.to_string(),
3734 "python3-breezy | python3-dulwich (<< 0.18)"
3735 );
3736 }
3737
3738 #[test]
3739 fn test_entry_push_relation() {
3740 let relations: Relations = "python3-dulwich (>= 0.20.21)".parse().unwrap();
3741 let new_rel = Relation::simple("python3-breezy");
3742 let mut entry = relations.entries().next().unwrap();
3743 entry.push(new_rel);
3744 assert_eq!(
3745 entry.to_string(),
3746 "python3-dulwich (>= 0.20.21) | python3-breezy"
3747 );
3748 assert_eq!(
3749 relations.to_string(),
3750 "python3-dulwich (>= 0.20.21) | python3-breezy"
3751 );
3752 }
3753
3754 #[test]
3755 fn test_relations_remove_empty_entry() {
3756 let (mut relations, errors) = Relations::parse_relaxed("foo, , bar, ", false);
3757 assert_eq!(errors, Vec::<String>::new());
3758 assert_eq!(relations.to_string(), "foo, , bar, ");
3759 assert_eq!(relations.len(), 2);
3760 assert_eq!(
3761 relations.entries().next().unwrap().to_string(),
3762 "foo".to_string()
3763 );
3764 assert_eq!(
3765 relations.entries().nth(1).unwrap().to_string(),
3766 "bar".to_string()
3767 );
3768 relations.remove_entry(1);
3769 assert_eq!(relations.to_string(), "foo, , ");
3770 }
3771
3772 #[test]
3773 fn test_entry_remove_relation() {
3774 let entry: Entry = "python3-dulwich | samba".parse().unwrap();
3775 let removed = entry.remove_relation(0);
3776 assert_eq!(removed.to_string(), "python3-dulwich");
3777 assert_eq!(entry.to_string(), "samba");
3778 }
3779
3780 #[test]
3781 fn test_wrap_and_sort_removes_empty_entries() {
3782 let relations: Relations = "foo, , bar, ".parse().unwrap();
3783 let wrapped = relations.wrap_and_sort();
3784 assert_eq!(wrapped.to_string(), "bar, foo");
3785 }
3786
3787 #[test]
3788 fn test_set_archqual() {
3789 let entry: Entry = "python3-dulwich | samba".parse().unwrap();
3790 let mut rel = entry.relations().next().unwrap();
3791 rel.set_archqual("amd64");
3792 assert_eq!(rel.to_string(), "python3-dulwich:amd64");
3793 assert_eq!(rel.archqual(), Some("amd64".to_string()));
3794 assert_eq!(entry.to_string(), "python3-dulwich:amd64 | samba");
3795 rel.set_archqual("i386");
3796 assert_eq!(rel.to_string(), "python3-dulwich:i386");
3797 assert_eq!(rel.archqual(), Some("i386".to_string()));
3798 assert_eq!(entry.to_string(), "python3-dulwich:i386 | samba");
3799 }
3800
3801 #[test]
3802 fn test_set_architectures() {
3803 let mut relation = Relation::simple("samba");
3804 relation.set_architectures(vec!["amd64", "i386"].into_iter());
3805 assert_eq!(relation.to_string(), "samba [amd64 i386]");
3806 }
3807
3808 #[test]
3809 fn test_relation_builder_no_architectures() {
3810 let relation = Relation::build("debhelper").build();
3812 assert_eq!(relation.to_string(), "debhelper");
3813 }
3814
3815 #[test]
3816 fn test_relation_builder_with_architectures() {
3817 let relation = Relation::build("samba")
3819 .architectures(vec!["amd64".to_string(), "i386".to_string()])
3820 .build();
3821 assert_eq!(relation.to_string(), "samba [amd64 i386]");
3822 }
3823
3824 #[test]
3825 fn test_ensure_minimum_version_add_new() {
3826 let mut relations: Relations = "python3".parse().unwrap();
3827 relations.ensure_minimum_version("debhelper", &"12".parse().unwrap());
3828 assert_eq!(relations.to_string(), "python3, debhelper (>= 12)");
3829 }
3830
3831 #[test]
3832 fn test_ensure_minimum_version_update() {
3833 let mut relations: Relations = "debhelper (>= 9)".parse().unwrap();
3834 relations.ensure_minimum_version("debhelper", &"12".parse().unwrap());
3835 assert_eq!(relations.to_string(), "debhelper (>= 12)");
3836 }
3837
3838 #[test]
3839 fn test_ensure_minimum_version_no_change() {
3840 let mut relations: Relations = "debhelper (>= 13)".parse().unwrap();
3841 relations.ensure_minimum_version("debhelper", &"12".parse().unwrap());
3842 assert_eq!(relations.to_string(), "debhelper (>= 13)");
3843 }
3844
3845 #[test]
3846 fn test_ensure_minimum_version_no_version() {
3847 let mut relations: Relations = "debhelper".parse().unwrap();
3848 relations.ensure_minimum_version("debhelper", &"12".parse().unwrap());
3849 assert_eq!(relations.to_string(), "debhelper (>= 12)");
3850 }
3851
3852 #[test]
3853 fn test_ensure_minimum_version_preserves_newline() {
3854 let input = "\n debhelper (>= 9),\n pkg-config,\n uuid-dev";
3860 let mut relations: Relations = input.parse().unwrap();
3861 relations.ensure_minimum_version("debhelper", &"12~".parse().unwrap());
3862 let result = relations.to_string();
3863
3864 assert!(
3866 result.starts_with('\n'),
3867 "Expected result to start with newline, got: {:?}",
3868 result
3869 );
3870 assert_eq!(result, "\n debhelper (>= 12~),\n pkg-config,\n uuid-dev");
3871 }
3872
3873 #[test]
3874 fn test_ensure_minimum_version_preserves_newline_in_control() {
3875 use crate::lossless::Control;
3877 use std::str::FromStr;
3878
3879 let input = r#"Source: f2fs-tools
3880Section: admin
3881Priority: optional
3882Maintainer: Test <test@example.com>
3883Build-Depends:
3884 debhelper (>= 9),
3885 pkg-config,
3886 uuid-dev
3887
3888Package: f2fs-tools
3889Description: test
3890"#;
3891
3892 let control = Control::from_str(input).unwrap();
3893 let mut source = control.source().unwrap();
3894 let mut build_depends = source.build_depends().unwrap();
3895
3896 let version = Version::from_str("12~").unwrap();
3897 build_depends.ensure_minimum_version("debhelper", &version);
3898
3899 source.set_build_depends(&build_depends);
3900
3901 let output = control.to_string();
3902
3903 assert!(
3905 output.contains("Build-Depends:\n debhelper (>= 12~)"),
3906 "Expected 'Build-Depends:\\n debhelper (>= 12~)' but got:\n{}",
3907 output
3908 );
3909 }
3910
3911 #[test]
3912 fn test_ensure_exact_version_add_new() {
3913 let mut relations: Relations = "python3".parse().unwrap();
3914 relations.ensure_exact_version("debhelper", &"12".parse().unwrap());
3915 assert_eq!(relations.to_string(), "python3, debhelper (= 12)");
3916 }
3917
3918 #[test]
3919 fn test_ensure_exact_version_update() {
3920 let mut relations: Relations = "debhelper (>= 9)".parse().unwrap();
3921 relations.ensure_exact_version("debhelper", &"12".parse().unwrap());
3922 assert_eq!(relations.to_string(), "debhelper (= 12)");
3923 }
3924
3925 #[test]
3926 fn test_ensure_exact_version_no_change() {
3927 let mut relations: Relations = "debhelper (= 12)".parse().unwrap();
3928 relations.ensure_exact_version("debhelper", &"12".parse().unwrap());
3929 assert_eq!(relations.to_string(), "debhelper (= 12)");
3930 }
3931
3932 #[test]
3933 fn test_ensure_some_version_add_new() {
3934 let mut relations: Relations = "python3".parse().unwrap();
3935 relations.ensure_some_version("debhelper");
3936 assert_eq!(relations.to_string(), "python3, debhelper");
3937 }
3938
3939 #[test]
3940 fn test_ensure_some_version_exists_with_version() {
3941 let mut relations: Relations = "debhelper (>= 12)".parse().unwrap();
3942 relations.ensure_some_version("debhelper");
3943 assert_eq!(relations.to_string(), "debhelper (>= 12)");
3944 }
3945
3946 #[test]
3947 fn test_ensure_some_version_exists_no_version() {
3948 let mut relations: Relations = "debhelper".parse().unwrap();
3949 relations.ensure_some_version("debhelper");
3950 assert_eq!(relations.to_string(), "debhelper");
3951 }
3952
3953 #[test]
3954 fn test_ensure_substvar() {
3955 let mut relations: Relations = "python3".parse().unwrap();
3956 relations.ensure_substvar("${misc:Depends}").unwrap();
3957 assert_eq!(relations.to_string(), "python3, ${misc:Depends}");
3958 }
3959
3960 #[test]
3961 fn test_ensure_substvar_already_exists() {
3962 let (mut relations, _) = Relations::parse_relaxed("python3, ${misc:Depends}", true);
3963 relations.ensure_substvar("${misc:Depends}").unwrap();
3964 assert_eq!(relations.to_string(), "python3, ${misc:Depends}");
3965 }
3966
3967 #[test]
3968 fn test_ensure_substvar_empty_relations() {
3969 let mut relations: Relations = Relations::new();
3970 relations.ensure_substvar("${misc:Depends}").unwrap();
3971 assert_eq!(relations.to_string(), "${misc:Depends}");
3972 }
3973
3974 #[test]
3975 fn test_ensure_substvar_preserves_whitespace() {
3976 let (mut relations, _) = Relations::parse_relaxed("python3, rustc", false);
3978 relations.ensure_substvar("${misc:Depends}").unwrap();
3979 assert_eq!(relations.to_string(), "python3, rustc, ${misc:Depends}");
3981 }
3982
3983 #[test]
3984 fn test_ensure_substvar_to_existing_substvar() {
3985 let (mut relations, _) = Relations::parse_relaxed("${shlibs:Depends}", true);
3988 relations.ensure_substvar("${misc:Depends}").unwrap();
3989 assert_eq!(relations.to_string(), "${shlibs:Depends}, ${misc:Depends}");
3991 }
3992
3993 #[test]
3994 fn test_drop_substvar_basic() {
3995 let (mut relations, _) = Relations::parse_relaxed("python3, ${misc:Depends}", true);
3996 relations.drop_substvar("${misc:Depends}");
3997 assert_eq!(relations.to_string(), "python3");
3998 }
3999
4000 #[test]
4001 fn test_drop_substvar_first_position() {
4002 let (mut relations, _) = Relations::parse_relaxed("${misc:Depends}, python3", true);
4003 relations.drop_substvar("${misc:Depends}");
4004 assert_eq!(relations.to_string(), "python3");
4005 }
4006
4007 #[test]
4008 fn test_drop_substvar_middle_position() {
4009 let (mut relations, _) = Relations::parse_relaxed("python3, ${misc:Depends}, rustc", true);
4010 relations.drop_substvar("${misc:Depends}");
4011 assert_eq!(relations.to_string(), "python3, rustc");
4012 }
4013
4014 #[test]
4015 fn test_drop_substvar_only_substvar() {
4016 let (mut relations, _) = Relations::parse_relaxed("${misc:Depends}", true);
4017 relations.drop_substvar("${misc:Depends}");
4018 assert_eq!(relations.to_string(), "");
4019 }
4020
4021 #[test]
4022 fn test_drop_substvar_not_exists() {
4023 let (mut relations, _) = Relations::parse_relaxed("python3, rustc", false);
4024 relations.drop_substvar("${misc:Depends}");
4025 assert_eq!(relations.to_string(), "python3, rustc");
4026 }
4027
4028 #[test]
4029 fn test_drop_substvar_multiple_substvars() {
4030 let (mut relations, _) =
4031 Relations::parse_relaxed("python3, ${misc:Depends}, ${shlibs:Depends}", true);
4032 relations.drop_substvar("${misc:Depends}");
4033 assert_eq!(relations.to_string(), "python3, ${shlibs:Depends}");
4034 }
4035
4036 #[test]
4037 fn test_drop_substvar_preserves_whitespace() {
4038 let (mut relations, _) = Relations::parse_relaxed("python3, ${misc:Depends}", true);
4039 relations.drop_substvar("${misc:Depends}");
4040 assert_eq!(relations.to_string(), "python3");
4041 }
4042
4043 #[test]
4044 fn test_filter_entries_basic() {
4045 let mut relations: Relations = "python3, debhelper, rustc".parse().unwrap();
4046 relations.filter_entries(|entry| entry.relations().any(|r| r.name().starts_with("python")));
4047 assert_eq!(relations.to_string(), "python3");
4048 }
4049
4050 #[test]
4051 fn test_filter_entries_keep_all() {
4052 let mut relations: Relations = "python3, debhelper".parse().unwrap();
4053 relations.filter_entries(|_| true);
4054 assert_eq!(relations.to_string(), "python3, debhelper");
4055 }
4056
4057 #[test]
4058 fn test_filter_entries_remove_all() {
4059 let mut relations: Relations = "python3, debhelper".parse().unwrap();
4060 relations.filter_entries(|_| false);
4061 assert_eq!(relations.to_string(), "");
4062 }
4063
4064 #[test]
4065 fn test_filter_entries_keep_middle() {
4066 let mut relations: Relations = "aaa, bbb, ccc".parse().unwrap();
4067 relations.filter_entries(|entry| entry.relations().any(|r| r.name() == "bbb"));
4068 assert_eq!(relations.to_string(), "bbb");
4069 }
4070
4071 #[test]
4074 fn test_is_sorted_wrap_and_sort_order() {
4075 let relations: Relations = "debhelper, python3, rustc".parse().unwrap();
4077 assert!(relations.is_sorted(&WrapAndSortOrder));
4078
4079 let relations: Relations = "rustc, debhelper, python3".parse().unwrap();
4081 assert!(!relations.is_sorted(&WrapAndSortOrder));
4082
4083 let (relations, _) =
4085 Relations::parse_relaxed("cdbs, debhelper-compat, python3, ${misc:Depends}", true);
4086 assert!(relations.is_sorted(&WrapAndSortOrder));
4087 }
4088
4089 #[test]
4090 fn test_is_sorted_default_order() {
4091 let relations: Relations = "aaa, bbb, ccc".parse().unwrap();
4093 assert!(relations.is_sorted(&DefaultSortingOrder));
4094
4095 let relations: Relations = "ccc, aaa, bbb".parse().unwrap();
4097 assert!(!relations.is_sorted(&DefaultSortingOrder));
4098
4099 let (relations, _) = Relations::parse_relaxed("aaa, bbb, ${misc:Depends}", true);
4101 assert!(relations.is_sorted(&DefaultSortingOrder));
4102 }
4103
4104 #[test]
4105 fn test_is_sorted_with_substvars() {
4106 let (relations, _) = Relations::parse_relaxed("python3, ${misc:Depends}, rustc", true);
4108 assert!(relations.is_sorted(&DefaultSortingOrder));
4110 }
4111
4112 #[test]
4113 fn test_drop_dependency_exists() {
4114 let mut relations: Relations = "python3, debhelper, rustc".parse().unwrap();
4115 assert!(relations.drop_dependency("debhelper"));
4116 assert_eq!(relations.to_string(), "python3, rustc");
4117 }
4118
4119 #[test]
4120 fn test_drop_dependency_not_exists() {
4121 let mut relations: Relations = "python3, rustc".parse().unwrap();
4122 assert!(!relations.drop_dependency("nonexistent"));
4123 assert_eq!(relations.to_string(), "python3, rustc");
4124 }
4125
4126 #[test]
4127 fn test_drop_dependency_only_item() {
4128 let mut relations: Relations = "python3".parse().unwrap();
4129 assert!(relations.drop_dependency("python3"));
4130 assert_eq!(relations.to_string(), "");
4131 }
4132
4133 #[test]
4134 fn test_add_dependency_to_empty() {
4135 let mut relations: Relations = "".parse().unwrap();
4136 let entry = Entry::from(Relation::simple("python3"));
4137 relations.add_dependency(entry, None);
4138 assert_eq!(relations.to_string(), "python3");
4139 }
4140
4141 #[test]
4142 fn test_add_dependency_sorted_position() {
4143 let mut relations: Relations = "debhelper, rustc".parse().unwrap();
4144 let entry = Entry::from(Relation::simple("python3"));
4145 relations.add_dependency(entry, None);
4146 assert_eq!(relations.to_string(), "debhelper, python3, rustc");
4148 }
4149
4150 #[test]
4151 fn test_add_dependency_explicit_position() {
4152 let mut relations: Relations = "python3, rustc".parse().unwrap();
4153 let entry = Entry::from(Relation::simple("debhelper"));
4154 relations.add_dependency(entry, Some(0));
4155 assert_eq!(relations.to_string(), "debhelper, python3, rustc");
4156 }
4157
4158 #[test]
4159 fn test_add_dependency_build_system_first() {
4160 let mut relations: Relations = "python3, rustc".parse().unwrap();
4161 let entry = Entry::from(Relation::simple("debhelper-compat"));
4162 relations.add_dependency(entry, None);
4163 assert_eq!(relations.to_string(), "debhelper-compat, python3, rustc");
4165 }
4166
4167 #[test]
4168 fn test_add_dependency_at_end() {
4169 let mut relations: Relations = "debhelper, python3".parse().unwrap();
4170 let entry = Entry::from(Relation::simple("zzz-package"));
4171 relations.add_dependency(entry, None);
4172 assert_eq!(relations.to_string(), "debhelper, python3, zzz-package");
4174 }
4175
4176 #[test]
4177 fn test_add_dependency_to_single_entry() {
4178 let mut relations: Relations = "python3-dulwich".parse().unwrap();
4180 let entry: Entry = "debhelper-compat (= 12)".parse().unwrap();
4181 relations.add_dependency(entry, None);
4182 assert_eq!(
4184 relations.to_string(),
4185 "debhelper-compat (= 12), python3-dulwich"
4186 );
4187 }
4188
4189 #[test]
4190 fn test_get_relation_exists() {
4191 let relations: Relations = "python3, debhelper (>= 12), rustc".parse().unwrap();
4192 let result = relations.get_relation("debhelper");
4193 assert!(result.is_ok());
4194 let (idx, entry) = result.unwrap();
4195 assert_eq!(idx, 1);
4196 assert_eq!(entry.to_string(), "debhelper (>= 12)");
4197 }
4198
4199 #[test]
4200 fn test_get_relation_not_exists() {
4201 let relations: Relations = "python3, rustc".parse().unwrap();
4202 let result = relations.get_relation("nonexistent");
4203 assert_eq!(result, Err("Package nonexistent not found".to_string()));
4204 }
4205
4206 #[test]
4207 fn test_get_relation_complex_rule() {
4208 let relations: Relations = "python3 | python3-minimal, rustc".parse().unwrap();
4209 let result = relations.get_relation("python3");
4210 assert_eq!(
4211 result,
4212 Err("Complex rule for python3, aborting".to_string())
4213 );
4214 }
4215
4216 #[test]
4217 fn test_iter_relations_for_simple() {
4218 let relations: Relations = "python3, debhelper, python3-dev".parse().unwrap();
4219 let entries: Vec<_> = relations.iter_relations_for("python3").collect();
4220 assert_eq!(entries.len(), 1);
4221 assert_eq!(entries[0].0, 0);
4222 assert_eq!(entries[0].1.to_string(), "python3");
4223 }
4224
4225 #[test]
4226 fn test_iter_relations_for_alternatives() {
4227 let relations: Relations = "python3 | python3-minimal, python3-dev".parse().unwrap();
4228 let entries: Vec<_> = relations.iter_relations_for("python3").collect();
4229 assert_eq!(entries.len(), 1);
4231 assert_eq!(entries[0].0, 0);
4232 }
4233
4234 #[test]
4235 fn test_iter_relations_for_not_found() {
4236 let relations: Relations = "python3, rustc".parse().unwrap();
4237 let entries: Vec<_> = relations.iter_relations_for("debhelper").collect();
4238 assert_eq!(entries.len(), 0);
4239 }
4240
4241 #[test]
4242 fn test_has_relation_exists() {
4243 let relations: Relations = "python3, debhelper, rustc".parse().unwrap();
4244 assert!(relations.has_relation("debhelper"));
4245 assert!(relations.has_relation("python3"));
4246 assert!(relations.has_relation("rustc"));
4247 }
4248
4249 #[test]
4250 fn test_has_relation_not_exists() {
4251 let relations: Relations = "python3, rustc".parse().unwrap();
4252 assert!(!relations.has_relation("debhelper"));
4253 }
4254
4255 #[test]
4256 fn test_has_relation_in_alternative() {
4257 let relations: Relations = "python3 | python3-minimal".parse().unwrap();
4258 assert!(relations.has_relation("python3"));
4259 assert!(relations.has_relation("python3-minimal"));
4260 }
4261
4262 #[test]
4263 fn test_sorting_order_wrap_and_sort_build_systems() {
4264 let order = WrapAndSortOrder;
4265 assert!(order.lt("debhelper", "python3"));
4267 assert!(order.lt("debhelper-compat", "rustc"));
4268 assert!(order.lt("cdbs", "aaa"));
4269 assert!(order.lt("dh-python", "python3"));
4270 }
4271
4272 #[test]
4273 fn test_sorting_order_wrap_and_sort_regular_packages() {
4274 let order = WrapAndSortOrder;
4275 assert!(order.lt("aaa", "bbb"));
4277 assert!(order.lt("python3", "rustc"));
4278 assert!(!order.lt("rustc", "python3"));
4279 }
4280
4281 #[test]
4282 fn test_sorting_order_wrap_and_sort_substvars() {
4283 let order = WrapAndSortOrder;
4284 assert!(order.lt("python3", "${misc:Depends}"));
4286 assert!(!order.lt("${misc:Depends}", "python3"));
4287 assert!(!order.ignore("${misc:Depends}"));
4289 }
4290
4291 #[test]
4292 fn test_sorting_order_default_special_items() {
4293 let order = DefaultSortingOrder;
4294 assert!(order.lt("python3", "${misc:Depends}"));
4296 assert!(order.lt("aaa", "@cdbs@"));
4297 assert!(order.ignore("${misc:Depends}"));
4299 assert!(order.ignore("@cdbs@"));
4300 assert!(!order.ignore("python3"));
4301 }
4302
4303 #[test]
4304 fn test_is_special_package_name() {
4305 assert!(is_special_package_name("${misc:Depends}"));
4306 assert!(is_special_package_name("${shlibs:Depends}"));
4307 assert!(is_special_package_name("@cdbs@"));
4308 assert!(!is_special_package_name("python3"));
4309 assert!(!is_special_package_name("debhelper"));
4310 }
4311
4312 #[test]
4313 fn test_add_dependency_with_explicit_position() {
4314 let mut relations: Relations = "python3, rustc".parse().unwrap();
4316 let entry = Entry::from(Relation::simple("debhelper"));
4317 relations.add_dependency(entry, Some(1));
4318 assert_eq!(relations.to_string(), "python3, debhelper, rustc");
4320 }
4321
4322 #[test]
4323 fn test_whitespace_detection_single_space() {
4324 let mut relations: Relations = "python3, rustc".parse().unwrap();
4325 let entry = Entry::from(Relation::simple("debhelper"));
4326 relations.add_dependency(entry, Some(1));
4327 assert_eq!(relations.to_string(), "python3, debhelper, rustc");
4328 }
4329
4330 #[test]
4331 fn test_whitespace_detection_multiple_spaces() {
4332 let mut relations: Relations = "python3, rustc, gcc".parse().unwrap();
4333 let entry = Entry::from(Relation::simple("debhelper"));
4334 relations.add_dependency(entry, Some(1));
4335 assert_eq!(relations.to_string(), "python3, debhelper, rustc, gcc");
4337 }
4338
4339 #[test]
4340 fn test_whitespace_detection_mixed_patterns() {
4341 let mut relations: Relations = "a, b, c, d, e".parse().unwrap();
4343 let entry = Entry::from(Relation::simple("x"));
4344 relations.push(entry);
4345 assert_eq!(relations.to_string(), "a, b, c, d, e, x");
4348 }
4349
4350 #[test]
4351 fn test_whitespace_detection_newlines() {
4352 let mut relations: Relations = "python3,\n rustc".parse().unwrap();
4353 let entry = Entry::from(Relation::simple("debhelper"));
4354 relations.add_dependency(entry, Some(1));
4355 assert_eq!(relations.to_string(), "python3,\n debhelper,\n rustc");
4357 }
4358
4359 #[test]
4360 fn test_append_with_newline_no_trailing() {
4361 let mut relations: Relations = "foo,\n bar".parse().unwrap();
4362 let entry = Entry::from(Relation::simple("blah"));
4363 relations.add_dependency(entry, None);
4364 assert_eq!(relations.to_string(), "foo,\n bar,\n blah");
4365 }
4366
4367 #[test]
4368 fn test_append_with_trailing_newline() {
4369 let mut relations: Relations = "foo,\n bar\n".parse().unwrap();
4370 let entry = Entry::from(Relation::simple("blah"));
4371 relations.add_dependency(entry, None);
4372 assert_eq!(relations.to_string(), "foo,\n bar,\n blah");
4373 }
4374
4375 #[test]
4376 fn test_append_with_4_space_indent() {
4377 let mut relations: Relations = "foo,\n bar".parse().unwrap();
4378 let entry = Entry::from(Relation::simple("blah"));
4379 relations.add_dependency(entry, None);
4380 assert_eq!(relations.to_string(), "foo,\n bar,\n blah");
4381 }
4382
4383 #[test]
4384 fn test_append_with_4_space_and_trailing_newline() {
4385 let mut relations: Relations = "foo,\n bar\n".parse().unwrap();
4386 let entry = Entry::from(Relation::simple("blah"));
4387 relations.add_dependency(entry, None);
4388 assert_eq!(relations.to_string(), "foo,\n bar,\n blah");
4389 }
4390
4391 #[test]
4392 fn test_odd_syntax_append_no_trailing() {
4393 let mut relations: Relations = "\n foo\n , bar".parse().unwrap();
4394 let entry = Entry::from(Relation::simple("blah"));
4395 relations.add_dependency(entry, None);
4396 assert_eq!(relations.to_string(), "\n foo\n , bar\n , blah");
4397 }
4398
4399 #[test]
4400 fn test_odd_syntax_append_with_trailing() {
4401 let mut relations: Relations = "\n foo\n , bar\n".parse().unwrap();
4402 let entry = Entry::from(Relation::simple("blah"));
4403 relations.add_dependency(entry, None);
4404 assert_eq!(relations.to_string(), "\n foo\n , bar\n , blah");
4405 }
4406
4407 #[test]
4408 fn test_insert_at_1_no_trailing() {
4409 let mut relations: Relations = "foo,\n bar".parse().unwrap();
4410 let entry = Entry::from(Relation::simple("blah"));
4411 relations.add_dependency(entry, Some(1));
4412 assert_eq!(relations.to_string(), "foo,\n blah,\n bar");
4413 }
4414
4415 #[test]
4416 fn test_insert_at_1_with_trailing() {
4417 let mut relations: Relations = "foo,\n bar\n".parse().unwrap();
4418 let entry = Entry::from(Relation::simple("blah"));
4419 relations.add_dependency(entry, Some(1));
4420 assert_eq!(relations.to_string(), "foo,\n blah,\n bar");
4421 }
4422
4423 #[test]
4424 fn test_odd_syntax_insert_at_1() {
4425 let mut relations: Relations = "\n foo\n , bar\n".parse().unwrap();
4426 let entry = Entry::from(Relation::simple("blah"));
4427 relations.add_dependency(entry, Some(1));
4428 assert_eq!(relations.to_string(), "\n foo\n , blah\n , bar");
4429 }
4430
4431 #[test]
4432 fn test_relations_preserves_exact_whitespace() {
4433 let input =
4435 "debhelper (>= 10), quilt (>= 0.40),\n libsystemd-dev [linux-any], pkg-config";
4436
4437 let relations: Relations = input.parse().unwrap();
4438
4439 assert_eq!(
4441 relations.to_string(),
4442 input,
4443 "Relations should preserve exact whitespace from input"
4444 );
4445 }
4446
4447 #[test]
4448 fn test_remove_entry_preserves_indentation() {
4449 let input = "debhelper (>= 10), quilt (>= 0.40),\n libsystemd-dev [linux-any], dh-systemd (>= 1.5), pkg-config";
4451
4452 let mut relations: Relations = input.parse().unwrap();
4453
4454 let mut to_remove = Vec::new();
4456 for (idx, entry) in relations.entries().enumerate() {
4457 for relation in entry.relations() {
4458 if relation.name() == "dh-systemd" {
4459 to_remove.push(idx);
4460 break;
4461 }
4462 }
4463 }
4464
4465 for idx in to_remove.into_iter().rev() {
4466 relations.remove_entry(idx);
4467 }
4468
4469 let output = relations.to_string();
4470 println!("After removal: '{}'", output);
4471
4472 assert!(
4474 output.contains("\n libsystemd-dev"),
4475 "Expected 4-space indentation to be preserved, but got:\n'{}'",
4476 output
4477 );
4478 }
4479
4480 #[test]
4481 fn test_relation_is_implied_by_same_package() {
4482 let inner = Relation::new(
4484 "pkg",
4485 Some((VersionConstraint::GreaterThanEqual, "1.0".parse().unwrap())),
4486 );
4487 let outer = Relation::new(
4488 "pkg",
4489 Some((VersionConstraint::GreaterThanEqual, "1.5".parse().unwrap())),
4490 );
4491 assert!(inner.is_implied_by(&outer));
4492 }
4493
4494 #[test]
4495 fn test_relation_is_implied_by_different_package() {
4496 let inner = Relation::new("pkg1", None);
4498 let outer = Relation::new("pkg2", None);
4499 assert!(!inner.is_implied_by(&outer));
4500 }
4501
4502 #[test]
4503 fn test_relation_is_implied_by_no_version() {
4504 let inner = Relation::new("pkg", None);
4506 let outer = Relation::new(
4507 "pkg",
4508 Some((VersionConstraint::GreaterThanEqual, "1.0".parse().unwrap())),
4509 );
4510 assert!(inner.is_implied_by(&outer));
4511 }
4512
4513 #[test]
4514 fn test_relation_is_implied_by_identical() {
4515 let inner = Relation::new(
4517 "pkg",
4518 Some((VersionConstraint::Equal, "1.0".parse().unwrap())),
4519 );
4520 let outer = Relation::new(
4521 "pkg",
4522 Some((VersionConstraint::Equal, "1.0".parse().unwrap())),
4523 );
4524 assert!(inner.is_implied_by(&outer));
4525 assert!(outer.is_implied_by(&inner));
4526 }
4527
4528 #[test]
4529 fn test_relation_is_implied_by_greater_than_equal() {
4530 let inner = Relation::new(
4532 "pkg",
4533 Some((VersionConstraint::GreaterThanEqual, "1.0".parse().unwrap())),
4534 );
4535 let outer = Relation::new(
4536 "pkg",
4537 Some((VersionConstraint::GreaterThanEqual, "2.0".parse().unwrap())),
4538 );
4539 assert!(inner.is_implied_by(&outer));
4540 assert!(!outer.is_implied_by(&inner));
4541
4542 let outer = Relation::new(
4544 "pkg",
4545 Some((VersionConstraint::Equal, "2.0".parse().unwrap())),
4546 );
4547 assert!(inner.is_implied_by(&outer));
4548
4549 let outer = Relation::new(
4551 "pkg",
4552 Some((VersionConstraint::GreaterThan, "1.5".parse().unwrap())),
4553 );
4554 assert!(inner.is_implied_by(&outer));
4555
4556 let inner = Relation::new(
4558 "pkg",
4559 Some((VersionConstraint::GreaterThanEqual, "3.0".parse().unwrap())),
4560 );
4561 let outer = Relation::new(
4562 "pkg",
4563 Some((VersionConstraint::GreaterThan, "3.0".parse().unwrap())),
4564 );
4565 assert!(!inner.is_implied_by(&outer));
4566 }
4567
4568 #[test]
4569 fn test_relation_is_implied_by_less_than_equal() {
4570 let inner = Relation::new(
4572 "pkg",
4573 Some((VersionConstraint::LessThanEqual, "2.0".parse().unwrap())),
4574 );
4575 let outer = Relation::new(
4576 "pkg",
4577 Some((VersionConstraint::LessThanEqual, "1.0".parse().unwrap())),
4578 );
4579 assert!(inner.is_implied_by(&outer));
4580 assert!(!outer.is_implied_by(&inner));
4581
4582 let outer = Relation::new(
4584 "pkg",
4585 Some((VersionConstraint::Equal, "1.0".parse().unwrap())),
4586 );
4587 assert!(inner.is_implied_by(&outer));
4588
4589 let outer = Relation::new(
4591 "pkg",
4592 Some((VersionConstraint::LessThan, "1.5".parse().unwrap())),
4593 );
4594 assert!(inner.is_implied_by(&outer));
4595 }
4596
4597 #[test]
4598 fn test_relation_is_implied_by_equal() {
4599 let inner = Relation::new(
4601 "pkg",
4602 Some((VersionConstraint::Equal, "1.0".parse().unwrap())),
4603 );
4604 let outer = Relation::new(
4605 "pkg",
4606 Some((VersionConstraint::Equal, "1.0".parse().unwrap())),
4607 );
4608 assert!(inner.is_implied_by(&outer));
4609
4610 let outer = Relation::new(
4612 "pkg",
4613 Some((VersionConstraint::Equal, "2.0".parse().unwrap())),
4614 );
4615 assert!(!inner.is_implied_by(&outer));
4616
4617 let outer = Relation::new(
4619 "pkg",
4620 Some((VersionConstraint::GreaterThanEqual, "1.0".parse().unwrap())),
4621 );
4622 assert!(!inner.is_implied_by(&outer));
4623 }
4624
4625 #[test]
4626 fn test_relation_is_implied_by_greater_than() {
4627 let inner = Relation::new(
4629 "pkg",
4630 Some((VersionConstraint::GreaterThan, "1.0".parse().unwrap())),
4631 );
4632 let outer = Relation::new(
4633 "pkg",
4634 Some((VersionConstraint::GreaterThan, "2.0".parse().unwrap())),
4635 );
4636 assert!(inner.is_implied_by(&outer));
4637
4638 let outer = Relation::new(
4640 "pkg",
4641 Some((VersionConstraint::Equal, "2.0".parse().unwrap())),
4642 );
4643 assert!(inner.is_implied_by(&outer));
4644
4645 let outer = Relation::new(
4647 "pkg",
4648 Some((VersionConstraint::GreaterThanEqual, "1.5".parse().unwrap())),
4649 );
4650 assert!(inner.is_implied_by(&outer));
4651
4652 let outer = Relation::new(
4654 "pkg",
4655 Some((VersionConstraint::GreaterThanEqual, "1.0".parse().unwrap())),
4656 );
4657 assert!(!inner.is_implied_by(&outer));
4658 }
4659
4660 #[test]
4661 fn test_relation_is_implied_by_less_than() {
4662 let inner = Relation::new(
4664 "pkg",
4665 Some((VersionConstraint::LessThan, "2.0".parse().unwrap())),
4666 );
4667 let outer = Relation::new(
4668 "pkg",
4669 Some((VersionConstraint::LessThan, "1.0".parse().unwrap())),
4670 );
4671 assert!(inner.is_implied_by(&outer));
4672
4673 let outer = Relation::new(
4675 "pkg",
4676 Some((VersionConstraint::Equal, "1.0".parse().unwrap())),
4677 );
4678 assert!(inner.is_implied_by(&outer));
4679
4680 let outer = Relation::new(
4682 "pkg",
4683 Some((VersionConstraint::LessThanEqual, "1.5".parse().unwrap())),
4684 );
4685 assert!(inner.is_implied_by(&outer));
4686 }
4687
4688 #[test]
4689 fn test_relation_is_implied_by_incompatible_constraints() {
4690 let inner = Relation::new(
4692 "pkg",
4693 Some((VersionConstraint::GreaterThanEqual, "1.0".parse().unwrap())),
4694 );
4695 let outer = Relation::new(
4696 "pkg",
4697 Some((VersionConstraint::LessThanEqual, "2.0".parse().unwrap())),
4698 );
4699 assert!(!inner.is_implied_by(&outer));
4700 assert!(!outer.is_implied_by(&inner));
4701 }
4702
4703 #[test]
4704 fn test_entry_is_implied_by_identical() {
4705 let inner: Entry = "pkg (>= 1.0)".parse().unwrap();
4706 let outer: Entry = "pkg (>= 1.0)".parse().unwrap();
4707 assert!(inner.is_implied_by(&outer));
4708 }
4709
4710 #[test]
4711 fn test_entry_is_implied_by_or_group() {
4712 let inner: Entry = "pkg (>= 1.0)".parse().unwrap();
4714 let outer: Entry = "pkg (>= 1.5) | libc6".parse().unwrap();
4715 assert!(inner.is_implied_by(&outer));
4716 }
4717
4718 #[test]
4719 fn test_entry_is_implied_by_simple_or() {
4720 let inner: Entry = "pkg1 | pkg2".parse().unwrap();
4722 let outer: Entry = "pkg1".parse().unwrap();
4723 assert!(inner.is_implied_by(&outer));
4724
4725 let outer: Entry = "pkg2".parse().unwrap();
4727 assert!(inner.is_implied_by(&outer));
4728 }
4729
4730 #[test]
4731 fn test_entry_is_implied_by_not_implied() {
4732 let inner: Entry = "pkg (>= 2.0)".parse().unwrap();
4734 let outer: Entry = "pkg (>= 1.0)".parse().unwrap();
4735 assert!(!inner.is_implied_by(&outer));
4736 }
4737
4738 #[test]
4739 fn test_entry_is_implied_by_different_packages() {
4740 let inner: Entry = "pkg1".parse().unwrap();
4741 let outer: Entry = "pkg2".parse().unwrap();
4742 assert!(!inner.is_implied_by(&outer));
4743 }
4744
4745 #[test]
4746 fn test_entry_is_implied_by_complex_or() {
4747 let inner: Entry = "pkg1 | pkg2".parse().unwrap();
4749 let outer: Entry = "pkg1 | pkg2".parse().unwrap();
4750 assert!(inner.is_implied_by(&outer));
4751
4752 let outer: Entry = "pkg1 | pkg2 | pkg3".parse().unwrap();
4754 assert!(inner.is_implied_by(&outer));
4755 }
4756
4757 #[test]
4758 fn test_parse_version_with_epoch() {
4759 let input = "amule-dbg (<< 1:2.3.2-2~)";
4762 let parsed: Relations = input.parse().unwrap();
4763 assert_eq!(parsed.to_string(), input);
4764 assert_eq!(parsed.entries().count(), 1);
4765 let entry = parsed.entries().next().unwrap();
4766 assert_eq!(entry.to_string(), "amule-dbg (<< 1:2.3.2-2~)");
4767 assert_eq!(entry.relations().count(), 1);
4768 let relation = entry.relations().next().unwrap();
4769 assert_eq!(relation.name(), "amule-dbg");
4770 assert_eq!(relation.to_string(), "amule-dbg (<< 1:2.3.2-2~)");
4771 assert_eq!(
4772 relation.version(),
4773 Some((VersionConstraint::LessThan, "1:2.3.2-2~".parse().unwrap()))
4774 );
4775 }
4776
4777 #[test]
4778 fn test_ensure_relation_add_new() {
4779 let mut relations: Relations = "python3".parse().unwrap();
4781 let new_entry: Entry = "debhelper (>= 12)".parse().unwrap();
4782 let added = relations.ensure_relation(new_entry);
4783 assert!(added);
4784 assert_eq!(relations.to_string(), "debhelper (>= 12), python3");
4786 }
4787
4788 #[test]
4789 fn test_ensure_relation_already_satisfied() {
4790 let mut relations: Relations = "debhelper (>= 13)".parse().unwrap();
4792 let new_entry: Entry = "debhelper (>= 12)".parse().unwrap();
4793 let added = relations.ensure_relation(new_entry);
4794 assert!(!added);
4795 assert_eq!(relations.to_string(), "debhelper (>= 13)");
4796 }
4797
4798 #[test]
4799 fn test_ensure_relation_replace_weaker() {
4800 let mut relations: Relations = "debhelper (>= 11)".parse().unwrap();
4802 let new_entry: Entry = "debhelper (>= 13)".parse().unwrap();
4803 let added = relations.ensure_relation(new_entry);
4804 assert!(added);
4805 assert_eq!(relations.to_string(), "debhelper (>= 13)");
4806 }
4807
4808 #[test]
4809 fn test_ensure_relation_replace_multiple_weaker() {
4810 let mut relations: Relations = "debhelper (>= 11), debhelper (>= 10), python3"
4812 .parse()
4813 .unwrap();
4814 let new_entry: Entry = "debhelper (>= 13)".parse().unwrap();
4815 let added = relations.ensure_relation(new_entry);
4816 assert!(added);
4817 assert_eq!(relations.to_string(), "debhelper (>= 13), python3");
4818 }
4819
4820 #[test]
4821 fn test_ensure_relation_identical_entry() {
4822 let mut relations: Relations = "debhelper (>= 12)".parse().unwrap();
4824 let new_entry: Entry = "debhelper (>= 12)".parse().unwrap();
4825 let added = relations.ensure_relation(new_entry);
4826 assert!(!added);
4827 assert_eq!(relations.to_string(), "debhelper (>= 12)");
4828 }
4829
4830 #[test]
4831 fn test_ensure_relation_no_version_constraint() {
4832 let mut relations: Relations = "python3".parse().unwrap();
4834 let new_entry: Entry = "debhelper".parse().unwrap();
4835 let added = relations.ensure_relation(new_entry);
4836 assert!(added);
4837 assert_eq!(relations.to_string(), "debhelper, python3");
4839 }
4840
4841 #[test]
4842 fn test_ensure_relation_strengthen_unversioned() {
4843 let mut relations: Relations = "debhelper".parse().unwrap();
4846 let new_entry: Entry = "debhelper (>= 12)".parse().unwrap();
4847 let added = relations.ensure_relation(new_entry);
4848 assert!(added);
4849 assert_eq!(relations.to_string(), "debhelper (>= 12)");
4850 }
4851
4852 #[test]
4853 fn test_ensure_relation_versioned_implies_unversioned() {
4854 let mut relations: Relations = "debhelper (>= 12)".parse().unwrap();
4857 let new_entry: Entry = "debhelper".parse().unwrap();
4858 let added = relations.ensure_relation(new_entry);
4859 assert!(!added);
4860 assert_eq!(relations.to_string(), "debhelper (>= 12)");
4861 }
4862
4863 #[test]
4864 fn test_ensure_relation_preserves_whitespace() {
4865 let mut relations: Relations = "python3, rustc".parse().unwrap();
4867 let new_entry: Entry = "debhelper (>= 12)".parse().unwrap();
4868 let added = relations.ensure_relation(new_entry);
4869 assert!(added);
4870 assert_eq!(relations.to_string(), "debhelper (>= 12), python3, rustc");
4872 }
4873
4874 #[test]
4875 fn test_ensure_relation_empty_relations() {
4876 let mut relations: Relations = Relations::new();
4878 let new_entry: Entry = "debhelper (>= 12)".parse().unwrap();
4879 let added = relations.ensure_relation(new_entry);
4880 assert!(added);
4881 assert_eq!(relations.to_string(), "debhelper (>= 12)");
4882 }
4883
4884 #[test]
4885 fn test_ensure_relation_alternative_dependencies() {
4886 let mut relations: Relations = "python3 | python3-minimal".parse().unwrap();
4888 let new_entry: Entry = "debhelper (>= 12)".parse().unwrap();
4889 let added = relations.ensure_relation(new_entry);
4890 assert!(added);
4891 assert_eq!(
4893 relations.to_string(),
4894 "debhelper (>= 12), python3 | python3-minimal"
4895 );
4896 }
4897
4898 #[test]
4899 fn test_ensure_relation_replace_in_middle() {
4900 let mut relations: Relations = "python3, debhelper (>= 11), rustc".parse().unwrap();
4902 let new_entry: Entry = "debhelper (>= 13)".parse().unwrap();
4903 let added = relations.ensure_relation(new_entry);
4904 assert!(added);
4905 assert_eq!(relations.to_string(), "python3, debhelper (>= 13), rustc");
4906 }
4907
4908 #[test]
4909 fn test_ensure_relation_with_different_package() {
4910 let mut relations: Relations = "python3, debhelper (>= 12)".parse().unwrap();
4912 let new_entry: Entry = "rustc".parse().unwrap();
4913 let added = relations.ensure_relation(new_entry);
4914 assert!(added);
4915 assert_eq!(relations.to_string(), "python3, debhelper (>= 12), rustc");
4916 }
4917
4918 #[test]
4919 fn test_parse_invalid_token_in_arch_list() {
4920 let input = "foo [>= bar]";
4921 let result: Result<Relations, _> = input.parse();
4922 assert!(
4923 result.is_err(),
4924 "Expected error for invalid token in architecture list"
4925 );
4926 }
4927
4928 #[test]
4929 fn test_parse_invalid_token_in_profile_list() {
4930 let input = "foo <[] baz>";
4931 let result: Result<Relations, _> = input.parse();
4932 assert!(
4933 result.is_err(),
4934 "Expected error for invalid token in profile list"
4935 );
4936 }
4937
4938 #[test]
4939 fn test_parse_relaxed_unterminated_arch_list() {
4940 let (relations, errors) = Relations::parse_relaxed("libc6 [", true);
4941 assert!(!errors.is_empty());
4942 assert_eq!(relations.to_string(), "libc6 [");
4943 }
4944
4945 #[test]
4946 fn test_parse_relaxed_partial_arch_name() {
4947 let (relations, errors) = Relations::parse_relaxed("libc6 [amd", true);
4948 assert!(!errors.is_empty());
4949 assert_eq!(relations.to_string(), "libc6 [amd");
4950 }
4951
4952 #[test]
4953 fn test_parse_relaxed_unterminated_profile_list() {
4954 let (relations, errors) = Relations::parse_relaxed("libc6 <cross", true);
4955 assert!(!errors.is_empty());
4956 assert_eq!(relations.to_string(), "libc6 <cross");
4957 }
4958
4959 #[test]
4960 fn test_parse_relaxed_unterminated_substvar() {
4961 let (relations, errors) = Relations::parse_relaxed("${shlibs:Depends", true);
4962 assert!(!errors.is_empty());
4963 assert_eq!(relations.to_string(), "${shlibs:Depends");
4964 }
4965
4966 #[test]
4967 fn test_parse_relaxed_empty_substvar() {
4968 let (relations, errors) = Relations::parse_relaxed("${", true);
4969 assert!(!errors.is_empty());
4970 assert_eq!(relations.to_string(), "${");
4971 }
4972
4973 #[test]
4974 fn test_parse_with_comments() {
4975 let input = "dh-python,\nlibsvn-dev,\n# python-all-dbg (>= 2.6.6-3),\npython3-all-dev,\n# python3-all-dbg,\npython3-docutils";
4976 let relations: Relations = input.parse().unwrap();
4977 let entries: Vec<_> = relations.entries().collect();
4978 assert_eq!(entries.len(), 4);
4979 assert_eq!(entries[0].to_string(), "dh-python");
4980 assert_eq!(entries[1].to_string(), "libsvn-dev");
4981 assert_eq!(entries[2].to_string(), "python3-all-dev");
4982 assert_eq!(entries[3].to_string(), "python3-docutils");
4983 assert_eq!(relations.to_string(), input);
4985 }
4986
4987 #[test]
4988 fn test_remove_entry_with_adjacent_comment() {
4989 let input = "dh-python,\n# commented-out,\npython3-all-dev";
4990 let mut relations: Relations = input.parse().unwrap();
4991 assert_eq!(relations.entries().count(), 2);
4992 relations.remove_entry(0);
4993 assert_eq!(relations.entries().count(), 1);
4994 assert_eq!(
4995 relations.entries().next().unwrap().to_string(),
4996 "python3-all-dev"
4997 );
4998 }
4999
5000 #[test]
5001 fn test_insert_entry_with_comments_present() {
5002 let input = "dh-python,\n# commented-out,\npython3-all-dev";
5003 let mut relations: Relations = input.parse().unwrap();
5004 let new_entry: Entry = "libfoo-dev".parse().unwrap();
5005 relations.push(new_entry);
5006 let entries: Vec<_> = relations.entries().collect();
5008 assert_eq!(entries.len(), 3);
5009 assert_eq!(entries[2].to_string(), "libfoo-dev");
5010 }
5011
5012 #[test]
5013 fn test_drop_dependency_with_comments() {
5014 let input = "dh-python,\n# commented-out,\npython3-all-dev,\nlibfoo-dev";
5015 let mut relations: Relations = input.parse().unwrap();
5016 assert!(relations.drop_dependency("python3-all-dev"));
5017 let entries: Vec<_> = relations.entries().collect();
5018 assert_eq!(entries.len(), 2);
5019 assert_eq!(entries[0].to_string(), "dh-python");
5020 assert_eq!(entries[1].to_string(), "libfoo-dev");
5021 }
5022
5023 #[test]
5024 fn test_relation_remove_first_with_malformed_tree() {
5025 let (relations, errors) = Relations::parse_relaxed("foo @ bar | baz", false);
5028 assert!(!errors.is_empty());
5029 let entry = relations.get_entry(0).unwrap();
5030 let mut relation = entry.get_relation(0).unwrap();
5031 relation.remove();
5033 }
5034}