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

Acknowledge --dry-run when writing task.yaml #256

Merged
merged 2 commits into from
Sep 23, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
36 changes: 6 additions & 30 deletions task-maker-format/src/ioi/format/italian_yaml/cases_gen.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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: <https://github.com/pest-parser/pest/issues/326>
#[allow(missing_docs)]
mod parser {
Expand Down Expand Up @@ -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")?;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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(())
}

Expand Down Expand Up @@ -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<bool, Error> {
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 {
Expand Down
86 changes: 56 additions & 30 deletions task-maker-format/src/ioi/format/italian_yaml/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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;
Expand All @@ -283,6 +282,9 @@ mod cases_gen;
mod gen_gen;
mod static_inputs;

/// String placed in the auto-generated files marking them 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)
Expand Down Expand Up @@ -500,21 +502,25 @@ pub fn parse_task<P: AsRef<Path>>(
) -> Result<IOITask, Error> {
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());
Expand Down Expand Up @@ -630,26 +636,38 @@ pub fn parse_task<P: AsRef<Path>>(
yaml.score_type = Some(testcase_score_aggregator);

if task_yaml_overwrite {
yaml.score_type_parameters = Some(
subtasks
.iter()
.sorted_by_key(|(id, _)| *id)
.map(|(_, st)| {
let testcases = st
.testcases
if !task_yaml_path.exists() || is_tm_deletable(&task_yaml_path)? {
if !eval_config.dry_run {
yaml.score_type_parameters = Some(
subtasks
.iter()
.map(|tc_num| format!("{tc_num:03}"))
.join("|");
(st.max_score, testcases)
})
.collect(),
);

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")?;
.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
);
}
} else if subtasks.values().any(|st| !st.dependencies.is_empty()) {
bail!("Use task.yaml.orig to use subtask dependencies");
}
Expand Down Expand Up @@ -690,6 +708,14 @@ pub fn parse_task<P: AsRef<Path>>(
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<bool, Error> {
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.
Expand Down
20 changes: 18 additions & 2 deletions task-maker-format/src/ioi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;
Expand Down Expand Up @@ -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())
Expand All @@ -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(())
}

Expand Down