From ce4822dd4600477a0bddc80bfef66ca65c692fdc Mon Sep 17 00:00:00 2001 From: 132nd-Professor <132nd-Professor> Date: Sun, 27 Jun 2021 16:22:16 +0200 Subject: [PATCH] first commit --- .gitignore | 118 +++++++++++ .idea/.gitignore | 8 + .idea/inspectionProfiles/Project_Default.xml | 24 +++ .../inspectionProfiles/profiles_settings.xml | 6 + .idea/misc.xml | 4 + .idea/modules.xml | 8 + .idea/tacview-splitter.iml | 11 + .idea/vcs.xml | 6 + src/lib.rs | 88 ++++++++ src/main.rs | 196 ++++++++++++++++++ 10 files changed, 469 insertions(+) create mode 100644 .idea/.gitignore create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/tacview-splitter.iml create mode 100644 .idea/vcs.xml create mode 100644 src/lib.rs create mode 100644 src/main.rs diff --git a/.gitignore b/.gitignore index 8ffd9f4..3600152 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,121 @@ Cargo.lock # These are backup files generated by rustfmt **/*.rs.bk +*.txt.acmi +*.zip.acmi +testfiles +perf.data* +flamegraph*svg + + +# Created by https://www.toptal.com/developers/gitignore/api/intellij +# Edit at https://www.toptal.com/developers/gitignore?templates=intellij + +### Intellij ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### Intellij Patch ### +# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 + +# *.iml +# modules.xml +# .idea/misc.xml +# *.ipr + +# Sonarlint plugin +# https://plugins.jetbrains.com/plugin/7973-sonarlint +.idea/**/sonarlint/ + +# SonarQube Plugin +# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin +.idea/**/sonarIssues.xml + +# Markdown Navigator plugin +# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced +.idea/**/markdown-navigator.xml +.idea/**/markdown-navigator-enh.xml +.idea/**/markdown-navigator/ + +# Cache file creation bug +# See https://youtrack.jetbrains.com/issue/JBR-2257 +.idea/$CACHE_FILE$ + +# CodeStream plugin +# https://plugins.jetbrains.com/plugin/12206-codestream +.idea/codestream.xml + +# End of https://www.toptal.com/developers/gitignore/api/intellij + diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..73f69e0 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..a7fdc7b --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,24 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..65531ca --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..c9b434f --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/tacview-splitter.iml b/.idea/tacview-splitter.iml new file mode 100644 index 0000000..d355f80 --- /dev/null +++ b/.idea/tacview-splitter.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..2fd7904 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,88 @@ +pub mod lib { + use std::io::Write; + use std::fs; + use zip; + + const ERR_CANNOT_WRITE_DATA: &str = "Could not write data"; + + pub struct IDs<'a> { + pub blue: Vec<&'a str>, + pub red: Vec<&'a str>, + pub violet: Vec<&'a str>, + pub unknown: Vec<&'a str>, + } + + pub struct DescriptorsZip { + blue: zip::ZipWriter, + red: zip::ZipWriter, + violet: zip::ZipWriter, + } + + pub struct DescriptorsTxt { + pub blue: fs::File, + pub red: fs::File, + pub violet: fs::File, + } + + pub struct OutputFilenames { + pub txt: FilenamesVariant, + pub zip: FilenamesVariant, + } + + pub struct FilenamesVariant { + pub blue: String, + pub red: String, + pub violet: String, + } + + pub struct BodiesByCoalition<'a> { + pub blue: Vec<&'a str>, + pub red: Vec<&'a str>, + pub violet: Vec<&'a str>, + } + + pub trait WriteData { + fn write(&mut self, header: Vec, bodies_by_coalition: BodiesByCoalition); + } + + impl WriteData for DescriptorsTxt { + fn write(&mut self, header: Vec, bodies_by_coalition: BodiesByCoalition) { + for line in &header { + write!(self.blue, "{}\n", line).expect(ERR_CANNOT_WRITE_DATA); + write!(self.red, "{}\n", line).expect(ERR_CANNOT_WRITE_DATA); + write!(self.violet, "{}\n", line).expect(ERR_CANNOT_WRITE_DATA); + } + for line in &bodies_by_coalition.blue { + write!(self.blue, "{}\n", line).expect(ERR_CANNOT_WRITE_DATA); + } + for line in &bodies_by_coalition.red { + write!(self.red, "{}\n", line).expect(ERR_CANNOT_WRITE_DATA); + } + for line in &bodies_by_coalition.violet { + write!(self.violet, "{}\n", line).expect(ERR_CANNOT_WRITE_DATA); + } + } + } + + impl WriteData for DescriptorsZip { + fn write(&mut self, header: Vec, bodies_by_coalition: BodiesByCoalition) { + for line in &header { + write!(self.blue, "{}\n", line).expect(ERR_CANNOT_WRITE_DATA); + write!(self.red, "{}\n", line).expect(ERR_CANNOT_WRITE_DATA); + write!(self.violet, "{}\n", line).expect(ERR_CANNOT_WRITE_DATA); + } + for line in &bodies_by_coalition.blue { + write!(self.blue, "{}\n", line).expect(ERR_CANNOT_WRITE_DATA); + } + for line in &bodies_by_coalition.red { + write!(self.red, "{}\n", line).expect(ERR_CANNOT_WRITE_DATA); + } + for line in &bodies_by_coalition.violet { + write!(self.violet, "{}\n", line).expect(ERR_CANNOT_WRITE_DATA); + } + + } + } + +} + diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..791a1d5 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,196 @@ +use std::{fs, str}; + +use std::io::{BufRead, BufReader}; +use zip; +use tacview_splitter::lib; +use tacview_splitter::lib::WriteData; + +const COMMENT: char = '#'; +const MINUS: char = '-'; +const EXTENSION_ZIP: &str = ".zip.acmi"; +const EXTENSION_TXT: &str = ".txt.acmi"; + +const UNKNOWN: u8 = 0; +const TIMESTAMP: u8 = 1; +const DESTRUCTION: u8 = 2; +const TELEMETRY: u8 = 3; + +const ERR_CANNOT_OPEN_OUTPUT: &str = "Could not open output file"; + + +fn main() { + let (input_filename, is_zip) = 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 output_filenames = get_output_filenames(&input_filename); + if is_zip { + let mut descriptors = get_descriptors_zip(output_filenames); + descriptors.write(header, bodies_by_coalition) + } else { + let mut descriptors = get_descriptors_txt(output_filenames); + descriptors.write(header, bodies_by_coalition) + } +} + +fn split_into_header_and_body(lines: Vec) -> (Vec, Vec) { + let mut i=0; + for line in &lines { + if line.chars().nth(0).expect("malformed line") == COMMENT { + break + } + i += 1; + } + return (lines[..i].to_vec(), lines[i..].to_vec()); +} + +fn divide_body_by_coalition(body: &'static Vec) -> lib::BodiesByCoalition<'static> { + let mut bbc = lib::BodiesByCoalition{blue: Vec::new(), red: Vec::new(), violet: Vec::new()}; + let mut continued = false; + let mut line_type: u8 = UNKNOWN; + let mut ids = lib::IDs{blue: Vec::new(), red: Vec::new(), violet: Vec::new(), unknown: Vec::new()}; + for line in body { + let result = process_line(continued, &mut ids, line, line_type); + line_type = result.0; + continued = result.1; + let id = result.2; + if line_type == TIMESTAMP { + bbc.blue.push(line); + bbc.red.push(line); + bbc.violet.push(line); + } else { // destruction or telemetry + if ids.blue.contains(&id) { + bbc.blue.push(line); + } else if ids.red.contains(&id) { + bbc.red.push(line); + } else if ids.violet.contains(&id) { + bbc.violet.push(line); + } + } + } + bbc +} + +fn process_line<'a>(continued: bool, ids: &mut lib::IDs<'a>, line: &'a String, last_line_type: u8) -> (u8, bool, &'a str) { + let mut id = "both"; + let line_type; + let will_continue: bool; + if !continued { + let first_char = line.chars().nth(0).expect("malformed line"); + if first_char == COMMENT { + line_type = TIMESTAMP; + } else if first_char == MINUS { + line_type = DESTRUCTION; + } else { + line_type = TELEMETRY; + } + + if line_type == TELEMETRY { + id = line + .split_once(',') + .unwrap() + .0; + //let id_str = id.to_string(); + if line.contains("Color=") { + if line.contains("Color=Blue") { + ids.blue.push(id); + } else if line.contains("Color=Red") { + ids.red.push(id); + } else if line.contains("Color=Violet") { + ids.violet.push(id); + } else { + ids.unknown.push(id); + } + } + } + } else { + line_type = last_line_type; + } + if line.ends_with("\\") { + will_continue = true; + } else { + will_continue = false; + } + (line_type, will_continue, id) +} + +fn get_output_filenames(input_filename: &String) -> lib::OutputFilenames { + let blue = input_filename.replace(".zip", "_blue.zip"); + let red = input_filename.replace(".zip", "_red.zip"); + let violet = input_filename.replace(".zip", "_violet.zip"); + let output_filenames_zip = lib::FilenamesVariant{blue, red, violet}; + + let blue = input_filename.replace(".zip", "_blue.txt"); + let red = input_filename.replace(".zip", "_red.txt"); + let violet = input_filename.replace(".zip", "_violet.txt"); + let output_filenames_txt = lib::FilenamesVariant{blue, red, violet}; + + let output_filenames = lib::OutputFilenames{txt: output_filenames_txt, zip: output_filenames_zip}; + output_filenames +} + +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).unwrap(); + 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 + } +} + +fn get_descriptors_zip(filenames: lib::OutputFilenames) -> lib::DescriptorsZip { + let options = zip::write::FileOptions::default().compression_method(zip::CompressionMethod::Deflated); + + let file = fs::File::create(&filenames.zip.blue).expect(ERR_CANNOT_OPEN_OUTPUT); + let mut blue = zip::ZipWriter::new(file); + blue.start_file(&filenames.txt.blue, options).unwrap(); + + let file = fs::File::create(&filenames.zip.red).expect(ERR_CANNOT_OPEN_OUTPUT); + let mut red = zip::ZipWriter::new(file); + red.start_file(&filenames.txt.red, options).unwrap(); + + let file = fs::File::create(&filenames.zip.violet).expect(ERR_CANNOT_OPEN_OUTPUT); + let mut violet = zip::ZipWriter::new(file); + violet.start_file(&filenames.txt.violet, options).unwrap(); + + let descriptors = lib::DescriptorsZip{blue, red, violet}; + descriptors +} + +fn get_descriptors_txt(filenames: lib::OutputFilenames) -> lib::DescriptorsTxt { + let blue = fs::File::create(&filenames.txt.blue).expect(ERR_CANNOT_OPEN_OUTPUT); + let red = fs::File::create(&filenames.txt.red).expect(ERR_CANNOT_OPEN_OUTPUT); + let violet = fs::File::create(&filenames.txt.violet).expect(ERR_CANNOT_OPEN_OUTPUT); + let descriptors = lib::DescriptorsTxt{blue, red, violet}; + descriptors +} +