package_parser/pkgs/
spec.rs1use lazy_static::lazy_static;
2use regex::Regex;
3use std::fs::File;
4use std::io::{BufRead, BufReader};
5use std::path::Path;
6use std::vec;
7
8use crate::error::SourcePkgError;
9
10#[derive(Debug, Clone, Default)]
11pub struct SpecInfo {
12 pub name: Option<String>,
13 pub version: Option<String>,
14 pub license: Option<String>,
15 pub summary: Option<String>,
16 pub description: Option<String>,
17 pub homepage: Option<String>,
18 pub source: Option<String>,
19 pub emails: Vec<String>,
20 pub authors: Vec<String>,
21}
22
23fn pre_process(line: &mut String) -> String {
25 let newline = line
26 .find('#')
27 .map(|i| line.split_off(i))
28 .unwrap_or_else(|| line.to_string());
29 newline.trim().to_string()
30}
31
32fn get_stripped_data(data: &mut str) -> String {
34 let new_data = data
35 .replace(['\'', '\"', '{', '}', '[', ']'], "")
36 .replace("%q", "");
37
38 new_data.trim().to_string()
39}
40
41fn get_description(path: impl AsRef<Path>) -> Option<String> {
48 let location = path.as_ref();
49 let fs = match File::open(location) {
50 Ok(fs) => fs,
51 Err(_) => return None,
52 };
53
54 let mut description = String::new();
55 let mut enter_description_section = false;
56 for line in BufReader::new(fs).lines() {
57 let line = match line {
58 Ok(line) => line,
59 Err(_) => continue,
60 };
61
62 if line.contains(".description") {
63 enter_description_section = true;
64 continue;
65 }
66
67 if enter_description_section {
68 let line = line.trim().to_string();
69 if line.contains("DESC") {
70 break;
71 }
72 description.push_str(&line);
73 }
74 }
75 Some(description)
76}
77
78pub struct Spec;
79
80lazy_static! {
81 static ref PARSE_NAME: Regex = Regex::new(r".*\.name(\s*)=(?P<name>.*)").unwrap();
82 static ref PARSE_VERSION: Regex = Regex::new(r".*\.version(\s*)=(?P<version>.*)").unwrap();
83 static ref PARSE_LICENSE: Regex = Regex::new(r".*\.license(\s*)=(?P<license>.*)").unwrap();
84 static ref PARSE_SUMMARY: Regex = Regex::new(r".*\.summary(\s*)=(?P<summary>.*)").unwrap();
85 static ref PARSE_DESCRIPTION: Regex =
86 Regex::new(r".*\.description(\s*)=(?P<description>.*)").unwrap();
87 static ref PARSE_HOMEPAGE: Regex = Regex::new(r".*\.homepage(\s*)=(?P<homepage>.*)").unwrap();
88 static ref PARSE_SOURCE: Regex = Regex::new(r".*\.source(\s*)=(?P<source>.*)").unwrap();
89}
90
91impl Spec {
92 pub fn new() -> Self {
93 Self {}
94 }
95
96 pub fn parse_spec(&self, path: impl AsRef<Path>) -> Result<SpecInfo, SourcePkgError> {
97 let location = path.as_ref();
98 let fs = File::open(location).map_err(SourcePkgError::Io)?;
99
100 let mut spec_info = SpecInfo::default();
101 for line in BufReader::new(fs).lines() {
102 let mut line = line.unwrap_or_default();
103 let line = pre_process(&mut line);
104
105 if let Some(name) = self.parse_name(&line) {
106 spec_info.name = Some(name);
107 }
108
109 if let Some(version) = self.parse_version(&line) {
110 spec_info.version = Some(version);
111 }
112
113 if let Some(license) = self.parse_license(&line) {
114 spec_info.license = Some(license);
115 }
116
117 if let Some(summary) = self.parse_summary(&line) {
118 spec_info.summary = Some(summary)
119 }
120
121 if let Some(homepage) = self.parse_homepage(&line) {
122 spec_info.homepage = Some(homepage);
123 }
124
125 if let Some(description) = self.parse_description(&path, &line) {
126 spec_info.description = Some(description);
127 }
128
129 let emails = self.parse_email(&line);
130 if !emails.is_empty() {
131 spec_info.emails.extend(emails);
132 }
133 }
134
135 Ok(spec_info)
136 }
137
138 fn parse_name(&self, line: &str) -> Option<String> {
139 if let Some(captures) = PARSE_NAME.captures(line) {
140 let mut name = match captures.name("name") {
141 Some(name) => name.as_str().to_string(),
142 None => return None,
143 };
144 let name = get_stripped_data(&mut name);
145 return Some(name);
146 }
147
148 None
149 }
150
151 fn parse_version(&self, line: &str) -> Option<String> {
152 if let Some(captures) = PARSE_VERSION.captures(line) {
153 let mut version = match captures.name("version") {
154 Some(version) => version.as_str().to_string(),
155 None => return None,
156 };
157 let version = get_stripped_data(&mut version);
158 return Some(version);
159 }
160
161 None
162 }
163
164 fn parse_license(&self, line: &str) -> Option<String> {
165 if let Some(captures) = PARSE_LICENSE.captures(line) {
166 let mut license = match captures.name("license") {
167 Some(license) => license.as_str().to_string(),
168 None => return None,
169 };
170 let license = get_stripped_data(&mut license);
171 return Some(license);
172 }
173
174 None
175 }
176
177 fn parse_summary(&self, line: &str) -> Option<String> {
178 if let Some(captures) = PARSE_SUMMARY.captures(line) {
179 let mut summary = match captures.name("summary") {
180 Some(summary) => summary.as_str().to_string(),
181 None => return None,
182 };
183 let summary = get_stripped_data(&mut summary);
184 return Some(summary);
185 }
186
187 None
188 }
189
190 fn parse_homepage(&self, line: &str) -> Option<String> {
191 if let Some(captures) = PARSE_HOMEPAGE.captures(line) {
192 let mut homepage = match captures.name("homepage") {
193 Some(homepage) => homepage.as_str().to_string(),
194 None => return None,
195 };
196 let homepage = get_stripped_data(&mut homepage);
197 return Some(homepage);
198 }
199
200 None
201 }
202
203 #[allow(dead_code)]
204 fn parse_source(&self, line: &str) -> Option<String> {
205 lazy_static! {
206 static ref SOURCE_REGEX1: Regex = Regex::new(r"/*.*source.*?>").unwrap();
207 static ref SOURCE_REGEX2: Regex = Regex::new(r",.*").unwrap();
208 };
209
210 if let Some(captures) = PARSE_SOURCE.captures(line) {
211 let source = match captures.name("source") {
212 Some(source) => source.as_str().to_string(),
213 None => return None,
214 };
215
216 let source = SOURCE_REGEX1.replace_all(&source, "");
217 let mut stripped_source = SOURCE_REGEX2.replace_all(&source, "").to_string();
218 let stripped_source = get_stripped_data(&mut stripped_source);
219 return Some(stripped_source);
220 }
221
222 None
223 }
224
225 fn parse_description(&self, path: impl AsRef<Path>, line: &str) -> Option<String> {
226 if let Some(captures) = PARSE_DESCRIPTION.captures(line) {
227 let mut description = match captures.name("description") {
228 Some(description) => description.as_str().to_string(),
229 None => return None,
230 };
231
232 let location = path.as_ref();
233 let location = location.to_string_lossy();
234 if location.ends_with(".gemspec") {
235 let description = get_stripped_data(&mut description);
238 return Some(description);
239 } else {
240 return get_description(path);
241 }
242 }
243
244 None
245 }
246
247 fn parse_email(&self, line: &str) -> Vec<String> {
248 if line.contains(".email") {
249 let stripped_emails = line.rfind('=').map(|index| {
250 let email = &line[index + 1..line.len() - 1];
251 email.to_string()
252 });
253
254 match stripped_emails {
255 Some(mut email) => {
256 let email = get_stripped_data(&mut email);
257 let email = email.trim();
258 let emails = email
259 .split(',')
260 .map(|email| email.to_string())
261 .collect::<Vec<String>>();
262 return emails;
263 }
264 None => return vec![],
265 }
266 }
267
268 vec![]
269 }
270
271 #[allow(dead_code)]
272 fn parse_author(&self, line: &str) -> Vec<String> {
273 lazy_static! {
274 static ref AUTHOR_REGEX1: Regex = Regex::new(r"/*.*author.*?=").unwrap();
275 static ref AUTHOR_REGEX2: Regex = Regex::new(r"(\s*=>\s*)").unwrap();
276 }
277
278 if line.contains(".author") {
279 let mut stripped_authors = AUTHOR_REGEX1.replace_all(line, "").to_string();
280 let stripped_authors = get_stripped_data(&mut stripped_authors);
281 let stripped_authors = AUTHOR_REGEX2
282 .replace_all(&stripped_authors, "=>")
283 .to_string();
284 let stripped_authors = stripped_authors.trim();
285 let stripped_authors = stripped_authors
286 .split(',')
287 .map(|author| author.to_string())
288 .collect::<Vec<String>>();
289
290 return stripped_authors;
291 }
292
293 vec![]
294 }
295}
296
297#[cfg(test)]
298mod tests {
299 use super::*;
300 use std::path::PathBuf;
301
302 #[test]
303 fn test_bower_json() {
304 let filepath = PathBuf::from(concat!(
305 env!("CARGO_MANIFEST_DIR"),
306 "/testdata/cocoapods/podspec/SwiftLib.podspec"
307 ));
308
309 let p = Spec::new();
310 let result = p.parse_spec(filepath).unwrap();
311 println!("{:#?}", result);
312 }
313}