#[path = "./cmd_get.rs"]
pub mod cmd_get;
pub use cmd_get::*;
use clap::{Args, Parser, Subcommand};
use s2protocol::WriteArrowIpcProps;
use std::collections::HashMap;
use std::path::PathBuf;
use syntect::easy::HighlightLines;
use syntect::highlighting::{Color, Style, ThemeSet};
use syntect::parsing::SyntaxSet;
use syntect::util::{LinesWithEndings, as_24_bit_terminal_escaped};
use s2protocol::dir_stats::{SC2ReplaysDirStats, scan_path};
use s2protocol::filters::SC2ReplayFilters;
use s2protocol::game_events::VersionedBalanceUnit;
use s2protocol::game_events::ability::balance_data::json_handler::*;
use s2protocol::game_events::ability::traverse_versioned_balance_abilities;
use s2protocol::generator::proto_morphist::ProtoMorphist;
use s2protocol::tracker_events::{unit_tag_index, unit_tag_recycle};
#[derive(Subcommand, Debug, Clone)]
pub enum CommandUtils {
XlateTagToIndexRecycle { tag: i64 },
}
#[derive(Args, Debug, Clone)]
pub struct WriteArrowIpcPropsArgs {
#[arg(long, default_value = "1000000")]
pub scan_max_files: usize,
#[arg(long, default_value = "1000000")]
pub process_max_files: usize,
#[arg(long, default_value = "8")]
pub traverse_max_depth: usize,
#[arg(long)]
pub min_version: Option<u32>,
#[arg(long)]
pub max_version: Option<u32>,
}
impl From<WriteArrowIpcPropsArgs> for s2protocol::WriteArrowIpcProps {
fn from(src: WriteArrowIpcPropsArgs) -> WriteArrowIpcProps {
let WriteArrowIpcPropsArgs {
scan_max_files,
process_max_files,
traverse_max_depth,
min_version,
max_version,
} = src;
WriteArrowIpcProps {
scan_max_files,
process_max_files,
traverse_max_depth,
min_version,
max_version,
}
}
}
#[derive(Subcommand, Debug, Clone)]
pub enum Commands {
Generate,
BalanceDataToJson,
Scan,
#[command(subcommand)]
Get(ReadTypes),
WriteArrowIpc(WriteArrowIpcPropsArgs),
#[command(subcommand)]
Util(CommandUtils),
}
#[derive(Parser, Debug, Clone)]
#[command(author, version, about, long_about = None)]
pub struct Cli {
#[arg(short, long)]
source: String,
#[arg(short, long, default_value = "")]
xml_balance_data_dir: String,
#[arg(short, long, default_value = "")]
json_balance_data_dir: String,
#[arg(short, long, default_value = "info")]
verbosity_level: String,
#[command(subcommand)]
command: Commands,
#[arg(short, long)]
output: Option<String>,
#[arg(short, long, default_value = "false")]
timing: bool,
#[arg(short, long, default_value = "false")]
color: bool,
#[arg(long)]
pub player_id: Option<u8>,
#[arg(long)]
pub min_loop: Option<i64>,
#[arg(long)]
pub max_loop: Option<i64>,
#[arg(long)]
pub event_type: Option<String>,
#[arg(long)]
pub unit_name: Option<String>,
#[arg(long)]
pub max_events: Option<usize>,
#[arg(long)]
pub include_stats: bool,
#[arg(long, default_value = "false")]
pub quiet: bool,
#[arg(long, default_value = "false")]
pub disable_paralellism: bool,
#[arg(long, default_value = "false")]
pub tui: bool,
}
pub fn syntect_json_highlight<'a>(
json_str: &'a str,
syntect_syntax_set: &SyntaxSet,
syntect_theme_set: &ThemeSet,
) -> Vec<(Style, &'a str)> {
let mut res: Vec<(Style, &str)> = vec![];
let syntax = syntect_syntax_set.find_syntax_by_extension("json").unwrap();
let mut highlighter =
HighlightLines::new(syntax, &syntect_theme_set.themes["base16-ocean.dark"]);
for line in LinesWithEndings::from(json_str) {
let ranges: Vec<(Style, &str)> = highlighter
.highlight_line(line, syntect_syntax_set)
.unwrap();
res.extend(ranges);
}
res
}
pub fn syntect_json_print(
json_str: String,
syntect_syntax_set: &SyntaxSet,
syntect_theme_set: &ThemeSet,
) {
syntect_json_highlight(&json_str, syntect_syntax_set, syntect_theme_set)
.iter()
.for_each(|segment| {
print!("{}", as_24_bit_terminal_escaped(&[*segment], false));
});
}
pub fn process_cli_request() -> Result<(), Box<dyn std::error::Error>> {
let init_time = std::time::Instant::now();
let cli = Cli::parse();
let level = match cli.verbosity_level.as_str() {
"error" => tracing::Level::ERROR,
"warn" => tracing::Level::WARN,
"info" => tracing::Level::INFO,
"debug" => tracing::Level::DEBUG,
"trace" => tracing::Level::TRACE,
_ => {
tracing::warn!("Invalid verbosity level, defaulting to INFO");
tracing::Level::INFO
}
};
let color = cli.color;
if color {
tracing_subscriber::fmt()
.with_max_level(level)
.with_ansi(true)
.with_env_filter(level.to_string())
.init();
} else {
tracing_subscriber::fmt()
.with_max_level(level)
.with_ansi(false)
.with_env_filter(level.to_string())
.init();
}
let syntect_syntax_set = SyntaxSet::load_defaults_newlines();
let mut syntect_theme_set = ThemeSet::load_defaults();
{
syntect_theme_set
.themes
.get_mut("base16-ocean.dark")
.unwrap()
.settings
.background = Some(Color {
r: 0x00,
g: 0x00,
b: 0x00,
a: 0x00,
});
}
let versioned_abilities: HashMap<(u32, String), VersionedBalanceUnit> =
if cli.json_balance_data_dir.is_empty() {
read_balance_data_from_included_assets()?
} else {
read_balance_data_from_json_dir(PathBuf::from(&cli.json_balance_data_dir))?
};
match &cli.command {
Commands::Generate => {
ProtoMorphist::r#gen(&cli.source, &cli.output.expect("Requires --output"))?;
}
Commands::BalanceDataToJson => {
if cli.source.is_empty() {
return Err(Box::new(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"Source XML Balance Data directory must be provided",
)));
}
if cli.json_balance_data_dir.is_empty() {
return Err(Box::new(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"Destination JSON Balance Data directory must be provided",
)));
}
let versioned_abilities =
traverse_versioned_balance_abilities(PathBuf::from(&cli.source))?;
write_balance_data_to_json(&cli.json_balance_data_dir, versioned_abilities)?;
}
Commands::Get(read_type) => {
cmd_get::handle_get_cmd(&cli, read_type, syntect_syntax_set, syntect_theme_set)?;
}
Commands::WriteArrowIpc(cmd) => {
s2protocol::ArrowIpcTypes::handle_arrow_ipc_cmd(
PathBuf::from(&cli.source),
PathBuf::from(&cli.output.expect("Requires --output")),
&cmd.to_owned().into(),
&versioned_abilities,
cli.disable_paralellism,
)?;
}
Commands::Util(cmd) => match cmd {
CommandUtils::XlateTagToIndexRecycle { tag } => {
let index = unit_tag_index(*tag);
let recycle = unit_tag_recycle(*tag);
println!("Index: {}, Recycle: {}", index, recycle)
}
},
Commands::Scan => {
let stats = handle_scan_cli_cmd(&cli, &versioned_abilities)?;
tracing::info!("Scan complete: {:?}", stats);
}
}
if cli.timing {
println!("Total time: {:?}", init_time.elapsed());
}
Ok(())
}
pub fn handle_scan_cli_cmd(
cli: &Cli,
unit_abilities: &HashMap<(u32, String), VersionedBalanceUnit>,
) -> Result<SC2ReplaysDirStats, Box<dyn std::error::Error>> {
scan_path(&cli.source, unit_abilities, false)
}
impl From<Cli> for SC2ReplayFilters {
fn from(cli: Cli) -> Self {
SC2ReplayFilters {
player_id: cli.player_id,
min_loop: cli.min_loop,
max_loop: cli.max_loop,
event_type: cli.event_type,
unit_name: cli.unit_name,
max_events: cli.max_events,
include_stats: cli.include_stats,
..Default::default()
}
}
}