diff --git a/Cargo.lock b/Cargo.lock index bb39736f6de..3609173aa6d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -272,6 +272,7 @@ dependencies = [ "chrono", "civet", "claim", + "clap", "comrak", "conduit", "conduit-conditional-get", @@ -288,7 +289,6 @@ dependencies = [ "diesel", "diesel_full_text_search", "diesel_migrations", - "docopt", "dotenv", "env_logger", "failure", @@ -401,6 +401,38 @@ dependencies = [ "autocfg", ] +[[package]] +name = "clap" +version = "3.0.0-beta.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bd1061998a501ee7d4b6d449020df3266ca3124b941ec56cf2005c3779ca142" +dependencies = [ + "atty", + "bitflags", + "clap_derive", + "indexmap", + "lazy_static", + "os_str_bytes", + "strsim", + "termcolor", + "textwrap", + "unicode-width", + "vec_map", +] + +[[package]] +name = "clap_derive" +version = "3.0.0-beta.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "370f715b81112975b1b69db93e0b56ea4cd4e5002ac43b2da8474106a54096a1" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "cloudabi" version = "0.1.0" @@ -718,18 +750,6 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" -[[package]] -name = "docopt" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f525a586d310c87df72ebcd98009e57f1cc030c8c268305287a476beb653969" -dependencies = [ - "lazy_static", - "regex", - "serde", - "strsim", -] - [[package]] name = "dotenv" version = "0.15.0" @@ -1051,6 +1071,15 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" +[[package]] +name = "heck" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "hermit-abi" version = "0.1.17" @@ -1803,6 +1832,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "os_str_bytes" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ac6fe3538f701e339953a3ebbe4f39941aababa8a3f6964635b24ab526daeac" + [[package]] name = "parking_lot" version = "0.11.0" @@ -1995,6 +2030,30 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro-hack" version = "0.5.18" @@ -2632,9 +2691,9 @@ dependencies = [ [[package]] name = "strsim" -version = "0.9.3" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "subtle" @@ -2735,6 +2794,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "textwrap" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "203008d98caf094106cfaba70acfed15e18ed3ddb7d94e49baec153a2b462789" +dependencies = [ + "unicode-width", +] + [[package]] name = "thiserror" version = "1.0.21" @@ -2992,6 +3060,18 @@ dependencies = [ "smallvec 0.6.13", ] +[[package]] +name = "unicode-segmentation" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" + +[[package]] +name = "unicode-width" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" + [[package]] name = "unicode-xid" version = "0.2.1" @@ -3048,6 +3128,12 @@ version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6454029bf181f092ad1b853286f23e2c507d8e8194d01d92da4a55c274a5508c" +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + [[package]] name = "version_check" version = "0.9.2" diff --git a/Cargo.toml b/Cargo.toml index 21a29501a2f..fed99b833c9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ base64 = "0.13" cargo-registry-s3 = { path = "src/s3", version = "0.2.0" } chrono = { version = "0.4.0", features = ["serde"] } civet = "0.12.0-alpha.4" +clap = "=3.0.0-beta.2" comrak = { version = "0.8", default-features = false } conduit = "0.9.0-alpha.3" @@ -50,7 +51,6 @@ ctrlc = { version = "3.0", features = ["termination"] } derive_deref = "1.1.1" diesel = { version = "1.4.0", features = ["postgres", "serde_json", "chrono", "r2d2"] } diesel_full_text_search = "1.0.0" -docopt = "1.0" dotenv = "0.15" env_logger = "0.8" failure = "0.1.1" diff --git a/src/bin/delete-crate.rs b/src/bin/delete-crate.rs index b26abcd09ae..918e8577296 100644 --- a/src/bin/delete-crate.rs +++ b/src/bin/delete-crate.rs @@ -1,20 +1,21 @@ -// Purge all references to a crate from the database. -// -// Please be super sure you want to do this before running this. -// -// Usage: -// cargo run --bin delete-crate crate-name - #![warn(clippy::all, rust_2018_idioms)] use cargo_registry::{db, models::Crate, schema::crates}; -use std::{ - env, - io::{self, prelude::*}, -}; +use std::io::{self, prelude::*}; +use clap::Clap; use diesel::prelude::*; +#[derive(Clap, Debug)] +#[clap( + name = "delete-crate", + about = "Purge all references to a crate from the database.\n\nPlease be super sure you want to do this before running this." +)] +struct Opts { + /// Name of the crate + crate_name: String, +} + fn main() { let conn = db::connect_now().unwrap(); conn.transaction::<_, diesel::result::Error, _>(|| { @@ -25,18 +26,12 @@ fn main() { } fn delete(conn: &PgConnection) { - let name = match env::args().nth(1) { - None => { - println!("needs a crate-name argument"); - return; - } - Some(s) => s, - }; - - let krate: Crate = Crate::by_name(&name).first(conn).unwrap(); + let opts: Opts = Opts::parse(); + + let krate: Crate = Crate::by_name(&opts.crate_name).first(conn).unwrap(); print!( "Are you sure you want to delete {} ({}) [y/N]: ", - name, krate.id + opts.crate_name, krate.id ); io::stdout().flush().unwrap(); let mut line = String::new(); diff --git a/src/bin/delete-version.rs b/src/bin/delete-version.rs index dbb94d514b3..be6f21dbfe0 100644 --- a/src/bin/delete-version.rs +++ b/src/bin/delete-version.rs @@ -1,10 +1,3 @@ -// Purge all references to a crate's version from the database. -// -// Please be super sure you want to do this before running this. -// -// Usage: -// cargo run --bin delete-version crate-name version-number - #![warn(clippy::all, rust_2018_idioms)] use cargo_registry::{ @@ -12,13 +5,23 @@ use cargo_registry::{ models::{Crate, Version}, schema::versions, }; -use std::{ - env, - io::{self, prelude::*}, -}; +use std::io::{self, prelude::*}; +use clap::Clap; use diesel::prelude::*; +#[derive(Clap, Debug)] +#[clap( + name = "delete-version", + about = "Purge all references to a crate's version from the database.\n\nPlease be super sure you want to do this before running this." +)] +struct Opts { + /// Name of the crate + crate_name: String, + /// Version number that should be deleted + version: String, +} + fn main() { let conn = db::connect_now().unwrap(); conn.transaction::<_, diesel::result::Error, _>(|| { @@ -29,29 +32,16 @@ fn main() { } fn delete(conn: &PgConnection) { - let name = match env::args().nth(1) { - None => { - println!("needs a crate-name argument"); - return; - } - Some(s) => s, - }; - let version = match env::args().nth(2) { - None => { - println!("needs a version argument"); - return; - } - Some(s) => s, - }; + let opts: Opts = Opts::parse(); - let krate: Crate = Crate::by_name(&name).first(conn).unwrap(); + let krate: Crate = Crate::by_name(&opts.crate_name).first(conn).unwrap(); let v: Version = Version::belonging_to(&krate) - .filter(versions::num.eq(&version)) + .filter(versions::num.eq(&opts.version)) .first(conn) .unwrap(); print!( "Are you sure you want to delete {}#{} ({}) [y/N]: ", - name, version, v.id + opts.crate_name, opts.version, v.id ); io::stdout().flush().unwrap(); let mut line = String::new(); diff --git a/src/bin/populate.rs b/src/bin/populate.rs index bd2f990f378..108531bd69a 100644 --- a/src/bin/populate.rs +++ b/src/bin/populate.rs @@ -1,17 +1,21 @@ -// Populate a set of dummy download statistics for a specific version in the -// database. -// -// Usage: -// cargo run --bin populate version_id1 version_id2 ... - #![warn(clippy::all, rust_2018_idioms)] use cargo_registry::{db, schema::version_downloads}; -use std::env; +use clap::Clap; use diesel::prelude::*; use rand::{thread_rng, Rng}; +#[derive(Clap, Debug)] +#[clap( + name = "populate", + about = "Populate a set of dummy download statistics for a specific version in the database." +)] +struct Opts { + #[clap(required = true)] + version_ids: Vec, +} + fn main() { let conn = db::connect_now().unwrap(); conn.transaction(|| update(&conn)).unwrap(); @@ -20,10 +24,9 @@ fn main() { fn update(conn: &PgConnection) -> QueryResult<()> { use diesel::dsl::*; - let ids = env::args() - .skip(1) - .filter_map(|arg| arg.parse::().ok()); - for id in ids { + let opts: Opts = Opts::parse(); + + for id in opts.version_ids { let mut rng = thread_rng(); let mut dls = rng.gen_range(5_000i32, 10_000); diff --git a/src/bin/render-readmes.rs b/src/bin/render-readmes.rs index ed892e46af6..198d4404897 100644 --- a/src/bin/render-readmes.rs +++ b/src/bin/render-readmes.rs @@ -1,8 +1,3 @@ -// Iterates over every crate versions ever uploaded and (re-)renders their -// readme using the readme renderer from the cargo_registry crate. -// -// Warning: this can take a lot of time. - #![warn(clippy::all, rust_2018_idioms)] #[macro_use] @@ -18,42 +13,45 @@ use cargo_registry::{ use std::{io::Read, path::Path, thread}; use chrono::{TimeZone, Utc}; +use clap::Clap; use diesel::{dsl::any, prelude::*}; -use docopt::Docopt; use flate2::read::GzDecoder; use reqwest::{blocking::Client, header}; use tar::{self, Archive}; const CACHE_CONTROL_README: &str = "public,max-age=604800"; -const DEFAULT_PAGE_SIZE: usize = 25; -const USAGE: &str = " -Usage: render-readmes [options] - render-readmes --help -Options: - -h, --help Show this message. - --page-size NUM How many versions should be queried and processed at a time. - --older-than DATE Only rerender readmes that are older than this date. - --crate NAME Only rerender readmes for the specified crate. -"; +#[derive(Clap, Debug)] +#[clap( + name = "render-readmes", + about = "Iterates over every crate versions ever uploaded and (re-)renders their \ + readme using the readme renderer from the cargo_registry crate.\n\ + \n\ + Warning: this can take a lot of time." +)] +struct Opts { + /// How many versions should be queried and processed at a time. + #[clap(long, default_value = "25")] + page_size: usize, + + /// Only rerender readmes that are older than this date. + #[clap(long)] + older_than: Option, -#[derive(Deserialize)] -struct Args { - flag_page_size: Option, - flag_older_than: Option, - flag_crate: Option, + /// Only rerender readmes for the specified crate. + #[clap(long = "crate")] + crate_name: Option, } fn main() { - let args: Args = Docopt::new(USAGE) - .and_then(|d| d.deserialize()) - .unwrap_or_else(|e| e.exit()); + let opts: Opts = Opts::parse(); + let config = Config::default(); let conn = db::connect_now().unwrap(); let start_time = Utc::now(); - let older_than = if let Some(ref time) = args.flag_older_than { + let older_than = if let Some(ref time) = opts.older_than { Utc.datetime_from_str(time, "%Y-%m-%d %H:%M:%S") .expect("Could not parse --older-than argument as a time") } else { @@ -75,7 +73,7 @@ fn main() { .select(versions::id) .into_boxed(); - if let Some(crate_name) = args.flag_crate { + if let Some(crate_name) = opts.crate_name { println!("Rendering readmes for {}", crate_name); query = query.filter(crates::name.eq(crate_name)); } @@ -85,7 +83,7 @@ fn main() { let total_versions = version_ids.len(); println!("Rendering {} versions", total_versions); - let page_size = args.flag_page_size.unwrap_or(DEFAULT_PAGE_SIZE); + let page_size = opts.page_size; let total_pages = total_versions / page_size; let total_pages = if total_versions % page_size == 0 { diff --git a/src/bin/test-pagerduty.rs b/src/bin/test-pagerduty.rs index d48f5469375..3c292562018 100644 --- a/src/bin/test-pagerduty.rs +++ b/src/bin/test-pagerduty.rs @@ -1,38 +1,55 @@ -//! Send a test event to pagerduty -//! -//! Usage: -//! cargo run --bin test-pagerduty event_type [description] -//! -//! Event type can be trigger, acknowledge, or resolve - #![warn(clippy::all, rust_2018_idioms)] mod on_call; -use std::env::args; - use anyhow::Result; +use clap::Clap; +use failure::_core::str::FromStr; -fn main() -> Result<()> { - let args = args().collect::>(); +#[derive(Debug)] +enum EventType { + Trigger, + Acknowledge, + Resolve, +} + +impl FromStr for EventType { + type Err = &'static str; - let event_type = &*args[1]; - let description = args.get(2).cloned(); + fn from_str(value: &str) -> Result { + match value { + "trigger" => Ok(EventType::Trigger), + "acknowledge" => Ok(EventType::Acknowledge), + "resolve" => Ok(EventType::Resolve), + _ => Err("Event type must be trigger, acknowledge, or resolve"), + } + } +} + +#[derive(Clap, Debug)] +#[clap(name = "test-pagerduty", about = "Send a test event to pagerduty")] +struct Opts { + #[clap(possible_values = &["trigger", "acknowledge", "resolve"])] + event_type: EventType, + description: Option, +} + +fn main() -> Result<()> { + let opts: Opts = Opts::parse(); - let event = match event_type { - "trigger" => on_call::Event::Trigger { + let event = match opts.event_type { + EventType::Trigger => on_call::Event::Trigger { incident_key: Some("test".into()), - description: description.unwrap_or_else(|| "Test event".into()), + description: opts.description.unwrap_or_else(|| "Test event".into()), }, - "acknowledge" => on_call::Event::Acknowledge { + EventType::Acknowledge => on_call::Event::Acknowledge { incident_key: "test".into(), - description, + description: opts.description, }, - "resolve" => on_call::Event::Resolve { + EventType::Resolve => on_call::Event::Resolve { incident_key: "test".into(), - description, + description: opts.description, }, - _ => panic!("Event type must be trigger, acknowledge, or resolve"), }; event.send() } diff --git a/src/bin/transfer-crates.rs b/src/bin/transfer-crates.rs index b6dd913ea43..ddc8a713793 100644 --- a/src/bin/transfer-crates.rs +++ b/src/bin/transfer-crates.rs @@ -1,8 +1,3 @@ -// Transfer all crates from one user to another. -// -// Usage: -// cargo run --bin transfer-crates from-user to-user - #![warn(clippy::all, rust_2018_idioms)] use cargo_registry::{ @@ -11,13 +6,25 @@ use cargo_registry::{ schema::{crate_owners, crates, users}, }; use std::{ - env, io::{self, prelude::*}, process::exit, }; +use clap::Clap; use diesel::prelude::*; +#[derive(Clap, Debug)] +#[clap( + name = "transfer-crates", + about = "Transfer all crates from one user to another." +)] +struct Opts { + /// GitHub login of the "from" user + from_user: String, + /// GitHub login of the "to" user + to_user: String, +} + fn main() { let conn = db::connect_now().unwrap(); conn.transaction::<_, diesel::result::Error, _>(|| { @@ -28,27 +35,15 @@ fn main() { } fn transfer(conn: &PgConnection) { - let from = match env::args().nth(1) { - None => { - println!("needs a from-user argument"); - return; - } - Some(s) => s, - }; - let to = match env::args().nth(2) { - None => { - println!("needs a to-user argument"); - return; - } - Some(s) => s, - }; + let opts: Opts = Opts::parse(); let from: User = users::table - .filter(users::gh_login.eq(from)) + .filter(users::gh_login.eq(opts.from_user)) .first(conn) .unwrap(); + let to: User = users::table - .filter(users::gh_login.eq(to)) + .filter(users::gh_login.eq(opts.to_user)) .first(conn) .unwrap(); diff --git a/src/bin/verify-token.rs b/src/bin/verify-token.rs index dc2198f58d7..584eb37667e 100644 --- a/src/bin/verify-token.rs +++ b/src/bin/verify-token.rs @@ -1,14 +1,23 @@ -// Look up a username by API token. Used by staff to verify someone's identity -// by having an API token given. If an error occurs, including being unable to -// find a user with that API token, the error will be displayed. - use cargo_registry::{db, models::User, util::errors::AppResult}; -use std::env; + +use clap::Clap; + +#[derive(Clap, Debug)] +#[clap( + name = "verify-token", + about = "Look up a username by API token. Used by staff to verify someone's identity \ + by having an API token given. If an error occurs, including being unable to \ + find a user with that API token, the error will be displayed." +)] +struct Opts { + api_token: String, +} fn main() -> AppResult<()> { + let opts: Opts = Opts::parse(); + let conn = db::connect_now()?; - let token = env::args().nth(1).expect("API token argument required"); - let user = User::find_by_api_token(&conn, &token)?; + let user = User::find_by_api_token(&conn, &opts.api_token)?; println!("The token belongs to user {}", user.gh_login); Ok(()) }