use crate::shell::Shell;
use anyhow::{bail, Context as _};
use itertools::Itertools as _;
use serde::{
de::{DeserializeOwned, Error as _},
Deserialize, Deserializer,
};
use std::{env, ffi::OsStr, path::Path};
use url::Url;
pub(crate) fn get_problem(
url: &Url,
system: bool,
cwd: &Path,
shell: &mut Shell,
) -> anyhow::Result<Problem> {
let args = &mut vec!["get-problem", url.as_ref()];
if system {
args.push("--system".as_ref());
}
call(args, cwd, shell)
}
pub(crate) fn get_contest(url: &Url, cwd: &Path, shell: &mut Shell) -> anyhow::Result<Vec<Url>> {
let Contest { problems } = call(&["get-contest", url.as_ref()], cwd, shell)?;
return Ok(problems
.into_iter()
.map(|ContestProblem { url }| url)
.collect());
#[derive(Deserialize)]
struct Contest {
problems: Vec<ContestProblem>,
}
#[derive(Deserialize)]
struct ContestProblem {
url: Url,
}
}
pub(crate) fn guess_language_id(
url: &Url,
file: &Path,
cwd: &Path,
shell: &mut Shell,
) -> anyhow::Result<String> {
return call(
&[
OsStr::new("guess-language-id"),
url.as_str().as_ref(),
"--file".as_ref(),
file.as_ref(),
],
cwd,
shell,
)
.map(|GuessLanguageId { id }| id);
#[derive(Deserialize)]
struct GuessLanguageId {
id: String,
}
}
pub(crate) fn submit_code(
url: &Url,
file: &Path,
language: &str,
cwd: &Path,
shell: &mut Shell,
) -> anyhow::Result<Url> {
return call(
&[
"submit-code".as_ref(),
url.as_str().as_ref(),
"--file".as_ref(),
file.as_os_str(),
"--language".as_ref(),
language.as_ref(),
],
cwd,
shell,
)
.map(|SubmitCode { url }| url);
#[derive(Deserialize)]
struct SubmitCode {
url: Url,
}
}
fn call<T: DeserializeOwned, S: AsRef<OsStr>>(
args: &[S],
cwd: &Path,
shell: &mut Shell,
) -> anyhow::Result<T> {
let oj_api_exe = which::which_in("oj-api", env::var_os("PATH"), cwd)
.with_context(|| "`oj-api` not found")?;
let output = crate::process::process(oj_api_exe)
.args(args)
.cwd(cwd)
.display_cwd()
.read_with_shell_status(shell)?;
let Outcome { result, messages } = serde_json::from_str(&output)
.with_context(|| "could not parse the output from `oj-api`")?;
return if let Ok(result) = result {
for message in messages {
shell.warn(format!("oj-api: {}", message))?;
}
Ok(result)
} else {
bail!(
"`oj-api` returned error:\n{}",
messages.iter().map(|s| format!("- {}\n", s)).join(""),
);
};
struct Outcome<T> {
result: Result<T, ()>,
messages: Vec<String>,
}
impl<'de, T: DeserializeOwned> Deserialize<'de> for Outcome<T> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let Repr {
status,
messages,
result,
} = Repr::deserialize(deserializer)?;
return match &*status {
"ok" => Ok(Self {
result: Ok(result),
messages,
}),
"error" => Ok(Self {
result: Err(()),
messages,
}),
status => Err(D::Error::custom(format!(
"expected \"ok\" or \"error\", got {:?}",
status,
))),
};
#[derive(Deserialize)]
struct Repr<T> {
status: String,
messages: Vec<String>,
result: T,
}
}
}
}
#[derive(Debug, Deserialize)]
pub(crate) struct Problem {
pub(crate) url: Url,
pub(crate) name: Option<String>,
pub(crate) context: ProblemContext,
pub(crate) time_limit: Option<u64>,
pub(crate) tests: Vec<ProblemTest>,
}
#[derive(Debug, Deserialize)]
pub(crate) struct ProblemContext {
pub(crate) contest: Option<ProblemContextContest>,
pub(crate) alphabet: Option<String>,
}
#[derive(Debug, Deserialize)]
pub(crate) struct ProblemContextContest {
pub(crate) url: Option<Url>,
pub(crate) name: Option<String>,
}
#[derive(Debug, Deserialize)]
pub(crate) struct ProblemTest {
pub(crate) name: Option<String>,
pub(crate) input: String,
pub(crate) output: String,
}