Skip to content

Commit 7ba2012

Browse files
committed
Auto merge of #3301 - integer32llc:categories, r=alexcrichton
Upload categories specified in the manifest This adds support for uploading categories to crates.io, if they are specified in the manifest. This goes with rust-lang/crates.io#473. It should be fine to merge this PR either before or after that one; crates.io master doesn't care if the categories are in the metadata or not. With that PR, I was able to use this patch with cargo to add categories to a crate!
2 parents a43403b + f697b8c commit 7ba2012

File tree

5 files changed

+62
-11
lines changed

5 files changed

+62
-11
lines changed

src/cargo/core/manifest.rs

+1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ pub struct VirtualManifest {
4747
pub struct ManifestMetadata {
4848
pub authors: Vec<String>,
4949
pub keywords: Vec<String>,
50+
pub categories: Vec<String>,
5051
pub license: Option<String>,
5152
pub license_file: Option<String>,
5253
pub description: Option<String>, // not markdown

src/cargo/ops/registry.rs

+19-4
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ fn transmit(config: &Config,
113113
let ManifestMetadata {
114114
ref authors, ref description, ref homepage, ref documentation,
115115
ref keywords, ref readme, ref repository, ref license, ref license_file,
116+
ref categories,
116117
} = *manifest.metadata();
117118
let readme = match *readme {
118119
Some(ref readme) => Some(paths::read(&pkg.root().join(readme))?),
@@ -133,7 +134,7 @@ fn transmit(config: &Config,
133134
return Ok(());
134135
}
135136

136-
registry.publish(&NewCrate {
137+
let publish = registry.publish(&NewCrate {
137138
name: pkg.name().to_string(),
138139
vers: pkg.version().to_string(),
139140
deps: deps,
@@ -143,13 +144,27 @@ fn transmit(config: &Config,
143144
homepage: homepage.clone(),
144145
documentation: documentation.clone(),
145146
keywords: keywords.clone(),
147+
categories: categories.clone(),
146148
readme: readme,
147149
repository: repository.clone(),
148150
license: license.clone(),
149151
license_file: license_file.clone(),
150-
}, tarball).map_err(|e| {
151-
human(e.to_string())
152-
})
152+
}, tarball);
153+
154+
match publish {
155+
Ok(warnings) => {
156+
if !warnings.invalid_categories.is_empty() {
157+
let msg = format!("\
158+
the following are not valid category slugs and were \
159+
ignored: {}. Please see https://crates.io/category_slugs \
160+
for the list of all category slugs. \
161+
", warnings.invalid_categories.join(", "));
162+
config.shell().warn(&msg)?;
163+
}
164+
Ok(())
165+
},
166+
Err(e) => Err(human(e.to_string())),
167+
}
153168
}
154169

155170
pub fn registry_configuration(config: &Config) -> CargoResult<RegistryConfig> {

src/cargo/util/toml.rs

+2
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,7 @@ pub struct TomlProject {
317317
documentation: Option<String>,
318318
readme: Option<String>,
319319
keywords: Option<Vec<String>>,
320+
categories: Option<Vec<String>>,
320321
license: Option<String>,
321322
license_file: Option<String>,
322323
repository: Option<String>,
@@ -654,6 +655,7 @@ impl TomlManifest {
654655
license_file: project.license_file.clone(),
655656
repository: project.repository.clone(),
656657
keywords: project.keywords.clone().unwrap_or(Vec::new()),
658+
categories: project.categories.clone().unwrap_or(Vec::new()),
657659
};
658660

659661
let workspace_config = match (self.workspace.as_ref(),

src/crates-io/lib.rs

+32-5
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use std::io::{self, Cursor};
1010
use std::result;
1111

1212
use curl::easy::{Easy, List};
13-
use rustc_serialize::json;
13+
use rustc_serialize::json::{self, Json};
1414

1515
use url::percent_encoding::{percent_encode, QUERY_ENCODE_SET};
1616

@@ -39,6 +39,7 @@ pub enum Error {
3939
NotFound,
4040
JsonEncodeError(json::EncoderError),
4141
JsonDecodeError(json::DecoderError),
42+
JsonParseError(json::ParserError),
4243
}
4344

4445
impl From<json::EncoderError> for Error {
@@ -53,6 +54,12 @@ impl From<json::DecoderError> for Error {
5354
}
5455
}
5556

57+
impl From<json::ParserError> for Error {
58+
fn from(err: json::ParserError) -> Error {
59+
Error::JsonParseError(err)
60+
}
61+
}
62+
5663
impl From<curl::Error> for Error {
5764
fn from(err: curl::Error) -> Error {
5865
Error::Curl(err)
@@ -78,6 +85,7 @@ pub struct NewCrate {
7885
pub homepage: Option<String>,
7986
pub readme: Option<String>,
8087
pub keywords: Vec<String>,
88+
pub categories: Vec<String>,
8189
pub license: Option<String>,
8290
pub license_file: Option<String>,
8391
pub repository: Option<String>,
@@ -103,14 +111,17 @@ pub struct User {
103111
pub name: Option<String>,
104112
}
105113

114+
pub struct Warnings {
115+
pub invalid_categories: Vec<String>,
116+
}
117+
106118
#[derive(RustcDecodable)] struct R { ok: bool }
107119
#[derive(RustcDecodable)] struct ApiErrorList { errors: Vec<ApiError> }
108120
#[derive(RustcDecodable)] struct ApiError { detail: String }
109121
#[derive(RustcEncodable)] struct OwnersReq<'a> { users: &'a [&'a str] }
110122
#[derive(RustcDecodable)] struct Users { users: Vec<User> }
111123
#[derive(RustcDecodable)] struct TotalCrates { total: u32 }
112124
#[derive(RustcDecodable)] struct Crates { crates: Vec<Crate>, meta: TotalCrates }
113-
114125
impl Registry {
115126
pub fn new(host: String, token: Option<String>) -> Registry {
116127
Registry::new_handle(host, token, Easy::new())
@@ -147,7 +158,8 @@ impl Registry {
147158
Ok(json::decode::<Users>(&body)?.users)
148159
}
149160

150-
pub fn publish(&mut self, krate: &NewCrate, tarball: &File) -> Result<()> {
161+
pub fn publish(&mut self, krate: &NewCrate, tarball: &File)
162+
-> Result<Warnings> {
151163
let json = json::encode(krate)?;
152164
// Prepare the body. The format of the upload request is:
153165
//
@@ -190,10 +202,24 @@ impl Registry {
190202
headers.append(&format!("Authorization: {}", token))?;
191203
self.handle.http_headers(headers)?;
192204

193-
let _body = handle(&mut self.handle, &mut |buf| {
205+
let body = handle(&mut self.handle, &mut |buf| {
194206
body.read(buf).unwrap_or(0)
195207
})?;
196-
Ok(())
208+
// Can't derive RustcDecodable because JSON has a key named "crate" :(
209+
let response = if body.len() > 0 {
210+
Json::from_str(&body)?
211+
} else {
212+
Json::from_str("{}")?
213+
};
214+
let invalid_categories: Vec<String> =
215+
response
216+
.find_path(&["warnings", "invalid_categories"])
217+
.and_then(Json::as_array)
218+
.map(|x| {
219+
x.iter().flat_map(Json::as_string).map(Into::into).collect()
220+
})
221+
.unwrap_or_else(Vec::new);
222+
Ok(Warnings { invalid_categories: invalid_categories })
197223
}
198224

199225
pub fn search(&mut self, query: &str, limit: u8) -> Result<(Vec<Crate>, u32)> {
@@ -328,6 +354,7 @@ impl fmt::Display for Error {
328354
Error::NotFound => write!(f, "cannot find crate"),
329355
Error::JsonEncodeError(ref e) => write!(f, "json encode error: {}", e),
330356
Error::JsonDecodeError(ref e) => write!(f, "json decode error: {}", e),
357+
Error::JsonParseError(ref e) => write!(f, "json parse error: {}", e),
331358
}
332359
}
333360
}

src/doc/manifest.md

+8-2
Original file line numberDiff line numberDiff line change
@@ -118,10 +118,16 @@ repository = "..."
118118
# contents of this file are stored and indexed in the registry.
119119
readme = "..."
120120

121-
# This is a small list of keywords used to categorize and search for this
122-
# package.
121+
# This is a list of up to five keywords that describe this crate. Keywords
122+
# are searchable on crates.io, and you may choose any words that would
123+
# help someone find this crate.
123124
keywords = ["...", "..."]
124125

126+
# This is a list of up to five categories where this crate would fit.
127+
# Categories are a fixed list available at crates.io/categories, and
128+
# they must match exactly.
129+
categories = ["...", "..."]
130+
125131
# This is a string description of the license for this package. Currently
126132
# crates.io will validate the license provided against a whitelist of known
127133
# license identifiers from http://spdx.org/licenses/. Multiple licenses can be

0 commit comments

Comments
 (0)