1 use std::collections::HashSet;
2 use std::ffi::OsStr;
3 use std::ffi::OsString;
4 use std::path::Path;
5 use std::path::PathBuf;
6 
7 use anyhow::Context;
8 use protobuf::descriptor::FileDescriptorSet;
9 
10 use crate::protoc;
11 use crate::pure;
12 use crate::which_parser::WhichParser;
13 use crate::ParsedAndTypechecked;
14 
15 /// Configure and invoke `.proto` parser.
16 #[derive(Default, Debug)]
17 pub struct Parser {
18     which_parser: WhichParser,
19     pub(crate) includes: Vec<PathBuf>,
20     pub(crate) inputs: Vec<PathBuf>,
21     pub(crate) protoc: Option<PathBuf>,
22     pub(crate) protoc_extra_args: Vec<OsString>,
23     pub(crate) capture_stderr: bool,
24 }
25 
26 impl Parser {
27     /// Create new default configured parser.
new() -> Parser28     pub fn new() -> Parser {
29         Parser::default()
30     }
31 
32     /// Use pure rust parser.
pure(&mut self) -> &mut Self33     pub fn pure(&mut self) -> &mut Self {
34         self.which_parser = WhichParser::Pure;
35         self
36     }
37 
38     /// Use `protoc` for parsing.
protoc(&mut self) -> &mut Self39     pub fn protoc(&mut self) -> &mut Self {
40         self.which_parser = WhichParser::Protoc;
41         self
42     }
43 
44     /// Add an include directory.
include(&mut self, include: impl AsRef<Path>) -> &mut Self45     pub fn include(&mut self, include: impl AsRef<Path>) -> &mut Self {
46         self.includes.push(include.as_ref().to_owned());
47         self
48     }
49 
50     /// Add include directories.
includes(&mut self, includes: impl IntoIterator<Item = impl AsRef<Path>>) -> &mut Self51     pub fn includes(&mut self, includes: impl IntoIterator<Item = impl AsRef<Path>>) -> &mut Self {
52         for include in includes {
53             self.include(include);
54         }
55         self
56     }
57 
58     /// Append a `.proto` file path to compile
input(&mut self, input: impl AsRef<Path>) -> &mut Self59     pub fn input(&mut self, input: impl AsRef<Path>) -> &mut Self {
60         self.inputs.push(input.as_ref().to_owned());
61         self
62     }
63 
64     /// Append multiple `.proto` file paths to compile
inputs(&mut self, inputs: impl IntoIterator<Item = impl AsRef<Path>>) -> &mut Self65     pub fn inputs(&mut self, inputs: impl IntoIterator<Item = impl AsRef<Path>>) -> &mut Self {
66         for input in inputs {
67             self.input(input);
68         }
69         self
70     }
71 
72     /// Specify `protoc` path used for parsing.
73     ///
74     /// This is ignored if pure rust parser is used.
protoc_path(&mut self, protoc: &Path) -> &mut Self75     pub fn protoc_path(&mut self, protoc: &Path) -> &mut Self {
76         self.protoc = Some(protoc.to_owned());
77         self
78     }
79 
80     /// Extra arguments to pass to `protoc` command (like experimental options).
81     ///
82     /// This is ignored if pure rust parser is used.
protoc_extra_args( &mut self, args: impl IntoIterator<Item = impl AsRef<OsStr>>, ) -> &mut Self83     pub fn protoc_extra_args(
84         &mut self,
85         args: impl IntoIterator<Item = impl AsRef<OsStr>>,
86     ) -> &mut Self {
87         self.protoc_extra_args = args.into_iter().map(|s| s.as_ref().to_owned()).collect();
88         self
89     }
90 
91     /// Capture stderr and return it in error.
92     ///
93     /// This option applies only to `protoc` parser.
94     /// By default `protoc` stderr is inherited from this process stderr.
capture_stderr(&mut self) -> &mut Self95     pub fn capture_stderr(&mut self) -> &mut Self {
96         self.capture_stderr = true;
97         self
98     }
99 
100     /// Parse `.proto` files and typecheck them using pure Rust parser of `protoc` command.
parse_and_typecheck(&self) -> anyhow::Result<ParsedAndTypechecked>101     pub fn parse_and_typecheck(&self) -> anyhow::Result<ParsedAndTypechecked> {
102         match &self.which_parser {
103             WhichParser::Pure => {
104                 pure::parse_and_typecheck::parse_and_typecheck(&self).context("using pure parser")
105             }
106             WhichParser::Protoc => protoc::parse_and_typecheck::parse_and_typecheck(&self)
107                 .context("using protoc parser"),
108         }
109     }
110 
111     /// Parse and convert result to `FileDescriptorSet`.
file_descriptor_set(&self) -> anyhow::Result<FileDescriptorSet>112     pub fn file_descriptor_set(&self) -> anyhow::Result<FileDescriptorSet> {
113         let mut generated = self.parse_and_typecheck()?;
114         let relative_paths: HashSet<_> = generated
115             .relative_paths
116             .iter()
117             .map(|path| path.to_string())
118             .collect();
119         generated
120             .file_descriptors
121             .retain(|fd| relative_paths.contains(fd.name()));
122         let mut fds = FileDescriptorSet::new();
123         fds.file = generated.file_descriptors;
124         Ok(fds)
125     }
126 }
127