Skip to content

Commit

Permalink
feat(Tools): 🎉 Add basic quoj to mdoj command
Browse files Browse the repository at this point in the history
  • Loading branch information
KAIYOHUGO committed Aug 31, 2024
1 parent efc5563 commit 9596241
Show file tree
Hide file tree
Showing 8 changed files with 400 additions and 1 deletion.
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"Backend",
"Judger",
"Testsuit",
"Grpc"
"Grpc",
"Tools"
]
}
43 changes: 43 additions & 0 deletions tools/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
[package]
name = "tools"
version = "0.1.0"
edition = "2021"

[[bin]]
name = "mdoj"
path = "src/main.rs"

[dependencies]
anyhow = "1.0.86"
serde_json = "1.0.127"
zip = "2.2.0"


[dependencies.tonic]
workspace = true
features = ["transport", "codegen", "prost", "channel"]

[dependencies.grpc]
path = "../grpc"
features = ["backend", "client", "extra_trait", "transport"]

[dependencies.serde]
workspace = true
features = ["derive"]

[dependencies.tokio]
workspace = true
features = ["macros", "rt-multi-thread"]

[dependencies.reqwest]
version = "0.12.7"
features = ["json"]

[dependencies.clap]
version = "4.5.16"
features = ["derive"]

[dependencies.futures]
version = "0.3.30"
default-features = false
features = ["std"]
23 changes: 23 additions & 0 deletions tools/src/grpc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
pub use grpc::backend::*;
use tonic::{metadata::MetadataMap, IntoRequest, Request};

pub trait WithToken: Sized {
/// this will add token to request.
fn with_token(self, token: impl AsRef<str>) -> Request<Self>;
}

impl<T> WithToken for T
where
T: IntoRequest<T>,
{
fn with_token(self, token: impl AsRef<str>) -> Request<Self> {
let mut req = self.into_request();
let Ok(token) = token.as_ref().parse() else {
return req;
};
let mut metadata = MetadataMap::new();
metadata.insert("token", token);
*req.metadata_mut() = metadata;
req
}
}
21 changes: 21 additions & 0 deletions tools/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
mod grpc;
mod quoj;
mod quoj2mdoj;

use anyhow::Result;
use clap::Parser;

#[derive(Debug, Parser)]
enum Cli {
Quoj2mdoj(quoj2mdoj::Quoj2mdoj),
}

#[tokio::main]
async fn main() -> Result<()> {
let cli = Cli::parse();
match cli {
Cli::Quoj2mdoj(v) => quoj2mdoj::quoj2mdoj(v).await?,
};

Ok(())
}
42 changes: 42 additions & 0 deletions tools/src/quoj/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
pub mod problem;
pub mod testcases;

use anyhow::Result;
use reqwest::{
header::{self, HeaderMap},
Client, IntoUrl, Url,
};
use std::io::{Read, Seek};

#[derive(Debug, Clone)]
pub struct QuojClient {
base_url: Url,
client: Client,
}

impl QuojClient {
pub fn new(base_url: impl IntoUrl, session: String) -> Result<Self> {
let base_url = base_url.into_url()?;

let mut headers = HeaderMap::new();
headers.insert(header::COOKIE, format!("sessionid={session}").parse()?);

let client = reqwest::ClientBuilder::new()
.default_headers(headers)
.build()?;

Ok(Self { base_url, client })
}

pub async fn problem(&self, id: usize) -> Result<problem::ProblemData> {
problem::problem(&self.client, &self.base_url, id).await
}

pub async fn problems(&self) -> Result<Vec<problem::ProblemData>> {
problem::problems(&self.client, &self.base_url).await
}

pub async fn testcases(&self, id: u64) -> Result<testcases::Testcases<impl Read + Seek>> {
testcases::testcases(&self.client, &self.base_url, id).await
}
}
99 changes: 99 additions & 0 deletions tools/src/quoj/problem.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
use anyhow::Result;
use reqwest::{Client, Url};
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Problem {
pub data: ProblemData,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Problems {
pub data: ProblemsData,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProblemsData {
pub results: Vec<ProblemData>,
pub total: u64,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProblemData {
pub id: u64,
pub tags: Vec<String>,
pub title: String,
pub description: String,
pub input_description: String,
pub output_description: String,
pub samples: Vec<Sample>,
pub test_case_id: String,
pub test_case_score: Vec<TestCaseScore>,
pub hint: String,
pub languages: Vec<String>,
pub create_time: String,
pub last_update_time: Option<serde_json::Value>,
pub time_limit: u64,
pub memory_limit: u64,
pub io_mode: IoMode,
pub rule_type: String,
pub difficulty: Difficulty,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum Difficulty {
Low,
Mid,
High,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IoMode {
pub input: String,
pub output: String,
pub io_mode: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Sample {
pub input: String,
pub output: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TestCaseScore {
pub score: i64,
pub input_name: String,
pub output_name: String,
}

pub async fn problem(client: &Client, base_url: &Url, id: usize) -> Result<ProblemData> {
let problem: Problem = client
.get(base_url.join("admin/problem")?)
.query(&[("id", id)])
.send()
.await?
.json()
.await?;
Ok(problem.data)
}

pub async fn problems(client: &Client, base_url: &Url) -> Result<Vec<ProblemData>> {
const PAGE_SIZE: u64 = 250;
let mut ret = vec![];
for i in 0.. {
let problems: Problems = client
.get(base_url.join("admin/problem")?)
.query(&[("limit", PAGE_SIZE), ("offset", PAGE_SIZE * i)])
.send()
.await?
.json()
.await?;

ret.extend(problems.data.results);
if problems.data.total <= PAGE_SIZE * i {
break;
}
}
Ok(ret)
}
35 changes: 35 additions & 0 deletions tools/src/quoj/testcases.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
use std::io::{Cursor, Read, Seek};

use anyhow::Result;
use reqwest::{Client, Url};
use zip::ZipArchive;

pub async fn testcases(
client: &Client,
base_url: &Url,
id: u64,
) -> Result<Testcases<impl Read + Seek>> {
let bytes = Cursor::new(
client
.get(base_url.join("admin/test_case")?)
.query(&[("problem_id", id)])
.send()
.await?
.bytes()
.await?,
);
let testcases = ZipArchive::new(bytes)?;
Ok(Testcases(testcases))
}

pub struct Testcases<T: Read + Seek>(ZipArchive<T>);

impl<T: Read + Seek> Testcases<T> {
pub fn testcase(&mut self, name: impl AsRef<str>) -> Result<Vec<u8>> {
let mut buf = vec![];
let mut file = self.0.by_name(name.as_ref())?;
buf.reserve_exact(file.size() as usize);
file.read_to_end(&mut buf)?;
Ok(buf)
}
}
Loading

0 comments on commit 9596241

Please sign in to comment.