pub mod generator;
pub mod testcase;
use std::path::PathBuf;
use anyhow::Context;
#[derive(Debug)]
pub struct ProblemContext {
pub contest_id: String,
pub problem_alphabet: String,
pub task_screen_name: String,
pub problem_dir: PathBuf,
pub problem_url: String,
}
#[derive(Debug)]
pub struct ContestContext {
pub contest_id: String,
pub contest_dir: PathBuf,
}
pub enum CurrentContext {
ProblemDir(ProblemContext),
ContestDir(ContestContext),
Outside,
}
pub fn detect_current_context() -> CurrentContext {
if let Ok(ctx) = detect_problem_dir() {
CurrentContext::ProblemDir(ctx)
} else if let Ok(ctx) = detect_contest_dir() {
CurrentContext::ContestDir(ctx)
} else {
CurrentContext::Outside
}
}
pub fn require_problem_context(
current: CurrentContext,
problem: Option<&str>,
) -> anyhow::Result<ProblemContext> {
match current {
CurrentContext::ProblemDir(ctx) => match problem {
Some(_) => anyhow::bail!(
"Cannot specify a problem from a problem directory. Move to the contest directory."
),
None => Ok(ctx),
},
CurrentContext::ContestDir(ctx) => match problem {
Some(p) => detect_problem_dir_from(&ctx.contest_dir.join(p.to_lowercase()))
.with_context(|| format!("Problem '{}' not found", p)),
None => anyhow::bail!("Specify a problem, or run from a problem directory."),
},
CurrentContext::Outside => {
anyhow::bail!("Run this command from a problem or contest directory.")
}
}
}
pub fn detect_problem_dir() -> anyhow::Result<ProblemContext> {
let cwd = std::env::current_dir().context("Failed to get current directory")?;
detect_problem_dir_from(&cwd)
}
pub fn detect_problem_dir_from(dir: &std::path::Path) -> anyhow::Result<ProblemContext> {
let cargo_toml_path = dir.join("Cargo.toml");
let content = std::fs::read_to_string(&cargo_toml_path)
.with_context(|| format!("No Cargo.toml found in {}", dir.display()))?;
let doc: toml::Value =
toml::from_str(&content).context("Failed to parse Cargo.toml")?;
let problem_url = doc
.get("package")
.and_then(|p| p.get("metadata"))
.and_then(|m| m.get("acr"))
.and_then(|a| a.get("problem_url"))
.and_then(|u| u.as_str())
.ok_or_else(|| {
anyhow::anyhow!(
"Not a problem directory: [package.metadata.acr] not found in {}",
cargo_toml_path.display()
)
})?
.to_string();
let parts: Vec<&str> = problem_url.split('/').collect();
let contest_id = parts
.get(4)
.ok_or_else(|| anyhow::anyhow!("Invalid problem_url: {}", problem_url))?
.to_string();
let task_screen_name = parts
.get(6)
.ok_or_else(|| anyhow::anyhow!("Invalid problem_url: {}", problem_url))?
.to_string();
let problem_alphabet = dir
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("")
.to_string();
Ok(ProblemContext {
contest_id,
problem_alphabet,
task_screen_name,
problem_dir: dir.to_path_buf(),
problem_url,
})
}
pub fn list_contest_problems(contest_dir: &std::path::Path) -> anyhow::Result<Vec<ProblemContext>> {
let mut problems = Vec::new();
for entry in std::fs::read_dir(contest_dir)
.with_context(|| format!("Failed to read directory: {}", contest_dir.display()))?
{
let path = entry?.path();
if path.is_dir()
&& let Ok(ctx) = detect_problem_dir_from(&path)
{
problems.push(ctx);
}
}
problems.sort_by(|a, b| a.problem_alphabet.cmp(&b.problem_alphabet));
Ok(problems)
}
pub fn find_contest_dir_by_id(contest_id: &str) -> anyhow::Result<ContestContext> {
let cwd = std::env::current_dir().context("Failed to get current directory")?;
let candidate = cwd.join(contest_id);
if !candidate.exists() {
anyhow::bail!("Contest '{}' not found", contest_id);
}
let cargo_toml = candidate.join("Cargo.toml");
let content = std::fs::read_to_string(&cargo_toml)
.with_context(|| format!("No Cargo.toml found in {}", candidate.display()))?;
let doc: toml::Value = toml::from_str(&content).context("Failed to parse Cargo.toml")?;
if doc.get("workspace").is_none() {
anyhow::bail!(
"{} is not a contest workspace (no [workspace] in Cargo.toml)",
candidate.display()
);
}
Ok(ContestContext {
contest_id: contest_id.to_string(),
contest_dir: candidate,
})
}
pub fn detect_contest_dir() -> anyhow::Result<ContestContext> {
let cwd = std::env::current_dir().context("Failed to get current directory")?;
detect_contest_dir_from(&cwd)
}
pub fn detect_contest_dir_from(dir: &std::path::Path) -> anyhow::Result<ContestContext> {
for candidate in [dir, dir.parent().unwrap_or(dir)] {
let cargo_toml = candidate.join("Cargo.toml");
if let Ok(content) = std::fs::read_to_string(&cargo_toml)
&& let Ok(doc) = toml::from_str::<toml::Value>(&content)
&& doc.get("workspace").is_some()
{
let contest_id = candidate
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("")
.to_string();
return Ok(ContestContext {
contest_id,
contest_dir: candidate.to_path_buf(),
});
}
}
Err(anyhow::anyhow!(
"Not in a contest workspace directory. Run this command from a contest or problem directory."
))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_detect_problem_dir() {
let dir = tempfile::tempdir().unwrap();
let problem_dir = dir.path().join("abc001").join("a");
std::fs::create_dir_all(&problem_dir).unwrap();
std::fs::write(
problem_dir.join("Cargo.toml"),
r#"[package]
name = "abc001-a"
version = "0.1.0"
edition = "2021"
[package.metadata.acr]
problem_url = "https://atcoder.jp/contests/abc001/tasks/abc001_a"
"#,
)
.unwrap();
let ctx = detect_problem_dir_from(&problem_dir).unwrap();
assert_eq!(ctx.contest_id, "abc001");
assert_eq!(ctx.task_screen_name, "abc001_a");
assert_eq!(ctx.problem_alphabet, "a");
assert_eq!(
ctx.problem_url,
"https://atcoder.jp/contests/abc001/tasks/abc001_a"
);
}
#[test]
fn test_detect_problem_dir_not_a_problem() {
let dir = tempfile::tempdir().unwrap();
std::fs::write(
dir.path().join("Cargo.toml"),
"[package]\nname = \"test\"\n",
)
.unwrap();
assert!(detect_problem_dir_from(dir.path()).is_err());
}
#[test]
fn test_detect_contest_dir_from_workspace() {
let dir = tempfile::tempdir().unwrap();
let ws = dir.path().join("abc001");
std::fs::create_dir_all(&ws).unwrap();
std::fs::write(
ws.join("Cargo.toml"),
"[workspace]\nmembers = [\"a\"]\nresolver = \"2\"\n",
)
.unwrap();
let ctx = detect_contest_dir_from(&ws).unwrap();
assert_eq!(ctx.contest_dir, ws);
assert_eq!(ctx.contest_id, "abc001");
}
#[test]
fn test_detect_contest_dir_from_problem_dir() {
let dir = tempfile::tempdir().unwrap();
let ws = dir.path().join("abc001");
let problem = ws.join("a");
std::fs::create_dir_all(&problem).unwrap();
std::fs::write(
ws.join("Cargo.toml"),
"[workspace]\nmembers = [\"a\"]\nresolver = \"2\"\n",
)
.unwrap();
let ctx = detect_contest_dir_from(&problem).unwrap();
assert_eq!(ctx.contest_dir, ws);
assert_eq!(ctx.contest_id, "abc001");
}
}