Skip to content

Commit 0c25226

Browse files
author
Jakub Bukaj
committed
Add a search command to cargo
1 parent 672af89 commit 0c25226

File tree

5 files changed

+124
-19
lines changed

5 files changed

+124
-19
lines changed

src/bin/cargo.rs

+1
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ macro_rules! each_subcommand( ($macro:ident) => ({
7474
$macro!(publish)
7575
$macro!(read_manifest)
7676
$macro!(run)
77+
$macro!(search)
7778
$macro!(test)
7879
$macro!(update)
7980
$macro!(verify_project)

src/bin/search.rs

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
use cargo::ops;
2+
use cargo::core::{MultiShell};
3+
use cargo::util::{CliResult, CliError};
4+
5+
#[deriving(Decodable)]
6+
struct Options {
7+
flag_host: Option<String>,
8+
flag_verbose: bool,
9+
arg_query: String
10+
}
11+
12+
pub const USAGE: &'static str = "
13+
Search packages in crates.io
14+
15+
Usage:
16+
cargo search [options] <query>
17+
18+
Options:
19+
-h, --help Print this message
20+
--host HOST Host of a registry to search in
21+
-v, --verbose Use verbose output
22+
";
23+
24+
pub fn execute(options: Options, shell: &mut MultiShell) -> CliResult<Option<()>> {
25+
shell.set_verbose(options.flag_verbose);
26+
let Options {
27+
flag_host: host,
28+
arg_query: query,
29+
..
30+
} = options;
31+
32+
ops::search(query.as_slice(), shell, host)
33+
.map(|_| None)
34+
.map_err(|err| CliError::from_boxed(err, 101))
35+
}

src/cargo/ops/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ pub use self::lockfile::{write_lockfile, write_pkg_lockfile};
1616
pub use self::cargo_test::{run_tests, run_benches, TestOptions};
1717
pub use self::cargo_package::package;
1818
pub use self::registry::{publish, registry_configuration, RegistryConfig};
19-
pub use self::registry::{registry_login, http_proxy, http_handle};
19+
pub use self::registry::{registry_login, search, http_proxy, http_handle};
2020
pub use self::registry::{modify_owners, yank, OwnersOptions};
2121
pub use self::cargo_fetch::{fetch};
2222
pub use self::cargo_pkgid::pkgid;

src/cargo/ops/registry.rs

+46-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::collections::HashMap;
22
use std::io::File;
33
use std::os;
4+
use term::color::BLACK;
45

56
use curl::http;
67
use git2;
@@ -12,7 +13,7 @@ use core::manifest::ManifestMetadata;
1213
use ops;
1314
use sources::{PathSource, RegistrySource};
1415
use util::config;
15-
use util::{CargoResult, human, internal, ChainError, Require, ToUrl};
16+
use util::{CargoResult, human, internal, ChainError, ToUrl};
1617
use util::config::{Config, ConfigValue, Location};
1718

1819
pub struct RegistryConfig {
@@ -143,9 +144,7 @@ pub fn registry(shell: &mut MultiShell,
143144
token: token_config,
144145
index: index_config,
145146
} = try!(registry_configuration());
146-
let token = try!(token.or(token_config).require(|| {
147-
human("no upload token found, please run `cargo login`")
148-
}));
147+
let token = token.or(token_config);
149148
let index = index.or(index_config).unwrap_or(RegistrySource::default_url());
150149
let index = try!(index.as_slice().to_url().map_err(human));
151150
let sid = SourceId::for_registry(&index);
@@ -323,3 +322,46 @@ pub fn yank(manifest_path: &Path,
323322

324323
Ok(())
325324
}
325+
326+
pub fn search(query: &str, shell: &mut MultiShell, index: Option<String>) -> CargoResult<()> {
327+
fn truncate_with_ellipsis(s: &str, max_length: uint) -> String {
328+
if s.len() < max_length {
329+
s.to_string()
330+
} else {
331+
format!("{}…", s[..max_length - 1])
332+
}
333+
}
334+
335+
let (mut registry, _) = try!(registry(shell, None, index));
336+
337+
let crates = try!(registry.search(query).map_err(|e| {
338+
human(format!("failed to retrieve search results from the registry: {}", e))
339+
}));
340+
341+
let list_items = crates.iter()
342+
.map(|krate| (
343+
format!("{} ({})", krate.name, krate.max_version),
344+
krate.description.as_ref().map(|desc|
345+
truncate_with_ellipsis(desc.replace("\n", " ").as_slice(), 128))
346+
))
347+
.collect::<Vec<_>>();
348+
let description_margin = list_items.iter()
349+
.map(|&(ref left, _)| left.len() + 4)
350+
.max()
351+
.unwrap_or(0);
352+
353+
for (name, description) in list_items.into_iter() {
354+
let line = match description {
355+
Some(desc) => {
356+
let space = String::from_char(
357+
description_margin - name.len(),
358+
' ');
359+
name + space + desc
360+
}
361+
None => name
362+
};
363+
try!(shell.say(line, BLACK));
364+
}
365+
366+
Ok(())
367+
}

src/registry/lib.rs

+41-14
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,35 @@ use serialize::json;
1313

1414
pub struct Registry {
1515
host: String,
16-
token: String,
16+
token: Option<String>,
1717
handle: http::Handle,
1818
}
1919

2020
pub type Result<T> = result::Result<T, Error>;
2121

22+
#[deriving(PartialEq)]
23+
pub enum Auth {
24+
Authorized,
25+
Unauthorized
26+
}
27+
2228
pub enum Error {
2329
Curl(curl::ErrCode),
2430
NotOkResponse(http::Response),
2531
NonUtf8Body,
2632
Api(Vec<String>),
2733
Unauthorized,
34+
TokenMissing,
2835
Io(io::IoError),
2936
}
3037

38+
#[deriving(Decodable)]
39+
pub struct Crate {
40+
pub name: String,
41+
pub description: Option<String>,
42+
pub max_version: String
43+
}
44+
3145
#[deriving(Encodable)]
3246
pub struct NewCrate {
3347
pub name: String,
@@ -68,13 +82,14 @@ pub struct User {
6882
#[deriving(Decodable)] struct ApiError { detail: String }
6983
#[deriving(Encodable)] struct OwnersReq<'a> { users: &'a [&'a str] }
7084
#[deriving(Decodable)] struct Users { users: Vec<User> }
85+
#[deriving(Decodable)] struct Crates { crates: Vec<Crate> }
7186

7287
impl Registry {
73-
pub fn new(host: String, token: String) -> Registry {
88+
pub fn new(host: String, token: Option<String>) -> Registry {
7489
Registry::new_handle(host, token, http::Handle::new())
7590
}
7691

77-
pub fn new_handle(host: String, token: String,
92+
pub fn new_handle(host: String, token: Option<String>,
7893
handle: http::Handle) -> Registry {
7994
Registry {
8095
host: host,
@@ -126,16 +141,23 @@ impl Registry {
126141
box tarball as Box<Reader>].into_iter());
127142

128143
let url = format!("{}/api/v1/crates/new", self.host);
129-
let response = handle(self.handle.put(url, &mut body)
130-
.content_length(size)
131-
.header("Authorization",
132-
self.token.as_slice())
133-
.header("Accept", "application/json")
134-
.exec());
144+
145+
let token = try!(self.token.as_ref().ok_or(Error::TokenMissing)).as_slice();
146+
let request = self.handle.put(url, &mut body)
147+
.content_length(size)
148+
.header("Accept", "application/json")
149+
.header("Authorization", token);
150+
let response = handle(request.exec());
135151
let _body = try!(response);
136152
Ok(())
137153
}
138154

155+
pub fn search(&mut self, query: &str) -> Result<Vec<Crate>> {
156+
let body = try!(self.req(format!("/crates?q={}", query), None, Get, Auth::Unauthorized));
157+
158+
Ok(json::decode::<Crates>(body.as_slice()).unwrap().crates)
159+
}
160+
139161
pub fn yank(&mut self, krate: &str, version: &str) -> Result<()> {
140162
let body = try!(self.delete(format!("/crates/{}/{}/yank", krate, version),
141163
None));
@@ -151,24 +173,28 @@ impl Registry {
151173
}
152174

153175
fn put(&mut self, path: String, b: &[u8]) -> Result<String> {
154-
self.req(path, Some(b), Put)
176+
self.req(path, Some(b), Put, Auth::Authorized)
155177
}
156178

157179
fn get(&mut self, path: String) -> Result<String> {
158-
self.req(path, None, Get)
180+
self.req(path, None, Get, Auth::Authorized)
159181
}
160182

161183
fn delete(&mut self, path: String, b: Option<&[u8]>) -> Result<String> {
162-
self.req(path, b, Delete)
184+
self.req(path, b, Delete, Auth::Authorized)
163185
}
164186

165187
fn req(&mut self, path: String, body: Option<&[u8]>,
166-
method: Method) -> Result<String> {
188+
method: Method, authorized: Auth) -> Result<String> {
167189
let mut req = Request::new(&mut self.handle, method)
168190
.uri(format!("{}/api/v1{}", self.host, path))
169-
.header("Authorization", self.token.as_slice())
170191
.header("Accept", "application/json")
171192
.content_type("application/json");
193+
194+
let token = try!(self.token.as_ref().ok_or(Error::TokenMissing)).as_slice();
195+
if authorized == Auth::Authorized {
196+
req = req.header("Authorization", token);
197+
}
172198
match body {
173199
Some(b) => req = req.body(b),
174200
None => {}
@@ -213,6 +239,7 @@ impl fmt::Show for Error {
213239
write!(f, "api errors: {}", errs.connect(", "))
214240
}
215241
Error::Unauthorized => write!(f, "unauthorized API access"),
242+
Error::TokenMissing => write!(f, "no upload token found, please run `cargo login`"),
216243
Error::Io(ref e) => write!(f, "io error: {}", e),
217244
}
218245
}

0 commit comments

Comments
 (0)