From 8f7c0236d508345b1bf68a2c7b2d9f3807d75e53 Mon Sep 17 00:00:00 2001 From: Francesco Vercellesi Date: Tue, 1 Oct 2024 09:37:58 +0200 Subject: [PATCH 1/5] added symlinks to attachments in task_info --- task-maker-format/src/ioi/task_info.rs | 43 ++++++++++++++------------ 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/task-maker-format/src/ioi/task_info.rs b/task-maker-format/src/ioi/task_info.rs index 0f15e45ea..8049da7ec 100644 --- a/task-maker-format/src/ioi/task_info.rs +++ b/task-maker-format/src/ioi/task_info.rs @@ -1,4 +1,5 @@ use std::path::PathBuf; +use std::fs::FileType; use anyhow::Error; use itertools::Itertools; @@ -13,66 +14,70 @@ pub struct IOITaskInfo { /// Version of this task-info structure. version: u64, /// Short name of the task. - name: String, + pub name: String, /// Title of the task. - title: String, + pub title: String, /// Scoring info. - scoring: TaskInfoScoring, + pub scoring: TaskInfoScoring, /// Limits of the task. - limits: TaskInfoLimits, + pub limits: TaskInfoLimits, /// Statements of the task. - statements: Vec, + pub statements: Vec, /// Attachments of the task. - attachments: Vec, + pub attachments: Vec, } /// Limits of the task. #[derive(Debug, Clone, Serialize, Deserialize, TypeScriptify)] pub struct TaskInfoLimits { /// Time limit in seconds. - time: Option, + pub time: Option, /// Memory limit in megabytes. - memory: Option, + pub memory: Option, } /// Attachment of the task. #[derive(Debug, Clone, Serialize, Deserialize, TypeScriptify)] pub struct TaskInfoAttachment { /// Name of this attachment. - name: String, + pub name: String, /// MIME type of this attachment. - content_type: String, + pub content_type: String, /// Path of this attachment relative to task directory. - path: PathBuf, + pub path: PathBuf, } /// Info of the subtasks. #[derive(Debug, Clone, Serialize, Deserialize, TypeScriptify)] pub struct TaskInfoSubtask { /// Maximum score for this subtask. - max_score: f64, + pub max_score: f64, /// Number of testcases for this subtask. - testcases: u64, + pub testcases: u64, } /// Scoring for the task. #[derive(Debug, Clone, Serialize, Deserialize, TypeScriptify)] pub struct TaskInfoScoring { /// Maximum score for the task. - max_score: f64, + pub max_score: f64, /// Subtasks of this task. - subtasks: Vec, + pub subtasks: Vec, } /// Statement of the task. #[derive(Debug, Clone, Serialize, Deserialize, TypeScriptify)] pub struct TaskInfoStatement { /// Language of the statement. - language: String, + pub language: String, /// Content type of the statement, as MIME type. - content_type: String, + pub content_type: String, /// Path of the task, relative to the task directory. - path: PathBuf, + pub path: PathBuf, +} + +fn is_file_or_symlink(file_type: FileType) -> bool { + file_type.is_file() || file_type.is_symlink() } impl IOITaskInfo { @@ -117,7 +122,7 @@ impl IOITaskInfo { .join("att") .read_dir() .map(|dir| { - dir.filter(|entry| entry.as_ref().unwrap().file_type().unwrap().is_file()) + dir.filter(|entry| is_file_or_symlink(entry.as_ref().unwrap().file_type().unwrap())) .map(|entry| { let entry = entry.unwrap(); let path = entry.path(); From b3aa0bc9b8563a4902d1955a8fe85a2a00284160 Mon Sep 17 00:00:00 2001 From: Francesco Vercellesi Date: Tue, 1 Oct 2024 10:32:24 +0200 Subject: [PATCH 2/5] Added tool to copy all statements and attachments from a contest in a directory --- src/tools/copy_competition_files.rs | 142 ++++++++++++++++++++++++++++ src/tools/main.rs | 2 + src/tools/mod.rs | 1 + src/tools/opt.rs | 3 + 4 files changed, 148 insertions(+) create mode 100644 src/tools/copy_competition_files.rs diff --git a/src/tools/copy_competition_files.rs b/src/tools/copy_competition_files.rs new file mode 100644 index 000000000..43fb92ec2 --- /dev/null +++ b/src/tools/copy_competition_files.rs @@ -0,0 +1,142 @@ +use std::fs::{create_dir, copy}; +use std::path::{Path, PathBuf}; + +use anyhow::{bail, Context, Error}; +use clap::Parser; + +use task_maker_format::ioi::{Booklet, BookletConfig, IOITask, IOITaskInfo}; +use task_maker_format::{find_task, EvaluationConfig, TaskInfo}; + +use crate::context::RuntimeContext; +use crate::{ExecutionOpt, LoggerOpt, StorageOpt, ToolsSandboxRunner, UIOpt}; + +#[derive(Parser, Debug, Clone)] +pub struct CopyCompetitionFilesOpt { + /// Directory of the context. + /// + /// When not specified, . is assumed. + #[clap(short = 'c', long = "contest-dir", default_value = ".")] + pub contest_dir: PathBuf, + + /// Look at most for this number of parents for searching the task + #[clap(long = "max-depth", default_value = "3")] + pub max_depth: u32, + + #[clap(flatten, next_help_heading = Some("UI"))] + pub ui: UIOpt, + + #[clap(flatten, next_help_heading = Some("EXECUTION"))] + pub execution: ExecutionOpt, + + #[clap(flatten, next_help_heading = Some("STORAGE"))] + pub storage: StorageOpt, +} + +pub fn copy_competition_files_main(mut opt: CopyCompetitionFilesOpt, logger_opt: LoggerOpt) -> Result<(), Error> { + opt.ui.disable_if_needed(&logger_opt); + let eval_config = EvaluationConfig { + solution_filter: vec![], + booklet_solutions: false, + no_statement: false, + solution_paths: vec![], + disabled_sanity_checks: vec![], + seed: None, + dry_run: opt.execution.dry_run, + }; + + // create folder for competition files + let competition_files_dir = opt.contest_dir.join("competition-files"); + if !competition_files_dir.exists() { + create_dir(&competition_files_dir)?; + } + + for (mut task, booklets) in get_tasks_from_contest_dir(&opt.contest_dir, &opt, &eval_config)? { + // clean up the task a bit for a cleaner ui + task.subtasks.clear(); + + // setup the configuration and the evaluation metadata + let mut context = RuntimeContext::new(task.clone().into(), &opt.execution, |_task, eval| { + for booklet in booklets { + booklet.build(eval)?; + } + Ok(()) + })?; + context.sandbox_runner(ToolsSandboxRunner::default()); + + // start the execution + let executor = context.connect_executor(&opt.execution, &opt.storage)?; + let executor = executor.start_ui(&opt.ui.ui, |ui, mex| ui.on_message(mex))?; + executor.execute()?; + + let TaskInfo::IOI(task_info) = task.task_info()? else { + bail!("Competition folder creation is supported only for IOI tasks now"); + }; + + copy_files(task_info, &opt.contest_dir.join(&task.name), &competition_files_dir.join(&task.name))?; + } + + Ok(()) +} + +fn copy_files(task_info: IOITaskInfo, task_path: &Path, files_path: &Path) -> Result<(), Error> { + // create problem directory + if !files_path.exists() { + create_dir(&files_path)?; + } + + // copy statements + for statement in task_info.statements { + let statement_path = task_path.join(&statement.path); + let target_path = files_path.join(&statement.path.file_name().unwrap()); + + copy(statement_path, target_path)?; + } + + // copy attachments + for attachment in task_info.attachments { + let attachment_path = task_path.join(&attachment.path); + let target_path = files_path.join(&attachment.path.file_name().unwrap()); + + copy(attachment_path, target_path)?; + } + + Ok(()) +} + +fn get_tasks_from_contest_dir( + contest_dir: &Path, + opt: &CopyCompetitionFilesOpt, + eval_config: &EvaluationConfig, +) -> Result)>, Error> { + let contest_yaml = if let Some(contest_yaml) = BookletConfig::contest_yaml(contest_dir) { + contest_yaml? + } else { + bail!("Missing contest.yaml"); + }; + + let mut tasks = vec![]; + for task in contest_yaml.tasks { + let task_dir = contest_dir.join(task); + tasks.push(task_dir); + } + + tasks.iter().map(|task| get_task_from_task_dir(task, opt, eval_config)).collect() +} + +fn get_task_from_task_dir( + path: &Path, + opt: &CopyCompetitionFilesOpt, + eval_config: &EvaluationConfig, +) -> Result<(IOITask, Vec), Error> { + let task = find_task(Some(path.into()), opt.max_depth, eval_config)?; + let path = task.path(); + let task = IOITask::new(path, eval_config).with_context(|| { + format!( + "Booklet compilation is supported only for IOI tasks for now (task at {})", + path.display() + ) + })?; + + let booklets = task.booklets.clone(); + Ok((task, booklets)) +} diff --git a/src/tools/main.rs b/src/tools/main.rs index c5c8ce690..c88c1ead0 100644 --- a/src/tools/main.rs +++ b/src/tools/main.rs @@ -4,6 +4,7 @@ use task_maker_rust::error::NiceError; use task_maker_rust::tools::add_solution_checks::main_add_solution_checks; use task_maker_rust::tools::booklet::main_booklet; use task_maker_rust::tools::clear::main_clear; +use task_maker_rust::tools::copy_competition_files::copy_competition_files_main; use task_maker_rust::tools::find_bad_case::main_find_bad_case; use task_maker_rust::tools::fuzz_checker::main_fuzz_checker; use task_maker_rust::tools::gen_autocompletion::main_get_autocompletion; @@ -29,6 +30,7 @@ fn main() { Tool::Sandbox(opt) => main_sandbox(opt), Tool::TaskInfo(opt) => main_task_info(opt), Tool::Booklet(opt) => main_booklet(opt, base_opt.logger), + Tool::CopyCompetitionFiles(opt) => copy_competition_files_main(opt, base_opt.logger), Tool::FuzzChecker(opt) => main_fuzz_checker(opt), Tool::FindBadCase(opt) => main_find_bad_case(opt), Tool::AddSolutionChecks(opt) => main_add_solution_checks(opt, base_opt.logger), diff --git a/src/tools/mod.rs b/src/tools/mod.rs index 5f1398b9c..8688e50dd 100644 --- a/src/tools/mod.rs +++ b/src/tools/mod.rs @@ -1,6 +1,7 @@ pub mod add_solution_checks; pub mod booklet; pub mod clear; +pub mod copy_competition_files; pub mod find_bad_case; pub mod fuzz_checker; pub mod gen_autocompletion; diff --git a/src/tools/opt.rs b/src/tools/opt.rs index 55ec4b427..dee7e408e 100644 --- a/src/tools/opt.rs +++ b/src/tools/opt.rs @@ -3,6 +3,7 @@ use clap::Parser; use crate::tools::add_solution_checks::AddSolutionChecksOpt; use crate::tools::booklet::BookletOpt; use crate::tools::clear::ClearOpt; +use crate::tools::copy_competition_files::CopyCompetitionFilesOpt; use crate::tools::find_bad_case::FindBadCaseOpt; use crate::tools::fuzz_checker::FuzzCheckerOpt; use crate::tools::gen_autocompletion::GenAutocompletionOpt; @@ -46,6 +47,8 @@ pub enum Tool { TaskInfo(TaskInfoOpt), /// Compile just the booklet for a task or a contest. Booklet(BookletOpt), + /// Copy statements and attachments of a contest in a separate directory + CopyCompetitionFiles(CopyCompetitionFilesOpt), /// Fuzz the checker of a task. FuzzChecker(FuzzCheckerOpt), /// Generate and search for an input file that make a solution fail. From 2a5760151d0edfbf0e1689c3b1ff7143745621aa Mon Sep 17 00:00:00 2001 From: Francesco Vercellesi Date: Tue, 1 Oct 2024 11:14:09 +0200 Subject: [PATCH 3/5] removed useless parameter --- src/tools/copy_competition_files.rs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/tools/copy_competition_files.rs b/src/tools/copy_competition_files.rs index 43fb92ec2..55658db49 100644 --- a/src/tools/copy_competition_files.rs +++ b/src/tools/copy_competition_files.rs @@ -18,10 +18,6 @@ pub struct CopyCompetitionFilesOpt { #[clap(short = 'c', long = "contest-dir", default_value = ".")] pub contest_dir: PathBuf, - /// Look at most for this number of parents for searching the task - #[clap(long = "max-depth", default_value = "3")] - pub max_depth: u32, - #[clap(flatten, next_help_heading = Some("UI"))] pub ui: UIOpt, @@ -50,7 +46,7 @@ pub fn copy_competition_files_main(mut opt: CopyCompetitionFilesOpt, logger_opt: create_dir(&competition_files_dir)?; } - for (mut task, booklets) in get_tasks_from_contest_dir(&opt.contest_dir, &opt, &eval_config)? { + for (mut task, booklets) in get_tasks_from_contest_dir(&opt.contest_dir, &eval_config)? { // clean up the task a bit for a cleaner ui task.subtasks.clear(); @@ -105,7 +101,6 @@ fn copy_files(task_info: IOITaskInfo, task_path: &Path, files_path: &Path) -> Re fn get_tasks_from_contest_dir( contest_dir: &Path, - opt: &CopyCompetitionFilesOpt, eval_config: &EvaluationConfig, ) -> Result)>, Error> { let contest_yaml = if let Some(contest_yaml) = BookletConfig::contest_yaml(contest_dir) { @@ -120,15 +115,14 @@ fn get_tasks_from_contest_dir( tasks.push(task_dir); } - tasks.iter().map(|task| get_task_from_task_dir(task, opt, eval_config)).collect() + tasks.iter().map(|task| get_task_from_task_dir(task, eval_config)).collect() } fn get_task_from_task_dir( path: &Path, - opt: &CopyCompetitionFilesOpt, eval_config: &EvaluationConfig, ) -> Result<(IOITask, Vec), Error> { - let task = find_task(Some(path.into()), opt.max_depth, eval_config)?; + let task = find_task(Some(path.into()), 1, eval_config)?; let path = task.path(); let task = IOITask::new(path, eval_config).with_context(|| { format!( From b456dea26b2c56a4c8eae9dc2588ca732c8a8f08 Mon Sep 17 00:00:00 2001 From: Francesco Vercellesi Date: Tue, 1 Oct 2024 11:43:42 +0200 Subject: [PATCH 4/5] fixed check for files --- task-maker-format/src/ioi/task_info.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/task-maker-format/src/ioi/task_info.rs b/task-maker-format/src/ioi/task_info.rs index 8049da7ec..46f71980f 100644 --- a/task-maker-format/src/ioi/task_info.rs +++ b/task-maker-format/src/ioi/task_info.rs @@ -1,5 +1,4 @@ use std::path::PathBuf; -use std::fs::FileType; use anyhow::Error; use itertools::Itertools; @@ -76,10 +75,6 @@ pub struct TaskInfoStatement { pub path: PathBuf, } -fn is_file_or_symlink(file_type: FileType) -> bool { - file_type.is_file() || file_type.is_symlink() -} - impl IOITaskInfo { /// Generate the task information from the provided `Task`. pub fn new(task: &IOITask) -> Result { @@ -122,7 +117,7 @@ impl IOITaskInfo { .join("att") .read_dir() .map(|dir| { - dir.filter(|entry| is_file_or_symlink(entry.as_ref().unwrap().file_type().unwrap())) + dir.filter(|entry| entry.as_ref().unwrap().path().is_file()) .map(|entry| { let entry = entry.unwrap(); let path = entry.path(); From d7364bf29c1948fbec299e775d3c59d16483fc9f Mon Sep 17 00:00:00 2001 From: Francesco Vercellesi Date: Tue, 1 Oct 2024 11:54:13 +0200 Subject: [PATCH 5/5] clippy lints and formatting --- src/tools/copy_competition_files.rs | 43 ++++++++++++++++++----------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/src/tools/copy_competition_files.rs b/src/tools/copy_competition_files.rs index 55658db49..3aa475361 100644 --- a/src/tools/copy_competition_files.rs +++ b/src/tools/copy_competition_files.rs @@ -1,4 +1,4 @@ -use std::fs::{create_dir, copy}; +use std::fs::{copy, create_dir}; use std::path::{Path, PathBuf}; use anyhow::{bail, Context, Error}; @@ -28,7 +28,10 @@ pub struct CopyCompetitionFilesOpt { pub storage: StorageOpt, } -pub fn copy_competition_files_main(mut opt: CopyCompetitionFilesOpt, logger_opt: LoggerOpt) -> Result<(), Error> { +pub fn copy_competition_files_main( + mut opt: CopyCompetitionFilesOpt, + logger_opt: LoggerOpt, +) -> Result<(), Error> { opt.ui.disable_if_needed(&logger_opt); let eval_config = EvaluationConfig { solution_filter: vec![], @@ -51,12 +54,13 @@ pub fn copy_competition_files_main(mut opt: CopyCompetitionFilesOpt, logger_opt: task.subtasks.clear(); // setup the configuration and the evaluation metadata - let mut context = RuntimeContext::new(task.clone().into(), &opt.execution, |_task, eval| { - for booklet in booklets { - booklet.build(eval)?; - } - Ok(()) - })?; + let mut context = + RuntimeContext::new(task.clone().into(), &opt.execution, |_task, eval| { + for booklet in booklets { + booklet.build(eval)?; + } + Ok(()) + })?; context.sandbox_runner(ToolsSandboxRunner::default()); // start the execution @@ -68,7 +72,11 @@ pub fn copy_competition_files_main(mut opt: CopyCompetitionFilesOpt, logger_opt: bail!("Competition folder creation is supported only for IOI tasks now"); }; - copy_files(task_info, &opt.contest_dir.join(&task.name), &competition_files_dir.join(&task.name))?; + copy_files( + task_info, + &opt.contest_dir.join(&task.name), + &competition_files_dir.join(&task.name), + )?; } Ok(()) @@ -77,22 +85,22 @@ pub fn copy_competition_files_main(mut opt: CopyCompetitionFilesOpt, logger_opt: fn copy_files(task_info: IOITaskInfo, task_path: &Path, files_path: &Path) -> Result<(), Error> { // create problem directory if !files_path.exists() { - create_dir(&files_path)?; + create_dir(files_path)?; } - + // copy statements for statement in task_info.statements { let statement_path = task_path.join(&statement.path); - let target_path = files_path.join(&statement.path.file_name().unwrap()); - + let target_path = files_path.join(statement.path.file_name().unwrap()); + copy(statement_path, target_path)?; } // copy attachments for attachment in task_info.attachments { let attachment_path = task_path.join(&attachment.path); - let target_path = files_path.join(&attachment.path.file_name().unwrap()); - + let target_path = files_path.join(attachment.path.file_name().unwrap()); + copy(attachment_path, target_path)?; } @@ -115,7 +123,10 @@ fn get_tasks_from_contest_dir( tasks.push(task_dir); } - tasks.iter().map(|task| get_task_from_task_dir(task, eval_config)).collect() + tasks + .iter() + .map(|task| get_task_from_task_dir(task, eval_config)) + .collect() } fn get_task_from_task_dir(