mod program;
pub(crate) mod sample;
mod submit;
pub(crate) use crate::application::program::RunReport;
use crate::application::program::run_program;
use crate::application::sample::{SampleTestReport, run_sample_tests_selected};
use crate::application::submit::{PreparedSolution, prepare_solution, submit_prepared_solution};
use crate::client::AtCoderClient;
use crate::client::auth::{prompt_revel_session, verify_current_session};
use crate::client::cookie::Cookie;
use crate::validation::validate_atcoder_identifier;
use crate::workspace::command::{CommandInput, SystemCommandRunner};
use crate::workspace::contest::save_contest_to;
use crate::workspace::problem::ProblemWorkspace;
use crate::workspace::template::{
NewTemplate, TemplateRegistry, create_template, load_template_config_from,
};
use anyhow::{Context, Result};
use std::num::NonZeroUsize;
use std::path::PathBuf;
pub(crate) enum AppEvent {
FetchingContest(String),
SavingContest(String),
Testing,
TestSuccessful,
Submitting,
SubmitSuccessful,
LoggingIn,
LoggingOut,
}
pub(crate) enum LoginOutcome {
LoggedIn { username: String },
AlreadyLoggedIn { username: String },
ExistingSessionInvalid,
}
pub(crate) enum SessionStatus {
LoggedIn { username: String },
Invalid,
LoggedOut,
}
pub(crate) struct DownloadOutcome {
pub(crate) path: PathBuf,
}
pub(crate) struct SubmitOutcome {
pub(crate) submission_url: String,
}
pub(crate) struct TemplateCreated {
pub(crate) path: PathBuf,
}
pub(crate) struct TemplateSummary {
pub(crate) name: String,
pub(crate) is_default: bool,
}
pub(crate) struct TemplateDetails {
pub(crate) name: String,
pub(crate) path: PathBuf,
pub(crate) submit_file: PathBuf,
pub(crate) language_id: u16,
pub(crate) exec_command: Vec<String>,
pub(crate) compile_command: Option<Vec<String>>,
pub(crate) pre_submit: Option<Vec<String>>,
pub(crate) is_default: bool,
}
pub(crate) struct Application {
start_path: PathBuf,
}
impl Application {
pub(crate) fn new(start_path: PathBuf) -> Self {
Self { start_path }
}
pub(crate) async fn download<F>(
&self,
contest_id: &str,
template_name: Option<&str>,
no_template: bool,
mut event: F,
) -> Result<DownloadOutcome>
where
F: FnMut(AppEvent),
{
validate_atcoder_identifier(contest_id, "contest ID")?;
event(AppEvent::FetchingContest(contest_id.to_string()));
let client = AtCoderClient::from_stored_session()?;
let contest = client.fetch_contest(contest_id).await?;
event(AppEvent::SavingContest(contest_id.to_string()));
let registry = (!no_template).then(TemplateRegistry::load).transpose()?;
let template = registry
.as_ref()
.map(|registry| registry.select(template_name))
.transpose()?
.flatten();
let path = save_contest_to(&self.start_path, &contest, template)?;
Ok(DownloadOutcome { path })
}
pub(crate) async fn test(
&self,
selected_case: Option<NonZeroUsize>,
) -> Result<SampleTestReport> {
let workspace = ProblemWorkspace::discover_from(&self.start_path)?;
run_sample_tests_selected(&workspace, &SystemCommandRunner, selected_case).await
}
pub(crate) async fn run(&self, input: Option<PathBuf>) -> Result<RunReport> {
let workspace = ProblemWorkspace::discover_from(&self.start_path)?;
let input = input
.map(|path| CommandInput::File(self.start_path.join(path)))
.unwrap_or(CommandInput::Inherit);
run_program(&workspace, &SystemCommandRunner, input).await
}
pub(crate) async fn prepare(&self, no_test: bool) -> Result<PreparedSolution> {
let workspace = ProblemWorkspace::discover_from(&self.start_path)?;
prepare_solution(&workspace, &SystemCommandRunner, no_test).await
}
pub(crate) async fn submit<F>(&self, no_test: bool, mut event: F) -> Result<SubmitOutcome>
where
F: FnMut(AppEvent),
{
event(AppEvent::Testing);
let workspace = ProblemWorkspace::discover_from(&self.start_path)?;
let runner = SystemCommandRunner;
let source = prepare_solution(&workspace, &runner, no_test).await?;
event(AppEvent::TestSuccessful);
event(AppEvent::Submitting);
let client = AtCoderClient::from_stored_session()?;
let submission_url = submit_prepared_solution(&workspace, &client, source).await?;
event(AppEvent::SubmitSuccessful);
Ok(SubmitOutcome { submission_url })
}
pub(crate) async fn login<F>(&self, overwrite: bool, mut event: F) -> Result<LoginOutcome>
where
F: FnMut(AppEvent),
{
if !overwrite && let Some(result) = verify_current_session().await {
return Ok(match result {
Ok(username) => LoginOutcome::AlreadyLoggedIn { username },
Err(_) => LoginOutcome::ExistingSessionInvalid,
});
}
let revel_session = prompt_revel_session()?;
event(AppEvent::LoggingIn);
let client = AtCoderClient::new(Some(&revel_session))?;
let username = client.validate_session().await?;
Cookie { revel_session }
.store()
.context("Failed to store REVEL_SESSION.")?;
Ok(LoginOutcome::LoggedIn { username })
}
pub(crate) fn logout<F>(&self, mut event: F) -> Result<SessionStatus>
where
F: FnMut(AppEvent),
{
event(AppEvent::LoggingOut);
Cookie::set_default().context("Failed to clear REVEL_SESSION.")?;
Ok(SessionStatus::LoggedOut)
}
pub(crate) async fn whoami(&self) -> Result<SessionStatus> {
Ok(match verify_current_session().await {
Some(Ok(username)) => SessionStatus::LoggedIn { username },
Some(Err(_)) => SessionStatus::Invalid,
None => SessionStatus::LoggedOut,
})
}
pub(crate) fn create_template(&self, request: NewTemplate<'_>) -> Result<TemplateCreated> {
Ok(TemplateCreated {
path: create_template(request)?,
})
}
pub(crate) fn list_templates(&self) -> Result<Vec<TemplateSummary>> {
let registry = TemplateRegistry::load()?;
let mut templates = registry
.templates()
.map(|template| TemplateSummary {
name: template.name.clone(),
is_default: template.is_default,
})
.collect::<Vec<_>>();
templates.sort_by(|left, right| left.name.cmp(&right.name));
Ok(templates)
}
pub(crate) fn show_template(&self, name: &str) -> Result<TemplateDetails> {
let registry = TemplateRegistry::load()?;
let template = registry.get(name)?;
let config_path = template.template_path.join("template.json");
let config = load_template_config_from(&config_path)?;
Ok(TemplateDetails {
name: template.name.clone(),
path: template.template_path.clone(),
submit_file: config.submit_file,
language_id: config.language_id,
exec_command: config.exec_command.words(),
compile_command: config.compile_command.map(|command| command.words()),
pre_submit: config.pre_submit.map(|command| command.words()),
is_default: template.is_default,
})
}
pub(crate) fn set_default_template(&self, name: &str) -> Result<()> {
TemplateRegistry::set_default(name)
}
}