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.set("Description", description);
1138        } else {
1139            self.0.remove("Description");
1140        }
1141    }
1142
1143    /// Return the upstream homepage
1144    pub fn homepage(&self) -> Option<url::Url> {
1145        self.0.get("Homepage").and_then(|s| s.parse().ok())
1146    }
1147
1148    /// Set the upstream homepage
1149    pub fn set_homepage(&mut self, url: &url::Url) {
1150        self.set("Homepage", url.as_str());
1151    }
1152
1153    /// Set a field in the binary paragraph, using canonical field ordering for binary packages
1154    pub fn set(&mut self, key: &str, value: &str) {
1155        self.0.set_with_field_order(key, value, BINARY_FIELD_ORDER);
1156    }
1157
1158    /// Check if this binary paragraph's range overlaps with the given range
1159    ///
1160    /// # Arguments
1161    /// * `range` - The text range to check for overlap
1162    ///
1163    /// # Returns
1164    /// `true` if the paragraph overlaps with the given range, `false` otherwise
1165    pub fn overlaps_range(&self, range: rowan::TextRange) -> bool {
1166        let para_range = self.0.syntax().text_range();
1167        para_range.start() < range.end() && range.start() < para_range.end()
1168    }
1169
1170    /// Get fields in this binary paragraph that overlap with the given range
1171    ///
1172    /// # Arguments
1173    /// * `range` - The text range to check for overlaps
1174    ///
1175    /// # Returns
1176    /// An iterator over Entry items that overlap with the given range
1177    pub fn fields_in_range(
1178        &self,
1179        range: rowan::TextRange,
1180    ) -> impl Iterator<Item = deb822_lossless::Entry> + '_ {
1181        self.0.entries().filter(move |entry| {
1182            let entry_range = entry.syntax().text_range();
1183            entry_range.start() < range.end() && range.start() < entry_range.end()
1184        })
1185    }
1186}
1187
1188#[cfg(test)]
1189mod tests {
1190    use super::*;
1191    use crate::relations::VersionConstraint;
1192
1193    #[test]
1194    fn test_source_set_field_ordering() {
1195        let mut control = Control::new();
1196        let mut source = control.add_source("mypackage");
1197
1198        // Add fields in random order
1199        source.set("Homepage", "https://example.com");
1200        source.set("Build-Depends", "debhelper");
1201        source.set("Standards-Version", "4.5.0");
1202        source.set("Maintainer", "Test <test@example.com>");
1203
1204        // Convert to string and check field order
1205        let output = source.to_string();
1206        let lines: Vec<&str> = output.lines().collect();
1207
1208        // Source should be first
1209        assert!(lines[0].starts_with("Source:"));
1210
1211        // Find the positions of each field
1212        let maintainer_pos = lines
1213            .iter()
1214            .position(|l| l.starts_with("Maintainer:"))
1215            .unwrap();
1216        let build_depends_pos = lines
1217            .iter()
1218            .position(|l| l.starts_with("Build-Depends:"))
1219            .unwrap();
1220        let standards_pos = lines
1221            .iter()
1222            .position(|l| l.starts_with("Standards-Version:"))
1223            .unwrap();
1224        let homepage_pos = lines
1225            .iter()
1226            .position(|l| l.starts_with("Homepage:"))
1227            .unwrap();
1228
1229        // Check ordering according to SOURCE_FIELD_ORDER
1230        assert!(maintainer_pos < build_depends_pos);
1231        assert!(build_depends_pos < standards_pos);
1232        assert!(standards_pos < homepage_pos);
1233    }
1234
1235    #[test]
1236    fn test_binary_set_field_ordering() {
1237        let mut control = Control::new();
1238        let mut binary = control.add_binary("mypackage");
1239
1240        // Add fields in random order
1241        binary.set("Description", "A test package");
1242        binary.set("Architecture", "amd64");
1243        binary.set("Depends", "libc6");
1244        binary.set("Section", "utils");
1245
1246        // Convert to string and check field order
1247        let output = binary.to_string();
1248        let lines: Vec<&str> = output.lines().collect();
1249
1250        // Package should be first
1251        assert!(lines[0].starts_with("Package:"));
1252
1253        // Find the positions of each field
1254        let arch_pos = lines
1255            .iter()
1256            .position(|l| l.starts_with("Architecture:"))
1257            .unwrap();
1258        let section_pos = lines
1259            .iter()
1260            .position(|l| l.starts_with("Section:"))
1261            .unwrap();
1262        let depends_pos = lines
1263            .iter()
1264            .position(|l| l.starts_with("Depends:"))
1265            .unwrap();
1266        let desc_pos = lines
1267            .iter()
1268            .position(|l| l.starts_with("Description:"))
1269            .unwrap();
1270
1271        // Check ordering according to BINARY_FIELD_ORDER
1272        assert!(arch_pos < section_pos);
1273        assert!(section_pos < depends_pos);
1274        assert!(depends_pos < desc_pos);
1275    }
1276
1277    #[test]
1278    fn test_source_specific_set_methods_use_field_ordering() {
1279        let mut control = Control::new();
1280        let mut source = control.add_source("mypackage");
1281
1282        // Use specific set_* methods in random order
1283        source.set_homepage(&"https://example.com".parse().unwrap());
1284        source.set_maintainer("Test <test@example.com>");
1285        source.set_standards_version("4.5.0");
1286        source.set_vcs_git("https://github.com/example/repo");
1287
1288        // Convert to string and check field order
1289        let output = source.to_string();
1290        let lines: Vec<&str> = output.lines().collect();
1291
1292        // Find the positions of each field
1293        let source_pos = lines.iter().position(|l| l.starts_with("Source:")).unwrap();
1294        let maintainer_pos = lines
1295            .iter()
1296            .position(|l| l.starts_with("Maintainer:"))
1297            .unwrap();
1298        let standards_pos = lines
1299            .iter()
1300            .position(|l| l.starts_with("Standards-Version:"))
1301            .unwrap();
1302        let vcs_git_pos = lines
1303            .iter()
1304            .position(|l| l.starts_with("Vcs-Git:"))
1305            .unwrap();
1306        let homepage_pos = lines
1307            .iter()
1308            .position(|l| l.starts_with("Homepage:"))
1309            .unwrap();
1310
1311        // Check ordering according to SOURCE_FIELD_ORDER
1312        assert!(source_pos < maintainer_pos);
1313        assert!(maintainer_pos < standards_pos);
1314        assert!(standards_pos < vcs_git_pos);
1315        assert!(vcs_git_pos < homepage_pos);
1316    }
1317
1318    #[test]
1319    fn test_binary_specific_set_methods_use_field_ordering() {
1320        let mut control = Control::new();
1321        let mut binary = control.add_binary("mypackage");
1322
1323        // Use specific set_* methods in random order
1324        binary.set_description(Some("A test package"));
1325        binary.set_architecture(Some("amd64"));
1326        let depends = "libc6".parse().unwrap();
1327        binary.set_depends(Some(&depends));
1328        binary.set_section(Some("utils"));
1329        binary.set_priority(Some(Priority::Optional));
1330
1331        // Convert to string and check field order
1332        let output = binary.to_string();
1333        let lines: Vec<&str> = output.lines().collect();
1334
1335        // Find the positions of each field
1336        let package_pos = lines
1337            .iter()
1338            .position(|l| l.starts_with("Package:"))
1339            .unwrap();
1340        let arch_pos = lines
1341            .iter()
1342            .position(|l| l.starts_with("Architecture:"))
1343            .unwrap();
1344        let section_pos = lines
1345            .iter()
1346            .position(|l| l.starts_with("Section:"))
1347            .unwrap();
1348        let priority_pos = lines
1349            .iter()
1350            .position(|l| l.starts_with("Priority:"))
1351            .unwrap();
1352        let depends_pos = lines
1353            .iter()
1354            .position(|l| l.starts_with("Depends:"))
1355            .unwrap();
1356        let desc_pos = lines
1357            .iter()
1358            .position(|l| l.starts_with("Description:"))
1359            .unwrap();
1360
1361        // Check ordering according to BINARY_FIELD_ORDER
1362        assert!(package_pos < arch_pos);
1363        assert!(arch_pos < section_pos);
1364        assert!(section_pos < priority_pos);
1365        assert!(priority_pos < depends_pos);
1366        assert!(depends_pos < desc_pos);
1367    }
1368
1369    #[test]
1370    fn test_parse() {
1371        let control: Control = r#"Source: foo
1372Section: libs
1373Priority: optional
1374Build-Depends: bar (>= 1.0.0), baz (>= 1.0.0)
1375Homepage: https://example.com
1376
1377"#
1378        .parse()
1379        .unwrap();
1380        let source = control.source().unwrap();
1381
1382        assert_eq!(source.name(), Some("foo".to_owned()));
1383        assert_eq!(source.section(), Some("libs".to_owned()));
1384        assert_eq!(source.priority(), Some(super::Priority::Optional));
1385        assert_eq!(
1386            source.homepage(),
1387            Some("https://example.com".parse().unwrap())
1388        );
1389        let bd = source.build_depends().unwrap();
1390        let entries = bd.entries().collect::<Vec<_>>();
1391        assert_eq!(entries.len(), 2);
1392        let rel = entries[0].relations().collect::<Vec<_>>().pop().unwrap();
1393        assert_eq!(rel.name(), "bar");
1394        assert_eq!(
1395            rel.version(),
1396            Some((
1397                VersionConstraint::GreaterThanEqual,
1398                "1.0.0".parse().unwrap()
1399            ))
1400        );
1401        let rel = entries[1].relations().collect::<Vec<_>>().pop().unwrap();
1402        assert_eq!(rel.name(), "baz");
1403        assert_eq!(
1404            rel.version(),
1405            Some((
1406                VersionConstraint::GreaterThanEqual,
1407                "1.0.0".parse().unwrap()
1408            ))
1409        );
1410    }
1411
1412    #[test]
1413    fn test_description() {
1414        let control: Control = r#"Source: foo
1415
1416Package: foo
1417Description: this is the short description
1418 And the longer one
1419 .
1420 is on the next lines
1421"#
1422        .parse()
1423        .unwrap();
1424        let binary = control.binaries().next().unwrap();
1425        assert_eq!(
1426            binary.description(),
1427            Some(
1428                "this is the short description\nAnd the longer one\n.\nis on the next lines"
1429                    .to_owned()
1430            )
1431        );
1432    }
1433
1434    #[test]
1435    fn test_as_mut_deb822() {
1436        let mut control = Control::new();
1437        let deb822 = control.as_mut_deb822();
1438        let mut p = deb822.add_paragraph();
1439        p.set("Source", "foo");
1440        assert_eq!(control.source().unwrap().name(), Some("foo".to_owned()));
1441    }
1442
1443    #[test]
1444    fn test_as_deb822() {
1445        let control = Control::new();
1446        let _deb822: &Deb822 = control.as_deb822();
1447    }
1448
1449    #[test]
1450    fn test_set_depends() {
1451        let mut control = Control::new();
1452        let mut binary = control.add_binary("foo");
1453        let relations: Relations = "bar (>= 1.0.0)".parse().unwrap();
1454        binary.set_depends(Some(&relations));
1455    }
1456
1457    #[test]
1458    fn test_wrap_and_sort() {
1459        let mut control: Control = r#"Package: blah
1460Section:     libs
1461
1462
1463
1464Package: foo
1465Description: this is a 
1466      bar
1467      blah
1468"#
1469        .parse()
1470        .unwrap();
1471        control.wrap_and_sort(deb822_lossless::Indentation::Spaces(2), false, None);
1472        let expected = r#"Package: blah
1473Section: libs
1474
1475Package: foo
1476Description: this is a 
1477  bar
1478  blah
1479"#
1480        .to_owned();
1481        assert_eq!(control.to_string(), expected);
1482    }
1483
1484    #[test]
1485    fn test_wrap_and_sort_source() {
1486        let mut control: Control = r#"Source: blah
1487Depends: foo, bar   (<=  1.0.0)
1488
1489"#
1490        .parse()
1491        .unwrap();
1492        control.wrap_and_sort(deb822_lossless::Indentation::Spaces(2), true, None);
1493        let expected = r#"Source: blah
1494Depends: bar (<= 1.0.0), foo
1495"#
1496        .to_owned();
1497        assert_eq!(control.to_string(), expected);
1498    }
1499
1500    #[test]
1501    fn test_source_wrap_and_sort() {
1502        let control: Control = r#"Source: blah
1503Build-Depends: foo, bar (>= 1.0.0)
1504
1505"#
1506        .parse()
1507        .unwrap();
1508        let mut source = control.source().unwrap();
1509        source.wrap_and_sort(deb822_lossless::Indentation::Spaces(2), true, None);
1510        // The actual behavior - the method modifies the source in-place
1511        // but doesn't automatically affect the overall control structure
1512        // So we just test that the method executes without error
1513        assert!(source.build_depends().is_some());
1514    }
1515
1516    #[test]
1517    fn test_binary_set_breaks() {
1518        let mut control = Control::new();
1519        let mut binary = control.add_binary("foo");
1520        let relations: Relations = "bar (>= 1.0.0)".parse().unwrap();
1521        binary.set_breaks(Some(&relations));
1522        assert!(binary.breaks().is_some());
1523    }
1524
1525    #[test]
1526    fn test_binary_set_pre_depends() {
1527        let mut control = Control::new();
1528        let mut binary = control.add_binary("foo");
1529        let relations: Relations = "bar (>= 1.0.0)".parse().unwrap();
1530        binary.set_pre_depends(Some(&relations));
1531        assert!(binary.pre_depends().is_some());
1532    }
1533
1534    #[test]
1535    fn test_binary_set_provides() {
1536        let mut control = Control::new();
1537        let mut binary = control.add_binary("foo");
1538        let relations: Relations = "bar (>= 1.0.0)".parse().unwrap();
1539        binary.set_provides(Some(&relations));
1540        assert!(binary.provides().is_some());
1541    }
1542
1543    #[test]
1544    fn test_source_build_conflicts() {
1545        let control: Control = r#"Source: blah
1546Build-Conflicts: foo, bar (>= 1.0.0)
1547
1548"#
1549        .parse()
1550        .unwrap();
1551        let source = control.source().unwrap();
1552        let conflicts = source.build_conflicts();
1553        assert!(conflicts.is_some());
1554    }
1555
1556    #[test]
1557    fn test_source_vcs_svn() {
1558        let control: Control = r#"Source: blah
1559Vcs-Svn: https://example.com/svn/repo
1560
1561"#
1562        .parse()
1563        .unwrap();
1564        let source = control.source().unwrap();
1565        assert_eq!(
1566            source.vcs_svn(),
1567            Some("https://example.com/svn/repo".to_string())
1568        );
1569    }
1570
1571    #[test]
1572    fn test_control_from_conversion() {
1573        let deb822_data = r#"Source: test
1574Section: libs
1575
1576"#;
1577        let deb822: Deb822 = deb822_data.parse().unwrap();
1578        let control = Control::from(deb822);
1579        assert!(control.source().is_some());
1580    }
1581
1582    #[test]
1583    fn test_fields_in_range() {
1584        let control_text = r#"Source: test-package
1585Maintainer: Test User <test@example.com>
1586Build-Depends: debhelper (>= 12)
1587
1588Package: test-binary
1589Architecture: any
1590Depends: ${shlibs:Depends}
1591Description: Test package
1592 This is a test package
1593"#;
1594        let control: Control = control_text.parse().unwrap();
1595
1596        // Test range that covers only the Source field
1597        let source_start = 0;
1598        let source_end = "Source: test-package".len();
1599        let source_range =
1600            rowan::TextRange::new((source_start as u32).into(), (source_end as u32).into());
1601
1602        let fields: Vec<_> = control.fields_in_range(source_range).collect();
1603        assert_eq!(fields.len(), 1);
1604        assert_eq!(fields[0].key(), Some("Source".to_string()));
1605
1606        // Test range that covers multiple fields in source paragraph
1607        let maintainer_start = control_text.find("Maintainer:").unwrap();
1608        let build_depends_end = control_text
1609            .find("Build-Depends: debhelper (>= 12)")
1610            .unwrap()
1611            + "Build-Depends: debhelper (>= 12)".len();
1612        let multi_range = rowan::TextRange::new(
1613            (maintainer_start as u32).into(),
1614            (build_depends_end as u32).into(),
1615        );
1616
1617        let fields: Vec<_> = control.fields_in_range(multi_range).collect();
1618        assert_eq!(fields.len(), 2);
1619        assert_eq!(fields[0].key(), Some("Maintainer".to_string()));
1620        assert_eq!(fields[1].key(), Some("Build-Depends".to_string()));
1621
1622        // Test range that spans across paragraphs
1623        let cross_para_start = control_text.find("Build-Depends:").unwrap();
1624        let cross_para_end =
1625            control_text.find("Architecture: any").unwrap() + "Architecture: any".len();
1626        let cross_range = rowan::TextRange::new(
1627            (cross_para_start as u32).into(),
1628            (cross_para_end as u32).into(),
1629        );
1630
1631        let fields: Vec<_> = control.fields_in_range(cross_range).collect();
1632        assert_eq!(fields.len(), 3); // Build-Depends, Package, Architecture
1633        assert_eq!(fields[0].key(), Some("Build-Depends".to_string()));
1634        assert_eq!(fields[1].key(), Some("Package".to_string()));
1635        assert_eq!(fields[2].key(), Some("Architecture".to_string()));
1636
1637        // Test empty range (should return no fields)
1638        let empty_range = rowan::TextRange::new(1000.into(), 1001.into());
1639        let fields: Vec<_> = control.fields_in_range(empty_range).collect();
1640        assert_eq!(fields.len(), 0);
1641    }
1642
1643    #[test]
1644    fn test_source_overlaps_range() {
1645        let control_text = r#"Source: test-package
1646Maintainer: Test User <test@example.com>
1647
1648Package: test-binary
1649Architecture: any
1650"#;
1651        let control: Control = control_text.parse().unwrap();
1652        let source = control.source().unwrap();
1653
1654        // Test range that overlaps with source paragraph
1655        let overlap_range = rowan::TextRange::new(10.into(), 30.into());
1656        assert!(source.overlaps_range(overlap_range));
1657
1658        // Test range that doesn't overlap with source paragraph
1659        let binary_start = control_text.find("Package:").unwrap();
1660        let no_overlap_range = rowan::TextRange::new(
1661            (binary_start as u32).into(),
1662            ((binary_start + 20) as u32).into(),
1663        );
1664        assert!(!source.overlaps_range(no_overlap_range));
1665
1666        // Test range that starts before and ends within source paragraph
1667        let partial_overlap = rowan::TextRange::new(0.into(), 15.into());
1668        assert!(source.overlaps_range(partial_overlap));
1669    }
1670
1671    #[test]
1672    fn test_source_fields_in_range() {
1673        let control_text = r#"Source: test-package
1674Maintainer: Test User <test@example.com>
1675Build-Depends: debhelper (>= 12)
1676
1677Package: test-binary
1678"#;
1679        let control: Control = control_text.parse().unwrap();
1680        let source = control.source().unwrap();
1681
1682        // Test range covering Maintainer field
1683        let maintainer_start = control_text.find("Maintainer:").unwrap();
1684        let maintainer_end = maintainer_start + "Maintainer: Test User <test@example.com>".len();
1685        let maintainer_range = rowan::TextRange::new(
1686            (maintainer_start as u32).into(),
1687            (maintainer_end as u32).into(),
1688        );
1689
1690        let fields: Vec<_> = source.fields_in_range(maintainer_range).collect();
1691        assert_eq!(fields.len(), 1);
1692        assert_eq!(fields[0].key(), Some("Maintainer".to_string()));
1693
1694        // Test range covering multiple fields
1695        let all_source_range = rowan::TextRange::new(0.into(), 100.into());
1696        let fields: Vec<_> = source.fields_in_range(all_source_range).collect();
1697        assert_eq!(fields.len(), 3); // Source, Maintainer, Build-Depends
1698    }
1699
1700    #[test]
1701    fn test_binary_overlaps_range() {
1702        let control_text = r#"Source: test-package
1703
1704Package: test-binary
1705Architecture: any
1706Depends: ${shlibs:Depends}
1707"#;
1708        let control: Control = control_text.parse().unwrap();
1709        let binary = control.binaries().next().unwrap();
1710
1711        // Test range that overlaps with binary paragraph
1712        let package_start = control_text.find("Package:").unwrap();
1713        let overlap_range = rowan::TextRange::new(
1714            (package_start as u32).into(),
1715            ((package_start + 30) as u32).into(),
1716        );
1717        assert!(binary.overlaps_range(overlap_range));
1718
1719        // Test range before binary paragraph
1720        let no_overlap_range = rowan::TextRange::new(0.into(), 10.into());
1721        assert!(!binary.overlaps_range(no_overlap_range));
1722    }
1723
1724    #[test]
1725    fn test_binary_fields_in_range() {
1726        let control_text = r#"Source: test-package
1727
1728Package: test-binary
1729Architecture: any
1730Depends: ${shlibs:Depends}
1731Description: Test binary
1732 This is a test binary package
1733"#;
1734        let control: Control = control_text.parse().unwrap();
1735        let binary = control.binaries().next().unwrap();
1736
1737        // Test range covering Architecture and Depends
1738        let arch_start = control_text.find("Architecture:").unwrap();
1739        let depends_end = control_text.find("Depends: ${shlibs:Depends}").unwrap()
1740            + "Depends: ${shlibs:Depends}".len();
1741        let range = rowan::TextRange::new((arch_start as u32).into(), (depends_end as u32).into());
1742
1743        let fields: Vec<_> = binary.fields_in_range(range).collect();
1744        assert_eq!(fields.len(), 2);
1745        assert_eq!(fields[0].key(), Some("Architecture".to_string()));
1746        assert_eq!(fields[1].key(), Some("Depends".to_string()));
1747
1748        // Test partial overlap with Description field
1749        let desc_start = control_text.find("Description:").unwrap();
1750        let partial_range = rowan::TextRange::new(
1751            ((desc_start + 5) as u32).into(),
1752            ((desc_start + 15) as u32).into(),
1753        );
1754        let fields: Vec<_> = binary.fields_in_range(partial_range).collect();
1755        assert_eq!(fields.len(), 1);
1756        assert_eq!(fields[0].key(), Some("Description".to_string()));
1757    }
1758
1759    #[test]
1760    fn test_incremental_parsing_use_case() {
1761        // This test simulates a real LSP use case where only changed fields are processed
1762        let control_text = r#"Source: example
1763Maintainer: John Doe <john@example.com>
1764Standards-Version: 4.6.0
1765Build-Depends: debhelper-compat (= 13)
1766
1767Package: example-bin
1768Architecture: all
1769Depends: ${misc:Depends}
1770Description: Example package
1771 This is an example.
1772"#;
1773        let control: Control = control_text.parse().unwrap();
1774
1775        // Simulate a change to Standards-Version field
1776        let change_start = control_text.find("Standards-Version:").unwrap();
1777        let change_end = change_start + "Standards-Version: 4.6.0".len();
1778        let change_range =
1779            rowan::TextRange::new((change_start as u32).into(), (change_end as u32).into());
1780
1781        // Only process fields in the changed range
1782        let affected_fields: Vec<_> = control.fields_in_range(change_range).collect();
1783        assert_eq!(affected_fields.len(), 1);
1784        assert_eq!(
1785            affected_fields[0].key(),
1786            Some("Standards-Version".to_string())
1787        );
1788
1789        // Verify that we're not processing unrelated fields
1790        for entry in &affected_fields {
1791            let key = entry.key().unwrap();
1792            assert_ne!(key, "Maintainer");
1793            assert_ne!(key, "Build-Depends");
1794            assert_ne!(key, "Architecture");
1795        }
1796    }
1797
1798    #[test]
1799    fn test_positioned_parse_errors() {
1800        // Test case from the requirements document
1801        let input = "Invalid: field\nBroken field without colon";
1802        let parsed = Control::parse(input);
1803
1804        // Should have positioned errors accessible
1805        let positioned_errors = parsed.positioned_errors();
1806        assert!(
1807            !positioned_errors.is_empty(),
1808            "Should have positioned errors"
1809        );
1810
1811        // Test that we can access error properties
1812        for error in positioned_errors {
1813            let start_offset: u32 = error.range.start().into();
1814            let end_offset: u32 = error.range.end().into();
1815
1816            // Verify we have meaningful error messages
1817            assert!(!error.message.is_empty());
1818
1819            // Verify ranges are valid
1820            assert!(start_offset <= end_offset);
1821            assert!(end_offset <= input.len() as u32);
1822
1823            // Error should have a code
1824            assert!(error.code.is_some());
1825
1826            println!(
1827                "Error at {:?}: {} (code: {:?})",
1828                error.range, error.message, error.code
1829            );
1830        }
1831
1832        // Should also be able to get string errors for backward compatibility
1833        let string_errors = parsed.errors();
1834        assert!(!string_errors.is_empty());
1835        assert_eq!(string_errors.len(), positioned_errors.len());
1836    }
1837
1838    #[test]
1839    fn test_sort_binaries_basic() {
1840        let input = r#"Source: foo
1841
1842Package: libfoo
1843Architecture: all
1844
1845Package: libbar
1846Architecture: all
1847"#;
1848
1849        let mut control: Control = input.parse().unwrap();
1850        control.sort_binaries(false);
1851
1852        let binaries: Vec<_> = control.binaries().collect();
1853        assert_eq!(binaries.len(), 2);
1854        assert_eq!(binaries[0].name(), Some("libbar".to_string()));
1855        assert_eq!(binaries[1].name(), Some("libfoo".to_string()));
1856    }
1857
1858    #[test]
1859    fn test_sort_binaries_keep_first() {
1860        let input = r#"Source: foo
1861
1862Package: zzz-first
1863Architecture: all
1864
1865Package: libbar
1866Architecture: all
1867
1868Package: libaaa
1869Architecture: all
1870"#;
1871
1872        let mut control: Control = input.parse().unwrap();
1873        control.sort_binaries(true);
1874
1875        let binaries: Vec<_> = control.binaries().collect();
1876        assert_eq!(binaries.len(), 3);
1877        // First binary should remain in place
1878        assert_eq!(binaries[0].name(), Some("zzz-first".to_string()));
1879        // The rest should be sorted
1880        assert_eq!(binaries[1].name(), Some("libaaa".to_string()));
1881        assert_eq!(binaries[2].name(), Some("libbar".to_string()));
1882    }
1883
1884    #[test]
1885    fn test_sort_binaries_already_sorted() {
1886        let input = r#"Source: foo
1887
1888Package: aaa
1889Architecture: all
1890
1891Package: bbb
1892Architecture: all
1893
1894Package: ccc
1895Architecture: all
1896"#;
1897
1898        let mut control: Control = input.parse().unwrap();
1899        control.sort_binaries(false);
1900
1901        let binaries: Vec<_> = control.binaries().collect();
1902        assert_eq!(binaries.len(), 3);
1903        assert_eq!(binaries[0].name(), Some("aaa".to_string()));
1904        assert_eq!(binaries[1].name(), Some("bbb".to_string()));
1905        assert_eq!(binaries[2].name(), Some("ccc".to_string()));
1906    }
1907
1908    #[test]
1909    fn test_sort_binaries_no_binaries() {
1910        let input = r#"Source: foo
1911Maintainer: test@example.com
1912"#;
1913
1914        let mut control: Control = input.parse().unwrap();
1915        control.sort_binaries(false);
1916
1917        // Should not crash, just do nothing
1918        assert_eq!(control.binaries().count(), 0);
1919    }
1920
1921    #[test]
1922    fn test_sort_binaries_one_binary() {
1923        let input = r#"Source: foo
1924
1925Package: bar
1926Architecture: all
1927"#;
1928
1929        let mut control: Control = input.parse().unwrap();
1930        control.sort_binaries(false);
1931
1932        let binaries: Vec<_> = control.binaries().collect();
1933        assert_eq!(binaries.len(), 1);
1934        assert_eq!(binaries[0].name(), Some("bar".to_string()));
1935    }
1936
1937    #[test]
1938    fn test_sort_binaries_preserves_fields() {
1939        let input = r#"Source: foo
1940
1941Package: zzz
1942Architecture: any
1943Depends: libc6
1944Description: ZZZ package
1945
1946Package: aaa
1947Architecture: all
1948Depends: ${misc:Depends}
1949Description: AAA package
1950"#;
1951
1952        let mut control: Control = input.parse().unwrap();
1953        control.sort_binaries(false);
1954
1955        let binaries: Vec<_> = control.binaries().collect();
1956        assert_eq!(binaries.len(), 2);
1957
1958        // First binary should be aaa
1959        assert_eq!(binaries[0].name(), Some("aaa".to_string()));
1960        assert_eq!(binaries[0].architecture(), Some("all".to_string()));
1961        assert_eq!(binaries[0].description(), Some("AAA package".to_string()));
1962
1963        // Second binary should be zzz
1964        assert_eq!(binaries[1].name(), Some("zzz".to_string()));
1965        assert_eq!(binaries[1].architecture(), Some("any".to_string()));
1966        assert_eq!(binaries[1].description(), Some("ZZZ package".to_string()));
1967    }
1968
1969    #[test]
1970    fn test_remove_binary_basic() {
1971        let mut control = Control::new();
1972        control.add_binary("foo");
1973        assert_eq!(control.binaries().count(), 1);
1974        assert!(control.remove_binary("foo"));
1975        assert_eq!(control.binaries().count(), 0);
1976    }
1977
1978    #[test]
1979    fn test_remove_binary_nonexistent() {
1980        let mut control = Control::new();
1981        control.add_binary("foo");
1982        assert!(!control.remove_binary("bar"));
1983        assert_eq!(control.binaries().count(), 1);
1984    }
1985
1986    #[test]
1987    fn test_remove_binary_multiple() {
1988        let mut control = Control::new();
1989        control.add_binary("foo");
1990        control.add_binary("bar");
1991        control.add_binary("baz");
1992        assert_eq!(control.binaries().count(), 3);
1993
1994        assert!(control.remove_binary("bar"));
1995        assert_eq!(control.binaries().count(), 2);
1996
1997        let names: Vec<_> = control.binaries().map(|b| b.name().unwrap()).collect();
1998        assert_eq!(names, vec!["foo", "baz"]);
1999    }
2000
2001    #[test]
2002    fn test_remove_binary_preserves_source() {
2003        let input = r#"Source: mypackage
2004
2005Package: foo
2006Architecture: all
2007
2008Package: bar
2009Architecture: all
2010"#;
2011        let mut control: Control = input.parse().unwrap();
2012        assert!(control.source().is_some());
2013        assert_eq!(control.binaries().count(), 2);
2014
2015        assert!(control.remove_binary("foo"));
2016
2017        // Source should still be present
2018        assert!(control.source().is_some());
2019        assert_eq!(
2020            control.source().unwrap().name(),
2021            Some("mypackage".to_string())
2022        );
2023
2024        // Only bar should remain
2025        assert_eq!(control.binaries().count(), 1);
2026        assert_eq!(
2027            control.binaries().next().unwrap().name(),
2028            Some("bar".to_string())
2029        );
2030    }
2031
2032    #[test]
2033    fn test_remove_binary_from_parsed() {
2034        let input = r#"Source: test
2035
2036Package: test-bin
2037Architecture: any
2038Depends: libc6
2039Description: Test binary
2040
2041Package: test-lib
2042Architecture: all
2043Description: Test library
2044"#;
2045        let mut control: Control = input.parse().unwrap();
2046        assert_eq!(control.binaries().count(), 2);
2047
2048        assert!(control.remove_binary("test-bin"));
2049
2050        let output = control.to_string();
2051        assert!(!output.contains("test-bin"));
2052        assert!(output.contains("test-lib"));
2053        assert!(output.contains("Source: test"));
2054    }
2055}