1use 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 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}