use clap::{ArgAction, Command, CommandFactory, Parser};
use std::error::Error;
use std::fs::File;
use std::io::{self, Cursor, Read, Write};
use std::path::{Path, PathBuf};
use crate::header::parse_header;
#[derive(Clone, Copy)]
struct PrefixAliasSpec {
arg_id: &'static str,
base: &'static str,
min_len: usize,
}
const PREFIX_ALIASES: &[PrefixAliasSpec] = &[
PrefixAliasSpec {
arg_id: "input",
base: "input",
min_len: 2,
},
PrefixAliasSpec {
arg_id: "phase_reference",
base: "phase",
min_len: 2,
},
PrefixAliasSpec {
arg_id: "phase_reference",
base: "phase-reference",
min_len: "phase-r".len(),
},
PrefixAliasSpec {
arg_id: "length",
base: "length",
min_len: 2,
},
PrefixAliasSpec {
arg_id: "skip",
base: "skip",
min_len: 2,
},
PrefixAliasSpec {
arg_id: "loop_",
base: "loop",
min_len: 2,
},
PrefixAliasSpec {
arg_id: "plot",
base: "plot",
min_len: 2,
},
PrefixAliasSpec {
arg_id: "frequency",
base: "frequency",
min_len: 3,
},
PrefixAliasSpec {
arg_id: "cor2bin",
base: "cor2bin",
min_len: "cor2b".len(),
},
PrefixAliasSpec {
arg_id: "spectrum",
base: "spectrum",
min_len: 4,
},
PrefixAliasSpec {
arg_id: "output",
base: "output",
min_len: 2,
},
PrefixAliasSpec {
arg_id: "rate_padding",
base: "rate-padding",
min_len: "rate-p".len(),
},
PrefixAliasSpec {
arg_id: "cumulate",
base: "cumulate",
min_len: 2,
},
PrefixAliasSpec {
arg_id: "add_plot",
base: "add-plot",
min_len: "add".len(),
},
PrefixAliasSpec {
arg_id: "raw_visibility",
base: "raw-visibility",
min_len: 2,
},
];
const EXPLICIT_ALIASES: &[(&str, &[&str])] = &[
("closure_phase", &["cp"]),
("cor2bin", &["c2b"]),
("delay_correct", &["delay", "delay-corr"]),
("rate_correct", &["rate", "rate-corr"]),
("acel_correct", &["acel", "acel-corr"]),
("drange", &["delay-w", "delay-win"]),
("rrange", &["rate-w", "rate-win"]),
("in_beam", &["inbeam", "in-beam-vlbi"]),
("dynamic_spectrum", &["ds", "dynamic"]),
("bandpass", &["bp"]),
("bandpass_table", &["bptable"]),
("flagging", &["flag"]),
("allan_deviance", &["allan", "allan-dev"]),
("fringe_rate_map", &["frmap"]),
("folding", &["fold"]),
("multi_sideband", &["msb"]),
];
fn with_aliases(mut command: Command) -> Command {
for spec in PREFIX_ALIASES {
command = add_prefix_aliases(command, spec);
}
for (arg_id, aliases) in EXPLICIT_ALIASES {
command = command.mut_arg(*arg_id, |arg| arg.aliases(*aliases));
}
command
}
fn add_prefix_aliases(mut command: Command, spec: &PrefixAliasSpec) -> Command {
for end in spec.min_len..spec.base.len() {
if !spec.base.is_char_boundary(end) {
continue;
}
let alias = &spec.base[..end];
command = command.mut_arg(spec.arg_id, |arg| arg.alias(alias));
}
command
}
#[derive(Parser, Debug, Clone)]
#[command(
name = "frinZ",
version = env!("CARGO_PKG_VERSION"),
author = "Masanori AKIMOTO <masanori.akimoto.ac@gmail.com>",
about = "fringe search for Yamaguchi Interferometer and Japanese VLBI Network",
after_help = r#"(c) M.AKIMOTO with Gemini in 2025/08/04
github: https://github.com/M-AKIMOTOO/frinZrs
This program is licensed under the MIT License
see https://opensource.org/license/mit"#
)]
pub struct Args {
#[arg(long)]
pub input: Option<PathBuf>,
#[arg(long, num_args = 2..=6, value_names = ["CALIBRATOR", "TARGET", "FIT_SPEC", "CAL_LENGTH", "TGT_LENGTH", "LOOP"])]
pub phase_reference: Vec<String>,
#[arg(long = "closure-phase", num_args = 0.., value_name = "FILE|KEY:VALUE")]
pub closure_phase: Option<Vec<String>>,
#[arg(long, default_value_t = 0)]
pub length: i32,
#[arg(long, default_value_t = 0)]
pub skip: i32,
#[arg(long, default_value_t = 1)]
pub loop_: i32,
#[arg(long, num_args = 1.., value_name = "MIN,MAX")]
pub rfi: Vec<String>,
#[arg(long)]
pub plot: bool,
#[arg(long)]
pub frequency: bool,
#[arg(long)]
pub cor2bin: bool,
#[arg(long)]
pub spectrum: bool,
#[arg(long)]
pub output: bool,
#[arg(long, default_value_t = 0.0, allow_negative_numbers = true)]
pub delay_correct: f32,
#[arg(long, default_value_t = 0.0, allow_negative_numbers = true)]
pub rate_correct: f32,
#[arg(long, default_value_t = 0.0, allow_negative_numbers = true)]
pub acel_correct: f32,
#[arg(long, value_name = "FILE")]
pub scan_correct: Option<PathBuf>,
#[arg(
long = "drange",
num_args = 2,
value_name = "MIN MAX",
allow_negative_numbers = true
)]
pub drange: Vec<f32>,
#[arg(
long = "rrange",
num_args = 2,
value_name = "MIN MAX",
allow_negative_numbers = true
)]
pub rrange: Vec<f32>,
#[arg(
long,
num_args = 4,
value_name = "DELAY_MIN DELAY_MAX RATE_MIN RATE_MAX",
allow_negative_numbers = true
)]
pub mask: Vec<f32>,
#[arg(long = "frange", num_args = 2, value_name = "MIN MAX")]
pub frange: Vec<f32>,
#[arg(long, default_value_t = 1)]
pub rate_padding: u32,
#[arg(long, default_value_t = 0)]
pub cumulate: i32,
#[arg(long)]
pub add_plot: bool,
#[arg(long)]
pub wwz: bool,
#[arg(long)]
pub header: bool,
#[arg(long, value_name = "POINTS")]
pub fft_rebin: Option<i32>,
#[arg(
long,
num_args = 0..=1,
default_missing_value = "peak",
value_name = "MODE",
value_parser = ["peak", "deep", "deep2", "rate", "acel"],
action = ArgAction::Append
)]
pub search: Vec<String>,
#[arg(long = "in-beam")]
pub in_beam: bool,
#[arg(long, default_value_t = 5)]
pub iter: u32,
#[arg(long)]
pub dynamic_spectrum: bool,
#[arg(long)]
pub bandpass: Option<PathBuf>,
#[arg(long = "norm-acf")]
pub norm_acf: bool,
#[arg(long)]
pub bandpass_table: bool,
#[arg(long, default_value_t = 0)]
pub cpu: u32,
#[arg(long, num_args = 1.., value_name = "MODE [ARGS...]")]
pub flagging: Vec<String>,
#[arg(long)]
pub allan_deviance: bool,
#[arg(long)]
pub raw_visibility: bool,
#[arg(long, num_args = 0..=1, default_missing_value = "1")]
pub uv: Option<i32>,
#[arg(long, num_args = 0.., value_name = "KEY[:VALUE]")]
pub fringe_rate_map: Option<Vec<String>>,
#[arg(long, num_args = 1.., value_name = "KEY:VALUE")]
pub maser: Vec<String>,
#[arg(long, num_args = 1.., value_name = "KEY:VALUE")]
pub folding: Vec<String>,
#[arg(long, num_args = 6, value_names = ["C_COR", "C_BP", "C_DELAY", "X_COR", "X_BP", "X_DELAY"], allow_negative_numbers = true)]
pub multi_sideband: Vec<String>,
#[arg(long)]
pub uptimeplot: bool,
#[arg(long, num_args = 0.., value_name = "KEY[:VALUE]", requires = "input")]
pub imaging: Option<Vec<String>>,
#[arg(long)]
pub imaging_test: bool,
#[arg(long)]
pub detail: bool,
}
impl Args {
pub fn command_with_aliases() -> Command {
with_aliases(Self::command())
}
pub fn primary_search_mode(&self) -> Option<&str> {
self.search
.iter()
.find(|mode| *mode == "peak" || *mode == "deep" || *mode == "deep2")
.map(|s| s.as_str())
}
}
pub fn check_memory_usage(args: &Args, input_path: &Path) -> Result<bool, Box<dyn Error>> {
let mut file = File::open(input_path)?;
let mut buffer = vec![0; 256];
file.read_exact(&mut buffer)?;
let mut cursor = Cursor::new(buffer.as_slice());
let header = parse_header(&mut cursor)?;
let fft_point = header.fft_point as u64;
let pp = header.number_of_sector as u64;
let rate_padding = args.rate_padding as u64;
let required_memory = 4 * fft_point * pp.next_power_of_two() * rate_padding;
let mem_info = sys_info::mem_info()?;
let total_ram = mem_info.total * 1024; let quarter_ram = total_ram / 4;
if required_memory > quarter_ram {
println!(
"Warning: The estimated memory usage ({:.2} GB) exceeds 25% of your system RAM ({:.2} GB).",
required_memory as f64 / 1_073_741_824.0,
total_ram as f64 / 1_073_741_824.0
);
print!("Do you want to continue? (y/n): ");
io::stdout().flush()?;
let mut input = String::new();
io::stdin().read_line(&mut input)?;
if input.trim().to_lowercase() != "y" {
println!("Aborting.");
return Ok(false);
}
}
Ok(true)
}