quack_builder/
lib.rs

1pub mod errors;
2mod keywords;
3mod parser;
4mod scc;
5pub mod types;
6
7use errors::{Error, Result};
8use std::path::{Path, PathBuf};
9use types::Config;
10
11/// A builder for [Config]
12///
13/// # Example build.rs
14///
15/// ```rust,no_run
16/// use pb_rs::{types::FileDescriptor, ConfigBuilder};
17/// use std::path::{Path, PathBuf};
18/// use walkdir::WalkDir;
19///
20/// # fn main() {
21/// let out_dir = std::env::var("OUT_DIR").unwrap();
22/// let out_dir = Path::new(&out_dir).join("protos");
23///
24/// let in_dir = PathBuf::from(::std::env::var("CARGO_MANIFEST_DIR").unwrap()).join("protos");
25/// // Re-run this build.rs if the protos dir changes (i.e. a new file is added)
26/// println!("cargo:rerun-if-changed={}", in_dir.to_str().unwrap());
27///
28/// // Find all *.proto files in the `in_dir` and add them to the list of files
29/// let mut protos = Vec::new();
30/// let proto_ext = Some(Path::new("proto").as_os_str());
31/// for entry in WalkDir::new(&in_dir) {
32///     let path = entry.unwrap().into_path();
33///     if path.extension() == proto_ext {
34///         // Re-run this build.rs if any of the files in the protos dir change
35///         println!("cargo:rerun-if-changed={}", path.to_str().unwrap());
36///         protos.push(path);
37///     }
38/// }
39///
40/// // Delete all old generated files before re-generating new ones
41/// if out_dir.exists() {
42///     std::fs::remove_dir_all(&out_dir).unwrap();
43/// }
44/// std::fs::DirBuilder::new().create(&out_dir).unwrap();
45/// let config_builder = ConfigBuilder::new(&protos, None, Some(&out_dir), &[in_dir]).unwrap();
46/// FileDescriptor::run(&config_builder.build()).unwrap()
47/// # }
48/// ```
49#[derive(Debug, Default)]
50pub struct ConfigBuilder {
51    in_files: Vec<PathBuf>,
52    out_file: Option<PathBuf>,
53    include_paths: Vec<PathBuf>,
54    single_module: bool,
55    no_output: bool,
56    error_cycle: bool,
57    headers: bool,
58    dont_use_cow: bool,
59    custom_struct_derive: Vec<String>,
60    custom_repr: Option<String>,
61    owned: bool,
62    nostd: bool,
63    hashbrown: bool,
64    gen_info: bool,
65    add_deprecated_fields: bool,
66    generate_getters: bool,
67}
68
69impl ConfigBuilder {
70    pub fn new<P: AsRef<Path>>(
71        in_files: &[P],
72        output: Option<&P>,
73        output_dir: Option<&P>,
74        include_paths: &[P],
75    ) -> Result<ConfigBuilder> {
76        let in_files = in_files
77            .iter()
78            .map(|f| f.as_ref().into())
79            .collect::<Vec<PathBuf>>();
80        let output = output.map(|f| f.as_ref().into());
81        let output_dir: Option<PathBuf> = output_dir.map(|f| f.as_ref().into());
82        let mut include_paths = include_paths
83            .iter()
84            .map(|f| f.as_ref().into())
85            .collect::<Vec<PathBuf>>();
86
87        if in_files.is_empty() {
88            return Err(Error::NoProto);
89        }
90
91        for f in &in_files {
92            if !f.exists() {
93                return Err(Error::InputFile(format!("{}", f.display())));
94            }
95        }
96
97        let out_file = match (output, output_dir) {
98            (Some(_), None) if in_files.len() > 1 => {
99                return Err(Error::OutputMultipleInputs);
100            }
101            (Some(output), None) => Some(output),
102            (None, Some(output_dir)) => {
103                if !output_dir.is_dir() {
104                    return Err(Error::OutputDirectory(format!("{}", output_dir.display())));
105                }
106                Some(output_dir)
107            }
108            (Some(_), Some(_)) => {
109                return Err(Error::OutputAndOutputDir);
110            }
111            (None, None) => None,
112        };
113
114        let default = PathBuf::from(".");
115        if include_paths.is_empty() || !include_paths.contains(&default) {
116            include_paths.push(default);
117        }
118
119        Ok(ConfigBuilder {
120            in_files,
121            out_file,
122            include_paths,
123            headers: true,
124            ..Default::default()
125        })
126    }
127
128    /// Omit generation of modules for each package when there is only one package
129    pub fn single_module(
130        mut self,
131        val: bool,
132    ) -> Self {
133        self.single_module = val;
134        self
135    }
136
137    /// Show enums and messages in this .proto file, including those imported. No code generated.
138    /// `no_output` should probably only be used by the pb-rs cli.
139    pub fn no_output(
140        mut self,
141        val: bool,
142    ) -> Self {
143        self.no_output = val;
144        self
145    }
146
147    /// Error out if recursive messages do not have optional fields
148    pub fn error_cycle(
149        mut self,
150        val: bool,
151    ) -> Self {
152        self.error_cycle = val;
153        self
154    }
155
156    /// Enable module comments and module attributes in generated file (default = true)
157    pub fn headers(
158        mut self,
159        val: bool,
160    ) -> Self {
161        self.headers = val;
162        self
163    }
164
165    /// Add custom values to `#[derive(...)]` at the beginning of every structure
166    pub fn custom_struct_derive(
167        mut self,
168        val: Vec<String>,
169    ) -> Self {
170        self.custom_struct_derive = val;
171        self
172    }
173
174    /// Add custom values to `#[repr(...)]` at the beginning of every structure
175    pub fn custom_repr(
176        mut self,
177        val: Option<String>,
178    ) -> Self {
179        self.custom_repr = val;
180        self
181    }
182
183    /// Use `Cow<_,_>` for Strings and Bytes
184    pub fn dont_use_cow(
185        mut self,
186        val: bool,
187    ) -> Self {
188        self.dont_use_cow = val;
189        self
190    }
191
192    /// Generate Owned structs when the proto struct has a lifetime
193    pub fn owned(
194        mut self,
195        val: bool,
196    ) -> Self {
197        self.owned = val;
198        self
199    }
200
201    /// Generate `#![no_std]` compliant code
202    pub fn nostd(
203        mut self,
204        val: bool,
205    ) -> Self {
206        self.nostd = val;
207        self
208    }
209
210    /// Use hashbrown as `HashMap` implementation instead of [std::collections::HashMap] or
211    /// [alloc::collections::BTreeMap](https://doc.rust-lang.org/alloc/collections/btree_map/struct.BTreeMap.html)
212    /// in a `no_std` environment
213    pub fn hashbrown(
214        mut self,
215        val: bool,
216    ) -> Self {
217        self.hashbrown = val;
218        self
219    }
220
221    /// Generate `MessageInfo` implementations
222    pub fn gen_info(
223        mut self,
224        val: bool,
225    ) -> Self {
226        self.gen_info = val;
227        self
228    }
229
230    /// Add deprecated fields and mark them as `#[deprecated]`
231    pub fn add_deprecated_fields(
232        mut self,
233        val: bool,
234    ) -> Self {
235        self.add_deprecated_fields = val;
236        self
237    }
238
239    /// Generate getters for Proto2 `optional` fields with custom default values
240    pub fn generate_getters(
241        mut self,
242        val: bool,
243    ) -> Self {
244        self.generate_getters = val;
245        self
246    }
247
248    /// Build [Config] from this `ConfigBuilder`
249    pub fn build(self) -> Vec<Config> {
250        self.in_files
251            .iter()
252            .map(|in_file| {
253                let mut out_file = in_file.with_extension("rs");
254
255                if let Some(ref ofile) = self.out_file {
256                    if ofile.is_dir() {
257                        out_file = ofile.join(out_file.file_name().unwrap());
258                    } else {
259                        out_file = ofile.into();
260                    }
261                }
262
263                Config {
264                    in_file: in_file.to_owned(),
265                    out_file,
266                    import_search_path: self.include_paths.clone(),
267                    single_module: self.single_module,
268                    no_output: self.no_output,
269                    error_cycle: self.error_cycle,
270                    headers: self.headers,
271                    dont_use_cow: self.dont_use_cow, // Change this to true to not use cow with ./generate.sh for v2 and v3 tests
272                    custom_struct_derive: self.custom_struct_derive.clone(),
273                    custom_repr: self.custom_repr.clone(),
274                    custom_rpc_generator: Box::new(|_, _| Ok(())),
275                    custom_includes: Vec::new(),
276                    owned: self.owned,
277                    nostd: self.nostd,
278                    hashbrown: self.hashbrown,
279                    gen_info: self.gen_info,
280                    add_deprecated_fields: self.add_deprecated_fields,
281                    generate_getters: self.generate_getters,
282                }
283            })
284            .collect()
285    }
286}