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.try_name() == other.try_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.try_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.substvar_nodes().map(|s| s.to_string())
1030 }
1031
1032 pub fn substvar_nodes(&self) -> impl Iterator<Item = Substvar> + '_ {
1034 self.0.children().filter_map(Substvar::cast)
1035 }
1036
1037 pub fn parse_relaxed(s: &str, allow_substvar: bool) -> (Relations, Vec<String>) {
1039 let parse = parse(s, allow_substvar);
1040 (parse.root_mut(), parse.errors)
1041 }
1042
1043 pub fn satisfied_by(&self, package_version: impl crate::VersionLookup + Copy) -> bool {
1045 self.entries().all(|e| e.satisfied_by(package_version))
1046 }
1047
1048 pub fn is_empty(&self) -> bool {
1050 self.entries().count() == 0
1051 }
1052
1053 pub fn len(&self) -> usize {
1055 self.entries().count()
1056 }
1057
1058 pub fn ensure_minimum_version(&mut self, package: &str, minimum_version: &Version) {
1081 let mut found = false;
1082 let mut obsolete_indices = vec![];
1083 let mut update_idx = None;
1084
1085 let entries: Vec<_> = self.entries().collect();
1086 for (idx, entry) in entries.iter().enumerate() {
1087 let relations: Vec<_> = entry.relations().collect();
1088
1089 let names: Vec<_> = relations.iter().filter_map(|r| r.try_name()).collect();
1091 if names.len() > 1 && names.contains(&package.to_string()) {
1092 let is_obsolete = relations.iter().any(|r| {
1094 if r.try_name().as_deref() != Some(package) {
1095 return false;
1096 }
1097 if let Some((vc, ver)) = r.version() {
1098 matches!(vc, VersionConstraint::GreaterThan if &ver < minimum_version)
1099 || matches!(vc, VersionConstraint::GreaterThanEqual if &ver <= minimum_version)
1100 } else {
1101 false
1102 }
1103 });
1104 if is_obsolete {
1105 obsolete_indices.push(idx);
1106 }
1107 continue;
1108 }
1109
1110 if names.len() == 1 && names[0] == package {
1112 found = true;
1113 let relation = relations.into_iter().next().unwrap();
1114
1115 let should_update = if let Some((vc, ver)) = relation.version() {
1117 match vc {
1118 VersionConstraint::GreaterThanEqual | VersionConstraint::GreaterThan => {
1119 &ver < minimum_version
1120 }
1121 _ => false,
1122 }
1123 } else {
1124 true
1125 };
1126
1127 if should_update {
1128 update_idx = Some(idx);
1129 }
1130 break;
1131 }
1132 }
1133
1134 if let Some(idx) = update_idx {
1136 let relation = Relation::new(
1137 package,
1138 Some((VersionConstraint::GreaterThanEqual, minimum_version.clone())),
1139 );
1140 let mut entry = self.get_entry(idx).unwrap();
1142 entry.replace(0, relation);
1143 self.replace(idx, entry);
1144 }
1145
1146 for idx in obsolete_indices.into_iter().rev() {
1148 self.remove_entry(idx);
1149 }
1150
1151 if !found {
1153 let relation = Relation::new(
1154 package,
1155 Some((VersionConstraint::GreaterThanEqual, minimum_version.clone())),
1156 );
1157 self.push(Entry::from(relation));
1158 }
1159 }
1160
1161 pub fn ensure_exact_version(&mut self, package: &str, version: &Version) {
1176 let mut found = false;
1177 let mut update_idx = None;
1178
1179 let entries: Vec<_> = self.entries().collect();
1180 for (idx, entry) in entries.iter().enumerate() {
1181 let relations: Vec<_> = entry.relations().collect();
1182 let names: Vec<_> = relations.iter().filter_map(|r| r.try_name()).collect();
1183
1184 if names.len() > 1 && names[0] == package {
1185 panic!("Complex rule for {}, aborting", package);
1186 }
1187
1188 if names.len() == 1 && names[0] == package {
1189 found = true;
1190 let relation = relations.into_iter().next().unwrap();
1191
1192 let should_update = if let Some((vc, ver)) = relation.version() {
1193 vc != VersionConstraint::Equal || &ver != version
1194 } else {
1195 true
1196 };
1197
1198 if should_update {
1199 update_idx = Some(idx);
1200 }
1201 break;
1202 }
1203 }
1204
1205 if let Some(idx) = update_idx {
1207 let relation =
1208 Relation::new(package, Some((VersionConstraint::Equal, version.clone())));
1209 let mut entry = self.get_entry(idx).unwrap();
1211 entry.replace(0, relation);
1212 self.replace(idx, entry);
1213 }
1214
1215 if !found {
1216 let relation =
1217 Relation::new(package, Some((VersionConstraint::Equal, version.clone())));
1218 self.push(Entry::from(relation));
1219 }
1220 }
1221
1222 pub fn ensure_some_version(&mut self, package: &str) {
1240 for entry in self.entries() {
1241 let relations: Vec<_> = entry.relations().collect();
1242 let names: Vec<_> = relations.iter().filter_map(|r| r.try_name()).collect();
1243
1244 if names.len() > 1 && names[0] == package {
1245 panic!("Complex rule for {}, aborting", package);
1246 }
1247
1248 if names.len() == 1 && names[0] == package {
1249 return;
1251 }
1252 }
1253
1254 let relation = Relation::simple(package);
1256 self.push(Entry::from(relation));
1257 }
1258
1259 pub fn ensure_relation(&mut self, new_entry: Entry) -> bool {
1284 let mut to_replace: Vec<usize> = Vec::new();
1285 let mut to_remove: Vec<usize> = Vec::new();
1286 let mut already_satisfied = false;
1287
1288 for (idx, existing_entry) in self.entries().enumerate() {
1290 if new_entry.is_implied_by(&existing_entry) {
1291 already_satisfied = true;
1293 break;
1294 }
1295 if existing_entry.is_implied_by(&new_entry) {
1296 if to_replace.is_empty() {
1299 to_replace.push(idx);
1300 } else {
1301 to_remove.push(idx);
1302 }
1303 }
1304 }
1305
1306 if already_satisfied {
1307 return false;
1308 }
1309
1310 for idx in to_remove.into_iter().rev() {
1312 self.remove_entry(idx);
1313 }
1314
1315 if let Some(&idx) = to_replace.first() {
1317 self.replace(idx, new_entry);
1318 } else {
1319 self.add_dependency(new_entry, None);
1320 }
1321
1322 true
1323 }
1324
1325 pub fn ensure_substvar(&mut self, substvar: &str) -> Result<(), String> {
1345 for existing in self.substvars() {
1347 if existing.trim() == substvar.trim() {
1348 return Ok(());
1349 }
1350 }
1351
1352 let (parsed, errors) = Relations::parse_relaxed(substvar, true);
1354 if !errors.is_empty() {
1355 return Err(errors.join("\n"));
1356 }
1357
1358 let whitespace = self.detect_whitespace_pattern(" ");
1360
1361 for substvar_node in parsed.0.children().filter(|n| n.kind() == SUBSTVAR) {
1363 let has_content = self.entries().next().is_some() || self.substvars().next().is_some();
1364
1365 let mut builder = GreenNodeBuilder::new();
1366 builder.start_node(ROOT.into());
1367
1368 for child in self.0.children_with_tokens() {
1370 match child {
1371 NodeOrToken::Node(n) => inject(&mut builder, n),
1372 NodeOrToken::Token(t) => builder.token(t.kind().into(), t.text()),
1373 }
1374 }
1375
1376 if has_content {
1378 builder.token(COMMA.into(), ",");
1379 builder.token(WHITESPACE.into(), whitespace.as_str());
1380 }
1381
1382 inject(&mut builder, substvar_node);
1384
1385 builder.finish_node();
1386 self.0 = SyntaxNode::new_root_mut(builder.finish());
1387 }
1388
1389 Ok(())
1390 }
1391
1392 pub fn drop_substvar(&mut self, substvar: &str) {
1409 let substvars_to_remove: Vec<_> = self
1411 .0
1412 .children()
1413 .filter_map(Substvar::cast)
1414 .filter(|s| s.to_string().trim() == substvar.trim())
1415 .collect();
1416
1417 for substvar_node in substvars_to_remove {
1418 let is_first = !substvar_node
1420 .0
1421 .siblings(Direction::Prev)
1422 .skip(1)
1423 .any(|n| n.kind() == ENTRY || n.kind() == SUBSTVAR);
1424
1425 let mut removed_comma = false;
1426
1427 while let Some(n) = substvar_node.0.next_sibling_or_token() {
1429 if matches!(n.kind(), WHITESPACE | NEWLINE | COMMENT) {
1430 n.detach();
1431 } else if n.kind() == COMMA {
1432 n.detach();
1433 removed_comma = true;
1434 break;
1435 } else {
1436 break;
1437 }
1438 }
1439
1440 if !is_first {
1442 while let Some(n) = substvar_node.0.prev_sibling_or_token() {
1443 if matches!(n.kind(), WHITESPACE | NEWLINE | COMMENT) {
1444 n.detach();
1445 } else if !removed_comma && n.kind() == COMMA {
1446 n.detach();
1447 break;
1448 } else {
1449 break;
1450 }
1451 }
1452 } else {
1453 while let Some(n) = substvar_node.0.next_sibling_or_token() {
1455 if matches!(n.kind(), WHITESPACE | NEWLINE | COMMENT) {
1456 n.detach();
1457 } else {
1458 break;
1459 }
1460 }
1461 }
1462
1463 substvar_node.0.detach();
1465 }
1466 }
1467
1468 pub fn filter_entries<F>(&mut self, keep: F)
1484 where
1485 F: Fn(&Entry) -> bool,
1486 {
1487 let indices_to_remove: Vec<_> = self
1488 .entries()
1489 .enumerate()
1490 .filter_map(|(idx, entry)| if keep(&entry) { None } else { Some(idx) })
1491 .collect();
1492
1493 for idx in indices_to_remove.into_iter().rev() {
1495 self.remove_entry(idx);
1496 }
1497 }
1498
1499 pub fn is_sorted(&self, sorting_order: &impl SortingOrder) -> bool {
1515 let mut last_name: Option<String> = None;
1516 for entry in self.entries() {
1517 let mut relations = entry.relations();
1519 let Some(relation) = relations.next() else {
1520 continue;
1521 };
1522
1523 let Some(name) = relation.try_name() else {
1524 continue;
1525 };
1526
1527 if sorting_order.ignore(&name) {
1529 continue;
1530 }
1531
1532 if let Some(ref last) = last_name {
1534 if sorting_order.lt(&name, last) {
1535 return false;
1536 }
1537 }
1538
1539 last_name = Some(name);
1540 }
1541 true
1542 }
1543
1544 fn find_insert_position(&self, entry: &Entry) -> usize {
1556 let Some(relation) = entry.relations().next() else {
1558 return self.len();
1560 };
1561 let Some(package_name) = relation.try_name() else {
1562 return self.len();
1563 };
1564
1565 let count = self.entries().filter(|e| !e.is_empty()).count();
1567
1568 let sorting_order: Box<dyn SortingOrder> = if count < 2 {
1570 Box::new(WrapAndSortOrder)
1571 } else {
1572 if self.is_sorted(&WrapAndSortOrder) {
1575 Box::new(WrapAndSortOrder)
1576 } else if self.is_sorted(&DefaultSortingOrder) {
1577 Box::new(DefaultSortingOrder)
1578 } else {
1579 return self.len();
1581 }
1582 };
1583
1584 if sorting_order.ignore(&package_name) {
1586 return self.len();
1587 }
1588
1589 let mut position = 0;
1591 for (idx, existing_entry) in self.entries().enumerate() {
1592 let mut existing_relations = existing_entry.relations();
1593 let Some(existing_relation) = existing_relations.next() else {
1594 position += 1;
1596 continue;
1597 };
1598
1599 let Some(existing_name) = existing_relation.try_name() else {
1600 position += 1;
1601 continue;
1602 };
1603
1604 if sorting_order.ignore(&existing_name) {
1606 position += 1;
1607 continue;
1608 }
1609
1610 if sorting_order.lt(&package_name, &existing_name) {
1612 return idx;
1613 }
1614 position += 1;
1615 }
1616
1617 position
1618 }
1619
1620 pub fn drop_dependency(&mut self, package: &str) -> bool {
1638 let indices_to_remove: Vec<_> = self
1639 .entries()
1640 .enumerate()
1641 .filter_map(|(idx, entry)| {
1642 let relations: Vec<_> = entry.relations().collect();
1643 let names: Vec<_> = relations.iter().filter_map(|r| r.try_name()).collect();
1644 if names == vec![package] {
1645 Some(idx)
1646 } else {
1647 None
1648 }
1649 })
1650 .collect();
1651
1652 let found = !indices_to_remove.is_empty();
1653
1654 for idx in indices_to_remove.into_iter().rev() {
1656 self.remove_entry(idx);
1657 }
1658
1659 found
1660 }
1661
1662 pub fn add_dependency(&mut self, entry: Entry, position: Option<usize>) {
1683 let pos = position.unwrap_or_else(|| self.find_insert_position(&entry));
1684 self.insert(pos, entry);
1685 }
1686
1687 pub fn get_relation(&self, package: &str) -> Result<(usize, Entry), String> {
1711 for (idx, entry) in self.entries().enumerate() {
1712 let relations: Vec<_> = entry.relations().collect();
1713 let names: Vec<_> = relations.iter().filter_map(|r| r.try_name()).collect();
1714
1715 if names.len() > 1 && names.contains(&package.to_string()) {
1716 return Err(format!("Complex rule for {}, aborting", package));
1717 }
1718
1719 if names.len() == 1 && names[0] == package {
1720 return Ok((idx, entry));
1721 }
1722 }
1723 Err(format!("Package {} not found", package))
1724 }
1725
1726 pub fn iter_relations_for(&self, package: &str) -> impl Iterator<Item = (usize, Entry)> + '_ {
1743 let package = package.to_string();
1744 self.entries().enumerate().filter(move |(_, entry)| {
1745 let names: Vec<_> = entry.relations().filter_map(|r| r.try_name()).collect();
1746 names.contains(&package)
1747 })
1748 }
1749
1750 pub fn has_relation(&self, package: &str) -> bool {
1767 self.entries().any(|entry| {
1768 entry
1769 .relations()
1770 .any(|r| r.try_name().as_deref() == Some(package))
1771 })
1772 }
1773}
1774
1775impl From<Vec<Entry>> for Relations {
1776 fn from(entries: Vec<Entry>) -> Self {
1777 let mut builder = GreenNodeBuilder::new();
1778 builder.start_node(ROOT.into());
1779 for (i, entry) in entries.into_iter().enumerate() {
1780 if i > 0 {
1781 builder.token(COMMA.into(), ",");
1782 builder.token(WHITESPACE.into(), " ");
1783 }
1784 inject(&mut builder, entry.0);
1785 }
1786 builder.finish_node();
1787 Relations(SyntaxNode::new_root_mut(builder.finish()))
1788 }
1789}
1790
1791impl From<Entry> for Relations {
1792 fn from(entry: Entry) -> Self {
1793 Self::from(vec![entry])
1794 }
1795}
1796
1797impl Default for Entry {
1798 fn default() -> Self {
1799 Self::new()
1800 }
1801}
1802
1803impl PartialOrd for Entry {
1804 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
1805 Some(self.cmp(other))
1806 }
1807}
1808
1809impl Eq for Entry {}
1810
1811impl Ord for Entry {
1812 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1813 let mut rels_a = self.relations();
1814 let mut rels_b = other.relations();
1815 while let (Some(a), Some(b)) = (rels_a.next(), rels_b.next()) {
1816 match a.cmp(&b) {
1817 std::cmp::Ordering::Equal => continue,
1818 x => return x,
1819 }
1820 }
1821
1822 if rels_a.next().is_some() {
1823 return std::cmp::Ordering::Greater;
1824 }
1825
1826 if rels_b.next().is_some() {
1827 return std::cmp::Ordering::Less;
1828 }
1829
1830 std::cmp::Ordering::Equal
1831 }
1832}
1833
1834impl Entry {
1835 pub fn new() -> Self {
1837 let mut builder = GreenNodeBuilder::new();
1838 builder.start_node(SyntaxKind::ENTRY.into());
1839 builder.finish_node();
1840 Entry(SyntaxNode::new_root_mut(builder.finish()))
1841 }
1842
1843 pub fn replace(&mut self, idx: usize, relation: Relation) {
1845 let current_relation = self.get_relation(idx).unwrap();
1846
1847 let old_root = current_relation.0;
1848 let new_root = relation.0;
1849 let mut prev = new_root.first_child_or_token();
1851 let mut new_head_len = 0;
1852 while let Some(p) = prev {
1854 if p.kind() == WHITESPACE || p.kind() == NEWLINE {
1855 new_head_len += 1;
1856 prev = p.next_sibling_or_token();
1857 } else {
1858 break;
1859 }
1860 }
1861 let mut new_tail_len = 0;
1862 let mut next = new_root.last_child_or_token();
1863 while let Some(n) = next {
1864 if n.kind() == WHITESPACE || n.kind() == NEWLINE {
1865 new_tail_len += 1;
1866 next = n.prev_sibling_or_token();
1867 } else {
1868 break;
1869 }
1870 }
1871 let mut prev = old_root.first_child_or_token();
1873 let mut old_head = vec![];
1874 while let Some(p) = prev {
1875 if p.kind() == WHITESPACE || p.kind() == NEWLINE {
1876 old_head.push(p.clone());
1877 prev = p.next_sibling_or_token();
1878 } else {
1879 break;
1880 }
1881 }
1882 let mut old_tail = vec![];
1883 let mut next = old_root.last_child_or_token();
1884 while let Some(n) = next {
1885 if n.kind() == WHITESPACE || n.kind() == NEWLINE {
1886 old_tail.push(n.clone());
1887 next = n.prev_sibling_or_token();
1888 } else {
1889 break;
1890 }
1891 }
1892 new_root.splice_children(0..new_head_len, old_head);
1893 let tail_pos = new_root.children_with_tokens().count() - new_tail_len;
1894 new_root.splice_children(
1895 tail_pos - new_tail_len..tail_pos,
1896 old_tail.into_iter().rev(),
1897 );
1898 let index = old_root.index();
1899 self.0
1900 .splice_children(index..index + 1, vec![new_root.into()]);
1901 }
1902
1903 #[must_use]
1905 pub fn wrap_and_sort(&self) -> Self {
1906 let mut relations = self
1907 .relations()
1908 .map(|r| r.wrap_and_sort())
1909 .collect::<Vec<_>>();
1910 relations.sort();
1912 Self::from(relations)
1913 }
1914
1915 pub fn relations(&self) -> impl Iterator<Item = Relation> + '_ {
1917 self.0.children().filter_map(Relation::cast)
1918 }
1919
1920 pub fn iter(&self) -> impl Iterator<Item = Relation> + '_ {
1922 self.relations()
1923 }
1924
1925 pub fn get_relation(&self, idx: usize) -> Option<Relation> {
1927 self.relations().nth(idx)
1928 }
1929
1930 pub fn remove_relation(&self, idx: usize) -> Relation {
1943 let mut relation = self.get_relation(idx).unwrap();
1944 relation.remove();
1945 relation
1946 }
1947
1948 pub fn satisfied_by(&self, package_version: impl crate::VersionLookup + Copy) -> bool {
1964 self.relations().any(|r| {
1965 let Some(name) = r.try_name() else {
1966 return false;
1967 };
1968 let actual = package_version.lookup_version(name.as_str());
1969 if let Some((vc, version)) = r.version() {
1970 if let Some(actual) = actual {
1971 match vc {
1972 VersionConstraint::GreaterThanEqual => *actual >= version,
1973 VersionConstraint::LessThanEqual => *actual <= version,
1974 VersionConstraint::Equal => *actual == version,
1975 VersionConstraint::GreaterThan => *actual > version,
1976 VersionConstraint::LessThan => *actual < version,
1977 }
1978 } else {
1979 false
1980 }
1981 } else {
1982 actual.is_some()
1983 }
1984 })
1985 }
1986
1987 pub fn remove(&mut self) {
1998 let mut removed_comma = false;
1999 let is_first = !self
2000 .0
2001 .siblings(Direction::Prev)
2002 .skip(1)
2003 .any(|n| n.kind() == ENTRY);
2004 while let Some(n) = self.0.next_sibling_or_token() {
2005 if matches!(n.kind(), WHITESPACE | NEWLINE | COMMENT) {
2006 n.detach();
2007 } else if n.kind() == COMMA {
2008 n.detach();
2009 removed_comma = true;
2010 break;
2011 } else {
2012 panic!("Unexpected node: {:?}", n);
2013 }
2014 }
2015 if !is_first {
2016 while let Some(n) = self.0.prev_sibling_or_token() {
2017 if matches!(n.kind(), WHITESPACE | NEWLINE | COMMENT) {
2018 n.detach();
2019 } else if !removed_comma && n.kind() == COMMA {
2020 n.detach();
2021 break;
2022 } else {
2023 break;
2024 }
2025 }
2026 } else {
2027 while let Some(n) = self.0.next_sibling_or_token() {
2028 if matches!(n.kind(), WHITESPACE | NEWLINE | COMMENT) {
2029 n.detach();
2030 } else {
2031 break;
2032 }
2033 }
2034 }
2035 self.0.detach();
2036 }
2037
2038 pub fn is_empty(&self) -> bool {
2040 self.relations().count() == 0
2041 }
2042
2043 pub fn len(&self) -> usize {
2045 self.relations().count()
2046 }
2047
2048 pub fn push(&mut self, relation: Relation) {
2061 let is_empty = !self
2062 .0
2063 .children_with_tokens()
2064 .any(|n| n.kind() == PIPE || n.kind() == RELATION);
2065
2066 let (position, new_children) = if let Some(current_relation) = self.relations().last() {
2067 let to_insert: Vec<NodeOrToken<GreenNode, GreenToken>> = if is_empty {
2068 vec![relation.0.green().into()]
2069 } else {
2070 vec![
2071 NodeOrToken::Token(GreenToken::new(WHITESPACE.into(), " ")),
2072 NodeOrToken::Token(GreenToken::new(PIPE.into(), "|")),
2073 NodeOrToken::Token(GreenToken::new(WHITESPACE.into(), " ")),
2074 relation.0.green().into(),
2075 ]
2076 };
2077
2078 (current_relation.0.index() + 1, to_insert)
2079 } else {
2080 let child_count = self.0.children_with_tokens().count();
2081 (
2082 child_count,
2083 if is_empty {
2084 vec![relation.0.green().into()]
2085 } else {
2086 vec![
2087 NodeOrToken::Token(GreenToken::new(PIPE.into(), "|")),
2088 NodeOrToken::Token(GreenToken::new(WHITESPACE.into(), " ")),
2089 relation.0.green().into(),
2090 ]
2091 },
2092 )
2093 };
2094
2095 let new_root = SyntaxNode::new_root_mut(
2096 self.0.replace_with(
2097 self.0
2098 .green()
2099 .splice_children(position..position, new_children),
2100 ),
2101 );
2102
2103 if let Some(parent) = self.0.parent() {
2104 parent.splice_children(self.0.index()..self.0.index() + 1, vec![new_root.into()]);
2105 self.0 = parent
2106 .children_with_tokens()
2107 .nth(self.0.index())
2108 .unwrap()
2109 .clone()
2110 .into_node()
2111 .unwrap();
2112 } else {
2113 self.0 = new_root;
2114 }
2115 }
2116
2117 pub fn is_implied_by(&self, outer: &Entry) -> bool {
2142 if self == outer {
2144 return true;
2145 }
2146
2147 for inner_rel in self.relations() {
2149 for outer_rel in outer.relations() {
2150 if inner_rel.is_implied_by(&outer_rel) {
2151 return true;
2152 }
2153 }
2154 }
2155
2156 false
2157 }
2158}
2159
2160fn inject(builder: &mut GreenNodeBuilder, node: SyntaxNode) {
2161 builder.start_node(node.kind().into());
2162 for child in node.children_with_tokens() {
2163 match child {
2164 rowan::NodeOrToken::Node(child) => {
2165 inject(builder, child);
2166 }
2167 rowan::NodeOrToken::Token(token) => {
2168 builder.token(token.kind().into(), token.text());
2169 }
2170 }
2171 }
2172 builder.finish_node();
2173}
2174
2175impl From<Vec<Relation>> for Entry {
2176 fn from(relations: Vec<Relation>) -> Self {
2177 let mut builder = GreenNodeBuilder::new();
2178 builder.start_node(SyntaxKind::ENTRY.into());
2179 for (i, relation) in relations.into_iter().enumerate() {
2180 if i > 0 {
2181 builder.token(WHITESPACE.into(), " ");
2182 builder.token(COMMA.into(), "|");
2183 builder.token(WHITESPACE.into(), " ");
2184 }
2185 inject(&mut builder, relation.0);
2186 }
2187 builder.finish_node();
2188 Entry(SyntaxNode::new_root_mut(builder.finish()))
2189 }
2190}
2191
2192impl From<Relation> for Entry {
2193 fn from(relation: Relation) -> Self {
2194 Self::from(vec![relation])
2195 }
2196}
2197
2198fn tokenize_version(builder: &mut GreenNodeBuilder, version: &Version) {
2201 let version_str = version.to_string();
2202
2203 if let Some(colon_pos) = version_str.find(':') {
2205 builder.token(IDENT.into(), &version_str[..colon_pos]);
2207 builder.token(COLON.into(), ":");
2208 builder.token(IDENT.into(), &version_str[colon_pos + 1..]);
2210 } else {
2211 builder.token(IDENT.into(), version_str.as_str());
2213 }
2214}
2215
2216impl Relation {
2217 pub fn new(name: &str, version_constraint: Option<(VersionConstraint, Version)>) -> Self {
2231 let mut builder = GreenNodeBuilder::new();
2232 builder.start_node(SyntaxKind::RELATION.into());
2233 builder.token(IDENT.into(), name);
2234 if let Some((vc, version)) = version_constraint {
2235 builder.token(WHITESPACE.into(), " ");
2236 builder.start_node(SyntaxKind::VERSION.into());
2237 builder.token(L_PARENS.into(), "(");
2238 builder.start_node(SyntaxKind::CONSTRAINT.into());
2239 for c in vc.to_string().chars() {
2240 builder.token(
2241 match c {
2242 '>' => R_ANGLE.into(),
2243 '<' => L_ANGLE.into(),
2244 '=' => EQUAL.into(),
2245 _ => unreachable!(),
2246 },
2247 c.to_string().as_str(),
2248 );
2249 }
2250 builder.finish_node();
2251
2252 builder.token(WHITESPACE.into(), " ");
2253
2254 tokenize_version(&mut builder, &version);
2255
2256 builder.token(R_PARENS.into(), ")");
2257
2258 builder.finish_node();
2259 }
2260
2261 builder.finish_node();
2262 Relation(SyntaxNode::new_root_mut(builder.finish()))
2263 }
2264
2265 #[must_use]
2274 pub fn wrap_and_sort(&self) -> Self {
2275 let mut builder = GreenNodeBuilder::new();
2276 builder.start_node(SyntaxKind::RELATION.into());
2277 if let Some(name) = self.try_name() {
2278 builder.token(IDENT.into(), name.as_str());
2279 }
2280 if let Some(archqual) = self.archqual() {
2281 builder.token(COLON.into(), ":");
2282 builder.token(IDENT.into(), archqual.as_str());
2283 }
2284 if let Some((vc, version)) = self.version() {
2285 builder.token(WHITESPACE.into(), " ");
2286 builder.start_node(SyntaxKind::VERSION.into());
2287 builder.token(L_PARENS.into(), "(");
2288 builder.start_node(SyntaxKind::CONSTRAINT.into());
2289 builder.token(
2290 match vc {
2291 VersionConstraint::GreaterThanEqual => R_ANGLE.into(),
2292 VersionConstraint::LessThanEqual => L_ANGLE.into(),
2293 VersionConstraint::Equal => EQUAL.into(),
2294 VersionConstraint::GreaterThan => R_ANGLE.into(),
2295 VersionConstraint::LessThan => L_ANGLE.into(),
2296 },
2297 vc.to_string().as_str(),
2298 );
2299 builder.finish_node();
2300 builder.token(WHITESPACE.into(), " ");
2301 tokenize_version(&mut builder, &version);
2302 builder.token(R_PARENS.into(), ")");
2303 builder.finish_node();
2304 }
2305 if let Some(architectures) = self.architectures() {
2306 builder.token(WHITESPACE.into(), " ");
2307 builder.start_node(ARCHITECTURES.into());
2308 builder.token(L_BRACKET.into(), "[");
2309 for (i, arch) in architectures.enumerate() {
2310 if i > 0 {
2311 builder.token(WHITESPACE.into(), " ");
2312 }
2313 builder.token(IDENT.into(), arch.as_str());
2314 }
2315 builder.token(R_BRACKET.into(), "]");
2316 builder.finish_node();
2317 }
2318 for profiles in self.profiles() {
2319 builder.token(WHITESPACE.into(), " ");
2320 builder.start_node(PROFILES.into());
2321 builder.token(L_ANGLE.into(), "<");
2322 for (i, profile) in profiles.into_iter().enumerate() {
2323 if i > 0 {
2324 builder.token(WHITESPACE.into(), " ");
2325 }
2326 match profile {
2327 BuildProfile::Disabled(name) => {
2328 builder.token(NOT.into(), "!");
2329 builder.token(IDENT.into(), name.as_str());
2330 }
2331 BuildProfile::Enabled(name) => {
2332 builder.token(IDENT.into(), name.as_str());
2333 }
2334 }
2335 }
2336 builder.token(R_ANGLE.into(), ">");
2337 builder.finish_node();
2338 }
2339 builder.finish_node();
2340 Relation(SyntaxNode::new_root_mut(builder.finish()))
2341 }
2342
2343 pub fn simple(name: &str) -> Self {
2352 Self::new(name, None)
2353 }
2354
2355 pub fn drop_constraint(&mut self) -> bool {
2366 let version_token = self.0.children().find(|n| n.kind() == VERSION);
2367 if let Some(version_token) = version_token {
2368 while let Some(prev) = version_token.prev_sibling_or_token() {
2370 if prev.kind() == WHITESPACE || prev.kind() == NEWLINE {
2371 prev.detach();
2372 } else {
2373 break;
2374 }
2375 }
2376 version_token.detach();
2377 return true;
2378 }
2379
2380 false
2381 }
2382
2383 pub fn try_name(&self) -> Option<String> {
2395 self.name_token().map(|token| token.text().to_string())
2396 }
2397
2398 pub fn name_range(&self) -> Option<rowan::TextRange> {
2412 self.name_token().map(|token| token.text_range())
2413 }
2414
2415 fn name_token(&self) -> Option<crate::lossless::relations::SyntaxToken> {
2416 self.0.children_with_tokens().find_map(|it| match it {
2417 SyntaxElement::Token(token) if token.kind() == IDENT => Some(token),
2418 _ => None,
2419 })
2420 }
2421
2422 #[deprecated(
2435 since = "0.3.6",
2436 note = "Use try_name() instead, which returns Option<String>"
2437 )]
2438 pub fn name(&self) -> String {
2439 self.try_name().expect("Relation has no package name")
2440 }
2441
2442 pub fn archqual(&self) -> Option<String> {
2451 let archqual = self.0.children().find(|n| n.kind() == ARCHQUAL);
2452 let node = if let Some(archqual) = archqual {
2453 archqual.children_with_tokens().find_map(|it| match it {
2454 SyntaxElement::Token(token) if token.kind() == IDENT => Some(token),
2455 _ => None,
2456 })
2457 } else {
2458 None
2459 };
2460 node.map(|n| n.text().to_string())
2461 }
2462
2463 pub fn set_archqual(&mut self, archqual: &str) {
2473 let mut builder = GreenNodeBuilder::new();
2474 builder.start_node(ARCHQUAL.into());
2475 builder.token(COLON.into(), ":");
2476 builder.token(IDENT.into(), archqual);
2477 builder.finish_node();
2478
2479 let node_archqual = self.0.children().find(|n| n.kind() == ARCHQUAL);
2480 if let Some(node_archqual) = node_archqual {
2481 self.0.splice_children(
2482 node_archqual.index()..node_archqual.index() + 1,
2483 vec![SyntaxNode::new_root_mut(builder.finish()).into()],
2484 );
2485 } else {
2486 let name_node = self.0.children_with_tokens().find(|n| n.kind() == IDENT);
2487 let idx = if let Some(name_node) = name_node {
2488 name_node.index() + 1
2489 } else {
2490 0
2491 };
2492 self.0.splice_children(
2493 idx..idx,
2494 vec![SyntaxNode::new_root_mut(builder.finish()).into()],
2495 );
2496 }
2497 }
2498
2499 pub fn version(&self) -> Option<(VersionConstraint, Version)> {
2501 let vc = self.0.children().find(|n| n.kind() == VERSION);
2502 let vc = vc.as_ref()?;
2503 let constraint = vc.children().find(|n| n.kind() == CONSTRAINT);
2504
2505 let version_str: String = vc
2507 .children_with_tokens()
2508 .filter_map(|it| match it {
2509 SyntaxElement::Token(token) if token.kind() == IDENT || token.kind() == COLON => {
2510 Some(token.text().to_string())
2511 }
2512 _ => None,
2513 })
2514 .collect();
2515
2516 if let Some(constraint) = constraint {
2517 if !version_str.is_empty() {
2518 let vc: VersionConstraint = constraint.to_string().parse().unwrap();
2519 Some((vc, version_str.parse().unwrap()))
2520 } else {
2521 None
2522 }
2523 } else {
2524 None
2525 }
2526 }
2527
2528 pub fn set_version(&mut self, version_constraint: Option<(VersionConstraint, Version)>) {
2539 let current_version = self.0.children().find(|n| n.kind() == VERSION);
2540 if let Some((vc, version)) = version_constraint {
2541 let mut builder = GreenNodeBuilder::new();
2542 builder.start_node(VERSION.into());
2543 builder.token(L_PARENS.into(), "(");
2544 builder.start_node(CONSTRAINT.into());
2545 match vc {
2546 VersionConstraint::GreaterThanEqual => {
2547 builder.token(R_ANGLE.into(), ">");
2548 builder.token(EQUAL.into(), "=");
2549 }
2550 VersionConstraint::LessThanEqual => {
2551 builder.token(L_ANGLE.into(), "<");
2552 builder.token(EQUAL.into(), "=");
2553 }
2554 VersionConstraint::Equal => {
2555 builder.token(EQUAL.into(), "=");
2556 }
2557 VersionConstraint::GreaterThan => {
2558 builder.token(R_ANGLE.into(), ">");
2559 }
2560 VersionConstraint::LessThan => {
2561 builder.token(L_ANGLE.into(), "<");
2562 }
2563 }
2564 builder.finish_node(); builder.token(WHITESPACE.into(), " ");
2566 tokenize_version(&mut builder, &version);
2567 builder.token(R_PARENS.into(), ")");
2568 builder.finish_node(); if let Some(current_version) = current_version {
2571 self.0.splice_children(
2572 current_version.index()..current_version.index() + 1,
2573 vec![SyntaxNode::new_root_mut(builder.finish()).into()],
2574 );
2575 } else {
2576 let name_node = self.0.children_with_tokens().find(|n| n.kind() == IDENT);
2577 let idx = if let Some(name_node) = name_node {
2578 name_node.index() + 1
2579 } else {
2580 0
2581 };
2582 let new_children = vec![
2583 GreenToken::new(WHITESPACE.into(), " ").into(),
2584 builder.finish().into(),
2585 ];
2586 let new_root = SyntaxNode::new_root_mut(
2587 self.0.green().splice_children(idx..idx, new_children),
2588 );
2589 if let Some(parent) = self.0.parent() {
2590 parent
2591 .splice_children(self.0.index()..self.0.index() + 1, vec![new_root.into()]);
2592 self.0 = parent
2593 .children_with_tokens()
2594 .nth(self.0.index())
2595 .unwrap()
2596 .clone()
2597 .into_node()
2598 .unwrap();
2599 } else {
2600 self.0 = new_root;
2601 }
2602 }
2603 } else if let Some(current_version) = current_version {
2604 while let Some(prev) = current_version.prev_sibling_or_token() {
2606 if prev.kind() == WHITESPACE || prev.kind() == NEWLINE {
2607 prev.detach();
2608 } else {
2609 break;
2610 }
2611 }
2612 current_version.detach();
2613 }
2614 }
2615
2616 pub fn architectures(&self) -> Option<impl Iterator<Item = String> + '_> {
2625 let architectures = self.0.children().find(|n| n.kind() == ARCHITECTURES)?;
2626
2627 Some(architectures.children_with_tokens().filter_map(|node| {
2628 let token = node.as_token()?;
2629 if token.kind() == IDENT {
2630 Some(token.text().to_string())
2631 } else {
2632 None
2633 }
2634 }))
2635 }
2636
2637 pub fn profiles(&self) -> impl Iterator<Item = Vec<BuildProfile>> + '_ {
2647 let profiles = self.0.children().filter(|n| n.kind() == PROFILES);
2648
2649 profiles.map(|profile| {
2650 let mut ret = vec![];
2652 let mut current = vec![];
2653 for token in profile.children_with_tokens() {
2654 match token.kind() {
2655 WHITESPACE | NEWLINE => {
2656 if !current.is_empty() {
2657 ret.push(current.join("").parse::<BuildProfile>().unwrap());
2658 current = vec![];
2659 }
2660 }
2661 L_ANGLE | R_ANGLE => {}
2662 _ => {
2663 current.push(token.to_string());
2664 }
2665 }
2666 }
2667 if !current.is_empty() {
2668 ret.push(current.concat().parse().unwrap());
2669 }
2670 ret
2671 })
2672 }
2673
2674 pub fn profile_ranges(&self) -> impl Iterator<Item = rowan::TextRange> + '_ {
2691 self.0
2692 .children()
2693 .filter(|n| n.kind() == PROFILES)
2694 .flat_map(|profile| {
2695 profile
2696 .children_with_tokens()
2697 .filter_map(|t| t.into_token())
2698 .filter(|t| t.kind() == IDENT)
2699 .map(|t| t.text_range())
2700 .collect::<Vec<_>>()
2701 })
2702 }
2703
2704 pub fn remove(&mut self) {
2715 let is_first = !self
2716 .0
2717 .siblings(Direction::Prev)
2718 .skip(1)
2719 .any(|n| n.kind() == RELATION);
2720 if !is_first {
2721 while let Some(n) = self.0.prev_sibling_or_token() {
2724 if matches!(n.kind(), WHITESPACE | NEWLINE | COMMENT) {
2725 n.detach();
2726 } else if n.kind() == PIPE {
2727 n.detach();
2728 break;
2729 } else {
2730 break;
2731 }
2732 }
2733 while let Some(n) = self.0.prev_sibling_or_token() {
2734 if matches!(n.kind(), WHITESPACE | NEWLINE | COMMENT) {
2735 n.detach();
2736 } else {
2737 break;
2738 }
2739 }
2740 } else {
2741 while let Some(n) = self.0.next_sibling_or_token() {
2744 if matches!(n.kind(), WHITESPACE | NEWLINE | COMMENT) {
2745 n.detach();
2746 } else if n.kind() == PIPE {
2747 n.detach();
2748 break;
2749 } else {
2750 break;
2751 }
2752 }
2753
2754 while let Some(n) = self.0.next_sibling_or_token() {
2755 if matches!(n.kind(), WHITESPACE | NEWLINE | COMMENT) {
2756 n.detach();
2757 } else {
2758 break;
2759 }
2760 }
2761 }
2762 if let Some(mut parent) = self.0.parent().and_then(Entry::cast) {
2764 if parent.is_empty() {
2765 parent.remove();
2766 } else {
2767 self.0.detach();
2768 }
2769 } else {
2770 self.0.detach();
2771 }
2772 }
2773
2774 pub fn set_architectures<'a>(&mut self, architectures: impl Iterator<Item = &'a str>) {
2784 let mut builder = GreenNodeBuilder::new();
2785 builder.start_node(ARCHITECTURES.into());
2786 builder.token(L_BRACKET.into(), "[");
2787 for (i, arch) in architectures.enumerate() {
2788 if i > 0 {
2789 builder.token(WHITESPACE.into(), " ");
2790 }
2791 builder.token(IDENT.into(), arch);
2792 }
2793 builder.token(R_BRACKET.into(), "]");
2794 builder.finish_node();
2795
2796 let node_architectures = self.0.children().find(|n| n.kind() == ARCHITECTURES);
2797 if let Some(node_architectures) = node_architectures {
2798 let new_root = SyntaxNode::new_root_mut(builder.finish());
2799 self.0.splice_children(
2800 node_architectures.index()..node_architectures.index() + 1,
2801 vec![new_root.into()],
2802 );
2803 } else {
2804 let profiles = self.0.children().find(|n| n.kind() == PROFILES);
2805 let idx = if let Some(profiles) = profiles {
2806 profiles.index()
2807 } else {
2808 self.0.children_with_tokens().count()
2809 };
2810 let new_root = SyntaxNode::new_root(self.0.green().splice_children(
2811 idx..idx,
2812 vec![
2813 GreenToken::new(WHITESPACE.into(), " ").into(),
2814 builder.finish().into(),
2815 ],
2816 ));
2817 if let Some(parent) = self.0.parent() {
2818 parent.splice_children(self.0.index()..self.0.index() + 1, vec![new_root.into()]);
2819 self.0 = parent
2820 .children_with_tokens()
2821 .nth(self.0.index())
2822 .unwrap()
2823 .clone()
2824 .into_node()
2825 .unwrap();
2826 } else {
2827 self.0 = new_root;
2828 }
2829 }
2830 }
2831
2832 pub fn add_profile(&mut self, profile: &[BuildProfile]) {
2843 let mut builder = GreenNodeBuilder::new();
2844 builder.start_node(PROFILES.into());
2845 builder.token(L_ANGLE.into(), "<");
2846 for (i, profile) in profile.iter().enumerate() {
2847 if i > 0 {
2848 builder.token(WHITESPACE.into(), " ");
2849 }
2850 match profile {
2851 BuildProfile::Disabled(name) => {
2852 builder.token(NOT.into(), "!");
2853 builder.token(IDENT.into(), name.as_str());
2854 }
2855 BuildProfile::Enabled(name) => {
2856 builder.token(IDENT.into(), name.as_str());
2857 }
2858 }
2859 }
2860 builder.token(R_ANGLE.into(), ">");
2861 builder.finish_node();
2862
2863 let node_profiles = self.0.children().find(|n| n.kind() == PROFILES);
2864 if let Some(node_profiles) = node_profiles {
2865 let new_root = SyntaxNode::new_root_mut(builder.finish());
2866 self.0.splice_children(
2867 node_profiles.index()..node_profiles.index() + 1,
2868 vec![new_root.into()],
2869 );
2870 } else {
2871 let idx = self.0.children_with_tokens().count();
2872 let new_root = SyntaxNode::new_root(self.0.green().splice_children(
2873 idx..idx,
2874 vec![
2875 GreenToken::new(WHITESPACE.into(), " ").into(),
2876 builder.finish().into(),
2877 ],
2878 ));
2879 if let Some(parent) = self.0.parent() {
2880 parent.splice_children(self.0.index()..self.0.index() + 1, vec![new_root.into()]);
2881 self.0 = parent
2882 .children_with_tokens()
2883 .nth(self.0.index())
2884 .unwrap()
2885 .clone()
2886 .into_node()
2887 .unwrap();
2888 } else {
2889 self.0 = new_root;
2890 }
2891 }
2892 }
2893
2894 pub fn build(name: &str) -> RelationBuilder {
2896 RelationBuilder::new(name)
2897 }
2898
2899 pub fn is_implied_by(&self, outer: &Relation) -> bool {
2926 if self.try_name() != outer.try_name() {
2927 return false;
2928 }
2929
2930 let inner_version = self.version();
2931 let outer_version = outer.version();
2932
2933 if inner_version.is_none() {
2935 return true;
2936 }
2937
2938 if inner_version == outer_version {
2940 return true;
2941 }
2942
2943 if outer_version.is_none() {
2945 return false;
2946 }
2947
2948 let (inner_constraint, inner_ver) = inner_version.unwrap();
2949 let (outer_constraint, outer_ver) = outer_version.unwrap();
2950
2951 use VersionConstraint::*;
2952 match inner_constraint {
2953 GreaterThanEqual => match outer_constraint {
2954 GreaterThan => outer_ver > inner_ver,
2955 GreaterThanEqual | Equal => outer_ver >= inner_ver,
2956 LessThan | LessThanEqual => false,
2957 },
2958 Equal => match outer_constraint {
2959 Equal => outer_ver == inner_ver,
2960 _ => false,
2961 },
2962 LessThan => match outer_constraint {
2963 LessThan => outer_ver <= inner_ver,
2964 LessThanEqual | Equal => outer_ver < inner_ver,
2965 GreaterThan | GreaterThanEqual => false,
2966 },
2967 LessThanEqual => match outer_constraint {
2968 LessThanEqual | Equal | LessThan => outer_ver <= inner_ver,
2969 GreaterThan | GreaterThanEqual => false,
2970 },
2971 GreaterThan => match outer_constraint {
2972 GreaterThan => outer_ver >= inner_ver,
2973 Equal | GreaterThanEqual => outer_ver > inner_ver,
2974 LessThan | LessThanEqual => false,
2975 },
2976 }
2977 }
2978}
2979
2980pub struct RelationBuilder {
2994 name: String,
2995 version_constraint: Option<(VersionConstraint, Version)>,
2996 archqual: Option<String>,
2997 architectures: Option<Vec<String>>,
2998 profiles: Vec<Vec<BuildProfile>>,
2999}
3000
3001impl RelationBuilder {
3002 fn new(name: &str) -> Self {
3004 Self {
3005 name: name.to_string(),
3006 version_constraint: None,
3007 archqual: None,
3008 architectures: None,
3009 profiles: vec![],
3010 }
3011 }
3012
3013 pub fn version_constraint(mut self, vc: VersionConstraint, version: Version) -> Self {
3015 self.version_constraint = Some((vc, version));
3016 self
3017 }
3018
3019 pub fn archqual(mut self, archqual: &str) -> Self {
3021 self.archqual = Some(archqual.to_string());
3022 self
3023 }
3024
3025 pub fn architectures(mut self, architectures: Vec<String>) -> Self {
3027 self.architectures = Some(architectures);
3028 self
3029 }
3030
3031 pub fn profiles(mut self, profiles: Vec<Vec<BuildProfile>>) -> Self {
3033 self.profiles = profiles;
3034 self
3035 }
3036
3037 pub fn add_profile(mut self, profile: Vec<BuildProfile>) -> Self {
3039 self.profiles.push(profile);
3040 self
3041 }
3042
3043 pub fn build(self) -> Relation {
3045 let mut relation = Relation::new(&self.name, self.version_constraint);
3046 if let Some(archqual) = &self.archqual {
3047 relation.set_archqual(archqual);
3048 }
3049 if let Some(architectures) = &self.architectures {
3050 relation.set_architectures(architectures.iter().map(|s| s.as_str()));
3051 }
3052 for profile in &self.profiles {
3053 relation.add_profile(profile);
3054 }
3055 relation
3056 }
3057}
3058
3059impl PartialOrd for Relation {
3060 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
3061 Some(self.cmp(other))
3062 }
3063}
3064
3065impl Eq for Relation {}
3066
3067impl Ord for Relation {
3068 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
3069 let name_cmp = self.try_name().cmp(&other.try_name());
3071 if name_cmp != std::cmp::Ordering::Equal {
3072 return name_cmp;
3073 }
3074
3075 let self_version = self.version();
3076 let other_version = other.version();
3077
3078 match (self_version, other_version) {
3079 (Some((self_vc, self_version)), Some((other_vc, other_version))) => {
3080 let vc_cmp = self_vc.cmp(&other_vc);
3081 if vc_cmp != std::cmp::Ordering::Equal {
3082 return vc_cmp;
3083 }
3084
3085 self_version.cmp(&other_version)
3086 }
3087 (Some(_), None) => std::cmp::Ordering::Greater,
3088 (None, Some(_)) => std::cmp::Ordering::Less,
3089 (None, None) => std::cmp::Ordering::Equal,
3090 }
3091 }
3092}
3093
3094impl std::str::FromStr for Relations {
3095 type Err = String;
3096
3097 fn from_str(s: &str) -> Result<Self, Self::Err> {
3098 let parse = parse(s, false);
3099 if parse.errors.is_empty() {
3100 Ok(parse.root_mut())
3101 } else {
3102 Err(parse.errors.join("\n"))
3103 }
3104 }
3105}
3106
3107impl std::str::FromStr for Entry {
3108 type Err = String;
3109
3110 fn from_str(s: &str) -> Result<Self, Self::Err> {
3111 let root: Relations = s.parse()?;
3112
3113 let mut entries = root.entries();
3114 let entry = if let Some(entry) = entries.next() {
3115 entry
3116 } else {
3117 return Err("No entry found".to_string());
3118 };
3119
3120 if entries.next().is_some() {
3121 return Err("Multiple entries found".to_string());
3122 }
3123
3124 Ok(entry)
3125 }
3126}
3127
3128impl std::str::FromStr for Relation {
3129 type Err = String;
3130
3131 fn from_str(s: &str) -> Result<Self, Self::Err> {
3132 let entry: Entry = s.parse()?;
3133
3134 let mut relations = entry.relations();
3135 let relation = if let Some(relation) = relations.next() {
3136 relation
3137 } else {
3138 return Err("No relation found".to_string());
3139 };
3140
3141 if relations.next().is_some() {
3142 return Err("Multiple relations found".to_string());
3143 }
3144
3145 Ok(relation)
3146 }
3147}
3148
3149impl From<crate::lossy::Relation> for Relation {
3150 fn from(relation: crate::lossy::Relation) -> Self {
3151 let mut builder = Relation::build(&relation.name);
3152
3153 if let Some((vc, version)) = relation.version {
3154 builder = builder.version_constraint(vc, version);
3155 }
3156
3157 if let Some(archqual) = relation.archqual {
3158 builder = builder.archqual(&archqual);
3159 }
3160
3161 if let Some(architectures) = relation.architectures {
3162 builder = builder.architectures(architectures);
3163 }
3164
3165 builder = builder.profiles(relation.profiles);
3166
3167 builder.build()
3168 }
3169}
3170
3171impl From<Relation> for crate::lossy::Relation {
3172 fn from(relation: Relation) -> Self {
3173 crate::lossy::Relation {
3174 name: relation.try_name().unwrap_or_default(),
3175 version: relation.version(),
3176 archqual: relation.archqual(),
3177 architectures: relation.architectures().map(|a| a.collect()),
3178 profiles: relation.profiles().collect(),
3179 }
3180 }
3181}
3182
3183impl From<Entry> for Vec<crate::lossy::Relation> {
3184 fn from(entry: Entry) -> Self {
3185 entry.relations().map(|r| r.into()).collect()
3186 }
3187}
3188
3189impl From<Vec<crate::lossy::Relation>> for Entry {
3190 fn from(relations: Vec<crate::lossy::Relation>) -> Self {
3191 let relations: Vec<Relation> = relations.into_iter().map(|r| r.into()).collect();
3192 Entry::from(relations)
3193 }
3194}
3195
3196#[cfg(test)]
3197mod tests {
3198 use super::*;
3199
3200 #[test]
3201 fn test_parse() {
3202 let input = "python3-dulwich";
3203 let parsed: Relations = input.parse().unwrap();
3204 assert_eq!(parsed.to_string(), input);
3205 assert_eq!(parsed.entries().count(), 1);
3206 let entry = parsed.entries().next().unwrap();
3207 assert_eq!(entry.to_string(), "python3-dulwich");
3208 assert_eq!(entry.relations().count(), 1);
3209 let relation = entry.relations().next().unwrap();
3210 assert_eq!(relation.to_string(), "python3-dulwich");
3211 assert_eq!(relation.version(), None);
3212
3213 let input = "python3-dulwich (>= 0.20.21)";
3214 let parsed: Relations = input.parse().unwrap();
3215 assert_eq!(parsed.to_string(), input);
3216 assert_eq!(parsed.entries().count(), 1);
3217 let entry = parsed.entries().next().unwrap();
3218 assert_eq!(entry.to_string(), "python3-dulwich (>= 0.20.21)");
3219 assert_eq!(entry.relations().count(), 1);
3220 let relation = entry.relations().next().unwrap();
3221 assert_eq!(relation.to_string(), "python3-dulwich (>= 0.20.21)");
3222 assert_eq!(
3223 relation.version(),
3224 Some((
3225 VersionConstraint::GreaterThanEqual,
3226 "0.20.21".parse().unwrap()
3227 ))
3228 );
3229 }
3230
3231 #[test]
3232 fn test_multiple() {
3233 let input = "python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)";
3234 let parsed: Relations = input.parse().unwrap();
3235 assert_eq!(parsed.to_string(), input);
3236 assert_eq!(parsed.entries().count(), 2);
3237 let entry = parsed.entries().next().unwrap();
3238 assert_eq!(entry.to_string(), "python3-dulwich (>= 0.20.21)");
3239 assert_eq!(entry.relations().count(), 1);
3240 let relation = entry.relations().next().unwrap();
3241 assert_eq!(relation.to_string(), "python3-dulwich (>= 0.20.21)");
3242 assert_eq!(
3243 relation.version(),
3244 Some((
3245 VersionConstraint::GreaterThanEqual,
3246 "0.20.21".parse().unwrap()
3247 ))
3248 );
3249 let entry = parsed.entries().nth(1).unwrap();
3250 assert_eq!(entry.to_string(), "python3-dulwich (<< 0.21)");
3251 assert_eq!(entry.relations().count(), 1);
3252 let relation = entry.relations().next().unwrap();
3253 assert_eq!(relation.to_string(), "python3-dulwich (<< 0.21)");
3254 assert_eq!(
3255 relation.version(),
3256 Some((VersionConstraint::LessThan, "0.21".parse().unwrap()))
3257 );
3258 }
3259
3260 #[test]
3261 fn test_architectures() {
3262 let input = "python3-dulwich [amd64 arm64 armhf i386 mips mips64el mipsel ppc64el s390x]";
3263 let parsed: Relations = input.parse().unwrap();
3264 assert_eq!(parsed.to_string(), input);
3265 assert_eq!(parsed.entries().count(), 1);
3266 let entry = parsed.entries().next().unwrap();
3267 assert_eq!(
3268 entry.to_string(),
3269 "python3-dulwich [amd64 arm64 armhf i386 mips mips64el mipsel ppc64el s390x]"
3270 );
3271 assert_eq!(entry.relations().count(), 1);
3272 let relation = entry.relations().next().unwrap();
3273 assert_eq!(
3274 relation.to_string(),
3275 "python3-dulwich [amd64 arm64 armhf i386 mips mips64el mipsel ppc64el s390x]"
3276 );
3277 assert_eq!(relation.version(), None);
3278 assert_eq!(
3279 relation.architectures().unwrap().collect::<Vec<_>>(),
3280 vec![
3281 "amd64", "arm64", "armhf", "i386", "mips", "mips64el", "mipsel", "ppc64el", "s390x"
3282 ]
3283 .into_iter()
3284 .map(|s| s.to_string())
3285 .collect::<Vec<_>>()
3286 );
3287 }
3288
3289 #[test]
3290 fn test_profiles() {
3291 let input = "foo (>= 1.0) [i386 arm] <!nocheck> <!cross>, bar";
3292 let parsed: Relations = input.parse().unwrap();
3293 assert_eq!(parsed.to_string(), input);
3294 assert_eq!(parsed.entries().count(), 2);
3295 let entry = parsed.entries().next().unwrap();
3296 assert_eq!(
3297 entry.to_string(),
3298 "foo (>= 1.0) [i386 arm] <!nocheck> <!cross>"
3299 );
3300 assert_eq!(entry.relations().count(), 1);
3301 let relation = entry.relations().next().unwrap();
3302 assert_eq!(
3303 relation.to_string(),
3304 "foo (>= 1.0) [i386 arm] <!nocheck> <!cross>"
3305 );
3306 assert_eq!(
3307 relation.version(),
3308 Some((VersionConstraint::GreaterThanEqual, "1.0".parse().unwrap()))
3309 );
3310 assert_eq!(
3311 relation.architectures().unwrap().collect::<Vec<_>>(),
3312 vec!["i386", "arm"]
3313 .into_iter()
3314 .map(|s| s.to_string())
3315 .collect::<Vec<_>>()
3316 );
3317 assert_eq!(
3318 relation.profiles().collect::<Vec<_>>(),
3319 vec![
3320 vec![BuildProfile::Disabled("nocheck".to_string())],
3321 vec![BuildProfile::Disabled("cross".to_string())]
3322 ]
3323 );
3324 }
3325
3326 #[test]
3327 fn test_substvar() {
3328 let input = "${shlibs:Depends}";
3329
3330 let (parsed, errors) = Relations::parse_relaxed(input, true);
3331 assert_eq!(errors, Vec::<String>::new());
3332 assert_eq!(parsed.to_string(), input);
3333 assert_eq!(parsed.entries().count(), 0);
3334
3335 assert_eq!(
3336 parsed.substvars().collect::<Vec<_>>(),
3337 vec!["${shlibs:Depends}"]
3338 );
3339 }
3340
3341 #[test]
3342 fn test_substvar_nodes() {
3343 let input = "foo, ${shlibs:Depends}, bar, ${misc:Depends}";
3344
3345 let (parsed, errors) = Relations::parse_relaxed(input, true);
3346 assert_eq!(errors, Vec::<String>::new());
3347
3348 let substvar_nodes: Vec<Substvar> = parsed.substvar_nodes().collect();
3349 assert_eq!(substvar_nodes.len(), 2);
3350 assert_eq!(substvar_nodes[0].to_string(), "${shlibs:Depends}");
3351 assert_eq!(substvar_nodes[1].to_string(), "${misc:Depends}");
3352 }
3353
3354 #[test]
3355 fn test_substvar_nodes_empty() {
3356 let parsed: Relations = "foo, bar".parse().unwrap();
3357
3358 let substvar_nodes: Vec<Substvar> = parsed.substvar_nodes().collect();
3359 assert_eq!(substvar_nodes.len(), 0);
3360 }
3361
3362 #[test]
3363 fn test_new() {
3364 let r = Relation::new(
3365 "samba",
3366 Some((VersionConstraint::GreaterThanEqual, "2.0".parse().unwrap())),
3367 );
3368
3369 assert_eq!(r.to_string(), "samba (>= 2.0)");
3370 }
3371
3372 #[test]
3373 fn test_drop_constraint() {
3374 let mut r = Relation::new(
3375 "samba",
3376 Some((VersionConstraint::GreaterThanEqual, "2.0".parse().unwrap())),
3377 );
3378
3379 r.drop_constraint();
3380
3381 assert_eq!(r.to_string(), "samba");
3382 }
3383
3384 #[test]
3385 fn test_simple() {
3386 let r = Relation::simple("samba");
3387
3388 assert_eq!(r.to_string(), "samba");
3389 }
3390
3391 #[test]
3392 fn test_remove_first_entry() {
3393 let mut rels: Relations = r#"python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)"#
3394 .parse()
3395 .unwrap();
3396 let removed = rels.remove_entry(0);
3397 assert_eq!(removed.to_string(), "python3-dulwich (>= 0.20.21)");
3398 assert_eq!(rels.to_string(), "python3-dulwich (<< 0.21)");
3399 }
3400
3401 #[test]
3402 fn test_remove_last_entry() {
3403 let mut rels: Relations = r#"python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)"#
3404 .parse()
3405 .unwrap();
3406 rels.remove_entry(1);
3407 assert_eq!(rels.to_string(), "python3-dulwich (>= 0.20.21)");
3408 }
3409
3410 #[test]
3411 fn test_remove_middle() {
3412 let mut rels: Relations =
3413 r#"python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21), python3-dulwich (<< 0.22)"#
3414 .parse()
3415 .unwrap();
3416 rels.remove_entry(1);
3417 assert_eq!(
3418 rels.to_string(),
3419 "python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.22)"
3420 );
3421 }
3422
3423 #[test]
3424 fn test_remove_added() {
3425 let mut rels: Relations = r#"python3-dulwich (>= 0.20.21)"#.parse().unwrap();
3426 let entry = Entry::from(vec![Relation::simple("python3-dulwich")]);
3427 rels.push(entry);
3428 rels.remove_entry(1);
3429 assert_eq!(rels.to_string(), "python3-dulwich (>= 0.20.21)");
3430 }
3431
3432 #[test]
3433 fn test_push() {
3434 let mut rels: Relations = r#"python3-dulwich (>= 0.20.21)"#.parse().unwrap();
3435 let entry = Entry::from(vec![Relation::simple("python3-dulwich")]);
3436 rels.push(entry);
3437 assert_eq!(
3438 rels.to_string(),
3439 "python3-dulwich (>= 0.20.21), python3-dulwich"
3440 );
3441 }
3442
3443 #[test]
3444 fn test_insert_with_custom_separator() {
3445 let mut rels: Relations = "python3".parse().unwrap();
3446 let entry = Entry::from(vec![Relation::simple("debhelper")]);
3447 rels.insert_with_separator(1, entry, Some("\n "));
3448 assert_eq!(rels.to_string(), "python3,\n debhelper");
3449 }
3450
3451 #[test]
3452 fn test_insert_with_wrap_and_sort_separator() {
3453 let mut rels: Relations = "python3".parse().unwrap();
3454 let entry = Entry::from(vec![Relation::simple("rustc")]);
3455 rels.insert_with_separator(1, entry, Some("\n "));
3457 assert_eq!(rels.to_string(), "python3,\n rustc");
3458 }
3459
3460 #[test]
3461 fn test_push_from_empty() {
3462 let mut rels: Relations = "".parse().unwrap();
3463 let entry = Entry::from(vec![Relation::simple("python3-dulwich")]);
3464 rels.push(entry);
3465 assert_eq!(rels.to_string(), "python3-dulwich");
3466 }
3467
3468 #[test]
3469 fn test_insert() {
3470 let mut rels: Relations = r#"python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)"#
3471 .parse()
3472 .unwrap();
3473 let entry = Entry::from(vec![Relation::simple("python3-dulwich")]);
3474 rels.insert(1, entry);
3475 assert_eq!(
3476 rels.to_string(),
3477 "python3-dulwich (>= 0.20.21), python3-dulwich, python3-dulwich (<< 0.21)"
3478 );
3479 }
3480
3481 #[test]
3482 fn test_insert_at_start() {
3483 let mut rels: Relations = r#"python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)"#
3484 .parse()
3485 .unwrap();
3486 let entry = Entry::from(vec![Relation::simple("python3-dulwich")]);
3487 rels.insert(0, entry);
3488 assert_eq!(
3489 rels.to_string(),
3490 "python3-dulwich, python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)"
3491 );
3492 }
3493
3494 #[test]
3495 fn test_insert_after_error() {
3496 let (mut rels, errors) = Relations::parse_relaxed("@foo@, debhelper (>= 1.0)", false);
3497 assert_eq!(
3498 errors,
3499 vec![
3500 "expected $ or identifier but got ERROR",
3501 "expected comma or end of file but got Some(IDENT)",
3502 "expected $ or identifier but got ERROR"
3503 ]
3504 );
3505 let entry = Entry::from(vec![Relation::simple("bar")]);
3506 rels.push(entry);
3507 assert_eq!(rels.to_string(), "@foo@, debhelper (>= 1.0), bar");
3508 }
3509
3510 #[test]
3511 fn test_insert_before_error() {
3512 let (mut rels, errors) = Relations::parse_relaxed("debhelper (>= 1.0), @foo@, bla", false);
3513 assert_eq!(
3514 errors,
3515 vec![
3516 "expected $ or identifier but got ERROR",
3517 "expected comma or end of file but got Some(IDENT)",
3518 "expected $ or identifier but got ERROR"
3519 ]
3520 );
3521 let entry = Entry::from(vec![Relation::simple("bar")]);
3522 rels.insert(0, entry);
3523 assert_eq!(rels.to_string(), "bar, debhelper (>= 1.0), @foo@, bla");
3524 }
3525
3526 #[test]
3527 fn test_replace() {
3528 let mut rels: Relations = r#"python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)"#
3529 .parse()
3530 .unwrap();
3531 let entry = Entry::from(vec![Relation::simple("python3-dulwich")]);
3532 rels.replace(1, entry);
3533 assert_eq!(
3534 rels.to_string(),
3535 "python3-dulwich (>= 0.20.21), python3-dulwich"
3536 );
3537 }
3538
3539 #[test]
3540 fn test_relation_from_entries() {
3541 let entries = vec![
3542 Entry::from(vec![Relation::simple("python3-dulwich")]),
3543 Entry::from(vec![Relation::simple("python3-breezy")]),
3544 ];
3545 let rels: Relations = entries.into();
3546 assert_eq!(rels.entries().count(), 2);
3547 assert_eq!(rels.to_string(), "python3-dulwich, python3-breezy");
3548 }
3549
3550 #[test]
3551 fn test_entry_from_relations() {
3552 let relations = vec![
3553 Relation::simple("python3-dulwich"),
3554 Relation::simple("python3-breezy"),
3555 ];
3556 let entry: Entry = relations.into();
3557 assert_eq!(entry.relations().count(), 2);
3558 assert_eq!(entry.to_string(), "python3-dulwich | python3-breezy");
3559 }
3560
3561 #[test]
3562 fn test_parse_entry() {
3563 let parsed: Entry = "python3-dulwich (>= 0.20.21) | bar".parse().unwrap();
3564 assert_eq!(parsed.to_string(), "python3-dulwich (>= 0.20.21) | bar");
3565 assert_eq!(parsed.relations().count(), 2);
3566
3567 assert_eq!(
3568 "foo, bar".parse::<Entry>().unwrap_err(),
3569 "Multiple entries found"
3570 );
3571 assert_eq!("".parse::<Entry>().unwrap_err(), "No entry found");
3572 }
3573
3574 #[test]
3575 fn test_parse_relation() {
3576 let parsed: Relation = "python3-dulwich (>= 0.20.21)".parse().unwrap();
3577 assert_eq!(parsed.to_string(), "python3-dulwich (>= 0.20.21)");
3578 assert_eq!(
3579 parsed.version(),
3580 Some((
3581 VersionConstraint::GreaterThanEqual,
3582 "0.20.21".parse().unwrap()
3583 ))
3584 );
3585 assert_eq!(
3586 "foo | bar".parse::<Relation>().unwrap_err(),
3587 "Multiple relations found"
3588 );
3589 assert_eq!("".parse::<Relation>().unwrap_err(), "No entry found");
3590 }
3591
3592 #[test]
3593 fn test_special() {
3594 let parsed: Relation = "librust-breezyshim+dirty-tracker-dev:amd64 (>= 0.1.138-~~)"
3595 .parse()
3596 .unwrap();
3597 assert_eq!(
3598 parsed.to_string(),
3599 "librust-breezyshim+dirty-tracker-dev:amd64 (>= 0.1.138-~~)"
3600 );
3601 assert_eq!(
3602 parsed.version(),
3603 Some((
3604 VersionConstraint::GreaterThanEqual,
3605 "0.1.138-~~".parse().unwrap()
3606 ))
3607 );
3608 assert_eq!(parsed.archqual(), Some("amd64".to_string()));
3609 assert_eq!(parsed.name(), "librust-breezyshim+dirty-tracker-dev");
3610 }
3611
3612 #[test]
3613 fn test_relations_satisfied_by() {
3614 let rels: Relations = "python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)"
3615 .parse()
3616 .unwrap();
3617 let satisfied = |name: &str| -> Option<debversion::Version> {
3618 match name {
3619 "python3-dulwich" => Some("0.20.21".parse().unwrap()),
3620 _ => None,
3621 }
3622 };
3623 assert!(rels.satisfied_by(satisfied));
3624
3625 let satisfied = |name: &str| match name {
3626 "python3-dulwich" => Some("0.21".parse().unwrap()),
3627 _ => None,
3628 };
3629 assert!(!rels.satisfied_by(satisfied));
3630
3631 let satisfied = |name: &str| match name {
3632 "python3-dulwich" => Some("0.20.20".parse().unwrap()),
3633 _ => None,
3634 };
3635 assert!(!rels.satisfied_by(satisfied));
3636 }
3637
3638 #[test]
3639 fn test_entry_satisfied_by() {
3640 let entry: Entry = "python3-dulwich (>= 0.20.21) | python3-dulwich (<< 0.18)"
3641 .parse()
3642 .unwrap();
3643 let satisfied = |name: &str| -> Option<debversion::Version> {
3644 match name {
3645 "python3-dulwich" => Some("0.20.21".parse().unwrap()),
3646 _ => None,
3647 }
3648 };
3649 assert!(entry.satisfied_by(satisfied));
3650 let satisfied = |name: &str| -> Option<debversion::Version> {
3651 match name {
3652 "python3-dulwich" => Some("0.18".parse().unwrap()),
3653 _ => None,
3654 }
3655 };
3656 assert!(!entry.satisfied_by(satisfied));
3657 }
3658
3659 #[test]
3660 fn test_wrap_and_sort_relation() {
3661 let relation: Relation = " python3-dulwich (>= 11) [ amd64 ] < lala>"
3662 .parse()
3663 .unwrap();
3664
3665 let wrapped = relation.wrap_and_sort();
3666
3667 assert_eq!(
3668 wrapped.to_string(),
3669 "python3-dulwich (>= 11) [amd64] <lala>"
3670 );
3671 }
3672
3673 #[test]
3674 fn test_wrap_and_sort_relations() {
3675 let entry: Relations =
3676 "python3-dulwich (>= 0.20.21) | bar, \n\n\n\npython3-dulwich (<< 0.21)"
3677 .parse()
3678 .unwrap();
3679
3680 let wrapped = entry.wrap_and_sort();
3681
3682 assert_eq!(
3683 wrapped.to_string(),
3684 "bar | python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)"
3685 );
3686 }
3687
3688 #[cfg(feature = "serde")]
3689 #[test]
3690 fn test_serialize_relations() {
3691 let relations: Relations = "python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)"
3692 .parse()
3693 .unwrap();
3694 let serialized = serde_json::to_string(&relations).unwrap();
3695 assert_eq!(
3696 serialized,
3697 r#""python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)""#
3698 );
3699 }
3700
3701 #[cfg(feature = "serde")]
3702 #[test]
3703 fn test_deserialize_relations() {
3704 let relations: Relations = "python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)"
3705 .parse()
3706 .unwrap();
3707 let serialized = serde_json::to_string(&relations).unwrap();
3708 let deserialized: Relations = serde_json::from_str(&serialized).unwrap();
3709 assert_eq!(deserialized.to_string(), relations.to_string());
3710 }
3711
3712 #[cfg(feature = "serde")]
3713 #[test]
3714 fn test_serialize_relation() {
3715 let relation: Relation = "python3-dulwich (>= 0.20.21)".parse().unwrap();
3716 let serialized = serde_json::to_string(&relation).unwrap();
3717 assert_eq!(serialized, r#""python3-dulwich (>= 0.20.21)""#);
3718 }
3719
3720 #[cfg(feature = "serde")]
3721 #[test]
3722 fn test_deserialize_relation() {
3723 let relation: Relation = "python3-dulwich (>= 0.20.21)".parse().unwrap();
3724 let serialized = serde_json::to_string(&relation).unwrap();
3725 let deserialized: Relation = serde_json::from_str(&serialized).unwrap();
3726 assert_eq!(deserialized.to_string(), relation.to_string());
3727 }
3728
3729 #[cfg(feature = "serde")]
3730 #[test]
3731 fn test_serialize_entry() {
3732 let entry: Entry = "python3-dulwich (>= 0.20.21) | python3-dulwich (<< 0.18)"
3733 .parse()
3734 .unwrap();
3735 let serialized = serde_json::to_string(&entry).unwrap();
3736 assert_eq!(
3737 serialized,
3738 r#""python3-dulwich (>= 0.20.21) | python3-dulwich (<< 0.18)""#
3739 );
3740 }
3741
3742 #[cfg(feature = "serde")]
3743 #[test]
3744 fn test_deserialize_entry() {
3745 let entry: Entry = "python3-dulwich (>= 0.20.21) | python3-dulwich (<< 0.18)"
3746 .parse()
3747 .unwrap();
3748 let serialized = serde_json::to_string(&entry).unwrap();
3749 let deserialized: Entry = serde_json::from_str(&serialized).unwrap();
3750 assert_eq!(deserialized.to_string(), entry.to_string());
3751 }
3752
3753 #[test]
3754 fn test_remove_first_relation() {
3755 let entry: Entry = "python3-dulwich (>= 0.20.21) | python3-dulwich (<< 0.18)"
3756 .parse()
3757 .unwrap();
3758 let mut rel = entry.relations().next().unwrap();
3759 rel.remove();
3760 assert_eq!(entry.to_string(), "python3-dulwich (<< 0.18)");
3761 }
3762
3763 #[test]
3764 fn test_remove_last_relation() {
3765 let entry: Entry = "python3-dulwich (>= 0.20.21) | python3-dulwich (<< 0.18)"
3766 .parse()
3767 .unwrap();
3768 let mut rel = entry.relations().nth(1).unwrap();
3769 rel.remove();
3770 assert_eq!(entry.to_string(), "python3-dulwich (>= 0.20.21)");
3771 }
3772
3773 #[test]
3774 fn test_remove_only_relation() {
3775 let entry: Entry = "python3-dulwich (>= 0.20.21)".parse().unwrap();
3776 let mut rel = entry.relations().next().unwrap();
3777 rel.remove();
3778 assert_eq!(entry.to_string(), "");
3779 }
3780
3781 #[test]
3782 fn test_relations_is_empty() {
3783 let entry: Relations = "python3-dulwich (>= 0.20.21)".parse().unwrap();
3784 assert!(!entry.is_empty());
3785 assert_eq!(1, entry.len());
3786 let mut rel = entry.entries().next().unwrap();
3787 rel.remove();
3788 assert!(entry.is_empty());
3789 assert_eq!(0, entry.len());
3790 }
3791
3792 #[test]
3793 fn test_entry_is_empty() {
3794 let entry: Entry = "python3-dulwich (>= 0.20.21) | python3-dulwich (<< 0.18)"
3795 .parse()
3796 .unwrap();
3797 assert!(!entry.is_empty());
3798 assert_eq!(2, entry.len());
3799 let mut rel = entry.relations().next().unwrap();
3800 rel.remove();
3801 assert!(!entry.is_empty());
3802 assert_eq!(1, entry.len());
3803 let mut rel = entry.relations().next().unwrap();
3804 rel.remove();
3805 assert!(entry.is_empty());
3806 assert_eq!(0, entry.len());
3807 }
3808
3809 #[test]
3810 fn test_relation_set_version() {
3811 let mut rel: Relation = "samba".parse().unwrap();
3812 rel.set_version(None);
3813 assert_eq!("samba", rel.to_string());
3814 rel.set_version(Some((
3815 VersionConstraint::GreaterThanEqual,
3816 "2.0".parse().unwrap(),
3817 )));
3818 assert_eq!("samba (>= 2.0)", rel.to_string());
3819 rel.set_version(None);
3820 assert_eq!("samba", rel.to_string());
3821 rel.set_version(Some((
3822 VersionConstraint::GreaterThanEqual,
3823 "2.0".parse().unwrap(),
3824 )));
3825 rel.set_version(Some((
3826 VersionConstraint::GreaterThanEqual,
3827 "1.1".parse().unwrap(),
3828 )));
3829 assert_eq!("samba (>= 1.1)", rel.to_string());
3830 }
3831
3832 #[test]
3833 fn test_replace_relation() {
3834 let mut entry: Entry = "python3-dulwich (>= 0.20.21) | python3-dulwich (<< 0.18)"
3835 .parse()
3836 .unwrap();
3837 let new_rel = Relation::simple("python3-breezy");
3838 entry.replace(0, new_rel);
3839 assert_eq!(
3840 entry.to_string(),
3841 "python3-breezy | python3-dulwich (<< 0.18)"
3842 );
3843 }
3844
3845 #[test]
3846 fn test_entry_push_relation() {
3847 let relations: Relations = "python3-dulwich (>= 0.20.21)".parse().unwrap();
3848 let new_rel = Relation::simple("python3-breezy");
3849 let mut entry = relations.entries().next().unwrap();
3850 entry.push(new_rel);
3851 assert_eq!(
3852 entry.to_string(),
3853 "python3-dulwich (>= 0.20.21) | python3-breezy"
3854 );
3855 assert_eq!(
3856 relations.to_string(),
3857 "python3-dulwich (>= 0.20.21) | python3-breezy"
3858 );
3859 }
3860
3861 #[test]
3862 fn test_relations_remove_empty_entry() {
3863 let (mut relations, errors) = Relations::parse_relaxed("foo, , bar, ", false);
3864 assert_eq!(errors, Vec::<String>::new());
3865 assert_eq!(relations.to_string(), "foo, , bar, ");
3866 assert_eq!(relations.len(), 2);
3867 assert_eq!(
3868 relations.entries().next().unwrap().to_string(),
3869 "foo".to_string()
3870 );
3871 assert_eq!(
3872 relations.entries().nth(1).unwrap().to_string(),
3873 "bar".to_string()
3874 );
3875 relations.remove_entry(1);
3876 assert_eq!(relations.to_string(), "foo, , ");
3877 }
3878
3879 #[test]
3880 fn test_entry_remove_relation() {
3881 let entry: Entry = "python3-dulwich | samba".parse().unwrap();
3882 let removed = entry.remove_relation(0);
3883 assert_eq!(removed.to_string(), "python3-dulwich");
3884 assert_eq!(entry.to_string(), "samba");
3885 }
3886
3887 #[test]
3888 fn test_wrap_and_sort_removes_empty_entries() {
3889 let relations: Relations = "foo, , bar, ".parse().unwrap();
3890 let wrapped = relations.wrap_and_sort();
3891 assert_eq!(wrapped.to_string(), "bar, foo");
3892 }
3893
3894 #[test]
3895 fn test_set_archqual() {
3896 let entry: Entry = "python3-dulwich | samba".parse().unwrap();
3897 let mut rel = entry.relations().next().unwrap();
3898 rel.set_archqual("amd64");
3899 assert_eq!(rel.to_string(), "python3-dulwich:amd64");
3900 assert_eq!(rel.archqual(), Some("amd64".to_string()));
3901 assert_eq!(entry.to_string(), "python3-dulwich:amd64 | samba");
3902 rel.set_archqual("i386");
3903 assert_eq!(rel.to_string(), "python3-dulwich:i386");
3904 assert_eq!(rel.archqual(), Some("i386".to_string()));
3905 assert_eq!(entry.to_string(), "python3-dulwich:i386 | samba");
3906 }
3907
3908 #[test]
3909 fn test_set_architectures() {
3910 let mut relation = Relation::simple("samba");
3911 relation.set_architectures(vec!["amd64", "i386"].into_iter());
3912 assert_eq!(relation.to_string(), "samba [amd64 i386]");
3913 }
3914
3915 #[test]
3916 fn test_relation_builder_no_architectures() {
3917 let relation = Relation::build("debhelper").build();
3919 assert_eq!(relation.to_string(), "debhelper");
3920 }
3921
3922 #[test]
3923 fn test_relation_builder_with_architectures() {
3924 let relation = Relation::build("samba")
3926 .architectures(vec!["amd64".to_string(), "i386".to_string()])
3927 .build();
3928 assert_eq!(relation.to_string(), "samba [amd64 i386]");
3929 }
3930
3931 #[test]
3932 fn test_ensure_minimum_version_add_new() {
3933 let mut relations: Relations = "python3".parse().unwrap();
3934 relations.ensure_minimum_version("debhelper", &"12".parse().unwrap());
3935 assert_eq!(relations.to_string(), "python3, debhelper (>= 12)");
3936 }
3937
3938 #[test]
3939 fn test_ensure_minimum_version_update() {
3940 let mut relations: Relations = "debhelper (>= 9)".parse().unwrap();
3941 relations.ensure_minimum_version("debhelper", &"12".parse().unwrap());
3942 assert_eq!(relations.to_string(), "debhelper (>= 12)");
3943 }
3944
3945 #[test]
3946 fn test_ensure_minimum_version_no_change() {
3947 let mut relations: Relations = "debhelper (>= 13)".parse().unwrap();
3948 relations.ensure_minimum_version("debhelper", &"12".parse().unwrap());
3949 assert_eq!(relations.to_string(), "debhelper (>= 13)");
3950 }
3951
3952 #[test]
3953 fn test_ensure_minimum_version_no_version() {
3954 let mut relations: Relations = "debhelper".parse().unwrap();
3955 relations.ensure_minimum_version("debhelper", &"12".parse().unwrap());
3956 assert_eq!(relations.to_string(), "debhelper (>= 12)");
3957 }
3958
3959 #[test]
3960 fn test_ensure_minimum_version_preserves_newline() {
3961 let input = "\n debhelper (>= 9),\n pkg-config,\n uuid-dev";
3967 let mut relations: Relations = input.parse().unwrap();
3968 relations.ensure_minimum_version("debhelper", &"12~".parse().unwrap());
3969 let result = relations.to_string();
3970
3971 assert!(
3973 result.starts_with('\n'),
3974 "Expected result to start with newline, got: {:?}",
3975 result
3976 );
3977 assert_eq!(result, "\n debhelper (>= 12~),\n pkg-config,\n uuid-dev");
3978 }
3979
3980 #[test]
3981 fn test_ensure_minimum_version_preserves_newline_in_control() {
3982 use crate::lossless::Control;
3984 use std::str::FromStr;
3985
3986 let input = r#"Source: f2fs-tools
3987Section: admin
3988Priority: optional
3989Maintainer: Test <test@example.com>
3990Build-Depends:
3991 debhelper (>= 9),
3992 pkg-config,
3993 uuid-dev
3994
3995Package: f2fs-tools
3996Description: test
3997"#;
3998
3999 let control = Control::from_str(input).unwrap();
4000 let mut source = control.source().unwrap();
4001 let mut build_depends = source.build_depends().unwrap();
4002
4003 let version = Version::from_str("12~").unwrap();
4004 build_depends.ensure_minimum_version("debhelper", &version);
4005
4006 source.set_build_depends(&build_depends);
4007
4008 let output = control.to_string();
4009
4010 assert!(
4012 output.contains("Build-Depends:\n debhelper (>= 12~)"),
4013 "Expected 'Build-Depends:\\n debhelper (>= 12~)' but got:\n{}",
4014 output
4015 );
4016 }
4017
4018 #[test]
4019 fn test_ensure_exact_version_add_new() {
4020 let mut relations: Relations = "python3".parse().unwrap();
4021 relations.ensure_exact_version("debhelper", &"12".parse().unwrap());
4022 assert_eq!(relations.to_string(), "python3, debhelper (= 12)");
4023 }
4024
4025 #[test]
4026 fn test_ensure_exact_version_update() {
4027 let mut relations: Relations = "debhelper (>= 9)".parse().unwrap();
4028 relations.ensure_exact_version("debhelper", &"12".parse().unwrap());
4029 assert_eq!(relations.to_string(), "debhelper (= 12)");
4030 }
4031
4032 #[test]
4033 fn test_ensure_exact_version_no_change() {
4034 let mut relations: Relations = "debhelper (= 12)".parse().unwrap();
4035 relations.ensure_exact_version("debhelper", &"12".parse().unwrap());
4036 assert_eq!(relations.to_string(), "debhelper (= 12)");
4037 }
4038
4039 #[test]
4040 fn test_ensure_some_version_add_new() {
4041 let mut relations: Relations = "python3".parse().unwrap();
4042 relations.ensure_some_version("debhelper");
4043 assert_eq!(relations.to_string(), "python3, debhelper");
4044 }
4045
4046 #[test]
4047 fn test_ensure_some_version_exists_with_version() {
4048 let mut relations: Relations = "debhelper (>= 12)".parse().unwrap();
4049 relations.ensure_some_version("debhelper");
4050 assert_eq!(relations.to_string(), "debhelper (>= 12)");
4051 }
4052
4053 #[test]
4054 fn test_ensure_some_version_exists_no_version() {
4055 let mut relations: Relations = "debhelper".parse().unwrap();
4056 relations.ensure_some_version("debhelper");
4057 assert_eq!(relations.to_string(), "debhelper");
4058 }
4059
4060 #[test]
4061 fn test_ensure_substvar() {
4062 let mut relations: Relations = "python3".parse().unwrap();
4063 relations.ensure_substvar("${misc:Depends}").unwrap();
4064 assert_eq!(relations.to_string(), "python3, ${misc:Depends}");
4065 }
4066
4067 #[test]
4068 fn test_ensure_substvar_already_exists() {
4069 let (mut relations, _) = Relations::parse_relaxed("python3, ${misc:Depends}", true);
4070 relations.ensure_substvar("${misc:Depends}").unwrap();
4071 assert_eq!(relations.to_string(), "python3, ${misc:Depends}");
4072 }
4073
4074 #[test]
4075 fn test_ensure_substvar_empty_relations() {
4076 let mut relations: Relations = Relations::new();
4077 relations.ensure_substvar("${misc:Depends}").unwrap();
4078 assert_eq!(relations.to_string(), "${misc:Depends}");
4079 }
4080
4081 #[test]
4082 fn test_ensure_substvar_preserves_whitespace() {
4083 let (mut relations, _) = Relations::parse_relaxed("python3, rustc", false);
4085 relations.ensure_substvar("${misc:Depends}").unwrap();
4086 assert_eq!(relations.to_string(), "python3, rustc, ${misc:Depends}");
4088 }
4089
4090 #[test]
4091 fn test_ensure_substvar_to_existing_substvar() {
4092 let (mut relations, _) = Relations::parse_relaxed("${shlibs:Depends}", true);
4095 relations.ensure_substvar("${misc:Depends}").unwrap();
4096 assert_eq!(relations.to_string(), "${shlibs:Depends}, ${misc:Depends}");
4098 }
4099
4100 #[test]
4101 fn test_drop_substvar_basic() {
4102 let (mut relations, _) = Relations::parse_relaxed("python3, ${misc:Depends}", true);
4103 relations.drop_substvar("${misc:Depends}");
4104 assert_eq!(relations.to_string(), "python3");
4105 }
4106
4107 #[test]
4108 fn test_drop_substvar_first_position() {
4109 let (mut relations, _) = Relations::parse_relaxed("${misc:Depends}, python3", true);
4110 relations.drop_substvar("${misc:Depends}");
4111 assert_eq!(relations.to_string(), "python3");
4112 }
4113
4114 #[test]
4115 fn test_drop_substvar_middle_position() {
4116 let (mut relations, _) = Relations::parse_relaxed("python3, ${misc:Depends}, rustc", true);
4117 relations.drop_substvar("${misc:Depends}");
4118 assert_eq!(relations.to_string(), "python3, rustc");
4119 }
4120
4121 #[test]
4122 fn test_drop_substvar_only_substvar() {
4123 let (mut relations, _) = Relations::parse_relaxed("${misc:Depends}", true);
4124 relations.drop_substvar("${misc:Depends}");
4125 assert_eq!(relations.to_string(), "");
4126 }
4127
4128 #[test]
4129 fn test_drop_substvar_not_exists() {
4130 let (mut relations, _) = Relations::parse_relaxed("python3, rustc", false);
4131 relations.drop_substvar("${misc:Depends}");
4132 assert_eq!(relations.to_string(), "python3, rustc");
4133 }
4134
4135 #[test]
4136 fn test_drop_substvar_multiple_substvars() {
4137 let (mut relations, _) =
4138 Relations::parse_relaxed("python3, ${misc:Depends}, ${shlibs:Depends}", true);
4139 relations.drop_substvar("${misc:Depends}");
4140 assert_eq!(relations.to_string(), "python3, ${shlibs:Depends}");
4141 }
4142
4143 #[test]
4144 fn test_drop_substvar_preserves_whitespace() {
4145 let (mut relations, _) = Relations::parse_relaxed("python3, ${misc:Depends}", true);
4146 relations.drop_substvar("${misc:Depends}");
4147 assert_eq!(relations.to_string(), "python3");
4148 }
4149
4150 #[test]
4151 fn test_filter_entries_basic() {
4152 let mut relations: Relations = "python3, debhelper, rustc".parse().unwrap();
4153 relations.filter_entries(|entry| entry.relations().any(|r| r.name().starts_with("python")));
4154 assert_eq!(relations.to_string(), "python3");
4155 }
4156
4157 #[test]
4158 fn test_filter_entries_keep_all() {
4159 let mut relations: Relations = "python3, debhelper".parse().unwrap();
4160 relations.filter_entries(|_| true);
4161 assert_eq!(relations.to_string(), "python3, debhelper");
4162 }
4163
4164 #[test]
4165 fn test_filter_entries_remove_all() {
4166 let mut relations: Relations = "python3, debhelper".parse().unwrap();
4167 relations.filter_entries(|_| false);
4168 assert_eq!(relations.to_string(), "");
4169 }
4170
4171 #[test]
4172 fn test_filter_entries_keep_middle() {
4173 let mut relations: Relations = "aaa, bbb, ccc".parse().unwrap();
4174 relations.filter_entries(|entry| entry.relations().any(|r| r.name() == "bbb"));
4175 assert_eq!(relations.to_string(), "bbb");
4176 }
4177
4178 #[test]
4181 fn test_is_sorted_wrap_and_sort_order() {
4182 let relations: Relations = "debhelper, python3, rustc".parse().unwrap();
4184 assert!(relations.is_sorted(&WrapAndSortOrder));
4185
4186 let relations: Relations = "rustc, debhelper, python3".parse().unwrap();
4188 assert!(!relations.is_sorted(&WrapAndSortOrder));
4189
4190 let (relations, _) =
4192 Relations::parse_relaxed("cdbs, debhelper-compat, python3, ${misc:Depends}", true);
4193 assert!(relations.is_sorted(&WrapAndSortOrder));
4194 }
4195
4196 #[test]
4197 fn test_is_sorted_default_order() {
4198 let relations: Relations = "aaa, bbb, ccc".parse().unwrap();
4200 assert!(relations.is_sorted(&DefaultSortingOrder));
4201
4202 let relations: Relations = "ccc, aaa, bbb".parse().unwrap();
4204 assert!(!relations.is_sorted(&DefaultSortingOrder));
4205
4206 let (relations, _) = Relations::parse_relaxed("aaa, bbb, ${misc:Depends}", true);
4208 assert!(relations.is_sorted(&DefaultSortingOrder));
4209 }
4210
4211 #[test]
4212 fn test_is_sorted_with_substvars() {
4213 let (relations, _) = Relations::parse_relaxed("python3, ${misc:Depends}, rustc", true);
4215 assert!(relations.is_sorted(&DefaultSortingOrder));
4217 }
4218
4219 #[test]
4220 fn test_drop_dependency_exists() {
4221 let mut relations: Relations = "python3, debhelper, rustc".parse().unwrap();
4222 assert!(relations.drop_dependency("debhelper"));
4223 assert_eq!(relations.to_string(), "python3, rustc");
4224 }
4225
4226 #[test]
4227 fn test_drop_dependency_not_exists() {
4228 let mut relations: Relations = "python3, rustc".parse().unwrap();
4229 assert!(!relations.drop_dependency("nonexistent"));
4230 assert_eq!(relations.to_string(), "python3, rustc");
4231 }
4232
4233 #[test]
4234 fn test_drop_dependency_only_item() {
4235 let mut relations: Relations = "python3".parse().unwrap();
4236 assert!(relations.drop_dependency("python3"));
4237 assert_eq!(relations.to_string(), "");
4238 }
4239
4240 #[test]
4241 fn test_add_dependency_to_empty() {
4242 let mut relations: Relations = "".parse().unwrap();
4243 let entry = Entry::from(Relation::simple("python3"));
4244 relations.add_dependency(entry, None);
4245 assert_eq!(relations.to_string(), "python3");
4246 }
4247
4248 #[test]
4249 fn test_add_dependency_sorted_position() {
4250 let mut relations: Relations = "debhelper, rustc".parse().unwrap();
4251 let entry = Entry::from(Relation::simple("python3"));
4252 relations.add_dependency(entry, None);
4253 assert_eq!(relations.to_string(), "debhelper, python3, rustc");
4255 }
4256
4257 #[test]
4258 fn test_add_dependency_explicit_position() {
4259 let mut relations: Relations = "python3, rustc".parse().unwrap();
4260 let entry = Entry::from(Relation::simple("debhelper"));
4261 relations.add_dependency(entry, Some(0));
4262 assert_eq!(relations.to_string(), "debhelper, python3, rustc");
4263 }
4264
4265 #[test]
4266 fn test_add_dependency_build_system_first() {
4267 let mut relations: Relations = "python3, rustc".parse().unwrap();
4268 let entry = Entry::from(Relation::simple("debhelper-compat"));
4269 relations.add_dependency(entry, None);
4270 assert_eq!(relations.to_string(), "debhelper-compat, python3, rustc");
4272 }
4273
4274 #[test]
4275 fn test_add_dependency_at_end() {
4276 let mut relations: Relations = "debhelper, python3".parse().unwrap();
4277 let entry = Entry::from(Relation::simple("zzz-package"));
4278 relations.add_dependency(entry, None);
4279 assert_eq!(relations.to_string(), "debhelper, python3, zzz-package");
4281 }
4282
4283 #[test]
4284 fn test_add_dependency_to_single_entry() {
4285 let mut relations: Relations = "python3-dulwich".parse().unwrap();
4287 let entry: Entry = "debhelper-compat (= 12)".parse().unwrap();
4288 relations.add_dependency(entry, None);
4289 assert_eq!(
4291 relations.to_string(),
4292 "debhelper-compat (= 12), python3-dulwich"
4293 );
4294 }
4295
4296 #[test]
4297 fn test_get_relation_exists() {
4298 let relations: Relations = "python3, debhelper (>= 12), rustc".parse().unwrap();
4299 let result = relations.get_relation("debhelper");
4300 assert!(result.is_ok());
4301 let (idx, entry) = result.unwrap();
4302 assert_eq!(idx, 1);
4303 assert_eq!(entry.to_string(), "debhelper (>= 12)");
4304 }
4305
4306 #[test]
4307 fn test_get_relation_not_exists() {
4308 let relations: Relations = "python3, rustc".parse().unwrap();
4309 let result = relations.get_relation("nonexistent");
4310 assert_eq!(result, Err("Package nonexistent not found".to_string()));
4311 }
4312
4313 #[test]
4314 fn test_get_relation_complex_rule() {
4315 let relations: Relations = "python3 | python3-minimal, rustc".parse().unwrap();
4316 let result = relations.get_relation("python3");
4317 assert_eq!(
4318 result,
4319 Err("Complex rule for python3, aborting".to_string())
4320 );
4321 }
4322
4323 #[test]
4324 fn test_iter_relations_for_simple() {
4325 let relations: Relations = "python3, debhelper, python3-dev".parse().unwrap();
4326 let entries: Vec<_> = relations.iter_relations_for("python3").collect();
4327 assert_eq!(entries.len(), 1);
4328 assert_eq!(entries[0].0, 0);
4329 assert_eq!(entries[0].1.to_string(), "python3");
4330 }
4331
4332 #[test]
4333 fn test_iter_relations_for_alternatives() {
4334 let relations: Relations = "python3 | python3-minimal, python3-dev".parse().unwrap();
4335 let entries: Vec<_> = relations.iter_relations_for("python3").collect();
4336 assert_eq!(entries.len(), 1);
4338 assert_eq!(entries[0].0, 0);
4339 }
4340
4341 #[test]
4342 fn test_iter_relations_for_not_found() {
4343 let relations: Relations = "python3, rustc".parse().unwrap();
4344 let entries: Vec<_> = relations.iter_relations_for("debhelper").collect();
4345 assert_eq!(entries.len(), 0);
4346 }
4347
4348 #[test]
4349 fn test_has_relation_exists() {
4350 let relations: Relations = "python3, debhelper, rustc".parse().unwrap();
4351 assert!(relations.has_relation("debhelper"));
4352 assert!(relations.has_relation("python3"));
4353 assert!(relations.has_relation("rustc"));
4354 }
4355
4356 #[test]
4357 fn test_has_relation_not_exists() {
4358 let relations: Relations = "python3, rustc".parse().unwrap();
4359 assert!(!relations.has_relation("debhelper"));
4360 }
4361
4362 #[test]
4363 fn test_has_relation_in_alternative() {
4364 let relations: Relations = "python3 | python3-minimal".parse().unwrap();
4365 assert!(relations.has_relation("python3"));
4366 assert!(relations.has_relation("python3-minimal"));
4367 }
4368
4369 #[test]
4370 fn test_sorting_order_wrap_and_sort_build_systems() {
4371 let order = WrapAndSortOrder;
4372 assert!(order.lt("debhelper", "python3"));
4374 assert!(order.lt("debhelper-compat", "rustc"));
4375 assert!(order.lt("cdbs", "aaa"));
4376 assert!(order.lt("dh-python", "python3"));
4377 }
4378
4379 #[test]
4380 fn test_sorting_order_wrap_and_sort_regular_packages() {
4381 let order = WrapAndSortOrder;
4382 assert!(order.lt("aaa", "bbb"));
4384 assert!(order.lt("python3", "rustc"));
4385 assert!(!order.lt("rustc", "python3"));
4386 }
4387
4388 #[test]
4389 fn test_sorting_order_wrap_and_sort_substvars() {
4390 let order = WrapAndSortOrder;
4391 assert!(order.lt("python3", "${misc:Depends}"));
4393 assert!(!order.lt("${misc:Depends}", "python3"));
4394 assert!(!order.ignore("${misc:Depends}"));
4396 }
4397
4398 #[test]
4399 fn test_sorting_order_default_special_items() {
4400 let order = DefaultSortingOrder;
4401 assert!(order.lt("python3", "${misc:Depends}"));
4403 assert!(order.lt("aaa", "@cdbs@"));
4404 assert!(order.ignore("${misc:Depends}"));
4406 assert!(order.ignore("@cdbs@"));
4407 assert!(!order.ignore("python3"));
4408 }
4409
4410 #[test]
4411 fn test_is_special_package_name() {
4412 assert!(is_special_package_name("${misc:Depends}"));
4413 assert!(is_special_package_name("${shlibs:Depends}"));
4414 assert!(is_special_package_name("@cdbs@"));
4415 assert!(!is_special_package_name("python3"));
4416 assert!(!is_special_package_name("debhelper"));
4417 }
4418
4419 #[test]
4420 fn test_add_dependency_with_explicit_position() {
4421 let mut relations: Relations = "python3, rustc".parse().unwrap();
4423 let entry = Entry::from(Relation::simple("debhelper"));
4424 relations.add_dependency(entry, Some(1));
4425 assert_eq!(relations.to_string(), "python3, debhelper, rustc");
4427 }
4428
4429 #[test]
4430 fn test_whitespace_detection_single_space() {
4431 let mut relations: Relations = "python3, rustc".parse().unwrap();
4432 let entry = Entry::from(Relation::simple("debhelper"));
4433 relations.add_dependency(entry, Some(1));
4434 assert_eq!(relations.to_string(), "python3, debhelper, rustc");
4435 }
4436
4437 #[test]
4438 fn test_whitespace_detection_multiple_spaces() {
4439 let mut relations: Relations = "python3, rustc, gcc".parse().unwrap();
4440 let entry = Entry::from(Relation::simple("debhelper"));
4441 relations.add_dependency(entry, Some(1));
4442 assert_eq!(relations.to_string(), "python3, debhelper, rustc, gcc");
4444 }
4445
4446 #[test]
4447 fn test_whitespace_detection_mixed_patterns() {
4448 let mut relations: Relations = "a, b, c, d, e".parse().unwrap();
4450 let entry = Entry::from(Relation::simple("x"));
4451 relations.push(entry);
4452 assert_eq!(relations.to_string(), "a, b, c, d, e, x");
4455 }
4456
4457 #[test]
4458 fn test_whitespace_detection_newlines() {
4459 let mut relations: Relations = "python3,\n rustc".parse().unwrap();
4460 let entry = Entry::from(Relation::simple("debhelper"));
4461 relations.add_dependency(entry, Some(1));
4462 assert_eq!(relations.to_string(), "python3,\n debhelper,\n rustc");
4464 }
4465
4466 #[test]
4467 fn test_append_with_newline_no_trailing() {
4468 let mut relations: Relations = "foo,\n bar".parse().unwrap();
4469 let entry = Entry::from(Relation::simple("blah"));
4470 relations.add_dependency(entry, None);
4471 assert_eq!(relations.to_string(), "foo,\n bar,\n blah");
4472 }
4473
4474 #[test]
4475 fn test_append_with_trailing_newline() {
4476 let mut relations: Relations = "foo,\n bar\n".parse().unwrap();
4477 let entry = Entry::from(Relation::simple("blah"));
4478 relations.add_dependency(entry, None);
4479 assert_eq!(relations.to_string(), "foo,\n bar,\n blah");
4480 }
4481
4482 #[test]
4483 fn test_append_with_4_space_indent() {
4484 let mut relations: Relations = "foo,\n bar".parse().unwrap();
4485 let entry = Entry::from(Relation::simple("blah"));
4486 relations.add_dependency(entry, None);
4487 assert_eq!(relations.to_string(), "foo,\n bar,\n blah");
4488 }
4489
4490 #[test]
4491 fn test_append_with_4_space_and_trailing_newline() {
4492 let mut relations: Relations = "foo,\n bar\n".parse().unwrap();
4493 let entry = Entry::from(Relation::simple("blah"));
4494 relations.add_dependency(entry, None);
4495 assert_eq!(relations.to_string(), "foo,\n bar,\n blah");
4496 }
4497
4498 #[test]
4499 fn test_odd_syntax_append_no_trailing() {
4500 let mut relations: Relations = "\n foo\n , bar".parse().unwrap();
4501 let entry = Entry::from(Relation::simple("blah"));
4502 relations.add_dependency(entry, None);
4503 assert_eq!(relations.to_string(), "\n foo\n , bar\n , blah");
4504 }
4505
4506 #[test]
4507 fn test_odd_syntax_append_with_trailing() {
4508 let mut relations: Relations = "\n foo\n , bar\n".parse().unwrap();
4509 let entry = Entry::from(Relation::simple("blah"));
4510 relations.add_dependency(entry, None);
4511 assert_eq!(relations.to_string(), "\n foo\n , bar\n , blah");
4512 }
4513
4514 #[test]
4515 fn test_insert_at_1_no_trailing() {
4516 let mut relations: Relations = "foo,\n bar".parse().unwrap();
4517 let entry = Entry::from(Relation::simple("blah"));
4518 relations.add_dependency(entry, Some(1));
4519 assert_eq!(relations.to_string(), "foo,\n blah,\n bar");
4520 }
4521
4522 #[test]
4523 fn test_insert_at_1_with_trailing() {
4524 let mut relations: Relations = "foo,\n bar\n".parse().unwrap();
4525 let entry = Entry::from(Relation::simple("blah"));
4526 relations.add_dependency(entry, Some(1));
4527 assert_eq!(relations.to_string(), "foo,\n blah,\n bar");
4528 }
4529
4530 #[test]
4531 fn test_odd_syntax_insert_at_1() {
4532 let mut relations: Relations = "\n foo\n , bar\n".parse().unwrap();
4533 let entry = Entry::from(Relation::simple("blah"));
4534 relations.add_dependency(entry, Some(1));
4535 assert_eq!(relations.to_string(), "\n foo\n , blah\n , bar");
4536 }
4537
4538 #[test]
4539 fn test_relations_preserves_exact_whitespace() {
4540 let input =
4542 "debhelper (>= 10), quilt (>= 0.40),\n libsystemd-dev [linux-any], pkg-config";
4543
4544 let relations: Relations = input.parse().unwrap();
4545
4546 assert_eq!(
4548 relations.to_string(),
4549 input,
4550 "Relations should preserve exact whitespace from input"
4551 );
4552 }
4553
4554 #[test]
4555 fn test_remove_entry_preserves_indentation() {
4556 let input = "debhelper (>= 10), quilt (>= 0.40),\n libsystemd-dev [linux-any], dh-systemd (>= 1.5), pkg-config";
4558
4559 let mut relations: Relations = input.parse().unwrap();
4560
4561 let mut to_remove = Vec::new();
4563 for (idx, entry) in relations.entries().enumerate() {
4564 for relation in entry.relations() {
4565 if relation.name() == "dh-systemd" {
4566 to_remove.push(idx);
4567 break;
4568 }
4569 }
4570 }
4571
4572 for idx in to_remove.into_iter().rev() {
4573 relations.remove_entry(idx);
4574 }
4575
4576 let output = relations.to_string();
4577 println!("After removal: '{}'", output);
4578
4579 assert!(
4581 output.contains("\n libsystemd-dev"),
4582 "Expected 4-space indentation to be preserved, but got:\n'{}'",
4583 output
4584 );
4585 }
4586
4587 #[test]
4588 fn test_relation_is_implied_by_same_package() {
4589 let inner = Relation::new(
4591 "pkg",
4592 Some((VersionConstraint::GreaterThanEqual, "1.0".parse().unwrap())),
4593 );
4594 let outer = Relation::new(
4595 "pkg",
4596 Some((VersionConstraint::GreaterThanEqual, "1.5".parse().unwrap())),
4597 );
4598 assert!(inner.is_implied_by(&outer));
4599 }
4600
4601 #[test]
4602 fn test_relation_is_implied_by_different_package() {
4603 let inner = Relation::new("pkg1", None);
4605 let outer = Relation::new("pkg2", None);
4606 assert!(!inner.is_implied_by(&outer));
4607 }
4608
4609 #[test]
4610 fn test_relation_is_implied_by_no_version() {
4611 let inner = Relation::new("pkg", None);
4613 let outer = Relation::new(
4614 "pkg",
4615 Some((VersionConstraint::GreaterThanEqual, "1.0".parse().unwrap())),
4616 );
4617 assert!(inner.is_implied_by(&outer));
4618 }
4619
4620 #[test]
4621 fn test_relation_is_implied_by_identical() {
4622 let inner = Relation::new(
4624 "pkg",
4625 Some((VersionConstraint::Equal, "1.0".parse().unwrap())),
4626 );
4627 let outer = Relation::new(
4628 "pkg",
4629 Some((VersionConstraint::Equal, "1.0".parse().unwrap())),
4630 );
4631 assert!(inner.is_implied_by(&outer));
4632 assert!(outer.is_implied_by(&inner));
4633 }
4634
4635 #[test]
4636 fn test_relation_is_implied_by_greater_than_equal() {
4637 let inner = Relation::new(
4639 "pkg",
4640 Some((VersionConstraint::GreaterThanEqual, "1.0".parse().unwrap())),
4641 );
4642 let outer = Relation::new(
4643 "pkg",
4644 Some((VersionConstraint::GreaterThanEqual, "2.0".parse().unwrap())),
4645 );
4646 assert!(inner.is_implied_by(&outer));
4647 assert!(!outer.is_implied_by(&inner));
4648
4649 let outer = Relation::new(
4651 "pkg",
4652 Some((VersionConstraint::Equal, "2.0".parse().unwrap())),
4653 );
4654 assert!(inner.is_implied_by(&outer));
4655
4656 let outer = Relation::new(
4658 "pkg",
4659 Some((VersionConstraint::GreaterThan, "1.5".parse().unwrap())),
4660 );
4661 assert!(inner.is_implied_by(&outer));
4662
4663 let inner = Relation::new(
4665 "pkg",
4666 Some((VersionConstraint::GreaterThanEqual, "3.0".parse().unwrap())),
4667 );
4668 let outer = Relation::new(
4669 "pkg",
4670 Some((VersionConstraint::GreaterThan, "3.0".parse().unwrap())),
4671 );
4672 assert!(!inner.is_implied_by(&outer));
4673 }
4674
4675 #[test]
4676 fn test_relation_is_implied_by_less_than_equal() {
4677 let inner = Relation::new(
4679 "pkg",
4680 Some((VersionConstraint::LessThanEqual, "2.0".parse().unwrap())),
4681 );
4682 let outer = Relation::new(
4683 "pkg",
4684 Some((VersionConstraint::LessThanEqual, "1.0".parse().unwrap())),
4685 );
4686 assert!(inner.is_implied_by(&outer));
4687 assert!(!outer.is_implied_by(&inner));
4688
4689 let outer = Relation::new(
4691 "pkg",
4692 Some((VersionConstraint::Equal, "1.0".parse().unwrap())),
4693 );
4694 assert!(inner.is_implied_by(&outer));
4695
4696 let outer = Relation::new(
4698 "pkg",
4699 Some((VersionConstraint::LessThan, "1.5".parse().unwrap())),
4700 );
4701 assert!(inner.is_implied_by(&outer));
4702 }
4703
4704 #[test]
4705 fn test_relation_is_implied_by_equal() {
4706 let inner = Relation::new(
4708 "pkg",
4709 Some((VersionConstraint::Equal, "1.0".parse().unwrap())),
4710 );
4711 let outer = Relation::new(
4712 "pkg",
4713 Some((VersionConstraint::Equal, "1.0".parse().unwrap())),
4714 );
4715 assert!(inner.is_implied_by(&outer));
4716
4717 let outer = Relation::new(
4719 "pkg",
4720 Some((VersionConstraint::Equal, "2.0".parse().unwrap())),
4721 );
4722 assert!(!inner.is_implied_by(&outer));
4723
4724 let outer = Relation::new(
4726 "pkg",
4727 Some((VersionConstraint::GreaterThanEqual, "1.0".parse().unwrap())),
4728 );
4729 assert!(!inner.is_implied_by(&outer));
4730 }
4731
4732 #[test]
4733 fn test_relation_is_implied_by_greater_than() {
4734 let inner = Relation::new(
4736 "pkg",
4737 Some((VersionConstraint::GreaterThan, "1.0".parse().unwrap())),
4738 );
4739 let outer = Relation::new(
4740 "pkg",
4741 Some((VersionConstraint::GreaterThan, "2.0".parse().unwrap())),
4742 );
4743 assert!(inner.is_implied_by(&outer));
4744
4745 let outer = Relation::new(
4747 "pkg",
4748 Some((VersionConstraint::Equal, "2.0".parse().unwrap())),
4749 );
4750 assert!(inner.is_implied_by(&outer));
4751
4752 let outer = Relation::new(
4754 "pkg",
4755 Some((VersionConstraint::GreaterThanEqual, "1.5".parse().unwrap())),
4756 );
4757 assert!(inner.is_implied_by(&outer));
4758
4759 let outer = Relation::new(
4761 "pkg",
4762 Some((VersionConstraint::GreaterThanEqual, "1.0".parse().unwrap())),
4763 );
4764 assert!(!inner.is_implied_by(&outer));
4765 }
4766
4767 #[test]
4768 fn test_relation_is_implied_by_less_than() {
4769 let inner = Relation::new(
4771 "pkg",
4772 Some((VersionConstraint::LessThan, "2.0".parse().unwrap())),
4773 );
4774 let outer = Relation::new(
4775 "pkg",
4776 Some((VersionConstraint::LessThan, "1.0".parse().unwrap())),
4777 );
4778 assert!(inner.is_implied_by(&outer));
4779
4780 let outer = Relation::new(
4782 "pkg",
4783 Some((VersionConstraint::Equal, "1.0".parse().unwrap())),
4784 );
4785 assert!(inner.is_implied_by(&outer));
4786
4787 let outer = Relation::new(
4789 "pkg",
4790 Some((VersionConstraint::LessThanEqual, "1.5".parse().unwrap())),
4791 );
4792 assert!(inner.is_implied_by(&outer));
4793 }
4794
4795 #[test]
4796 fn test_relation_is_implied_by_incompatible_constraints() {
4797 let inner = Relation::new(
4799 "pkg",
4800 Some((VersionConstraint::GreaterThanEqual, "1.0".parse().unwrap())),
4801 );
4802 let outer = Relation::new(
4803 "pkg",
4804 Some((VersionConstraint::LessThanEqual, "2.0".parse().unwrap())),
4805 );
4806 assert!(!inner.is_implied_by(&outer));
4807 assert!(!outer.is_implied_by(&inner));
4808 }
4809
4810 #[test]
4811 fn test_entry_is_implied_by_identical() {
4812 let inner: Entry = "pkg (>= 1.0)".parse().unwrap();
4813 let outer: Entry = "pkg (>= 1.0)".parse().unwrap();
4814 assert!(inner.is_implied_by(&outer));
4815 }
4816
4817 #[test]
4818 fn test_entry_is_implied_by_or_group() {
4819 let inner: Entry = "pkg (>= 1.0)".parse().unwrap();
4821 let outer: Entry = "pkg (>= 1.5) | libc6".parse().unwrap();
4822 assert!(inner.is_implied_by(&outer));
4823 }
4824
4825 #[test]
4826 fn test_entry_is_implied_by_simple_or() {
4827 let inner: Entry = "pkg1 | pkg2".parse().unwrap();
4829 let outer: Entry = "pkg1".parse().unwrap();
4830 assert!(inner.is_implied_by(&outer));
4831
4832 let outer: Entry = "pkg2".parse().unwrap();
4834 assert!(inner.is_implied_by(&outer));
4835 }
4836
4837 #[test]
4838 fn test_entry_is_implied_by_not_implied() {
4839 let inner: Entry = "pkg (>= 2.0)".parse().unwrap();
4841 let outer: Entry = "pkg (>= 1.0)".parse().unwrap();
4842 assert!(!inner.is_implied_by(&outer));
4843 }
4844
4845 #[test]
4846 fn test_entry_is_implied_by_different_packages() {
4847 let inner: Entry = "pkg1".parse().unwrap();
4848 let outer: Entry = "pkg2".parse().unwrap();
4849 assert!(!inner.is_implied_by(&outer));
4850 }
4851
4852 #[test]
4853 fn test_entry_is_implied_by_complex_or() {
4854 let inner: Entry = "pkg1 | pkg2".parse().unwrap();
4856 let outer: Entry = "pkg1 | pkg2".parse().unwrap();
4857 assert!(inner.is_implied_by(&outer));
4858
4859 let outer: Entry = "pkg1 | pkg2 | pkg3".parse().unwrap();
4861 assert!(inner.is_implied_by(&outer));
4862 }
4863
4864 #[test]
4865 fn test_parse_version_with_epoch() {
4866 let input = "amule-dbg (<< 1:2.3.2-2~)";
4869 let parsed: Relations = input.parse().unwrap();
4870 assert_eq!(parsed.to_string(), input);
4871 assert_eq!(parsed.entries().count(), 1);
4872 let entry = parsed.entries().next().unwrap();
4873 assert_eq!(entry.to_string(), "amule-dbg (<< 1:2.3.2-2~)");
4874 assert_eq!(entry.relations().count(), 1);
4875 let relation = entry.relations().next().unwrap();
4876 assert_eq!(relation.name(), "amule-dbg");
4877 assert_eq!(relation.to_string(), "amule-dbg (<< 1:2.3.2-2~)");
4878 assert_eq!(
4879 relation.version(),
4880 Some((VersionConstraint::LessThan, "1:2.3.2-2~".parse().unwrap()))
4881 );
4882 }
4883
4884 #[test]
4885 fn test_ensure_relation_add_new() {
4886 let mut relations: Relations = "python3".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!(relations.to_string(), "debhelper (>= 12), python3");
4893 }
4894
4895 #[test]
4896 fn test_ensure_relation_already_satisfied() {
4897 let mut relations: Relations = "debhelper (>= 13)".parse().unwrap();
4899 let new_entry: Entry = "debhelper (>= 12)".parse().unwrap();
4900 let added = relations.ensure_relation(new_entry);
4901 assert!(!added);
4902 assert_eq!(relations.to_string(), "debhelper (>= 13)");
4903 }
4904
4905 #[test]
4906 fn test_ensure_relation_replace_weaker() {
4907 let mut relations: Relations = "debhelper (>= 11)".parse().unwrap();
4909 let new_entry: Entry = "debhelper (>= 13)".parse().unwrap();
4910 let added = relations.ensure_relation(new_entry);
4911 assert!(added);
4912 assert_eq!(relations.to_string(), "debhelper (>= 13)");
4913 }
4914
4915 #[test]
4916 fn test_ensure_relation_replace_multiple_weaker() {
4917 let mut relations: Relations = "debhelper (>= 11), debhelper (>= 10), python3"
4919 .parse()
4920 .unwrap();
4921 let new_entry: Entry = "debhelper (>= 13)".parse().unwrap();
4922 let added = relations.ensure_relation(new_entry);
4923 assert!(added);
4924 assert_eq!(relations.to_string(), "debhelper (>= 13), python3");
4925 }
4926
4927 #[test]
4928 fn test_ensure_relation_identical_entry() {
4929 let mut relations: Relations = "debhelper (>= 12)".parse().unwrap();
4931 let new_entry: Entry = "debhelper (>= 12)".parse().unwrap();
4932 let added = relations.ensure_relation(new_entry);
4933 assert!(!added);
4934 assert_eq!(relations.to_string(), "debhelper (>= 12)");
4935 }
4936
4937 #[test]
4938 fn test_ensure_relation_no_version_constraint() {
4939 let mut relations: Relations = "python3".parse().unwrap();
4941 let new_entry: Entry = "debhelper".parse().unwrap();
4942 let added = relations.ensure_relation(new_entry);
4943 assert!(added);
4944 assert_eq!(relations.to_string(), "debhelper, python3");
4946 }
4947
4948 #[test]
4949 fn test_ensure_relation_strengthen_unversioned() {
4950 let mut relations: Relations = "debhelper".parse().unwrap();
4953 let new_entry: Entry = "debhelper (>= 12)".parse().unwrap();
4954 let added = relations.ensure_relation(new_entry);
4955 assert!(added);
4956 assert_eq!(relations.to_string(), "debhelper (>= 12)");
4957 }
4958
4959 #[test]
4960 fn test_ensure_relation_versioned_implies_unversioned() {
4961 let mut relations: Relations = "debhelper (>= 12)".parse().unwrap();
4964 let new_entry: Entry = "debhelper".parse().unwrap();
4965 let added = relations.ensure_relation(new_entry);
4966 assert!(!added);
4967 assert_eq!(relations.to_string(), "debhelper (>= 12)");
4968 }
4969
4970 #[test]
4971 fn test_ensure_relation_preserves_whitespace() {
4972 let mut relations: Relations = "python3, rustc".parse().unwrap();
4974 let new_entry: Entry = "debhelper (>= 12)".parse().unwrap();
4975 let added = relations.ensure_relation(new_entry);
4976 assert!(added);
4977 assert_eq!(relations.to_string(), "debhelper (>= 12), python3, rustc");
4979 }
4980
4981 #[test]
4982 fn test_ensure_relation_empty_relations() {
4983 let mut relations: Relations = Relations::new();
4985 let new_entry: Entry = "debhelper (>= 12)".parse().unwrap();
4986 let added = relations.ensure_relation(new_entry);
4987 assert!(added);
4988 assert_eq!(relations.to_string(), "debhelper (>= 12)");
4989 }
4990
4991 #[test]
4992 fn test_ensure_relation_alternative_dependencies() {
4993 let mut relations: Relations = "python3 | python3-minimal".parse().unwrap();
4995 let new_entry: Entry = "debhelper (>= 12)".parse().unwrap();
4996 let added = relations.ensure_relation(new_entry);
4997 assert!(added);
4998 assert_eq!(
5000 relations.to_string(),
5001 "debhelper (>= 12), python3 | python3-minimal"
5002 );
5003 }
5004
5005 #[test]
5006 fn test_ensure_relation_replace_in_middle() {
5007 let mut relations: Relations = "python3, debhelper (>= 11), rustc".parse().unwrap();
5009 let new_entry: Entry = "debhelper (>= 13)".parse().unwrap();
5010 let added = relations.ensure_relation(new_entry);
5011 assert!(added);
5012 assert_eq!(relations.to_string(), "python3, debhelper (>= 13), rustc");
5013 }
5014
5015 #[test]
5016 fn test_ensure_relation_with_different_package() {
5017 let mut relations: Relations = "python3, debhelper (>= 12)".parse().unwrap();
5019 let new_entry: Entry = "rustc".parse().unwrap();
5020 let added = relations.ensure_relation(new_entry);
5021 assert!(added);
5022 assert_eq!(relations.to_string(), "python3, debhelper (>= 12), rustc");
5023 }
5024
5025 #[test]
5026 fn test_parse_invalid_token_in_arch_list() {
5027 let input = "foo [>= bar]";
5028 let result: Result<Relations, _> = input.parse();
5029 assert!(
5030 result.is_err(),
5031 "Expected error for invalid token in architecture list"
5032 );
5033 }
5034
5035 #[test]
5036 fn test_parse_invalid_token_in_profile_list() {
5037 let input = "foo <[] baz>";
5038 let result: Result<Relations, _> = input.parse();
5039 assert!(
5040 result.is_err(),
5041 "Expected error for invalid token in profile list"
5042 );
5043 }
5044
5045 #[test]
5046 fn test_parse_relaxed_unterminated_arch_list() {
5047 let (relations, errors) = Relations::parse_relaxed("libc6 [", true);
5048 assert!(!errors.is_empty());
5049 assert_eq!(relations.to_string(), "libc6 [");
5050 }
5051
5052 #[test]
5053 fn test_parse_relaxed_partial_arch_name() {
5054 let (relations, errors) = Relations::parse_relaxed("libc6 [amd", true);
5055 assert!(!errors.is_empty());
5056 assert_eq!(relations.to_string(), "libc6 [amd");
5057 }
5058
5059 #[test]
5060 fn test_parse_relaxed_unterminated_profile_list() {
5061 let (relations, errors) = Relations::parse_relaxed("libc6 <cross", true);
5062 assert!(!errors.is_empty());
5063 assert_eq!(relations.to_string(), "libc6 <cross");
5064 }
5065
5066 #[test]
5067 fn test_parse_relaxed_unterminated_substvar() {
5068 let (relations, errors) = Relations::parse_relaxed("${shlibs:Depends", true);
5069 assert!(!errors.is_empty());
5070 assert_eq!(relations.to_string(), "${shlibs:Depends");
5071 }
5072
5073 #[test]
5074 fn test_parse_relaxed_empty_substvar() {
5075 let (relations, errors) = Relations::parse_relaxed("${", true);
5076 assert!(!errors.is_empty());
5077 assert_eq!(relations.to_string(), "${");
5078 }
5079
5080 #[test]
5081 fn test_parse_with_comments() {
5082 let input = "dh-python,\nlibsvn-dev,\n# python-all-dbg (>= 2.6.6-3),\npython3-all-dev,\n# python3-all-dbg,\npython3-docutils";
5083 let relations: Relations = input.parse().unwrap();
5084 let entries: Vec<_> = relations.entries().collect();
5085 assert_eq!(entries.len(), 4);
5086 assert_eq!(entries[0].to_string(), "dh-python");
5087 assert_eq!(entries[1].to_string(), "libsvn-dev");
5088 assert_eq!(entries[2].to_string(), "python3-all-dev");
5089 assert_eq!(entries[3].to_string(), "python3-docutils");
5090 assert_eq!(relations.to_string(), input);
5092 }
5093
5094 #[test]
5095 fn test_remove_entry_with_adjacent_comment() {
5096 let input = "dh-python,\n# commented-out,\npython3-all-dev";
5097 let mut relations: Relations = input.parse().unwrap();
5098 assert_eq!(relations.entries().count(), 2);
5099 relations.remove_entry(0);
5100 assert_eq!(relations.entries().count(), 1);
5101 assert_eq!(
5102 relations.entries().next().unwrap().to_string(),
5103 "python3-all-dev"
5104 );
5105 }
5106
5107 #[test]
5108 fn test_insert_entry_with_comments_present() {
5109 let input = "dh-python,\n# commented-out,\npython3-all-dev";
5110 let mut relations: Relations = input.parse().unwrap();
5111 let new_entry: Entry = "libfoo-dev".parse().unwrap();
5112 relations.push(new_entry);
5113 let entries: Vec<_> = relations.entries().collect();
5115 assert_eq!(entries.len(), 3);
5116 assert_eq!(entries[2].to_string(), "libfoo-dev");
5117 }
5118
5119 #[test]
5120 fn test_drop_dependency_with_comments() {
5121 let input = "dh-python,\n# commented-out,\npython3-all-dev,\nlibfoo-dev";
5122 let mut relations: Relations = input.parse().unwrap();
5123 assert!(relations.drop_dependency("python3-all-dev"));
5124 let entries: Vec<_> = relations.entries().collect();
5125 assert_eq!(entries.len(), 2);
5126 assert_eq!(entries[0].to_string(), "dh-python");
5127 assert_eq!(entries[1].to_string(), "libfoo-dev");
5128 }
5129
5130 #[test]
5131 fn test_relation_remove_first_with_malformed_tree() {
5132 let (relations, errors) = Relations::parse_relaxed("foo @ bar | baz", false);
5135 assert!(!errors.is_empty());
5136 let entry = relations.get_entry(0).unwrap();
5137 let mut relation = entry.get_relation(0).unwrap();
5138 relation.remove();
5140 }
5141}