apt_parser/
lib.rs

1pub mod case_map;
2pub mod control;
3pub mod errors;
4pub mod packages;
5pub mod release;
6
7pub use control::*;
8pub use packages::*;
9pub use release::*;
10
11use case_map::CaseMap;
12use errors::KVError;
13use regex::Regex;
14
15#[warn(clippy::all)]
16#[warn(clippy::correctness)]
17#[warn(clippy::suspicious)]
18#[warn(clippy::pedantic)]
19#[warn(clippy::style)]
20#[warn(clippy::complexity)]
21#[warn(clippy::perf)]
22
23// HashMap<String, String>
24pub fn parse_kv(raw_apt_data: &str) -> Result<CaseMap, KVError> {
25	// clean the string
26	let binding = raw_apt_data.replace("\r\n", "\n").replace('\0', "");
27	let apt_data = binding.trim().split('\n');
28
29	let mut fields = CaseMap::new();
30	let mut current_key = "";
31
32	// Compile our regex before-hand
33	let regex = match Regex::new(r"^(.*?):\s(.*)$") {
34		Ok(regex) => regex,
35		Err(_) => return Err(KVError),
36	};
37
38	for line in apt_data {
39		let line = line.trim();
40
41		if line.is_empty() {
42			continue;
43		}
44
45		if !regex.is_match(line) {
46			if line.ends_with(':') {
47				let mut chars = line.chars();
48				chars.next_back(); // Pop the last character off
49
50				current_key = chars.as_str();
51				fields.insert(current_key, "");
52				continue;
53			}
54
55			if !current_key.is_empty() {
56				let existing_value = match fields.get(current_key) {
57					Some(value) => value,
58					None => "",
59				};
60
61				// On multiline descriptions, the '.' signifies a newline (blank)
62				if line == "." {
63					let updated_key = format!("{existing_value}\n\n");
64					fields.insert(current_key, &updated_key);
65				} else {
66					let updated_key = match existing_value.ends_with('\n') {
67						true => format!("{existing_value}{line}"),
68						false => format!("{existing_value} {line}"),
69					};
70
71					fields.insert(current_key, &updated_key);
72				}
73
74				continue;
75			}
76		}
77
78		let captures = regex.captures(line);
79		if captures.is_none() {
80			return Err(KVError);
81		}
82
83		let captures = captures.unwrap();
84		let key = captures.get(1);
85		let value = captures.get(2);
86
87		if key.is_none() || value.is_none() {
88			return Err(KVError);
89		};
90
91		let key = key.unwrap().as_str();
92		let value = value.unwrap().as_str();
93
94		if fields.contains_key(key) {
95			continue;
96		}
97
98		if key.to_lowercase() == "description" && !value.is_empty() {
99			let format = format!("{value}\n");
100			fields.insert(key, &format);
101		} else {
102			if current_key.to_lowercase() == "description" {
103				let existing_value = match fields.get(current_key) {
104					Some(value) => value,
105					None => "",
106				};
107
108				if existing_value.ends_with('\n') {
109					let substring = existing_value
110						.chars()
111						.take(existing_value.len() - 1)
112						.collect::<String>();
113
114					fields.insert(current_key, &substring);
115				}
116			}
117
118			fields.insert(key, value);
119		}
120
121		current_key = key;
122	}
123
124	Ok(fields)
125}
126
127pub fn make_array(raw_data: Option<&String>) -> Option<Vec<String>> {
128	return match raw_data {
129		Some(raw_data) => {
130			let mut data = Vec::new();
131			for line in raw_data.split(',') {
132				data.push(line.trim().to_string());
133			}
134
135			Some(data)
136		}
137		None => None,
138	};
139}