cargo_pack/
lib.rs

1//! an infrastructure library for 'cargo-pack'ers.
2//! This crate provides only common features of `pack`ers, currently, files to package.
3//! Currently, you can write these metadata in Cargo.toml:
4//!
5//! ```toml
6//! [package.metadata.pack]
7//! # Not used for now. Reserved for future use
8//! default-packers = ["docker"]
9//! # files to pack in addition to binaries
10//! files = ["README.md"]
11//! ```
12
13#![deny(missing_docs)]
14
15use cargo_metadata::{Metadata, MetadataCommand, Package};
16use failure::format_err;
17use log::debug;
18use serde::de::DeserializeOwned;
19use serde::Deserialize;
20use serde_json::Value;
21
22/// Errors and related
23pub mod error {
24    /// result type for the cargo-pack
25    pub type Result<T> = ::std::result::Result<T, ::failure::Error>;
26}
27
28use crate::error::*;
29
30/// Rust side of configurations in `Cargo.toml`
31///
32/// Cargo.toml will look like
33///
34/// ```toml
35/// [package.metadata.pack]
36/// default-packers = ["docker"]
37/// files = ["README.md"]
38/// ```
39#[derive(Deserialize, Debug)]
40#[serde(rename_all = "kebab-case")]
41pub struct PackConfig {
42    /// files to pack into other than binaries
43    pub files: Option<Vec<String>>,
44    /// reserved for future usage.
45    pub default_packers: Option<Vec<String>>,
46}
47
48/// cargo-pack API
49pub struct CargoPack {
50    package_name: Option<String>,
51    pack_config: PackConfig,
52    metadata: Metadata,
53}
54
55fn lookup(mut value: Value, path: &[&str]) -> Option<Value> {
56    for key in path {
57        match value {
58            Value::Object(mut hm) => match hm.remove(*key) {
59                // removing to take the ownership
60                Some(v) => value = v,
61                None => return None,
62            },
63            Value::Array(mut v) => match key.parse::<usize>().ok() {
64                // NOTICE: may change the index
65                Some(idx) if idx < v.len() => value = v.remove(idx),
66                _ => return None,
67            },
68            _ => return None,
69        }
70    }
71
72    Some(value)
73}
74
75impl CargoPack {
76    /// create a new CargoPack value
77    ///
78    /// ```ignore
79    /// let config = Config::default().unwrap();
80    /// let pack = CargoPack::new(&config, None);
81    /// ```
82
83    pub fn new<P: Into<Option<String>>>(package_name: P) -> Result<Self> {
84        let package_name = package_name.into();
85        let metadata = MetadataCommand::new().no_deps().exec()?;
86        let pack_config: PackConfig = Self::decode_from_manifest_static(
87            &metadata,
88            package_name.as_ref().map(|s| s.as_ref()),
89        )?;
90        debug!("config: {:?}", pack_config);
91        Ok(CargoPack {
92            pack_config,
93            package_name,
94            metadata,
95        })
96    }
97
98    /// returns the Metadata value
99    pub fn metadata(&self) -> &Metadata {
100        &self.metadata
101    }
102
103    /// returns the PackConfig value
104    pub fn config(&self) -> &PackConfig {
105        &self.pack_config
106    }
107
108    /// returns the `Package` value of `package_name`
109    pub fn package(&self) -> Result<&Package> {
110        Self::find_package(
111            self.metadata(),
112            self.package_name.as_ref().map(AsRef::as_ref),
113        )
114    }
115
116    fn find_package<'a, 'b>(
117        metadata: &'a Metadata,
118        package_name: Option<&'b str>,
119    ) -> Result<&'a Package> {
120        if let Some(ref name) = package_name {
121            let packages = metadata
122                .packages
123                .iter()
124                .filter(|p| &p.name == name)
125                .collect::<Vec<_>>();
126            match packages.len() {
127                0 => return Err(format_err!("unknown package {}", name)),
128                1 => Ok(packages[0]),
129                _ => return Err(format_err!("ambiguous name {}", name)),
130            }
131        } else {
132            match metadata.packages.len() {
133                1 => Ok(&metadata.packages[0]),
134                _ => return Err(format_err!("virtual hogehoge")),
135            }
136        }
137    }
138
139    fn decode_from_manifest_static<T: DeserializeOwned>(
140        metadata: &Metadata,
141        package_name: Option<&str>,
142    ) -> Result<T> {
143        let package = Self::find_package(metadata, package_name)?;
144        debug!("package: {:?}", package);
145        let data = lookup(package.metadata.clone(), &["pack"])
146            .expect("no package.metadata.pack found in Cargo.toml");
147        serde_json::from_value(data).map_err(Into::into)
148    }
149
150    /// decode a value from the manifest toml file.
151    pub fn decode_from_manifest<'a, T: DeserializeOwned>(&self) -> Result<T> {
152        let package_name = self.package_name.as_ref().map(|s| s.as_ref());
153        Self::decode_from_manifest_static(self.metadata(), package_name)
154    }
155
156    /// returns files defined in `package.metadata.pack.files` in the Cargo.toml.
157    pub fn files(&self) -> &[String] {
158        self.pack_config
159            .files
160            .as_ref()
161            .map(AsRef::as_ref)
162            .unwrap_or(&[])
163    }
164}