diff --git a/src/cargo/core/manifest.rs b/src/cargo/core/manifest.rs index d03cf688b93..1d7d4fbda20 100644 --- a/src/cargo/core/manifest.rs +++ b/src/cargo/core/manifest.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; use std::fmt; use std::path::{PathBuf, Path}; @@ -55,6 +56,7 @@ pub struct ManifestMetadata { pub homepage: Option, // url pub repository: Option, // url pub documentation: Option, // url + pub badges: HashMap>, } #[derive(Debug, Clone, PartialEq, Eq, Hash)] diff --git a/src/cargo/ops/registry.rs b/src/cargo/ops/registry.rs index 2397f8204a9..f3c8de93415 100644 --- a/src/cargo/ops/registry.rs +++ b/src/cargo/ops/registry.rs @@ -113,7 +113,7 @@ fn transmit(config: &Config, let ManifestMetadata { ref authors, ref description, ref homepage, ref documentation, ref keywords, ref readme, ref repository, ref license, ref license_file, - ref categories, + ref categories, ref badges, } = *manifest.metadata(); let readme = match *readme { Some(ref readme) => Some(paths::read(&pkg.root().join(readme))?), @@ -149,6 +149,7 @@ fn transmit(config: &Config, repository: repository.clone(), license: license.clone(), license_file: license_file.clone(), + badges: badges.clone(), }, tarball); match publish { @@ -161,6 +162,18 @@ fn transmit(config: &Config, ", warnings.invalid_categories.join(", ")); config.shell().warn(&msg)?; } + + if !warnings.invalid_badges.is_empty() { + let msg = format!("\ + the following are not valid badges and were ignored: {}. \ + Either the badge type specified is unknown or a required \ + attribute is missing. Please see \ + http://doc.crates.io/manifest.html#package-metadata \ + for valid badge types and their required attributes.", + warnings.invalid_badges.join(", ")); + config.shell().warn(&msg)?; + } + Ok(()) }, Err(e) => Err(human(e.to_string())), diff --git a/src/cargo/util/toml.rs b/src/cargo/util/toml.rs index 8abbaa2f183..7f04d5875fd 100644 --- a/src/cargo/util/toml.rs +++ b/src/cargo/util/toml.rs @@ -246,6 +246,7 @@ pub struct TomlManifest { target: Option>, replace: Option>, workspace: Option, + badges: Option>>, } #[derive(RustcDecodable, Clone, Default)] @@ -656,6 +657,7 @@ impl TomlManifest { repository: project.repository.clone(), keywords: project.keywords.clone().unwrap_or(Vec::new()), categories: project.categories.clone().unwrap_or(Vec::new()), + badges: self.badges.clone().unwrap_or_else(HashMap::new), }; let workspace_config = match (self.workspace.as_ref(), diff --git a/src/crates-io/lib.rs b/src/crates-io/lib.rs index 00637be3dc2..915f44363a3 100644 --- a/src/crates-io/lib.rs +++ b/src/crates-io/lib.rs @@ -89,6 +89,7 @@ pub struct NewCrate { pub license: Option, pub license_file: Option, pub repository: Option, + pub badges: HashMap>, } #[derive(RustcEncodable)] @@ -113,6 +114,7 @@ pub struct User { pub struct Warnings { pub invalid_categories: Vec, + pub invalid_badges: Vec, } #[derive(RustcDecodable)] struct R { ok: bool } @@ -205,12 +207,14 @@ impl Registry { let body = handle(&mut self.handle, &mut |buf| { body.read(buf).unwrap_or(0) })?; + // Can't derive RustcDecodable because JSON has a key named "crate" :( let response = if body.len() > 0 { Json::from_str(&body)? } else { Json::from_str("{}")? }; + let invalid_categories: Vec = response .find_path(&["warnings", "invalid_categories"]) @@ -219,7 +223,20 @@ impl Registry { x.iter().flat_map(Json::as_string).map(Into::into).collect() }) .unwrap_or_else(Vec::new); - Ok(Warnings { invalid_categories: invalid_categories }) + + let invalid_badges: Vec = + response + .find_path(&["warnings", "invalid_badges"]) + .and_then(Json::as_array) + .map(|x| { + x.iter().flat_map(Json::as_string).map(Into::into).collect() + }) + .unwrap_or_else(Vec::new); + + Ok(Warnings { + invalid_categories: invalid_categories, + invalid_badges: invalid_badges, + }) } pub fn search(&mut self, query: &str, limit: u8) -> Result<(Vec, u32)> { diff --git a/src/doc/manifest.md b/src/doc/manifest.md index 09fa5e04e9f..facaff5b093 100644 --- a/src/doc/manifest.md +++ b/src/doc/manifest.md @@ -138,6 +138,16 @@ license = "..." # lieu of the above key and must point to a file relative to this manifest # (similar to the readme key). license-file = "..." + +# Optional specificaion of badges to be displayed on crates.io. The badges +# currently available are Travis CI and Appveyor latest build status, specified +# using the following parameters: +[badges] +# Travis CI: `repository` is required. `branch` is optional; default is `master` +travis-ci = { repository = "...", branch = "master" } +# Appveyor: `repository` is required. `branch` is optional; default is `master` +# `service` is optional; valid values are `github` (default) and `bitbucket` +appveyor = { repository = "...", branch = "master", service = "github" } ``` The [crates.io](https://crates.io) registry will render the description, display