use std::path::{Path, PathBuf};
use anyhow::Context as _;
use anyhow::Result;
use ruggle_engine::search::Hit;
use ruggle_engine::search::Scope;
use ruggle_server::{generate_bin_index, make_index, make_sets, perform_search, shake_index};
use structopt::StructOpt;
use tracing::info;
use tracing_subscriber::EnvFilter;
#[derive(Debug, StructOpt)]
struct Cli {
#[structopt(long, default_value = "http://localhost:8000")]
host: String,
#[structopt(long, parse(from_os_str))]
index: Option<PathBuf>,
#[structopt(long)]
scope: String,
#[structopt(long, default_value = "30")]
limit: usize,
#[structopt(long, default_value = "0.4")]
threshold: f32,
#[structopt(long)]
json: bool,
#[structopt(long)]
query: String,
#[structopt(long)]
server: bool,
#[structopt(long)]
shake: bool,
#[structopt(long)]
binary: bool,
}
async fn ask_server(
host: &str,
scope: &str,
query: &str,
limit: usize,
threshold: f32,
) -> Result<Vec<Hit>> {
let client = reqwest::Client::new();
tracing::debug!("(scope={}, query={})", scope, query);
let url = format!(
"{}/search?scope={}&query={}&limit={}&threshold={}",
host,
urlencoding::encode(scope),
urlencoding::encode(query),
limit,
threshold
);
tracing::debug!("requesting {}", url);
let res = client.get(&url).send().await.context("request failed")?;
let status = res.status();
if !status.is_success() {
let text = res.text().await.unwrap_or_default();
anyhow::bail!("{}: {}", status, text);
}
let hits: Vec<Hit> = res.json().await.context("invalid response body")?;
Ok(hits)
}
#[tokio::main]
async fn main() -> Result<()> {
println!("ruggle Client v{}", env!("CARGO_PKG_VERSION"));
tracing_subscriber::fmt::fmt()
.with_env_filter(EnvFilter::from_default_env())
.with_file(true)
.with_line_number(true)
.without_time()
.init();
println!("Logger initialized");
let cli = Cli::from_args();
println!("Arguments parsed: {:?}", cli);
println!("Searching for: {}", cli.query);
let index_dir = cli
.index
.unwrap_or_else(|| PathBuf::from(concat!(env!("CARGO_MANIFEST_DIR"), "/../ruggle-index")));
if cli.shake {
shake_index(&index_dir).context("failed to shake index")?;
info!("index shaken successfully");
return Ok(());
}
if cli.binary {
info!("generating binary index under {}", index_dir.display());
generate_bin_index(&index_dir).context("failed to generate binary index")?;
info!("binary index generated successfully");
return Ok(());
}
let hits = if cli.server {
ask_server(&cli.host, &cli.scope, &cli.query, cli.limit, cli.threshold).await?
} else {
let index = make_index(&index_dir).await.expect("failed to build index");
tracing::info!("index built successfully");
let sets = make_sets(Path::new(&index_dir));
let krates = index
.crates
.keys()
.map(|k| (k.clone(), Scope::Crate(k.clone())))
.collect();
let scopes = ruggle_server::Scopes { sets, krates };
perform_search(
&index,
&scopes,
&cli.query,
&cli.scope,
Some(cli.limit),
Some(cli.threshold),
)?
};
if cli.json {
println!("{}", serde_json::to_string_pretty(&hits)?);
return Ok(());
}
for (i, h) in hits.iter().enumerate() {
let link = format!("https://doc.rust-lang.org/{}", h.link);
println!(
"{:>2}. {} ({}) ({}) ({})\n {}",
i + 1,
h.name,
h.id.0,
h.path.join("::"),
h.signature,
link
);
}
Ok(())
}