mirror of
https://github.com/132nd-vWing/tacview-splitter.git
synced 2026-08-26 07:58:36 +02:00
refactor
This commit is contained in:
+1
-1
@@ -5,7 +5,7 @@ authors = ["132nd-Professor <132nd-Professor>"]
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
zip = ">= 0.6.2"
|
zip = {version = ">= 0.6.2", default-features = false, features = ["deflate"]}
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = true
|
lto = true
|
||||||
|
|||||||
@@ -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 = '-';
|
||||||
+63
-21
@@ -1,6 +1,7 @@
|
|||||||
pub mod lib {
|
pub mod lib {
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
use std::hash::Hash;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
|
||||||
use zip;
|
use zip;
|
||||||
@@ -9,11 +10,32 @@ pub mod lib {
|
|||||||
const ERR_CANNOT_OPEN_OUTPUT: &str = "Could not open output file";
|
const ERR_CANNOT_OPEN_OUTPUT: &str = "Could not open output file";
|
||||||
const ERR_CANNOT_BEGIN_FILE: &str = "Could not begin file in zip archive";
|
const ERR_CANNOT_BEGIN_FILE: &str = "Could not begin file in zip archive";
|
||||||
|
|
||||||
pub struct IDs<'a> {
|
pub struct IDs<'a, S: AsRef<str> + Hash + Eq> {
|
||||||
pub blue: HashSet<&'a str>,
|
pub blue: HashSet<&'a S>,
|
||||||
pub red: HashSet<&'a str>,
|
pub red: HashSet<&'a S>,
|
||||||
pub violet: HashSet<&'a str>,
|
pub violet: HashSet<&'a S>,
|
||||||
pub unknown: HashSet<&'a str>,
|
pub unknown: HashSet<&'a S>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, S: AsRef<str> + 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<str> + Hash + Eq> Default for IDs<'a, S> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Descriptors<T: Write> {
|
pub struct Descriptors<T: Write> {
|
||||||
@@ -39,31 +61,51 @@ pub mod lib {
|
|||||||
pub violet: String,
|
pub violet: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct BodiesByCoalition<'a> {
|
pub struct BodiesByCoalition<'a, S: AsRef<str>> {
|
||||||
pub blue: Vec<&'a str>,
|
pub blue: Vec<&'a S>,
|
||||||
pub red: Vec<&'a str>,
|
pub red: Vec<&'a S>,
|
||||||
pub violet: Vec<&'a str>,
|
pub violet: Vec<&'a S>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, S: AsRef<str>> BodiesByCoalition<'a, S> {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
blue: vec![],
|
||||||
|
red: vec![],
|
||||||
|
violet: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, S: AsRef<str>> Default for BodiesByCoalition<'a, S> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Handling {
|
pub trait Handling {
|
||||||
fn write(&mut self, header: Vec<String>, bodies_by_coalition: BodiesByCoalition);
|
fn write<S: AsRef<str>>(&mut self, header: &[S], bodies_by_coalition: BodiesByCoalition<S>);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Write> Handling for Descriptors<T> {
|
impl<T: Write> Handling for Descriptors<T> {
|
||||||
fn write(&mut self, header: Vec<String>, bodies_by_coalition: BodiesByCoalition) {
|
fn write<S: AsRef<str>>(
|
||||||
for line in &header {
|
&mut self,
|
||||||
writeln!(self.blue, "{}", line).expect(ERR_CANNOT_WRITE_DATA);
|
header: &[S],
|
||||||
writeln!(self.red, "{}", line).expect(ERR_CANNOT_WRITE_DATA);
|
bodies_by_coalition: BodiesByCoalition<S>,
|
||||||
writeln!(self.violet, "{}", line).expect(ERR_CANNOT_WRITE_DATA);
|
) {
|
||||||
|
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 {
|
for line in bodies_by_coalition.blue {
|
||||||
writeln!(self.blue, "{}", line).expect(ERR_CANNOT_WRITE_DATA);
|
writeln!(self.blue, "{}", line.as_ref()).expect(ERR_CANNOT_WRITE_DATA);
|
||||||
}
|
}
|
||||||
for line in &bodies_by_coalition.red {
|
for line in bodies_by_coalition.red {
|
||||||
writeln!(self.red, "{}", line).expect(ERR_CANNOT_WRITE_DATA);
|
writeln!(self.red, "{}", line.as_ref()).expect(ERR_CANNOT_WRITE_DATA);
|
||||||
}
|
}
|
||||||
for line in &bodies_by_coalition.violet {
|
for line in bodies_by_coalition.violet {
|
||||||
writeln!(self.violet, "{}", line).expect(ERR_CANNOT_WRITE_DATA);
|
writeln!(self.violet, "{}", line.as_ref()).expect(ERR_CANNOT_WRITE_DATA);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+19
-222
@@ -1,231 +1,28 @@
|
|||||||
use std::collections::HashSet;
|
mod constants;
|
||||||
use std::io::{BufRead, BufReader};
|
mod processor;
|
||||||
use std::{fs, str};
|
mod reader;
|
||||||
|
mod tacview;
|
||||||
|
mod writer;
|
||||||
|
|
||||||
use tacview_splitter::lib;
|
use crate::{tacview::Coalition, writer::StringWriter};
|
||||||
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,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let (input_filename, is_zip) = find_input_file();
|
let (input_filename, is_zip) = reader::find_input_file();
|
||||||
println!("Processing {}", input_filename);
|
println!("Processing {}", input_filename);
|
||||||
let lines = read_data(&input_filename, is_zip);
|
let lines = reader::read_data(&input_filename, is_zip);
|
||||||
let (header, body) = split_into_header_and_body(lines);
|
let (header, body) = processor::split_into_header_and_body(&lines);
|
||||||
let bodies_by_coalition = divide_body_by_coalition(&body);
|
|
||||||
|
|
||||||
let output_filenames = get_output_filenames(&input_filename, is_zip);
|
let coalition_per_line = processor::divide_body_by_coalition(body);
|
||||||
if is_zip {
|
|
||||||
let mut descriptors = lib::Descriptors::<zip::ZipWriter<fs::File>>::new(output_filenames);
|
|
||||||
descriptors.write(header, bodies_by_coalition);
|
|
||||||
} else {
|
|
||||||
let mut descriptors = lib::Descriptors::<fs::File>::new(output_filenames);
|
|
||||||
descriptors.write(header, bodies_by_coalition);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn split_into_header_and_body(lines: Vec<String>) -> (Vec<String>, Vec<String>) {
|
let mut blue_writer = writer::create_writer(is_zip, &input_filename);
|
||||||
let mut i = 0;
|
blue_writer.write_strings(header);
|
||||||
for line in &lines {
|
blue_writer.write_for_coalition(body, &coalition_per_line, Coalition::Blue);
|
||||||
if line.chars().next().expect("malformed line") == COMMENT {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
(lines[..i].to_vec(), lines[i..].to_vec())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn divide_body_by_coalition(body: &Vec<String>) -> lib::BodiesByCoalition {
|
let mut red_writer = writer::create_writer(is_zip, &input_filename);
|
||||||
let mut bbc = lib::BodiesByCoalition {
|
red_writer.write_strings(header);
|
||||||
blue: Vec::new(),
|
red_writer.write_for_coalition(body, &coalition_per_line, Coalition::Red);
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
fn process_line<'a>(
|
let mut purple_writer = writer::create_writer(is_zip, &input_filename);
|
||||||
continued: bool,
|
purple_writer.write_strings(header);
|
||||||
coalitions: &mut lib::IDs<'a>,
|
purple_writer.write_for_coalition(body, &coalition_per_line, Coalition::Purple);
|
||||||
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<String> {
|
|
||||||
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<String> = inner_buf
|
|
||||||
.lines()
|
|
||||||
.map(|l| l.expect("Could not parse line"))
|
|
||||||
.collect();
|
|
||||||
lines
|
|
||||||
} else {
|
|
||||||
let lines: Vec<String> = buf
|
|
||||||
.lines()
|
|
||||||
.map(|l| l.expect("Could not parse line"))
|
|
||||||
.collect();
|
|
||||||
lines
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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<S: AsRef<str>>(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<S: AsRef<str> + Hash + Eq>(body: &[S]) -> Vec<Coalition> {
|
||||||
|
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<str>>(
|
||||||
|
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<str>>(
|
||||||
|
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<str>>(
|
||||||
|
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<S: AsRef<str>>(current_line: &S) -> bool {
|
||||||
|
current_line.as_ref().ends_with('\\')
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_id_from_line<S: AsRef<str>>(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<S: AsRef<str>>(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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<String> {
|
||||||
|
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<File>) -> Vec<String> {
|
||||||
|
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<T: BufRead>(buf: T) -> Vec<String> {
|
||||||
|
let lines: Vec<String> = buf
|
||||||
|
.lines()
|
||||||
|
.map(|l| l.expect("Could not parse line"))
|
||||||
|
.collect();
|
||||||
|
lines
|
||||||
|
}
|
||||||
@@ -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<S: AsRef<str>>(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<S: AsRef<str>>(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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<dyn Write> {
|
||||||
|
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<File> {
|
||||||
|
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<S: AsRef<str>>(&mut self, line: &S);
|
||||||
|
|
||||||
|
fn write_strings<S: AsRef<str>>(&mut self, lines: &[S]);
|
||||||
|
|
||||||
|
fn write_for_coalition<S: AsRef<str>>(
|
||||||
|
&mut self,
|
||||||
|
lines: &[S],
|
||||||
|
coalition_per_line: &[Coalition],
|
||||||
|
coalition: Coalition,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> StringWriter for T
|
||||||
|
where
|
||||||
|
T: Write,
|
||||||
|
{
|
||||||
|
fn write_line<S: AsRef<str>>(&mut self, line: &S) {
|
||||||
|
writeln!(self, "{}", line.as_ref()).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_strings<S: AsRef<str>>(&mut self, lines: &[S]) {
|
||||||
|
for line in lines {
|
||||||
|
self.write_line(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_for_coalition<S: AsRef<str>>(
|
||||||
|
&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));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user