Skip to main content

leetcode_cli/cache/
models.rs

1//! Leetcode data models
2use super::schemas::{problems, tags};
3use crate::helper::HTML;
4use colored::Colorize;
5use serde::{Deserialize, Serialize};
6use serde_json::Number;
7use unicode_width::UnicodeWidthChar;
8use unicode_width::UnicodeWidthStr;
9
10/// Tag model
11#[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/// Problem model
19#[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            // truncate carefully to target width - 3 (because "..." will take some width)
110            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); // add "..."
121            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/// desc model
151#[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::*;
181/// deps of Question
182mod question {
183    use serde::{Deserialize, Serialize};
184
185    /// Code samples
186    #[derive(Debug, Default, Serialize, Deserialize)]
187    pub struct CodeDefintion(pub Vec<CodeDefintionInner>);
188
189    /// CodeDefinition Inner struct
190    #[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    /// Question status
199    #[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    /// Algorithm metadata
214    #[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    /// MetaData nested fields
222    #[derive(Debug, Default, Serialize, Deserialize)]
223    pub struct Param {
224        pub name: String,
225        pub r#type: String,
226    }
227
228    /// MetaData nested fields
229    #[derive(Debug, Default, Serialize, Deserialize)]
230    pub struct Return {
231        pub r#type: String,
232    }
233}
234
235/// run_code Result
236#[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/// verify result model
250#[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)]
260    // lang: String,
261    #[serde(default)]
262    pretty_lang: String,
263    // #[serde(default)]
264    // submission_id: String,
265    // #[serde(default)]
266    // run_success: bool,
267    #[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    // flatten
279    // #[serde(flatten, default)]
280    // info: VerifyInfo,
281    #[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                    // Pass Tests
311                    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                    // only Submit execute this branch
328                    // Submit Successfully
329                    // TODO: result should be all 1;
330                    // Lines below are sucks...
331                    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                    // prints
342                    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                    // Wrong Answer during testing
392                    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            // Failed some tests during submission
408            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.analyse
414                    .total_correct
415                    .as_ref()
416                    .unwrap_or(&Number::from(0))
417                    .to_string()
418                    .green(),
419                "Total cases:".after_spaces(3).yellow(),
420                self.analyse
421                    .total_testcases
422                    .as_ref()
423                    .unwrap_or(&Number::from(0))
424                    .to_string()
425                    .bold()
426                    .yellow(),
427                "Last case:".after_spaces(5).dimmed(),
428                self.submit.last_testcase.replace('\n', "↩ ").dimmed(),
429                "\nOutput:".after_spaces(8),
430                self.code_output[0],
431                "\nExpected:".after_spaces(6),
432                self.expected_output[0],
433            )?,
434            // Memory Exceeded
435            12 => write!(
436                f,
437                "\n{}\n\n{}{}\n",
438                self.status.status_msg.yellow().bold(),
439                "Last case:".after_spaces(5).dimmed(),
440                self.data_input.replace('\n', "↩ "),
441            )?,
442            // Output Timeout Exceeded
443            //
444            // TODO: 13 and 14 might have some different,
445            // if anybody reach this, welcome to fix this!
446            13 | 14 => write!(f, "\n{}\n", self.status.status_msg.yellow().bold(),)?,
447            // Runtime error
448            15 => write!(
449                f,
450                "\n{}\n{}\n'",
451                self.status.status_msg.red().bold(),
452                self.status.runtime_error
453            )?,
454            // Compile Error
455            20 => write!(
456                f,
457                "\n{}:\n\n{}\n",
458                self.status.status_msg.red().bold(),
459                self.error.full_compile_error.dimmed()
460            )?,
461            _ => write!(
462                f,
463                "{}{}{}{}{}{}{}{}",
464                "\nUnknown Error...\n".red().bold(),
465                "\nBingo! Welcome to fix this! Pull your request at ".yellow(),
466                "https://github.com/clearloop/leetcode-cli/pulls"
467                    .dimmed()
468                    .underline(),
469                ", and this file is located at ".yellow(),
470                "leetcode-cli/src/cache/models.rs".dimmed().underline(),
471                " waiting for you! Yep, line ".yellow(),
472                "385".dimmed().underline(),
473                ".\n".yellow(),
474            )?,
475        };
476
477        match &self.result_type {
478            Run::Test => {
479                if !self.code_output.is_empty() {
480                    write!(
481                        f,
482                        "{}{}",
483                        "Stdout:".after_spaces(8).purple(),
484                        self.code_output.join(&"\n".after_spaces(15))
485                    )
486                } else {
487                    write!(f, "")
488                }
489            }
490            _ => {
491                if !self.std_output.is_empty() {
492                    write!(
493                        f,
494                        "{}{}",
495                        "Stdout:".after_spaces(8).purple(),
496                        self.std_output[0].replace('\n', &"\n".after_spaces(15))
497                    )
498                } else {
499                    write!(f, "")
500                }
501            }
502        }
503    }
504}
505
506use crate::Config;
507use verify::*;
508
509mod verify {
510    use super::super::parser::ssr;
511    use serde::Deserialize;
512    use serde_json::Number;
513
514    #[derive(Debug, Default, Deserialize)]
515    pub struct Submit {
516        #[serde(default)]
517        pub question_id: String,
518        #[serde(default)]
519        pub last_testcase: String,
520        #[serde(default)]
521        pub compare_result: String,
522    }
523
524    // #[derive(Debug, Default, Deserialize)]
525    // pub struct VerifyInfo {
526    //     #[serde(default)]
527    //     memory: i64,
528    //     #[serde(default)]
529    //     elapsed_time: i64,
530    //     #[serde(default)]
531    //     task_finish_time: i64,
532    // }
533
534    #[derive(Debug, Default, Deserialize)]
535    pub struct Analyse {
536        #[serde(default)]
537        pub total_correct: Option<Number>,
538        #[serde(default)]
539        pub total_testcases: Option<Number>,
540        #[serde(default)]
541        pub runtime_percentile: Option<Number>,
542        #[serde(default)]
543        pub memory_percentile: Option<Number>,
544    }
545
546    #[derive(Debug, Default, Deserialize)]
547    pub struct VerifyStatus {
548        #[serde(default)]
549        pub status_code: i32,
550        #[serde(default)]
551        pub status_msg: String,
552        #[serde(default)]
553        pub status_memory: String,
554        #[serde(default)]
555        pub status_runtime: String,
556        #[serde(default)]
557        pub runtime_error: String,
558    }
559
560    #[derive(Debug, Default, Deserialize)]
561    pub struct CompileError {
562        // #[serde(default)]
563        // compile_error: String,
564        #[serde(default)]
565        pub full_compile_error: String,
566    }
567
568    #[derive(Debug, Default, Deserialize)]
569    pub struct Expected {
570        // #[serde(default)]
571        // expected_status_code: i32,
572        // #[serde(default)]
573        // expected_lang: String,
574        // #[serde(default)]
575        // expected_run_success: bool,
576        // #[serde(default)]
577        // expected_status_runtime: String,
578        // #[serde(default)]
579        // expected_memory: i64,
580        // #[serde(default, deserialize_with = "ssr")]
581        // expected_code_output: Vec<String>,
582        // #[serde(default)]
583        // expected_elapsed_time: i64,
584        // #[serde(default)]
585        // expected_task_finish_time: i64,
586        #[serde(default, deserialize_with = "ssr")]
587        pub expected_code_answer: Vec<String>,
588    }
589}
590
591/// Formatter for str
592trait Formatter {
593    fn after_spaces(&self, spaces: usize) -> String;
594    fn before_spaces(&self, spaces: usize) -> String;
595}
596
597impl Formatter for str {
598    fn after_spaces(&self, spaces: usize) -> String {
599        let mut r = String::new();
600        r.push_str(self);
601        r.push_str(&" ".repeat(spaces));
602        r
603    }
604
605    fn before_spaces(&self, spaces: usize) -> String {
606        let mut r = String::new();
607        r.push_str(&" ".repeat(spaces));
608        r.push_str(self);
609        r
610    }
611}