From c4c74267a54a3d064169040985327ecb091b96e6 Mon Sep 17 00:00:00 2001 From: Ryo Yamashita Date: Mon, 3 Aug 2020 17:51:43 +0900 Subject: [PATCH 1/7] Save URLs to `package.metadata` --- Cargo.toml | 2 +- src/commands/retrieve_testcases.rs | 36 ++++++++++++----------- src/commands/submit.rs | 8 +++--- src/project.rs | 46 +++++++++++++++++++++--------- src/testing.rs | 8 +++--- 5 files changed, 61 insertions(+), 39 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f04e7b5..98d4bf8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,7 +45,7 @@ termcolor = "1.1.0" tokio = { version = "0.2.22", features = ["signal"] } toml = "0.5.6" toml_edit = "0.2.0" -url = "2.1.1" +url = { version = "2.1.1", features = ["serde"] } which = "4.0.1" [target.'cfg(windows)'.dependencies] diff --git a/src/commands/retrieve_testcases.rs b/src/commands/retrieve_testcases.rs index 940e58b..f36ad07 100644 --- a/src/commands/retrieve_testcases.rs +++ b/src/commands/retrieve_testcases.rs @@ -107,20 +107,24 @@ pub(crate) fn run(opt: OptCompeteRetrieveTestcases, ctx: crate::Context<'_>) -> member.read_package_metadata()?.bin { match problem { - TargetProblem::Atcoder { contest, index } => atcoder_targets + TargetProblem::Atcoder { contest, index, .. } => atcoder_targets .entry(contest) .or_insert_with(BTreeSet::new) .insert(index), - TargetProblem::Codeforces { contest, index } => codeforces_targets + TargetProblem::Codeforces { contest, index, .. } => codeforces_targets .entry(contest) .or_insert_with(BTreeSet::new) .insert(index), TargetProblem::Yukicoder(target) => match target { - TargetProblemYukicoder::Problem { no } => yukicoder_problem_targets.insert(no), - TargetProblemYukicoder::Contest { contest, index } => yukicoder_contest_targets - .entry(contest) - .or_insert_with(BTreeSet::new) - .insert(index), + TargetProblemYukicoder::Problem { no, .. } => { + yukicoder_problem_targets.insert(no) + } + TargetProblemYukicoder::Contest { contest, index, .. } => { + yukicoder_contest_targets + .entry(contest) + .or_insert_with(BTreeSet::new) + .insert(index) + } }, }; } @@ -198,10 +202,10 @@ pub(crate) fn run(opt: OptCompeteRetrieveTestcases, ctx: crate::Context<'_>) -> .map(|RetrieveTestCasesOutcomeContest { id, .. }| id) .unwrap_or(&contest); - let problem_indexes = outcome + let problems = outcome .problems .iter() - .map(|RetrieveTestCasesOutcomeProblem { index, .. }| index.clone()) + .map(|RetrieveTestCasesOutcomeProblem { index, url, .. }| (&**index, url)) .collect(); let workspace_root = metadata.workspace_root.clone(); @@ -209,7 +213,7 @@ pub(crate) fn run(opt: OptCompeteRetrieveTestcases, ctx: crate::Context<'_>) -> let src_paths = src_paths(&pkg_manifest_dir, &outcome); let urls = urls(&outcome); - metadata.add_member(package_name, &problem_indexes, false, shell)?; + metadata.add_member(package_name, &problems, false, shell)?; let test_suite_paths = save_test_cases( &workspace_root, @@ -242,10 +246,10 @@ pub(crate) fn run(opt: OptCompeteRetrieveTestcases, ctx: crate::Context<'_>) -> .map(|RetrieveTestCasesOutcomeContest { id, .. }| id) .unwrap_or(&contest); - let problem_indexes = outcome + let problems = outcome .problems .iter() - .map(|RetrieveTestCasesOutcomeProblem { index, .. }| index.clone()) + .map(|RetrieveTestCasesOutcomeProblem { index, url, .. }| (&**index, url)) .collect(); let workspace_root = metadata.workspace_root.clone(); @@ -253,7 +257,7 @@ pub(crate) fn run(opt: OptCompeteRetrieveTestcases, ctx: crate::Context<'_>) -> let src_paths = src_paths(&pkg_manifest_dir, &outcome); let urls = urls(&outcome); - metadata.add_member(package_name, &problem_indexes, false, shell)?; + metadata.add_member(package_name, &problems, false, shell)?; let test_suite_paths = save_test_cases( &workspace_root, @@ -288,10 +292,10 @@ pub(crate) fn run(opt: OptCompeteRetrieveTestcases, ctx: crate::Context<'_>) -> let is_no = package_name.is_none(); let package_name = package_name.unwrap_or("problems"); - let problem_indexes = outcome + let problems = outcome .problems .iter() - .map(|RetrieveTestCasesOutcomeProblem { index, .. }| index.clone()) + .map(|RetrieveTestCasesOutcomeProblem { index, url, .. }| (&**index, url)) .collect(); let workspace_root = metadata.workspace_root.clone(); @@ -299,7 +303,7 @@ pub(crate) fn run(opt: OptCompeteRetrieveTestcases, ctx: crate::Context<'_>) -> let src_paths = src_paths(&pkg_manifest_dir, &outcome); let urls = urls(&outcome); - metadata.add_member(package_name, &problem_indexes, is_no, shell)?; + metadata.add_member(package_name, &problems, is_no, shell)?; let test_suite_paths = save_test_cases( &workspace_root, diff --git a/src/commands/submit.rs b/src/commands/submit.rs index 4ceea3a..4d20b75 100644 --- a/src/commands/submit.rs +++ b/src/commands/submit.rs @@ -205,7 +205,7 @@ pub(crate) fn run(opt: OptCompeteSubmit, ctx: crate::Context<'_>) -> anyhow::Res let timeout = crate::web::TIMEOUT; let outcome = match package_metadata_bin.problem { - TargetProblem::Atcoder { contest, index } => { + TargetProblem::Atcoder { contest, index, .. } => { let shell = RefCell::new(shell.borrow_mut()); let credentials = AtcoderSubmitCredentials { @@ -230,7 +230,7 @@ pub(crate) fn run(opt: OptCompeteSubmit, ctx: crate::Context<'_>) -> anyhow::Res shell: &shell, })? } - TargetProblem::Codeforces { contest, index } => { + TargetProblem::Codeforces { contest, index, .. } => { let (api_key, api_secret) = credentials::codeforces_api_key_and_secret(shell)?; let shell = RefCell::new(shell.borrow_mut()); @@ -266,10 +266,10 @@ pub(crate) fn run(opt: OptCompeteSubmit, ctx: crate::Context<'_>) -> anyhow::Res Yukicoder::exec(Submit { target: match target_problem { - TargetProblemYukicoder::Contest { contest, index } => { + TargetProblemYukicoder::Contest { contest, index, .. } => { YukicoderSubmitTarget::Contest(contest, index) } - TargetProblemYukicoder::Problem { no } => { + TargetProblemYukicoder::Problem { no, .. } => { YukicoderSubmitTarget::ProblemNo(no.to_string()) } }, diff --git a/src/project.rs b/src/project.rs index 3bbf2b8..f990009 100644 --- a/src/project.rs +++ b/src/project.rs @@ -7,7 +7,7 @@ use indexmap::IndexMap; use itertools::Itertools as _; use serde::{de::Error as _, Deserialize, Deserializer}; use std::{ - collections::{BTreeMap, BTreeSet}, + collections::BTreeMap, env, path::{Path, PathBuf}, str, @@ -170,16 +170,31 @@ pub(crate) struct PackageMetadataCargoCompeteBin { #[derive(Deserialize, Debug, Ord, PartialOrd, Eq, PartialEq)] #[serde(rename_all = "kebab-case", tag = "platform")] pub(crate) enum TargetProblem { - Atcoder { contest: String, index: String }, - Codeforces { contest: String, index: String }, + Atcoder { + contest: String, + index: String, + url: Option, + }, + Codeforces { + contest: String, + index: String, + url: Option, + }, Yukicoder(TargetProblemYukicoder), } #[derive(Deserialize, Debug, Ord, PartialOrd, Eq, PartialEq)] #[serde(rename_all = "kebab-case", tag = "kind")] pub(crate) enum TargetProblemYukicoder { - Problem { no: u64 }, - Contest { contest: String, index: String }, + Problem { + no: u64, + url: Option, + }, + Contest { + contest: String, + index: String, + url: Option, + }, } #[ext(MetadataExt)] @@ -239,7 +254,7 @@ impl Metadata { pub(crate) fn add_member( self, package_name: &str, - problem_indexes: &BTreeSet, + problems: &BTreeMap<&str, &Url>, problems_are_yukicoder_no: bool, shell: &mut Shell, ) -> anyhow::Result<()> { @@ -255,8 +270,8 @@ publish = false [package.metadata.cargo-compete.bin] {}"#, - problem_indexes - .iter() + problems + .keys() .map(|problem_index| format!( r#"{} = {{ name = "", problem = {{ {} }} }} "#, @@ -264,13 +279,13 @@ publish = false match (&workspace_metadata.platform, problems_are_yukicoder_no) { (WorkspaceMetadataCargoCompetePlatform::Atcoder { .. }, _) | (WorkspaceMetadataCargoCompetePlatform::Codeforces, _) => { - r#"platform = "", contest = "", index = """# + r#"platform = "", contest = "", index = "", url = """# } (WorkspaceMetadataCargoCompetePlatform::Yukicoder, true) => { - r#"platform = "", kind = "no", no = """# + r#"platform = "", kind = "no", no = "", url = """# } (WorkspaceMetadataCargoCompetePlatform::Yukicoder, false) => { - r#"platform = "", kind = "contest", contest = "", index = """# + r#"platform = "", kind = "contest", contest = "", index = "", url = """# } } )) @@ -281,7 +296,7 @@ publish = false manifest["package"]["name"] = toml_edit::value(package_name); let tbl = &mut manifest["package"]["metadata"]["cargo-compete"]["bin"]; - for problem_index in problem_indexes { + for (problem_index, problem_url) in problems { tbl[problem_index.to_kebab_case()]["name"] = toml_edit::value(format!( "{}-{}", package_name, @@ -295,11 +310,13 @@ publish = false tbl["platform"] = toml_edit::value("atcoder"); tbl["contest"] = toml_edit::value(package_name); tbl["index"] = toml_edit::value(&**problem_index); + tbl["url"] = toml_edit::value(problem_url.as_str()); } WorkspaceMetadataCargoCompetePlatform::Codeforces => { tbl["platform"] = toml_edit::value("codeforces"); tbl["contest"] = toml_edit::value(package_name); tbl["index"] = toml_edit::value(&**problem_index); + tbl["url"] = toml_edit::value(problem_url.as_str()); } WorkspaceMetadataCargoCompetePlatform::Yukicoder => { tbl["platform"] = toml_edit::value("yukicoder"); @@ -309,6 +326,7 @@ publish = false tbl["contest"] = toml_edit::value(package_name); tbl["index"] = toml_edit::value(&**problem_index); } + tbl["url"] = toml_edit::value(problem_url.as_str()); } } } @@ -323,7 +341,7 @@ publish = false manifest["bin"] = toml_edit::Item::ArrayOfTables({ let mut arr = toml_edit::ArrayOfTables::new(); - for problem_index in problem_indexes { + for problem_index in problems.keys() { let mut tbl = toml_edit::Table::new(); tbl["name"] = toml_edit::value(format!( "{}-{}", @@ -357,7 +375,7 @@ publish = false let template_code = crate::fs::read_to_string(self.workspace_root.join(workspace_metadata.template.code))?; - for problem_index in problem_indexes { + for problem_index in problems.keys() { let src_path = src_bin .join(problem_index.to_kebab_case()) .with_extension("rs"); diff --git a/src/testing.rs b/src/testing.rs index 97c015e..bfa6cb5 100644 --- a/src/testing.rs +++ b/src/testing.rs @@ -37,9 +37,9 @@ pub(crate) fn test(args: Args<'_>) -> anyhow::Result<()> { let bin = member.bin_target(&package_metadata_bin.name)?; let test_suite_path = match &package_metadata_bin.problem { - TargetProblem::Atcoder { contest, index } - | TargetProblem::Codeforces { contest, index } - | TargetProblem::Yukicoder(TargetProblemYukicoder::Contest { contest, index }) => { + TargetProblem::Atcoder { contest, index, .. } + | TargetProblem::Codeforces { contest, index, .. } + | TargetProblem::Yukicoder(TargetProblemYukicoder::Contest { contest, index, .. }) => { let test_suite_path = workspace_metadata .test_suite .eval(&btreemap!("contest" => &**contest, "problem" => &index))?; @@ -49,7 +49,7 @@ pub(crate) fn test(args: Args<'_>) -> anyhow::Result<()> { .unwrap_or(&test_suite_path); metadata.workspace_root.join(test_suite_path) } - TargetProblem::Yukicoder(TargetProblemYukicoder::Problem { no }) => { + TargetProblem::Yukicoder(TargetProblemYukicoder::Problem { no, .. }) => { let no = no.to_string(); let test_suite_path = workspace_metadata .test_suite From e921906f6591fcc983dff55b4e22603b6a9b2a97 Mon Sep 17 00:00:00 2001 From: Ryo Yamashita Date: Mon, 3 Aug 2020 20:20:05 +0900 Subject: [PATCH 2/7] Implement `open` command --- src/commands/mod.rs | 1 + src/commands/open.rs | 116 ++++++++ src/commands/retrieve_testcases.rs | 440 ++++------------------------- src/lib.rs | 13 +- src/open.rs | 42 +++ src/project.rs | 45 +-- src/testing.rs | 56 ++-- src/web/mod.rs | 1 + src/web/retrieve_testcases.rs | 311 ++++++++++++++++++++ 9 files changed, 589 insertions(+), 436 deletions(-) create mode 100644 src/commands/open.rs create mode 100644 src/open.rs create mode 100644 src/web/retrieve_testcases.rs diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 8f76ebe..888cac3 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,5 +1,6 @@ pub(crate) mod init; pub(crate) mod login; +pub(crate) mod open; pub(crate) mod participate; pub(crate) mod retrieve_testcases; pub(crate) mod submit; diff --git a/src/commands/open.rs b/src/commands/open.rs new file mode 100644 index 0000000..da9ab00 --- /dev/null +++ b/src/commands/open.rs @@ -0,0 +1,116 @@ +use crate::{ + project::{ + MetadataExt as _, PackageExt as _, PackageMetadataCargoCompeteBin, TargetProblem, + TargetProblemYukicoder, + }, + shell::ColorChoice, +}; +use maplit::hashset; +use std::{collections::HashSet, path::PathBuf}; +use structopt::StructOpt; +use strum::VariantNames as _; + +#[derive(StructOpt, Debug)] +pub struct OptCompeteOpen { + /// Retrieves system test cases + #[structopt(long)] + pub full: bool, + + /// Problem indexes + #[structopt(long)] + pub problems: Option>, + + /// Package (see `cargo help pkgid`) + #[structopt(short, long, value_name("SPEC"))] + pub package: Option, + + /// Path to Cargo.toml + #[structopt(long, value_name("PATH"))] + pub manifest_path: Option, + + /// Coloring + #[structopt( + long, + value_name("WHEN"), + possible_values(ColorChoice::VARIANTS), + default_value("auto") + )] + pub color: ColorChoice, +} + +pub(crate) fn run(opt: OptCompeteOpen, ctx: crate::Context<'_>) -> anyhow::Result<()> { + let OptCompeteOpen { + full, + problems, + package, + manifest_path, + color, + } = opt; + + let crate::Context { cwd, shell } = ctx; + + shell.set_color_choice(color); + + let problems = problems.map(|ps| ps.into_iter().collect::>()); + + let manifest_path = manifest_path + .map(Ok) + .unwrap_or_else(|| crate::project::locate_project(&cwd))?; + let metadata = crate::project::cargo_metadata(&manifest_path)?; + let workspace_metadata = metadata.read_workspace_metadata()?; + + let member = metadata.query_for_member(package)?; + + let package_metadata_bin = member.read_package_metadata()?.bin; + + let mut urls = vec![]; + let mut file_paths = vec![]; + let mut missing = hashset!(); + + for (index, PackageMetadataCargoCompeteBin { name, problem, .. }) in &package_metadata_bin { + if problems.as_ref().map_or(true, |ps| ps.contains(index)) { + urls.extend(match problem { + TargetProblem::Atcoder { url, .. } + | TargetProblem::Codeforces { url, .. } + | TargetProblem::Yukicoder(TargetProblemYukicoder::Problem { url, .. }) + | TargetProblem::Yukicoder(TargetProblemYukicoder::Contest { url, .. }) => { + url.clone() + } + }); + + let test_suite_path = crate::testing::test_suite_path( + &metadata.workspace_root, + &workspace_metadata.test_suite, + &problem, + )?; + + if !test_suite_path.exists() { + missing.insert(index.clone()); + } + + file_paths.push((&member.bin_target(&name)?.src_path, test_suite_path)); + } + } + + if !missing.is_empty() { + shell.status("Retrieving", "missing test cases")?; + + crate::web::retrieve_testcases::dl_missing( + &package_metadata_bin, + Some(&missing), + full, + &metadata.workspace_root, + &workspace_metadata.test_suite, + shell, + )?; + } + + crate::open::open( + &urls, + workspace_metadata.open, + &file_paths, + member.manifest_path.parent().unwrap(), + &cwd, + shell, + ) +} diff --git a/src/commands/retrieve_testcases.rs b/src/commands/retrieve_testcases.rs index f36ad07..c797625 100644 --- a/src/commands/retrieve_testcases.rs +++ b/src/commands/retrieve_testcases.rs @@ -1,32 +1,13 @@ use crate::{ - project::{ - MetadataExt as _, Open, PackageExt as _, PackageMetadataCargoCompeteBin, TargetProblem, - TargetProblemYukicoder, TemplateString, WorkspaceMetadataCargoCompetePlatform, - }, - shell::{ColorChoice, Shell}, - web::credentials, + project::{MetadataExt as _, WorkspaceMetadataCargoCompetePlatform}, + shell::ColorChoice, }; use anyhow::Context as _; use heck::KebabCase as _; -use maplit::{btreemap, btreeset}; -use snowchains_core::{ - testsuite::{Additional, BatchTestSuite, TestSuite}, - web::{ - Atcoder, AtcoderRetrieveFullTestCasesCredentials, - AtcoderRetrieveSampleTestCasesCredentials, AtcoderRetrieveTestCasesTargets, Codeforces, - CodeforcesRetrieveSampleTestCasesCredentials, CodeforcesRetrieveTestCasesTargets, - CookieStorage, RetrieveFullTestCases, RetrieveTestCases, RetrieveTestCasesOutcome, - RetrieveTestCasesOutcomeContest, RetrieveTestCasesOutcomeProblem, - RetrieveTestCasesOutcomeProblemTextFiles, Yukicoder, - YukicoderRetrieveFullTestCasesCredentials, YukicoderRetrieveTestCasesTargets, - }, -}; -use std::{ - borrow::BorrowMut as _, - cell::RefCell, - collections::BTreeSet, - path::{Path, PathBuf}, +use snowchains_core::web::{ + RetrieveTestCasesOutcome, RetrieveTestCasesOutcomeContest, RetrieveTestCasesOutcomeProblem, }; +use std::path::{Path, PathBuf}; use structopt::StructOpt; use strum::VariantNames as _; use url::Url; @@ -98,103 +79,16 @@ pub(crate) fn run(opt: OptCompeteRetrieveTestcases, ctx: crate::Context<'_>) -> }; if let Some(member) = member { - let mut atcoder_targets = btreemap!(); - let mut codeforces_targets = btreemap!(); - let mut yukicoder_problem_targets = btreeset!(); - let mut yukicoder_contest_targets = btreemap!(); - - for (_, PackageMetadataCargoCompeteBin { problem, .. }) in - member.read_package_metadata()?.bin - { - match problem { - TargetProblem::Atcoder { contest, index, .. } => atcoder_targets - .entry(contest) - .or_insert_with(BTreeSet::new) - .insert(index), - TargetProblem::Codeforces { contest, index, .. } => codeforces_targets - .entry(contest) - .or_insert_with(BTreeSet::new) - .insert(index), - TargetProblem::Yukicoder(target) => match target { - TargetProblemYukicoder::Problem { no, .. } => { - yukicoder_problem_targets.insert(no) - } - TargetProblemYukicoder::Contest { contest, index, .. } => { - yukicoder_contest_targets - .entry(contest) - .or_insert_with(BTreeSet::new) - .insert(index) - } - }, - }; - } - - let mut outcomes = vec![]; - - for (contest, problems) in atcoder_targets { - outcomes.push(dl_from_atcoder(&contest, Some(problems), full, shell)?); - } - for (contest, problems) in codeforces_targets { - outcomes.push(dl_from_codeforces(&contest, Some(problems), shell)?); - } - if !yukicoder_problem_targets.is_empty() { - outcomes.push(dl_from_yukicoder( - None, - Some( - yukicoder_problem_targets - .iter() - .map(ToString::to_string) - .collect(), - ), - full, - shell, - )?); - } - for (contest, problems) in yukicoder_contest_targets { - outcomes.push(dl_from_yukicoder( - Some(&contest), - Some(problems), - full, - shell, - )?); - } - - let src_paths = member - .targets - .iter() - .filter(|t| t.kind == ["bin".to_owned()]) - .map(|t| t.src_path.clone()) - .collect::>(); - - for outcome in outcomes { - let urls = urls(&outcome); - - let test_suite_paths = save_test_cases( - &metadata.workspace_root, - &workspace_metadata.test_suite, - outcome, - shell, - )?; - - if open { - open_urls_and_files( - &urls, - workspace_metadata.open, - &src_paths, - &test_suite_paths, - member.manifest_path.parent().unwrap(), - &cwd, - shell, - )?; - } - } + todo!(); } else { match workspace_metadata.platform { WorkspaceMetadataCargoCompetePlatform::Atcoder { .. } => { let contest = contest.with_context(|| "`contest` is required for AtCoder")?; let problems = problems.map(|ps| ps.into_iter().collect()); - let outcome = dl_from_atcoder(&contest, problems, full, shell)?; + let outcome = crate::web::retrieve_testcases::dl_from_atcoder( + &contest, problems, full, shell, + )?; let package_name = outcome .contest @@ -210,24 +104,26 @@ pub(crate) fn run(opt: OptCompeteRetrieveTestcases, ctx: crate::Context<'_>) -> let workspace_root = metadata.workspace_root.clone(); let pkg_manifest_dir = metadata.workspace_root.join(package_name); - let src_paths = src_paths(&pkg_manifest_dir, &outcome); let urls = urls(&outcome); metadata.add_member(package_name, &problems, false, shell)?; - let test_suite_paths = save_test_cases( - &workspace_root, - &workspace_metadata.test_suite, - outcome, - shell, - )?; + let file_paths = itertools::zip_eq( + src_paths(&pkg_manifest_dir, &outcome), + crate::web::retrieve_testcases::save_test_cases( + &workspace_root, + &workspace_metadata.test_suite, + outcome, + shell, + )?, + ) + .collect::>(); if open { - open_urls_and_files( + crate::open::open( &urls, workspace_metadata.open, - &src_paths, - &test_suite_paths, + &file_paths, &pkg_manifest_dir, &cwd, shell, @@ -238,7 +134,8 @@ pub(crate) fn run(opt: OptCompeteRetrieveTestcases, ctx: crate::Context<'_>) -> let contest = contest.with_context(|| "`contest` is required for Codeforces")?; let problems = problems.map(|ps| ps.into_iter().collect()); - let outcome = dl_from_codeforces(&contest, problems, shell)?; + let outcome = + crate::web::retrieve_testcases::dl_from_codeforces(&contest, problems, shell)?; let package_name = outcome .contest @@ -254,24 +151,26 @@ pub(crate) fn run(opt: OptCompeteRetrieveTestcases, ctx: crate::Context<'_>) -> let workspace_root = metadata.workspace_root.clone(); let pkg_manifest_dir = metadata.workspace_root.join(package_name); - let src_paths = src_paths(&pkg_manifest_dir, &outcome); let urls = urls(&outcome); metadata.add_member(package_name, &problems, false, shell)?; - let test_suite_paths = save_test_cases( - &workspace_root, - &workspace_metadata.test_suite, - outcome, - shell, - )?; + let file_paths = itertools::zip_eq( + src_paths(&pkg_manifest_dir, &outcome), + crate::web::retrieve_testcases::save_test_cases( + &workspace_root, + &workspace_metadata.test_suite, + outcome, + shell, + )?, + ) + .collect::>(); if open { - open_urls_and_files( + crate::open::open( &urls, workspace_metadata.open, - &src_paths, - &test_suite_paths, + &file_paths, &pkg_manifest_dir, &cwd, shell, @@ -282,7 +181,9 @@ pub(crate) fn run(opt: OptCompeteRetrieveTestcases, ctx: crate::Context<'_>) -> let contest = contest.as_deref(); let problems = problems.map(|ps| ps.into_iter().collect()); - let outcome = dl_from_yukicoder(contest, problems, full, shell)?; + let outcome = crate::web::retrieve_testcases::dl_from_yukicoder( + contest, problems, full, shell, + )?; let package_name = outcome .contest @@ -300,24 +201,26 @@ pub(crate) fn run(opt: OptCompeteRetrieveTestcases, ctx: crate::Context<'_>) -> let workspace_root = metadata.workspace_root.clone(); let pkg_manifest_dir = metadata.workspace_root.join(package_name); - let src_paths = src_paths(&pkg_manifest_dir, &outcome); let urls = urls(&outcome); metadata.add_member(package_name, &problems, is_no, shell)?; - let test_suite_paths = save_test_cases( - &workspace_root, - &workspace_metadata.test_suite, - outcome, - shell, - )?; + let file_paths = itertools::zip_eq( + src_paths(&pkg_manifest_dir, &outcome), + crate::web::retrieve_testcases::save_test_cases( + &workspace_root, + &workspace_metadata.test_suite, + outcome, + shell, + )?, + ) + .collect::>(); if open { - open_urls_and_files( + crate::open::open( &urls, workspace_metadata.open, - &src_paths, - &test_suite_paths, + &file_paths, &pkg_manifest_dir, &cwd, shell, @@ -329,115 +232,6 @@ pub(crate) fn run(opt: OptCompeteRetrieveTestcases, ctx: crate::Context<'_>) -> Ok(()) } -fn dl_from_atcoder( - contest: &str, - problems: Option>, - full: bool, - shell: &mut Shell, -) -> anyhow::Result { - let targets = AtcoderRetrieveTestCasesTargets { - contest: contest.to_owned(), - problems, - }; - - let shell = RefCell::new(shell.borrow_mut()); - - let credentials = AtcoderRetrieveSampleTestCasesCredentials { - username_and_password: &mut credentials::username_and_password( - &shell, - "Username: ", - "Password: ", - ), - }; - - let full = if full { - Some(RetrieveFullTestCases { - credentials: AtcoderRetrieveFullTestCasesCredentials { - dropbox_access_token: credentials::dropbox_access_token()?, - }, - }) - } else { - None - }; - - let cookie_storage = CookieStorage::with_jsonl(credentials::cookies_path()?)?; - - Atcoder::exec(RetrieveTestCases { - targets, - credentials, - full, - cookie_storage, - timeout: crate::web::TIMEOUT, - shell: &shell, - }) -} - -fn dl_from_codeforces( - contest: &str, - problems: Option>, - shell: &mut Shell, -) -> anyhow::Result { - let targets = CodeforcesRetrieveTestCasesTargets { - contest: contest.to_owned(), - problems, - }; - - let shell = RefCell::new(shell.borrow_mut()); - - let credentials = CodeforcesRetrieveSampleTestCasesCredentials { - username_and_password: &mut credentials::username_and_password( - &shell, - "Username: ", - "Password: ", - ), - }; - - let cookie_storage = CookieStorage::with_jsonl(credentials::cookies_path()?)?; - - Codeforces::exec(RetrieveTestCases { - targets, - credentials, - full: None, - cookie_storage, - timeout: crate::web::TIMEOUT, - shell: &shell, - }) -} - -fn dl_from_yukicoder( - contest: Option<&str>, - problems: Option>, - full: bool, - shell: &mut Shell, -) -> anyhow::Result { - let targets = if let Some(contest) = contest { - YukicoderRetrieveTestCasesTargets::Contest(contest.to_owned(), problems) - } else { - YukicoderRetrieveTestCasesTargets::ProblemNos(problems.unwrap_or_default()) - }; - - let full = if full { - Some(RetrieveFullTestCases { - credentials: YukicoderRetrieveFullTestCasesCredentials { - api_key: credentials::yukicoder_api_key(shell)?, - }, - }) - } else { - None - }; - - let shell = RefCell::new(shell.borrow_mut()); - - Yukicoder::exec(RetrieveTestCases { - targets, - credentials: (), - full, - cookie_storage: (), - timeout: crate::web::TIMEOUT, - shell: &shell, - }) -} - fn src_paths(pkg_manifest_dir: &Path, outcome: &RetrieveTestCasesOutcome) -> Vec { outcome .problems @@ -455,137 +249,3 @@ fn src_paths(pkg_manifest_dir: &Path, outcome: &RetrieveTestCasesOutcome) -> Vec fn urls(outcome: &RetrieveTestCasesOutcome) -> Vec { outcome.problems.iter().map(|p| p.url.clone()).collect() } - -fn open_urls_and_files( - urls: &[Url], - open: Option, - src_paths: &[PathBuf], - test_suite_paths: &[PathBuf], - pkg_manifest_dir: &Path, - cwd: &Path, - shell: &mut Shell, -) -> anyhow::Result<()> { - for url in urls { - shell.status("Opening", url)?; - opener::open(url.as_str())?; - } - - if let Some(open) = open { - let mut cmd = match open { - Open::Vscode => crate::process::with_which("code", cwd)?, - Open::Emacsclient => { - let mut cmd = crate::process::with_which("emacsclient", cwd)?; - cmd.arg("-n"); - cmd - } - }; - - for path in itertools::interleave(src_paths, test_suite_paths) { - cmd.arg(path); - } - - if open == Open::Vscode { - cmd.arg("-a"); - cmd.arg(pkg_manifest_dir); - } - - cmd.exec_with_shell_status(shell)?; - } - Ok(()) -} - -fn save_test_cases( - workspace_root: &Path, - path: &TemplateString, - outcome: RetrieveTestCasesOutcome, - shell: &mut Shell, -) -> anyhow::Result> { - let mut acc = vec![]; - - let contest = outcome - .contest - .as_ref() - .map(|RetrieveTestCasesOutcomeContest { id, .. }| &**id) - .unwrap_or("problems"); - - for snowchains_core::web::RetrieveTestCasesOutcomeProblem { - index, - mut test_suite, - text_files, - .. - } in outcome.problems - { - let path = path.eval(&btreemap!("contest" => contest, "problem" => &index))?; - let path = Path::new(&path); - let path = workspace_root.join(path.strip_prefix(".").unwrap_or(&path)); - - acc.push(path.clone()); - - let txt_path = |dir_file_name: &str, txt_file_name: &str| -> _ { - path.with_file_name(index.to_kebab_case()) - .join(dir_file_name) - .join(txt_file_name) - .with_extension("txt") - }; - - for (name, RetrieveTestCasesOutcomeProblemTextFiles { r#in, out }) in &text_files { - let in_path = txt_path("in", name); - crate::fs::create_dir_all(in_path.parent().unwrap())?; - crate::fs::write(in_path, &r#in)?; - if let Some(out) = out { - let out_path = txt_path("out", name); - crate::fs::create_dir_all(out_path.parent().unwrap())?; - crate::fs::write(out_path, &r#out)?; - } - } - - if !text_files.is_empty() { - if let TestSuite::Batch(BatchTestSuite { cases, extend, .. }) = &mut test_suite { - cases.clear(); - - extend.push(Additional::Text { - path: format!("./{}", index.to_kebab_case()), - r#in: "/in/*.txt".to_owned(), - out: "/out/*.txt".to_owned(), - timelimit: None, - r#match: None, - }) - } - } - - crate::fs::create_dir_all(path.parent().unwrap())?; - crate::fs::write(&path, test_suite.to_yaml_pretty())?; - - shell.status( - "Saved", - format!( - "{} to {}", - match &test_suite { - TestSuite::Batch(BatchTestSuite { cases, .. }) => { - match cases.len() + text_files.len() { - 0 => "no test cases".to_owned(), - 1 => "1 test case".to_owned(), - n => format!("{} test cases", n), - } - } - TestSuite::Interactive(_) => "no test cases (interactive problem)".to_owned(), - TestSuite::Unsubmittable => "no test cases (unsubmittable problem)".to_owned(), - }, - if text_files.is_empty() { - format!("{}", path.display()) - } else { - format!( - "{}", - path.with_file_name(format!( - "{{{index}.yml, {index}/}}", - index = index.to_kebab_case(), - )) - .display(), - ) - }, - ), - )?; - } - - Ok(acc) -} diff --git a/src/lib.rs b/src/lib.rs index d247ad7..61255bb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ mod commands; mod fs; +mod open; mod process; mod project; pub mod shell; @@ -8,9 +9,10 @@ mod web; use crate::{ commands::{ - init::OptCompeteInit, login::OptCompeteLogin, participate::OptCompeteParticipate, - retrieve_testcases::OptCompeteRetrieveTestcases, submit::OptCompeteSubmit, - test::OptCompeteTest, watch_submissions::OptCompeteWatchSubmissions, + init::OptCompeteInit, login::OptCompeteLogin, open::OptCompeteOpen, + participate::OptCompeteParticipate, retrieve_testcases::OptCompeteRetrieveTestcases, + submit::OptCompeteSubmit, test::OptCompeteTest, + watch_submissions::OptCompeteWatchSubmissions, }, shell::Shell, }; @@ -70,6 +72,10 @@ pub enum OptCompete { #[structopt(author, visible_alias("w"))] Watch(OptCompeteWatch), + /// Open URLs and files + #[structopt(author, visible_alias("o"))] + Open(OptCompeteOpen), + /// Test your code #[structopt(author, visible_alias("t"))] Test(OptCompeteTest), @@ -109,6 +115,7 @@ pub fn run(opt: OptCompete, ctx: Context<'_>) -> anyhow::Result<()> { OptCompete::Watch(OptCompeteWatch::Submissions(opt)) => { commands::watch_submissions::run(opt, ctx) } + OptCompete::Open(opt) => commands::open::run(opt, ctx), OptCompete::Test(opt) => commands::test::run(opt, ctx), OptCompete::Submit(opt) => commands::submit::run(opt, ctx), } diff --git a/src/open.rs b/src/open.rs new file mode 100644 index 0000000..ee0a86b --- /dev/null +++ b/src/open.rs @@ -0,0 +1,42 @@ +use crate::{project::Open, shell::Shell}; +use std::{borrow::Borrow, path::Path}; +use url::Url; + +pub(crate) fn open( + urls: &[impl Borrow], + open: Option, + paths: &[(impl AsRef, impl AsRef)], + pkg_manifest_dir: &Path, + cwd: &Path, + shell: &mut Shell, +) -> anyhow::Result<()> { + for url in urls { + let url = url.borrow(); + shell.status("Opening", url)?; + opener::open(url.as_str())?; + } + + if let Some(open) = open { + let mut cmd = match open { + Open::Vscode => crate::process::with_which("code", cwd)?, + Open::Emacsclient => { + let mut cmd = crate::process::with_which("emacsclient", cwd)?; + cmd.arg("-n"); + cmd + } + }; + + for (src_path, test_suite_path) in paths { + cmd.arg(src_path.as_ref()); + cmd.arg(test_suite_path.as_ref()); + } + + if open == Open::Vscode { + cmd.arg("-a"); + cmd.arg(pkg_manifest_dir); + } + + cmd.exec_with_shell_status(shell)?; + } + Ok(()) +} diff --git a/src/project.rs b/src/project.rs index f990009..b75b6c5 100644 --- a/src/project.rs +++ b/src/project.rs @@ -41,28 +41,37 @@ pub(crate) enum Open { pub(crate) struct TemplateString(Vec); impl TemplateString { - pub(crate) fn eval(&self, vars: &BTreeMap<&'static str, &str>) -> anyhow::Result { + pub(crate) fn eval( + &self, + vars: &BTreeMap<&'static str, impl AsRef>, + ) -> anyhow::Result { let mut acc = "".to_owned(); for token in &self.0 { match token { TemplateWord::Plain(s) => acc += s, TemplateWord::Var(name) => { - acc += vars.get(&**name).with_context(|| { - format!( - "unrecognized variable {:?} (expected {:?})", - name, - vars.keys().collect::>(), - ) - })?; + acc += vars + .get(&**name) + .with_context(|| { + format!( + "unrecognized variable {:?} (expected {:?})", + name, + vars.keys().collect::>(), + ) + })? + .as_ref(); } TemplateWord::App(f, name) => { - let arg = vars.get(&**name).with_context(|| { - format!( - "unrecognized variable {:?} (expected one of {:?})", - name, - vars.keys().collect::>(), - ) - })?; + let arg = vars + .get(&**name) + .with_context(|| { + format!( + "unrecognized variable {:?} (expected one of {:?})", + name, + vars.keys().collect::>(), + ) + })? + .as_ref(); acc += &*match &**f { "kebab-case" => arg.to_kebab_case(), _ => bail!(r#"expected one of ["kebab-case"]"#), @@ -205,10 +214,12 @@ impl Metadata { Ok(cargo_compete) } - pub(crate) fn query_for_member<'a>( + pub(crate) fn query_for_member<'a, S: AsRef>( &'a self, - spec: Option<&str>, + spec: Option, ) -> anyhow::Result<&'a Package> { + let spec = spec.as_ref().map(AsRef::as_ref); + let cargo_exe = env::var_os("CARGO").with_context(|| "`$CARGO` should be present")?; let manifest_path = self diff --git a/src/testing.rs b/src/testing.rs index bfa6cb5..e5cdd2a 100644 --- a/src/testing.rs +++ b/src/testing.rs @@ -11,7 +11,7 @@ use cargo_metadata::{Metadata, Package}; use human_size::{Byte, Size}; use maplit::btreemap; use snowchains_core::{judge::CommandExpression, testsuite::TestSuite}; -use std::path::Path; +use std::path::{Path, PathBuf}; pub(crate) struct Args<'a> { pub(crate) metadata: &'a Metadata, @@ -36,31 +36,11 @@ pub(crate) fn test(args: Args<'_>) -> anyhow::Result<()> { let bin = member.bin_target(&package_metadata_bin.name)?; - let test_suite_path = match &package_metadata_bin.problem { - TargetProblem::Atcoder { contest, index, .. } - | TargetProblem::Codeforces { contest, index, .. } - | TargetProblem::Yukicoder(TargetProblemYukicoder::Contest { contest, index, .. }) => { - let test_suite_path = workspace_metadata - .test_suite - .eval(&btreemap!("contest" => &**contest, "problem" => &index))?; - let test_suite_path = Path::new(&test_suite_path); - let test_suite_path = test_suite_path - .strip_prefix(".") - .unwrap_or(&test_suite_path); - metadata.workspace_root.join(test_suite_path) - } - TargetProblem::Yukicoder(TargetProblemYukicoder::Problem { no, .. }) => { - let no = no.to_string(); - let test_suite_path = workspace_metadata - .test_suite - .eval(&btreemap!("contest" => "problems", "problem" => &no))?; - let test_suite_path = Path::new(&test_suite_path); - let test_suite_path = test_suite_path - .strip_prefix(".") - .unwrap_or(&test_suite_path); - metadata.workspace_root.join(test_suite_path) - } - }; + let test_suite_path = test_suite_path( + &metadata.workspace_root, + &workspace_metadata.test_suite, + &package_metadata_bin.problem, + )?; let test_suite = crate::fs::read_yaml(&test_suite_path)?; @@ -138,3 +118,27 @@ pub(crate) fn test(args: Args<'_>) -> anyhow::Result<()> { outcome.print_pretty(shell.err(), Some(display_limit))?; outcome.error_on_fail() } + +pub(crate) fn test_suite_path( + workspace_root: &Path, + workspace_metadata_test_suite: &crate::project::TemplateString, + target_problem: &TargetProblem, +) -> anyhow::Result { + let vars = match target_problem { + TargetProblem::Atcoder { contest, index, .. } + | TargetProblem::Codeforces { contest, index, .. } + | TargetProblem::Yukicoder(TargetProblemYukicoder::Contest { contest, index, .. }) => { + btreemap!("contest" => contest.clone(), "problem" => index.clone()) + } + TargetProblem::Yukicoder(TargetProblemYukicoder::Problem { no, .. }) => { + btreemap!("contest" => "problems".to_owned(), "problem" => no.to_string()) + } + }; + + let test_suite_path = workspace_metadata_test_suite.eval(&vars)?; + let test_suite_path = Path::new(&test_suite_path); + let test_suite_path = test_suite_path + .strip_prefix(".") + .unwrap_or(&test_suite_path); + Ok(workspace_root.join(test_suite_path)) +} diff --git a/src/web/mod.rs b/src/web/mod.rs index 1593f33..b55a44b 100644 --- a/src/web/mod.rs +++ b/src/web/mod.rs @@ -1,4 +1,5 @@ pub(crate) mod credentials; +pub(crate) mod retrieve_testcases; use std::time::Duration; diff --git a/src/web/retrieve_testcases.rs b/src/web/retrieve_testcases.rs new file mode 100644 index 0000000..d0f1ff5 --- /dev/null +++ b/src/web/retrieve_testcases.rs @@ -0,0 +1,311 @@ +use crate::{ + project::{PackageMetadataCargoCompeteBin, TargetProblem, TargetProblemYukicoder}, + shell::Shell, + web::credentials, +}; +use heck::KebabCase as _; +use indexmap::IndexMap; +use maplit::{btreemap, btreeset}; +use snowchains_core::{ + testsuite::{Additional, BatchTestSuite, TestSuite}, + web::{ + Atcoder, AtcoderRetrieveFullTestCasesCredentials, + AtcoderRetrieveSampleTestCasesCredentials, AtcoderRetrieveTestCasesTargets, Codeforces, + CodeforcesRetrieveSampleTestCasesCredentials, CodeforcesRetrieveTestCasesTargets, + CookieStorage, RetrieveFullTestCases, RetrieveTestCases, RetrieveTestCasesOutcome, + RetrieveTestCasesOutcomeContest, RetrieveTestCasesOutcomeProblemTextFiles, Yukicoder, + YukicoderRetrieveFullTestCasesCredentials, YukicoderRetrieveTestCasesTargets, + }, +}; +use std::{ + borrow::BorrowMut as _, + cell::RefCell, + collections::{BTreeSet, HashSet}, + path::{Path, PathBuf}, +}; + +pub(crate) fn dl_missing( + package_metadata_bin: &IndexMap, + indexes: Option<&HashSet>, + full: bool, + workspace_root: &Path, + test_suite_path: &crate::project::TemplateString, + shell: &mut Shell, +) -> anyhow::Result> { + let mut atcoder_targets = btreemap!(); + let mut codeforces_targets = btreemap!(); + let mut yukicoder_problem_targets = btreeset!(); + let mut yukicoder_contest_targets = btreemap!(); + + for (index, PackageMetadataCargoCompeteBin { problem, .. }) in package_metadata_bin { + if indexes.map_or(true, |indexes| indexes.contains(index)) { + match problem { + TargetProblem::Atcoder { contest, index, .. } => atcoder_targets + .entry(contest.clone()) + .or_insert_with(BTreeSet::new) + .insert(index.clone()), + TargetProblem::Codeforces { contest, index, .. } => codeforces_targets + .entry(contest.clone()) + .or_insert_with(BTreeSet::new) + .insert(index.clone()), + TargetProblem::Yukicoder(target) => match target { + TargetProblemYukicoder::Problem { no, .. } => { + yukicoder_problem_targets.insert(*no) + } + TargetProblemYukicoder::Contest { contest, index, .. } => { + yukicoder_contest_targets + .entry(contest.clone()) + .or_insert_with(BTreeSet::new) + .insert(index.clone()) + } + }, + }; + } + } + + let mut outcomes = vec![]; + + for (contest, problems) in atcoder_targets { + outcomes.push(dl_from_atcoder(&contest, Some(problems), full, shell)?); + } + for (contest, problems) in codeforces_targets { + outcomes.push(dl_from_codeforces(&contest, Some(problems), shell)?); + } + if !yukicoder_problem_targets.is_empty() { + outcomes.push(dl_from_yukicoder( + None, + Some( + yukicoder_problem_targets + .iter() + .map(ToString::to_string) + .collect(), + ), + full, + shell, + )?); + } + for (contest, problems) in yukicoder_contest_targets { + outcomes.push(dl_from_yukicoder( + Some(&contest), + Some(problems), + full, + shell, + )?); + } + + let mut test_suite_paths = vec![]; + for outcome in outcomes { + test_suite_paths.extend(save_test_cases( + workspace_root, + test_suite_path, + outcome, + shell, + )?); + } + Ok(test_suite_paths) +} + +pub(crate) fn dl_from_atcoder( + contest: &str, + problems: Option>, + full: bool, + shell: &mut Shell, +) -> anyhow::Result { + let targets = AtcoderRetrieveTestCasesTargets { + contest: contest.to_owned(), + problems, + }; + + let shell = RefCell::new(shell.borrow_mut()); + + let credentials = AtcoderRetrieveSampleTestCasesCredentials { + username_and_password: &mut credentials::username_and_password( + &shell, + "Username: ", + "Password: ", + ), + }; + + let full = if full { + Some(RetrieveFullTestCases { + credentials: AtcoderRetrieveFullTestCasesCredentials { + dropbox_access_token: credentials::dropbox_access_token()?, + }, + }) + } else { + None + }; + + let cookie_storage = CookieStorage::with_jsonl(credentials::cookies_path()?)?; + + Atcoder::exec(RetrieveTestCases { + targets, + credentials, + full, + cookie_storage, + timeout: crate::web::TIMEOUT, + shell: &shell, + }) +} + +pub(crate) fn dl_from_codeforces( + contest: &str, + problems: Option>, + shell: &mut Shell, +) -> anyhow::Result { + let targets = CodeforcesRetrieveTestCasesTargets { + contest: contest.to_owned(), + problems, + }; + + let shell = RefCell::new(shell.borrow_mut()); + + let credentials = CodeforcesRetrieveSampleTestCasesCredentials { + username_and_password: &mut credentials::username_and_password( + &shell, + "Username: ", + "Password: ", + ), + }; + + let cookie_storage = CookieStorage::with_jsonl(credentials::cookies_path()?)?; + + Codeforces::exec(RetrieveTestCases { + targets, + credentials, + full: None, + cookie_storage, + timeout: crate::web::TIMEOUT, + shell: &shell, + }) +} + +pub(crate) fn dl_from_yukicoder( + contest: Option<&str>, + problems: Option>, + full: bool, + shell: &mut Shell, +) -> anyhow::Result { + let targets = if let Some(contest) = contest { + YukicoderRetrieveTestCasesTargets::Contest(contest.to_owned(), problems) + } else { + YukicoderRetrieveTestCasesTargets::ProblemNos(problems.unwrap_or_default()) + }; + + let full = if full { + Some(RetrieveFullTestCases { + credentials: YukicoderRetrieveFullTestCasesCredentials { + api_key: credentials::yukicoder_api_key(shell)?, + }, + }) + } else { + None + }; + + let shell = RefCell::new(shell.borrow_mut()); + + Yukicoder::exec(RetrieveTestCases { + targets, + credentials: (), + full, + cookie_storage: (), + timeout: crate::web::TIMEOUT, + shell: &shell, + }) +} + +pub(crate) fn save_test_cases( + workspace_root: &Path, + path: &crate::project::TemplateString, + outcome: RetrieveTestCasesOutcome, + shell: &mut Shell, +) -> anyhow::Result> { + let mut acc = vec![]; + + let contest = outcome + .contest + .as_ref() + .map(|RetrieveTestCasesOutcomeContest { id, .. }| &**id) + .unwrap_or("problems"); + + for snowchains_core::web::RetrieveTestCasesOutcomeProblem { + index, + mut test_suite, + text_files, + .. + } in outcome.problems + { + let path = path.eval(&btreemap!("contest" => contest, "problem" => &index))?; + let path = Path::new(&path); + let path = workspace_root.join(path.strip_prefix(".").unwrap_or(&path)); + + acc.push(path.clone()); + + let txt_path = |dir_file_name: &str, txt_file_name: &str| -> _ { + path.with_file_name(index.to_kebab_case()) + .join(dir_file_name) + .join(txt_file_name) + .with_extension("txt") + }; + + for (name, RetrieveTestCasesOutcomeProblemTextFiles { r#in, out }) in &text_files { + let in_path = txt_path("in", name); + crate::fs::create_dir_all(in_path.parent().unwrap())?; + crate::fs::write(in_path, &r#in)?; + if let Some(out) = out { + let out_path = txt_path("out", name); + crate::fs::create_dir_all(out_path.parent().unwrap())?; + crate::fs::write(out_path, &r#out)?; + } + } + + if !text_files.is_empty() { + if let TestSuite::Batch(BatchTestSuite { cases, extend, .. }) = &mut test_suite { + cases.clear(); + + extend.push(Additional::Text { + path: format!("./{}", index.to_kebab_case()), + r#in: "/in/*.txt".to_owned(), + out: "/out/*.txt".to_owned(), + timelimit: None, + r#match: None, + }) + } + } + + crate::fs::create_dir_all(path.parent().unwrap())?; + crate::fs::write(&path, test_suite.to_yaml_pretty())?; + + shell.status( + "Saved", + format!( + "{} to {}", + match &test_suite { + TestSuite::Batch(BatchTestSuite { cases, .. }) => { + match cases.len() + text_files.len() { + 0 => "no test cases".to_owned(), + 1 => "1 test case".to_owned(), + n => format!("{} test cases", n), + } + } + TestSuite::Interactive(_) => "no test cases (interactive problem)".to_owned(), + TestSuite::Unsubmittable => "no test cases (unsubmittable problem)".to_owned(), + }, + if text_files.is_empty() { + format!("{}", path.display()) + } else { + format!( + "{}", + path.with_file_name(format!( + "{{{index}.yml, {index}/}}", + index = index.to_kebab_case(), + )) + .display(), + ) + }, + ), + )?; + } + + Ok(acc) +} From e174078f337103ee811a78c1031a11504e2bf642 Mon Sep 17 00:00:00 2001 From: Ryo Yamashita Date: Mon, 3 Aug 2020 20:39:08 +0900 Subject: [PATCH 3/7] Split the `r t` command into `new` and `r t` --- src/commands/mod.rs | 1 + src/commands/new.rs | 230 +++++++++++++++++++++++++++++ src/commands/retrieve_testcases.rs | 194 +----------------------- src/lib.rs | 7 +- 4 files changed, 241 insertions(+), 191 deletions(-) create mode 100644 src/commands/new.rs diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 888cac3..6d84798 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,5 +1,6 @@ pub(crate) mod init; pub(crate) mod login; +pub(crate) mod new; pub(crate) mod open; pub(crate) mod participate; pub(crate) mod retrieve_testcases; diff --git a/src/commands/new.rs b/src/commands/new.rs new file mode 100644 index 0000000..a11a07d --- /dev/null +++ b/src/commands/new.rs @@ -0,0 +1,230 @@ +use crate::{ + project::{MetadataExt as _, WorkspaceMetadataCargoCompetePlatform}, + shell::ColorChoice, +}; +use anyhow::Context as _; +use heck::KebabCase as _; +use snowchains_core::web::{ + RetrieveTestCasesOutcome, RetrieveTestCasesOutcomeContest, RetrieveTestCasesOutcomeProblem, +}; +use std::path::{Path, PathBuf}; +use structopt::StructOpt; +use strum::VariantNames as _; +use url::Url; + +#[derive(StructOpt, Debug)] +pub struct OptCompeteNew { + /// Retrieve system test cases + #[structopt(long)] + pub full: bool, + + /// Open URLs and files + #[structopt(long)] + pub open: bool, + + /// Retrieve only the problems + #[structopt(long, value_name("INDEX"))] + pub problems: Option>, + + /// Path to Cargo.toml + #[structopt(long, value_name("PATH"))] + pub manifest_path: Option, + + /// Coloring + #[structopt( + long, + value_name("WHEN"), + possible_values(ColorChoice::VARIANTS), + default_value("auto") + )] + pub color: ColorChoice, + + /// Contest ID. Required for some platforms + pub contest: Option, +} + +pub fn run(opt: OptCompeteNew, ctx: crate::Context<'_>) -> anyhow::Result<()> { + let OptCompeteNew { + full, + open, + problems, + manifest_path, + color, + contest, + } = opt; + + let crate::Context { cwd, shell } = ctx; + + shell.set_color_choice(color); + + let manifest_path = manifest_path + .map(Ok) + .unwrap_or_else(|| crate::project::locate_project(&cwd))?; + let metadata = crate::project::cargo_metadata(&manifest_path)?; + let workspace_metadata = metadata.read_workspace_metadata()?; + + match workspace_metadata.platform { + WorkspaceMetadataCargoCompetePlatform::Atcoder { .. } => { + let contest = contest.with_context(|| "`contest` is required for AtCoder")?; + let problems = problems.map(|ps| ps.into_iter().collect()); + + let outcome = + crate::web::retrieve_testcases::dl_from_atcoder(&contest, problems, full, shell)?; + + let package_name = outcome + .contest + .as_ref() + .map(|RetrieveTestCasesOutcomeContest { id, .. }| id) + .unwrap_or(&contest); + + let problems = outcome + .problems + .iter() + .map(|RetrieveTestCasesOutcomeProblem { index, url, .. }| (&**index, url)) + .collect(); + + let workspace_root = metadata.workspace_root.clone(); + let pkg_manifest_dir = metadata.workspace_root.join(package_name); + let urls = urls(&outcome); + + metadata.add_member(package_name, &problems, false, shell)?; + + let file_paths = itertools::zip_eq( + src_paths(&pkg_manifest_dir, &outcome), + crate::web::retrieve_testcases::save_test_cases( + &workspace_root, + &workspace_metadata.test_suite, + outcome, + shell, + )?, + ) + .collect::>(); + + if open { + crate::open::open( + &urls, + workspace_metadata.open, + &file_paths, + &pkg_manifest_dir, + &cwd, + shell, + )?; + } + } + WorkspaceMetadataCargoCompetePlatform::Codeforces => { + let contest = contest.with_context(|| "`contest` is required for Codeforces")?; + let problems = problems.map(|ps| ps.into_iter().collect()); + + let outcome = + crate::web::retrieve_testcases::dl_from_codeforces(&contest, problems, shell)?; + + let package_name = outcome + .contest + .as_ref() + .map(|RetrieveTestCasesOutcomeContest { id, .. }| id) + .unwrap_or(&contest); + + let problems = outcome + .problems + .iter() + .map(|RetrieveTestCasesOutcomeProblem { index, url, .. }| (&**index, url)) + .collect(); + + let workspace_root = metadata.workspace_root.clone(); + let pkg_manifest_dir = metadata.workspace_root.join(package_name); + let urls = urls(&outcome); + + metadata.add_member(package_name, &problems, false, shell)?; + + let file_paths = itertools::zip_eq( + src_paths(&pkg_manifest_dir, &outcome), + crate::web::retrieve_testcases::save_test_cases( + &workspace_root, + &workspace_metadata.test_suite, + outcome, + shell, + )?, + ) + .collect::>(); + + if open { + crate::open::open( + &urls, + workspace_metadata.open, + &file_paths, + &pkg_manifest_dir, + &cwd, + shell, + )?; + } + } + WorkspaceMetadataCargoCompetePlatform::Yukicoder => { + let contest = contest.as_deref(); + let problems = problems.map(|ps| ps.into_iter().collect()); + + let outcome = + crate::web::retrieve_testcases::dl_from_yukicoder(contest, problems, full, shell)?; + + let package_name = outcome + .contest + .as_ref() + .map(|RetrieveTestCasesOutcomeContest { id, .. }| &**id) + .or(contest); + let is_no = package_name.is_none(); + let package_name = package_name.unwrap_or("problems"); + + let problems = outcome + .problems + .iter() + .map(|RetrieveTestCasesOutcomeProblem { index, url, .. }| (&**index, url)) + .collect(); + + let workspace_root = metadata.workspace_root.clone(); + let pkg_manifest_dir = metadata.workspace_root.join(package_name); + let urls = urls(&outcome); + + metadata.add_member(package_name, &problems, is_no, shell)?; + + let file_paths = itertools::zip_eq( + src_paths(&pkg_manifest_dir, &outcome), + crate::web::retrieve_testcases::save_test_cases( + &workspace_root, + &workspace_metadata.test_suite, + outcome, + shell, + )?, + ) + .collect::>(); + + if open { + crate::open::open( + &urls, + workspace_metadata.open, + &file_paths, + &pkg_manifest_dir, + &cwd, + shell, + )?; + } + } + } + Ok(()) +} + +fn src_paths(pkg_manifest_dir: &Path, outcome: &RetrieveTestCasesOutcome) -> Vec { + outcome + .problems + .iter() + .map(|problem| { + pkg_manifest_dir + .join("src") + .join("bin") + .join(problem.index.to_kebab_case()) + .with_extension("rs") + }) + .collect() +} + +fn urls(outcome: &RetrieveTestCasesOutcome) -> Vec { + outcome.problems.iter().map(|p| p.url.clone()).collect() +} diff --git a/src/commands/retrieve_testcases.rs b/src/commands/retrieve_testcases.rs index c797625..690ea02 100644 --- a/src/commands/retrieve_testcases.rs +++ b/src/commands/retrieve_testcases.rs @@ -1,16 +1,7 @@ -use crate::{ - project::{MetadataExt as _, WorkspaceMetadataCargoCompetePlatform}, - shell::ColorChoice, -}; -use anyhow::Context as _; -use heck::KebabCase as _; -use snowchains_core::web::{ - RetrieveTestCasesOutcome, RetrieveTestCasesOutcomeContest, RetrieveTestCasesOutcomeProblem, -}; -use std::path::{Path, PathBuf}; +use crate::{project::MetadataExt as _, shell::ColorChoice}; +use std::path::PathBuf; use structopt::StructOpt; use strum::VariantNames as _; -use url::Url; #[derive(StructOpt, Debug)] pub struct OptCompeteRetrieveTestcases { @@ -68,184 +59,7 @@ pub(crate) fn run(opt: OptCompeteRetrieveTestcases, ctx: crate::Context<'_>) -> let metadata = crate::project::cargo_metadata(&manifest_path)?; let workspace_metadata = metadata.read_workspace_metadata()?; - let member = if let Some(package) = package { - Some(metadata.query_for_member(Some(&package))?) - } else { - metadata - .workspace_members - .iter() - .map(|id| &metadata[id]) - .find(|p| p.manifest_path == manifest_path) - }; + let member = metadata.query_for_member(package)?; - if let Some(member) = member { - todo!(); - } else { - match workspace_metadata.platform { - WorkspaceMetadataCargoCompetePlatform::Atcoder { .. } => { - let contest = contest.with_context(|| "`contest` is required for AtCoder")?; - let problems = problems.map(|ps| ps.into_iter().collect()); - - let outcome = crate::web::retrieve_testcases::dl_from_atcoder( - &contest, problems, full, shell, - )?; - - let package_name = outcome - .contest - .as_ref() - .map(|RetrieveTestCasesOutcomeContest { id, .. }| id) - .unwrap_or(&contest); - - let problems = outcome - .problems - .iter() - .map(|RetrieveTestCasesOutcomeProblem { index, url, .. }| (&**index, url)) - .collect(); - - let workspace_root = metadata.workspace_root.clone(); - let pkg_manifest_dir = metadata.workspace_root.join(package_name); - let urls = urls(&outcome); - - metadata.add_member(package_name, &problems, false, shell)?; - - let file_paths = itertools::zip_eq( - src_paths(&pkg_manifest_dir, &outcome), - crate::web::retrieve_testcases::save_test_cases( - &workspace_root, - &workspace_metadata.test_suite, - outcome, - shell, - )?, - ) - .collect::>(); - - if open { - crate::open::open( - &urls, - workspace_metadata.open, - &file_paths, - &pkg_manifest_dir, - &cwd, - shell, - )?; - } - } - WorkspaceMetadataCargoCompetePlatform::Codeforces => { - let contest = contest.with_context(|| "`contest` is required for Codeforces")?; - let problems = problems.map(|ps| ps.into_iter().collect()); - - let outcome = - crate::web::retrieve_testcases::dl_from_codeforces(&contest, problems, shell)?; - - let package_name = outcome - .contest - .as_ref() - .map(|RetrieveTestCasesOutcomeContest { id, .. }| id) - .unwrap_or(&contest); - - let problems = outcome - .problems - .iter() - .map(|RetrieveTestCasesOutcomeProblem { index, url, .. }| (&**index, url)) - .collect(); - - let workspace_root = metadata.workspace_root.clone(); - let pkg_manifest_dir = metadata.workspace_root.join(package_name); - let urls = urls(&outcome); - - metadata.add_member(package_name, &problems, false, shell)?; - - let file_paths = itertools::zip_eq( - src_paths(&pkg_manifest_dir, &outcome), - crate::web::retrieve_testcases::save_test_cases( - &workspace_root, - &workspace_metadata.test_suite, - outcome, - shell, - )?, - ) - .collect::>(); - - if open { - crate::open::open( - &urls, - workspace_metadata.open, - &file_paths, - &pkg_manifest_dir, - &cwd, - shell, - )?; - } - } - WorkspaceMetadataCargoCompetePlatform::Yukicoder => { - let contest = contest.as_deref(); - let problems = problems.map(|ps| ps.into_iter().collect()); - - let outcome = crate::web::retrieve_testcases::dl_from_yukicoder( - contest, problems, full, shell, - )?; - - let package_name = outcome - .contest - .as_ref() - .map(|RetrieveTestCasesOutcomeContest { id, .. }| &**id) - .or(contest); - let is_no = package_name.is_none(); - let package_name = package_name.unwrap_or("problems"); - - let problems = outcome - .problems - .iter() - .map(|RetrieveTestCasesOutcomeProblem { index, url, .. }| (&**index, url)) - .collect(); - - let workspace_root = metadata.workspace_root.clone(); - let pkg_manifest_dir = metadata.workspace_root.join(package_name); - let urls = urls(&outcome); - - metadata.add_member(package_name, &problems, is_no, shell)?; - - let file_paths = itertools::zip_eq( - src_paths(&pkg_manifest_dir, &outcome), - crate::web::retrieve_testcases::save_test_cases( - &workspace_root, - &workspace_metadata.test_suite, - outcome, - shell, - )?, - ) - .collect::>(); - - if open { - crate::open::open( - &urls, - workspace_metadata.open, - &file_paths, - &pkg_manifest_dir, - &cwd, - shell, - )?; - } - } - } - } - Ok(()) -} - -fn src_paths(pkg_manifest_dir: &Path, outcome: &RetrieveTestCasesOutcome) -> Vec { - outcome - .problems - .iter() - .map(|problem| { - pkg_manifest_dir - .join("src") - .join("bin") - .join(problem.index.to_kebab_case()) - .with_extension("rs") - }) - .collect() -} - -fn urls(outcome: &RetrieveTestCasesOutcome) -> Vec { - outcome.problems.iter().map(|p| p.url.clone()).collect() + todo!(); } diff --git a/src/lib.rs b/src/lib.rs index 61255bb..8ec3f3e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,7 +9,7 @@ mod web; use crate::{ commands::{ - init::OptCompeteInit, login::OptCompeteLogin, open::OptCompeteOpen, + init::OptCompeteInit, login::OptCompeteLogin, new::OptCompeteNew, open::OptCompeteOpen, participate::OptCompeteParticipate, retrieve_testcases::OptCompeteRetrieveTestcases, submit::OptCompeteSubmit, test::OptCompeteTest, watch_submissions::OptCompeteWatchSubmissions, @@ -60,6 +60,10 @@ pub enum OptCompete { #[structopt(author, visible_alias("p"))] Participate(OptCompeteParticipate), + /// Retrieve test cases and create a package + #[structopt(author, visible_alias("n"))] + New(OptCompeteNew), + /// Retrieve data #[structopt(author, visible_alias("r"))] Retrieve(OptCompeteRetrieve), @@ -109,6 +113,7 @@ pub fn run(opt: OptCompete, ctx: Context<'_>) -> anyhow::Result<()> { OptCompete::Init(opt) => commands::init::run(opt, ctx), OptCompete::Login(opt) => commands::login::run(opt, ctx), OptCompete::Participate(opt) => commands::participate::run(opt, ctx), + OptCompete::New(opt) => commands::new::run(opt, ctx), OptCompete::Retrieve(OptCompeteRetrieve::Testcases(opt)) | OptCompete::Download(opt) => { commands::retrieve_testcases::run(opt, ctx) } From 71904ee4477ac304834656a938b48050e397bcd2 Mon Sep 17 00:00:00 2001 From: Ryo Yamashita Date: Mon, 3 Aug 2020 20:50:43 +0900 Subject: [PATCH 4/7] Use the `src_path`s from `add_member` --- src/commands/new.rs | 29 ++++------------------------- src/project.rs | 22 +++++++++++++++------- 2 files changed, 19 insertions(+), 32 deletions(-) diff --git a/src/commands/new.rs b/src/commands/new.rs index a11a07d..b83bd43 100644 --- a/src/commands/new.rs +++ b/src/commands/new.rs @@ -3,11 +3,10 @@ use crate::{ shell::ColorChoice, }; use anyhow::Context as _; -use heck::KebabCase as _; use snowchains_core::web::{ RetrieveTestCasesOutcome, RetrieveTestCasesOutcomeContest, RetrieveTestCasesOutcomeProblem, }; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; use structopt::StructOpt; use strum::VariantNames as _; use url::Url; @@ -87,10 +86,8 @@ pub fn run(opt: OptCompeteNew, ctx: crate::Context<'_>) -> anyhow::Result<()> { let pkg_manifest_dir = metadata.workspace_root.join(package_name); let urls = urls(&outcome); - metadata.add_member(package_name, &problems, false, shell)?; - let file_paths = itertools::zip_eq( - src_paths(&pkg_manifest_dir, &outcome), + metadata.add_member(package_name, &problems, false, shell)?, crate::web::retrieve_testcases::save_test_cases( &workspace_root, &workspace_metadata.test_suite, @@ -134,10 +131,8 @@ pub fn run(opt: OptCompeteNew, ctx: crate::Context<'_>) -> anyhow::Result<()> { let pkg_manifest_dir = metadata.workspace_root.join(package_name); let urls = urls(&outcome); - metadata.add_member(package_name, &problems, false, shell)?; - let file_paths = itertools::zip_eq( - src_paths(&pkg_manifest_dir, &outcome), + metadata.add_member(package_name, &problems, false, shell)?, crate::web::retrieve_testcases::save_test_cases( &workspace_root, &workspace_metadata.test_suite, @@ -183,10 +178,8 @@ pub fn run(opt: OptCompeteNew, ctx: crate::Context<'_>) -> anyhow::Result<()> { let pkg_manifest_dir = metadata.workspace_root.join(package_name); let urls = urls(&outcome); - metadata.add_member(package_name, &problems, is_no, shell)?; - let file_paths = itertools::zip_eq( - src_paths(&pkg_manifest_dir, &outcome), + metadata.add_member(package_name, &problems, is_no, shell)?, crate::web::retrieve_testcases::save_test_cases( &workspace_root, &workspace_metadata.test_suite, @@ -211,20 +204,6 @@ pub fn run(opt: OptCompeteNew, ctx: crate::Context<'_>) -> anyhow::Result<()> { Ok(()) } -fn src_paths(pkg_manifest_dir: &Path, outcome: &RetrieveTestCasesOutcome) -> Vec { - outcome - .problems - .iter() - .map(|problem| { - pkg_manifest_dir - .join("src") - .join("bin") - .join(problem.index.to_kebab_case()) - .with_extension("rs") - }) - .collect() -} - fn urls(outcome: &RetrieveTestCasesOutcome) -> Vec { outcome.problems.iter().map(|p| p.url.clone()).collect() } diff --git a/src/project.rs b/src/project.rs index b75b6c5..2206b1f 100644 --- a/src/project.rs +++ b/src/project.rs @@ -268,7 +268,7 @@ impl Metadata { problems: &BTreeMap<&str, &Url>, problems_are_yukicoder_no: bool, shell: &mut Shell, - ) -> anyhow::Result<()> { + ) -> anyhow::Result> { let (workspace_metadata, workspace_metadata_edit) = self.read_workspace_metadata_preserving()?; @@ -386,10 +386,16 @@ publish = false let template_code = crate::fs::read_to_string(self.workspace_root.join(workspace_metadata.template.code))?; - for problem_index in problems.keys() { - let src_path = src_bin - .join(problem_index.to_kebab_case()) - .with_extension("rs"); + let src_paths = problems + .keys() + .map(|problem_index| { + src_bin + .join(problem_index.to_kebab_case()) + .with_extension("rs") + }) + .collect::>(); + + for src_path in &src_paths { crate::fs::write(src_path, &template_code)?; } @@ -402,7 +408,7 @@ publish = false ), )?; - return match workspace_metadata.new_workspace_member { + match workspace_metadata.new_workspace_member { NewWorkspaceMember::Include => { cargo_member::Include::new(&self.workspace_root, &[pkg_manifest_dir]) .stderr(shell.err()) @@ -413,7 +419,9 @@ publish = false .stderr(shell.err()) .exec() } - }; + }?; + + return Ok(src_paths); fn escape_key(s: &str) -> String { if s.chars().any(|c| c.is_whitespace() || c.is_control()) { From 0b8d416c015678be3124bd930c37fc7622cae9fc Mon Sep 17 00:00:00 2001 From: Ryo Yamashita Date: Mon, 3 Aug 2020 21:30:00 +0900 Subject: [PATCH 5/7] Implement `retrieve testcases` command again --- src/commands/open.rs | 16 ++------- src/commands/retrieve_testcases.rs | 56 +++++++++++++++++++++--------- src/project.rs | 11 ++++++ src/web/retrieve_testcases.rs | 14 +++----- 4 files changed, 58 insertions(+), 39 deletions(-) diff --git a/src/commands/open.rs b/src/commands/open.rs index da9ab00..67291e0 100644 --- a/src/commands/open.rs +++ b/src/commands/open.rs @@ -1,8 +1,5 @@ use crate::{ - project::{ - MetadataExt as _, PackageExt as _, PackageMetadataCargoCompeteBin, TargetProblem, - TargetProblemYukicoder, - }, + project::{MetadataExt as _, PackageExt as _, PackageMetadataCargoCompeteBin}, shell::ColorChoice, }; use maplit::hashset; @@ -69,14 +66,7 @@ pub(crate) fn run(opt: OptCompeteOpen, ctx: crate::Context<'_>) -> anyhow::Resul for (index, PackageMetadataCargoCompeteBin { name, problem, .. }) in &package_metadata_bin { if problems.as_ref().map_or(true, |ps| ps.contains(index)) { - urls.extend(match problem { - TargetProblem::Atcoder { url, .. } - | TargetProblem::Codeforces { url, .. } - | TargetProblem::Yukicoder(TargetProblemYukicoder::Problem { url, .. }) - | TargetProblem::Yukicoder(TargetProblemYukicoder::Contest { url, .. }) => { - url.clone() - } - }); + urls.extend(problem.url()); let test_suite_path = crate::testing::test_suite_path( &metadata.workspace_root, @@ -95,7 +85,7 @@ pub(crate) fn run(opt: OptCompeteOpen, ctx: crate::Context<'_>) -> anyhow::Resul if !missing.is_empty() { shell.status("Retrieving", "missing test cases")?; - crate::web::retrieve_testcases::dl_missing( + crate::web::retrieve_testcases::dl_for_existing_package( &package_metadata_bin, Some(&missing), full, diff --git a/src/commands/retrieve_testcases.rs b/src/commands/retrieve_testcases.rs index 690ea02..7ee09de 100644 --- a/src/commands/retrieve_testcases.rs +++ b/src/commands/retrieve_testcases.rs @@ -1,5 +1,8 @@ -use crate::{project::MetadataExt as _, shell::ColorChoice}; -use std::path::PathBuf; +use crate::{ + project::{MetadataExt as _, PackageExt as _, PackageMetadataCargoCompeteBin}, + shell::ColorChoice, +}; +use std::{collections::HashSet, path::PathBuf}; use structopt::StructOpt; use strum::VariantNames as _; @@ -9,15 +12,11 @@ pub struct OptCompeteRetrieveTestcases { #[structopt(long)] pub full: bool, - /// Open URL and files - #[structopt(long)] - pub open: bool, - - /// Problem Indexes - #[structopt(long, value_name("STRING"))] + /// Retrieve only the problems + #[structopt(long, value_name("INDEX"))] pub problems: Option>, - /// Existing package to retrieving test cases for + /// Package (see `cargo help pkgid`) #[structopt(short, long, value_name("SPEC"))] pub package: Option, @@ -33,26 +32,24 @@ pub struct OptCompeteRetrieveTestcases { default_value("auto") )] pub color: ColorChoice, - - /// Creates pacakge afresh & retrieve test cases for the contest ID - pub contest: Option, } pub(crate) fn run(opt: OptCompeteRetrieveTestcases, ctx: crate::Context<'_>) -> anyhow::Result<()> { let OptCompeteRetrieveTestcases { full, - open, + problems, package, manifest_path, color, - problems, - contest, } = opt; let crate::Context { cwd, shell } = ctx; shell.set_color_choice(color); + let problems = problems.map(|ps| ps.into_iter().collect::>()); + let problems = problems.as_ref(); + let manifest_path = manifest_path .map(Ok) .unwrap_or_else(|| crate::project::locate_project(&cwd))?; @@ -60,6 +57,33 @@ pub(crate) fn run(opt: OptCompeteRetrieveTestcases, ctx: crate::Context<'_>) -> let workspace_metadata = metadata.read_workspace_metadata()?; let member = metadata.query_for_member(package)?; + let package_metadata_bin = member.read_package_metadata()?.bin; + + let mut urls = vec![]; + let mut file_paths = vec![]; + + for (index, PackageMetadataCargoCompeteBin { name, problem, .. }) in &package_metadata_bin { + if problems.map_or(true, |ps| ps.contains(index)) { + urls.extend(problem.url()); + + let test_suite_path = crate::testing::test_suite_path( + &metadata.workspace_root, + &workspace_metadata.test_suite, + &problem, + )?; + + file_paths.push((&member.bin_target(&name)?.src_path, test_suite_path)); + } + } + + crate::web::retrieve_testcases::dl_for_existing_package( + &package_metadata_bin, + problems, + full, + &metadata.workspace_root, + &workspace_metadata.test_suite, + shell, + )?; - todo!(); + Ok(()) } diff --git a/src/project.rs b/src/project.rs index 2206b1f..1fa0256 100644 --- a/src/project.rs +++ b/src/project.rs @@ -192,6 +192,17 @@ pub(crate) enum TargetProblem { Yukicoder(TargetProblemYukicoder), } +impl TargetProblem { + pub(crate) fn url(&self) -> Option<&Url> { + match self { + Self::Atcoder { url, .. } + | Self::Codeforces { url, .. } + | Self::Yukicoder(TargetProblemYukicoder::Problem { url, .. }) + | Self::Yukicoder(TargetProblemYukicoder::Contest { url, .. }) => url.as_ref(), + } + } +} + #[derive(Deserialize, Debug, Ord, PartialOrd, Eq, PartialEq)] #[serde(rename_all = "kebab-case", tag = "kind")] pub(crate) enum TargetProblemYukicoder { diff --git a/src/web/retrieve_testcases.rs b/src/web/retrieve_testcases.rs index d0f1ff5..f5c4a97 100644 --- a/src/web/retrieve_testcases.rs +++ b/src/web/retrieve_testcases.rs @@ -24,14 +24,14 @@ use std::{ path::{Path, PathBuf}, }; -pub(crate) fn dl_missing( +pub(crate) fn dl_for_existing_package( package_metadata_bin: &IndexMap, indexes: Option<&HashSet>, full: bool, workspace_root: &Path, test_suite_path: &crate::project::TemplateString, shell: &mut Shell, -) -> anyhow::Result> { +) -> anyhow::Result<()> { let mut atcoder_targets = btreemap!(); let mut codeforces_targets = btreemap!(); let mut yukicoder_problem_targets = btreeset!(); @@ -93,16 +93,10 @@ pub(crate) fn dl_missing( )?); } - let mut test_suite_paths = vec![]; for outcome in outcomes { - test_suite_paths.extend(save_test_cases( - workspace_root, - test_suite_path, - outcome, - shell, - )?); + save_test_cases(workspace_root, test_suite_path, outcome, shell)?; } - Ok(test_suite_paths) + Ok(()) } pub(crate) fn dl_from_atcoder( From 97b29237fd2b7f39ce8ef5921733cab97b626f1c Mon Sep 17 00:00:00 2001 From: Ryo Yamashita Date: Mon, 3 Aug 2020 21:36:55 +0900 Subject: [PATCH 6/7] Update CHANGELOG.md --- CHANGELOG.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f89ac2f..73cddc5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,4 +2,10 @@ ## [Unreleased] -- Fixed `package.repository` of this package. +### Changed + +- Splitted the `download` command into `new`, `open`, and `download`. ([#3](https://github.com/qryxip/cargo-compete/pull/3)) + +### Fixed + +- Fixed `package.repository` of this package. ([#2](https://github.com/qryxip/cargo-compete/pull/3)) From 5aea1a12ff2a808870be646762d1b1a4b55d0591 Mon Sep 17 00:00:00 2001 From: Ryo Yamashita Date: Mon, 3 Aug 2020 21:37:07 +0900 Subject: [PATCH 7/7] Update README-ja.md --- README-ja.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README-ja.md b/README-ja.md index 856b4bc..2364e00 100644 --- a/README-ja.md +++ b/README-ja.md @@ -66,6 +66,8 @@ AtCoderを選択に入れる場合、 ### `cargo compete download` +**インターフェイスがよろしくないのでv0.2.0で`new`, `open`, `download`の3つに分割する予定です。** ([#3](https://github.com/qryxip/cargo-compete/pull/3)) + テストケースの取得を行います。 **workspace rootかworkspace memberのどちらかを対象に取ります。**