leetcode_cli/cache/
models.rs

1//! Leetcode data models
2use 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/// 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
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            // Memory Exceeded
437            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            // Output Timeout Exceeded
445            //
446            // TODO: 13 and 14 might have some different,
447            // if anybody reach this, welcome to fix this!
448            13 | 14 => write!(f, "\n{}\n", &self.status.status_msg.yellow().bold(),)?,
449            // Runtime error
450            15 => write!(
451                f,
452                "\n{}\n{}\n'",
453                &self.status.status_msg.red().bold(),
454                &self.status.runtime_error
455            )?,
456            // Compile Error
457            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)]
527    // pub struct VerifyInfo {
528    //     #[serde(default)]
529    //     memory: i64,
530    //     #[serde(default)]
531    //     elapsed_time: i64,
532    //     #[serde(default)]
533    //     task_finish_time: i64,
534    // }
535
536    #[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)]
565        // compile_error: String,
566        #[serde(default)]
567        pub full_compile_error: String,
568    }
569
570    #[derive(Debug, Default, Deserialize)]
571    pub struct Expected {
572        // #[serde(default)]
573        // expected_status_code: i32,
574        // #[serde(default)]
575        // expected_lang: String,
576        // #[serde(default)]
577        // expected_run_success: bool,
578        // #[serde(default)]
579        // expected_status_runtime: String,
580        // #[serde(default)]
581        // expected_memory: i64,
582        // #[serde(default, deserialize_with = "ssr")]
583        // expected_code_output: Vec<String>,
584        // #[serde(default)]
585        // expected_elapsed_time: i64,
586        // #[serde(default)]
587        // expected_task_finish_time: i64,
588        #[serde(default, deserialize_with = "ssr")]
589        pub expected_code_answer: Vec<String>,
590    }
591}
592
593/// Formatter for str
594trait 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}