es_fluent_cli_helpers/
cli.rs

1//! Inventory collection functionality for CLI commands.
2
3use serde::Serialize;
4use std::collections::{HashMap, HashSet};
5
6/// Expected key information from inventory.
7#[derive(Serialize)]
8pub struct ExpectedKey {
9    pub key: String,
10    pub variables: Vec<String>,
11    /// The Rust source file where this key is defined.
12    pub source_file: Option<String>,
13    /// The line number in the Rust source file.
14    pub source_line: Option<u32>,
15}
16
17/// The inventory data output.
18#[derive(Serialize)]
19pub struct InventoryData {
20    pub expected_keys: Vec<ExpectedKey>,
21}
22
23/// Intermediate metadata for a key during collection.
24#[derive(Default)]
25struct KeyMeta {
26    variables: HashSet<String>,
27    source_file: Option<String>,
28    source_line: Option<u32>,
29}
30
31/// Collects inventory data for a crate and writes it to `inventory.json`.
32///
33/// This function is used by the es-fluent CLI to collect expected FTL keys
34/// and their variables from inventory-registered types.
35///
36/// # Arguments
37///
38/// * `crate_name` - The name of the crate to collect inventory for (e.g., "my-crate")
39///
40/// # Panics
41///
42/// Panics if serialization or file writing fails.
43pub fn write_inventory_for_crate(crate_name: &str) {
44    let crate_ident = crate_name.replace('-', "_");
45
46    // Collect all registered type infos for this crate
47    let type_infos: Vec<_> = es_fluent::registry::get_all_ftl_type_infos()
48        .filter(|info| {
49            info.module_path == crate_ident
50                || info.module_path.starts_with(&format!("{}::", crate_ident))
51        })
52        .collect();
53
54    // Build a map of expected keys with their metadata
55    let mut keys_map: HashMap<String, KeyMeta> = HashMap::new();
56    for info in &type_infos {
57        for variant in info.variants {
58            let key = variant.ftl_key.to_string();
59            let vars: HashSet<String> = variant.args.iter().map(|s| s.to_string()).collect();
60            let entry = keys_map.entry(key).or_insert_with(|| KeyMeta {
61                variables: HashSet::new(),
62                source_file: if info.file_path.is_empty() {
63                    None
64                } else {
65                    Some(info.file_path.to_string())
66                },
67                source_line: Some(variant.line),
68            });
69            entry.variables.extend(vars);
70            // Keep the first source location we encounter
71        }
72    }
73
74    // Convert to output format
75    let expected_keys: Vec<ExpectedKey> = keys_map
76        .into_iter()
77        .map(|(key, meta)| ExpectedKey {
78            key,
79            variables: meta.variables.into_iter().collect(),
80            source_file: meta.source_file,
81            source_line: meta.source_line,
82        })
83        .collect();
84
85    let data = InventoryData { expected_keys };
86
87    // Write inventory data to file
88    let json = serde_json::to_string(&data).expect("Failed to serialize inventory data");
89
90    let metadata_dir = std::path::Path::new("metadata").join(crate_name);
91    std::fs::create_dir_all(&metadata_dir).expect("Failed to create metadata directory");
92
93    std::fs::write(metadata_dir.join("inventory.json"), json)
94        .expect("Failed to write inventory file");
95}