Skip to content
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
11 changes: 9 additions & 2 deletions crates/cli/src/cli/comp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use clap::{Arg, ArgAction, ArgMatches, Command};

use crate::comp::{add, create, finish, list, remove, rename, solve, test};
use crate::config::get_settings;
use crate::problem::run::{RunnableCategory, RunnableFile};
use crate::util::get_project_root;

pub fn cli() -> Command {
Expand Down Expand Up @@ -189,15 +190,21 @@ pub fn exec(args: &ArgMatches) -> Result<()> {
.context("Competition name is required")?;
let solution_lang = cmd.try_get_one::<String>("lang")?;

solve::solve(&settings, &problems_dir, comp_name, solution_lang)?;
let solution_file =
RunnableFile::new(&settings, RunnableCategory::Solution, None, solution_lang)?;

solve::solve(&settings, &problems_dir, comp_name, solution_file)?;
}
Some(("test", cmd)) => {
let comp_name = cmd
.try_get_one::<String>("comp")?
.context("Competition name is required")?;
let solution_lang = cmd.try_get_one::<String>("lang")?;

test::test(&settings, &problems_dir, comp_name, solution_lang)?;
let solution_file =
RunnableFile::new(&settings, RunnableCategory::Solution, None, solution_lang)?;

test::test(&settings, &problems_dir, comp_name, solution_file)?;
}
_ => {}
}
Expand Down
42 changes: 24 additions & 18 deletions crates/cli/src/cli/problem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ pub fn cli() -> Command {
.long("generator-file")
.help("Name of the generator file")
.action(ArgAction::Set),
Arg::new("generator-lang")
.long("generator-lang")
.help("Language of the generator file (e.g. cpp, py)")
.action(ArgAction::Set),
Arg::new("problem")
.short('p')
.long("problem")
Expand All @@ -94,6 +98,10 @@ pub fn cli() -> Command {
.long("file")
.help("Name of the generator file")
.action(ArgAction::Set),
Arg::new("lang")
.long("lang")
.help("Language of the generator file (e.g. cpp, py)")
.action(ArgAction::Set),
Arg::new("problem")
.short('p')
.long("problem")
Expand Down Expand Up @@ -190,7 +198,7 @@ pub fn exec(args: &ArgMatches) -> Result<()> {
let mut solution_files: Vec<RunnableFile> = Vec::new();
for f in files {
let solution_file =
RunnableFile::new(&settings, RunnableCategory::Solution, Some(f));
RunnableFile::new(&settings, RunnableCategory::Solution, Some(f), None);
solution_files.push(solution_file?);
}

Expand Down Expand Up @@ -231,14 +239,15 @@ pub fn exec(args: &ArgMatches) -> Result<()> {
let mut solution_files: Vec<RunnableFile> = Vec::new();
for f in files {
let solution_file =
RunnableFile::new(&settings, RunnableCategory::Solution, Some(f));
RunnableFile::new(&settings, RunnableCategory::Solution, Some(f), None);
solution_files.push(solution_file?);
}

let generator = RunnableFile::new(
&settings,
RunnableCategory::Generator,
cmd.try_get_one::<String>("generator-file")?,
cmd.try_get_one::<String>("generator-lang")?,
)?;

let fuzz_args = fuzz::FuzzArgs {
Expand All @@ -260,6 +269,7 @@ pub fn exec(args: &ArgMatches) -> Result<()> {
&settings,
RunnableCategory::Generator,
cmd.try_get_one::<String>("file")?,
cmd.try_get_one::<String>("lang")?,
)?;

let test_name = cmd
Expand All @@ -281,33 +291,29 @@ pub fn exec(args: &ArgMatches) -> Result<()> {
None => &get_problem_from_cwd(&problems_dir)?,
};

let solution_file = cmd.try_get_one::<String>("file")?.map(|f| f.as_str());
let solution_lang = cmd.try_get_one::<String>("lang")?;

solve::solve(
let solution_file = RunnableFile::new(
&settings,
&problems_dir,
problem_name,
solution_file,
solution_lang,
RunnableCategory::Solution,
cmd.try_get_one::<String>("file")?,
cmd.try_get_one::<String>("lang")?,
)?;

solve::solve(&settings, &problems_dir, problem_name, &solution_file)?;
}
Some(("test", cmd)) => {
let problem_name = match cmd.try_get_one::<String>("problem")? {
Some(name) => name,
None => &get_problem_from_cwd(&problems_dir)?,
};

let solution_file = cmd.try_get_one::<String>("file")?.map(|f| f.as_str());
let solution_lang = cmd.try_get_one::<String>("lang")?;

test::test(
let solution_file = RunnableFile::new(
&settings,
&problems_dir,
problem_name,
solution_file,
solution_lang,
RunnableCategory::Solution,
cmd.try_get_one::<String>("file")?,
cmd.try_get_one::<String>("lang")?,
)?;

test::test(&settings, &problems_dir, problem_name, &solution_file)?;
}
_ => {}
}
Expand Down
6 changes: 3 additions & 3 deletions crates/cli/src/comp/solve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use anyhow::{bail, Context, Result};
use serde_json::from_reader;

use crate::config::Settings;
use crate::problem::run::RunnableFile;
use crate::problem::solve::solve as problem_solve;

use super::{Competitions, COMPETITIONS_FILE};
Expand All @@ -13,7 +14,7 @@ pub fn solve(
settings: &Settings,
problems_dir: &Path,
comp_name: &str,
solution_lang: Option<&String>,
solution_file: RunnableFile,
) -> Result<()> {
let comp_file_path = problems_dir.join(COMPETITIONS_FILE);
if !fs::exists(&comp_file_path)? {
Expand All @@ -34,8 +35,7 @@ pub fn solve(
settings,
problems_dir,
problem_name.as_str(),
None,
solution_lang,
&solution_file,
)?;
}

Expand Down
6 changes: 3 additions & 3 deletions crates/cli/src/comp/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use anyhow::{bail, Context, Result};
use serde_json::from_reader;

use crate::config::Settings;
use crate::problem::run::RunnableFile;
use crate::problem::test::test as problem_test;

use super::{Competitions, COMPETITIONS_FILE};
Expand All @@ -13,7 +14,7 @@ pub fn test(
settings: &Settings,
problems_dir: &Path,
comp_name: &str,
solution_lang: Option<&String>,
solution_file: RunnableFile,
) -> Result<()> {
let comp_file_path = problems_dir.join(COMPETITIONS_FILE);
if !fs::exists(&comp_file_path)? {
Expand All @@ -34,8 +35,7 @@ pub fn test(
settings,
problems_dir,
problem_name.as_str(),
None,
solution_lang,
&solution_file,
)?;
}

Expand Down
50 changes: 35 additions & 15 deletions crates/cli/src/problem/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,26 +38,46 @@ pub struct RunnableFile {

impl RunnableFile {
/// Sets the file name if given, and infers the language from the file
/// extension. Otherwise defaults to the category name and the default
/// language from the settings.
/// extension. If the language is provided but not the file name, the
/// default language file is used for that category.
///
/// If neither is provided, it defaults to the category name
/// and the default language from the settings.
pub fn new(
settings: &Settings,
category: RunnableCategory,
name: Option<&String>,
language: Option<&String>,
) -> Result<Self> {
let lang: String;
let filename: String;
if name.is_none() {
lang = match category {
RunnableCategory::Solution => settings.problem.default_lang.clone(),
RunnableCategory::Generator => settings.problem.default_generator_lang.clone(),
};
filename = format!("{category}.{lang}");
} else {
lang =
get_lang_from_extension(name.context("Failed to get filename of runnable file")?)?;
filename = name.unwrap().to_string();
}
let (filename, lang) = match (name, language) {
(Some(name), Some(lang)) => {
let file_lang = get_lang_from_extension(name)
.context("Failed to get language from file extension")?;
if file_lang != *lang {
bail!(
"Language from file extension ({file_lang}) does not match provided language ({lang})"
);
}
(name.to_string(), lang.to_string())
}
(Some(name), None) => {
let lang = get_lang_from_extension(name)
.context("Failed to get language from file extension")?;
(name.to_string(), lang)
}
(None, Some(lang)) => {
let filename = format!("{category}.{lang}");
(filename, lang.to_string())
}
(None, None) => {
let lang = match category {
RunnableCategory::Solution => settings.problem.default_lang.clone(),
RunnableCategory::Generator => settings.problem.default_generator_lang.clone(),
};
let filename = format!("{category}.{lang}");
(filename, lang)
}
};

Ok(Self {
name: filename,
Expand Down
113 changes: 20 additions & 93 deletions crates/cli/src/problem/solve.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
use std::ffi::OsStr;
use std::fs::{self, File};
use std::fs::File;
use std::io::Write;
use std::path::Path;

use anyhow::{bail, Context, Result};
use normpath::PathExt;
use subprocess::Exec;
use anyhow::{Context, Result};

use super::sync_mappings::get_problem;
use crate::problem::run::{RunCommand, RunnableFile};
use crate::util::get_project_root;
use crate::{config::Settings, util::get_input_files_in_directory};

Expand All @@ -15,113 +14,41 @@ pub fn solve(
settings: &Settings,
problems_dir: &Path,
problem_name: &str,
solution_file_name: Option<&str>,
solution_lang: Option<&String>,
solution_file: &RunnableFile,
) -> Result<()> {
let project_root = get_project_root()?;
let problem = project_root.join(get_problem(problems_dir, problem_name)?);
let problem_path = project_root.join(get_problem(problems_dir, problem_name)?);

let solution_lang = solution_lang.unwrap_or(&settings.problem.default_lang);
let mut solution_file = problem.join(format!("solutions/solution.{solution_lang}"));
if solution_file_name.is_some() {
solution_file = problem.join(format!(
"solutions/{}",
solution_file_name.context("Failed to get solution file name")?
));
}
solution_file = solution_file.normalize()?.into();

if !fs::exists(&solution_file).expect("Failed to check if path exists") {
bail!("Solution file does not exist: {:?}", solution_file);
}

eprintln!("Using solution file at: {}", solution_file.display());

let bin_file = problem.join("solutions/solution.out");
let script_file = problem.join(format!("solutions/solution.{solution_lang}"));

let lang_settings = settings
.problem
.solution
.get(solution_lang)
.context(format!(
"Could not get settings for language `{solution_lang}`"
))?;

let compile_command = lang_settings.compile_command.clone();
let run_command = RunCommand::new(
settings,
&problem_path,
solution_file,
problem_path.join("solutions/solution.out"),
problem_path.join(format!("{solution_file}")),
)?;

// Check if the solution file is a script (if it needs compilation or not)
let needs_compilation = compile_command.is_some();
let compile_command = compile_command.unwrap_or_default();
if needs_compilation && compile_command.is_empty() {
bail!("compile_command specified in the settings, but array is empty");
}

if needs_compilation {
let mut cmd_iter = compile_command.iter();
let mut final_cmd = Exec::cmd(cmd_iter.next().context("Failed to get command")?);
for c in cmd_iter {
// Replace strings where necessary
final_cmd = match c.as_str() {
"@in_file" => final_cmd.arg(&solution_file),
"@bin_file" => final_cmd.arg(&bin_file),
_ => final_cmd.arg(c),
}
}
eprint!("Compiling the solution file... ");
// Run the compile command
final_cmd.join()?;
eprintln!("Done");
}

let run_command = lang_settings.run_command.clone().unwrap_or_default();
if run_command.is_empty() {
bail!("No run command specified in the settings. It must be specified!");
}
let cmd_iter = run_command.iter();
let test_files = get_input_files_in_directory(problem.join("tests"))?;
let test_files = get_input_files_in_directory(problem_path.join("tests"))?;

eprintln!("Running the solution file for each test case...");
// Run the file for every test input and generate the corresponding output
for test_file in test_files {
let input_file_path = problem.join(format!("tests/{test_file}"));
let output_file_path = problem.join(format!(
let input_file_path = problem_path.join(format!("tests/{test_file}"));
let output_file_path = problem_path.join(format!(
"tests/{}.out",
test_file
.strip_suffix(".in")
.context("Failed to strip suffix of test file")?
));

let mut cmd_iter_clone = cmd_iter.clone();
let cmd = cmd_iter_clone.next().context("Failed to get command")?;
let mut final_cmd = Exec::cmd(match cmd.as_str() {
"@bin_file" => bin_file.as_os_str(),
"@script_file" => script_file.as_os_str(),
_ => OsStr::new(cmd),
});

for c in cmd_iter_clone {
// Replace strings where necessary
final_cmd = match c.as_str() {
"@bin_file" => final_cmd.arg(&bin_file),
"@script_file" => final_cmd.arg(&script_file),
_ => final_cmd.arg(c),
}
}
let result = run_command.get_result(Some(&input_file_path))?;
let mut output_file = File::create(output_file_path)?;
output_file.write_all(result.output.as_bytes())?;

let input_file = File::open(input_file_path)?;
let output_file = File::create(output_file_path)?;

final_cmd = final_cmd.stdin(input_file).stdout(output_file);
final_cmd.capture()?;
eprintln!(" - generated output for test file: {test_file}");
}
eprintln!("Finished generating outputs for all test cases");

// Delete the compiled run file, if it exists
if bin_file.exists() {
fs::remove_file(bin_file)?;
}
run_command.cleanup()?;

Ok(())
}
Loading