1use unicode_width::UnicodeWidthStr;
3use unicode_width::UnicodeWidthChar;
4use super::schemas::{problems, tags};
5use crate::helper::HTML;
6use colored::Colorize;
7use serde::{Deserialize, Serialize};
8use serde_json::Number;
9
10#[derive(Clone, Insertable, Queryable, Serialize, Debug)]
12#[diesel(table_name = tags)]
13pub struct Tag {
14 pub tag: String,
15 pub refs: String,
16}
17
18#[derive(AsChangeset, Clone, Identifiable, Insertable, Queryable, Serialize, Debug)]
20#[diesel(table_name = problems)]
21pub struct Problem {
22 pub category: String,
23 pub fid: i32,
24 pub id: i32,
25 pub level: i32,
26 pub locked: bool,
27 pub name: String,
28 pub percent: f32,
29 pub slug: String,
30 pub starred: bool,
31 pub status: String,
32 pub desc: String,
33}
34
35impl Problem {
36 fn display_level(&self) -> &str {
37 match self.level {
38 1 => "Easy",
39 2 => "Medium",
40 3 => "Hard",
41 _ => "Unknown",
42 }
43 }
44
45 pub fn desc_comment(&self, conf: &Config) -> String {
46 let mut res = String::new();
47 let comment_leading = &conf.code.comment_leading;
48 res += format!("{} Category: {}\n", comment_leading, self.category).as_str();
49 res += format!("{} Level: {}\n", comment_leading, self.display_level(),).as_str();
50 res += format!("{} Percent: {}%\n\n", comment_leading, self.percent).as_str();
51
52 res + "\n"
53 }
54}
55
56static DONE: &str = " ✔";
57static ETC: &str = "...";
58static LOCK: &str = "🔒";
59static NDONE: &str = " ✘";
60static SPACE: &str = " ";
61impl std::fmt::Display for Problem {
62 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63 let space_2 = SPACE.repeat(2);
64 let mut lock = space_2.as_str();
65 let mut done = space_2.normal();
66 let mut id = "".to_string();
67 let mut name = "".to_string();
68 let mut level = "".normal();
69
70 if self.locked {
71 lock = LOCK
72 };
73 if self.status == "ac" {
74 done = DONE.green().bold();
75 } else if self.status == "notac" {
76 done = NDONE.green().bold();
77 }
78
79 match self.fid.to_string().len() {
80 1 => {
81 id.push_str(&SPACE.repeat(2));
82 id.push_str(&self.fid.to_string());
83 id.push_str(SPACE);
84 }
85 2 => {
86 id.push_str(SPACE);
87 id.push_str(&self.fid.to_string());
88 id.push_str(SPACE);
89 }
90 3 => {
91 id.push_str(SPACE);
92 id.push_str(&self.fid.to_string());
93 }
94 4 => {
95 id.push_str(&self.fid.to_string());
96 }
97 _ => {
98 id.push_str(&space_2);
99 id.push_str(&space_2);
100 }
101 }
102
103 let name_width = UnicodeWidthStr::width(self.name.as_str());
104 let target_width = 60;
105 if name_width <= target_width {
106 name.push_str(&self.name);
107 name.push_str(&SPACE.repeat(target_width - name_width));
108 } else {
109 let mut truncated = String::new();
111 let mut current_width = 0;
112 for c in self.name.chars() {
113 let char_width = UnicodeWidthChar::width(c).unwrap_or(0);
114 if current_width + char_width > target_width - 3 {
115 break;
116 }
117 truncated.push(c);
118 current_width += char_width;
119 }
120 truncated.push_str(ETC); let truncated_width = UnicodeWidthStr::width(truncated.as_str());
122 truncated.push_str(&SPACE.repeat(target_width - truncated_width));
123 name = truncated;
124 }
125
126 level = match self.level {
127 1 => "Easy ".bright_green(),
128 2 => "Medium".bright_yellow(),
129 3 => "Hard ".bright_red(),
130 _ => level,
131 };
132
133 let mut pct = self.percent.to_string();
134 if pct.len() < 5 {
135 pct.push_str(&"0".repeat(5 - pct.len()));
136 }
137 write!(
138 f,
139 " {} {} [{}] {} {} ({} %)",
140 lock,
141 done,
142 id,
143 name,
144 level,
145 &pct[..5]
146 )
147 }
148}
149
150#[derive(Debug, Default, Serialize, Deserialize)]
152pub struct Question {
153 pub content: String,
154 pub stats: Stats,
155 pub defs: CodeDefintion,
156 pub case: String,
157 pub all_cases: String,
158 pub metadata: MetaData,
159 pub test: bool,
160 pub t_content: String,
161}
162
163impl Question {
164 pub fn desc(&self) -> String {
165 self.content.render()
166 }
167
168 pub fn desc_comment(&self, conf: &Config) -> String {
169 let desc = self.content.render();
170
171 let mut res = desc.lines().fold("\n".to_string(), |acc, e| {
172 acc + "" + conf.code.comment_leading.as_str() + " " + e + "\n"
173 });
174 res += " \n";
175
176 res
177 }
178}
179
180use question::*;
181mod question {
183 use serde::{Deserialize, Serialize};
184
185 #[derive(Debug, Default, Serialize, Deserialize)]
187 pub struct CodeDefintion(pub Vec<CodeDefintionInner>);
188
189 #[derive(Debug, Default, Serialize, Deserialize)]
191 pub struct CodeDefintionInner {
192 pub value: String,
193 pub text: String,
194 #[serde(alias = "defaultCode")]
195 pub code: String,
196 }
197
198 #[derive(Debug, Default, Serialize, Deserialize)]
200 pub struct Stats {
201 #[serde(alias = "totalAccepted")]
202 tac: String,
203 #[serde(alias = "totalSubmission")]
204 tsm: String,
205 #[serde(alias = "totalAcceptedRaw")]
206 tacr: i32,
207 #[serde(alias = "totalSubmissionRaw")]
208 tsmr: i32,
209 #[serde(alias = "acRate")]
210 rate: String,
211 }
212
213 #[derive(Debug, Default, Serialize, Deserialize)]
215 pub struct MetaData {
216 pub name: Option<String>,
217 pub params: Option<Vec<Param>>,
218 pub r#return: Return,
219 }
220
221 #[derive(Debug, Default, Serialize, Deserialize)]
223 pub struct Param {
224 pub name: String,
225 pub r#type: String,
226 }
227
228 #[derive(Debug, Default, Serialize, Deserialize)]
230 pub struct Return {
231 pub r#type: String,
232 }
233}
234
235#[derive(Debug, Deserialize)]
237pub struct RunCode {
238 #[serde(default)]
239 pub interpret_id: String,
240 #[serde(default)]
241 pub test_case: String,
242 #[serde(default)]
243 pub submission_id: i64,
244}
245
246use super::parser::ssr;
247use crate::cache::Run;
248
249#[derive(Default, Debug, Deserialize)]
251pub struct VerifyResult {
252 pub state: String,
253 #[serde(skip)]
254 pub name: String,
255 #[serde(skip)]
256 pub data_input: String,
257 #[serde(skip)]
258 pub result_type: Run,
259 #[serde(default)]
262 pretty_lang: String,
263 #[serde(default)]
268 correct_answer: bool,
269 #[serde(default, deserialize_with = "ssr")]
270 code_answer: Vec<String>,
271 #[serde(default, deserialize_with = "ssr")]
272 code_output: Vec<String>,
273 #[serde(default, deserialize_with = "ssr")]
274 expected_output: Vec<String>,
275 #[serde(default, deserialize_with = "ssr")]
276 std_output: Vec<String>,
277
278 #[serde(flatten, default)]
282 status: VerifyStatus,
283 #[serde(flatten, default)]
284 analyse: Analyse,
285 #[serde(flatten, default)]
286 expected: Expected,
287 #[serde(flatten, default)]
288 error: CompileError,
289 #[serde(flatten, default)]
290 submit: Submit,
291}
292
293impl std::fmt::Display for VerifyResult {
294 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
295 let ca = match &self.code_answer.len() {
296 1 => self.code_answer[0].to_string(),
297 _ => self.code_answer.join("↩ "),
298 };
299
300 let eca = match &self.expected.expected_code_answer.len() {
301 1 => self.expected.expected_code_answer[0].to_string(),
302 _ => self.expected.expected_code_answer.join("↩ "),
303 };
304
305 debug!("{:#?}", &self);
306
307 match &self.status.status_code {
308 10 => {
309 if matches!(self.result_type, Run::Test) && self.correct_answer {
310 write!(
312 f,
313 "\n{}{}{}\n{}{}{}{}{}{}\n",
314 &self.status.status_msg.green().bold(),
315 &"Runtime: ".before_spaces(7).dimmed(),
316 &self.status.status_runtime.dimmed(),
317 &"\nYour input:".after_spaces(4),
318 &self.data_input.replace('\n', "↩ "),
319 &"\nOutput:".after_spaces(8),
320 ca,
321 &"\nExpected:".after_spaces(6),
322 eca,
323 )?
324 } else if matches!(self.result_type, Run::Submit)
325 && !self.submit.compare_result.is_empty()
326 {
327 let cache = super::Cache::new().expect("cache gen failed");
332 cache
333 .update_after_ac(
334 self.submit
335 .question_id
336 .parse()
337 .expect("submit successfully, parse question_id to i32 failed"),
338 )
339 .expect("update ac to cache failed");
340
341 let rp = if let Some(n) = &self.analyse.runtime_percentile {
343 if n.is_f64() {
344 n.as_f64().unwrap_or(0.0) as i64
345 } else {
346 n.as_i64().unwrap_or(0)
347 }
348 } else {
349 0
350 };
351
352 let mp = if let Some(n) = &self.analyse.memory_percentile {
353 if n.is_f64() {
354 n.as_f64().unwrap_or(0.0) as i64
355 } else {
356 n.as_i64().unwrap_or(0)
357 }
358 } else {
359 0
360 };
361
362 write!(
363 f,
364 "\n{}{}{}\
365 , faster than \
366 {}{}\
367 of \
368 {} \
369 online submissions for \
370 {}.\n\n\
371 {}{}\
372 , less than \
373 {}{}\
374 of \
375 {} {}.\n\n",
376 "Success\n\n".green().bold(),
377 "Runtime: ".dimmed(),
378 &self.status.status_runtime.bold(),
379 rp.to_string().bold(),
380 "% ".bold(),
381 &self.pretty_lang,
382 &self.name,
383 "Memory Usage: ".dimmed(),
384 &self.status.status_memory.bold(),
385 mp.to_string().bold(),
386 "% ".bold(),
387 &self.pretty_lang,
388 &self.name,
389 )?
390 } else {
391 write!(
393 f,
394 "\n{}{}{}\n{}{}{}{}{}{}\n",
395 "Wrong Answer".red().bold(),
396 " Runtime: ".dimmed(),
397 &self.status.status_runtime.dimmed(),
398 &"\nYour input:".after_spaces(4),
399 &self.data_input.replace('\n', "↩ "),
400 &"\nOutput:".after_spaces(8),
401 ca,
402 &"\nExpected:".after_spaces(6),
403 eca,
404 )?
405 }
406 }
407 11 => write!(
409 f,
410 "\n{}\n\n{}{}\n{}{}\n{}{}{}{}{}{}\n",
411 &self.status.status_msg.red().bold(),
412 "Cases passed:".after_spaces(2).green(),
413 &self
414 .analyse
415 .total_correct
416 .as_ref()
417 .unwrap_or(&Number::from(0))
418 .to_string()
419 .green(),
420 &"Total cases:".after_spaces(3).yellow(),
421 &self
422 .analyse
423 .total_testcases
424 .as_ref()
425 .unwrap_or(&Number::from(0))
426 .to_string()
427 .bold()
428 .yellow(),
429 &"Last case:".after_spaces(5).dimmed(),
430 &self.submit.last_testcase.replace('\n', "↩ ").dimmed(),
431 &"\nOutput:".after_spaces(8),
432 self.code_output[0],
433 &"\nExpected:".after_spaces(6),
434 self.expected_output[0],
435 )?,
436 12 => write!(
438 f,
439 "\n{}\n\n{}{}\n",
440 &self.status.status_msg.yellow().bold(),
441 &"Last case:".after_spaces(5).dimmed(),
442 &self.data_input.replace('\n', "↩ "),
443 )?,
444 13 | 14 => write!(f, "\n{}\n", &self.status.status_msg.yellow().bold(),)?,
449 15 => write!(
451 f,
452 "\n{}\n{}\n'",
453 &self.status.status_msg.red().bold(),
454 &self.status.runtime_error
455 )?,
456 20 => write!(
458 f,
459 "\n{}:\n\n{}\n",
460 &self.status.status_msg.red().bold(),
461 &self.error.full_compile_error.dimmed()
462 )?,
463 _ => write!(
464 f,
465 "{}{}{}{}{}{}{}{}",
466 "\nUnknown Error...\n".red().bold(),
467 "\nBingo! Welcome to fix this! Pull your request at ".yellow(),
468 "https://github.com/clearloop/leetcode-cli/pulls"
469 .dimmed()
470 .underline(),
471 ", and this file is located at ".yellow(),
472 "leetcode-cli/src/cache/models.rs".dimmed().underline(),
473 " waiting for you! Yep, line ".yellow(),
474 "385".dimmed().underline(),
475 ".\n".yellow(),
476 )?,
477 };
478
479 match &self.result_type {
480 Run::Test => {
481 if !self.code_output.is_empty() {
482 write!(
483 f,
484 "{}{}",
485 &"Stdout:".after_spaces(8).purple(),
486 &self.code_output.join(&"\n".after_spaces(15))
487 )
488 } else {
489 write!(f, "")
490 }
491 }
492 _ => {
493 if !self.std_output.is_empty() {
494 write!(
495 f,
496 "{}{}",
497 &"Stdout:".after_spaces(8).purple(),
498 &self.std_output[0].replace('\n', &"\n".after_spaces(15))
499 )
500 } else {
501 write!(f, "")
502 }
503 }
504 }
505 }
506}
507
508use crate::Config;
509use verify::*;
510
511mod verify {
512 use super::super::parser::ssr;
513 use serde::Deserialize;
514 use serde_json::Number;
515
516 #[derive(Debug, Default, Deserialize)]
517 pub struct Submit {
518 #[serde(default)]
519 pub question_id: String,
520 #[serde(default)]
521 pub last_testcase: String,
522 #[serde(default)]
523 pub compare_result: String,
524 }
525
526 #[derive(Debug, Default, Deserialize)]
537 pub struct Analyse {
538 #[serde(default)]
539 pub total_correct: Option<Number>,
540 #[serde(default)]
541 pub total_testcases: Option<Number>,
542 #[serde(default)]
543 pub runtime_percentile: Option<Number>,
544 #[serde(default)]
545 pub memory_percentile: Option<Number>,
546 }
547
548 #[derive(Debug, Default, Deserialize)]
549 pub struct VerifyStatus {
550 #[serde(default)]
551 pub status_code: i32,
552 #[serde(default)]
553 pub status_msg: String,
554 #[serde(default)]
555 pub status_memory: String,
556 #[serde(default)]
557 pub status_runtime: String,
558 #[serde(default)]
559 pub runtime_error: String,
560 }
561
562 #[derive(Debug, Default, Deserialize)]
563 pub struct CompileError {
564 #[serde(default)]
567 pub full_compile_error: String,
568 }
569
570 #[derive(Debug, Default, Deserialize)]
571 pub struct Expected {
572 #[serde(default, deserialize_with = "ssr")]
589 pub expected_code_answer: Vec<String>,
590 }
591}
592
593trait Formatter {
595 fn after_spaces(&self, spaces: usize) -> String;
596 fn before_spaces(&self, spaces: usize) -> String;
597}
598
599impl Formatter for str {
600 fn after_spaces(&self, spaces: usize) -> String {
601 let mut r = String::new();
602 r.push_str(self);
603 r.push_str(&" ".repeat(spaces));
604 r
605 }
606
607 fn before_spaces(&self, spaces: usize) -> String {
608 let mut r = String::new();
609 r.push_str(&" ".repeat(spaces));
610 r.push_str(self);
611 r
612 }
613}