use prost_build; use std::fs; use std::path::Path; use fluent_syntax::ast::{Entry::Message, ResourceEntry}; use fluent_syntax::parser::parse; fn get_identifiers(ftl_text: &str) -> Vec { let res = parse(ftl_text).unwrap(); let mut idents = vec![]; for entry in res.body { if let ResourceEntry::Entry(Message(m)) = entry { idents.push(m.id.name.to_string()); } } idents.sort(); idents } fn proto_enum(idents: &[String]) -> String { let mut buf = String::from( r#" syntax = "proto3"; package backend_proto; enum FluentString { "#, ); for (idx, s) in idents.iter().enumerate() { let name = s.replace("-", "_").to_uppercase(); buf += &format!(" {} = {};\n", name, idx); } buf += "}\n"; buf } fn rust_string_vec(idents: &[String]) -> String { let mut buf = String::from( r#"// This file is automatically generated as part of the build process. pub(super) const FLUENT_KEYS: &[&str] = &[ "#, ); for s in idents { buf += &format!(" \"{}\",\n", s); } buf += "];\n"; buf } #[cfg(test)] mod test { use crate::i18n::extract_idents::{get_identifiers, proto_enum, rust_string_vec}; #[test] fn all() { let idents = get_identifiers("key-one = foo\nkey-two = bar"); assert_eq!(idents, vec!["key-one", "key-two"]); assert_eq!( proto_enum(&idents), r#" syntax = "proto3"; package backend_strings; enum FluentString { KEY_ONE = 0; KEY_TWO = 1; } "# ); assert_eq!( rust_string_vec(&idents), r#"// This file is automatically generated as part of the build process. const FLUENT_KEYS: &[&str] = &[ "key-one", "key-two", ]; "# ); } } fn main() -> std::io::Result<()> { // write template.ftl let mut buf = String::new(); let mut ftl_template_dirs = vec!["./ftl".to_string()]; if let Ok(paths) = std::env::var("FTL_TEMPLATE_DIRS") { ftl_template_dirs.extend(paths.split(",").map(|s| s.to_string())); } for ftl_dir in ftl_template_dirs { let ftl_dir = Path::new(&ftl_dir); for entry in fs::read_dir(ftl_dir)? { let entry = entry?; let fname = entry.file_name().into_string().unwrap(); if !fname.ends_with(".ftl") { continue; } let path = entry.path(); println!("cargo:rerun-if-changed=./ftl/{}", fname); buf += &fs::read_to_string(path)?; } } let combined_ftl = Path::new("src/i18n/ftl/template.ftl"); fs::write(combined_ftl, &buf)?; // generate code completion for ftl strings let idents = get_identifiers(&buf); let string_proto_path = Path::new("../proto/fluent.proto"); fs::write(string_proto_path, proto_enum(&idents))?; let rust_string_path = Path::new("src/i18n/autogen.rs"); fs::write(rust_string_path, rust_string_vec(&idents))?; // output protobuf generated code // we avoid default OUT_DIR for now, as it breaks code completion std::env::set_var("OUT_DIR", "src"); println!("cargo:rerun-if-changed=../proto/backend.proto"); prost_build::compile_protos(&["../proto/backend.proto"], &["../proto"]).unwrap(); // write the other language ftl files // fixme: doesn't currently support extra dirs let mut ftl_lang_dirs = vec!["./ftl/repo/core".to_string()]; if let Ok(paths) = std::env::var("FTL_LANG_DIRS") { ftl_lang_dirs.extend(paths.split(",").map(|s| s.to_string())); } for ftl_dir in ftl_lang_dirs { for ftl_dir in fs::read_dir(ftl_dir)? { let ftl_dir = ftl_dir?; if ftl_dir.file_name() == "templates" { continue; } let mut buf = String::new(); let lang = ftl_dir.file_name().into_string().unwrap(); for entry in fs::read_dir(ftl_dir.path())? { let entry = entry?; let fname = entry.file_name().into_string().unwrap(); let path = entry.path(); println!("cargo:rerun-if-changed=./ftl/{}", fname); buf += &fs::read_to_string(path)?; } fs::write(format!("src/i18n/ftl/{}.ftl", lang), buf)?; } } Ok(()) }