alpm_pkgbuild/bridge/
parser.rs

1//! Parsing of the [`alpm-pkgbuild-bridge`] script output, which prints package metadata contained
2//! in [`PKGBUILD`] files.
3//!
4//! This module handles the parsing of output generated by the [`alpm-pkgbuild-bridge`] script,
5//! which is shipped as a separate package. At this step, we **do not** yet ensure the correctness
6//! of the parsed data, instead we only parse the data structure of the output and bring it into an
7//! easy to handle struct.
8//! You can think of it as an equivalent of serde's intermediate `Value`.
9//!
10//! The output of the script has a well-known structure and provides some guarantees that we'll use
11//! to keep the parsers simple.
12//!
13//! Overall format specification:
14//! 1. Variables are declared one-line per variable.
15//! 1. All variables declaration lines begin with `VAR`
16//! 1. All variable values are surrounded by double quotes `"`. If a value contains double quotes,
17//!    those double quotes are escaped with a backslash: `\"`.
18//! 1. If a variable is an array, the array's values are represented as space separated items on the
19//!    same line. Each value is surrounded by double quotes.
20//! 1. Variables **cannot** be declared twice.
21//! 1. pkgbase variable declarations look as follows:
22//!   
23//!    ```text
24//!    VAR GLOBAL [ARRAY|STRING] $KEY $VALUES
25//!    Examples:
26//!    VAR GLOBAL STRING pkgrel "6"
27//!    VAR GLOBAL ARRAY license "GPL-2.0-or-later" "CCPL"
28//!    ```
29//! 1. package function variable declarations look as follows:
30//!   
31//!    ```txt
32//!    VAR FUNCTION $PACKAGE_NAME [ARRAY|STRING] $KEY $VALUES
33//!    Examples:
34//!    VAR FUNCTION package STRING pkgdesc "My normal package"
35//!    VAR FUNCTION package_my-split-package STRING pkgdesc "My special split package"
36//!    VAR FUNCTION package_my-split-package ARRAY depends "cargo" "glibc"
37//!    ```
38//! 1. Package overrides that clear a value/array simply don't have any string values.
39//!   
40//!    Example:
41//!   
42//!    ```text
43//!    VAR FUNCTION package_my-split-package ARRAY depends
44//!    ```
45//! 1. Split-/Package functions are declared as: `FUNCTION $PACKAGE_NAME`
46//!   
47//!    Example:
48//!    
49//!    ```text
50//!    FUNCTION package_my-split-package
51//!    ```
52//!    
53//!    Note that single-package PKGBUILD files don't necessarily need to have the `package_$NAME`
54//!    format, but rather may just have a single `package` function.
55//!
56//! Structure guarantees:
57//!
58//! - There are no empty lines.
59//! - Global variables (those that apply to all packages defined in a [`PKGBUILD`]) are declared
60//!   first.
61//! - Variables that are declared in package functions of the [`PKGBUILD`] are always returned after
62//!   the global variables and are grouped by package.
63//! - The declarations of package function names are always returned at the end (after all package
64//!   function variables are returned).
65//!
66//! [`PKGBUILD`]: https://man.archlinux.org/man/PKGBUILD.5
67//! [`alpm-pkgbuild-bridge`]: https://gitlab.archlinux.org/archlinux/alpm/alpm-pkgbuild-bridge
68
69use std::{collections::HashMap, fmt::Display, path::Path, str::FromStr};
70
71use alpm_parsers::iter_str_context;
72#[cfg(doc)]
73use alpm_types::Architecture;
74use strum::{EnumString, VariantNames};
75use winnow::{
76    ModalResult,
77    Parser,
78    ascii::{alphanumeric1, space0, space1},
79    combinator::{alt, cut_err, delimited, eof, opt, preceded, repeat, terminated, trace},
80    error::{StrContext, StrContextValue},
81    token::{none_of, one_of, take_till, take_until},
82};
83
84use crate::{bridge::run_bridge_script, error::Error};
85
86/// A single value or a list of values declared in the output of the `alpm-pkgbuild-bridge` script.
87///
88/// Both parser functions ensure that at least one value exists.
89#[derive(Clone, Debug)]
90pub enum Value {
91    /// A single value.
92    Single(String),
93    /// An array of values.
94    Array(Vec<String>),
95}
96
97impl Value {
98    /// Returns the values of `&self` in vector representation.
99    ///
100    /// This is useful for values that may be available as both single values and arrays.
101    pub fn as_vec(&self) -> Vec<&String> {
102        match self {
103            Value::Single(item) => vec![&item],
104            Value::Array(items) => Vec::from_iter(items.iter()),
105        }
106    }
107
108    /// Returns the values of `self` in vector representation.
109    ///
110    /// This is useful for values that may be available as both single values and arrays.
111    pub fn as_owned_vec(self) -> Vec<String> {
112        match self {
113            Value::Single(item) => vec![item],
114            Value::Array(items) => items,
115        }
116    }
117
118    /// Checks whether this holds a value.
119    ///
120    /// Returns `true` if `self` is [`Value::Single`] (they always have a value set by definition),
121    /// or if `self` is [`Value::Array`] and contains at least one element.
122    /// Returns `false` in all other cases.
123    pub fn has_value(&self) -> bool {
124        match self {
125            Value::Single(_) => true,
126            Value::Array(items) => !items.is_empty(),
127        }
128    }
129
130    /// Recognizes a [`Value::Single`] in a string slice while handling the surroundings.
131    ///
132    /// Consumes leading and trailing spaces while also consuming newlines.
133    /// Also allows variables at the end of the file where no newline is found.
134    /// Calls [`Value::parse_next_value`] for variable parsing.
135    ///
136    /// # Errors
137    ///
138    /// Returns an error if no [`Value::Single`] can be recognized in `input`.
139    fn single_till_newline(input: &mut &str) -> ModalResult<Self> {
140        cut_err(delimited(
141            space1,
142            Self::parse_next_value,
143            (space0, alt(("\n", eof))),
144        ))
145        .context(StrContext::Label("variable"))
146        .map(Value::Single)
147        .parse_next(input)
148    }
149
150    /// Recognizes a [`Value::Array`] in a string slice while handling the surroundings.
151    ///
152    /// Consumes leading and trailing spaces while also consuming newlines.
153    /// Also allows variables at the end of the file where no newline is found.
154    /// Calls [`Self::parse_next_value`] for variable parsing.
155    ///
156    /// # Errors
157    ///
158    /// Returns an error if no [`Value::Array`] is found in `input`.
159    fn list_till_newline(input: &mut &str) -> ModalResult<Self> {
160        let values = repeat(0.., preceded(space1, Value::parse_next_value))
161            .map(Value::Array)
162            .parse_next(input)?;
163
164        // Make sure we find the end of the line after the last element in the list.
165        cut_err(preceded(space0, alt(("\n", eof))))
166            .context(StrContext::Label("character"))
167            .context(StrContext::Expected(StrContextValue::Description(
168                "end of line or end of file.",
169            )))
170            .parse_next(input)?;
171
172        Ok(values)
173    }
174
175    /// Recognizes a [`Value::Single`] in a string slice.
176    ///
177    /// Calls [`Value::variable_character`] to handle escaped characters in `input`.
178    ///
179    /// # Examples
180    ///
181    /// ```text
182    /// "This is a string value\" with escaped \\ characters"
183    /// ```
184    ///
185    /// # Errors
186    ///
187    /// Returns an error if no [`Value::Single`] is found in `input`.
188    pub fn parse_next_value(input: &mut &str) -> ModalResult<String> {
189        // We expect a value surrounded with double quotes (") that may contain escaped characters.
190        // As we resolve the escaping, we cannot just `.take()`. Instead we have to accumulate in
191        // a `String`.
192        let string_value = trace(
193            "variable",
194            preceded(
195                '"',
196                cut_err(
197                    terminated(
198                        repeat(0.., Self::variable_character).fold(String::new, |mut string, c| {
199                            string.push(c);
200                            string
201                        }),
202                        '"',
203                    )
204                    .context(StrContext::Label("variable"))
205                    .context(StrContext::Expected(
206                        StrContextValue::Description("A string surrounded by double quotes"),
207                    )),
208                ),
209            ),
210        )
211        .parse_next(input)?;
212
213        Ok(string_value)
214    }
215
216    /// Recognizes a single [`char`] in a string slice.
217    ///
218    /// This allows for detecting escaped characters, such as `\"` or `\\`.
219    ///
220    /// Consumes a single character and returns afterwards.
221    ///
222    /// # Errors
223    ///
224    /// Returns an error if a stand-alone `"` or `\` is encountered.
225    pub fn variable_character(input: &mut &str) -> ModalResult<char> {
226        let c = none_of('"').parse_next(input)?;
227        if c == '\\' {
228            cut_err(one_of(['"', '\\']))
229                .context(StrContext::Label("escaped sequence"))
230                .context(StrContext::Expected(StrContextValue::Description(
231                    "one of the allowed escape characters: ['\"', '\\']",
232                )))
233                .parse_next(input)
234        } else {
235            Ok(c)
236        }
237    }
238}
239
240/// Represents a potentially cleared or overridden value.
241///
242/// This type is used for values in `package` sections where values from a `pkgbase` section might
243/// be cleared or overridden.
244#[derive(Clone, Debug)]
245pub enum ClearableValue {
246    /// A single value.
247    Single(Option<String>),
248    /// An array of values.
249    Array(Option<Vec<String>>),
250}
251
252impl ClearableValue {
253    /// Recognizes a [`ClearableValue::Single`] while handling the surroundings.
254    ///
255    /// Cleared values are represented by non-existing values.
256    ///
257    /// Consumes leading and trailing spaces while also consuming newlines.
258    /// Also allows variables at the end of the file where no newline is found.
259    /// Calls [`Value::parse_next_value`] for variable parsing.
260    ///
261    /// # Errors
262    ///
263    /// Returns an error if no [`ClearableValue::Single`] is found in `input`.
264    fn single_till_newline(input: &mut &str) -> ModalResult<Self> {
265        let value = preceded(
266            space1,
267            terminated(opt(Value::parse_next_value), (space0, alt(("\n", eof)))),
268        )
269        .parse_next(input)?;
270
271        let Some(value) = value else {
272            return Ok(ClearableValue::Single(None));
273        };
274
275        // Returns a "Cleared" value if the value is an empty string.
276        if value.is_empty() {
277            return Ok(ClearableValue::Single(None));
278        }
279
280        Ok(ClearableValue::Single(Some(value)))
281    }
282
283    /// Recognizes a [`ClearableValue::Array`] while handling the surroundings.
284    ///
285    /// Cleared values are represented by the non-existance of a value.
286    ///
287    /// Consumes leading and trailing spaces while also consuming newlines.
288    /// Also allows variables at the end of the file where no newline is found.
289    /// Calls [`Value::parse_next_value`] for variable parsing.
290    ///
291    /// # Errors
292    ///
293    /// Returns an error if no [`ClearableValue::Array`] is found in `input`.
294    fn list_till_newline(input: &mut &str) -> ModalResult<Self> {
295        let values = opt(repeat(1.., preceded(space1, Value::parse_next_value)))
296            .map(ClearableValue::Array)
297            .parse_next(input)?;
298
299        // Make sure we find the end of the line after the last element in the list.
300        cut_err(preceded(space0, alt(("\n", eof))))
301            .context(StrContext::Label("character"))
302            .context(StrContext::Expected(StrContextValue::Description(
303                "end of line or end of file.",
304            )))
305            .parse_next(input)?;
306
307        Ok(values)
308    }
309}
310
311/// A keyword (with an optional [`Architecture`] suffix) of a variable found in the output of the
312/// `alpm-pkgbuild-bridge` script.
313#[derive(Clone, Debug, Eq, Hash, PartialEq)]
314pub struct Keyword {
315    /// The name of the keyword.
316    pub keyword: String,
317    /// The optional `_$suffix` that indicates a specific architecture.
318    pub suffix: Option<String>,
319}
320
321impl Display for Keyword {
322    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
323        write!(f, "{}", self.keyword)?;
324        if let Some(suffix) = &self.suffix {
325            write!(f, "_{suffix}")?;
326        }
327
328        Ok(())
329    }
330}
331
332impl Keyword {
333    /// Creates a [`Keyword`] without an [`Architecture`] suffix from a string.
334    pub fn simple<T: ToString>(keyword: T) -> Self {
335        Self {
336            keyword: keyword.to_string(),
337            suffix: None,
338        }
339    }
340
341    /// Recognizes a [`Keyword`] in a variable declaration line in the output of the
342    /// `alpm-pkgbuild-bridge` script.
343    ///
344    /// This parser is aware of its surrounding and consumes preceding spaces.
345    ///
346    /// # Errors
347    ///
348    /// Returns an error if no [`Keyword`] can be found in `input`.
349    pub(crate) fn parser(input: &mut &str) -> ModalResult<Keyword> {
350        let (keyword, suffix) = trace(
351            "keyword",
352            cut_err(preceded(
353                space1,
354                (
355                    alphanumeric1,
356                    opt(preceded('_', take_till(1.., |c| c == ' ' || c == '\n'))),
357                ),
358            ))
359            .context(StrContext::Label(
360                "keyword with potential architecture suffix, e.g. 'source_x86_64'",
361            ))
362            .context(StrContext::Expected(StrContextValue::Description(
363                "alphabetic keyword with potential architecture suffix, e.g. 'source_x86_64'",
364            ))),
365        )
366        .parse_next(input)?;
367        Ok(Keyword {
368            keyword: keyword.to_owned(),
369            suffix: suffix.map(ToString::to_string),
370        })
371    }
372}
373
374/// A type of variable found in the output of the `alpm-pkgbuild-bridge` script.
375#[derive(Debug, EnumString, VariantNames)]
376#[strum(serialize_all = "UPPERCASE")]
377enum VariableType {
378    Array,
379    String,
380}
381
382impl VariableType {
383    /// Recognizes a [`VariableType`] in a variable declaration line in the output of the
384    /// `alpm-pkgbuild-bridge` script.
385    ///
386    /// This parser is aware of its surrounding and consumes preceding spaces.
387    ///
388    /// # Errors
389    ///
390    /// Returns an error if no [`VariableType`] is found in `input`.
391    pub fn parser(input: &mut &str) -> ModalResult<VariableType> {
392        trace(
393            "variable_type",
394            cut_err(preceded(
395                space1,
396                take_until(1.., ' ').try_map(VariableType::from_str),
397            ))
398            .context_with(iter_str_context!([VariableType::VARIANTS])),
399        )
400        .parse_next(input)
401    }
402}
403
404/// A raw, unchecked package name.
405///
406/// Functions and variables may be specific for a package in a [`PKGBUILD`].
407///
408/// If a [`PKGBUILD`] only defines a single package, the package's `package` function will be named
409/// `package`. This is represented by `RawPackageName(None)`.
410///
411/// If a [`PKGBUILD`] defines one or more [alpm-split-package]s, there are as many custom `package`
412/// functions as there are split packages. Here, each function is named `package_<name>`, where
413/// `<name>` denotes an [alpm-package-name]. This is represented by `RawPackageName(Some(name))`.
414///
415/// [`PKGBUILD`]: https://man.archlinux.org/man/PKGBUILD.5
416/// [alpm-package-name]: https://alpm.archlinux.page/specifications/alpm-package-name.7.html
417/// [alpm-split-package]: https://alpm.archlinux.page/specifications/alpm-split-package.7.html
418#[derive(Clone, Debug, Eq, Hash, PartialEq)]
419pub struct RawPackageName(pub Option<String>);
420
421impl RawPackageName {
422    /// Recognizes a [`RawPackageName`] in the output of the `alpm-pkgbuild-bride` script.
423    ///
424    /// This parser is aware of its surrounding and consumes preceding spaces and until the eol.
425    ///
426    /// # Errors
427    ///
428    /// Returns an error if no [`RawPackageName`] is found in `input`.
429    pub(crate) fn parser(input: &mut &str) -> ModalResult<Self> {
430        let package_name = trace(
431            "PackageName",
432            cut_err(preceded(
433                (space1, "package"),
434                opt(preceded('_', take_till(1.., |c| c == ' ' || c == '\n'))),
435            ))
436            .context(StrContext::Expected(StrContextValue::Description(
437                "A 'package' function or a 'package_split-package-name' function.",
438            )))
439            .map(|opt| opt.map(ToString::to_string)),
440        )
441        .parse_next(input)?;
442
443        Ok(Self(package_name))
444    }
445}
446
447/// Represents the raw parsed, but not yet typed output of the [`alpm-pkgbuild-bridge`] script.
448///
449/// [`alpm-pkgbuild-bridge`]: https://gitlab.archlinux.org/archlinux/alpm/alpm-pkgbuild-bridge
450#[derive(Clone, Debug)]
451pub struct BridgeOutput {
452    /// The map of all assigned keywords in the `pkgbase` section of the bridge output.
453    pub package_base: HashMap<Keyword, Value>,
454    /// The map of all assigned keywords in the `package` section of the bridge output.
455    /// The keywords are grouped by package name.
456    pub packages: HashMap<RawPackageName, HashMap<Keyword, ClearableValue>>,
457    /// The list of all package function names that are declared in the bridge output.
458    pub functions: Vec<RawPackageName>,
459}
460
461impl BridgeOutput {
462    /// Creates a [`BridgeOutput`] from a [`PKGBUILD`] at a given path, by calling the
463    /// [`alpm-pkgbuild-bridge`] script.
464    ///
465    /// [`PKGBUILD`]: https://man.archlinux.org/man/PKGBUILD.5
466    /// [`alpm-pkgbuild-bridge`]: https://gitlab.archlinux.org/archlinux/alpm/alpm-pkgbuild-bridge
467    pub fn from_file(pkgbuild_path: &Path) -> Result<Self, Error> {
468        let input = run_bridge_script(pkgbuild_path)?;
469        Self::from_script_output(&input)
470    }
471
472    /// Creates a [`BridgeOutput`] from some [`alpm-pkgbuild-bridge`] script output.
473    ///
474    /// This function is mostly exposed for testing, consider using [`Self::from_file`].
475    ///
476    /// [`alpm-pkgbuild-bridge`]: https://gitlab.archlinux.org/archlinux/alpm/alpm-pkgbuild-bridge
477    pub fn from_script_output(input: &str) -> Result<Self, Error> {
478        Self::parser
479            .parse(input)
480            .map_err(|err| Error::BridgeParseError(format!("{err}")))
481    }
482
483    /// Parse some given [`alpm-pkgbuild-bridge`] script output into `Self`.
484    ///
485    /// Use [`Self::from_script_output`] for convenient error handling.
486    /// Recognizes a [`BridgeOutput`] in the output of the `alpm-pkgbuild-bridge` script.
487    ///
488    /// # Errors
489    ///
490    /// Returns an error if no [`BridgeOutput`] can be found in `input`.
491    fn parser(input: &mut &str) -> ModalResult<Self> {
492        let package_base = Self::package_base(input)?;
493        let packages = Self::packages(input)?;
494        let functions = Self::functions(input)?;
495
496        // Consume any trailing empty lines
497        let _: Option<()> = opt(repeat(0.., (space0, "\n", space0))).parse_next(input)?;
498        // Make sure we reached the end of the file.
499        cut_err(eof)
500            .context(StrContext::Expected(StrContextValue::Description(
501                "end of file.",
502            )))
503            .parse_next(input)?;
504
505        Ok(Self {
506            package_base,
507            packages,
508            functions,
509        })
510    }
511
512    /// Recognizes a map of [`Keyword`]-[`Value`] pairs in a string slice.
513    ///
514    /// Backtracks as soon as the next section is hit.
515    ///
516    /// # Errors
517    ///
518    /// Returns an error if there is not at least one [`Keyword`]-[`Value`] pair in `input`.
519    fn package_base(input: &mut &str) -> ModalResult<HashMap<Keyword, Value>> {
520        // We don't have to check for duplicates, and directly convert the iterator into a HashMap
521        // as the bridge guarantees that variables are only declared once per keyword.
522        repeat(1.., Self::package_base_line).parse_next(input)
523    }
524
525    /// Recognizes a [`Keyword`]-[`Value`] pair in a string slice.
526    ///
527    /// Backtracks as soon any other line prefix than `VAR GLOBAL` is encountered.
528    ///
529    /// # Errors
530    ///
531    /// Returns an error if no [`Keyword`]-[`Value`] pair can be found in `input`.
532    fn package_base_line(input: &mut &str) -> ModalResult<(Keyword, Value)> {
533        // Parse the start of the line.
534        //
535        // The start `VAR GLOBAL` may backtrack. This indicates that the next section has been hit.
536        // **Everything else** will return a hard error, as it indicates invalid or unexpected
537        // bridge output.
538        ("VAR GLOBAL").parse_next(input)?;
539
540        let variable_type = VariableType::parser.parse_next(input)?;
541
542        // Get the keyword with an optional suffix.
543        let keyword = Keyword::parser.parse_next(input)?;
544
545        let value = match variable_type {
546            VariableType::Array => Value::list_till_newline(input)?,
547            VariableType::String => Value::single_till_newline(input)?,
548        };
549
550        Ok((keyword, value))
551    }
552
553    /// Recognizes an entire section of package variable declarations.
554    ///
555    /// Delegates to [`BridgeOutput::package_line`] to recognize singular tuples of
556    /// [`RawPackageName`], [`Keyword`] and [`ClearableValue`] per line and combines the data in a
557    /// [`HashMap`].
558    ///
559    /// Backtracks as soon as the next section is hit.
560    ///
561    /// # Errors
562    ///
563    /// Returns an error if [`BridgeOutput::package_line`] does not find at least one line in
564    /// `input`.
565    fn packages(
566        input: &mut &str,
567    ) -> ModalResult<HashMap<RawPackageName, HashMap<Keyword, ClearableValue>>> {
568        let lines: Vec<(RawPackageName, Keyword, ClearableValue)> =
569            repeat(0.., Self::package_line).parse_next(input)?;
570
571        let mut packages = HashMap::new();
572        for (package_name, keyword, value) in lines {
573            let value_map: &mut HashMap<Keyword, ClearableValue> =
574                packages.entry(package_name).or_default();
575            // We don't have to check for duplicates, as the bridge guarantees that variables are
576            // only declared once per keyword.
577            value_map.insert(keyword, value);
578        }
579
580        Ok(packages)
581    }
582
583    /// Recognizes a tuple of [`RawPackageName`], [`Keyword`] and [`ClearableValue`] in a string
584    /// slice.
585    ///
586    /// The tuple represents a single package variable line.
587    /// Backtracks as soon any other line prefix than `FUNCTION` is encountered.
588    ///
589    /// # Errors
590    ///
591    /// Returns an error if one of the components ([`RawPackageName`], [`Keyword`] and
592    /// [`ClearableValue`]) is not found in `input`.
593    fn package_line(input: &mut &str) -> ModalResult<(RawPackageName, Keyword, ClearableValue)> {
594        // Parse the start of the line.
595        //
596        // The start `VAR FUNCTION` may backtrack. This indicates that the next section has been
597        // hit. **Everything else** will return a hard error, as it indicates invalid or
598        // unexpected bridge output.
599        ("VAR FUNCTION").parse_next(input)?;
600
601        let package_name = RawPackageName::parser.parse_next(input)?;
602
603        let variable_type = VariableType::parser.parse_next(input)?;
604
605        // Get the keyword with an optional suffix.
606        let keyword = Keyword::parser.parse_next(input)?;
607
608        let value = match variable_type {
609            VariableType::Array => ClearableValue::list_till_newline(input)?,
610            VariableType::String => ClearableValue::single_till_newline(input)?,
611        };
612
613        Ok((package_name, keyword, value))
614    }
615
616    /// Recognizes a list of [`RawPackageName`]s in a string slice.
617    ///
618    /// Delegates to [`BridgeOutput::function_line`] to recognize each singular [`RawPackageName`].
619    /// This parser may return an empty array, which is the case for [alpm-meta-packages] without a
620    /// `package` function.
621    ///
622    /// # Errors
623    ///
624    /// Returns an error if [`BridgeOutput::function_line`] fails.
625    ///
626    /// [alpm-meta-packages]: https://alpm.archlinux.page/specifications/alpm-meta-package.7.html
627    fn functions(input: &mut &str) -> ModalResult<Vec<RawPackageName>> {
628        repeat(0.., Self::function_line).parse_next(input)
629    }
630
631    /// Recognizes a [`RawPackageName`] in a string slice.
632    ///
633    /// # Errors
634    ///
635    /// Returns an error if no [`RawPackageName`] can be found in `input`.
636    fn function_line(input: &mut &str) -> ModalResult<RawPackageName> {
637        // Parse the start of the line.
638        //
639        // The start `FUNCTION` may backtrack. This indicates that the next section has been
640        // hit. **Everything else** will return a hard error, as it indicates invalid or
641        // unexpected bridge output.
642        ("FUNCTION").parse_next(input)?;
643        let package_name = RawPackageName::parser.parse_next(input)?;
644
645        cut_err((space0, alt((eof, "\n"))))
646            .context(StrContext::Label("character"))
647            .context(StrContext::Expected(StrContextValue::Description(
648                "end of line or end of file.",
649            )))
650            .parse_next(input)?;
651
652        Ok(package_name)
653    }
654}