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
+}
+