Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added tool to copy attachments and statements from a contest #266

Merged
merged 5 commits into from
Oct 1, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 136 additions & 0 deletions src/tools/copy_competition_files.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
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,

#[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<Vec<(IOITask, Vec<Booklet>)>, 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<Booklet>), 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))
}
2 changes: 2 additions & 0 deletions src/tools/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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),
Expand Down
1 change: 1 addition & 0 deletions src/tools/mod.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
3 changes: 3 additions & 0 deletions src/tools/opt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
Expand Down
43 changes: 24 additions & 19 deletions task-maker-format/src/ioi/task_info.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::path::PathBuf;
use std::fs::FileType;

use anyhow::Error;
use itertools::Itertools;
Expand All @@ -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<TaskInfoStatement>,
pub statements: Vec<TaskInfoStatement>,
/// Attachments of the task.
attachments: Vec<TaskInfoAttachment>,
pub attachments: Vec<TaskInfoAttachment>,
}

/// Limits of the task.
#[derive(Debug, Clone, Serialize, Deserialize, TypeScriptify)]
pub struct TaskInfoLimits {
/// Time limit in seconds.
time: Option<f64>,
pub time: Option<f64>,
/// Memory limit in megabytes.
memory: Option<u64>,
pub memory: Option<u64>,
}

/// 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<TaskInfoSubtask>,
pub subtasks: Vec<TaskInfoSubtask>,
}

/// 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 {
Expand Down Expand Up @@ -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()))
veluca93 marked this conversation as resolved.
Show resolved Hide resolved
.map(|entry| {
let entry = entry.unwrap();
let path = entry.path();
Expand Down