From 5f9b3b3ff8059e0bb98a3bf11b4339f2c08d191c Mon Sep 17 00:00:00 2001 From: Filippo Casarin Date: Mon, 23 Sep 2024 14:55:24 +0200 Subject: [PATCH] Clear task.yaml --- .../src/ioi/format/italian_yaml/cases_gen.rs | 36 ++------ .../src/ioi/format/italian_yaml/mod.rs | 86 ++++++++++++------- task-maker-format/src/ioi/mod.rs | 20 ++++- 3 files changed, 79 insertions(+), 63 deletions(-) diff --git a/task-maker-format/src/ioi/format/italian_yaml/cases_gen.rs b/task-maker-format/src/ioi/format/italian_yaml/cases_gen.rs index 0a08ae39a..3c6b60635 100644 --- a/task-maker-format/src/ioi/format/italian_yaml/cases_gen.rs +++ b/task-maker-format/src/ioi/format/italian_yaml/cases_gen.rs @@ -1,9 +1,6 @@ use std::collections::HashMap; -use std::fmt::Debug; -use std::fmt::Display; -use std::fmt::Formatter; -use std::fmt::Write; -use std::io::Read; +use std::fmt::{Debug, Display, Formatter, Write}; +use std::fs; use std::path::{Path, PathBuf}; use std::str::FromStr; use std::sync::Arc; @@ -13,16 +10,13 @@ use pest::Parser; use task_maker_diagnostics::CodeSpan; -use crate::ioi::format::italian_yaml::TaskInputEntry; +use crate::ioi::italian_yaml::{is_tm_deletable, TaskInputEntry, TM_ALLOW_DELETE_COOKIE}; use crate::ioi::{ InputGenerator, InputValidator, OutputGenerator, SubtaskId, SubtaskInfo, TestcaseId, TestcaseInfo, TM_VALIDATION_FILE_NAME, }; use crate::SourceFile; -/// String placed in the auto-generated gen/GEN marking it as safely deletable. -pub(crate) const TM_ALLOW_DELETE_COOKIE: &str = "tm-allow-delete"; - /// This module exists because of a `pest`'s bug: #[allow(missing_docs)] mod parser { @@ -146,7 +140,7 @@ where .context("Invalid gen/cases.gen path")? .parent() .context("Invalid gen/cases.gen path")?; - let content = std::fs::read_to_string(path) + let content = fs::read_to_string(path) .with_context(|| format!("Failed to read {}", path.display()))?; let mut file = parser::CasesGenParser::parse(parser::Rule::file, &content) .context("Cannot parse cases.gen")?; @@ -209,7 +203,7 @@ where /// Write an auto-generated version of the gen/GEN file inside the task directory. pub(crate) fn write_gen_gen(&self) -> Result<(), Error> { let dest = self.task_dir.join("gen/GEN"); - if dest.exists() && !is_gen_gen_deletable(&dest)? { + if dest.exists() && !is_tm_deletable(&dest)? { warn!( "The gen/GEN file does not contain {}. Won't overwrite", TM_ALLOW_DELETE_COOKIE @@ -256,7 +250,7 @@ where } } } - std::fs::write(self.task_dir.join("gen/GEN"), gen).context("Failed to write gen/GEN")?; + fs::write(self.task_dir.join("gen/GEN"), gen).context("Failed to write gen/GEN")?; Ok(()) } @@ -668,24 +662,6 @@ where } } -/// Check if the gen/GEN file is deletable, i.e. it exists and it is autogenerated. -pub(crate) fn is_gen_gen_deletable(path: &Path) -> Result { - if !path.exists() { - return Ok(false); - } - let mut buffer = vec![]; - let mut file = std::fs::File::open(path) - .with_context(|| format!("Cannot open file {}", path.display()))?; - file.read_to_end(&mut buffer) - .context("Cannot read gen/GEN file")?; - let content = String::from_utf8_lossy(&buffer); - // if the gen/GEN is present and it does not contain the cookie, skip all the generation - if !content.contains(TM_ALLOW_DELETE_COOKIE) { - return Ok(false); - } - Ok(true) -} - impl ConstraintOperator { /// Apply the operator to the provided values and return the result of the comparison. fn is_valid(&self, lhs: i64, rhs: i64) -> bool { diff --git a/task-maker-format/src/ioi/format/italian_yaml/mod.rs b/task-maker-format/src/ioi/format/italian_yaml/mod.rs index 7d2a2192f..aeff54d6a 100644 --- a/task-maker-format/src/ioi/format/italian_yaml/mod.rs +++ b/task-maker-format/src/ioi/format/italian_yaml/mod.rs @@ -256,8 +256,8 @@ //! The subtask also does not have a name, the default one (`subtask2`) will be used. use std::collections::HashMap; -use std::fs::File; -use std::io::BufWriter; +use std::fs::{self, File}; +use std::io::{BufWriter, Write}; use std::path::{Path, PathBuf}; use std::sync::Arc; @@ -267,7 +267,6 @@ use serde::{Deserialize, Serialize, Serializer}; use unic::normal::StrNormalForm; use unic::ucd::category::GeneralCategory; -pub(crate) use cases_gen::{is_gen_gen_deletable, TM_ALLOW_DELETE_COOKIE}; use task_maker_lang::GraderMap; use crate::ioi::sanity_checks::get_sanity_checks; @@ -283,6 +282,9 @@ mod cases_gen; mod gen_gen; mod static_inputs; +/// String placed in the auto-generated gen/GEN marking it as safely deletable. +pub(crate) const TM_ALLOW_DELETE_COOKIE: &str = "tm-allow-delete"; + /// The set of valid Unicode General Categories for the characters composing a subtask name. pub const VALID_SUBTASK_NAME_CHARACTER_CATEGORIES: &[GeneralCategory] = &[ // L group (included in XID_Start) @@ -500,21 +502,25 @@ pub fn parse_task>( ) -> Result { let task_dir = task_dir.as_ref(); + let task_yaml_path = task_dir.join("task.yaml"); + let task_yaml_orig_path = task_dir.join("task.yaml.orig"); let task_yaml_overwrite: bool; let mut yaml: TaskYAML; - if task_dir.join("task.yaml.orig").exists() { + if task_yaml_orig_path.exists() { task_yaml_overwrite = true; - let path = task_dir.join(task_dir.join("task.yaml.orig")); - let file = File::open(&path) - .with_context(|| format!("Cannot open task.yaml.orig from {}", path.display()))?; + let file = File::open(&task_yaml_orig_path).with_context(|| { + format!( + "Cannot open task.yaml.orig from {}", + task_yaml_orig_path.display() + ) + })?; let yaml_orig: TaskYAMLOrig = serde_yaml::from_reader(file).context("Failed to deserialize task.yaml.orig")?; yaml = yaml_orig.into_task_yaml(task_dir); - } else if task_dir.join("task.yaml").exists() { + } else if task_yaml_path.exists() { task_yaml_overwrite = false; - let path = task_dir.join(task_dir.join("task.yaml")); - let file = File::open(&path) - .with_context(|| format!("Cannot open task.yaml from {}", path.display()))?; + let file = File::open(&task_yaml_path) + .with_context(|| format!("Cannot open task.yaml from {}", task_yaml_path.display()))?; yaml = serde_yaml::from_reader(file).context("Failed to deserialize task.yaml")?; } else { bail!("No task.yaml found in {}", task_dir.display()); @@ -630,27 +636,37 @@ pub fn parse_task>( yaml.score_type = Some(testcase_score_aggregator); if task_yaml_overwrite { - if !eval_config.dry_run { - yaml.score_type_parameters = Some( - subtasks - .iter() - .sorted_by_key(|(id, _)| *id) - .map(|(_, st)| { - let testcases = st - .testcases - .iter() - .map(|tc_num| format!("{tc_num:03}")) - .join("|"); - (st.max_score, testcases) - }) - .collect(), + if !task_yaml_path.exists() || is_tm_deletable(&task_yaml_path)? { + if !eval_config.dry_run { + yaml.score_type_parameters = Some( + subtasks + .iter() + .sorted_by_key(|(id, _)| *id) + .map(|(_, st)| { + let testcases = st + .testcases + .iter() + .map(|tc_num| format!("{tc_num:03}")) + .join("|"); + (st.max_score, testcases) + }) + .collect(), + ); + + let file = File::create(&task_yaml_path).with_context(|| { + format!("Cannot open task.yaml from {}", task_yaml_path.display()) + })?; + let mut buf_file = BufWriter::new(file); + writeln!(buf_file, "# Generated by task-maker. Do not edit!")?; + writeln!(buf_file, "# {}", TM_ALLOW_DELETE_COOKIE)?; + writeln!(buf_file, "# Removing or changing the line above will prevent task-maker from touching this file again.")?; + serde_yaml::to_writer(buf_file, &yaml).context("Failed to serialize task.yaml")?; + } + } else { + warn!( + "The task.yaml file does not contain {}. Won't overwrite", + TM_ALLOW_DELETE_COOKIE ); - - let path = task_dir.join("task.yaml"); - let file = File::create(&path) - .with_context(|| format!("Cannot open task.yaml from {}", path.display()))?; - serde_yaml::to_writer(BufWriter::new(file), &yaml) - .context("Failed to serialize task.yaml")?; } } else if subtasks.values().any(|st| !st.dependencies.is_empty()) { bail!("Use task.yaml.orig to use subtask dependencies"); @@ -692,6 +708,14 @@ pub fn parse_task>( Ok(task) } +/// Check if the file is deletable, i.e. it contains the TM_ALLOW_DELETE_COOKIE +/// Assumes the file exists. +pub(crate) fn is_tm_deletable(path: &Path) -> Result { + let buffer = fs::read(path).with_context(|| format!("Cannot read file {}", path.display()))?; + let content = String::from_utf8_lossy(&buffer); + Ok(content.contains(TM_ALLOW_DELETE_COOKIE)) +} + /// Search for a valid input validator inside the task directory. Will return a function that, given /// a subtask id, returns an `InputValidator` using that validator. If no validator is found, /// `InputValidator::AssumeValid` is used. diff --git a/task-maker-format/src/ioi/mod.rs b/task-maker-format/src/ioi/mod.rs index 8ad657b62..ec7489e12 100644 --- a/task-maker-format/src/ioi/mod.rs +++ b/task-maker-format/src/ioi/mod.rs @@ -38,7 +38,7 @@ use task_maker_lang::GraderMap; pub use ui_state::*; use crate::ioi::format::italian_yaml::TM_ALLOW_DELETE_COOKIE; -use crate::ioi::italian_yaml::is_gen_gen_deletable; +use crate::ioi::italian_yaml::is_tm_deletable; use crate::sanity_checks::SanityChecks; use crate::solution::SolutionInfo; use crate::ui::*; @@ -481,7 +481,7 @@ impl IOITask { let gen_gen_path = self.path.join("gen/GEN"); let cases_gen_path = self.path.join("gen/cases.gen"); if cases_gen_path.exists() && gen_gen_path.exists() { - if is_gen_gen_deletable(&gen_gen_path)? { + if is_tm_deletable(&gen_gen_path)? { info!("Removing {}", gen_gen_path.display()); std::fs::remove_file(&gen_gen_path).with_context(|| { format!("Failed to remove gen/GEN at {}", gen_gen_path.display()) @@ -493,6 +493,22 @@ impl IOITask { ); } } + // remove task.yaml if there is task.yaml.orig + let task_yaml_path = self.path.join("task.yaml"); + let task_yaml_orig_path = self.path.join("task.yaml.orig"); + if task_yaml_orig_path.exists() && task_yaml_path.exists() { + if is_tm_deletable(&task_yaml_path)? { + info!("Removing {}", task_yaml_path.display()); + std::fs::remove_file(&task_yaml_path).with_context(|| { + format!("Failed to remove task.yaml at {}", task_yaml_path.display()) + })?; + } else { + warn!( + "Won't remove task.yaml since it doesn't contain {}", + TM_ALLOW_DELETE_COOKIE + ); + } + } Ok(()) }