1pub use clap::Parser;
4use serde::Serialize;
5pub use serde_json;
6pub use serde_yaml;
7use std::collections::HashMap;
8
9#[derive(Serialize)]
10pub enum Gender {
11 MALE = 1,
12 FEMALE = 2,
13 UNSPECIFIED = 9,
14}
15
16#[derive(clap::ValueEnum, Debug, Clone)]
17pub enum OutputFormat {
18 Json,
19 Yaml,
20}
21
22#[derive(Serialize)]
23pub struct ParsedData {
24 pub vehicle_class: String,
25 pub driving_privileges: String,
26 pub additional_privileges: String,
27 pub expiration_date: String,
28 pub last_name: String,
29 pub first_name: String,
30 pub middle_name: String,
31 pub issue_date: String,
32 pub date_of_birth: String,
33 pub gender: Gender,
34 pub eye_color: String,
35 pub height: String,
36 pub street: String,
37 pub city: String,
38 pub state: String,
39 pub postal_code: String,
40}
41
42impl ParsedData {
43 pub fn from_raw_data(raw_data: &str) -> Self {
44 let mut parsed_data = ParsedData {
45 vehicle_class: String::new(),
46 driving_privileges: String::new(),
47 additional_privileges: String::new(),
48 expiration_date: String::new(),
49 last_name: String::new(),
50 first_name: String::new(),
51 middle_name: String::new(),
52 issue_date: String::new(),
53 date_of_birth: String::new(),
54 gender: Gender::UNSPECIFIED,
55 eye_color: String::new(),
56 height: String::new(),
57 street: String::new(),
58 city: String::new(),
59 state: String::new(),
60 postal_code: String::new(),
61 };
62
63 let mut data_map = HashMap::new();
64
65 for line in raw_data.lines() {
67 if line.len() >= 3 {
68 let key = line[..3].to_string();
69 let value = line[3..].trim().to_string(); data_map.insert(key, value);
72 }
73 }
74
75 if let Some(value) = data_map.get("DCA") {
77 parsed_data.vehicle_class = value.to_string();
78 }
79 if let Some(value) = data_map.get("DCB") {
80 parsed_data.driving_privileges = value.to_string();
81 }
82 if let Some(value) = data_map.get("DCD") {
83 parsed_data.additional_privileges = value.to_string();
84 }
85 if let Some(value) = data_map.get("DBA") {
86 parsed_data.expiration_date = Self::standardize_date(value);
87 }
88 if let Some(value) = data_map.get("DCS") {
89 parsed_data.last_name = value.to_string();
90 }
91 if let Some(value) = data_map.get("DAC") {
92 parsed_data.first_name = value.to_string();
93 }
94 if let Some(value) = data_map.get("DAD") {
95 parsed_data.middle_name = value.to_string();
96 }
97 if let Some(value) = data_map.get("DBD") {
98 parsed_data.issue_date = Self::standardize_date(value);
99 }
100 if let Some(value) = data_map.get("DBB") {
101 parsed_data.date_of_birth = Self::standardize_date(value);
102 }
103 if let Some(value) = data_map.get("DBC") {
104 parsed_data.gender = match value.parse::<u8>().unwrap_or(9) {
105 1 => Gender::MALE,
106 2 => Gender::FEMALE,
107 _ => Gender::UNSPECIFIED,
108 };
109 }
110 if let Some(value) = data_map.get("DAY") {
111 parsed_data.eye_color = value.to_string();
112 }
113 if let Some(value) = data_map.get("DAU") {
114 parsed_data.height = Self::convert_height(value);
115 }
116 if let Some(value) = data_map.get("DAG") {
117 parsed_data.street = value.to_string();
118 }
119 if let Some(value) = data_map.get("DAI") {
120 parsed_data.city = value.to_string();
121 }
122 if let Some(value) = data_map.get("DAJ") {
123 parsed_data.state = value.to_string();
124 }
125 if let Some(value) = data_map.get("DAK") {
126 parsed_data.postal_code = value.to_string();
127 }
128
129 parsed_data
130 }
131
132 fn standardize_date(date_str: &str) -> String {
133 if date_str.len() == 8 {
134 if let Ok(month) = date_str[..2].parse::<u32>() {
136 if let Ok(day) = date_str[2..4].parse::<u32>() {
137 let year_str = &date_str[4..8];
138 let year = match year_str.parse::<u32>() {
139 Ok(y) => y,
140 Err(_) => return "Invalid Date".to_string(),
141 };
142
143 return format!("{:04}-{:02}-{:02}", year, month, day);
145 }
146 }
147 } else if date_str.len() == 8 {
148 let year = &date_str[..4];
150 let month = &date_str[4..6];
151 let day = &date_str[6..8];
152
153 return format!("{}-{}-{}", year, month, day);
155 }
156
157 "Invalid Date".to_string() }
159
160 fn convert_height(height_str: &str) -> String {
161 if height_str.ends_with("CM") {
162 if let Ok(cm) = height_str[..height_str.len() - 2].trim().parse::<f64>() {
163 let inches = cm / 2.54; return format!("{:.2}", inches); }
166 } else if height_str.ends_with("IN") {
167 if let Ok(inches) = height_str[..height_str.len() - 2].trim().parse::<f64>() {
168 return format!("{:.2}", inches); }
170 }
171 "Invalid Height".to_string() }
173}
174
175#[derive(Parser, Debug)]
176pub struct CommandLineArguments {
177 #[arg(short, long, value_name = "FILE")]
178 pub file: Option<String>,
180
181 #[arg(short = 'o', long, value_name = "FORMAT", default_value = "json")]
182 pub format: OutputFormat,
184}