use std::io::Write;
use regex::Regex;
fn main() {
let args: Vec<String> = std::env::args().collect();
let jeod_root = resolve_jeod_root(&args).unwrap_or_else(|| {
eprintln!(
"extract_planet_pfixposn: JEOD source not found.\n\
Pass `--jeod-home <PATH>` or set JEOD_HOME \
(see CLAUDE.md \"Environment Setup\")."
);
std::process::exit(2);
});
let input_py = jeod_root.join(
"models/utils/planet_fixed/planet_fixed_posn/\
verif/SIM_PFIXPOSN_VERIF/SET_test/RUN_pfixposn_test/input.py",
);
let content = std::fs::read_to_string(&input_py).unwrap_or_else(|e| {
panic!(
"Cannot read {} (under JEOD_HOME = {}): {e}",
input_py.display(),
jeod_root.display(),
)
});
let cases = parse_input_py(&content);
assert!(
cases.len() >= 3,
"expected three add_read blocks in {}, found {}",
input_py.display(),
cases.len(),
);
let out_path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("test_data/planet_pfixposn_seeds.json");
let mut f = std::fs::File::create(&out_path)
.unwrap_or_else(|e| panic!("Cannot create {}: {e}", out_path.display()));
write_json(&mut f, &cases);
println!("wrote {} ({} cases)", out_path.display(), cases.len());
}
fn resolve_jeod_root(args: &[String]) -> Option<std::path::PathBuf> {
if let Some(idx) = args.iter().position(|a| a == "--jeod-home") {
if let Some(p) = args.get(idx + 1) {
return Some(std::path::PathBuf::from(p));
}
}
if let Ok(p) = std::env::var("JEOD_HOME") {
return Some(std::path::PathBuf::from(p));
}
None
}
#[derive(Debug)]
enum Case {
Cart {
read_time: f64,
cart_m: [f64; 3],
},
Spher {
read_time: f64,
altitude_m: f64,
latitude_rad: f64,
longitude_rad: f64,
},
Ellip {
read_time: f64,
altitude_m: f64,
latitude_rad: f64,
longitude_rad: f64,
},
}
fn parse_input_py(content: &str) -> Vec<Case> {
let block_re = Regex::new(
r#"(?ms)read\s*=\s*([0-9eE+.\-]+)\s*\n\s*trick\.add_read\s*\(\s*read\s*,\s*"""(?P<body>.*?)"""\s*\)"#,
)
.expect("regex for add_read block");
let mut cases = Vec::new();
for caps in block_re.captures_iter(content) {
let read_time: f64 = caps[1]
.parse()
.unwrap_or_else(|e| panic!("malformed read time {:?}: {e}", &caps[1]));
let body = &caps["body"];
if body.contains("update_from_cart") {
let xyz = parse_array3(body, r"earth\.cartesian_pos\s*=\s*\[([^\]]+)\]")
.unwrap_or_else(|| panic!("cartesian_pos not parseable in:\n{body}"));
cases.push(Case::Cart {
read_time,
cart_m: xyz,
});
} else if body.contains("update_from_spher") {
cases.push(Case::Spher {
read_time,
altitude_m: parse_assign(
body,
r"earth\.spherical_pos\.altitude\s*=\s*([\-0-9eE+.]+)",
),
latitude_rad: parse_assign(
body,
r"earth\.spherical_pos\.latitude\s*=\s*([\-0-9eE+.]+)",
),
longitude_rad: parse_assign(
body,
r"earth\.spherical_pos\.longitude\s*=\s*([\-0-9eE+.]+)",
),
});
} else if body.contains("update_from_ellip") {
cases.push(Case::Ellip {
read_time,
altitude_m: parse_assign(
body,
r"earth\.elliptical_pos\.altitude\s*=\s*([\-0-9eE+.]+)",
),
latitude_rad: parse_assign(
body,
r"earth\.elliptical_pos\.latitude\s*=\s*([\-0-9eE+.]+)",
),
longitude_rad: parse_assign(
body,
r"earth\.elliptical_pos\.longitude\s*=\s*([\-0-9eE+.]+)",
),
});
}
}
cases
}
fn parse_array3(text: &str, pattern: &str) -> Option<[f64; 3]> {
let re = Regex::new(pattern).ok()?;
let caps = re.captures(text)?;
let parts: Vec<f64> = caps[1]
.split(',')
.map(|s| s.trim().parse::<f64>())
.collect::<Result<_, _>>()
.ok()?;
if parts.len() != 3 {
return None;
}
Some([parts[0], parts[1], parts[2]])
}
fn parse_assign(text: &str, pattern: &str) -> f64 {
let re = Regex::new(pattern).expect("valid regex");
let caps = re
.captures(text)
.unwrap_or_else(|| panic!("pattern {pattern:?} did not match in:\n{text}"));
caps[1]
.parse()
.unwrap_or_else(|e| panic!("failed to parse {:?}: {e}", &caps[1]))
}
fn write_json(out: &mut std::fs::File, cases: &[Case]) {
writeln!(out, "{{").unwrap();
writeln!(out, " \"schema_version\": 1,").unwrap();
writeln!(
out,
" \"source\": \"models/utils/planet_fixed/planet_fixed_posn/verif/SIM_PFIXPOSN_VERIF/SET_test/RUN_pfixposn_test/input.py\","
)
.unwrap();
writeln!(out, " \"jeod_version\": \"5.4\",").unwrap();
writeln!(
out,
" \"note\": \"PlanetFixedPosition verification seeds. Regenerate with: cargo run -p astrodyn_planet --bin extract_planet_pfixposn -- --jeod-home $JEOD_HOME\","
)
.unwrap();
writeln!(out, " \"cases\": [").unwrap();
for (i, c) in cases.iter().enumerate() {
let comma = if i + 1 < cases.len() { "," } else { "" };
match c {
Case::Cart { read_time, cart_m } => writeln!(
out,
" {{\"kind\": \"cartesian\", \"read_time\": {}, \"cart_m\": [{}, {}, {}]}}{}",
fmt(*read_time),
fmt(cart_m[0]),
fmt(cart_m[1]),
fmt(cart_m[2]),
comma,
),
Case::Spher {
read_time,
altitude_m,
latitude_rad,
longitude_rad,
} => writeln!(
out,
" {{\"kind\": \"spherical\", \"read_time\": {}, \"altitude_m\": {}, \"latitude_rad\": {}, \"longitude_rad\": {}}}{}",
fmt(*read_time),
fmt(*altitude_m),
fmt(*latitude_rad),
fmt(*longitude_rad),
comma,
),
Case::Ellip {
read_time,
altitude_m,
latitude_rad,
longitude_rad,
} => writeln!(
out,
" {{\"kind\": \"elliptical\", \"read_time\": {}, \"altitude_m\": {}, \"latitude_rad\": {}, \"longitude_rad\": {}}}{}",
fmt(*read_time),
fmt(*altitude_m),
fmt(*latitude_rad),
fmt(*longitude_rad),
comma,
),
}
.unwrap();
}
writeln!(out, " ]").unwrap();
writeln!(out, "}}").unwrap();
}
fn fmt(x: f64) -> String {
let s = format!("{x:?}");
s
}