Skip to content

Commit

Permalink
Add a search command to cargo
Browse files Browse the repository at this point in the history
  • Loading branch information
Jakub Bukaj committed Nov 24, 2014
1 parent 672af89 commit 0c25226
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 19 deletions.
1 change: 1 addition & 0 deletions src/bin/cargo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ macro_rules! each_subcommand( ($macro:ident) => ({
$macro!(publish)
$macro!(read_manifest)
$macro!(run)
$macro!(search)
$macro!(test)
$macro!(update)
$macro!(verify_project)
Expand Down
35 changes: 35 additions & 0 deletions src/bin/search.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
use cargo::ops;
use cargo::core::{MultiShell};
use cargo::util::{CliResult, CliError};

#[deriving(Decodable)]
struct Options {
flag_host: Option<String>,
flag_verbose: bool,
arg_query: String
}

pub const USAGE: &'static str = "
Search packages in crates.io
Usage:
cargo search [options] <query>
Options:
-h, --help Print this message
--host HOST Host of a registry to search in
-v, --verbose Use verbose output
";

pub fn execute(options: Options, shell: &mut MultiShell) -> CliResult<Option<()>> {
shell.set_verbose(options.flag_verbose);
let Options {
flag_host: host,
arg_query: query,
..
} = options;

ops::search(query.as_slice(), shell, host)
.map(|_| None)
.map_err(|err| CliError::from_boxed(err, 101))
}
2 changes: 1 addition & 1 deletion src/cargo/ops/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ pub use self::lockfile::{write_lockfile, write_pkg_lockfile};
pub use self::cargo_test::{run_tests, run_benches, TestOptions};
pub use self::cargo_package::package;
pub use self::registry::{publish, registry_configuration, RegistryConfig};
pub use self::registry::{registry_login, http_proxy, http_handle};
pub use self::registry::{registry_login, search, http_proxy, http_handle};
pub use self::registry::{modify_owners, yank, OwnersOptions};
pub use self::cargo_fetch::{fetch};
pub use self::cargo_pkgid::pkgid;
Expand Down
50 changes: 46 additions & 4 deletions src/cargo/ops/registry.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::collections::HashMap;
use std::io::File;
use std::os;
use term::color::BLACK;

use curl::http;
use git2;
Expand All @@ -12,7 +13,7 @@ use core::manifest::ManifestMetadata;
use ops;
use sources::{PathSource, RegistrySource};
use util::config;
use util::{CargoResult, human, internal, ChainError, Require, ToUrl};
use util::{CargoResult, human, internal, ChainError, ToUrl};
use util::config::{Config, ConfigValue, Location};

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

Ok(())
}

pub fn search(query: &str, shell: &mut MultiShell, index: Option<String>) -> CargoResult<()> {
fn truncate_with_ellipsis(s: &str, max_length: uint) -> String {
if s.len() < max_length {
s.to_string()
} else {
format!("{}…", s[..max_length - 1])
}
}

let (mut registry, _) = try!(registry(shell, None, index));

let crates = try!(registry.search(query).map_err(|e| {
human(format!("failed to retrieve search results from the registry: {}", e))
}));

let list_items = crates.iter()
.map(|krate| (
format!("{} ({})", krate.name, krate.max_version),
krate.description.as_ref().map(|desc|
truncate_with_ellipsis(desc.replace("\n", " ").as_slice(), 128))
))
.collect::<Vec<_>>();
let description_margin = list_items.iter()
.map(|&(ref left, _)| left.len() + 4)
.max()
.unwrap_or(0);

for (name, description) in list_items.into_iter() {
let line = match description {
Some(desc) => {
let space = String::from_char(
description_margin - name.len(),
' ');
name + space + desc
}
None => name
};
try!(shell.say(line, BLACK));
}

Ok(())
}
55 changes: 41 additions & 14 deletions src/registry/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,35 @@ use serialize::json;

pub struct Registry {
host: String,
token: String,
token: Option<String>,
handle: http::Handle,
}

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

#[deriving(PartialEq)]
pub enum Auth {
Authorized,
Unauthorized
}

pub enum Error {
Curl(curl::ErrCode),
NotOkResponse(http::Response),
NonUtf8Body,
Api(Vec<String>),
Unauthorized,
TokenMissing,
Io(io::IoError),
}

#[deriving(Decodable)]
pub struct Crate {
pub name: String,
pub description: Option<String>,
pub max_version: String
}

#[deriving(Encodable)]
pub struct NewCrate {
pub name: String,
Expand Down Expand Up @@ -68,13 +82,14 @@ pub struct User {
#[deriving(Decodable)] struct ApiError { detail: String }
#[deriving(Encodable)] struct OwnersReq<'a> { users: &'a [&'a str] }
#[deriving(Decodable)] struct Users { users: Vec<User> }
#[deriving(Decodable)] struct Crates { crates: Vec<Crate> }

impl Registry {
pub fn new(host: String, token: String) -> Registry {
pub fn new(host: String, token: Option<String>) -> Registry {
Registry::new_handle(host, token, http::Handle::new())
}

pub fn new_handle(host: String, token: String,
pub fn new_handle(host: String, token: Option<String>,
handle: http::Handle) -> Registry {
Registry {
host: host,
Expand Down Expand Up @@ -126,16 +141,23 @@ impl Registry {
box tarball as Box<Reader>].into_iter());

let url = format!("{}/api/v1/crates/new", self.host);
let response = handle(self.handle.put(url, &mut body)
.content_length(size)
.header("Authorization",
self.token.as_slice())
.header("Accept", "application/json")
.exec());

let token = try!(self.token.as_ref().ok_or(Error::TokenMissing)).as_slice();
let request = self.handle.put(url, &mut body)
.content_length(size)
.header("Accept", "application/json")
.header("Authorization", token);
let response = handle(request.exec());
let _body = try!(response);
Ok(())
}

pub fn search(&mut self, query: &str) -> Result<Vec<Crate>> {
let body = try!(self.req(format!("/crates?q={}", query), None, Get, Auth::Unauthorized));

Ok(json::decode::<Crates>(body.as_slice()).unwrap().crates)
}

pub fn yank(&mut self, krate: &str, version: &str) -> Result<()> {
let body = try!(self.delete(format!("/crates/{}/{}/yank", krate, version),
None));
Expand All @@ -151,24 +173,28 @@ impl Registry {
}

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

fn get(&mut self, path: String) -> Result<String> {
self.req(path, None, Get)
self.req(path, None, Get, Auth::Authorized)
}

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

fn req(&mut self, path: String, body: Option<&[u8]>,
method: Method) -> Result<String> {
method: Method, authorized: Auth) -> Result<String> {
let mut req = Request::new(&mut self.handle, method)
.uri(format!("{}/api/v1{}", self.host, path))
.header("Authorization", self.token.as_slice())
.header("Accept", "application/json")
.content_type("application/json");

let token = try!(self.token.as_ref().ok_or(Error::TokenMissing)).as_slice();
if authorized == Auth::Authorized {
req = req.header("Authorization", token);
}
match body {
Some(b) => req = req.body(b),
None => {}
Expand Down Expand Up @@ -213,6 +239,7 @@ impl fmt::Show for Error {
write!(f, "api errors: {}", errs.connect(", "))
}
Error::Unauthorized => write!(f, "unauthorized API access"),
Error::TokenMissing => write!(f, "no upload token found, please run `cargo login`"),
Error::Io(ref e) => write!(f, "io error: {}", e),
}
}
Expand Down

5 comments on commit 0c25226

@bors
Copy link
Contributor

@bors bors commented on 0c25226 Nov 24, 2014

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bors
Copy link
Contributor

@bors bors commented on 0c25226 Nov 24, 2014

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

merging jakub-/cargo/cargo-search = 0c25226 into auto-cargo

@bors
Copy link
Contributor

@bors bors commented on 0c25226 Nov 24, 2014

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

jakub-/cargo/cargo-search = 0c25226 merged ok, testing candidate = de5bc1e

@bors
Copy link
Contributor

@bors bors commented on 0c25226 Nov 24, 2014

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bors
Copy link
Contributor

@bors bors commented on 0c25226 Nov 24, 2014

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fast-forwarding master to auto-cargo = de5bc1e

Please sign in to comment.