1use blake2b_simd::Params;
11use color_eyre::{
12 eyre::{bail, eyre, WrapErr},
13 Result,
14};
15use crates_index::BareIndex;
16use semver::{Version, VersionReq};
17use serde::Serialize;
18use std::path::PathBuf;
19use structopt::{clap::arg_enum, StructOpt};
20
21static DEFAULT_INDEX_URL: &str = "https://github.com/rust-lang/crates.io-index";
22
23#[derive(Debug, StructOpt)]
32#[doc(hidden)]
33pub struct Args {
34 #[structopt(long)]
36 index_path: Option<PathBuf>,
37
38 #[structopt(long = "req", default_value)]
40 version_req: VersionReq,
41
42 #[structopt(long, short = "c", default_value)]
44 cache_version: u64,
45
46 #[structopt(long, possible_values = &MessageFormat::variants(), case_insensitive = true, default_value = "plain")]
48 message_format: MessageFormat,
49
50 crate_name: String,
52}
53
54const BLAKE2B_PREFIX: &str = "blake2b24:";
55const BLAKE2B_HASH_LEN: usize = 24;
56const MAX_VERSIONS_IN_ERR: usize = 8;
57
58arg_enum! {
59 #[derive(Copy, Clone, Debug)]
60 #[allow(non_camel_case_types)]
61 enum MessageFormat {
62 toml,
63 plain,
64 json,
65 github,
66 }
67}
68
69impl Args {
70 pub fn exec(self) -> Result<()> {
72 let index = match &self.index_path {
73 Some(path) => BareIndex::with_path(path.clone(), DEFAULT_INDEX_URL),
74 None => BareIndex::new_cargo_default(),
75 };
76
77 let mut repo = index.open_or_clone().wrap_err_with(|| {
78 format!(
79 "opening or cloning index at {} failed",
80 index.path().display()
81 )
82 })?;
83
84 repo.retrieve()
85 .wrap_err_with(|| format!("updating index at {} failed", index.path().display()))?;
86
87 let crate_ = repo
88 .crate_(&self.crate_name)
89 .ok_or_else(|| eyre!("crate {} not found", &self.crate_name))?;
90 let mut matching_versions: Vec<Version> = crate_
91 .versions()
92 .iter()
93 .filter_map(|version| {
94 let version = match version.version().parse::<Version>() {
95 Ok(version) => version,
96 Err(_) => {
97 return None;
99 }
100 };
101
102 if self.version_req.matches(&version) {
103 Some(version)
104 } else {
105 None
106 }
107 })
108 .collect();
109
110 matching_versions.sort_unstable();
111 let output = match matching_versions.last() {
112 Some(found_version) => {
113 let mut params = Params::new();
114 let mut state = params.hash_length(BLAKE2B_HASH_LEN).to_state();
115
116 state.update(crate_.name().as_bytes());
117 state.update(b"\0");
118 state.update(found_version.to_string().as_bytes());
119 state.update(b"\0");
120 state.update(&self.cache_version.to_be_bytes());
121 let blake2b_hash = state.finalize();
122 let blake2b_hex = blake2b_hash.to_hex();
123
124 let hash_str = BLAKE2B_PREFIX.to_owned() + blake2b_hex.as_str();
127 Output {
128 crate_name: crate_.name().to_owned(),
129 version: found_version.clone(),
130 hash: hash_str,
131 }
132 }
133 None => {
134 let versions = crate_.versions();
135 let latest_versions: Vec<_> = crate_
136 .versions()
137 .iter()
138 .rev()
139 .take(8)
140 .map(|v| v.version())
141 .collect();
142 let versions_found_str = latest_versions.join(", ");
143
144 let and_more = if versions.len() > MAX_VERSIONS_IN_ERR {
145 format!(" and {} more", versions.len() - MAX_VERSIONS_IN_ERR)
146 } else {
147 String::new()
148 };
149 bail!(
150 "for crate {}, no matching versions for req {} (versions found: {}{})",
151 crate_.name(),
152 self.version_req,
153 versions_found_str,
154 and_more,
155 );
156 }
157 };
158
159 match self.message_format {
160 MessageFormat::plain => {
161 println!(
162 "{} {} (hash: {})",
163 output.crate_name, output.version, output.hash
164 );
165 }
166 MessageFormat::toml => {
167 println!(r#"{} = "{}""#, output.crate_name, output.version);
168 }
169 MessageFormat::json => {
170 let json = serde_json::to_string(&output).wrap_err_with(|| {
171 format!("couldn't serialize serde output for {:?}", output)
172 })?;
173 println!("{}", json);
174 }
175 MessageFormat::github => {
176 println!("::set-output name=crate-name::{}", output.crate_name);
177 println!("::set-output name=version::{}", output.version);
178 println!("::set-output name=hash::{}", output.hash);
179 }
180 }
181
182 Ok(())
183 }
184}
185
186#[derive(Debug, Serialize)]
187#[serde(rename_all = "kebab-case")]
188struct Output {
189 crate_name: String,
190 version: Version,
191 hash: String,
192}