debian_control/lossless/
control.rs

1//! This module provides a lossless representation of a Debian control file.
2//!
3//! # Example
4//! ```rust
5//! use debian_control::lossless::Control;
6//! use debian_control::relations::VersionConstraint;
7//! let input = r###"Source: dulwich
8//! ## Comments are preserved
9//! Maintainer: Jelmer Vernooij <jelmer@jelmer.uk>
10//! Build-Depends: python3, debhelper-compat (= 12)
11//!
12//! Package: python3-dulwich
13//! Architecture: amd64
14//! Description: Pure-python git implementation
15//! "###;
16//!
17//! let mut control: Control = input.parse().unwrap();
18//!
19//! // Bump debhelper-compat
20//! let source = control.source().unwrap();
21//! let bd = source.build_depends().unwrap();
22//!
23//! // Get entry with index 1 in Build-Depends, then set the version
24//! let entry = bd.get_entry(1).unwrap();
25//! let mut debhelper = entry.relations().next().unwrap();
26//! assert_eq!(debhelper.name(), "debhelper-compat");
27//! debhelper.set_version(Some((VersionConstraint::Equal, "13".parse().unwrap())));
28//!
29//! assert_eq!(source.to_string(), r###"Source: dulwich
30//! ## Comments are preserved
31//! Maintainer: Jelmer Vernooij <jelmer@jelmer.uk>
32//! Build-Depends: python3, debhelper-compat (= 12)
33//! "###);
34//! ```
35use crate::fields::{MultiArch, Priority};
36use crate::lossless::relations::Relations;
37use deb822_lossless::{Deb822, Paragraph, BINARY_FIELD_ORDER, SOURCE_FIELD_ORDER};
38use rowan::ast::AstNode;
39
40fn format_field(name: &str, value: &str) -> String {
41    match name {
42        "Uploaders" => value
43            .split(',')
44            .map(|s| s.trim().to_string())
45            .collect::<Vec<_>>()
46            .join(",\n"),
47        "Build-Depends"
48        | "Build-Depends-Indep"
49        | "Build-Depends-Arch"
50        | "Build-Conflicts"
51        | "Build-Conflicts-Indep"
52        | "Build-Conflics-Arch"
53        | "Depends"
54        | "Recommends"
55        | "Suggests"
56        | "Enhances"
57        | "Pre-Depends"
58        | "Breaks" => {
59            let relations: Relations = value.parse().unwrap();
60            let relations = relations.wrap_and_sort();
61            relations.to_string()
62        }
63        _ => value.to_string(),
64    }
65}
66
67/// A Debian control file
68#[derive(Debug, Clone, PartialEq, Eq)]
69pub struct Control(Deb822);
70
71impl Control {
72    /// Create a new control file
73    pub fn new() -> Self {
74        Control(Deb822::new())
75    }
76
77    /// Return the underlying deb822 object, mutable
78    pub fn as_mut_deb822(&mut self) -> &mut Deb822 {
79        &mut self.0
80    }
81
82    /// Return the underlying deb822 object
83    pub fn as_deb822(&self) -> &Deb822 {
84        &self.0
85    }
86
87    /// Parse control file text, returning a Parse result
88    pub fn parse(text: &str) -> deb822_lossless::Parse<Control> {
89        let deb822_parse = Deb822::parse(text);
90        // Transform Parse<Deb822> to Parse<Control>
91        let green = deb822_parse.green().clone();
92        let errors = deb822_parse.errors().to_vec();
93        let positioned_errors = deb822_parse.positioned_errors().to_vec();
94        deb822_lossless::Parse::new_with_positioned_errors(green, errors, positioned_errors)
95    }
96
97    /// Return the source package
98    pub fn source(&self) -> Option<Source> {
99        self.0
100            .paragraphs()
101            .find(|p| p.get("Source").is_some())
102            .map(Source)
103    }
104
105    /// Iterate over all binary packages
106    pub fn binaries(&self) -> impl Iterator<Item = Binary> {
107        self.0
108            .paragraphs()
109            .filter(|p| p.get("Package").is_some())
110            .map(Binary)
111    }
112
113    /// Add a new source package
114    ///
115    /// # Arguments
116    /// * `name` - The name of the source package
117    ///
118    /// # Returns
119    /// The newly created source package
120    ///
121    /// # Example
122    /// ```rust
123    /// use debian_control::lossless::control::Control;
124    /// let mut control = Control::new();
125    /// let source = control.add_source("foo");
126    /// assert_eq!(source.name(), Some("foo".to_owned()));
127    /// ```
128    pub fn add_source(&mut self, name: &str) -> Source {
129        let mut p = self.0.add_paragraph();
130        p.set("Source", name);
131        self.source().unwrap()
132    }
133
134    /// Add new binary package
135    ///
136    /// # Arguments
137    /// * `name` - The name of the binary package
138    ///
139    /// # Returns
140    /// The newly created binary package
141    ///
142    /// # Example
143    /// ```rust
144    /// use debian_control::lossless::control::Control;
145    /// let mut control = Control::new();
146    /// let binary = control.add_binary("foo");
147    /// assert_eq!(binary.name(), Some("foo".to_owned()));
148    /// ```
149    pub fn add_binary(&mut self, name: &str) -> Binary {
150        let mut p = self.0.add_paragraph();
151        p.set("Package", name);
152        Binary(p)
153    }
154
155    /// Remove a binary package paragraph by name
156    ///
157    /// # Arguments
158    /// * `name` - The name of the binary package to remove
159    ///
160    /// # Returns
161    /// `true` if a binary paragraph with the given name was found and removed, `false` otherwise
162    ///
163    /// # Example
164    /// ```rust
165    /// use debian_control::lossless::control::Control;
166    /// let mut control = Control::new();
167    /// control.add_binary("foo");
168    /// assert_eq!(control.binaries().count(), 1);
169    /// assert!(control.remove_binary("foo"));
170    /// assert_eq!(control.binaries().count(), 0);
171    /// ```
172    pub fn remove_binary(&mut self, name: &str) -> bool {
173        let index = self
174            .0
175            .paragraphs()
176            .position(|p| p.get("Package").as_deref() == Some(name));
177
178        if let Some(index) = index {
179            self.0.remove_paragraph(index);
180            true
181        } else {
182            false
183        }
184    }
185
186    /// Read a control file from a file
187    pub fn from_file<P: AsRef<std::path::Path>>(path: P) -> Result<Self, deb822_lossless::Error> {
188        Ok(Control(Deb822::from_file(path)?))
189    }
190
191    /// Read a control file from a file, allowing syntax errors
192    pub fn from_file_relaxed<P: AsRef<std::path::Path>>(
193        path: P,
194    ) -> Result<(Self, Vec<String>), std::io::Error> {
195        let (control, errors) = Deb822::from_file_relaxed(path)?;
196        Ok((Control(control), errors))
197    }
198
199    /// Read a control file from a reader
200    pub fn read<R: std::io::Read>(mut r: R) -> Result<Self, deb822_lossless::Error> {
201        Ok(Control(Deb822::read(&mut r)?))
202    }
203
204    /// Read a control file from a reader, allowing syntax errors
205    pub fn read_relaxed<R: std::io::Read>(
206        mut r: R,
207    ) -> Result<(Self, Vec<String>), deb822_lossless::Error> {
208        let (control, errors) = Deb822::read_relaxed(&mut r)?;
209        Ok((Self(control), errors))
210    }
211
212    /// Wrap and sort the control file
213    ///
214    /// # Arguments
215    /// * `indentation` - The indentation to use
216    /// * `immediate_empty_line` - Whether to add an empty line at the start of multi-line fields
217    /// * `max_line_length_one_liner` - The maximum line length for one-liner fields
218    pub fn wrap_and_sort(
219        &mut self,
220        indentation: deb822_lossless::Indentation,
221        immediate_empty_line: bool,
222        max_line_length_one_liner: Option<usize>,
223    ) {
224        let sort_paragraphs = |a: &Paragraph, b: &Paragraph| -> std::cmp::Ordering {
225            // Sort Source before Package
226            let a_is_source = a.get("Source").is_some();
227            let b_is_source = b.get("Source").is_some();
228
229            if a_is_source && !b_is_source {
230                return std::cmp::Ordering::Less;
231            } else if !a_is_source && b_is_source {
232                return std::cmp::Ordering::Greater;
233            } else if a_is_source && b_is_source {
234                return a.get("Source").cmp(&b.get("Source"));
235            }
236
237            a.get("Package").cmp(&b.get("Package"))
238        };
239
240        let wrap_paragraph = |p: &Paragraph| -> Paragraph {
241            // TODO: Add Source/Package specific wrapping
242            // TODO: Add support for wrapping and sorting fields
243            p.wrap_and_sort(
244                indentation,
245                immediate_empty_line,
246                max_line_length_one_liner,
247                None,
248                Some(&format_field),
249            )
250        };
251
252        self.0 = self
253            .0
254            .wrap_and_sort(Some(&sort_paragraphs), Some(&wrap_paragraph));
255    }
256
257    /// Sort binary package paragraphs alphabetically by package name.
258    ///
259    /// This method reorders the binary package paragraphs in alphabetical order
260    /// based on their Package field value. The source paragraph always remains first.
261    ///
262    /// # Arguments
263    /// * `keep_first` - If true, keeps the first binary package in place and only
264    ///   sorts the remaining binary packages. If false, sorts all binary packages.
265    ///
266    /// # Example
267    /// ```rust
268    /// use debian_control::lossless::Control;
269    ///
270    /// let input = r#"Source: foo
271    ///
272    /// Package: libfoo
273    /// Architecture: all
274    ///
275    /// Package: libbar
276    /// Architecture: all
277    /// "#;
278    ///
279    /// let mut control: Control = input.parse().unwrap();
280    /// control.sort_binaries(false);
281    ///
282    /// // Binary packages are now sorted: libbar comes before libfoo
283    /// let binaries: Vec<_> = control.binaries().collect();
284    /// assert_eq!(binaries[0].name(), Some("libbar".to_string()));
285    /// assert_eq!(binaries[1].name(), Some("libfoo".to_string()));
286    /// ```
287    pub fn sort_binaries(&mut self, keep_first: bool) {
288        let mut paragraphs: Vec<_> = self.0.paragraphs().collect();
289
290        if paragraphs.len() <= 1 {
291            return; // Only source paragraph, nothing to sort
292        }
293
294        // Find the index where binary packages start (after source)
295        let source_idx = paragraphs.iter().position(|p| p.get("Source").is_some());
296        let binary_start = source_idx.map(|i| i + 1).unwrap_or(0);
297
298        // Determine where to start sorting
299        let sort_start = if keep_first && paragraphs.len() > binary_start + 1 {
300            binary_start + 1
301        } else {
302            binary_start
303        };
304
305        if sort_start >= paragraphs.len() {
306            return; // Nothing to sort
307        }
308
309        // Sort binary packages by package name
310        paragraphs[sort_start..].sort_by(|a, b| {
311            let a_name = a.get("Package");
312            let b_name = b.get("Package");
313            a_name.cmp(&b_name)
314        });
315
316        // Rebuild the Deb822 with sorted paragraphs
317        let sort_paragraphs = |a: &Paragraph, b: &Paragraph| -> std::cmp::Ordering {
318            let a_pos = paragraphs.iter().position(|p| p == a);
319            let b_pos = paragraphs.iter().position(|p| p == b);
320            a_pos.cmp(&b_pos)
321        };
322
323        self.0 = self.0.wrap_and_sort(Some(&sort_paragraphs), None);
324    }
325
326    /// Iterate over fields that overlap with the given range
327    ///
328    /// This method returns all fields (entries) from all paragraphs that have any overlap
329    /// with the specified text range. This is useful for incremental parsing in LSP contexts
330    /// where you only want to process fields that were affected by a text change.
331    ///
332    /// # Arguments
333    /// * `range` - The text range to check for overlaps
334    ///
335    /// # Returns
336    /// An iterator over all Entry items that overlap with the given range
337    ///
338    /// # Example
339    /// ```rust
340    /// use debian_control::lossless::Control;
341    /// use deb822_lossless::TextRange;
342    ///
343    /// let control_text = "Source: foo\nMaintainer: test@example.com\n\nPackage: bar\nArchitecture: all\n";
344    /// let control: Control = control_text.parse().unwrap();
345    ///
346    /// // Get fields in a specific range (e.g., where a change occurred)
347    /// let change_range = TextRange::new(20.into(), 40.into());
348    /// for entry in control.fields_in_range(change_range) {
349    ///     if let Some(key) = entry.key() {
350    ///         println!("Field {} was in the changed range", key);
351    ///     }
352    /// }
353    /// ```
354    pub fn fields_in_range(
355        &self,
356        range: rowan::TextRange,
357    ) -> impl Iterator<Item = deb822_lossless::Entry> + '_ {
358        self.0
359            .paragraphs()
360            .flat_map(move |p| p.entries().collect::<Vec<_>>())
361            .filter(move |entry| {
362                let entry_range = entry.syntax().text_range();
363                // Check if ranges overlap
364                entry_range.start() < range.end() && range.start() < entry_range.end()
365            })
366    }
367}
368
369impl From<Control> for Deb822 {
370    fn from(c: Control) -> Self {
371        c.0
372    }
373}
374
375impl From<Deb822> for Control {
376    fn from(d: Deb822) -> Self {
377        Control(d)
378    }
379}
380
381impl Default for Control {
382    fn default() -> Self {
383        Self::new()
384    }
385}
386
387impl std::str::FromStr for Control {
388    type Err = deb822_lossless::ParseError;
389
390    fn from_str(s: &str) -> Result<Self, Self::Err> {
391        Control::parse(s).to_result()
392    }
393}
394
395/// A source package paragraph
396#[derive(Debug, Clone, PartialEq, Eq)]
397pub struct Source(Paragraph);
398
399impl From<Source> for Paragraph {
400    fn from(s: Source) -> Self {
401        s.0
402    }
403}
404
405impl From<Paragraph> for Source {
406    fn from(p: Paragraph) -> Self {
407        Source(p)
408    }
409}
410
411impl std::fmt::Display for Source {
412    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
413        self.0.fmt(f)
414    }
415}
416
417impl Source {
418    /// The name of the source package.
419    pub fn name(&self) -> Option<String> {
420        self.0.get("Source")
421    }
422
423    /// Wrap and sort the control file paragraph
424    pub fn wrap_and_sort(
425        &mut self,
426        indentation: deb822_lossless::Indentation,
427        immediate_empty_line: bool,
428        max_line_length_one_liner: Option<usize>,
429    ) {
430        self.0 = self.0.wrap_and_sort(
431            indentation,
432            immediate_empty_line,
433            max_line_length_one_liner,
434            None,
435            Some(&format_field),
436        );
437    }
438
439    /// Return the underlying deb822 paragraph, mutable
440    pub fn as_mut_deb822(&mut self) -> &mut Paragraph {
441        &mut self.0
442    }
443
444    /// Return the underlying deb822 paragraph
445    pub fn as_deb822(&self) -> &Paragraph {
446        &self.0
447    }
448
449    /// Set the name of the source package.
450    pub fn set_name(&mut self, name: &str) {
451        self.set("Source", name);
452    }
453
454    /// The default section of the packages built from this source package.
455    pub fn section(&self) -> Option<String> {
456        self.0.get("Section")
457    }
458
459    /// Set the section of the source package
460    pub fn set_section(&mut self, section: Option<&str>) {
461        if let Some(section) = section {
462            self.set("Section", section);
463        } else {
464            self.0.remove("Section");
465        }
466    }
467
468    /// The default priority of the packages built from this source package.
469    pub fn priority(&self) -> Option<Priority> {
470        self.0.get("Priority").and_then(|v| v.parse().ok())
471    }
472
473    /// Set the priority of the source package
474    pub fn set_priority(&mut self, priority: Option<Priority>) {
475        if let Some(priority) = priority {
476            self.set("Priority", priority.to_string().as_str());
477        } else {
478            self.0.remove("Priority");
479        }
480    }
481
482    /// The maintainer of the package.
483    pub fn maintainer(&self) -> Option<String> {
484        self.0.get("Maintainer")
485    }
486
487    /// Set the maintainer of the package
488    pub fn set_maintainer(&mut self, maintainer: &str) {
489        self.set("Maintainer", maintainer);
490    }
491
492    /// The build dependencies of the package.
493    pub fn build_depends(&self) -> Option<Relations> {
494        self.0.get("Build-Depends").map(|s| s.parse().unwrap())
495    }
496
497    /// Set the Build-Depends field
498    pub fn set_build_depends(&mut self, relations: &Relations) {
499        self.set("Build-Depends", relations.to_string().as_str());
500    }
501
502    /// Return the Build-Depends-Indep field
503    pub fn build_depends_indep(&self) -> Option<Relations> {
504        self.0
505            .get("Build-Depends-Indep")
506            .map(|s| s.parse().unwrap())
507    }
508
509    /// Return the Build-Depends-Arch field
510    pub fn build_depends_arch(&self) -> Option<Relations> {
511        self.0.get("Build-Depends-Arch").map(|s| s.parse().unwrap())
512    }
513
514    /// The build conflicts of the package.
515    pub fn build_conflicts(&self) -> Option<Relations> {
516        self.0.get("Build-Conflicts").map(|s| s.parse().unwrap())
517    }
518
519    /// Return the Build-Conflicts-Indep field
520    pub fn build_conflicts_indep(&self) -> Option<Relations> {
521        self.0
522            .get("Build-Conflicts-Indep")
523            .map(|s| s.parse().unwrap())
524    }
525
526    /// Return the Build-Conflicts-Arch field
527    pub fn build_conflicts_arch(&self) -> Option<Relations> {
528        self.0
529            .get("Build-Conflicts-Arch")
530            .map(|s| s.parse().unwrap())
531    }
532
533    /// Return the standards version
534    pub fn standards_version(&self) -> Option<String> {
535        self.0.get("Standards-Version")
536    }
537
538    /// Set the Standards-Version field
539    pub fn set_standards_version(&mut self, version: &str) {
540        self.set("Standards-Version", version);
541    }
542
543    /// Return the upstrea mHomepage
544    pub fn homepage(&self) -> Option<url::Url> {
545        self.0.get("Homepage").and_then(|s| s.parse().ok())
546    }
547
548    /// Set the Homepage field
549    pub fn set_homepage(&mut self, homepage: &url::Url) {
550        self.set("Homepage", homepage.to_string().as_str());
551    }
552
553    /// Return the Vcs-Git field
554    pub fn vcs_git(&self) -> Option<String> {
555        self.0.get("Vcs-Git")
556    }
557
558    /// Set the Vcs-Git field
559    pub fn set_vcs_git(&mut self, url: &str) {
560        self.set("Vcs-Git", url);
561    }
562
563    /// Return the Vcs-Browser field
564    pub fn vcs_svn(&self) -> Option<String> {
565        self.0.get("Vcs-Svn").map(|s| s.to_string())
566    }
567
568    /// Set the Vcs-Svn field
569    pub fn set_vcs_svn(&mut self, url: &str) {
570        self.set("Vcs-Svn", url);
571    }
572
573    /// Return the Vcs-Bzr field
574    pub fn vcs_bzr(&self) -> Option<String> {
575        self.0.get("Vcs-Bzr").map(|s| s.to_string())
576    }
577
578    /// Set the Vcs-Bzr field
579    pub fn set_vcs_bzr(&mut self, url: &str) {
580        self.set("Vcs-Bzr", url);
581    }
582
583    /// Return the Vcs-Arch field
584    pub fn vcs_arch(&self) -> Option<String> {
585        self.0.get("Vcs-Arch").map(|s| s.to_string())
586    }
587
588    /// Set the Vcs-Arch field
589    pub fn set_vcs_arch(&mut self, url: &str) {
590        self.set("Vcs-Arch", url);
591    }
592
593    /// Return the Vcs-Svk field
594    pub fn vcs_svk(&self) -> Option<String> {
595        self.0.get("Vcs-Svk").map(|s| s.to_string())
596    }
597
598    /// Set the Vcs-Svk field
599    pub fn set_vcs_svk(&mut self, url: &str) {
600        self.set("Vcs-Svk", url);
601    }
602
603    /// Return the Vcs-Darcs field
604    pub fn vcs_darcs(&self) -> Option<String> {
605        self.0.get("Vcs-Darcs").map(|s| s.to_string())
606    }
607
608    /// Set the Vcs-Darcs field
609    pub fn set_vcs_darcs(&mut self, url: &str) {
610        self.set("Vcs-Darcs", url);
611    }
612
613    /// Return the Vcs-Mtn field
614    pub fn vcs_mtn(&self) -> Option<String> {
615        self.0.get("Vcs-Mtn").map(|s| s.to_string())
616    }
617
618    /// Set the Vcs-Mtn field
619    pub fn set_vcs_mtn(&mut self, url: &str) {
620        self.set("Vcs-Mtn", url);
621    }
622
623    /// Return the Vcs-Cvs field
624    pub fn vcs_cvs(&self) -> Option<String> {
625        self.0.get("Vcs-Cvs").map(|s| s.to_string())
626    }
627
628    /// Set the Vcs-Cvs field
629    pub fn set_vcs_cvs(&mut self, url: &str) {
630        self.set("Vcs-Cvs", url);
631    }
632
633    /// Return the Vcs-Hg field
634    pub fn vcs_hg(&self) -> Option<String> {
635        self.0.get("Vcs-Hg").map(|s| s.to_string())
636    }
637
638    /// Set the Vcs-Hg field
639    pub fn set_vcs_hg(&mut self, url: &str) {
640        self.set("Vcs-Hg", url);
641    }
642
643    /// Set a field in the source paragraph, using canonical field ordering for source packages
644    pub fn set(&mut self, key: &str, value: &str) {
645        self.0.set_with_field_order(key, value, SOURCE_FIELD_ORDER);
646    }
647
648    /// Return the Vcs-Browser field
649    pub fn vcs_browser(&self) -> Option<String> {
650        self.0.get("Vcs-Browser")
651    }
652
653    /// Return the Vcs used by the package
654    pub fn vcs(&self) -> Option<crate::vcs::Vcs> {
655        for (name, value) in self.0.items() {
656            if name.starts_with("Vcs-") && name != "Vcs-Browser" {
657                return crate::vcs::Vcs::from_field(&name, &value).ok();
658            }
659        }
660        None
661    }
662
663    /// Set the Vcs-Browser field
664    pub fn set_vcs_browser(&mut self, url: Option<&str>) {
665        if let Some(url) = url {
666            self.set("Vcs-Browser", url);
667        } else {
668            self.0.remove("Vcs-Browser");
669        }
670    }
671
672    /// Return the Uploaders field
673    pub fn uploaders(&self) -> Option<Vec<String>> {
674        self.0
675            .get("Uploaders")
676            .map(|s| s.split(',').map(|s| s.trim().to_owned()).collect())
677    }
678
679    /// Set the uploaders field
680    pub fn set_uploaders(&mut self, uploaders: &[&str]) {
681        self.set(
682            "Uploaders",
683            uploaders
684                .iter()
685                .map(|s| s.to_string())
686                .collect::<Vec<_>>()
687                .join(", ")
688                .as_str(),
689        );
690    }
691
692    /// Return the architecture field
693    pub fn architecture(&self) -> Option<String> {
694        self.0.get("Architecture")
695    }
696
697    /// Set the architecture field
698    pub fn set_architecture(&mut self, arch: Option<&str>) {
699        if let Some(arch) = arch {
700            self.set("Architecture", arch);
701        } else {
702            self.0.remove("Architecture");
703        }
704    }
705
706    /// Return the Rules-Requires-Root field
707    pub fn rules_requires_root(&self) -> Option<bool> {
708        self.0
709            .get("Rules-Requires-Root")
710            .map(|s| match s.to_lowercase().as_str() {
711                "yes" => true,
712                "no" => false,
713                _ => panic!("invalid Rules-Requires-Root value"),
714            })
715    }
716
717    /// Set the Rules-Requires-Root field
718    pub fn set_rules_requires_root(&mut self, requires_root: bool) {
719        self.set(
720            "Rules-Requires-Root",
721            if requires_root { "yes" } else { "no" },
722        );
723    }
724
725    /// Return the Testsuite field
726    pub fn testsuite(&self) -> Option<String> {
727        self.0.get("Testsuite")
728    }
729
730    /// Set the Testsuite field
731    pub fn set_testsuite(&mut self, testsuite: &str) {
732        self.set("Testsuite", testsuite);
733    }
734
735    /// Check if this source paragraph's range overlaps with the given range
736    ///
737    /// # Arguments
738    /// * `range` - The text range to check for overlap
739    ///
740    /// # Returns
741    /// `true` if the paragraph overlaps with the given range, `false` otherwise
742    pub fn overlaps_range(&self, range: rowan::TextRange) -> bool {
743        let para_range = self.0.syntax().text_range();
744        para_range.start() < range.end() && range.start() < para_range.end()
745    }
746
747    /// Get fields in this source paragraph that overlap with the given range
748    ///
749    /// # Arguments
750    /// * `range` - The text range to check for overlaps
751    ///
752    /// # Returns
753    /// An iterator over Entry items that overlap with the given range
754    pub fn fields_in_range(
755        &self,
756        range: rowan::TextRange,
757    ) -> impl Iterator<Item = deb822_lossless::Entry> + '_ {
758        self.0.entries().filter(move |entry| {
759            let entry_range = entry.syntax().text_range();
760            entry_range.start() < range.end() && range.start() < entry_range.end()
761        })
762    }
763}
764
765#[cfg(feature = "python-debian")]
766impl<'py> pyo3::IntoPyObject<'py> for Source {
767    type Target = pyo3::PyAny;
768    type Output = pyo3::Bound<'py, Self::Target>;
769    type Error = pyo3::PyErr;
770
771    fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
772        self.0.into_pyobject(py)
773    }
774}
775
776#[cfg(feature = "python-debian")]
777impl<'a, 'py> pyo3::IntoPyObject<'py> for &'a Source {
778    type Target = pyo3::PyAny;
779    type Output = pyo3::Bound<'py, Self::Target>;
780    type Error = pyo3::PyErr;
781
782    fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
783        (&self.0).into_pyobject(py)
784    }
785}
786
787#[cfg(feature = "python-debian")]
788impl<'py> pyo3::FromPyObject<'_, 'py> for Source {
789    type Error = pyo3::PyErr;
790
791    fn extract(ob: pyo3::Borrowed<'_, 'py, pyo3::PyAny>) -> Result<Self, Self::Error> {
792        Ok(Source(ob.extract()?))
793    }
794}
795
796impl std::fmt::Display for Control {
797    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
798        self.0.fmt(f)
799    }
800}
801
802impl AstNode for Control {
803    type Language = deb822_lossless::Lang;
804
805    fn can_cast(kind: <Self::Language as rowan::Language>::Kind) -> bool {
806        Deb822::can_cast(kind)
807    }
808
809    fn cast(syntax: rowan::SyntaxNode<Self::Language>) -> Option<Self> {
810        Deb822::cast(syntax).map(Control)
811    }
812
813    fn syntax(&self) -> &rowan::SyntaxNode<Self::Language> {
814        self.0.syntax()
815    }
816}
817
818/// A binary package paragraph
819#[derive(Debug, Clone, PartialEq, Eq)]
820pub struct Binary(Paragraph);
821
822impl From<Binary> for Paragraph {
823    fn from(b: Binary) -> Self {
824        b.0
825    }
826}
827
828impl From<Paragraph> for Binary {
829    fn from(p: Paragraph) -> Self {
830        Binary(p)
831    }
832}
833
834#[cfg(feature = "python-debian")]
835impl<'py> pyo3::IntoPyObject<'py> for Binary {
836    type Target = pyo3::PyAny;
837    type Output = pyo3::Bound<'py, Self::Target>;
838    type Error = pyo3::PyErr;
839
840    fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
841        self.0.into_pyobject(py)
842    }
843}
844
845#[cfg(feature = "python-debian")]
846impl<'a, 'py> pyo3::IntoPyObject<'py> for &'a Binary {
847    type Target = pyo3::PyAny;
848    type Output = pyo3::Bound<'py, Self::Target>;
849    type Error = pyo3::PyErr;
850
851    fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
852        (&self.0).into_pyobject(py)
853    }
854}
855
856#[cfg(feature = "python-debian")]
857impl<'py> pyo3::FromPyObject<'_, 'py> for Binary {
858    type Error = pyo3::PyErr;
859
860    fn extract(ob: pyo3::Borrowed<'_, 'py, pyo3::PyAny>) -> Result<Self, Self::Error> {
861        Ok(Binary(ob.extract()?))
862    }
863}
864
865impl Default for Binary {
866    fn default() -> Self {
867        Self::new()
868    }
869}
870
871impl std::fmt::Display for Binary {
872    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
873        self.0.fmt(f)
874    }
875}
876
877impl Binary {
878    /// Create a new binary package control file
879    pub fn new() -> Self {
880        Binary(Paragraph::new())
881    }
882
883    /// Return the underlying deb822 paragraph, mutable
884    pub fn as_mut_deb822(&mut self) -> &mut Paragraph {
885        &mut self.0
886    }
887
888    /// Return the underlying deb822 paragraph
889    pub fn as_deb822(&self) -> &Paragraph {
890        &self.0
891    }
892
893    /// Wrap and sort the control file
894    pub fn wrap_and_sort(
895        &mut self,
896        indentation: deb822_lossless::Indentation,
897        immediate_empty_line: bool,
898        max_line_length_one_liner: Option<usize>,
899    ) {
900        self.0 = self.0.wrap_and_sort(
901            indentation,
902            immediate_empty_line,
903            max_line_length_one_liner,
904            None,
905            Some(&format_field),
906        );
907    }
908
909    /// The name of the package.
910    pub fn name(&self) -> Option<String> {
911        self.0.get("Package")
912    }
913
914    /// Set the name of the package
915    pub fn set_name(&mut self, name: &str) {
916        self.set("Package", name);
917    }
918
919    /// The section of the package.
920    pub fn section(&self) -> Option<String> {
921        self.0.get("Section")
922    }
923
924    /// Set the section
925    pub fn set_section(&mut self, section: Option<&str>) {
926        if let Some(section) = section {
927            self.set("Section", section);
928        } else {
929            self.0.remove("Section");
930        }
931    }
932
933    /// The priority of the package.
934    pub fn priority(&self) -> Option<Priority> {
935        self.0.get("Priority").and_then(|v| v.parse().ok())
936    }
937
938    /// Set the priority of the package
939    pub fn set_priority(&mut self, priority: Option<Priority>) {
940        if let Some(priority) = priority {
941            self.set("Priority", priority.to_string().as_str());
942        } else {
943            self.0.remove("Priority");
944        }
945    }
946
947    /// The architecture of the package.
948    pub fn architecture(&self) -> Option<String> {
949        self.0.get("Architecture")
950    }
951
952    /// Set the architecture of the package
953    pub fn set_architecture(&mut self, arch: Option<&str>) {
954        if let Some(arch) = arch {
955            self.set("Architecture", arch);
956        } else {
957            self.0.remove("Architecture");
958        }
959    }
960
961    /// The dependencies of the package.
962    pub fn depends(&self) -> Option<Relations> {
963        self.0.get("Depends").map(|s| s.parse().unwrap())
964    }
965
966    /// Set the Depends field
967    pub fn set_depends(&mut self, depends: Option<&Relations>) {
968        if let Some(depends) = depends {
969            self.set("Depends", depends.to_string().as_str());
970        } else {
971            self.0.remove("Depends");
972        }
973    }
974
975    /// The package that this package recommends
976    pub fn recommends(&self) -> Option<Relations> {
977        self.0.get("Recommends").map(|s| s.parse().unwrap())
978    }
979
980    /// Set the Recommends field
981    pub fn set_recommends(&mut self, recommends: Option<&Relations>) {
982        if let Some(recommends) = recommends {
983            self.set("Recommends", recommends.to_string().as_str());
984        } else {
985            self.0.remove("Recommends");
986        }
987    }
988
989    /// Packages that this package suggests
990    pub fn suggests(&self) -> Option<Relations> {
991        self.0.get("Suggests").map(|s| s.parse().unwrap())
992    }
993
994    /// Set the Suggests field
995    pub fn set_suggests(&mut self, suggests: Option<&Relations>) {
996        if let Some(suggests) = suggests {
997            self.set("Suggests", suggests.to_string().as_str());
998        } else {
999            self.0.remove("Suggests");
1000        }
1001    }
1002
1003    /// The package that this package enhances
1004    pub fn enhances(&self) -> Option<Relations> {
1005        self.0.get("Enhances").map(|s| s.parse().unwrap())
1006    }
1007
1008    /// Set the Enhances field
1009    pub fn set_enhances(&mut self, enhances: Option<&Relations>) {
1010        if let Some(enhances) = enhances {
1011            self.set("Enhances", enhances.to_string().as_str());
1012        } else {
1013            self.0.remove("Enhances");
1014        }
1015    }
1016
1017    /// The package that this package pre-depends on
1018    pub fn pre_depends(&self) -> Option<Relations> {
1019        self.0.get("Pre-Depends").map(|s| s.parse().unwrap())
1020    }
1021
1022    /// Set the Pre-Depends field
1023    pub fn set_pre_depends(&mut self, pre_depends: Option<&Relations>) {
1024        if let Some(pre_depends) = pre_depends {
1025            self.set("Pre-Depends", pre_depends.to_string().as_str());
1026        } else {
1027            self.0.remove("Pre-Depends");
1028        }
1029    }
1030
1031    /// The package that this package breaks
1032    pub fn breaks(&self) -> Option<Relations> {
1033        self.0.get("Breaks").map(|s| s.parse().unwrap())
1034    }
1035
1036    /// Set the Breaks field
1037    pub fn set_breaks(&mut self, breaks: Option<&Relations>) {
1038        if let Some(breaks) = breaks {
1039            self.set("Breaks", breaks.to_string().as_str());
1040        } else {
1041            self.0.remove("Breaks");
1042        }
1043    }
1044
1045    /// The package that this package conflicts with
1046    pub fn conflicts(&self) -> Option<Relations> {
1047        self.0.get("Conflicts").map(|s| s.parse().unwrap())
1048    }
1049
1050    /// Set the Conflicts field
1051    pub fn set_conflicts(&mut self, conflicts: Option<&Relations>) {
1052        if let Some(conflicts) = conflicts {
1053            self.set("Conflicts", conflicts.to_string().as_str());
1054        } else {
1055            self.0.remove("Conflicts");
1056        }
1057    }
1058
1059    /// The package that this package replaces
1060    pub fn replaces(&self) -> Option<Relations> {
1061        self.0.get("Replaces").map(|s| s.parse().unwrap())
1062    }
1063
1064    /// Set the Replaces field
1065    pub fn set_replaces(&mut self, replaces: Option<&Relations>) {
1066        if let Some(replaces) = replaces {
1067            self.set("Replaces", replaces.to_string().as_str());
1068        } else {
1069            self.0.remove("Replaces");
1070        }
1071    }
1072
1073    /// Return the Provides field
1074    pub fn provides(&self) -> Option<Relations> {
1075        self.0.get("Provides").map(|s| s.parse().unwrap())
1076    }
1077
1078    /// Set the Provides field
1079    pub fn set_provides(&mut self, provides: Option<&Relations>) {
1080        if let Some(provides) = provides {
1081            self.set("Provides", provides.to_string().as_str());
1082        } else {
1083            self.0.remove("Provides");
1084        }
1085    }
1086
1087    /// Return the Built-Using field
1088    pub fn built_using(&self) -> Option<Relations> {
1089        self.0.get("Built-Using").map(|s| s.parse().unwrap())
1090    }
1091
1092    /// Set the Built-Using field
1093    pub fn set_built_using(&mut self, built_using: Option<&Relations>) {
1094        if let Some(built_using) = built_using {
1095            self.set("Built-Using", built_using.to_string().as_str());
1096        } else {
1097            self.0.remove("Built-Using");
1098        }
1099    }
1100
1101    /// The Multi-Arch field
1102    pub fn multi_arch(&self) -> Option<MultiArch> {
1103        self.0.get("Multi-Arch").map(|s| s.parse().unwrap())
1104    }
1105
1106    /// Set the Multi-Arch field
1107    pub fn set_multi_arch(&mut self, multi_arch: Option<MultiArch>) {
1108        if let Some(multi_arch) = multi_arch {
1109            self.set("Multi-Arch", multi_arch.to_string().as_str());
1110        } else {
1111            self.0.remove("Multi-Arch");
1112        }
1113    }
1114
1115    /// Whether the package is essential
1116    pub fn essential(&self) -> bool {
1117        self.0.get("Essential").map(|s| s == "yes").unwrap_or(false)
1118    }
1119
1120    /// Set whether the package is essential
1121    pub fn set_essential(&mut self, essential: bool) {
1122        if essential {
1123            self.set("Essential", "yes");
1124        } else {
1125            self.0.remove("Essential");
1126        }
1127    }
1128
1129    /// Binary package description
1130    pub fn description(&self) -> Option<String> {
1131        self.0.get("Description")
1132    }
1133
1134    /// Set the binary package description
1135    pub fn set_description(&mut self, description: Option<&str>) {
1136        if let Some(description) = description {
1137            self.0.set_with_indent_pattern(
1138                "Description",
1139                description,
1140                Some(&deb822_lossless::IndentPattern::Fixed(1)),
1141                Some(BINARY_FIELD_ORDER),
1142            );
1143        } else {
1144            self.0.remove("Description");
1145        }
1146    }
1147
1148    /// Return the upstream homepage
1149    pub fn homepage(&self) -> Option<url::Url> {
1150        self.0.get("Homepage").and_then(|s| s.parse().ok())
1151    }
1152
1153    /// Set the upstream homepage
1154    pub fn set_homepage(&mut self, url: &url::Url) {
1155        self.set("Homepage", url.as_str());
1156    }
1157
1158    /// Set a field in the binary paragraph, using canonical field ordering for binary packages
1159    pub fn set(&mut self, key: &str, value: &str) {
1160        self.0.set_with_field_order(key, value, BINARY_FIELD_ORDER);
1161    }
1162
1163    /// Check if this binary paragraph's range overlaps with the given range
1164    ///
1165    /// # Arguments
1166    /// * `range` - The text range to check for overlap
1167    ///
1168    /// # Returns
1169    /// `true` if the paragraph overlaps with the given range, `false` otherwise
1170    pub fn overlaps_range(&self, range: rowan::TextRange) -> bool {
1171        let para_range = self.0.syntax().text_range();
1172        para_range.start() < range.end() && range.start() < para_range.end()
1173    }
1174
1175    /// Get fields in this binary paragraph that overlap with the given range
1176    ///
1177    /// # Arguments
1178    /// * `range` - The text range to check for overlaps
1179    ///
1180    /// # Returns
1181    /// An iterator over Entry items that overlap with the given range
1182    pub fn fields_in_range(
1183        &self,
1184        range: rowan::TextRange,
1185    ) -> impl Iterator<Item = deb822_lossless::Entry> + '_ {
1186        self.0.entries().filter(move |entry| {
1187            let entry_range = entry.syntax().text_range();
1188            entry_range.start() < range.end() && range.start() < entry_range.end()
1189        })
1190    }
1191}
1192
1193#[cfg(test)]
1194mod tests {
1195    use super::*;
1196    use crate::relations::VersionConstraint;
1197
1198    #[test]
1199    fn test_source_set_field_ordering() {
1200        let mut control = Control::new();
1201        let mut source = control.add_source("mypackage");
1202
1203        // Add fields in random order
1204        source.set("Homepage", "https://example.com");
1205        source.set("Build-Depends", "debhelper");
1206        source.set("Standards-Version", "4.5.0");
1207        source.set("Maintainer", "Test <test@example.com>");
1208
1209        // Convert to string and check field order
1210        let output = source.to_string();
1211        let lines: Vec<&str> = output.lines().collect();
1212
1213        // Source should be first
1214        assert!(lines[0].starts_with("Source:"));
1215
1216        // Find the positions of each field
1217        let maintainer_pos = lines
1218            .iter()
1219            .position(|l| l.starts_with("Maintainer:"))
1220            .unwrap();
1221        let build_depends_pos = lines
1222            .iter()
1223            .position(|l| l.starts_with("Build-Depends:"))
1224            .unwrap();
1225        let standards_pos = lines
1226            .iter()
1227            .position(|l| l.starts_with("Standards-Version:"))
1228            .unwrap();
1229        let homepage_pos = lines
1230            .iter()
1231            .position(|l| l.starts_with("Homepage:"))
1232            .unwrap();
1233
1234        // Check ordering according to SOURCE_FIELD_ORDER
1235        assert!(maintainer_pos < build_depends_pos);
1236        assert!(build_depends_pos < standards_pos);
1237        assert!(standards_pos < homepage_pos);
1238    }
1239
1240    #[test]
1241    fn test_binary_set_field_ordering() {
1242        let mut control = Control::new();
1243        let mut binary = control.add_binary("mypackage");
1244
1245        // Add fields in random order
1246        binary.set("Description", "A test package");
1247        binary.set("Architecture", "amd64");
1248        binary.set("Depends", "libc6");
1249        binary.set("Section", "utils");
1250
1251        // Convert to string and check field order
1252        let output = binary.to_string();
1253        let lines: Vec<&str> = output.lines().collect();
1254
1255        // Package should be first
1256        assert!(lines[0].starts_with("Package:"));
1257
1258        // Find the positions of each field
1259        let arch_pos = lines
1260            .iter()
1261            .position(|l| l.starts_with("Architecture:"))
1262            .unwrap();
1263        let section_pos = lines
1264            .iter()
1265            .position(|l| l.starts_with("Section:"))
1266            .unwrap();
1267        let depends_pos = lines
1268            .iter()
1269            .position(|l| l.starts_with("Depends:"))
1270            .unwrap();
1271        let desc_pos = lines
1272            .iter()
1273            .position(|l| l.starts_with("Description:"))
1274            .unwrap();
1275
1276        // Check ordering according to BINARY_FIELD_ORDER
1277        assert!(arch_pos < section_pos);
1278        assert!(section_pos < depends_pos);
1279        assert!(depends_pos < desc_pos);
1280    }
1281
1282    #[test]
1283    fn test_source_specific_set_methods_use_field_ordering() {
1284        let mut control = Control::new();
1285        let mut source = control.add_source("mypackage");
1286
1287        // Use specific set_* methods in random order
1288        source.set_homepage(&"https://example.com".parse().unwrap());
1289        source.set_maintainer("Test <test@example.com>");
1290        source.set_standards_version("4.5.0");
1291        source.set_vcs_git("https://github.com/example/repo");
1292
1293        // Convert to string and check field order
1294        let output = source.to_string();
1295        let lines: Vec<&str> = output.lines().collect();
1296
1297        // Find the positions of each field
1298        let source_pos = lines.iter().position(|l| l.starts_with("Source:")).unwrap();
1299        let maintainer_pos = lines
1300            .iter()
1301            .position(|l| l.starts_with("Maintainer:"))
1302            .unwrap();
1303        let standards_pos = lines
1304            .iter()
1305            .position(|l| l.starts_with("Standards-Version:"))
1306            .unwrap();
1307        let vcs_git_pos = lines
1308            .iter()
1309            .position(|l| l.starts_with("Vcs-Git:"))
1310            .unwrap();
1311        let homepage_pos = lines
1312            .iter()
1313            .position(|l| l.starts_with("Homepage:"))
1314            .unwrap();
1315
1316        // Check ordering according to SOURCE_FIELD_ORDER
1317        assert!(source_pos < maintainer_pos);
1318        assert!(maintainer_pos < standards_pos);
1319        assert!(standards_pos < vcs_git_pos);
1320        assert!(vcs_git_pos < homepage_pos);
1321    }
1322
1323    #[test]
1324    fn test_binary_specific_set_methods_use_field_ordering() {
1325        let mut control = Control::new();
1326        let mut binary = control.add_binary("mypackage");
1327
1328        // Use specific set_* methods in random order
1329        binary.set_description(Some("A test package"));
1330        binary.set_architecture(Some("amd64"));
1331        let depends = "libc6".parse().unwrap();
1332        binary.set_depends(Some(&depends));
1333        binary.set_section(Some("utils"));
1334        binary.set_priority(Some(Priority::Optional));
1335
1336        // Convert to string and check field order
1337        let output = binary.to_string();
1338        let lines: Vec<&str> = output.lines().collect();
1339
1340        // Find the positions of each field
1341        let package_pos = lines
1342            .iter()
1343            .position(|l| l.starts_with("Package:"))
1344            .unwrap();
1345        let arch_pos = lines
1346            .iter()
1347            .position(|l| l.starts_with("Architecture:"))
1348            .unwrap();
1349        let section_pos = lines
1350            .iter()
1351            .position(|l| l.starts_with("Section:"))
1352            .unwrap();
1353        let priority_pos = lines
1354            .iter()
1355            .position(|l| l.starts_with("Priority:"))
1356            .unwrap();
1357        let depends_pos = lines
1358            .iter()
1359            .position(|l| l.starts_with("Depends:"))
1360            .unwrap();
1361        let desc_pos = lines
1362            .iter()
1363            .position(|l| l.starts_with("Description:"))
1364            .unwrap();
1365
1366        // Check ordering according to BINARY_FIELD_ORDER
1367        assert!(package_pos < arch_pos);
1368        assert!(arch_pos < section_pos);
1369        assert!(section_pos < priority_pos);
1370        assert!(priority_pos < depends_pos);
1371        assert!(depends_pos < desc_pos);
1372    }
1373
1374    #[test]
1375    fn test_parse() {
1376        let control: Control = r#"Source: foo
1377Section: libs
1378Priority: optional
1379Build-Depends: bar (>= 1.0.0), baz (>= 1.0.0)
1380Homepage: https://example.com
1381
1382"#
1383        .parse()
1384        .unwrap();
1385        let source = control.source().unwrap();
1386
1387        assert_eq!(source.name(), Some("foo".to_owned()));
1388        assert_eq!(source.section(), Some("libs".to_owned()));
1389        assert_eq!(source.priority(), Some(super::Priority::Optional));
1390        assert_eq!(
1391            source.homepage(),
1392            Some("https://example.com".parse().unwrap())
1393        );
1394        let bd = source.build_depends().unwrap();
1395        let entries = bd.entries().collect::<Vec<_>>();
1396        assert_eq!(entries.len(), 2);
1397        let rel = entries[0].relations().collect::<Vec<_>>().pop().unwrap();
1398        assert_eq!(rel.name(), "bar");
1399        assert_eq!(
1400            rel.version(),
1401            Some((
1402                VersionConstraint::GreaterThanEqual,
1403                "1.0.0".parse().unwrap()
1404            ))
1405        );
1406        let rel = entries[1].relations().collect::<Vec<_>>().pop().unwrap();
1407        assert_eq!(rel.name(), "baz");
1408        assert_eq!(
1409            rel.version(),
1410            Some((
1411                VersionConstraint::GreaterThanEqual,
1412                "1.0.0".parse().unwrap()
1413            ))
1414        );
1415    }
1416
1417    #[test]
1418    fn test_description() {
1419        let control: Control = r#"Source: foo
1420
1421Package: foo
1422Description: this is the short description
1423 And the longer one
1424 .
1425 is on the next lines
1426"#
1427        .parse()
1428        .unwrap();
1429        let binary = control.binaries().next().unwrap();
1430        assert_eq!(
1431            binary.description(),
1432            Some(
1433                "this is the short description\nAnd the longer one\n.\nis on the next lines"
1434                    .to_owned()
1435            )
1436        );
1437    }
1438
1439    #[test]
1440    fn test_set_description_on_package_without_description() {
1441        let control: Control = r#"Source: foo
1442
1443Package: foo
1444Architecture: amd64
1445"#
1446        .parse()
1447        .unwrap();
1448        let mut binary = control.binaries().next().unwrap();
1449
1450        // Set description on a binary that doesn't have one
1451        binary.set_description(Some("Short description\nLonger description\n.\nAnother line"));
1452
1453        let output = binary.to_string();
1454
1455        // Check that the description was set
1456        assert_eq!(
1457            binary.description(),
1458            Some("Short description\nLonger description\n.\nAnother line".to_owned())
1459        );
1460
1461        // Verify the output format has exactly one space indent
1462        assert_eq!(
1463            output,
1464            "Package: foo\nArchitecture: amd64\nDescription: Short description\n Longer description\n .\n Another line\n"
1465        );
1466    }
1467
1468    #[test]
1469    fn test_as_mut_deb822() {
1470        let mut control = Control::new();
1471        let deb822 = control.as_mut_deb822();
1472        let mut p = deb822.add_paragraph();
1473        p.set("Source", "foo");
1474        assert_eq!(control.source().unwrap().name(), Some("foo".to_owned()));
1475    }
1476
1477    #[test]
1478    fn test_as_deb822() {
1479        let control = Control::new();
1480        let _deb822: &Deb822 = control.as_deb822();
1481    }
1482
1483    #[test]
1484    fn test_set_depends() {
1485        let mut control = Control::new();
1486        let mut binary = control.add_binary("foo");
1487        let relations: Relations = "bar (>= 1.0.0)".parse().unwrap();
1488        binary.set_depends(Some(&relations));
1489    }
1490
1491    #[test]
1492    fn test_wrap_and_sort() {
1493        let mut control: Control = r#"Package: blah
1494Section:     libs
1495
1496
1497
1498Package: foo
1499Description: this is a 
1500      bar
1501      blah
1502"#
1503        .parse()
1504        .unwrap();
1505        control.wrap_and_sort(deb822_lossless::Indentation::Spaces(2), false, None);
1506        let expected = r#"Package: blah
1507Section: libs
1508
1509Package: foo
1510Description: this is a 
1511  bar
1512  blah
1513"#
1514        .to_owned();
1515        assert_eq!(control.to_string(), expected);
1516    }
1517
1518    #[test]
1519    fn test_wrap_and_sort_source() {
1520        let mut control: Control = r#"Source: blah
1521Depends: foo, bar   (<=  1.0.0)
1522
1523"#
1524        .parse()
1525        .unwrap();
1526        control.wrap_and_sort(deb822_lossless::Indentation::Spaces(2), true, None);
1527        let expected = r#"Source: blah
1528Depends: bar (<= 1.0.0), foo
1529"#
1530        .to_owned();
1531        assert_eq!(control.to_string(), expected);
1532    }
1533
1534    #[test]
1535    fn test_source_wrap_and_sort() {
1536        let control: Control = r#"Source: blah
1537Build-Depends: foo, bar (>= 1.0.0)
1538
1539"#
1540        .parse()
1541        .unwrap();
1542        let mut source = control.source().unwrap();
1543        source.wrap_and_sort(deb822_lossless::Indentation::Spaces(2), true, None);
1544        // The actual behavior - the method modifies the source in-place
1545        // but doesn't automatically affect the overall control structure
1546        // So we just test that the method executes without error
1547        assert!(source.build_depends().is_some());
1548    }
1549
1550    #[test]
1551    fn test_binary_set_breaks() {
1552        let mut control = Control::new();
1553        let mut binary = control.add_binary("foo");
1554        let relations: Relations = "bar (>= 1.0.0)".parse().unwrap();
1555        binary.set_breaks(Some(&relations));
1556        assert!(binary.breaks().is_some());
1557    }
1558
1559    #[test]
1560    fn test_binary_set_pre_depends() {
1561        let mut control = Control::new();
1562        let mut binary = control.add_binary("foo");
1563        let relations: Relations = "bar (>= 1.0.0)".parse().unwrap();
1564        binary.set_pre_depends(Some(&relations));
1565        assert!(binary.pre_depends().is_some());
1566    }
1567
1568    #[test]
1569    fn test_binary_set_provides() {
1570        let mut control = Control::new();
1571        let mut binary = control.add_binary("foo");
1572        let relations: Relations = "bar (>= 1.0.0)".parse().unwrap();
1573        binary.set_provides(Some(&relations));
1574        assert!(binary.provides().is_some());
1575    }
1576
1577    #[test]
1578    fn test_source_build_conflicts() {
1579        let control: Control = r#"Source: blah
1580Build-Conflicts: foo, bar (>= 1.0.0)
1581
1582"#
1583        .parse()
1584        .unwrap();
1585        let source = control.source().unwrap();
1586        let conflicts = source.build_conflicts();
1587        assert!(conflicts.is_some());
1588    }
1589
1590    #[test]
1591    fn test_source_vcs_svn() {
1592        let control: Control = r#"Source: blah
1593Vcs-Svn: https://example.com/svn/repo
1594
1595"#
1596        .parse()
1597        .unwrap();
1598        let source = control.source().unwrap();
1599        assert_eq!(
1600            source.vcs_svn(),
1601            Some("https://example.com/svn/repo".to_string())
1602        );
1603    }
1604
1605    #[test]
1606    fn test_control_from_conversion() {
1607        let deb822_data = r#"Source: test
1608Section: libs
1609
1610"#;
1611        let deb822: Deb822 = deb822_data.parse().unwrap();
1612        let control = Control::from(deb822);
1613        assert!(control.source().is_some());
1614    }
1615
1616    #[test]
1617    fn test_fields_in_range() {
1618        let control_text = r#"Source: test-package
1619Maintainer: Test User <test@example.com>
1620Build-Depends: debhelper (>= 12)
1621
1622Package: test-binary
1623Architecture: any
1624Depends: ${shlibs:Depends}
1625Description: Test package
1626 This is a test package
1627"#;
1628        let control: Control = control_text.parse().unwrap();
1629
1630        // Test range that covers only the Source field
1631        let source_start = 0;
1632        let source_end = "Source: test-package".len();
1633        let source_range =
1634            rowan::TextRange::new((source_start as u32).into(), (source_end as u32).into());
1635
1636        let fields: Vec<_> = control.fields_in_range(source_range).collect();
1637        assert_eq!(fields.len(), 1);
1638        assert_eq!(fields[0].key(), Some("Source".to_string()));
1639
1640        // Test range that covers multiple fields in source paragraph
1641        let maintainer_start = control_text.find("Maintainer:").unwrap();
1642        let build_depends_end = control_text
1643            .find("Build-Depends: debhelper (>= 12)")
1644            .unwrap()
1645            + "Build-Depends: debhelper (>= 12)".len();
1646        let multi_range = rowan::TextRange::new(
1647            (maintainer_start as u32).into(),
1648            (build_depends_end as u32).into(),
1649        );
1650
1651        let fields: Vec<_> = control.fields_in_range(multi_range).collect();
1652        assert_eq!(fields.len(), 2);
1653        assert_eq!(fields[0].key(), Some("Maintainer".to_string()));
1654        assert_eq!(fields[1].key(), Some("Build-Depends".to_string()));
1655
1656        // Test range that spans across paragraphs
1657        let cross_para_start = control_text.find("Build-Depends:").unwrap();
1658        let cross_para_end =
1659            control_text.find("Architecture: any").unwrap() + "Architecture: any".len();
1660        let cross_range = rowan::TextRange::new(
1661            (cross_para_start as u32).into(),
1662            (cross_para_end as u32).into(),
1663        );
1664
1665        let fields: Vec<_> = control.fields_in_range(cross_range).collect();
1666        assert_eq!(fields.len(), 3); // Build-Depends, Package, Architecture
1667        assert_eq!(fields[0].key(), Some("Build-Depends".to_string()));
1668        assert_eq!(fields[1].key(), Some("Package".to_string()));
1669        assert_eq!(fields[2].key(), Some("Architecture".to_string()));
1670
1671        // Test empty range (should return no fields)
1672        let empty_range = rowan::TextRange::new(1000.into(), 1001.into());
1673        let fields: Vec<_> = control.fields_in_range(empty_range).collect();
1674        assert_eq!(fields.len(), 0);
1675    }
1676
1677    #[test]
1678    fn test_source_overlaps_range() {
1679        let control_text = r#"Source: test-package
1680Maintainer: Test User <test@example.com>
1681
1682Package: test-binary
1683Architecture: any
1684"#;
1685        let control: Control = control_text.parse().unwrap();
1686        let source = control.source().unwrap();
1687
1688        // Test range that overlaps with source paragraph
1689        let overlap_range = rowan::TextRange::new(10.into(), 30.into());
1690        assert!(source.overlaps_range(overlap_range));
1691
1692        // Test range that doesn't overlap with source paragraph
1693        let binary_start = control_text.find("Package:").unwrap();
1694        let no_overlap_range = rowan::TextRange::new(
1695            (binary_start as u32).into(),
1696            ((binary_start + 20) as u32).into(),
1697        );
1698        assert!(!source.overlaps_range(no_overlap_range));
1699
1700        // Test range that starts before and ends within source paragraph
1701        let partial_overlap = rowan::TextRange::new(0.into(), 15.into());
1702        assert!(source.overlaps_range(partial_overlap));
1703    }
1704
1705    #[test]
1706    fn test_source_fields_in_range() {
1707        let control_text = r#"Source: test-package
1708Maintainer: Test User <test@example.com>
1709Build-Depends: debhelper (>= 12)
1710
1711Package: test-binary
1712"#;
1713        let control: Control = control_text.parse().unwrap();
1714        let source = control.source().unwrap();
1715
1716        // Test range covering Maintainer field
1717        let maintainer_start = control_text.find("Maintainer:").unwrap();
1718        let maintainer_end = maintainer_start + "Maintainer: Test User <test@example.com>".len();
1719        let maintainer_range = rowan::TextRange::new(
1720            (maintainer_start as u32).into(),
1721            (maintainer_end as u32).into(),
1722        );
1723
1724        let fields: Vec<_> = source.fields_in_range(maintainer_range).collect();
1725        assert_eq!(fields.len(), 1);
1726        assert_eq!(fields[0].key(), Some("Maintainer".to_string()));
1727
1728        // Test range covering multiple fields
1729        let all_source_range = rowan::TextRange::new(0.into(), 100.into());
1730        let fields: Vec<_> = source.fields_in_range(all_source_range).collect();
1731        assert_eq!(fields.len(), 3); // Source, Maintainer, Build-Depends
1732    }
1733
1734    #[test]
1735    fn test_binary_overlaps_range() {
1736        let control_text = r#"Source: test-package
1737
1738Package: test-binary
1739Architecture: any
1740Depends: ${shlibs:Depends}
1741"#;
1742        let control: Control = control_text.parse().unwrap();
1743        let binary = control.binaries().next().unwrap();
1744
1745        // Test range that overlaps with binary paragraph
1746        let package_start = control_text.find("Package:").unwrap();
1747        let overlap_range = rowan::TextRange::new(
1748            (package_start as u32).into(),
1749            ((package_start + 30) as u32).into(),
1750        );
1751        assert!(binary.overlaps_range(overlap_range));
1752
1753        // Test range before binary paragraph
1754        let no_overlap_range = rowan::TextRange::new(0.into(), 10.into());
1755        assert!(!binary.overlaps_range(no_overlap_range));
1756    }
1757
1758    #[test]
1759    fn test_binary_fields_in_range() {
1760        let control_text = r#"Source: test-package
1761
1762Package: test-binary
1763Architecture: any
1764Depends: ${shlibs:Depends}
1765Description: Test binary
1766 This is a test binary package
1767"#;
1768        let control: Control = control_text.parse().unwrap();
1769        let binary = control.binaries().next().unwrap();
1770
1771        // Test range covering Architecture and Depends
1772        let arch_start = control_text.find("Architecture:").unwrap();
1773        let depends_end = control_text.find("Depends: ${shlibs:Depends}").unwrap()
1774            + "Depends: ${shlibs:Depends}".len();
1775        let range = rowan::TextRange::new((arch_start as u32).into(), (depends_end as u32).into());
1776
1777        let fields: Vec<_> = binary.fields_in_range(range).collect();
1778        assert_eq!(fields.len(), 2);
1779        assert_eq!(fields[0].key(), Some("Architecture".to_string()));
1780        assert_eq!(fields[1].key(), Some("Depends".to_string()));
1781
1782        // Test partial overlap with Description field
1783        let desc_start = control_text.find("Description:").unwrap();
1784        let partial_range = rowan::TextRange::new(
1785            ((desc_start + 5) as u32).into(),
1786            ((desc_start + 15) as u32).into(),
1787        );
1788        let fields: Vec<_> = binary.fields_in_range(partial_range).collect();
1789        assert_eq!(fields.len(), 1);
1790        assert_eq!(fields[0].key(), Some("Description".to_string()));
1791    }
1792
1793    #[test]
1794    fn test_incremental_parsing_use_case() {
1795        // This test simulates a real LSP use case where only changed fields are processed
1796        let control_text = r#"Source: example
1797Maintainer: John Doe <john@example.com>
1798Standards-Version: 4.6.0
1799Build-Depends: debhelper-compat (= 13)
1800
1801Package: example-bin
1802Architecture: all
1803Depends: ${misc:Depends}
1804Description: Example package
1805 This is an example.
1806"#;
1807        let control: Control = control_text.parse().unwrap();
1808
1809        // Simulate a change to Standards-Version field
1810        let change_start = control_text.find("Standards-Version:").unwrap();
1811        let change_end = change_start + "Standards-Version: 4.6.0".len();
1812        let change_range =
1813            rowan::TextRange::new((change_start as u32).into(), (change_end as u32).into());
1814
1815        // Only process fields in the changed range
1816        let affected_fields: Vec<_> = control.fields_in_range(change_range).collect();
1817        assert_eq!(affected_fields.len(), 1);
1818        assert_eq!(
1819            affected_fields[0].key(),
1820            Some("Standards-Version".to_string())
1821        );
1822
1823        // Verify that we're not processing unrelated fields
1824        for entry in &affected_fields {
1825            let key = entry.key().unwrap();
1826            assert_ne!(key, "Maintainer");
1827            assert_ne!(key, "Build-Depends");
1828            assert_ne!(key, "Architecture");
1829        }
1830    }
1831
1832    #[test]
1833    fn test_positioned_parse_errors() {
1834        // Test case from the requirements document
1835        let input = "Invalid: field\nBroken field without colon";
1836        let parsed = Control::parse(input);
1837
1838        // Should have positioned errors accessible
1839        let positioned_errors = parsed.positioned_errors();
1840        assert!(
1841            !positioned_errors.is_empty(),
1842            "Should have positioned errors"
1843        );
1844
1845        // Test that we can access error properties
1846        for error in positioned_errors {
1847            let start_offset: u32 = error.range.start().into();
1848            let end_offset: u32 = error.range.end().into();
1849
1850            // Verify we have meaningful error messages
1851            assert!(!error.message.is_empty());
1852
1853            // Verify ranges are valid
1854            assert!(start_offset <= end_offset);
1855            assert!(end_offset <= input.len() as u32);
1856
1857            // Error should have a code
1858            assert!(error.code.is_some());
1859
1860            println!(
1861                "Error at {:?}: {} (code: {:?})",
1862                error.range, error.message, error.code
1863            );
1864        }
1865
1866        // Should also be able to get string errors for backward compatibility
1867        let string_errors = parsed.errors();
1868        assert!(!string_errors.is_empty());
1869        assert_eq!(string_errors.len(), positioned_errors.len());
1870    }
1871
1872    #[test]
1873    fn test_sort_binaries_basic() {
1874        let input = r#"Source: foo
1875
1876Package: libfoo
1877Architecture: all
1878
1879Package: libbar
1880Architecture: all
1881"#;
1882
1883        let mut control: Control = input.parse().unwrap();
1884        control.sort_binaries(false);
1885
1886        let binaries: Vec<_> = control.binaries().collect();
1887        assert_eq!(binaries.len(), 2);
1888        assert_eq!(binaries[0].name(), Some("libbar".to_string()));
1889        assert_eq!(binaries[1].name(), Some("libfoo".to_string()));
1890    }
1891
1892    #[test]
1893    fn test_sort_binaries_keep_first() {
1894        let input = r#"Source: foo
1895
1896Package: zzz-first
1897Architecture: all
1898
1899Package: libbar
1900Architecture: all
1901
1902Package: libaaa
1903Architecture: all
1904"#;
1905
1906        let mut control: Control = input.parse().unwrap();
1907        control.sort_binaries(true);
1908
1909        let binaries: Vec<_> = control.binaries().collect();
1910        assert_eq!(binaries.len(), 3);
1911        // First binary should remain in place
1912        assert_eq!(binaries[0].name(), Some("zzz-first".to_string()));
1913        // The rest should be sorted
1914        assert_eq!(binaries[1].name(), Some("libaaa".to_string()));
1915        assert_eq!(binaries[2].name(), Some("libbar".to_string()));
1916    }
1917
1918    #[test]
1919    fn test_sort_binaries_already_sorted() {
1920        let input = r#"Source: foo
1921
1922Package: aaa
1923Architecture: all
1924
1925Package: bbb
1926Architecture: all
1927
1928Package: ccc
1929Architecture: all
1930"#;
1931
1932        let mut control: Control = input.parse().unwrap();
1933        control.sort_binaries(false);
1934
1935        let binaries: Vec<_> = control.binaries().collect();
1936        assert_eq!(binaries.len(), 3);
1937        assert_eq!(binaries[0].name(), Some("aaa".to_string()));
1938        assert_eq!(binaries[1].name(), Some("bbb".to_string()));
1939        assert_eq!(binaries[2].name(), Some("ccc".to_string()));
1940    }
1941
1942    #[test]
1943    fn test_sort_binaries_no_binaries() {
1944        let input = r#"Source: foo
1945Maintainer: test@example.com
1946"#;
1947
1948        let mut control: Control = input.parse().unwrap();
1949        control.sort_binaries(false);
1950
1951        // Should not crash, just do nothing
1952        assert_eq!(control.binaries().count(), 0);
1953    }
1954
1955    #[test]
1956    fn test_sort_binaries_one_binary() {
1957        let input = r#"Source: foo
1958
1959Package: bar
1960Architecture: all
1961"#;
1962
1963        let mut control: Control = input.parse().unwrap();
1964        control.sort_binaries(false);
1965
1966        let binaries: Vec<_> = control.binaries().collect();
1967        assert_eq!(binaries.len(), 1);
1968        assert_eq!(binaries[0].name(), Some("bar".to_string()));
1969    }
1970
1971    #[test]
1972    fn test_sort_binaries_preserves_fields() {
1973        let input = r#"Source: foo
1974
1975Package: zzz
1976Architecture: any
1977Depends: libc6
1978Description: ZZZ package
1979
1980Package: aaa
1981Architecture: all
1982Depends: ${misc:Depends}
1983Description: AAA package
1984"#;
1985
1986        let mut control: Control = input.parse().unwrap();
1987        control.sort_binaries(false);
1988
1989        let binaries: Vec<_> = control.binaries().collect();
1990        assert_eq!(binaries.len(), 2);
1991
1992        // First binary should be aaa
1993        assert_eq!(binaries[0].name(), Some("aaa".to_string()));
1994        assert_eq!(binaries[0].architecture(), Some("all".to_string()));
1995        assert_eq!(binaries[0].description(), Some("AAA package".to_string()));
1996
1997        // Second binary should be zzz
1998        assert_eq!(binaries[1].name(), Some("zzz".to_string()));
1999        assert_eq!(binaries[1].architecture(), Some("any".to_string()));
2000        assert_eq!(binaries[1].description(), Some("ZZZ package".to_string()));
2001    }
2002
2003    #[test]
2004    fn test_remove_binary_basic() {
2005        let mut control = Control::new();
2006        control.add_binary("foo");
2007        assert_eq!(control.binaries().count(), 1);
2008        assert!(control.remove_binary("foo"));
2009        assert_eq!(control.binaries().count(), 0);
2010    }
2011
2012    #[test]
2013    fn test_remove_binary_nonexistent() {
2014        let mut control = Control::new();
2015        control.add_binary("foo");
2016        assert!(!control.remove_binary("bar"));
2017        assert_eq!(control.binaries().count(), 1);
2018    }
2019
2020    #[test]
2021    fn test_remove_binary_multiple() {
2022        let mut control = Control::new();
2023        control.add_binary("foo");
2024        control.add_binary("bar");
2025        control.add_binary("baz");
2026        assert_eq!(control.binaries().count(), 3);
2027
2028        assert!(control.remove_binary("bar"));
2029        assert_eq!(control.binaries().count(), 2);
2030
2031        let names: Vec<_> = control.binaries().map(|b| b.name().unwrap()).collect();
2032        assert_eq!(names, vec!["foo", "baz"]);
2033    }
2034
2035    #[test]
2036    fn test_remove_binary_preserves_source() {
2037        let input = r#"Source: mypackage
2038
2039Package: foo
2040Architecture: all
2041
2042Package: bar
2043Architecture: all
2044"#;
2045        let mut control: Control = input.parse().unwrap();
2046        assert!(control.source().is_some());
2047        assert_eq!(control.binaries().count(), 2);
2048
2049        assert!(control.remove_binary("foo"));
2050
2051        // Source should still be present
2052        assert!(control.source().is_some());
2053        assert_eq!(
2054            control.source().unwrap().name(),
2055            Some("mypackage".to_string())
2056        );
2057
2058        // Only bar should remain
2059        assert_eq!(control.binaries().count(), 1);
2060        assert_eq!(
2061            control.binaries().next().unwrap().name(),
2062            Some("bar".to_string())
2063        );
2064    }
2065
2066    #[test]
2067    fn test_remove_binary_from_parsed() {
2068        let input = r#"Source: test
2069
2070Package: test-bin
2071Architecture: any
2072Depends: libc6
2073Description: Test binary
2074
2075Package: test-lib
2076Architecture: all
2077Description: Test library
2078"#;
2079        let mut control: Control = input.parse().unwrap();
2080        assert_eq!(control.binaries().count(), 2);
2081
2082        assert!(control.remove_binary("test-bin"));
2083
2084        let output = control.to_string();
2085        assert!(!output.contains("test-bin"));
2086        assert!(output.contains("test-lib"));
2087        assert!(output.contains("Source: test"));
2088    }
2089
2090    #[test]
2091    fn test_build_depends_preserves_indentation_after_removal() {
2092        let input = r#"Source: acpi-support
2093Section: admin
2094Priority: optional
2095Maintainer: Debian Acpi Team <pkg-acpi-devel@lists.alioth.debian.org>
2096Build-Depends: debhelper (>= 10), quilt (>= 0.40),
2097    libsystemd-dev [linux-any], dh-systemd (>= 1.5), pkg-config
2098"#;
2099        let control: Control = input.parse().unwrap();
2100        let mut source = control.source().unwrap();
2101
2102        // Get the Build-Depends
2103        let mut build_depends = source.build_depends().unwrap();
2104
2105        // Find and remove dh-systemd entry
2106        let mut to_remove = Vec::new();
2107        for (idx, entry) in build_depends.entries().enumerate() {
2108            for relation in entry.relations() {
2109                if relation.name() == "dh-systemd" {
2110                    to_remove.push(idx);
2111                    break;
2112                }
2113            }
2114        }
2115
2116        for idx in to_remove.into_iter().rev() {
2117            build_depends.remove_entry(idx);
2118        }
2119
2120        // Set it back
2121        source.set_build_depends(&build_depends);
2122
2123        let output = source.to_string();
2124
2125        // The indentation should be preserved (4 spaces on the continuation line)
2126        assert!(
2127            output.contains("Build-Depends: debhelper (>= 10), quilt (>= 0.40),\n    libsystemd-dev [linux-any], pkg-config"),
2128            "Expected 4-space indentation to be preserved, but got:\n{}",
2129            output
2130        );
2131    }
2132
2133    #[test]
2134    fn test_build_depends_direct_string_set_loses_indentation() {
2135        let input = r#"Source: acpi-support
2136Section: admin
2137Priority: optional
2138Maintainer: Debian Acpi Team <pkg-acpi-devel@lists.alioth.debian.org>
2139Build-Depends: debhelper (>= 10), quilt (>= 0.40),
2140    libsystemd-dev [linux-any], dh-systemd (>= 1.5), pkg-config
2141"#;
2142        let control: Control = input.parse().unwrap();
2143        let mut source = control.source().unwrap();
2144
2145        // Get the Build-Depends as Relations
2146        let mut build_depends = source.build_depends().unwrap();
2147
2148        // Find and remove dh-systemd entry
2149        let mut to_remove = Vec::new();
2150        for (idx, entry) in build_depends.entries().enumerate() {
2151            for relation in entry.relations() {
2152                if relation.name() == "dh-systemd" {
2153                    to_remove.push(idx);
2154                    break;
2155                }
2156            }
2157        }
2158
2159        for idx in to_remove.into_iter().rev() {
2160            build_depends.remove_entry(idx);
2161        }
2162
2163        // Set it back using the string representation - this is what might cause the bug
2164        source.set("Build-Depends", &build_depends.to_string());
2165
2166        let output = source.to_string();
2167        println!("Output with string set:");
2168        println!("{}", output);
2169
2170        // Check if indentation is preserved
2171        // This test documents the current behavior - it may fail if indentation is lost
2172        assert!(
2173            output.contains("Build-Depends: debhelper (>= 10), quilt (>= 0.40),\n    libsystemd-dev [linux-any], pkg-config"),
2174            "Expected 4-space indentation to be preserved, but got:\n{}",
2175            output
2176        );
2177    }
2178}