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}