cargo_parcel/
config.rs

1use anyhow::anyhow;
2
3fn cargo_toml_error(message: &str) -> anyhow::Error {
4    anyhow!("could not read Cargo.toml: {}", message)
5}
6
7/// The configuration, as parsed from `Cargo.toml`.
8#[derive(Debug)]
9pub struct Config {
10    package_name: String,
11    package_version: String,
12    metadata: Metadata,
13}
14
15impl Config {
16    /// Constructs the config from a `Cargo.toml` manifest.
17    pub fn from_manifest(manifest: &toml::value::Table) -> anyhow::Result<Self> {
18        let malformed_package = |e| anyhow!("malformed package table: {}", e);
19        let package = get_key(manifest, "package", toml_table)?;
20        let package_name = get_key(package, "name", toml_str).map_err(malformed_package)?;
21        let package_version = get_key(package, "version", toml_str).map_err(malformed_package)?;
22        let parcel = match get_key_opt(package, "metadata", toml_table)? {
23            Some(m) => get_key_opt(m, "parcel", toml_table)?,
24            None => None,
25        };
26        let metadata = if let Some(metadata) = parcel {
27            Metadata::from_toml(metadata)
28                .map_err(|e| anyhow!("malformed parcel metadata: {}", e))?
29        } else {
30            Default::default()
31        };
32        Ok(Config {
33            package_name: package_name.into(),
34            package_version: package_version.into(),
35            metadata,
36        })
37    }
38
39    /// Returns the package name.
40    ///
41    /// This will return the `pkg-name` metadata entry. If that key is missing,
42    /// the crate's name is returned.
43    pub fn package_name(&self) -> &str {
44        self.metadata
45            .pkg_name
46            .as_ref()
47            .map(|s| s.as_str())
48            .unwrap_or(&self.package_name)
49    }
50
51    /// Returns the package version string.
52    pub fn package_version(&self) -> &str {
53        &self.package_version
54    }
55
56    /// Returns the cargo binaries.
57    pub fn cargo_binaries(&self) -> impl Iterator<Item = &str> {
58        self.metadata.cargo_binaries.iter().map(|s| s.as_ref())
59    }
60
61    /// Returns the man pages.
62    pub fn man_pages(&self) -> impl Iterator<Item = &str> {
63        self.metadata.man_pages.iter().map(|s| s.as_ref())
64    }
65
66    /// Returns the package data resources.
67    pub fn pkg_data(&self) -> impl Iterator<Item = &str> {
68        self.metadata.pkg_data.iter().map(|s| s.as_ref())
69    }
70}
71
72fn toml_str_array(value: &toml::Value) -> anyhow::Result<Vec<String>> {
73    let array = value
74        .as_array()
75        .ok_or_else(|| anyhow!("must be an array"))?;
76    array
77        .iter()
78        .map(|item| {
79            item.as_str()
80                .map(ToOwned::to_owned)
81                .ok_or_else(|| anyhow!("contains non-string item `{}`", item))
82        })
83        .collect()
84}
85
86#[derive(Debug, Default)]
87struct Metadata {
88    pkg_name: Option<String>,
89    cargo_binaries: Vec<String>,
90    man_pages: Vec<String>,
91    pkg_data: Vec<String>,
92}
93
94impl Metadata {
95    fn from_toml(metadata: &toml::value::Table) -> anyhow::Result<Self> {
96        Ok(Metadata {
97            pkg_name: get_key_opt(metadata, "pkg-name", toml_str)?.map(|s| s.to_owned()),
98            cargo_binaries: get_key_opt(metadata, "cargo-binaries", toml_str_array)?
99                .unwrap_or_default(),
100            man_pages: get_key_opt(metadata, "man-pages", toml_str_array)?.unwrap_or_default(),
101            pkg_data: get_key_opt(metadata, "pkg-data", toml_str_array)?.unwrap_or_default(),
102        })
103    }
104}
105
106fn toml_table(value: &toml::Value) -> anyhow::Result<&toml::value::Table> {
107    value.as_table().ok_or_else(|| anyhow!("must be a table"))
108}
109
110fn toml_str(value: &toml::Value) -> anyhow::Result<&str> {
111    value.as_str().ok_or_else(|| anyhow!("must be a string"))
112}
113
114fn get_key<'a, F, T>(table: &'a toml::value::Table, name: &str, f: F) -> anyhow::Result<T>
115where
116    F: FnOnce(&'a toml::Value) -> anyhow::Result<T>,
117{
118    get_key_opt(table, name, f)?.ok_or_else(|| cargo_toml_error("required key `{}` missing"))
119}
120
121fn get_key_opt<'a, F, T>(
122    table: &'a toml::value::Table,
123    name: &str,
124    f: F,
125) -> anyhow::Result<Option<T>>
126where
127    F: FnOnce(&'a toml::Value) -> anyhow::Result<T>,
128{
129    table
130        .get(name)
131        .map(|v| f(v).map_err(|e| anyhow!("invalid value for key `{}`: {}", name, e)))
132        .transpose()
133}