#![warn(missing_docs)]
#![warn(clippy::missing_docs_in_private_items)]
#![feature(iterator_try_collect)]
#![feature(stmt_expr_attributes)]
#![feature(let_chains)]
#![feature(iter_collect_into)]
pub mod constants;
pub mod grade;
pub mod health;
pub mod java;
pub mod parsers;
pub mod util;
pub mod vscode;
use anyhow::{
Context,
Result,
};
use constants::{
BUILD_DIR,
COURSE,
LIB_DIR,
POSTGREST_CLIENT,
ROOT_DIR,
RUNTIME,
SCRIPT_AST,
TERM,
};
use grade::*;
use java::{
File,
FileType,
Parser,
Project,
};
use rhai::{
Engine,
EvalAltResult,
};
use umm_derive::generate_rhai_variant;
use util::{
use_active_retrieval,
use_heuristic_retrieval,
};
type Dict = std::collections::HashMap<String, String>;
pub fn create_engine() -> Engine {
let mut engine = Engine::new();
engine
.register_type_with_name::<FileType>("JavaFileType")
.build_type::<DocsGrader>()
.build_type::<ByUnitTestGrader>()
.build_type::<UnitTestGrader>()
.build_type::<ByHiddenTestGrader>()
.build_type::<DiffGrader>()
.build_type::<Grade>()
.build_type::<GradeResult>()
.build_type::<Parser>()
.build_type::<File>()
.build_type::<Query>()
.build_type::<QueryGrader>()
.build_type::<Project>()
.register_fn("clean", clean_script)
.register_fn("show_results", show_result_script)
.register_fn("generate_single_feedback", generate_single_feedback_script)
.register_fn("generate_feedback", generate_feedback_script)
.register_fn("use_active_retrieval", use_active_retrieval)
.register_fn("use_heuristic_retrieval", use_heuristic_retrieval);
engine
}
pub fn grade(name_or_path: &str) -> Result<()> {
let engine = create_engine();
let script = match std::fs::read_to_string(name_or_path) {
Ok(s) => s,
Err(_) => {
let assignment_name = name_or_path.to_string().replace(['\"', '\\'], "");
let rt = RUNTIME.handle().clone();
let resp = rt.block_on(async {
POSTGREST_CLIENT
.from("grading_scripts")
.eq("course", COURSE)
.eq("term", TERM)
.eq("assignment", &assignment_name)
.select("url")
.single()
.execute()
.await?
.text()
.await
.context(format!(
"Could not get grading script for {assignment_name}"
))
});
let resp: serde_json::Value = serde_json::from_str(resp?.as_str())?;
let resp = resp.as_object().unwrap();
if let Some(message) = resp.get("message") {
anyhow::bail!("Error for {assignment_name}: {message}");
}
let script_url = resp.get("url").unwrap().as_str().unwrap();
reqwest::blocking::get(script_url)
.context(format!("Cannot get url: {script_url}"))?
.text()
.context(format!(
"Could not parse the response from {script_url} to text."
))?
}
};
let ast = engine.compile(script)?;
{
let ast = std::sync::Arc::clone(&SCRIPT_AST);
let mut ast = ast.lock().unwrap();
*ast = ast.clone();
}
engine.run_ast(&ast)?;
Ok(())
}
#[generate_rhai_variant(Fallible)]
pub fn clean() -> Result<()> {
if BUILD_DIR.as_path().exists() {
std::fs::remove_dir_all(BUILD_DIR.as_path())
.with_context(|| format!("Could not delete {}", BUILD_DIR.display()))?;
}
if LIB_DIR.as_path().exists() {
std::fs::remove_dir_all(LIB_DIR.as_path())
.with_context(|| format!("Could not delete {}", LIB_DIR.display()))?;
}
if ROOT_DIR.join(".vscode/settings.json").as_path().exists() {
std::fs::remove_file(ROOT_DIR.join(".vscode/settings.json").as_path()).with_context(
|| {
format!(
"Could not delete {}",
ROOT_DIR.join(".vscode/settings.json").display()
)
},
)?;
}
if ROOT_DIR.join(".vscode/tasks.json").as_path().exists() {
std::fs::remove_file(ROOT_DIR.join(".vscode/tasks.json").as_path()).with_context(|| {
format!(
"Could not delete {}",
ROOT_DIR.join(".vscode/tasks.json").display()
)
})?;
}
Ok(())
}