1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
pub mod case_map;
pub mod control;
pub mod errors;
pub mod packages;
pub mod release;

pub use control::*;
pub use packages::*;
pub use release::*;

use case_map::CaseMap;
use errors::KVError;
use regex::Regex;

#[warn(clippy::all)]
#[warn(clippy::correctness)]
#[warn(clippy::suspicious)]
#[warn(clippy::pedantic)]
#[warn(clippy::style)]
#[warn(clippy::complexity)]
#[warn(clippy::perf)]

// HashMap<String, String>
pub fn parse_kv(raw_apt_data: &str) -> Result<CaseMap, KVError> {
	// clean the string
	let binding = raw_apt_data.replace("\r\n", "\n").replace('\0', "");
	let apt_data = binding.trim().split('\n');

	let mut fields = CaseMap::new();
	let mut current_key = "";

	// Compile our regex before-hand
	let regex = match Regex::new(r"^(.*?):\s(.*)$") {
		Ok(regex) => regex,
		Err(_) => return Err(KVError),
	};

	for line in apt_data {
		let line = line.trim();

		if line.is_empty() {
			continue;
		}

		if !regex.is_match(line) {
			if line.ends_with(':') {
				let mut chars = line.chars();
				chars.next_back(); // Pop the last character off

				current_key = chars.as_str();
				fields.insert(current_key, "");
				continue;
			}

			if !current_key.is_empty() {
				let existing_value = match fields.get(current_key) {
					Some(value) => value,
					None => "",
				};

				// On multiline descriptions, the '.' signifies a newline (blank)
				if line == "." {
					let updated_key = format!("{existing_value}\n\n");
					fields.insert(current_key, &updated_key);
				} else {
					let updated_key = match existing_value.ends_with('\n') {
						true => format!("{existing_value}{line}"),
						false => format!("{existing_value} {line}"),
					};

					fields.insert(current_key, &updated_key);
				}

				continue;
			}
		}

		let captures = regex.captures(line);
		if captures.is_none() {
			return Err(KVError);
		}

		let captures = captures.unwrap();
		let key = captures.get(1);
		let value = captures.get(2);

		if key.is_none() || value.is_none() {
			return Err(KVError);
		};

		let key = key.unwrap().as_str();
		let value = value.unwrap().as_str();

		if fields.contains_key(key) {
			continue;
		}

		if key.to_lowercase() == "description" && !value.is_empty() {
			let format = format!("{value}\n");
			fields.insert(key, &format);
		} else {
			if current_key.to_lowercase() == "description" {
				let existing_value = match fields.get(current_key) {
					Some(value) => value,
					None => "",
				};

				if existing_value.ends_with('\n') {
					let substring = existing_value
						.chars()
						.take(existing_value.len() - 1)
						.collect::<String>();

					fields.insert(current_key, &substring);
				}
			}

			fields.insert(key, value);
		}

		current_key = key;
	}

	Ok(fields)
}

pub fn make_array(raw_data: Option<&String>) -> Option<Vec<String>> {
	return match raw_data {
		Some(raw_data) => {
			let mut data = Vec::new();
			for line in raw_data.split(',') {
				data.push(line.trim().to_string());
			}

			Some(data)
		}
		None => None,
	};
}