1#![deny(missing_docs)]
47#![deny(rustdoc::broken_intra_doc_links)]
48
49extern crate tempfile;
50
51extern crate protobuf;
52extern crate protobuf_codegen;
53extern crate protoc;
54
55mod slashes;
56use std::fs;
57use std::io;
58use std::io::Read;
59use std::path::Path;
60use std::path::PathBuf;
61
62use protobuf::descriptor::FileDescriptorSet;
63use protobuf::Message;
64pub use protobuf_codegen::Customize;
65pub use protoc::Error;
66use protoc::Protoc;
67pub use protoc::Result;
68use slashes::Slashes;
69
70#[derive(Debug, Default)]
72#[deprecated(since = "2.14", note = "Use Codegen instead")]
73pub struct Args<'a> {
74 pub out_dir: &'a str,
76 pub includes: &'a [&'a str],
78 pub input: &'a [&'a str],
80 pub customize: Customize,
82}
83
84#[derive(Debug, Default)]
86pub struct Codegen {
87 out_dir: PathBuf,
89 includes: Vec<PathBuf>,
91 inputs: Vec<PathBuf>,
93 customize: Customize,
95 protoc: Option<Protoc>,
97}
98
99impl Codegen {
100 pub fn new() -> Self {
102 Self::default()
103 }
104
105 pub fn out_dir(&mut self, out_dir: impl AsRef<Path>) -> &mut Self {
107 self.out_dir = out_dir.as_ref().to_owned();
108 self
109 }
110
111 pub fn include(&mut self, include: impl AsRef<Path>) -> &mut Self {
113 self.includes.push(include.as_ref().to_owned());
114 self
115 }
116
117 pub fn includes(&mut self, includes: impl IntoIterator<Item = impl AsRef<Path>>) -> &mut Self {
119 for include in includes {
120 self.include(include);
121 }
122 self
123 }
124
125 pub fn input(&mut self, input: impl AsRef<Path>) -> &mut Self {
127 self.inputs.push(input.as_ref().to_owned());
128 self
129 }
130
131 pub fn inputs(&mut self, inputs: impl IntoIterator<Item = impl AsRef<Path>>) -> &mut Self {
133 for input in inputs {
134 self.input(input);
135 }
136 self
137 }
138
139 pub fn protoc_path(&mut self, protoc: impl Into<PathBuf>) -> &mut Self {
159 self.protoc = Some(Protoc::from_path(&protoc.into().to_str().unwrap()));
160 self
161 }
162
163 pub fn customize(&mut self, customize: Customize) -> &mut Self {
165 self.customize = customize;
166 self
167 }
168
169 pub fn run(&self) -> Result<()> {
171 let protoc = match self.protoc.clone() {
172 Some(protoc) => protoc,
173 None => Protoc::from_env_path(),
174 };
175 protoc.check()?;
176
177 let temp_dir = tempfile::Builder::new().prefix("protoc-rust").tempdir()?;
178 let temp_file = temp_dir.path().join("descriptor.pbbin");
179
180 let includes: Vec<&str> = self.includes.iter().map(|p| p.to_str().unwrap()).collect();
181 let inputs: Vec<&str> = self.inputs.iter().map(|p| p.to_str().unwrap()).collect();
182
183 protoc.write_descriptor_set(protoc::DescriptorSetOutArgs {
184 out: temp_file.as_os_str().to_str().unwrap(),
185 includes: &includes,
186 input: &inputs,
187 include_imports: true,
188 })?;
189
190 let mut fds = Vec::new();
191 let mut file = fs::File::open(temp_file)?;
192 file.read_to_end(&mut fds)?;
193
194 drop(file);
195 drop(temp_dir);
196
197 let fds = FileDescriptorSet::parse_from_bytes(&fds)
198 .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
199
200 let default_includes = vec![PathBuf::from(".")];
201 let includes = if self.includes.is_empty() {
202 &default_includes
203 } else {
204 &self.includes
205 };
206
207 let mut files_to_generate = Vec::new();
208 'outer: for file in &self.inputs {
209 for include in includes {
210 if let Some(truncated) =
211 remove_path_prefix(file.to_str().unwrap(), include.to_str().unwrap())
212 {
213 files_to_generate.push(truncated.to_owned());
214 continue 'outer;
215 }
216 }
217
218 return Err(Error::new(
219 io::ErrorKind::Other,
220 format!("file {:?} is not found in includes {:?}", file, includes),
221 ));
222 }
223
224 protobuf_codegen::gen_and_write(
225 fds.get_file(),
226 &files_to_generate,
227 &self.out_dir,
228 &self.customize,
229 )
230 }
231}
232
233#[deprecated(since = "2.14", note = "Use Codegen instead")]
235#[allow(deprecated)]
236pub fn run(args: Args) -> Result<()> {
237 Codegen::new()
238 .out_dir(args.out_dir)
239 .includes(args.includes)
240 .inputs(args.input)
241 .customize(args.customize)
242 .run()
243}
244
245fn remove_path_prefix(mut path: &str, mut prefix: &str) -> Option<String> {
246 let slashes = Slashes::here();
247 path = slashes.remove_dot_slashes(path);
248 prefix = slashes.remove_dot_slashes(prefix);
249
250 if prefix == "" {
251 return Some(path.to_owned());
252 }
253
254 let path = slashes.norm_path(path);
255 let mut prefix = slashes.norm_path(prefix);
256
257 if prefix.ends_with("/") {
258 let l = prefix.len();
259 prefix.truncate(l - 1);
260 }
261
262 if !path.starts_with(&prefix) {
263 return None;
264 }
265
266 if path.len() <= prefix.len() {
267 return None;
268 }
269
270 if path.as_bytes()[prefix.len()] == b'/' {
271 return Some(path[prefix.len() + 1..].to_owned());
272 } else {
273 return None;
274 }
275}
276
277#[cfg(test)]
278mod test {
279 #[test]
280 fn remove_path_prefix() {
281 assert_eq!(
282 Some("abc.proto".to_owned()),
283 super::remove_path_prefix("xxx/abc.proto", "xxx")
284 );
285 assert_eq!(
286 Some("abc.proto".to_owned()),
287 super::remove_path_prefix("xxx/abc.proto", "xxx/")
288 );
289 assert_eq!(
290 Some("abc.proto".to_owned()),
291 super::remove_path_prefix("../xxx/abc.proto", "../xxx/")
292 );
293 assert_eq!(
294 Some("abc.proto".to_owned()),
295 super::remove_path_prefix("abc.proto", ".")
296 );
297 assert_eq!(
298 Some("abc.proto".to_owned()),
299 super::remove_path_prefix("abc.proto", "./")
300 );
301 assert_eq!(None, super::remove_path_prefix("xxx/abc.proto", "yyy"));
302 assert_eq!(None, super::remove_path_prefix("xxx/abc.proto", "yyy/"));
303 }
304}