mod build_std;
use std::path::{Path, PathBuf};
use clap::{Parser, Subcommand, ValueHint};
mod git;
mod index;
mod mirror;
mod serve;
fn validate_url(url: &str) -> Result<String, String> {
if url.starts_with("http://") || url.starts_with("https://") {
Ok(url.to_string())
} else {
Err(String::from("The URL must start with http:// or https://"))
}
}
#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)]
struct Crate {
name: String,
version: String,
}
impl Crate {
pub fn new(name: String, version: String) -> Self {
Self { name, version }
}
}
#[derive(Parser)]
#[command(version, about)]
struct Args {
#[command(subcommand)]
command: Command,
#[arg(short, long, global = true)]
verbose: bool,
}
#[derive(Subcommand)]
enum Command {
Mirror {
mirror_path: PathBuf,
workspaces: Vec<String>,
#[arg(long = "crate", value_name = "NAME[@VERSION]")]
extra_crates: Vec<String>,
#[arg(long, value_name = "VERSION")]
build_std: Option<String>,
#[arg(long)]
#[arg(value_hint = ValueHint::Url, value_parser = validate_url)]
#[arg(requires = "git_index")]
git_index_url: Option<String>,
#[arg(long)]
git_index: bool,
#[arg(long)]
get_feature_gated: bool,
},
UpdateIndex {
mirror_path: PathBuf,
#[arg(long)]
#[arg(value_hint = ValueHint::Url, value_parser = validate_url)]
dl_url: Option<String>,
},
Serve {
mirror_path: PathBuf,
#[arg(long, default_value = "0.0.0.0:8080")]
bind: String,
},
}
fn main() -> anyhow::Result<()> {
tracing_subscriber::fmt()
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
.init();
let args = Args::parse();
match args.command {
Command::Mirror {
mirror_path,
workspaces,
extra_crates,
build_std,
git_index_url,
git_index,
get_feature_gated,
} => {
if workspaces.is_empty() && extra_crates.is_empty() && build_std.is_none() {
anyhow::bail!("provide at least one workspace, --crate, or --build-std");
}
mirror::mirror(
mirror_path,
workspaces,
extra_crates,
build_std,
git_index_url,
git_index,
get_feature_gated,
args.verbose,
)?;
}
Command::UpdateIndex {
mirror_path,
dl_url,
} => {
let index_path = mirror_path.join("crates.io-index");
let crates_path = mirror_path.join("crates");
index::update_index(&index_path, &crates_path, dl_url.as_deref(), args.verbose)?;
}
Command::Serve { mirror_path, bind } => {
serve::serve(mirror_path, bind)?;
}
}
Ok(())
}
pub fn get_index_prefix(crate_name: &str) -> Option<PathBuf> {
match crate_name.len() {
1 => Some(PathBuf::from("1")),
2 => Some(PathBuf::from("2")),
3 => {
let first = crate_name.get(0..1)?;
Some([PathBuf::from("3"), first.into()].iter().collect())
}
n if n >= 4 => {
let first_two = crate_name.get(0..2)?;
let second_two = crate_name.get(2..4)?;
Some([first_two, second_two].iter().collect())
}
_ => None,
}
}
pub fn get_crate_path(
mirror_path: &Path,
crate_name: &str,
crate_version: &str,
) -> Option<PathBuf> {
let crate_path = get_index_prefix(crate_name)?;
Some(
mirror_path
.join("crates")
.join(crate_path)
.join(crate_name)
.join(crate_version),
)
}