use std::path::Path;
use regex::Regex;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LanguageInfo {
pub code: u8,
pub name: &'static str,
}
pub fn detect_language(file_path: &str) -> Result<LanguageInfo, String> {
let path = Path::new(file_path);
let ext = path
.extension()
.and_then(|e| e.to_str())
.ok_or_else(|| format!("Cannot determine file extension for `{file_path}`"))?;
match ext {
"c" => Ok(LanguageInfo {
code: 1,
name: "ANSI C",
}),
"java" => Ok(LanguageInfo {
code: 2,
name: "Java",
}),
"cpp" | "cc" | "cxx" => Ok(LanguageInfo {
code: 5,
name: "C++11",
}),
"pas" => Ok(LanguageInfo {
code: 4,
name: "Pascal",
}),
"py" | "py3" => Ok(LanguageInfo {
code: 6,
name: "Python 3",
}),
_ => {
let raw_ext = path
.file_name()
.and_then(|n| n.to_str())
.and_then(|n| n.rsplit('.').next());
match raw_ext {
Some("C") => Ok(LanguageInfo {
code: 5,
name: "C++11",
}),
_ => Err(format!(
"Unsupported file extension `.{ext}` for `{file_path}`"
)),
}
}
}
}
pub fn extract_problem_id(file_path: &str) -> Option<u32> {
let path = Path::new(file_path);
let stem = path.file_stem()?.to_str()?;
let re = Regex::new(r"(\d+)").ok()?;
let m = re.find(stem)?;
m.as_str().parse::<u32>().ok()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn detect_c() {
let lang = detect_language("main.c").unwrap();
assert_eq!(lang.code, 1);
assert_eq!(lang.name, "ANSI C");
}
#[test]
fn detect_java() {
let lang = detect_language("Solution.java").unwrap();
assert_eq!(lang.code, 2);
assert_eq!(lang.name, "Java");
}
#[test]
fn detect_cpp() {
let lang = detect_language("solution.cpp").unwrap();
assert_eq!(lang.code, 5);
assert_eq!(lang.name, "C++11");
}
#[test]
fn detect_cc() {
let lang = detect_language("main.cc").unwrap();
assert_eq!(lang.code, 5);
assert_eq!(lang.name, "C++11");
}
#[test]
fn detect_cxx() {
let lang = detect_language("algo.cxx").unwrap();
assert_eq!(lang.code, 5);
assert_eq!(lang.name, "C++11");
}
#[test]
fn detect_uppercase_c() {
let lang = detect_language("solution.C").unwrap();
assert_eq!(lang.code, 5);
assert_eq!(lang.name, "C++11");
}
#[test]
fn detect_pascal() {
let lang = detect_language("program.pas").unwrap();
assert_eq!(lang.code, 4);
assert_eq!(lang.name, "Pascal");
}
#[test]
fn detect_python() {
let lang = detect_language("solve.py").unwrap();
assert_eq!(lang.code, 6);
assert_eq!(lang.name, "Python 3");
}
#[test]
fn detect_python3() {
let lang = detect_language("solve.py3").unwrap();
assert_eq!(lang.code, 6);
assert_eq!(lang.name, "Python 3");
}
#[test]
fn detect_unsupported_extension() {
let err = detect_language("solution.rs").unwrap_err();
assert!(err.contains(".rs"), "error should mention the extension");
}
#[test]
fn detect_no_extension() {
let err = detect_language("solution").unwrap_err();
assert!(
err.contains("Cannot determine"),
"should report missing extension"
);
}
#[test]
fn detect_path_with_directories() {
let lang = detect_language("src/problems/100.cpp").unwrap();
assert_eq!(lang.code, 5);
}
#[test]
fn extract_bare_number() {
assert_eq!(extract_problem_id("100.cpp"), Some(100));
}
#[test]
fn extract_uva_underscore_prefix() {
assert_eq!(extract_problem_id("uva_1234.py"), Some(1234));
}
#[test]
fn extract_uva_case_insensitive_prefix() {
assert_eq!(extract_problem_id("UVa100.java"), Some(100));
}
#[test]
fn extract_problem_word_prefix() {
assert_eq!(extract_problem_id("problem7.cpp"), Some(7));
}
#[test]
fn extract_no_number() {
assert_eq!(extract_problem_id("solution.cpp"), None);
}
#[test]
fn extract_only_digits_is_number() {
assert_eq!(extract_problem_id("1000"), Some(1000));
}
#[test]
fn extract_picks_first_number() {
assert_eq!(extract_problem_id("123_456.cpp"), Some(123));
}
#[test]
fn extract_with_path() {
assert_eq!(extract_problem_id("problems/500.py"), Some(500));
}
#[test]
fn extract_large_id() {
assert_eq!(extract_problem_id("uva_10000.cpp"), Some(10000));
}
#[test]
fn extract_empty_stem() {
assert_eq!(extract_problem_id(".cpp"), None);
}
}