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