cpp_linter/cli/
structs.rs

1use std::{fmt::Display, path::PathBuf};
2
3use clap::ArgMatches;
4
5use super::convert_extra_arg_val;
6use crate::{clang_tools::clang_tidy::CompilationUnit, common_fs::FileFilter};
7
8/// An enum to describe `--lines-changed-only` CLI option's behavior.
9#[derive(PartialEq, Clone, Debug, Default)]
10pub enum LinesChangedOnly {
11    /// All lines are scanned
12    #[default]
13    Off,
14    /// Only lines in the diff are scanned
15    Diff,
16    /// Only lines in the diff with additions are scanned.
17    On,
18}
19
20impl LinesChangedOnly {
21    fn from_string(val: &str) -> LinesChangedOnly {
22        match val {
23            "true" | "on" | "1" => LinesChangedOnly::On,
24            "diff" => LinesChangedOnly::Diff,
25            _ => LinesChangedOnly::Off,
26        }
27    }
28}
29
30impl Display for LinesChangedOnly {
31    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
32        match self {
33            LinesChangedOnly::Off => write!(f, "false"),
34            LinesChangedOnly::Diff => write!(f, "diff"),
35            LinesChangedOnly::On => write!(f, "true"),
36        }
37    }
38}
39
40/// A structure to contain parsed CLI options.
41pub struct Cli {
42    pub version: String,
43    pub verbosity: bool,
44    pub extensions: Vec<String>,
45    pub repo_root: String,
46    pub lines_changed_only: LinesChangedOnly,
47    pub files_changed_only: bool,
48    pub ignore: Vec<String>,
49    pub style: String,
50    pub ignore_format: Option<Vec<String>>,
51    pub ignore_tidy: Option<Vec<String>>,
52    pub tidy_checks: String,
53    pub database: Option<PathBuf>,
54    pub extra_arg: Vec<String>,
55    pub thread_comments: ThreadComments,
56    pub no_lgtm: bool,
57    pub step_summary: bool,
58    pub file_annotations: bool,
59    pub not_ignored: Option<Vec<String>>,
60    pub tidy_review: bool,
61    pub format_review: bool,
62    pub passive_reviews: bool,
63}
64
65impl From<&ArgMatches> for Cli {
66    /// Construct a [`Cli`] instance from a [`ArgMatches`] instance (after options are parsed from CLI).
67    fn from(args: &ArgMatches) -> Self {
68        let ignore = args
69            .get_many::<String>("ignore")
70            .unwrap()
71            .map(|s| s.to_owned())
72            .collect::<Vec<_>>();
73        let ignore_tidy = args
74            .get_many::<String>("ignore-tidy")
75            .map(|val| val.map(|s| s.to_owned()).collect::<Vec<_>>());
76        let ignore_format = args
77            .get_many::<String>("ignore-format")
78            .map(|val| val.map(|s| s.to_owned()).collect::<Vec<_>>());
79        let extra_arg = convert_extra_arg_val(args);
80
81        let lines_changed_only = LinesChangedOnly::from_string(
82            args.get_one::<String>("lines-changed-only")
83                .unwrap()
84                .as_str(),
85        );
86
87        let thread_comments = ThreadComments::from_string(
88            args.get_one::<String>("thread-comments").unwrap().as_str(),
89        );
90
91        let extensions = args
92            .get_many::<String>("extensions")
93            .unwrap()
94            .map(|s| s.to_string())
95            .collect::<Vec<_>>();
96
97        Self {
98            version: args.get_one::<String>("version").unwrap().to_owned(),
99            verbosity: args.get_one::<String>("verbosity").unwrap().as_str() == "debug",
100            extensions,
101            repo_root: args.get_one::<String>("repo-root").unwrap().to_owned(),
102            lines_changed_only,
103            files_changed_only: args.get_flag("files-changed-only"),
104            ignore,
105            style: args.get_one::<String>("style").unwrap().to_owned(),
106            ignore_format,
107            ignore_tidy,
108            tidy_checks: args.get_one::<String>("tidy-checks").unwrap().to_owned(),
109            database: args.get_one::<PathBuf>("database").map(|v| v.to_owned()),
110            extra_arg,
111            no_lgtm: args.get_flag("no-lgtm"),
112            step_summary: args.get_flag("step-summary"),
113            thread_comments,
114            file_annotations: args.get_flag("file-annotations"),
115            not_ignored: args
116                .get_many::<String>("files")
117                .map(|files| Vec::from_iter(files.map(|v| v.to_owned()))),
118            tidy_review: args.get_flag("tidy-review"),
119            format_review: args.get_flag("format-review"),
120            passive_reviews: args.get_flag("passive-reviews"),
121        }
122    }
123}
124
125/// An enum to describe `--thread-comments` CLI option's behavior.
126#[derive(PartialEq, Clone, Debug)]
127pub enum ThreadComments {
128    /// Always post a new comment and delete any outdated ones.
129    On,
130    /// Do not post thread comments.
131    Off,
132    /// Only update existing thread comments.
133    /// If none exist, then post a new one.
134    Update,
135}
136
137impl ThreadComments {
138    fn from_string(val: &str) -> ThreadComments {
139        match val {
140            "true" | "on" | "1" => ThreadComments::On,
141            "update" => ThreadComments::Update,
142            _ => ThreadComments::Off,
143        }
144    }
145}
146
147impl Display for ThreadComments {
148    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
149        match self {
150            ThreadComments::On => write!(f, "true"),
151            ThreadComments::Off => write!(f, "false"),
152            ThreadComments::Update => write!(f, "update"),
153        }
154    }
155}
156
157/// A data structure to contain CLI options that relate to
158/// clang-tidy or clang-format arguments.
159#[derive(Debug, Clone, Default)]
160pub struct ClangParams {
161    pub tidy_checks: String,
162    pub lines_changed_only: LinesChangedOnly,
163    pub database: Option<PathBuf>,
164    pub extra_args: Vec<String>,
165    pub database_json: Option<Vec<CompilationUnit>>,
166    pub style: String,
167    pub clang_tidy_command: Option<PathBuf>,
168    pub clang_format_command: Option<PathBuf>,
169    pub tidy_filter: Option<FileFilter>,
170    pub format_filter: Option<FileFilter>,
171    pub tidy_review: bool,
172    pub format_review: bool,
173}
174
175impl From<&Cli> for ClangParams {
176    /// Construct a [`ClangParams`] instance from a [`Cli`] instance.
177    fn from(args: &Cli) -> Self {
178        ClangParams {
179            tidy_checks: args.tidy_checks.clone(),
180            lines_changed_only: args.lines_changed_only.clone(),
181            database: args.database.clone(),
182            extra_args: args.extra_arg.clone(),
183            database_json: None,
184            style: args.style.clone(),
185            clang_tidy_command: None,
186            clang_format_command: None,
187            tidy_filter: args
188                .ignore_tidy
189                .as_ref()
190                .map(|ignore_tidy| FileFilter::new(ignore_tidy, args.extensions.clone())),
191            format_filter: args
192                .ignore_format
193                .as_ref()
194                .map(|ignore_format| FileFilter::new(ignore_format, args.extensions.clone())),
195            tidy_review: args.tidy_review,
196            format_review: args.format_review,
197        }
198    }
199}
200
201/// A struct to contain CLI options that relate to
202/// [`ResApiClient.post_feedback()`](fn@crate::rest_api::ResApiClient.post_feedback()).
203pub struct FeedbackInput {
204    pub thread_comments: ThreadComments,
205    pub no_lgtm: bool,
206    pub step_summary: bool,
207    pub file_annotations: bool,
208    pub style: String,
209    pub tidy_review: bool,
210    pub format_review: bool,
211    pub passive_reviews: bool,
212}
213
214impl From<&Cli> for FeedbackInput {
215    /// Construct a [`FeedbackInput`] instance from a [`Cli`] instance.
216    fn from(args: &Cli) -> Self {
217        FeedbackInput {
218            style: args.style.clone(),
219            no_lgtm: args.no_lgtm,
220            step_summary: args.step_summary,
221            thread_comments: args.thread_comments.clone(),
222            file_annotations: args.file_annotations,
223            tidy_review: args.tidy_review,
224            format_review: args.format_review,
225            passive_reviews: args.passive_reviews,
226        }
227    }
228}
229
230impl Default for FeedbackInput {
231    /// Construct a [`FeedbackInput`] instance with default values.
232    fn default() -> Self {
233        FeedbackInput {
234            thread_comments: ThreadComments::Off,
235            no_lgtm: true,
236            step_summary: false,
237            file_annotations: true,
238            style: "llvm".to_string(),
239            tidy_review: false,
240            format_review: false,
241            passive_reviews: false,
242        }
243    }
244}
245
246#[cfg(test)]
247mod test {
248    use crate::cli::get_arg_parser;
249
250    use super::{Cli, LinesChangedOnly, ThreadComments};
251
252    #[test]
253    fn parse_positional() {
254        let parser = get_arg_parser();
255        let args = parser.get_matches_from(["cpp-linter", "file1.c", "file2.h"]);
256        let cli = Cli::from(&args);
257        let not_ignored = cli.not_ignored.expect("failed to parse positional args");
258        assert!(!not_ignored.is_empty());
259        assert!(not_ignored.contains(&String::from("file1.c")));
260        assert!(not_ignored.contains(&String::from("file2.h")));
261    }
262
263    #[test]
264    fn display_lines_changed_only_enum() {
265        let input = "diff".to_string();
266        assert_eq!(
267            LinesChangedOnly::from_string(&input),
268            LinesChangedOnly::Diff
269        );
270        assert_eq!(format!("{}", LinesChangedOnly::Diff), input);
271    }
272
273    #[test]
274    fn display_thread_comments_enum() {
275        let input = "false".to_string();
276        assert_eq!(ThreadComments::from_string(&input), ThreadComments::Off);
277        assert_eq!(format!("{}", ThreadComments::Off), input);
278    }
279}