diff --git a/src/tools/copy_competition_files.rs b/src/tools/copy_competition_files.rs new file mode 100644 index 00000000..3aa47536 --- /dev/null +++ b/src/tools/copy_competition_files.rs @@ -0,0 +1,147 @@ +use std::fs::{copy, create_dir}; +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, + + #[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, &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, + 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, eval_config)) + .collect() +} + +fn get_task_from_task_dir( + path: &Path, + eval_config: &EvaluationConfig, +) -> Result<(IOITask, Vec), Error> { + let task = find_task(Some(path.into()), 1, 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 c5c8ce69..c88c1ead 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 5f1398b9..8688e50d 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 55ec4b42..dee7e408 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. diff --git a/task-maker-format/src/ioi/task_info.rs b/task-maker-format/src/ioi/task_info.rs index 0f15e45e..46f71980 100644 --- a/task-maker-format/src/ioi/task_info.rs +++ b/task-maker-format/src/ioi/task_info.rs @@ -13,66 +13,66 @@ 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, } impl IOITaskInfo { @@ -117,7 +117,7 @@ impl IOITaskInfo { .join("att") .read_dir() .map(|dir| { - dir.filter(|entry| entry.as_ref().unwrap().file_type().unwrap().is_file()) + dir.filter(|entry| entry.as_ref().unwrap().path().is_file()) .map(|entry| { let entry = entry.unwrap(); let path = entry.path();