#![deny(missing_docs)]
#![deny(rustdoc::broken_intra_doc_links)]
extern crate tempfile;
extern crate protobuf;
extern crate protobuf_codegen;
extern crate protoc;
mod slashes;
use std::fs;
use std::io;
use std::io::Read;
use std::path::Path;
use std::path::PathBuf;
use protobuf::descriptor::FileDescriptorSet;
use protobuf::Message;
pub use protobuf_codegen::Customize;
pub use protoc::Error;
use protoc::Protoc;
pub use protoc::Result;
use slashes::Slashes;
#[derive(Debug, Default)]
#[deprecated(since = "2.14", note = "Use Codegen instead")]
pub struct Args<'a> {
pub out_dir: &'a str,
pub includes: &'a [&'a str],
pub input: &'a [&'a str],
pub customize: Customize,
}
#[derive(Debug, Default)]
pub struct Codegen {
out_dir: PathBuf,
includes: Vec<PathBuf>,
inputs: Vec<PathBuf>,
customize: Customize,
protoc: Option<Protoc>,
}
impl Codegen {
pub fn new() -> Self {
Self::default()
}
pub fn out_dir(&mut self, out_dir: impl AsRef<Path>) -> &mut Self {
self.out_dir = out_dir.as_ref().to_owned();
self
}
pub fn include(&mut self, include: impl AsRef<Path>) -> &mut Self {
self.includes.push(include.as_ref().to_owned());
self
}
pub fn includes(&mut self, includes: impl IntoIterator<Item = impl AsRef<Path>>) -> &mut Self {
for include in includes {
self.include(include);
}
self
}
pub fn input(&mut self, input: impl AsRef<Path>) -> &mut Self {
self.inputs.push(input.as_ref().to_owned());
self
}
pub fn inputs(&mut self, inputs: impl IntoIterator<Item = impl AsRef<Path>>) -> &mut Self {
for input in inputs {
self.input(input);
}
self
}
pub fn protoc_path(&mut self, protoc: impl Into<PathBuf>) -> &mut Self {
self.protoc = Some(Protoc::from_path(&protoc.into().to_str().unwrap()));
self
}
pub fn customize(&mut self, customize: Customize) -> &mut Self {
self.customize = customize;
self
}
pub fn run(&self) -> Result<()> {
let protoc = match self.protoc.clone() {
Some(protoc) => protoc,
None => Protoc::from_env_path(),
};
protoc.check()?;
let temp_dir = tempfile::Builder::new().prefix("protoc-rust").tempdir()?;
let temp_file = temp_dir.path().join("descriptor.pbbin");
let includes: Vec<&str> = self.includes.iter().map(|p| p.to_str().unwrap()).collect();
let inputs: Vec<&str> = self.inputs.iter().map(|p| p.to_str().unwrap()).collect();
protoc.write_descriptor_set(protoc::DescriptorSetOutArgs {
out: temp_file.as_os_str().to_str().unwrap(),
includes: &includes,
input: &inputs,
include_imports: true,
})?;
let mut fds = Vec::new();
let mut file = fs::File::open(temp_file)?;
file.read_to_end(&mut fds)?;
drop(file);
drop(temp_dir);
let fds = FileDescriptorSet::parse_from_bytes(&fds)
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
let default_includes = vec![PathBuf::from(".")];
let includes = if self.includes.is_empty() {
&default_includes
} else {
&self.includes
};
let mut files_to_generate = Vec::new();
'outer: for file in &self.inputs {
for include in includes {
if let Some(truncated) =
remove_path_prefix(file.to_str().unwrap(), include.to_str().unwrap())
{
files_to_generate.push(truncated.to_owned());
continue 'outer;
}
}
return Err(Error::new(
io::ErrorKind::Other,
format!("file {:?} is not found in includes {:?}", file, includes),
));
}
protobuf_codegen::gen_and_write(
fds.get_file(),
&files_to_generate,
&self.out_dir,
&self.customize,
)
}
}
#[deprecated(since = "2.14", note = "Use Codegen instead")]
#[allow(deprecated)]
pub fn run(args: Args) -> Result<()> {
Codegen::new()
.out_dir(args.out_dir)
.includes(args.includes)
.inputs(args.input)
.customize(args.customize)
.run()
}
fn remove_path_prefix(mut path: &str, mut prefix: &str) -> Option<String> {
let slashes = Slashes::here();
path = slashes.remove_dot_slashes(path);
prefix = slashes.remove_dot_slashes(prefix);
if prefix == "" {
return Some(path.to_owned());
}
let path = slashes.norm_path(path);
let mut prefix = slashes.norm_path(prefix);
if prefix.ends_with("/") {
let l = prefix.len();
prefix.truncate(l - 1);
}
if !path.starts_with(&prefix) {
return None;
}
if path.len() <= prefix.len() {
return None;
}
if path.as_bytes()[prefix.len()] == b'/' {
return Some(path[prefix.len() + 1..].to_owned());
} else {
return None;
}
}
#[cfg(test)]
mod test {
#[test]
fn remove_path_prefix() {
assert_eq!(
Some("abc.proto".to_owned()),
super::remove_path_prefix("xxx/abc.proto", "xxx")
);
assert_eq!(
Some("abc.proto".to_owned()),
super::remove_path_prefix("xxx/abc.proto", "xxx/")
);
assert_eq!(
Some("abc.proto".to_owned()),
super::remove_path_prefix("../xxx/abc.proto", "../xxx/")
);
assert_eq!(
Some("abc.proto".to_owned()),
super::remove_path_prefix("abc.proto", ".")
);
assert_eq!(
Some("abc.proto".to_owned()),
super::remove_path_prefix("abc.proto", "./")
);
assert_eq!(None, super::remove_path_prefix("xxx/abc.proto", "yyy"));
assert_eq!(None, super::remove_path_prefix("xxx/abc.proto", "yyy/"));
}
}