From 84f1bf7efe5c52759c60eac6bdfc5d8890739a96 Mon Sep 17 00:00:00 2001 From: 132nd-Professor <132nd-Professor> Date: Thu, 11 Aug 2022 20:29:17 +0200 Subject: [PATCH] refactor --- Cargo.toml | 2 +- src/constants.rs | 5 + src/lib.rs | 84 ++++++++++++----- src/main.rs | 241 ++++------------------------------------------- src/processor.rs | 121 ++++++++++++++++++++++++ src/reader.rs | 48 ++++++++++ src/tacview.rs | 52 ++++++++++ src/writer.rs | 82 ++++++++++++++++ 8 files changed, 391 insertions(+), 244 deletions(-) create mode 100644 src/constants.rs create mode 100644 src/processor.rs create mode 100644 src/reader.rs create mode 100644 src/tacview.rs create mode 100644 src/writer.rs diff --git a/Cargo.toml b/Cargo.toml index 5928c7a..fe09a15 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ authors = ["132nd-Professor <132nd-Professor>"] edition = "2021" [dependencies] -zip = ">= 0.6.2" +zip = {version = ">= 0.6.2", default-features = false, features = ["deflate"]} [profile.release] lto = true diff --git a/src/constants.rs b/src/constants.rs new file mode 100644 index 0000000..3bc4fe2 --- /dev/null +++ b/src/constants.rs @@ -0,0 +1,5 @@ +pub const EXTENSION_ZIP: &str = ".zip.acmi"; +pub const EXTENSION_TXT: &str = ".txt.acmi"; + +pub const COMMENT: char = '#'; +pub const MINUS: char = '-'; diff --git a/src/lib.rs b/src/lib.rs index 066b33b..aef201c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,7 @@ pub mod lib { use std::collections::HashSet; use std::fs; + use std::hash::Hash; use std::io::Write; use zip; @@ -9,11 +10,32 @@ pub mod lib { const ERR_CANNOT_OPEN_OUTPUT: &str = "Could not open output file"; const ERR_CANNOT_BEGIN_FILE: &str = "Could not begin file in zip archive"; - pub struct IDs<'a> { - pub blue: HashSet<&'a str>, - pub red: HashSet<&'a str>, - pub violet: HashSet<&'a str>, - pub unknown: HashSet<&'a str>, + pub struct IDs<'a, S: AsRef + Hash + Eq> { + pub blue: HashSet<&'a S>, + pub red: HashSet<&'a S>, + pub violet: HashSet<&'a S>, + pub unknown: HashSet<&'a S>, + } + + impl<'a, S: AsRef + Hash + Eq> IDs<'a, S> { + pub fn new() -> Self { + let blue = HashSet::new(); + let red = HashSet::new(); + let violet = HashSet::new(); + let unknown = HashSet::new(); + Self { + blue, + red, + violet, + unknown, + } + } + } + + impl<'a, S: AsRef + Hash + Eq> Default for IDs<'a, S> { + fn default() -> Self { + Self::new() + } } pub struct Descriptors { @@ -39,31 +61,51 @@ pub mod lib { pub violet: String, } - pub struct BodiesByCoalition<'a> { - pub blue: Vec<&'a str>, - pub red: Vec<&'a str>, - pub violet: Vec<&'a str>, + pub struct BodiesByCoalition<'a, S: AsRef> { + pub blue: Vec<&'a S>, + pub red: Vec<&'a S>, + pub violet: Vec<&'a S>, + } + + impl<'a, S: AsRef> BodiesByCoalition<'a, S> { + pub fn new() -> Self { + Self { + blue: vec![], + red: vec![], + violet: vec![], + } + } + } + + impl<'a, S: AsRef> Default for BodiesByCoalition<'a, S> { + fn default() -> Self { + Self::new() + } } pub trait Handling { - fn write(&mut self, header: Vec, bodies_by_coalition: BodiesByCoalition); + fn write>(&mut self, header: &[S], bodies_by_coalition: BodiesByCoalition); } impl Handling for Descriptors { - fn write(&mut self, header: Vec, bodies_by_coalition: BodiesByCoalition) { - for line in &header { - writeln!(self.blue, "{}", line).expect(ERR_CANNOT_WRITE_DATA); - writeln!(self.red, "{}", line).expect(ERR_CANNOT_WRITE_DATA); - writeln!(self.violet, "{}", line).expect(ERR_CANNOT_WRITE_DATA); + fn write>( + &mut self, + header: &[S], + bodies_by_coalition: BodiesByCoalition, + ) { + for line in header { + writeln!(self.blue, "{}", line.as_ref()).expect(ERR_CANNOT_WRITE_DATA); + writeln!(self.red, "{}", line.as_ref()).expect(ERR_CANNOT_WRITE_DATA); + writeln!(self.violet, "{}", line.as_ref()).expect(ERR_CANNOT_WRITE_DATA); } - for line in &bodies_by_coalition.blue { - writeln!(self.blue, "{}", line).expect(ERR_CANNOT_WRITE_DATA); + for line in bodies_by_coalition.blue { + writeln!(self.blue, "{}", line.as_ref()).expect(ERR_CANNOT_WRITE_DATA); } - for line in &bodies_by_coalition.red { - writeln!(self.red, "{}", line).expect(ERR_CANNOT_WRITE_DATA); + for line in bodies_by_coalition.red { + writeln!(self.red, "{}", line.as_ref()).expect(ERR_CANNOT_WRITE_DATA); } - for line in &bodies_by_coalition.violet { - writeln!(self.violet, "{}", line).expect(ERR_CANNOT_WRITE_DATA); + for line in bodies_by_coalition.violet { + writeln!(self.violet, "{}", line.as_ref()).expect(ERR_CANNOT_WRITE_DATA); } } } diff --git a/src/main.rs b/src/main.rs index fec7a3e..668b45f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,231 +1,28 @@ -use std::collections::HashSet; -use std::io::{BufRead, BufReader}; -use std::{fs, str}; +mod constants; +mod processor; +mod reader; +mod tacview; +mod writer; -use tacview_splitter::lib; -use tacview_splitter::lib::Handling; - -const COMMENT: char = '#'; -const MINUS: char = '-'; - -const EXTENSION_ZIP: &str = ".zip.acmi"; -const EXTENSION_TXT: &str = ".txt.acmi"; - -#[derive(PartialEq)] -enum LineType { - Unknown, - Timestamp, - Destruction, - Telemetry, -} +use crate::{tacview::Coalition, writer::StringWriter}; fn main() { - let (input_filename, is_zip) = find_input_file(); + let (input_filename, is_zip) = reader::find_input_file(); println!("Processing {}", input_filename); - let lines = read_data(&input_filename, is_zip); - let (header, body) = split_into_header_and_body(lines); - let bodies_by_coalition = divide_body_by_coalition(&body); + let lines = reader::read_data(&input_filename, is_zip); + let (header, body) = processor::split_into_header_and_body(&lines); - let output_filenames = get_output_filenames(&input_filename, is_zip); - if is_zip { - let mut descriptors = lib::Descriptors::>::new(output_filenames); - descriptors.write(header, bodies_by_coalition); - } else { - let mut descriptors = lib::Descriptors::::new(output_filenames); - descriptors.write(header, bodies_by_coalition); - } -} + let coalition_per_line = processor::divide_body_by_coalition(body); -fn split_into_header_and_body(lines: Vec) -> (Vec, Vec) { - let mut i = 0; - for line in &lines { - if line.chars().next().expect("malformed line") == COMMENT { - break; - } - i += 1; - } - (lines[..i].to_vec(), lines[i..].to_vec()) -} + let mut blue_writer = writer::create_writer(is_zip, &input_filename); + blue_writer.write_strings(header); + blue_writer.write_for_coalition(body, &coalition_per_line, Coalition::Blue); -fn divide_body_by_coalition(body: &Vec) -> lib::BodiesByCoalition { - let mut bbc = lib::BodiesByCoalition { - blue: Vec::new(), - red: Vec::new(), - violet: Vec::new(), - }; - let mut continued = false; - let mut line_type = LineType::Unknown; - let mut coalitions = lib::IDs { - blue: HashSet::new(), - red: HashSet::new(), - violet: HashSet::new(), - unknown: HashSet::new(), - }; - for line in body { - let result = process_line(continued, &mut coalitions, line, line_type); - line_type = result.0; - continued = result.1; - let id = result.2; - if line_type == LineType::Timestamp { - bbc.blue.push(line); - bbc.red.push(line); - bbc.violet.push(line); - } else { - // destruction or telemetry - if coalitions.blue.contains(&id) { - bbc.blue.push(line); - } else if coalitions.red.contains(&id) { - bbc.red.push(line); - } else if coalitions.violet.contains(&id) { - bbc.violet.push(line); - } - } - } - bbc -} + let mut red_writer = writer::create_writer(is_zip, &input_filename); + red_writer.write_strings(header); + red_writer.write_for_coalition(body, &coalition_per_line, Coalition::Red); -fn process_line<'a>( - continued: bool, - coalitions: &mut lib::IDs<'a>, - line: &'a str, - last_line_type: LineType, -) -> (LineType, bool, &'a str) { - let mut id = ""; - let line_type; - if !continued { - line_type = determine_line_type(line); - if line_type == LineType::Telemetry { - id = get_id_from_line(line); - assign_id_to_coalitions(coalitions, line, id) - } - } else { - line_type = last_line_type; - } - let line_will_continue = will_line_continue(line); - (line_type, line_will_continue, id) -} - -fn will_line_continue(line: &str) -> bool { - line.ends_with('\\') -} - -fn get_id_from_line(line: &str) -> &str { - let result = line.split_once(','); - let split = match result { - Some(t) => t, - None => panic!("Could not get ID from line!"), - }; - split.0 as _ -} - -fn assign_id_to_coalitions<'a>(coalitions: &mut lib::IDs<'a>, line: &'a str, id: &'a str) { - if line.contains("Color=") { - if line.contains("Color=Blue") { - coalitions.blue.insert(id); - } else if line.contains("Color=Red") { - coalitions.red.insert(id); - } else if line.contains("Color=Violet") { - coalitions.violet.insert(id); - } else { - coalitions.unknown.insert(id); - } - } -} - -fn determine_line_type(line: &str) -> LineType { - let first_char = line.chars().next().expect("malformed line"); - if first_char == COMMENT { - LineType::Timestamp - } else if first_char == MINUS { - LineType::Destruction - } else { - LineType::Telemetry - } -} - -fn get_output_filenames(input_filename: &String, is_zip: bool) -> lib::OutputFilenames { - let output_filenames_zip: lib::FilenamesVariant; - let output_filenames_txt: lib::FilenamesVariant; - - if is_zip { - output_filenames_zip = - get_output_filenames_for_extension(input_filename, EXTENSION_ZIP, EXTENSION_ZIP); - output_filenames_txt = - get_output_filenames_for_extension(input_filename, EXTENSION_ZIP, EXTENSION_TXT); - } else { - output_filenames_zip = get_output_filenames_dummy(); - output_filenames_txt = - get_output_filenames_for_extension(input_filename, EXTENSION_TXT, EXTENSION_TXT); - } - - lib::OutputFilenames { - txt: output_filenames_txt, - zip: output_filenames_zip, - } -} - -fn get_output_filenames_dummy() -> lib::FilenamesVariant { - let blue = "".to_string(); - let red = "".to_string(); - let violet = "".to_string(); - lib::FilenamesVariant { blue, red, violet } -} - -fn get_output_filenames_for_extension( - input_filename: &String, - old_extension: &str, - new_extension: &str, -) -> lib::FilenamesVariant { - let blue = - lib::get_output_filenames_individual(input_filename, old_extension, new_extension, "_blue"); - let red = - lib::get_output_filenames_individual(input_filename, old_extension, new_extension, "_red"); - let violet = lib::get_output_filenames_individual( - input_filename, - old_extension, - new_extension, - "_violet", - ); - let output_filenames_zip = lib::FilenamesVariant { blue, red, violet }; - lib::sanity_check_output_filenames(input_filename, &output_filenames_zip); - output_filenames_zip -} - -fn find_input_file() -> (String, bool) { - let read_dir = fs::read_dir(".").expect("Could not read current directory"); - for entry_result in read_dir { - let entry = entry_result.expect("Could not parse DirEntry"); - let path_buf = entry.path(); - let filename = path_buf.to_string_lossy().to_string(); - if filename.ends_with(EXTENSION_TXT) { - return (filename, false); - } else if filename.ends_with(EXTENSION_ZIP) { - return (filename, true); - } - } - println!("No tacview input file found in current directory."); - std::process::exit(1); -} - -fn read_data(filename: &String, is_zip: bool) -> Vec { - let file = fs::File::open(filename).expect("Could not read from input file"); - let buf = BufReader::new(file); - return if is_zip { - let mut archive = zip::ZipArchive::new(buf).expect("Could not read zip data"); - let inner_file = archive - .by_index(0) - .expect("Could not read telemetry file from zip archive"); - let inner_buf = BufReader::new(inner_file); - let lines: Vec = inner_buf - .lines() - .map(|l| l.expect("Could not parse line")) - .collect(); - lines - } else { - let lines: Vec = buf - .lines() - .map(|l| l.expect("Could not parse line")) - .collect(); - lines - }; + let mut purple_writer = writer::create_writer(is_zip, &input_filename); + purple_writer.write_strings(header); + purple_writer.write_for_coalition(body, &coalition_per_line, Coalition::Purple); } diff --git a/src/processor.rs b/src/processor.rs new file mode 100644 index 0000000..b357d2b --- /dev/null +++ b/src/processor.rs @@ -0,0 +1,121 @@ +use std::hash::Hash; + +use crate::constants::{COMMENT, MINUS}; +use crate::tacview::{Coalition, CoalitionIDs}; + +pub fn split_into_header_and_body>(lines: &[S]) -> (&[S], &[S]) { + let mut i = 0; + for line in lines { + if line.as_ref().chars().next().expect("malformed line") == COMMENT { + break; + } + i += 1; + } + (&lines[..i], &lines[i..]) +} + +pub fn divide_body_by_coalition + Hash + Eq>(body: &[S]) -> Vec { + let mut result = vec![]; + let mut coalition_ids: CoalitionIDs = CoalitionIDs::new(); + + let mut old_line = Line::default(); + + for line_s in body { + let processed = Line::process_line(old_line, line_s, &mut coalition_ids); + result.push(processed.coalition.clone()); + old_line = processed; + } + result +} + +struct Line { + line_type: LineType, + continued: bool, + coalition: Coalition, +} + +impl Line { + fn process_line<'a, S: AsRef>( + old_line: Line, + current_line: &'a S, + coalition_ids: &mut CoalitionIDs<'a>, + ) -> Self { + let line_type = match old_line.continued { + true => old_line.line_type, + false => LineType::find_type(current_line), + }; + + match line_type { + LineType::Telemetry => Line::from_content(line_type, current_line, coalition_ids), + LineType::Timestamp => Line::new(line_type, false, Coalition::All), + LineType::Destruction => Line::from_content(line_type, current_line, coalition_ids), + LineType::Unknown => panic!(""), + } + } + + fn from_content<'a, S: AsRef>( + line_type: LineType, + current_line: &'a S, + coalition_ids: &mut CoalitionIDs<'a>, + ) -> Self { + let id = Self::get_id_from_line(current_line); + let continued = Self::will_line_continue(current_line); + let coalition = Self::assign_id_to_coalitions(coalition_ids, current_line, id); + Line::new(line_type, continued, coalition) + } + + fn assign_id_to_coalitions<'a, S: AsRef>( + coalition_ids: &mut CoalitionIDs<'a>, + line: &S, + id: &'a str, + ) -> Coalition { + if Coalition::line_contains_coalition(line) { + let coalition = Coalition::from_line(line); + coalition_ids.insert(id, coalition.clone()); + coalition + } else { + Coalition::Unknown + } + } + + fn will_line_continue>(current_line: &S) -> bool { + current_line.as_ref().ends_with('\\') + } + + fn get_id_from_line>(line: &S) -> &str { + line.as_ref().split_once(',').unwrap().0 + } + + fn new(line_type: LineType, continued: bool, coalition: Coalition) -> Self { + Self { + line_type, + continued, + coalition, + } + } + + fn default() -> Self { + Self::new(LineType::Unknown, false, Coalition::Unknown) + } +} + +#[derive(PartialEq, Clone)] +enum LineType { + Unknown, + Timestamp, + Destruction, + Telemetry, +} + +impl LineType { + fn find_type>(line: &S) -> Self { + let first_char = line.as_ref().chars().next().unwrap(); + if first_char == COMMENT { + Self::Timestamp + } else if first_char == MINUS { + Self::Destruction + } else { + LineType::Telemetry + } + } +} diff --git a/src/reader.rs b/src/reader.rs new file mode 100644 index 0000000..df76ca2 --- /dev/null +++ b/src/reader.rs @@ -0,0 +1,48 @@ +use crate::constants::{EXTENSION_TXT, EXTENSION_ZIP}; +use std::fs::{self, File}; +use std::io::{BufRead, BufReader}; + +pub fn find_input_file() -> (String, bool) { + let read_dir = fs::read_dir(".").expect("Could not read current directory"); + + for entry_result in read_dir { + let entry = entry_result.expect("Could not parse DirEntry"); + let path_buf = entry.path(); + let filename = path_buf.to_string_lossy().to_string(); + + if filename.ends_with(EXTENSION_TXT) { + return (filename, false); + } else if filename.ends_with(EXTENSION_ZIP) { + return (filename, true); + } + } + println!("No tacview input file found in current directory."); + std::process::exit(1); +} + +pub fn read_data(filename: &str, is_zip: bool) -> Vec { + let file = fs::File::open(filename).expect("Could not read from input file"); + let buf = BufReader::new(file); + if is_zip { + read_zip(buf) + } else { + read_txt(buf) + } +} + +fn read_zip(buf: BufReader) -> Vec { + let mut archive = zip::ZipArchive::new(buf).expect("Could not read zip data"); + let inner_file = archive + .by_index(0) + .expect("Could not read telemetry file from zip archive"); + let inner_buf = BufReader::new(inner_file); + read_txt(inner_buf) +} + +fn read_txt(buf: T) -> Vec { + let lines: Vec = buf + .lines() + .map(|l| l.expect("Could not parse line")) + .collect(); + lines +} diff --git a/src/tacview.rs b/src/tacview.rs new file mode 100644 index 0000000..2e8735a --- /dev/null +++ b/src/tacview.rs @@ -0,0 +1,52 @@ +use std::collections::HashSet; + +#[derive(PartialEq, Clone)] +pub enum Coalition { + Blue, + Red, + Purple, + All, + Unknown, +} + +impl Coalition { + pub fn from_line>(line: &S) -> Self { + let line = line.as_ref(); + if line.contains("Color=Blue") { + Self::Blue + } else if line.contains("Color=Red") { + Self::Red + } else if line.contains("Color=Purple") { + Self::Purple + } else { + Self::Unknown + } + } + + pub fn line_contains_coalition>(line: &S) -> bool { + line.as_ref().contains("Color=") + } +} + +pub struct CoalitionIDs<'a> { + pub blue: HashSet<&'a str>, + pub red: HashSet<&'a str>, + pub purple: HashSet<&'a str>, +} + +impl<'a> CoalitionIDs<'a> { + pub fn new() -> Self { + let (blue, red, purple) = (HashSet::new(), HashSet::new(), HashSet::new()); + Self { blue, red, purple } + } + + pub fn insert(&mut self, id: &'a str, coalition: Coalition) { + match coalition { + Coalition::Blue => self.blue.insert(id), + Coalition::Red => self.red.insert(id), + Coalition::Purple => self.purple.insert(id), + Coalition::All => false, + Coalition::Unknown => false, + }; + } +} diff --git a/src/writer.rs b/src/writer.rs new file mode 100644 index 0000000..70f1742 --- /dev/null +++ b/src/writer.rs @@ -0,0 +1,82 @@ +use crate::constants::{EXTENSION_TXT, EXTENSION_ZIP}; +use crate::tacview::Coalition; +use std::fs::File; +use std::io::Write; +use zip::write::FileOptions; +use zip::ZipWriter; + +pub fn create_writer(is_zip: bool, filename: &str) -> Box { + let base_name = remove_extension(filename, is_zip); + if is_zip { + Box::new(create_zipwriter( + &format!("{base_name}{EXTENSION_ZIP}"), + &format!("{base_name}{EXTENSION_TXT}"), + )) + } else { + Box::new(create_textwriter(base_name)) + } +} + +fn remove_extension(filename: &str, is_zip: bool) -> &str { + if is_zip { + &filename[..(filename.len() - EXTENSION_ZIP.len())] + } else { + &filename[..(filename.len() - EXTENSION_TXT.len())] + } +} + +fn create_textwriter(filename: &str) -> File { + File::create(filename).unwrap() +} + +fn create_zipwriter(outer_name: &str, inner_name: &str) -> ZipWriter { + let mut writer = ZipWriter::new(create_textwriter(outer_name)); + writer + .start_file( + inner_name, + FileOptions::default().compression_method(zip::CompressionMethod::Deflated), + ) + .unwrap(); + writer +} +pub trait StringWriter { + fn write_line>(&mut self, line: &S); + + fn write_strings>(&mut self, lines: &[S]); + + fn write_for_coalition>( + &mut self, + lines: &[S], + coalition_per_line: &[Coalition], + coalition: Coalition, + ); +} + +impl StringWriter for T +where + T: Write, +{ + fn write_line>(&mut self, line: &S) { + writeln!(self, "{}", line.as_ref()).unwrap(); + } + + fn write_strings>(&mut self, lines: &[S]) { + for line in lines { + self.write_line(line); + } + } + + fn write_for_coalition>( + &mut self, + lines: &[S], + coalition_per_line: &[Coalition], + coalition: Coalition, + ) { + assert_eq!(lines.len(), coalition_per_line.len()); + lines + .iter() + .zip(coalition_per_line) + .filter(|(_, c)| **c == coalition || **c == Coalition::All) + .for_each(|(l, _)| self.write_line(l)); + } +}