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