diff --git a/dev/Cargo.lock b/dev/Cargo.lock index bbc6ca74ea1e..d1a8e49471ac 100644 --- a/dev/Cargo.lock +++ b/dev/Cargo.lock @@ -118,6 +118,21 @@ version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "enquote" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06c36cb11dbde389f4096111698d8b567c0720e3452fd5ac3e6b4e47e1939932" +dependencies = [ + "thiserror", +] + [[package]] name = "env_filter" version = "0.1.3" @@ -159,6 +174,15 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "log" version = "0.4.22" @@ -177,9 +201,12 @@ version = "0.0.1" dependencies = [ "anyhow", "clap", + "enquote", "env_logger", + "itertools", "log", "pretty_assertions", + "proc-macro2", "syn", ] @@ -257,6 +284,26 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "unicode-ident" version = "1.0.14" diff --git a/dev/Cargo.toml b/dev/Cargo.toml index 7674646a94a8..31120d77f674 100644 --- a/dev/Cargo.toml +++ b/dev/Cargo.toml @@ -30,9 +30,12 @@ version = "0.0.1" [dependencies] anyhow = "1.0.95" clap = { version = "4.5.23", features = ["derive"] } +enquote = "1.1.0" env_logger = "0.11.6" +itertools = "0.13.0" log = "0.4.22" -syn = { version = "2.0.91", features = ["visit","full","extra-traits"] } +proc-macro2 = { version = "1.0.91", features = ["span-locations"] } +syn = { version = "2.0.91", features = ['parsing', 'full', 'derive', 'visit', 'extra-traits'] } [dev-dependencies] pretty_assertions = "1.4.1" diff --git a/dev/src/generate/binding_python.rs b/dev/src/generate/binding_python.rs index 7f3e3bdef83f..c6bef062034e 100644 --- a/dev/src/generate/binding_python.rs +++ b/dev/src/generate/binding_python.rs @@ -17,8 +17,9 @@ use crate::generate::parser::Services; use anyhow::Result; +use std::path::PathBuf; -pub fn generate(services: &Services) -> Result<()> { +pub fn generate(_project_root: PathBuf, services: &Services) -> Result<()> { println!("{:?}", services); Ok(()) diff --git a/dev/src/generate/mod.rs b/dev/src/generate/mod.rs index 743aa3b1c483..71ec44429998 100644 --- a/dev/src/generate/mod.rs +++ b/dev/src/generate/mod.rs @@ -25,10 +25,11 @@ use std::path::PathBuf; pub fn run(language: &str) -> Result<()> { let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let services_path = manifest_dir.join("../core/src/services").canonicalize()?; + let project_root = manifest_dir.join("..").canonicalize()?; let services = parser::parse(&services_path.to_string_lossy())?; match language { - "python" | "py" => binding_python::generate(&services), + "python" | "py" => binding_python::generate(project_root, &services), _ => Err(anyhow::anyhow!("Unsupported language: {}", language)), } } diff --git a/dev/src/generate/parser.rs b/dev/src/generate/parser.rs index e8c3b6f03551..1c9766040dc1 100644 --- a/dev/src/generate/parser.rs +++ b/dev/src/generate/parser.rs @@ -17,12 +17,12 @@ use anyhow::Result; use anyhow::{anyhow, Context}; +use itertools::Itertools; use log::debug; -use std::collections::hash_map; use std::collections::HashMap; -use std::fs; use std::fs::read_dir; use std::str::FromStr; +use std::{fs, vec}; use syn::{Field, GenericArgument, Item, ItemStruct, PathArguments, Type, TypePath}; #[derive(Debug, Clone)] @@ -30,10 +30,10 @@ pub struct Services(HashMap); impl IntoIterator for Services { type Item = (String, Service); - type IntoIter = hash_map::IntoIter; + type IntoIter = vec::IntoIter<(String, Service)>; fn into_iter(self) -> Self::IntoIter { - self.0.into_iter() + self.0.into_iter().sorted_by_key(|x| x.0.clone()) } } @@ -53,6 +53,8 @@ pub struct Config { pub value: ConfigType, /// If given config is optional or not. pub optional: bool, + /// if this field is deprecated, a deprecated message will be provided. + pub deprecated: Option, /// The comments for this config. /// /// All white spaces and extra new lines will be trimmed. @@ -181,7 +183,7 @@ impl ServiceParser { let mut config = Vec::with_capacity(config_struct.fields.len()); for field in config_struct.fields { - let field = Self::parse_field(field)?; + let field = self.parse_field(field)?; config.push(field); } @@ -190,12 +192,14 @@ impl ServiceParser { } /// TODO: Add comment parse support. - fn parse_field(field: Field) -> Result { + fn parse_field(&self, field: Field) -> Result { let name = field .ident .clone() .ok_or_else(|| anyhow!("field name is missing for {:?}", &field))?; + let deprecated = self.deprecated_note(&field); + let (cfg_type, optional) = match &field.ty { Type::Path(TypePath { path, .. }) => { let segment = path @@ -226,6 +230,7 @@ impl ServiceParser { }; let typ = type_name.as_str().parse()?; + let optional = optional || typ == ConfigType::Bool; (typ, optional) } @@ -236,9 +241,44 @@ impl ServiceParser { name: name.to_string(), value: cfg_type, optional, + deprecated, comments: "".to_string(), }) } + + fn deprecated_note(&self, field: &Field) -> Option { + for attr in &field.attrs { + if !attr.path().is_ident("deprecated") { + continue; + } + + let meta_list = match &attr.meta { + syn::Meta::List(meta_list) => meta_list, + _ => continue, + }; + + let tokens = Vec::from_iter(meta_list.tokens.clone().into_iter()); + for (index, token) in tokens.iter().enumerate() { + let ident = match token { + proc_macro2::TokenTree::Ident(ident) => ident, + _ => { + continue; + } + }; + + if ident == "note" { + return tokens + .get(index + 2) + .expect("deprecated attribute missing note") + .span() + .source_text() + .map(|s| enquote::unquote(s.as_str()).expect("should unquote string")); + } + } + } + + return None; + } } #[cfg(test)] @@ -256,6 +296,7 @@ mod tests { name: "root".to_string(), value: ConfigType::String, optional: true, + deprecated: None, comments: "".to_string(), }, ), @@ -265,6 +306,7 @@ mod tests { name: "root".to_string(), value: ConfigType::String, optional: false, + deprecated: None, comments: "".to_string(), }, ), @@ -495,12 +537,25 @@ impl Debug for S3Config { let service = parser.parse().unwrap(); assert_eq!(service.config.len(), 26); + assert_eq!( + service.config[21], + Config { + name: "batch_max_operations".to_string(), + value: ConfigType::Usize, + optional: true, + deprecated: Some( + "Please use `delete_max_size` instead of `batch_max_operations`".into() + ), + comments: "".to_string(), + }, + ); assert_eq!( service.config[25], Config { name: "disable_write_with_if_match".to_string(), value: ConfigType::Bool, - optional: false, + optional: true, + deprecated: None, comments: "".to_string(), }, );