use std::env;
use std::fs;
use std::path::PathBuf;
use serde::Deserialize;
use toml::Value;
#[derive(Deserialize)]
struct TradeFile {
trade: Vec<TradeRow>,
}
#[derive(Deserialize)]
struct QuoteFile {
quote: Vec<QuoteRow>,
}
#[derive(Deserialize)]
struct TradeRow {
code: i32,
name: String,
description: String,
cancel: bool,
late_report: bool,
auto_executed: bool,
open_report: bool,
volume: bool,
high: bool,
low: bool,
last: bool,
}
#[derive(Deserialize)]
struct QuoteRow {
code: i32,
name: String,
description: String,
firm: bool,
halted: bool,
}
fn rust_string_literal(s: &str) -> String {
let mut out = String::with_capacity(s.len() + 2);
out.push('"');
for ch in s.chars() {
match ch {
'\\' => out.push_str("\\\\"),
'"' => out.push_str("\\\""),
'\n' => out.push_str("\\n"),
'\r' => out.push_str("\\r"),
'\t' => out.push_str("\\t"),
c if (c as u32) < 0x20 => {
use std::fmt::Write;
let _ = write!(out, "\\u{{{:x}}}", c as u32);
}
c => out.push(c),
}
}
out.push('"');
out
}
fn main() {
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
let trade_toml = manifest_dir.join("data/trade_conditions.toml");
let quote_toml = manifest_dir.join("data/quote_conditions.toml");
let out = manifest_dir.join("src/conditions/tables_generated.rs");
println!("cargo:rerun-if-changed=data/trade_conditions.toml");
println!("cargo:rerun-if-changed=data/quote_conditions.toml");
println!("cargo:rerun-if-changed=build.rs");
let trade_src = fs::read_to_string(&trade_toml)
.unwrap_or_else(|e| panic!("read {}: {e}", trade_toml.display()));
let quote_src = fs::read_to_string("e_toml)
.unwrap_or_else(|e| panic!("read {}: {e}", quote_toml.display()));
let _: Value = toml::from_str(&trade_src).expect("trade_conditions.toml: invalid TOML");
let _: Value = toml::from_str("e_src).expect("quote_conditions.toml: invalid TOML");
let trades: TradeFile =
toml::from_str(&trade_src).expect("trade_conditions.toml: schema mismatch");
let quotes: QuoteFile =
toml::from_str("e_src).expect("quote_conditions.toml: schema mismatch");
assert_eq!(
trades.trade.len(),
149,
"trade_conditions.toml must have exactly 149 entries"
);
assert_eq!(
quotes.quote.len(),
75,
"quote_conditions.toml must have exactly 75 entries"
);
for (i, t) in trades.trade.iter().enumerate() {
assert_eq!(
t.code as usize, i,
"trade_conditions.toml[{i}] has code {} (must equal index)",
t.code
);
}
for (i, q) in quotes.quote.iter().enumerate() {
assert_eq!(
q.code as usize, i,
"quote_conditions.toml[{i}] has code {} (must equal index)",
q.code
);
}
let mut s = String::new();
s.push_str("// @generated DO NOT EDIT — regenerated by build.rs from data/*.toml\n");
s.push_str("//\n");
s.push_str("// Source-of-truth: crates/tdbe/data/trade_conditions.toml\n");
s.push_str("// crates/tdbe/data/quote_conditions.toml\n\n");
s.push_str("use super::{QuoteCondition, TradeCondition};\n\n");
s.push_str("/// All 149 trade condition codes (0..148).\n");
s.push_str("pub const TRADE_CONDITIONS: [TradeCondition; 149] = [\n");
for t in &trades.trade {
s.push_str(" TradeCondition {\n");
s.push_str(&format!(" code: {},\n", t.code));
s.push_str(&format!(
" name: {},\n",
rust_string_literal(&t.name)
));
s.push_str(&format!(
" description: {},\n",
rust_string_literal(&t.description)
));
s.push_str(&format!(" cancel: {},\n", t.cancel));
s.push_str(&format!(" late_report: {},\n", t.late_report));
s.push_str(&format!(" auto_executed: {},\n", t.auto_executed));
s.push_str(&format!(" open_report: {},\n", t.open_report));
s.push_str(&format!(" volume: {},\n", t.volume));
s.push_str(&format!(" high: {},\n", t.high));
s.push_str(&format!(" low: {},\n", t.low));
s.push_str(&format!(" last: {},\n", t.last));
s.push_str(" },\n");
}
s.push_str("];\n\n");
s.push_str("/// All 75 quote condition codes (0..74).\n");
s.push_str("pub const QUOTE_CONDITIONS: [QuoteCondition; 75] = [\n");
for q in "es.quote {
s.push_str(" QuoteCondition {\n");
s.push_str(&format!(" code: {},\n", q.code));
s.push_str(&format!(
" name: {},\n",
rust_string_literal(&q.name)
));
s.push_str(&format!(
" description: {},\n",
rust_string_literal(&q.description)
));
s.push_str(&format!(" firm: {},\n", q.firm));
s.push_str(&format!(" halted: {},\n", q.halted));
s.push_str(" },\n");
}
s.push_str("];\n");
let needs_write = match fs::read_to_string(&out) {
Ok(existing) => existing != s,
Err(_) => true,
};
if needs_write {
if let Some(parent) = out.parent() {
fs::create_dir_all(parent).expect("create conditions dir");
}
fs::write(&out, s).expect("write tables_generated.rs");
}
}