hglib/commands/
grep.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this file,
3// You can obtain one at http://mozilla.org/MPL/2.0/.
4
5use crate::client::{Client, HglibError, Runner};
6use crate::version;
7use crate::{runcommand, MkArg};
8
9pub struct Arg<'a> {
10    pub pattern: &'a str,
11    pub files: &'a [&'a str],
12    pub all: bool,
13    pub text: bool,
14    pub follow: bool,
15    pub ignorecase: bool,
16    pub fileswithmatches: bool,
17    pub line: bool,
18    pub user: bool,
19    pub date: bool,
20    pub include: &'a [&'a str],
21    pub exclude: &'a [&'a str],
22}
23
24impl<'a> Default for Arg<'a> {
25    fn default() -> Self {
26        Self {
27            pattern: "",
28            files: &[],
29            all: false,
30            text: false,
31            follow: false,
32            ignorecase: false,
33            fileswithmatches: false,
34            line: false,
35            user: false,
36            date: false,
37            include: &[],
38            exclude: &[],
39        }
40    }
41}
42
43impl<'a> Arg<'a> {
44    fn run(&self, client: &mut Client) -> Result<(Vec<u8>, i32), HglibError> {
45        let mut args = self.files.to_vec();
46        args.insert(0, self.pattern);
47        runcommand!(
48            client,
49            "grep",
50            args,
51            "--all",
52            self.all,
53            "-a",
54            self.text,
55            "-f",
56            self.follow,
57            "-i",
58            self.ignorecase,
59            "-l",
60            self.fileswithmatches,
61            "-n",
62            self.line,
63            "-u",
64            self.user,
65            "-d",
66            self.date,
67            "-I",
68            self.include,
69            "-X",
70            self.exclude,
71            "--print0",
72            true
73        )
74    }
75}
76
77#[derive(Debug, PartialEq)]
78#[cfg_attr(test, derive(Clone))]
79pub struct GrepRes {
80    pub filename: String,
81    pub rev: Option<u64>,
82    pub line: Option<u32>,
83    pub match_status: Option<String>,
84    pub user: Option<String>,
85    //TODO: convert string to datetime
86    // pub date: Option<DateTime<Utc>>,
87    pub date: Option<String>,
88    pub matched: Option<String>,
89}
90
91#[derive(Debug, PartialEq)]
92enum FieldType {
93    Filename,
94    Rev,
95    Line,
96    MatchStatus,
97    User,
98    Date,
99    Matched,
100}
101
102impl Client {
103    fn get_field_types(&mut self, x: &Arg) -> Result<Vec<FieldType>, HglibError> {
104        let mut field_types = vec![FieldType::Filename];
105
106        if x.all || self.version(version::Arg {})? < (5, 2, None) {
107            field_types.push(FieldType::Rev);
108        }
109        if x.line {
110            field_types.push(FieldType::Line);
111        }
112        if x.all {
113            field_types.push(FieldType::MatchStatus);
114        }
115        if x.user {
116            field_types.push(FieldType::User);
117        }
118        if x.date {
119            field_types.push(FieldType::Date);
120        }
121        if !x.fileswithmatches {
122            field_types.push(FieldType::Matched);
123        }
124
125        Ok(field_types)
126    }
127
128    pub fn grep(&mut self, x: Arg) -> Result<Vec<GrepRes>, HglibError> {
129        let mut res = Vec::new();
130
131        let data = match x.run(self) {
132            Ok(ret) => ret.0,
133            Err(e) => {
134                if e.code == 1 {
135                    return Ok(res);
136                } else {
137                    return Err(e);
138                }
139            }
140        };
141
142        let field_types = self.get_field_types(&x)?;
143        for (n, element) in data.split(|x| *x == b'\0').enumerate() {
144            match field_types[n % field_types.len()] {
145                FieldType::Filename => {
146                    let filename = String::from_utf8(element.to_vec())?;
147                    res.push(GrepRes {
148                        filename,
149                        rev: None,
150                        line: None,
151                        match_status: None,
152                        user: None,
153                        date: None,
154                        matched: None,
155                    });
156                }
157                FieldType::Rev => {
158                    res.last_mut().unwrap().rev =
159                        Some(element.iter().fold(0, |r, x| r * 10 + (*x - b'0') as u64));
160                }
161                FieldType::Line => {
162                    res.last_mut().unwrap().line =
163                        Some(element.iter().fold(0, |r, x| r * 10 + (*x - b'0') as u32));
164                }
165                FieldType::MatchStatus => {
166                    res.last_mut().unwrap().match_status =
167                        Some(String::from_utf8(element.to_vec())?);
168                }
169                FieldType::User => {
170                    res.last_mut().unwrap().user = Some(String::from_utf8(element.to_vec())?);
171                }
172                FieldType::Date => {
173                    let sdate = std::str::from_utf8(element)?;
174                    res.last_mut().unwrap().date = Some(sdate.to_string());
175                }
176                FieldType::Matched => {
177                    res.last_mut().unwrap().matched = Some(String::from_utf8(element.to_vec())?);
178                }
179            }
180        }
181        res.pop();
182        Ok(res)
183    }
184}