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    /// Read a control file from a file
156    pub fn from_file<P: AsRef<std::path::Path>>(path: P) -> Result<Self, deb822_lossless::Error> {
157        Ok(Control(Deb822::from_file(path)?))
158    }
159
160    /// Read a control file from a file, allowing syntax errors
161    pub fn from_file_relaxed<P: AsRef<std::path::Path>>(
162        path: P,
163    ) -> Result<(Self, Vec<String>), std::io::Error> {
164        let (control, errors) = Deb822::from_file_relaxed(path)?;
165        Ok((Control(control), errors))
166    }
167
168    /// Read a control file from a reader
169    pub fn read<R: std::io::Read>(mut r: R) -> Result<Self, deb822_lossless::Error> {
170        Ok(Control(Deb822::read(&mut r)?))
171    }
172
173    /// Read a control file from a reader, allowing syntax errors
174    pub fn read_relaxed<R: std::io::Read>(
175        mut r: R,
176    ) -> Result<(Self, Vec<String>), deb822_lossless::Error> {
177        let (control, errors) = Deb822::read_relaxed(&mut r)?;
178        Ok((Self(control), errors))
179    }
180
181    /// Wrap and sort the control file
182    ///
183    /// # Arguments
184    /// * `indentation` - The indentation to use
185    /// * `immediate_empty_line` - Whether to add an empty line at the start of multi-line fields
186    /// * `max_line_length_one_liner` - The maximum line length for one-liner fields
187    pub fn wrap_and_sort(
188        &mut self,
189        indentation: deb822_lossless::Indentation,
190        immediate_empty_line: bool,
191        max_line_length_one_liner: Option<usize>,
192    ) {
193        let sort_paragraphs = |a: &Paragraph, b: &Paragraph| -> std::cmp::Ordering {
194            // Sort Source before Package
195            let a_is_source = a.get("Source").is_some();
196            let b_is_source = b.get("Source").is_some();
197
198            if a_is_source && !b_is_source {
199                return std::cmp::Ordering::Less;
200            } else if !a_is_source && b_is_source {
201                return std::cmp::Ordering::Greater;
202            } else if a_is_source && b_is_source {
203                return a.get("Source").cmp(&b.get("Source"));
204            }
205
206            a.get("Package").cmp(&b.get("Package"))
207        };
208
209        let wrap_paragraph = |p: &Paragraph| -> Paragraph {
210            // TODO: Add Source/Package specific wrapping
211            // TODO: Add support for wrapping and sorting fields
212            p.wrap_and_sort(
213                indentation,
214                immediate_empty_line,
215                max_line_length_one_liner,
216                None,
217                Some(&format_field),
218            )
219        };
220
221        self.0 = self
222            .0
223            .wrap_and_sort(Some(&sort_paragraphs), Some(&wrap_paragraph));
224    }
225
226    /// Iterate over fields that overlap with the given range
227    ///
228    /// This method returns all fields (entries) from all paragraphs that have any overlap
229    /// with the specified text range. This is useful for incremental parsing in LSP contexts
230    /// where you only want to process fields that were affected by a text change.
231    ///
232    /// # Arguments
233    /// * `range` - The text range to check for overlaps
234    ///
235    /// # Returns
236    /// An iterator over all Entry items that overlap with the given range
237    ///
238    /// # Example
239    /// ```rust
240    /// use debian_control::lossless::Control;
241    /// use deb822_lossless::TextRange;
242    ///
243    /// let control_text = "Source: foo\nMaintainer: test@example.com\n\nPackage: bar\nArchitecture: all\n";
244    /// let control: Control = control_text.parse().unwrap();
245    ///
246    /// // Get fields in a specific range (e.g., where a change occurred)
247    /// let change_range = TextRange::new(20.into(), 40.into());
248    /// for entry in control.fields_in_range(change_range) {
249    ///     if let Some(key) = entry.key() {
250    ///         println!("Field {} was in the changed range", key);
251    ///     }
252    /// }
253    /// ```
254    pub fn fields_in_range(
255        &self,
256        range: rowan::TextRange,
257    ) -> impl Iterator<Item = deb822_lossless::Entry> + '_ {
258        self.0
259            .paragraphs()
260            .flat_map(move |p| p.entries().collect::<Vec<_>>())
261            .filter(move |entry| {
262                let entry_range = entry.syntax().text_range();
263                // Check if ranges overlap
264                entry_range.start() < range.end() && range.start() < entry_range.end()
265            })
266    }
267}
268
269impl From<Control> for Deb822 {
270    fn from(c: Control) -> Self {
271        c.0
272    }
273}
274
275impl From<Deb822> for Control {
276    fn from(d: Deb822) -> Self {
277        Control(d)
278    }
279}
280
281impl Default for Control {
282    fn default() -> Self {
283        Self::new()
284    }
285}
286
287impl std::str::FromStr for Control {
288    type Err = deb822_lossless::ParseError;
289
290    fn from_str(s: &str) -> Result<Self, Self::Err> {
291        Control::parse(s).to_result()
292    }
293}
294
295/// A source package paragraph
296#[derive(Debug, Clone, PartialEq, Eq)]
297pub struct Source(Paragraph);
298
299impl From<Source> for Paragraph {
300    fn from(s: Source) -> Self {
301        s.0
302    }
303}
304
305impl From<Paragraph> for Source {
306    fn from(p: Paragraph) -> Self {
307        Source(p)
308    }
309}
310
311impl std::fmt::Display for Source {
312    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
313        self.0.fmt(f)
314    }
315}
316
317impl Source {
318    /// The name of the source package.
319    pub fn name(&self) -> Option<String> {
320        self.0.get("Source")
321    }
322
323    /// Wrap and sort the control file paragraph
324    pub fn wrap_and_sort(
325        &mut self,
326        indentation: deb822_lossless::Indentation,
327        immediate_empty_line: bool,
328        max_line_length_one_liner: Option<usize>,
329    ) {
330        self.0 = self.0.wrap_and_sort(
331            indentation,
332            immediate_empty_line,
333            max_line_length_one_liner,
334            None,
335            Some(&format_field),
336        );
337    }
338
339    /// Return the underlying deb822 paragraph, mutable
340    pub fn as_mut_deb822(&mut self) -> &mut Paragraph {
341        &mut self.0
342    }
343
344    /// Return the underlying deb822 paragraph
345    pub fn as_deb822(&self) -> &Paragraph {
346        &self.0
347    }
348
349    /// Set the name of the source package.
350    pub fn set_name(&mut self, name: &str) {
351        self.set("Source", name);
352    }
353
354    /// The default section of the packages built from this source package.
355    pub fn section(&self) -> Option<String> {
356        self.0.get("Section")
357    }
358
359    /// Set the section of the source package
360    pub fn set_section(&mut self, section: Option<&str>) {
361        if let Some(section) = section {
362            self.set("Section", section);
363        } else {
364            self.0.remove("Section");
365        }
366    }
367
368    /// The default priority of the packages built from this source package.
369    pub fn priority(&self) -> Option<Priority> {
370        self.0.get("Priority").and_then(|v| v.parse().ok())
371    }
372
373    /// Set the priority of the source package
374    pub fn set_priority(&mut self, priority: Option<Priority>) {
375        if let Some(priority) = priority {
376            self.set("Priority", priority.to_string().as_str());
377        } else {
378            self.0.remove("Priority");
379        }
380    }
381
382    /// The maintainer of the package.
383    pub fn maintainer(&self) -> Option<String> {
384        self.0.get("Maintainer")
385    }
386
387    /// Set the maintainer of the package
388    pub fn set_maintainer(&mut self, maintainer: &str) {
389        self.set("Maintainer", maintainer);
390    }
391
392    /// The build dependencies of the package.
393    pub fn build_depends(&self) -> Option<Relations> {
394        self.0.get("Build-Depends").map(|s| s.parse().unwrap())
395    }
396
397    /// Set the Build-Depends field
398    pub fn set_build_depends(&mut self, relations: &Relations) {
399        self.set("Build-Depends", relations.to_string().as_str());
400    }
401
402    /// Return the Build-Depends-Indep field
403    pub fn build_depends_indep(&self) -> Option<Relations> {
404        self.0
405            .get("Build-Depends-Indep")
406            .map(|s| s.parse().unwrap())
407    }
408
409    /// Return the Build-Depends-Arch field
410    pub fn build_depends_arch(&self) -> Option<Relations> {
411        self.0.get("Build-Depends-Arch").map(|s| s.parse().unwrap())
412    }
413
414    /// The build conflicts of the package.
415    pub fn build_conflicts(&self) -> Option<Relations> {
416        self.0.get("Build-Conflicts").map(|s| s.parse().unwrap())
417    }
418
419    /// Return the Build-Conflicts-Indep field
420    pub fn build_conflicts_indep(&self) -> Option<Relations> {
421        self.0
422            .get("Build-Conflicts-Indep")
423            .map(|s| s.parse().unwrap())
424    }
425
426    /// Return the Build-Conflicts-Arch field
427    pub fn build_conflicts_arch(&self) -> Option<Relations> {
428        self.0
429            .get("Build-Conflicts-Arch")
430            .map(|s| s.parse().unwrap())
431    }
432
433    /// Return the standards version
434    pub fn standards_version(&self) -> Option<String> {
435        self.0.get("Standards-Version")
436    }
437
438    /// Set the Standards-Version field
439    pub fn set_standards_version(&mut self, version: &str) {
440        self.set("Standards-Version", version);
441    }
442
443    /// Return the upstrea mHomepage
444    pub fn homepage(&self) -> Option<url::Url> {
445        self.0.get("Homepage").and_then(|s| s.parse().ok())
446    }
447
448    /// Set the Homepage field
449    pub fn set_homepage(&mut self, homepage: &url::Url) {
450        self.set("Homepage", homepage.to_string().as_str());
451    }
452
453    /// Return the Vcs-Git field
454    pub fn vcs_git(&self) -> Option<String> {
455        self.0.get("Vcs-Git")
456    }
457
458    /// Set the Vcs-Git field
459    pub fn set_vcs_git(&mut self, url: &str) {
460        self.set("Vcs-Git", url);
461    }
462
463    /// Return the Vcs-Browser field
464    pub fn vcs_svn(&self) -> Option<String> {
465        self.0.get("Vcs-Svn").map(|s| s.to_string())
466    }
467
468    /// Set the Vcs-Svn field
469    pub fn set_vcs_svn(&mut self, url: &str) {
470        self.set("Vcs-Svn", url);
471    }
472
473    /// Return the Vcs-Bzr field
474    pub fn vcs_bzr(&self) -> Option<String> {
475        self.0.get("Vcs-Bzr").map(|s| s.to_string())
476    }
477
478    /// Set the Vcs-Bzr field
479    pub fn set_vcs_bzr(&mut self, url: &str) {
480        self.set("Vcs-Bzr", url);
481    }
482
483    /// Return the Vcs-Arch field
484    pub fn vcs_arch(&self) -> Option<String> {
485        self.0.get("Vcs-Arch").map(|s| s.to_string())
486    }
487
488    /// Set the Vcs-Arch field
489    pub fn set_vcs_arch(&mut self, url: &str) {
490        self.set("Vcs-Arch", url);
491    }
492
493    /// Return the Vcs-Svk field
494    pub fn vcs_svk(&self) -> Option<String> {
495        self.0.get("Vcs-Svk").map(|s| s.to_string())
496    }
497
498    /// Set the Vcs-Svk field
499    pub fn set_vcs_svk(&mut self, url: &str) {
500        self.set("Vcs-Svk", url);
501    }
502
503    /// Return the Vcs-Darcs field
504    pub fn vcs_darcs(&self) -> Option<String> {
505        self.0.get("Vcs-Darcs").map(|s| s.to_string())
506    }
507
508    /// Set the Vcs-Darcs field
509    pub fn set_vcs_darcs(&mut self, url: &str) {
510        self.set("Vcs-Darcs", url);
511    }
512
513    /// Return the Vcs-Mtn field
514    pub fn vcs_mtn(&self) -> Option<String> {
515        self.0.get("Vcs-Mtn").map(|s| s.to_string())
516    }
517
518    /// Set the Vcs-Mtn field
519    pub fn set_vcs_mtn(&mut self, url: &str) {
520        self.set("Vcs-Mtn", url);
521    }
522
523    /// Return the Vcs-Cvs field
524    pub fn vcs_cvs(&self) -> Option<String> {
525        self.0.get("Vcs-Cvs").map(|s| s.to_string())
526    }
527
528    /// Set the Vcs-Cvs field
529    pub fn set_vcs_cvs(&mut self, url: &str) {
530        self.set("Vcs-Cvs", url);
531    }
532
533    /// Return the Vcs-Hg field
534    pub fn vcs_hg(&self) -> Option<String> {
535        self.0.get("Vcs-Hg").map(|s| s.to_string())
536    }
537
538    /// Set the Vcs-Hg field
539    pub fn set_vcs_hg(&mut self, url: &str) {
540        self.set("Vcs-Hg", url);
541    }
542
543    /// Set a field in the source paragraph, using canonical field ordering for source packages
544    pub fn set(&mut self, key: &str, value: &str) {
545        self.0.set_with_field_order(key, value, SOURCE_FIELD_ORDER);
546    }
547
548    /// Return the Vcs-Browser field
549    pub fn vcs_browser(&self) -> Option<String> {
550        self.0.get("Vcs-Browser")
551    }
552
553    /// Return the Vcs used by the package
554    pub fn vcs(&self) -> Option<crate::vcs::Vcs> {
555        for (name, value) in self.0.items() {
556            if name.starts_with("Vcs-") && name != "Vcs-Browser" {
557                return crate::vcs::Vcs::from_field(&name, &value).ok();
558            }
559        }
560        None
561    }
562
563    /// Set the Vcs-Browser field
564    pub fn set_vcs_browser(&mut self, url: Option<&str>) {
565        if let Some(url) = url {
566            self.set("Vcs-Browser", url);
567        } else {
568            self.0.remove("Vcs-Browser");
569        }
570    }
571
572    /// Return the Uploaders field
573    pub fn uploaders(&self) -> Option<Vec<String>> {
574        self.0
575            .get("Uploaders")
576            .map(|s| s.split(',').map(|s| s.trim().to_owned()).collect())
577    }
578
579    /// Set the uploaders field
580    pub fn set_uploaders(&mut self, uploaders: &[&str]) {
581        self.set(
582            "Uploaders",
583            uploaders
584                .iter()
585                .map(|s| s.to_string())
586                .collect::<Vec<_>>()
587                .join(", ")
588                .as_str(),
589        );
590    }
591
592    /// Return the architecture field
593    pub fn architecture(&self) -> Option<String> {
594        self.0.get("Architecture")
595    }
596
597    /// Set the architecture field
598    pub fn set_architecture(&mut self, arch: Option<&str>) {
599        if let Some(arch) = arch {
600            self.set("Architecture", arch);
601        } else {
602            self.0.remove("Architecture");
603        }
604    }
605
606    /// Return the Rules-Requires-Root field
607    pub fn rules_requires_root(&self) -> Option<bool> {
608        self.0
609            .get("Rules-Requires-Root")
610            .map(|s| match s.to_lowercase().as_str() {
611                "yes" => true,
612                "no" => false,
613                _ => panic!("invalid Rules-Requires-Root value"),
614            })
615    }
616
617    /// Set the Rules-Requires-Root field
618    pub fn set_rules_requires_root(&mut self, requires_root: bool) {
619        self.set(
620            "Rules-Requires-Root",
621            if requires_root { "yes" } else { "no" },
622        );
623    }
624
625    /// Return the Testsuite field
626    pub fn testsuite(&self) -> Option<String> {
627        self.0.get("Testsuite")
628    }
629
630    /// Set the Testsuite field
631    pub fn set_testsuite(&mut self, testsuite: &str) {
632        self.set("Testsuite", testsuite);
633    }
634
635    /// Check if this source paragraph's range overlaps with the given range
636    ///
637    /// # Arguments
638    /// * `range` - The text range to check for overlap
639    ///
640    /// # Returns
641    /// `true` if the paragraph overlaps with the given range, `false` otherwise
642    pub fn overlaps_range(&self, range: rowan::TextRange) -> bool {
643        let para_range = self.0.syntax().text_range();
644        para_range.start() < range.end() && range.start() < para_range.end()
645    }
646
647    /// Get fields in this source paragraph that overlap with the given range
648    ///
649    /// # Arguments
650    /// * `range` - The text range to check for overlaps
651    ///
652    /// # Returns
653    /// An iterator over Entry items that overlap with the given range
654    pub fn fields_in_range(
655        &self,
656        range: rowan::TextRange,
657    ) -> impl Iterator<Item = deb822_lossless::Entry> + '_ {
658        self.0.entries().filter(move |entry| {
659            let entry_range = entry.syntax().text_range();
660            entry_range.start() < range.end() && range.start() < entry_range.end()
661        })
662    }
663}
664
665#[cfg(feature = "python-debian")]
666impl<'py> pyo3::IntoPyObject<'py> for Source {
667    type Target = pyo3::PyAny;
668    type Output = pyo3::Bound<'py, Self::Target>;
669    type Error = pyo3::PyErr;
670
671    fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
672        self.0.into_pyobject(py)
673    }
674}
675
676#[cfg(feature = "python-debian")]
677impl<'a, 'py> pyo3::IntoPyObject<'py> for &'a Source {
678    type Target = pyo3::PyAny;
679    type Output = pyo3::Bound<'py, Self::Target>;
680    type Error = pyo3::PyErr;
681
682    fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
683        (&self.0).into_pyobject(py)
684    }
685}
686
687#[cfg(feature = "python-debian")]
688impl<'py> pyo3::FromPyObject<'_, 'py> for Source {
689    type Error = pyo3::PyErr;
690
691    fn extract(ob: pyo3::Borrowed<'_, 'py, pyo3::PyAny>) -> Result<Self, Self::Error> {
692        Ok(Source(ob.extract()?))
693    }
694}
695
696impl std::fmt::Display for Control {
697    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
698        self.0.fmt(f)
699    }
700}
701
702impl AstNode for Control {
703    type Language = deb822_lossless::Lang;
704
705    fn can_cast(kind: <Self::Language as rowan::Language>::Kind) -> bool {
706        Deb822::can_cast(kind)
707    }
708
709    fn cast(syntax: rowan::SyntaxNode<Self::Language>) -> Option<Self> {
710        Deb822::cast(syntax).map(Control)
711    }
712
713    fn syntax(&self) -> &rowan::SyntaxNode<Self::Language> {
714        self.0.syntax()
715    }
716}
717
718/// A binary package paragraph
719#[derive(Debug, Clone, PartialEq, Eq)]
720pub struct Binary(Paragraph);
721
722impl From<Binary> for Paragraph {
723    fn from(b: Binary) -> Self {
724        b.0
725    }
726}
727
728impl From<Paragraph> for Binary {
729    fn from(p: Paragraph) -> Self {
730        Binary(p)
731    }
732}
733
734#[cfg(feature = "python-debian")]
735impl<'py> pyo3::IntoPyObject<'py> for Binary {
736    type Target = pyo3::PyAny;
737    type Output = pyo3::Bound<'py, Self::Target>;
738    type Error = pyo3::PyErr;
739
740    fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
741        self.0.into_pyobject(py)
742    }
743}
744
745#[cfg(feature = "python-debian")]
746impl<'a, 'py> pyo3::IntoPyObject<'py> for &'a Binary {
747    type Target = pyo3::PyAny;
748    type Output = pyo3::Bound<'py, Self::Target>;
749    type Error = pyo3::PyErr;
750
751    fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
752        (&self.0).into_pyobject(py)
753    }
754}
755
756#[cfg(feature = "python-debian")]
757impl<'py> pyo3::FromPyObject<'_, 'py> for Binary {
758    type Error = pyo3::PyErr;
759
760    fn extract(ob: pyo3::Borrowed<'_, 'py, pyo3::PyAny>) -> Result<Self, Self::Error> {
761        Ok(Binary(ob.extract()?))
762    }
763}
764
765impl Default for Binary {
766    fn default() -> Self {
767        Self::new()
768    }
769}
770
771impl std::fmt::Display for Binary {
772    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
773        self.0.fmt(f)
774    }
775}
776
777impl Binary {
778    /// Create a new binary package control file
779    pub fn new() -> Self {
780        Binary(Paragraph::new())
781    }
782
783    /// Return the underlying deb822 paragraph, mutable
784    pub fn as_mut_deb822(&mut self) -> &mut Paragraph {
785        &mut self.0
786    }
787
788    /// Return the underlying deb822 paragraph
789    pub fn as_deb822(&self) -> &Paragraph {
790        &self.0
791    }
792
793    /// Wrap and sort the control file
794    pub fn wrap_and_sort(
795        &mut self,
796        indentation: deb822_lossless::Indentation,
797        immediate_empty_line: bool,
798        max_line_length_one_liner: Option<usize>,
799    ) {
800        self.0 = self.0.wrap_and_sort(
801            indentation,
802            immediate_empty_line,
803            max_line_length_one_liner,
804            None,
805            Some(&format_field),
806        );
807    }
808
809    /// The name of the package.
810    pub fn name(&self) -> Option<String> {
811        self.0.get("Package")
812    }
813
814    /// Set the name of the package
815    pub fn set_name(&mut self, name: &str) {
816        self.set("Package", name);
817    }
818
819    /// The section of the package.
820    pub fn section(&self) -> Option<String> {
821        self.0.get("Section")
822    }
823
824    /// Set the section
825    pub fn set_section(&mut self, section: Option<&str>) {
826        if let Some(section) = section {
827            self.set("Section", section);
828        } else {
829            self.0.remove("Section");
830        }
831    }
832
833    /// The priority of the package.
834    pub fn priority(&self) -> Option<Priority> {
835        self.0.get("Priority").and_then(|v| v.parse().ok())
836    }
837
838    /// Set the priority of the package
839    pub fn set_priority(&mut self, priority: Option<Priority>) {
840        if let Some(priority) = priority {
841            self.set("Priority", priority.to_string().as_str());
842        } else {
843            self.0.remove("Priority");
844        }
845    }
846
847    /// The architecture of the package.
848    pub fn architecture(&self) -> Option<String> {
849        self.0.get("Architecture")
850    }
851
852    /// Set the architecture of the package
853    pub fn set_architecture(&mut self, arch: Option<&str>) {
854        if let Some(arch) = arch {
855            self.set("Architecture", arch);
856        } else {
857            self.0.remove("Architecture");
858        }
859    }
860
861    /// The dependencies of the package.
862    pub fn depends(&self) -> Option<Relations> {
863        self.0.get("Depends").map(|s| s.parse().unwrap())
864    }
865
866    /// Set the Depends field
867    pub fn set_depends(&mut self, depends: Option<&Relations>) {
868        if let Some(depends) = depends {
869            self.set("Depends", depends.to_string().as_str());
870        } else {
871            self.0.remove("Depends");
872        }
873    }
874
875    /// The package that this package recommends
876    pub fn recommends(&self) -> Option<Relations> {
877        self.0.get("Recommends").map(|s| s.parse().unwrap())
878    }
879
880    /// Set the Recommends field
881    pub fn set_recommends(&mut self, recommends: Option<&Relations>) {
882        if let Some(recommends) = recommends {
883            self.set("Recommends", recommends.to_string().as_str());
884        } else {
885            self.0.remove("Recommends");
886        }
887    }
888
889    /// Packages that this package suggests
890    pub fn suggests(&self) -> Option<Relations> {
891        self.0.get("Suggests").map(|s| s.parse().unwrap())
892    }
893
894    /// Set the Suggests field
895    pub fn set_suggests(&mut self, suggests: Option<&Relations>) {
896        if let Some(suggests) = suggests {
897            self.set("Suggests", suggests.to_string().as_str());
898        } else {
899            self.0.remove("Suggests");
900        }
901    }
902
903    /// The package that this package enhances
904    pub fn enhances(&self) -> Option<Relations> {
905        self.0.get("Enhances").map(|s| s.parse().unwrap())
906    }
907
908    /// Set the Enhances field
909    pub fn set_enhances(&mut self, enhances: Option<&Relations>) {
910        if let Some(enhances) = enhances {
911            self.set("Enhances", enhances.to_string().as_str());
912        } else {
913            self.0.remove("Enhances");
914        }
915    }
916
917    /// The package that this package pre-depends on
918    pub fn pre_depends(&self) -> Option<Relations> {
919        self.0.get("Pre-Depends").map(|s| s.parse().unwrap())
920    }
921
922    /// Set the Pre-Depends field
923    pub fn set_pre_depends(&mut self, pre_depends: Option<&Relations>) {
924        if let Some(pre_depends) = pre_depends {
925            self.set("Pre-Depends", pre_depends.to_string().as_str());
926        } else {
927            self.0.remove("Pre-Depends");
928        }
929    }
930
931    /// The package that this package breaks
932    pub fn breaks(&self) -> Option<Relations> {
933        self.0.get("Breaks").map(|s| s.parse().unwrap())
934    }
935
936    /// Set the Breaks field
937    pub fn set_breaks(&mut self, breaks: Option<&Relations>) {
938        if let Some(breaks) = breaks {
939            self.set("Breaks", breaks.to_string().as_str());
940        } else {
941            self.0.remove("Breaks");
942        }
943    }
944
945    /// The package that this package conflicts with
946    pub fn conflicts(&self) -> Option<Relations> {
947        self.0.get("Conflicts").map(|s| s.parse().unwrap())
948    }
949
950    /// Set the Conflicts field
951    pub fn set_conflicts(&mut self, conflicts: Option<&Relations>) {
952        if let Some(conflicts) = conflicts {
953            self.set("Conflicts", conflicts.to_string().as_str());
954        } else {
955            self.0.remove("Conflicts");
956        }
957    }
958
959    /// The package that this package replaces
960    pub fn replaces(&self) -> Option<Relations> {
961        self.0.get("Replaces").map(|s| s.parse().unwrap())
962    }
963
964    /// Set the Replaces field
965    pub fn set_replaces(&mut self, replaces: Option<&Relations>) {
966        if let Some(replaces) = replaces {
967            self.set("Replaces", replaces.to_string().as_str());
968        } else {
969            self.0.remove("Replaces");
970        }
971    }
972
973    /// Return the Provides field
974    pub fn provides(&self) -> Option<Relations> {
975        self.0.get("Provides").map(|s| s.parse().unwrap())
976    }
977
978    /// Set the Provides field
979    pub fn set_provides(&mut self, provides: Option<&Relations>) {
980        if let Some(provides) = provides {
981            self.set("Provides", provides.to_string().as_str());
982        } else {
983            self.0.remove("Provides");
984        }
985    }
986
987    /// Return the Built-Using field
988    pub fn built_using(&self) -> Option<Relations> {
989        self.0.get("Built-Using").map(|s| s.parse().unwrap())
990    }
991
992    /// Set the Built-Using field
993    pub fn set_built_using(&mut self, built_using: Option<&Relations>) {
994        if let Some(built_using) = built_using {
995            self.set("Built-Using", built_using.to_string().as_str());
996        } else {
997            self.0.remove("Built-Using");
998        }
999    }
1000
1001    /// The Multi-Arch field
1002    pub fn multi_arch(&self) -> Option<MultiArch> {
1003        self.0.get("Multi-Arch").map(|s| s.parse().unwrap())
1004    }
1005
1006    /// Set the Multi-Arch field
1007    pub fn set_multi_arch(&mut self, multi_arch: Option<MultiArch>) {
1008        if let Some(multi_arch) = multi_arch {
1009            self.set("Multi-Arch", multi_arch.to_string().as_str());
1010        } else {
1011            self.0.remove("Multi-Arch");
1012        }
1013    }
1014
1015    /// Whether the package is essential
1016    pub fn essential(&self) -> bool {
1017        self.0.get("Essential").map(|s| s == "yes").unwrap_or(false)
1018    }
1019
1020    /// Set whether the package is essential
1021    pub fn set_essential(&mut self, essential: bool) {
1022        if essential {
1023            self.set("Essential", "yes");
1024        } else {
1025            self.0.remove("Essential");
1026        }
1027    }
1028
1029    /// Binary package description
1030    pub fn description(&self) -> Option<String> {
1031        self.0.get("Description")
1032    }
1033
1034    /// Set the binary package description
1035    pub fn set_description(&mut self, description: Option<&str>) {
1036        if let Some(description) = description {
1037            self.set("Description", description);
1038        } else {
1039            self.0.remove("Description");
1040        }
1041    }
1042
1043    /// Return the upstream homepage
1044    pub fn homepage(&self) -> Option<url::Url> {
1045        self.0.get("Homepage").and_then(|s| s.parse().ok())
1046    }
1047
1048    /// Set the upstream homepage
1049    pub fn set_homepage(&mut self, url: &url::Url) {
1050        self.set("Homepage", url.as_str());
1051    }
1052
1053    /// Set a field in the binary paragraph, using canonical field ordering for binary packages
1054    pub fn set(&mut self, key: &str, value: &str) {
1055        self.0.set_with_field_order(key, value, BINARY_FIELD_ORDER);
1056    }
1057
1058    /// Check if this binary paragraph's range overlaps with the given range
1059    ///
1060    /// # Arguments
1061    /// * `range` - The text range to check for overlap
1062    ///
1063    /// # Returns
1064    /// `true` if the paragraph overlaps with the given range, `false` otherwise
1065    pub fn overlaps_range(&self, range: rowan::TextRange) -> bool {
1066        let para_range = self.0.syntax().text_range();
1067        para_range.start() < range.end() && range.start() < para_range.end()
1068    }
1069
1070    /// Get fields in this binary paragraph that overlap with the given range
1071    ///
1072    /// # Arguments
1073    /// * `range` - The text range to check for overlaps
1074    ///
1075    /// # Returns
1076    /// An iterator over Entry items that overlap with the given range
1077    pub fn fields_in_range(
1078        &self,
1079        range: rowan::TextRange,
1080    ) -> impl Iterator<Item = deb822_lossless::Entry> + '_ {
1081        self.0.entries().filter(move |entry| {
1082            let entry_range = entry.syntax().text_range();
1083            entry_range.start() < range.end() && range.start() < entry_range.end()
1084        })
1085    }
1086}
1087
1088#[cfg(test)]
1089mod tests {
1090    use super::*;
1091    use crate::relations::VersionConstraint;
1092
1093    #[test]
1094    fn test_source_set_field_ordering() {
1095        let mut control = Control::new();
1096        let mut source = control.add_source("mypackage");
1097
1098        // Add fields in random order
1099        source.set("Homepage", "https://example.com");
1100        source.set("Build-Depends", "debhelper");
1101        source.set("Standards-Version", "4.5.0");
1102        source.set("Maintainer", "Test <test@example.com>");
1103
1104        // Convert to string and check field order
1105        let output = source.to_string();
1106        let lines: Vec<&str> = output.lines().collect();
1107
1108        // Source should be first
1109        assert!(lines[0].starts_with("Source:"));
1110
1111        // Find the positions of each field
1112        let maintainer_pos = lines
1113            .iter()
1114            .position(|l| l.starts_with("Maintainer:"))
1115            .unwrap();
1116        let build_depends_pos = lines
1117            .iter()
1118            .position(|l| l.starts_with("Build-Depends:"))
1119            .unwrap();
1120        let standards_pos = lines
1121            .iter()
1122            .position(|l| l.starts_with("Standards-Version:"))
1123            .unwrap();
1124        let homepage_pos = lines
1125            .iter()
1126            .position(|l| l.starts_with("Homepage:"))
1127            .unwrap();
1128
1129        // Check ordering according to SOURCE_FIELD_ORDER
1130        assert!(maintainer_pos < build_depends_pos);
1131        assert!(build_depends_pos < standards_pos);
1132        assert!(standards_pos < homepage_pos);
1133    }
1134
1135    #[test]
1136    fn test_binary_set_field_ordering() {
1137        let mut control = Control::new();
1138        let mut binary = control.add_binary("mypackage");
1139
1140        // Add fields in random order
1141        binary.set("Description", "A test package");
1142        binary.set("Architecture", "amd64");
1143        binary.set("Depends", "libc6");
1144        binary.set("Section", "utils");
1145
1146        // Convert to string and check field order
1147        let output = binary.to_string();
1148        let lines: Vec<&str> = output.lines().collect();
1149
1150        // Package should be first
1151        assert!(lines[0].starts_with("Package:"));
1152
1153        // Find the positions of each field
1154        let arch_pos = lines
1155            .iter()
1156            .position(|l| l.starts_with("Architecture:"))
1157            .unwrap();
1158        let section_pos = lines
1159            .iter()
1160            .position(|l| l.starts_with("Section:"))
1161            .unwrap();
1162        let depends_pos = lines
1163            .iter()
1164            .position(|l| l.starts_with("Depends:"))
1165            .unwrap();
1166        let desc_pos = lines
1167            .iter()
1168            .position(|l| l.starts_with("Description:"))
1169            .unwrap();
1170
1171        // Check ordering according to BINARY_FIELD_ORDER
1172        assert!(arch_pos < section_pos);
1173        assert!(section_pos < depends_pos);
1174        assert!(depends_pos < desc_pos);
1175    }
1176
1177    #[test]
1178    fn test_source_specific_set_methods_use_field_ordering() {
1179        let mut control = Control::new();
1180        let mut source = control.add_source("mypackage");
1181
1182        // Use specific set_* methods in random order
1183        source.set_homepage(&"https://example.com".parse().unwrap());
1184        source.set_maintainer("Test <test@example.com>");
1185        source.set_standards_version("4.5.0");
1186        source.set_vcs_git("https://github.com/example/repo");
1187
1188        // Convert to string and check field order
1189        let output = source.to_string();
1190        let lines: Vec<&str> = output.lines().collect();
1191
1192        // Find the positions of each field
1193        let source_pos = lines.iter().position(|l| l.starts_with("Source:")).unwrap();
1194        let maintainer_pos = lines
1195            .iter()
1196            .position(|l| l.starts_with("Maintainer:"))
1197            .unwrap();
1198        let standards_pos = lines
1199            .iter()
1200            .position(|l| l.starts_with("Standards-Version:"))
1201            .unwrap();
1202        let vcs_git_pos = lines
1203            .iter()
1204            .position(|l| l.starts_with("Vcs-Git:"))
1205            .unwrap();
1206        let homepage_pos = lines
1207            .iter()
1208            .position(|l| l.starts_with("Homepage:"))
1209            .unwrap();
1210
1211        // Check ordering according to SOURCE_FIELD_ORDER
1212        assert!(source_pos < maintainer_pos);
1213        assert!(maintainer_pos < standards_pos);
1214        assert!(standards_pos < vcs_git_pos);
1215        assert!(vcs_git_pos < homepage_pos);
1216    }
1217
1218    #[test]
1219    fn test_binary_specific_set_methods_use_field_ordering() {
1220        let mut control = Control::new();
1221        let mut binary = control.add_binary("mypackage");
1222
1223        // Use specific set_* methods in random order
1224        binary.set_description(Some("A test package"));
1225        binary.set_architecture(Some("amd64"));
1226        let depends = "libc6".parse().unwrap();
1227        binary.set_depends(Some(&depends));
1228        binary.set_section(Some("utils"));
1229        binary.set_priority(Some(Priority::Optional));
1230
1231        // Convert to string and check field order
1232        let output = binary.to_string();
1233        let lines: Vec<&str> = output.lines().collect();
1234
1235        // Find the positions of each field
1236        let package_pos = lines
1237            .iter()
1238            .position(|l| l.starts_with("Package:"))
1239            .unwrap();
1240        let arch_pos = lines
1241            .iter()
1242            .position(|l| l.starts_with("Architecture:"))
1243            .unwrap();
1244        let section_pos = lines
1245            .iter()
1246            .position(|l| l.starts_with("Section:"))
1247            .unwrap();
1248        let priority_pos = lines
1249            .iter()
1250            .position(|l| l.starts_with("Priority:"))
1251            .unwrap();
1252        let depends_pos = lines
1253            .iter()
1254            .position(|l| l.starts_with("Depends:"))
1255            .unwrap();
1256        let desc_pos = lines
1257            .iter()
1258            .position(|l| l.starts_with("Description:"))
1259            .unwrap();
1260
1261        // Check ordering according to BINARY_FIELD_ORDER
1262        assert!(package_pos < arch_pos);
1263        assert!(arch_pos < section_pos);
1264        assert!(section_pos < priority_pos);
1265        assert!(priority_pos < depends_pos);
1266        assert!(depends_pos < desc_pos);
1267    }
1268
1269    #[test]
1270    fn test_parse() {
1271        let control: Control = r#"Source: foo
1272Section: libs
1273Priority: optional
1274Build-Depends: bar (>= 1.0.0), baz (>= 1.0.0)
1275Homepage: https://example.com
1276
1277"#
1278        .parse()
1279        .unwrap();
1280        let source = control.source().unwrap();
1281
1282        assert_eq!(source.name(), Some("foo".to_owned()));
1283        assert_eq!(source.section(), Some("libs".to_owned()));
1284        assert_eq!(source.priority(), Some(super::Priority::Optional));
1285        assert_eq!(
1286            source.homepage(),
1287            Some("https://example.com".parse().unwrap())
1288        );
1289        let bd = source.build_depends().unwrap();
1290        let entries = bd.entries().collect::<Vec<_>>();
1291        assert_eq!(entries.len(), 2);
1292        let rel = entries[0].relations().collect::<Vec<_>>().pop().unwrap();
1293        assert_eq!(rel.name(), "bar");
1294        assert_eq!(
1295            rel.version(),
1296            Some((
1297                VersionConstraint::GreaterThanEqual,
1298                "1.0.0".parse().unwrap()
1299            ))
1300        );
1301        let rel = entries[1].relations().collect::<Vec<_>>().pop().unwrap();
1302        assert_eq!(rel.name(), "baz");
1303        assert_eq!(
1304            rel.version(),
1305            Some((
1306                VersionConstraint::GreaterThanEqual,
1307                "1.0.0".parse().unwrap()
1308            ))
1309        );
1310    }
1311
1312    #[test]
1313    fn test_description() {
1314        let control: Control = r#"Source: foo
1315
1316Package: foo
1317Description: this is the short description
1318 And the longer one
1319 .
1320 is on the next lines
1321"#
1322        .parse()
1323        .unwrap();
1324        let binary = control.binaries().next().unwrap();
1325        assert_eq!(
1326            binary.description(),
1327            Some(
1328                "this is the short description\nAnd the longer one\n.\nis on the next lines"
1329                    .to_owned()
1330            )
1331        );
1332    }
1333
1334    #[test]
1335    fn test_as_mut_deb822() {
1336        let mut control = Control::new();
1337        let deb822 = control.as_mut_deb822();
1338        let mut p = deb822.add_paragraph();
1339        p.set("Source", "foo");
1340        assert_eq!(control.source().unwrap().name(), Some("foo".to_owned()));
1341    }
1342
1343    #[test]
1344    fn test_as_deb822() {
1345        let control = Control::new();
1346        let _deb822: &Deb822 = control.as_deb822();
1347    }
1348
1349    #[test]
1350    fn test_set_depends() {
1351        let mut control = Control::new();
1352        let mut binary = control.add_binary("foo");
1353        let relations: Relations = "bar (>= 1.0.0)".parse().unwrap();
1354        binary.set_depends(Some(&relations));
1355    }
1356
1357    #[test]
1358    fn test_wrap_and_sort() {
1359        let mut control: Control = r#"Package: blah
1360Section:     libs
1361
1362
1363
1364Package: foo
1365Description: this is a 
1366      bar
1367      blah
1368"#
1369        .parse()
1370        .unwrap();
1371        control.wrap_and_sort(deb822_lossless::Indentation::Spaces(2), false, None);
1372        let expected = r#"Package: blah
1373Section: libs
1374
1375Package: foo
1376Description: this is a 
1377  bar
1378  blah
1379"#
1380        .to_owned();
1381        assert_eq!(control.to_string(), expected);
1382    }
1383
1384    #[test]
1385    fn test_wrap_and_sort_source() {
1386        let mut control: Control = r#"Source: blah
1387Depends: foo, bar   (<=  1.0.0)
1388
1389"#
1390        .parse()
1391        .unwrap();
1392        control.wrap_and_sort(deb822_lossless::Indentation::Spaces(2), true, None);
1393        let expected = r#"Source: blah
1394Depends: bar (<= 1.0.0), foo
1395"#
1396        .to_owned();
1397        assert_eq!(control.to_string(), expected);
1398    }
1399
1400    #[test]
1401    fn test_source_wrap_and_sort() {
1402        let control: Control = r#"Source: blah
1403Build-Depends: foo, bar (>= 1.0.0)
1404
1405"#
1406        .parse()
1407        .unwrap();
1408        let mut source = control.source().unwrap();
1409        source.wrap_and_sort(deb822_lossless::Indentation::Spaces(2), true, None);
1410        // The actual behavior - the method modifies the source in-place
1411        // but doesn't automatically affect the overall control structure
1412        // So we just test that the method executes without error
1413        assert!(source.build_depends().is_some());
1414    }
1415
1416    #[test]
1417    fn test_binary_set_breaks() {
1418        let mut control = Control::new();
1419        let mut binary = control.add_binary("foo");
1420        let relations: Relations = "bar (>= 1.0.0)".parse().unwrap();
1421        binary.set_breaks(Some(&relations));
1422        assert!(binary.breaks().is_some());
1423    }
1424
1425    #[test]
1426    fn test_binary_set_pre_depends() {
1427        let mut control = Control::new();
1428        let mut binary = control.add_binary("foo");
1429        let relations: Relations = "bar (>= 1.0.0)".parse().unwrap();
1430        binary.set_pre_depends(Some(&relations));
1431        assert!(binary.pre_depends().is_some());
1432    }
1433
1434    #[test]
1435    fn test_binary_set_provides() {
1436        let mut control = Control::new();
1437        let mut binary = control.add_binary("foo");
1438        let relations: Relations = "bar (>= 1.0.0)".parse().unwrap();
1439        binary.set_provides(Some(&relations));
1440        assert!(binary.provides().is_some());
1441    }
1442
1443    #[test]
1444    fn test_source_build_conflicts() {
1445        let control: Control = r#"Source: blah
1446Build-Conflicts: foo, bar (>= 1.0.0)
1447
1448"#
1449        .parse()
1450        .unwrap();
1451        let source = control.source().unwrap();
1452        let conflicts = source.build_conflicts();
1453        assert!(conflicts.is_some());
1454    }
1455
1456    #[test]
1457    fn test_source_vcs_svn() {
1458        let control: Control = r#"Source: blah
1459Vcs-Svn: https://example.com/svn/repo
1460
1461"#
1462        .parse()
1463        .unwrap();
1464        let source = control.source().unwrap();
1465        assert_eq!(
1466            source.vcs_svn(),
1467            Some("https://example.com/svn/repo".to_string())
1468        );
1469    }
1470
1471    #[test]
1472    fn test_control_from_conversion() {
1473        let deb822_data = r#"Source: test
1474Section: libs
1475
1476"#;
1477        let deb822: Deb822 = deb822_data.parse().unwrap();
1478        let control = Control::from(deb822);
1479        assert!(control.source().is_some());
1480    }
1481
1482    #[test]
1483    fn test_fields_in_range() {
1484        let control_text = r#"Source: test-package
1485Maintainer: Test User <test@example.com>
1486Build-Depends: debhelper (>= 12)
1487
1488Package: test-binary
1489Architecture: any
1490Depends: ${shlibs:Depends}
1491Description: Test package
1492 This is a test package
1493"#;
1494        let control: Control = control_text.parse().unwrap();
1495
1496        // Test range that covers only the Source field
1497        let source_start = 0;
1498        let source_end = "Source: test-package".len();
1499        let source_range =
1500            rowan::TextRange::new((source_start as u32).into(), (source_end as u32).into());
1501
1502        let fields: Vec<_> = control.fields_in_range(source_range).collect();
1503        assert_eq!(fields.len(), 1);
1504        assert_eq!(fields[0].key(), Some("Source".to_string()));
1505
1506        // Test range that covers multiple fields in source paragraph
1507        let maintainer_start = control_text.find("Maintainer:").unwrap();
1508        let build_depends_end = control_text
1509            .find("Build-Depends: debhelper (>= 12)")
1510            .unwrap()
1511            + "Build-Depends: debhelper (>= 12)".len();
1512        let multi_range = rowan::TextRange::new(
1513            (maintainer_start as u32).into(),
1514            (build_depends_end as u32).into(),
1515        );
1516
1517        let fields: Vec<_> = control.fields_in_range(multi_range).collect();
1518        assert_eq!(fields.len(), 2);
1519        assert_eq!(fields[0].key(), Some("Maintainer".to_string()));
1520        assert_eq!(fields[1].key(), Some("Build-Depends".to_string()));
1521
1522        // Test range that spans across paragraphs
1523        let cross_para_start = control_text.find("Build-Depends:").unwrap();
1524        let cross_para_end =
1525            control_text.find("Architecture: any").unwrap() + "Architecture: any".len();
1526        let cross_range = rowan::TextRange::new(
1527            (cross_para_start as u32).into(),
1528            (cross_para_end as u32).into(),
1529        );
1530
1531        let fields: Vec<_> = control.fields_in_range(cross_range).collect();
1532        assert_eq!(fields.len(), 3); // Build-Depends, Package, Architecture
1533        assert_eq!(fields[0].key(), Some("Build-Depends".to_string()));
1534        assert_eq!(fields[1].key(), Some("Package".to_string()));
1535        assert_eq!(fields[2].key(), Some("Architecture".to_string()));
1536
1537        // Test empty range (should return no fields)
1538        let empty_range = rowan::TextRange::new(1000.into(), 1001.into());
1539        let fields: Vec<_> = control.fields_in_range(empty_range).collect();
1540        assert_eq!(fields.len(), 0);
1541    }
1542
1543    #[test]
1544    fn test_source_overlaps_range() {
1545        let control_text = r#"Source: test-package
1546Maintainer: Test User <test@example.com>
1547
1548Package: test-binary
1549Architecture: any
1550"#;
1551        let control: Control = control_text.parse().unwrap();
1552        let source = control.source().unwrap();
1553
1554        // Test range that overlaps with source paragraph
1555        let overlap_range = rowan::TextRange::new(10.into(), 30.into());
1556        assert!(source.overlaps_range(overlap_range));
1557
1558        // Test range that doesn't overlap with source paragraph
1559        let binary_start = control_text.find("Package:").unwrap();
1560        let no_overlap_range = rowan::TextRange::new(
1561            (binary_start as u32).into(),
1562            ((binary_start + 20) as u32).into(),
1563        );
1564        assert!(!source.overlaps_range(no_overlap_range));
1565
1566        // Test range that starts before and ends within source paragraph
1567        let partial_overlap = rowan::TextRange::new(0.into(), 15.into());
1568        assert!(source.overlaps_range(partial_overlap));
1569    }
1570
1571    #[test]
1572    fn test_source_fields_in_range() {
1573        let control_text = r#"Source: test-package
1574Maintainer: Test User <test@example.com>
1575Build-Depends: debhelper (>= 12)
1576
1577Package: test-binary
1578"#;
1579        let control: Control = control_text.parse().unwrap();
1580        let source = control.source().unwrap();
1581
1582        // Test range covering Maintainer field
1583        let maintainer_start = control_text.find("Maintainer:").unwrap();
1584        let maintainer_end = maintainer_start + "Maintainer: Test User <test@example.com>".len();
1585        let maintainer_range = rowan::TextRange::new(
1586            (maintainer_start as u32).into(),
1587            (maintainer_end as u32).into(),
1588        );
1589
1590        let fields: Vec<_> = source.fields_in_range(maintainer_range).collect();
1591        assert_eq!(fields.len(), 1);
1592        assert_eq!(fields[0].key(), Some("Maintainer".to_string()));
1593
1594        // Test range covering multiple fields
1595        let all_source_range = rowan::TextRange::new(0.into(), 100.into());
1596        let fields: Vec<_> = source.fields_in_range(all_source_range).collect();
1597        assert_eq!(fields.len(), 3); // Source, Maintainer, Build-Depends
1598    }
1599
1600    #[test]
1601    fn test_binary_overlaps_range() {
1602        let control_text = r#"Source: test-package
1603
1604Package: test-binary
1605Architecture: any
1606Depends: ${shlibs:Depends}
1607"#;
1608        let control: Control = control_text.parse().unwrap();
1609        let binary = control.binaries().next().unwrap();
1610
1611        // Test range that overlaps with binary paragraph
1612        let package_start = control_text.find("Package:").unwrap();
1613        let overlap_range = rowan::TextRange::new(
1614            (package_start as u32).into(),
1615            ((package_start + 30) as u32).into(),
1616        );
1617        assert!(binary.overlaps_range(overlap_range));
1618
1619        // Test range before binary paragraph
1620        let no_overlap_range = rowan::TextRange::new(0.into(), 10.into());
1621        assert!(!binary.overlaps_range(no_overlap_range));
1622    }
1623
1624    #[test]
1625    fn test_binary_fields_in_range() {
1626        let control_text = r#"Source: test-package
1627
1628Package: test-binary
1629Architecture: any
1630Depends: ${shlibs:Depends}
1631Description: Test binary
1632 This is a test binary package
1633"#;
1634        let control: Control = control_text.parse().unwrap();
1635        let binary = control.binaries().next().unwrap();
1636
1637        // Test range covering Architecture and Depends
1638        let arch_start = control_text.find("Architecture:").unwrap();
1639        let depends_end = control_text.find("Depends: ${shlibs:Depends}").unwrap()
1640            + "Depends: ${shlibs:Depends}".len();
1641        let range = rowan::TextRange::new((arch_start as u32).into(), (depends_end as u32).into());
1642
1643        let fields: Vec<_> = binary.fields_in_range(range).collect();
1644        assert_eq!(fields.len(), 2);
1645        assert_eq!(fields[0].key(), Some("Architecture".to_string()));
1646        assert_eq!(fields[1].key(), Some("Depends".to_string()));
1647
1648        // Test partial overlap with Description field
1649        let desc_start = control_text.find("Description:").unwrap();
1650        let partial_range = rowan::TextRange::new(
1651            ((desc_start + 5) as u32).into(),
1652            ((desc_start + 15) as u32).into(),
1653        );
1654        let fields: Vec<_> = binary.fields_in_range(partial_range).collect();
1655        assert_eq!(fields.len(), 1);
1656        assert_eq!(fields[0].key(), Some("Description".to_string()));
1657    }
1658
1659    #[test]
1660    fn test_incremental_parsing_use_case() {
1661        // This test simulates a real LSP use case where only changed fields are processed
1662        let control_text = r#"Source: example
1663Maintainer: John Doe <john@example.com>
1664Standards-Version: 4.6.0
1665Build-Depends: debhelper-compat (= 13)
1666
1667Package: example-bin
1668Architecture: all
1669Depends: ${misc:Depends}
1670Description: Example package
1671 This is an example.
1672"#;
1673        let control: Control = control_text.parse().unwrap();
1674
1675        // Simulate a change to Standards-Version field
1676        let change_start = control_text.find("Standards-Version:").unwrap();
1677        let change_end = change_start + "Standards-Version: 4.6.0".len();
1678        let change_range =
1679            rowan::TextRange::new((change_start as u32).into(), (change_end as u32).into());
1680
1681        // Only process fields in the changed range
1682        let affected_fields: Vec<_> = control.fields_in_range(change_range).collect();
1683        assert_eq!(affected_fields.len(), 1);
1684        assert_eq!(
1685            affected_fields[0].key(),
1686            Some("Standards-Version".to_string())
1687        );
1688
1689        // Verify that we're not processing unrelated fields
1690        for entry in &affected_fields {
1691            let key = entry.key().unwrap();
1692            assert_ne!(key, "Maintainer");
1693            assert_ne!(key, "Build-Depends");
1694            assert_ne!(key, "Architecture");
1695        }
1696    }
1697
1698    #[test]
1699    fn test_positioned_parse_errors() {
1700        // Test case from the requirements document
1701        let input = "Invalid: field\nBroken field without colon";
1702        let parsed = Control::parse(input);
1703
1704        // Should have positioned errors accessible
1705        let positioned_errors = parsed.positioned_errors();
1706        assert!(
1707            !positioned_errors.is_empty(),
1708            "Should have positioned errors"
1709        );
1710
1711        // Test that we can access error properties
1712        for error in positioned_errors {
1713            let start_offset: u32 = error.range.start().into();
1714            let end_offset: u32 = error.range.end().into();
1715
1716            // Verify we have meaningful error messages
1717            assert!(!error.message.is_empty());
1718
1719            // Verify ranges are valid
1720            assert!(start_offset <= end_offset);
1721            assert!(end_offset <= input.len() as u32);
1722
1723            // Error should have a code
1724            assert!(error.code.is_some());
1725
1726            println!(
1727                "Error at {:?}: {} (code: {:?})",
1728                error.range, error.message, error.code
1729            );
1730        }
1731
1732        // Should also be able to get string errors for backward compatibility
1733        let string_errors = parsed.errors();
1734        assert!(!string_errors.is_empty());
1735        assert_eq!(string_errors.len(), positioned_errors.len());
1736    }
1737}