diff options
-rw-r--r-- | Cargo.toml | 2 | ||||
-rw-r--r-- | shell.nix | 6 | ||||
-rw-r--r-- | src/cli.rs | 7 | ||||
-rw-r--r-- | src/config.rs | 89 | ||||
-rw-r--r-- | src/doxygen.rs | 454 | ||||
-rw-r--r-- | src/doxygen/builders.rs | 109 | ||||
-rw-r--r-- | src/entities.rs | 216 | ||||
-rw-r--r-- | src/generator/config.rs | 53 | ||||
-rw-r--r-- | src/generator/mod.rs (renamed from src/generator.rs) | 71 | ||||
-rw-r--r-- | src/generator/pandoc.rs (renamed from src/pandoc.rs) | 100 | ||||
-rw-r--r-- | src/main.rs | 19 | ||||
-rw-r--r-- | src/parsing.rs | 482 | ||||
-rw-r--r-- | src/parsing/clang/config.rs | 53 | ||||
-rw-r--r-- | src/parsing/clang/entities.rs | 369 | ||||
-rw-r--r-- | src/parsing/clang/mod.rs | 5 | ||||
-rw-r--r-- | src/parsing/clang/parsing.rs | 748 | ||||
-rw-r--r-- | src/parsing/mod.rs | 1 | ||||
-rw-r--r-- | src/types.rs | 22 |
18 files changed, 1389 insertions, 1417 deletions
@@ -8,7 +8,7 @@ edition = "2018" | |||
8 | 8 | ||
9 | [dependencies] | 9 | [dependencies] |
10 | anyhow = "1" | 10 | anyhow = "1" |
11 | clang = "0.23" | 11 | clang = { version = "0.23", features = ["clang_8_0"] } |
12 | codemap = "0.1" | 12 | codemap = "0.1" |
13 | codemap-diagnostic = "0.1" | 13 | codemap-diagnostic = "0.1" |
14 | log = "0.4" | 14 | log = "0.4" |
@@ -4,9 +4,9 @@ let | |||
4 | clang = pkgs.llvmPackages_latest.clang; | 4 | clang = pkgs.llvmPackages_latest.clang; |
5 | mkWrapper = name: mode: pkgs.writeShellScriptBin name '' | 5 | mkWrapper = name: mode: pkgs.writeShellScriptBin name '' |
6 | ./target/${mode}/poseidoc \ | 6 | ./target/${mode}/poseidoc \ |
7 | '--extra-arg=-idirafter "${clang.libc_dev}/include"' \ | 7 | '--clang-extra-args=-idirafter "${clang.libc_dev}/include"' \ |
8 | --extra-arg="$(echo -n "${clang.default_cxx_stdlib_compile}")" \ | 8 | --clang-extra-args="$(echo -n "${clang.default_cxx_stdlib_compile}")" \ |
9 | '--extra-arg="-resource-dir=${clang}/resource-root"' \ | 9 | '--clang-extra-args="-resource-dir=${clang}/resource-root"' \ |
10 | "$@" | 10 | "$@" |
11 | ''; | 11 | ''; |
12 | 12 | ||
@@ -1,4 +1,4 @@ | |||
1 | use crate::config::Config; | 1 | use crate::config::ProvidedConfig; |
2 | 2 | ||
3 | use structopt::StructOpt; | 3 | use structopt::StructOpt; |
4 | 4 | ||
@@ -9,9 +9,6 @@ pub(crate) struct Cli { | |||
9 | #[structopt(long, short, parse(from_occurrences))] | 9 | #[structopt(long, short, parse(from_occurrences))] |
10 | pub(crate) verbosity: u8, | 10 | pub(crate) verbosity: u8, |
11 | 11 | ||
12 | #[structopt(long, number_of_values = 1, parse(try_from_str = shell_words::split))] | ||
13 | pub(crate) extra_arg: Vec<Vec<String>>, | ||
14 | |||
15 | #[structopt(long, short = "C", default_value = ".")] | 12 | #[structopt(long, short = "C", default_value = ".")] |
16 | pub(crate) directory: PathBuf, | 13 | pub(crate) directory: PathBuf, |
17 | 14 | ||
@@ -19,7 +16,7 @@ pub(crate) struct Cli { | |||
19 | pub(crate) command: Command, | 16 | pub(crate) command: Command, |
20 | 17 | ||
21 | #[structopt(flatten)] | 18 | #[structopt(flatten)] |
22 | pub(crate) common_options: Config, | 19 | pub(crate) common_options: ProvidedConfig, |
23 | } | 20 | } |
24 | 21 | ||
25 | #[derive(Debug, Clone, StructOpt)] | 22 | #[derive(Debug, Clone, StructOpt)] |
diff --git a/src/config.rs b/src/config.rs index b448469..72a9ff6 100644 --- a/src/config.rs +++ b/src/config.rs | |||
@@ -1,4 +1,8 @@ | |||
1 | use crate::cli::Cli; | 1 | use crate::cli::Cli; |
2 | use crate::generator::config::{ | ||
3 | Config as GeneratorConfig, ProvidedConfig as ProvidedGeneratorConfig, | ||
4 | }; | ||
5 | use crate::parsing::clang::config::{Config as ClangConfig, ProvidedConfig as ProvidedClangConfig}; | ||
2 | 6 | ||
3 | use anyhow::{anyhow, Context, Result}; | 7 | use anyhow::{anyhow, Context, Result}; |
4 | use codemap::CodeMap; | 8 | use codemap::CodeMap; |
@@ -12,71 +16,56 @@ use std::sync::Arc; | |||
12 | 16 | ||
13 | pub(super) const DEFAULT_PROJECT_CONFIGURATION_FILE_NAME: &str = "poseidoc.toml"; | 17 | pub(super) const DEFAULT_PROJECT_CONFIGURATION_FILE_NAME: &str = "poseidoc.toml"; |
14 | 18 | ||
15 | #[derive(Debug, Clone, StructOpt, Deserialize, Serialize)] | 19 | #[derive(Debug, Clone, Serialize)] |
16 | #[structopt(rename_all = "kebab-case")] | ||
17 | #[serde(rename_all = "kebab-case", default)] | ||
18 | pub(crate) struct Config { | 20 | pub(crate) struct Config { |
19 | #[structopt(skip)] | 21 | pub(crate) name: String, |
20 | pub(crate) name: Option<String>, | 22 | pub(crate) clang: ClangConfig, |
21 | #[structopt(long)] | 23 | pub(crate) generator: GeneratorConfig, |
22 | pub(crate) compile_commands_location: Option<PathBuf>, | ||
23 | #[structopt(skip = vec![])] | ||
24 | pub(crate) extra_clang_args: Vec<String>, | ||
25 | #[structopt(flatten)] | ||
26 | pub(crate) class: ClassConfig, | ||
27 | } | ||
28 | |||
29 | impl Config { | ||
30 | pub(crate) fn merge_cli(self, cli: Cli) -> Self { | ||
31 | Config { | ||
32 | name: cli.common_options.name.or(self.name), | ||
33 | compile_commands_location: cli | ||
34 | .common_options | ||
35 | .compile_commands_location | ||
36 | .or(self.compile_commands_location), | ||
37 | extra_clang_args: cli | ||
38 | .extra_arg | ||
39 | .into_iter() | ||
40 | .flatten() | ||
41 | .chain(self.extra_clang_args) | ||
42 | .collect(), | ||
43 | class: self.class.merge_cli(cli.common_options.class), | ||
44 | } | ||
45 | } | ||
46 | } | 24 | } |
47 | 25 | ||
48 | impl Default for Config { | 26 | impl Default for Config { |
49 | fn default() -> Self { | 27 | fn default() -> Self { |
50 | Config { | 28 | Config { |
51 | name: Some(String::from("My Project")), | 29 | name: String::from("My Project"), |
52 | compile_commands_location: Some(PathBuf::from(r".")), | 30 | clang: ClangConfig::default(), |
53 | extra_clang_args: Vec::new(), | 31 | generator: GeneratorConfig::default(), |
54 | class: ClassConfig::default(), | ||
55 | } | 32 | } |
56 | } | 33 | } |
57 | } | 34 | } |
58 | 35 | ||
59 | #[derive(Debug, Clone, StructOpt, Deserialize, Serialize)] | 36 | impl Config { |
60 | #[structopt(rename_all = "kebab-case")] | 37 | pub(crate) fn from_merge(cli: Cli, conf: ProvidedConfig) -> Result<Self> { |
61 | #[serde(rename_all = "kebab-case", default)] | 38 | Ok(Config { |
62 | pub(crate) struct ClassConfig { | 39 | name: cli |
63 | } | 40 | .common_options |
64 | 41 | .name | |
65 | impl ClassConfig { | 42 | .or(conf.name) |
66 | fn merge_cli(self, _cli: ClassConfig) -> Self { | 43 | .unwrap_or_else(|| String::from("My Project")), |
67 | ClassConfig { | 44 | clang: ClangConfig::from_merge(cli.common_options.clang, conf.clang)?, |
68 | } | 45 | generator: GeneratorConfig::from_merge(cli.common_options.generator, conf.generator), |
46 | }) | ||
69 | } | 47 | } |
70 | } | 48 | } |
71 | 49 | ||
72 | impl Default for ClassConfig { | 50 | /// Same as ProvidedConfig but with options for when not user-provided |
73 | fn default() -> Self { | 51 | #[derive(Debug, Clone, Default, StructOpt, Deserialize)] |
74 | ClassConfig { | 52 | #[structopt(rename_all = "kebab-case")] |
75 | } | 53 | #[serde(rename_all = "kebab-case")] |
76 | } | 54 | pub(crate) struct ProvidedConfig { |
55 | #[structopt(skip)] | ||
56 | pub(crate) name: Option<String>, | ||
57 | #[structopt(flatten)] | ||
58 | #[serde(default)] | ||
59 | pub(crate) clang: ProvidedClangConfig, | ||
60 | #[structopt(skip)] | ||
61 | #[serde(default)] | ||
62 | pub(crate) generator: ProvidedGeneratorConfig, | ||
77 | } | 63 | } |
78 | 64 | ||
79 | pub(super) fn load_config(location: impl AsRef<Path>, codemap: &mut CodeMap) -> Result<Config> { | 65 | pub(super) fn load_config( |
66 | location: impl AsRef<Path>, | ||
67 | codemap: &mut CodeMap, | ||
68 | ) -> Result<ProvidedConfig> { | ||
80 | let location = location.as_ref(); | 69 | let location = location.as_ref(); |
81 | 70 | ||
82 | let final_path = if location.is_dir() { | 71 | let final_path = if location.is_dir() { |
diff --git a/src/doxygen.rs b/src/doxygen.rs deleted file mode 100644 index 8d2267d..0000000 --- a/src/doxygen.rs +++ /dev/null | |||
@@ -1,454 +0,0 @@ | |||
1 | /// A 'recursive descent' parser for Doxygen XML files | ||
2 | mod builders; | ||
3 | |||
4 | use xml::{ | ||
5 | attribute::OwnedAttribute, | ||
6 | reader::{EventReader, ParserConfig, XmlEvent}, | ||
7 | }; | ||
8 | |||
9 | use std::collections::HashMap; | ||
10 | use std::io::Read; | ||
11 | |||
12 | // === Types === | ||
13 | |||
14 | #[derive(Debug, Clone)] | ||
15 | pub(crate) enum DoxygenType { | ||
16 | NameSpace(NameSpace), | ||
17 | Class(Class), | ||
18 | Unhandled, | ||
19 | } | ||
20 | |||
21 | #[derive(Debug, Clone)] | ||
22 | pub(crate) struct NameSpace { | ||
23 | pub(crate) name: String, | ||
24 | pub(crate) members: Vec<String>, | ||
25 | pub(crate) brief_description: Description, | ||
26 | pub(crate) detailed_description: Description, | ||
27 | } | ||
28 | |||
29 | #[derive(Debug, Clone)] | ||
30 | pub(crate) struct FunctionArgument { | ||
31 | pub(crate) name: String, | ||
32 | pub(crate) r#type: String, | ||
33 | pub(crate) default_value: Option<String>, | ||
34 | } | ||
35 | |||
36 | #[derive(Debug, Clone)] | ||
37 | pub(crate) enum Member { | ||
38 | Variable { | ||
39 | name: String, | ||
40 | r#type: String, | ||
41 | }, | ||
42 | // What | ||
43 | /* | ||
44 | Enum { | ||
45 | |||
46 | } | ||
47 | */ | ||
48 | Function { | ||
49 | name: String, | ||
50 | args: Vec<FunctionArgument>, | ||
51 | return_type: String, | ||
52 | }, | ||
53 | Unhandled, | ||
54 | } | ||
55 | |||
56 | #[derive(Debug, Clone)] | ||
57 | pub(crate) struct Class { | ||
58 | pub(crate) name: String, | ||
59 | pub(crate) inners: Vec<(String, String)>, | ||
60 | pub(crate) sections: HashMap<String, Vec<Member>>, | ||
61 | pub(crate) brief_description: Description, | ||
62 | pub(crate) detailed_description: Description, | ||
63 | } | ||
64 | |||
65 | // === Description === | ||
66 | |||
67 | #[derive(Debug, Clone)] | ||
68 | pub(crate) struct Description { | ||
69 | pub(crate) inner: Vec<DescriptionNode>, | ||
70 | } | ||
71 | |||
72 | impl Description { | ||
73 | fn new() -> Self { | ||
74 | Self { inner: Vec::new() } | ||
75 | } | ||
76 | } | ||
77 | |||
78 | #[derive(Debug, Clone)] | ||
79 | pub(crate) enum DescriptionNode { | ||
80 | Text(String), | ||
81 | Title(String), | ||
82 | Para(Paragraph), | ||
83 | Sect1(String), | ||
84 | Internal(String), | ||
85 | } | ||
86 | |||
87 | #[derive(Debug, Clone)] | ||
88 | pub(crate) struct Paragraph { | ||
89 | pub(crate) inner: Vec<ParagraphNode>, | ||
90 | } | ||
91 | |||
92 | #[derive(Debug, Clone)] | ||
93 | pub(crate) enum ParagraphNode { | ||
94 | Text(String), | ||
95 | Ref(Ref), | ||
96 | } | ||
97 | |||
98 | #[derive(Debug, Clone)] | ||
99 | pub(crate) struct Ref { | ||
100 | pub(crate) id: String, | ||
101 | pub(crate) kind: RefKind, | ||
102 | pub(crate) text: String, | ||
103 | } | ||
104 | |||
105 | #[derive(Debug, Clone)] | ||
106 | pub(crate) enum RefKind { | ||
107 | Compound, | ||
108 | Member, | ||
109 | } | ||
110 | |||
111 | #[derive(Debug, Clone)] | ||
112 | enum DoxygenTypeKind { | ||
113 | NameSpace, | ||
114 | Unhandled, | ||
115 | } | ||
116 | |||
117 | trait TypeBuilder { | ||
118 | //const KIND: DoxygenTypeKind; | ||
119 | |||
120 | fn build(self: Box<Self>) -> DoxygenType; | ||
121 | |||
122 | fn name(&mut self, value: String); | ||
123 | fn brief_description(&mut self, description: Description); | ||
124 | fn detailed_description(&mut self, description: Description); | ||
125 | |||
126 | fn add_inner(&mut self, name: String, kind: String); | ||
127 | fn add_member(&mut self, section: String, member: Member); | ||
128 | } | ||
129 | |||
130 | trait FromXML { | ||
131 | fn from_xml(attributes: Vec<OwnedAttribute>, event_reader: &mut EventReader<impl Read>) | ||
132 | -> Self; | ||
133 | } | ||
134 | |||
135 | impl FromXML for Description { | ||
136 | fn from_xml( | ||
137 | _attributes: Vec<OwnedAttribute>, | ||
138 | mut event_reader: &mut EventReader<impl Read>, | ||
139 | ) -> Self { | ||
140 | let mut inner = Vec::new(); | ||
141 | |||
142 | while let Ok(event) = event_reader.next() { | ||
143 | match event { | ||
144 | XmlEvent::Characters(text) => inner.push(DescriptionNode::Text(text)), | ||
145 | XmlEvent::StartElement { | ||
146 | name, attributes, .. | ||
147 | } => inner.push(match name.local_name.as_str() { | ||
148 | "para" => { | ||
149 | DescriptionNode::Para(Paragraph::from_xml(attributes, &mut event_reader)) | ||
150 | } | ||
151 | "title" => DescriptionNode::Title(event_simple(&mut event_reader)), | ||
152 | "sect1" => DescriptionNode::Sect1(event_simple(&mut event_reader)), | ||
153 | "internal" => DescriptionNode::Internal(event_simple(&mut event_reader)), | ||
154 | other => { | ||
155 | warn!("Description element not supported: {}", other); | ||
156 | continue; | ||
157 | } | ||
158 | }), | ||
159 | XmlEvent::EndElement { .. } => break, | ||
160 | other => { | ||
161 | warn!("Description event not supported: {:?}", other); | ||
162 | } | ||
163 | } | ||
164 | } | ||
165 | |||
166 | Self { inner } | ||
167 | } | ||
168 | } | ||
169 | |||
170 | impl FromXML for Paragraph { | ||
171 | fn from_xml( | ||
172 | _attributes: Vec<OwnedAttribute>, | ||
173 | mut event_reader: &mut EventReader<impl Read>, | ||
174 | ) -> Self { | ||
175 | let mut inner = Vec::new(); | ||
176 | |||
177 | while let Ok(event) = event_reader.next() { | ||
178 | match event { | ||
179 | XmlEvent::Characters(text) => inner.push(ParagraphNode::Text(text)), | ||
180 | XmlEvent::StartElement { | ||
181 | name, attributes, .. | ||
182 | } => inner.push(match name.local_name.as_str() { | ||
183 | "ref" => ParagraphNode::Ref(Ref::from_xml(attributes, &mut event_reader)), | ||
184 | other => { | ||
185 | warn!("Paragraph element not supported: {}", other); | ||
186 | continue; | ||
187 | } | ||
188 | }), | ||
189 | XmlEvent::EndElement { .. } => break, | ||
190 | other => { | ||
191 | warn!("Paragraph event not supported: {:?}", other); | ||
192 | } | ||
193 | } | ||
194 | } | ||
195 | |||
196 | Self { inner } | ||
197 | } | ||
198 | } | ||
199 | |||
200 | impl FromXML for Ref { | ||
201 | fn from_xml( | ||
202 | attributes: Vec<OwnedAttribute>, | ||
203 | mut event_reader: &mut EventReader<impl Read>, | ||
204 | ) -> Self { | ||
205 | let mut id = None; | ||
206 | let mut kind = None; | ||
207 | |||
208 | for attr in attributes.into_iter() { | ||
209 | match attr.name.local_name.as_str() { | ||
210 | "refid" => id = Some(attr.value), | ||
211 | "kindref" => { | ||
212 | kind = Some(match attr.value.as_str() { | ||
213 | "compound" => RefKind::Compound, | ||
214 | "member" => RefKind::Member, | ||
215 | other => { | ||
216 | warn!("Ref kind not supported: {}", other); | ||
217 | RefKind::Compound | ||
218 | } | ||
219 | }); | ||
220 | } | ||
221 | other => { | ||
222 | warn!("Ref element not supported: {}", other); | ||
223 | } | ||
224 | } | ||
225 | } | ||
226 | |||
227 | let text = event_simple(&mut event_reader); | ||
228 | |||
229 | Ref { | ||
230 | id: id.unwrap(), | ||
231 | kind: kind.unwrap(), | ||
232 | text, | ||
233 | } | ||
234 | } | ||
235 | } | ||
236 | |||
237 | /// Parse a type from a XML Doxygen file | ||
238 | pub(crate) fn parse_type(reader: impl Read) -> Vec<DoxygenType> { | ||
239 | let mut event_reader = EventReader::new(reader); | ||
240 | |||
241 | match event_reader.next().unwrap() { | ||
242 | XmlEvent::StartDocument { .. } => (), | ||
243 | _ => panic!("XML file does not begin with document"), | ||
244 | } | ||
245 | |||
246 | match event_reader.next().unwrap() { | ||
247 | XmlEvent::StartElement { name, .. } => { | ||
248 | if name.local_name != "doxygen" { | ||
249 | panic!("XML file does not start with a 'doxygen' element"); | ||
250 | } | ||
251 | } | ||
252 | _ => panic!("XML file does not start with a 'doxygen' element"), | ||
253 | } | ||
254 | |||
255 | let mut result = Vec::new(); | ||
256 | |||
257 | while let Ok(event) = event_reader.next() { | ||
258 | match dbg!(event) { | ||
259 | XmlEvent::StartElement { | ||
260 | name, attributes, .. | ||
261 | } => { | ||
262 | if name.local_name != "compounddef" { | ||
263 | panic!("'doxygen' is not a sequence of 'compounddef' elements"); | ||
264 | } | ||
265 | |||
266 | result.push(event_compounddef(attributes, &mut event_reader)); | ||
267 | } | ||
268 | XmlEvent::Whitespace(_) => (), | ||
269 | XmlEvent::EndElement { .. } => break, | ||
270 | _ => panic!("'doxygen' is not a sequence of 'compounddef' elements"), | ||
271 | } | ||
272 | } | ||
273 | |||
274 | // Unnecessarily strict? | ||
275 | match event_reader.next().unwrap() { | ||
276 | XmlEvent::EndDocument => (), | ||
277 | _ => panic!("XML file does not end after 'doxygen' element"), | ||
278 | } | ||
279 | |||
280 | dbg!(result) | ||
281 | } | ||
282 | |||
283 | fn event_compounddef( | ||
284 | attributes: Vec<OwnedAttribute>, | ||
285 | mut event_reader: &mut EventReader<impl Read>, | ||
286 | ) -> DoxygenType { | ||
287 | let kind = attributes | ||
288 | .into_iter() | ||
289 | .find_map(|attr| { | ||
290 | if attr.name.local_name == "kind" { | ||
291 | Some(attr.value) | ||
292 | } else { | ||
293 | None | ||
294 | } | ||
295 | }) | ||
296 | .unwrap(); | ||
297 | |||
298 | let mut builder = builders::builder_for(kind); | ||
299 | |||
300 | while let Ok(event) = event_reader.next() { | ||
301 | match dbg!(event) { | ||
302 | XmlEvent::StartElement { | ||
303 | name, attributes, .. | ||
304 | } => match name.local_name.as_str() { | ||
305 | "compoundname" => { | ||
306 | let name = event_simple(&mut event_reader); | ||
307 | builder.name(name); | ||
308 | } | ||
309 | "briefdescription" => { | ||
310 | let brief_description = Description::from_xml(attributes, &mut event_reader); | ||
311 | builder.brief_description(brief_description); | ||
312 | } | ||
313 | "detaileddescription" => { | ||
314 | let detailed_description = Description::from_xml(attributes, &mut event_reader); | ||
315 | builder.detailed_description(detailed_description); | ||
316 | } | ||
317 | "location" => { | ||
318 | event_simple(&mut event_reader); | ||
319 | debug!("Do something?"); | ||
320 | } | ||
321 | "sectiondef" => { | ||
322 | event_section(&mut builder, &mut event_reader); | ||
323 | } | ||
324 | other_name if is_inner_type(other_name) => { | ||
325 | let inner = event_simple(&mut event_reader); | ||
326 | builder.add_inner(inner, other_name.to_string()); | ||
327 | } | ||
328 | _other_name => { | ||
329 | event_ignore(&mut event_reader); | ||
330 | } | ||
331 | }, | ||
332 | XmlEvent::Whitespace(_) => (), | ||
333 | XmlEvent::EndElement { .. } => break, | ||
334 | _ => panic!("Unhandled XML event"), | ||
335 | } | ||
336 | } | ||
337 | |||
338 | builder.build() | ||
339 | } | ||
340 | |||
341 | /// Returns true if the given XML Element is a reference to another inner type. | ||
342 | /// | ||
343 | /// Corresponds to the "refType" XSD type in `compound.xsd` | ||
344 | fn is_inner_type(element_name: &str) -> bool { | ||
345 | match element_name { | ||
346 | "innerdir" | "innerfile" | "innerclass" | "innernamespace" | "innerpage" | "innergroup" => { | ||
347 | true | ||
348 | } | ||
349 | _ => false, | ||
350 | } | ||
351 | } | ||
352 | |||
353 | /// Get the text inside a simple, non recursive XML tag | ||
354 | fn event_simple(event_reader: &mut EventReader<impl Read>) -> String { | ||
355 | let result; | ||
356 | |||
357 | match dbg!(event_reader.next().unwrap()) { | ||
358 | XmlEvent::Characters(tmp_result) => { | ||
359 | result = tmp_result; | ||
360 | } | ||
361 | XmlEvent::EndElement { .. } => return "".to_string(), | ||
362 | _ => panic!("Simple XML event is not so simple"), | ||
363 | } | ||
364 | |||
365 | match dbg!(event_reader.next().unwrap()) { | ||
366 | XmlEvent::EndElement { .. } => (), | ||
367 | _ => panic!("Simple XML event is not so simple"), | ||
368 | } | ||
369 | |||
370 | result | ||
371 | } | ||
372 | |||
373 | fn event_ignore(mut event_reader: &mut EventReader<impl Read>) { | ||
374 | loop { | ||
375 | let event = event_reader.next().unwrap(); | ||
376 | match dbg!(event) { | ||
377 | XmlEvent::StartElement { .. } => event_ignore(&mut event_reader), | ||
378 | XmlEvent::EndElement { .. } => break, | ||
379 | _ => (), | ||
380 | } | ||
381 | } | ||
382 | } | ||
383 | |||
384 | fn event_section( | ||
385 | mut builder: &mut Box<dyn TypeBuilder>, | ||
386 | mut event_reader: &mut EventReader<impl Read>, | ||
387 | ) { | ||
388 | loop { | ||
389 | let event = event_reader.next().unwrap(); | ||
390 | match dbg!(event) { | ||
391 | XmlEvent::StartElement { | ||
392 | name, attributes, .. | ||
393 | } => match name.local_name.as_str() { | ||
394 | "memberdef" => { | ||
395 | let member = event_member(attributes, &mut event_reader); | ||
396 | builder.add_member("bla".to_owned(), member); | ||
397 | } | ||
398 | _ => panic!("Unhandled thing"), | ||
399 | }, | ||
400 | |||
401 | XmlEvent::Whitespace(_) => (), | ||
402 | XmlEvent::EndElement { .. } => break, | ||
403 | _ => panic!("Unknown section XML event"), | ||
404 | } | ||
405 | } | ||
406 | } | ||
407 | |||
408 | fn event_member( | ||
409 | attributes: Vec<OwnedAttribute>, | ||
410 | mut event_reader: &mut EventReader<impl Read>, | ||
411 | ) -> Member { | ||
412 | let kind = attributes | ||
413 | .into_iter() | ||
414 | .find_map(|attr| { | ||
415 | if attr.name.local_name == "kind" { | ||
416 | Some(attr.value) | ||
417 | } else { | ||
418 | None | ||
419 | } | ||
420 | }) | ||
421 | .unwrap(); | ||
422 | |||
423 | match kind.as_str() { | ||
424 | "variable" => event_member_variable(&mut event_reader), | ||
425 | _ => { | ||
426 | event_ignore(&mut event_reader); | ||
427 | Member::Unhandled | ||
428 | } | ||
429 | } | ||
430 | } | ||
431 | |||
432 | fn event_member_variable(mut event_reader: &mut EventReader<impl Read>) -> Member { | ||
433 | let mut member_name = None; | ||
434 | let mut r#type = None; | ||
435 | |||
436 | loop { | ||
437 | let event = event_reader.next().unwrap(); | ||
438 | match dbg!(event) { | ||
439 | XmlEvent::StartElement { name, .. } => match name.local_name.as_str() { | ||
440 | "name" => member_name = Some(event_simple(&mut event_reader)), | ||
441 | "definition" => r#type = Some(event_simple(&mut event_reader)), | ||
442 | _ => event_ignore(&mut event_reader), | ||
443 | }, | ||
444 | XmlEvent::Whitespace(_) => (), | ||
445 | XmlEvent::EndElement { .. } => break, | ||
446 | _ => panic!("Unknown member XML event"), | ||
447 | } | ||
448 | } | ||
449 | |||
450 | Member::Variable { | ||
451 | name: member_name.unwrap(), | ||
452 | r#type: r#type.unwrap(), | ||
453 | } | ||
454 | } | ||
diff --git a/src/doxygen/builders.rs b/src/doxygen/builders.rs deleted file mode 100644 index 9b60325..0000000 --- a/src/doxygen/builders.rs +++ /dev/null | |||
@@ -1,109 +0,0 @@ | |||
1 | use super::*; | ||
2 | |||
3 | pub(super) fn builder_for(doxygen_kind: String) -> Box<dyn TypeBuilder> { | ||
4 | match doxygen_kind.as_str() { | ||
5 | "namespace" => Box::new(NameSpaceBuilder::default()), | ||
6 | "class" | "struct" => Box::new(ClassBuilder::default()), | ||
7 | _ => Box::new(UnhandledBuilder), | ||
8 | } | ||
9 | } | ||
10 | |||
11 | #[derive(Debug, Default)] | ||
12 | struct NameSpaceBuilder { | ||
13 | name: Option<String>, | ||
14 | members: Vec<String>, | ||
15 | brief_description: Option<Description>, | ||
16 | detailed_description: Option<Description>, | ||
17 | } | ||
18 | |||
19 | impl TypeBuilder for NameSpaceBuilder { | ||
20 | fn build(self: Box<Self>) -> DoxygenType { | ||
21 | let name = self.name.unwrap(); | ||
22 | |||
23 | DoxygenType::NameSpace(NameSpace { | ||
24 | name, | ||
25 | members: self.members, | ||
26 | brief_description: self.brief_description.unwrap(), | ||
27 | detailed_description: self.detailed_description.unwrap(), | ||
28 | }) | ||
29 | } | ||
30 | |||
31 | fn name(&mut self, value: String) { | ||
32 | self.name = Some(value); | ||
33 | } | ||
34 | |||
35 | fn add_inner(&mut self, name: String, kind: String) { | ||
36 | self.members.push(name); | ||
37 | } | ||
38 | |||
39 | fn add_member(&mut self, _section: String, _member: Member) { | ||
40 | panic!("Adding member in namespace?"); | ||
41 | } | ||
42 | |||
43 | fn brief_description(&mut self, description: Description) { | ||
44 | self.brief_description = Some(description); | ||
45 | } | ||
46 | |||
47 | fn detailed_description(&mut self, description: Description) { | ||
48 | self.brief_description = Some(description); | ||
49 | } | ||
50 | } | ||
51 | |||
52 | #[derive(Debug, Default)] | ||
53 | struct UnhandledBuilder; | ||
54 | |||
55 | impl TypeBuilder for UnhandledBuilder { | ||
56 | fn build(self: Box<Self>) -> DoxygenType { | ||
57 | DoxygenType::Unhandled | ||
58 | } | ||
59 | |||
60 | fn name(&mut self, _value: String) {} | ||
61 | |||
62 | fn add_inner(&mut self, _name: String, _kind: String) {} | ||
63 | fn add_member(&mut self, _section: String, _member: Member) {} | ||
64 | fn brief_description(&mut self, _description: Description) {} | ||
65 | fn detailed_description(&mut self, _description: Description) {} | ||
66 | } | ||
67 | |||
68 | #[derive(Debug, Default)] | ||
69 | struct ClassBuilder { | ||
70 | name: Option<String>, | ||
71 | inners: Vec<(String, String)>, | ||
72 | sections: HashMap<String, Vec<Member>>, | ||
73 | brief_description: Option<Description>, | ||
74 | detailed_description: Option<Description>, | ||
75 | } | ||
76 | |||
77 | impl TypeBuilder for ClassBuilder { | ||
78 | fn build(self: Box<Self>) -> DoxygenType { | ||
79 | let name = self.name.unwrap(); | ||
80 | |||
81 | DoxygenType::Class(Class { | ||
82 | name, | ||
83 | inners: self.inners, | ||
84 | sections: self.sections, | ||
85 | brief_description: self.brief_description.unwrap_or(Description::new()), | ||
86 | detailed_description: self.detailed_description.unwrap_or(Description::new()), | ||
87 | }) | ||
88 | } | ||
89 | |||
90 | fn name(&mut self, value: String) { | ||
91 | self.name = Some(value); | ||
92 | } | ||
93 | |||
94 | fn add_inner(&mut self, name: String, kind: String) { | ||
95 | self.inners.push((name, kind)); | ||
96 | } | ||
97 | |||
98 | fn add_member(&mut self, section: String, member: Member) { | ||
99 | self.sections.entry(section).or_default().push(member); | ||
100 | } | ||
101 | |||
102 | fn brief_description(&mut self, description: Description) { | ||
103 | self.brief_description = Some(description); | ||
104 | } | ||
105 | |||
106 | fn detailed_description(&mut self, description: Description) { | ||
107 | self.brief_description = Some(description); | ||
108 | } | ||
109 | } | ||
diff --git a/src/entities.rs b/src/entities.rs deleted file mode 100644 index 89905c6..0000000 --- a/src/entities.rs +++ /dev/null | |||
@@ -1,216 +0,0 @@ | |||
1 | use std::collections::HashMap; | ||
2 | |||
3 | pub(crate) type DynEntity = dyn Entity + Sync + Send; | ||
4 | |||
5 | #[derive(Debug, Clone, Hash, PartialEq, Eq)] | ||
6 | pub(crate) struct Usr(pub(crate) String); | ||
7 | |||
8 | pub(crate) struct EntitiesManager { | ||
9 | toplevel_entities: HashMap<Usr, Box<DynEntity>>, | ||
10 | descriptions: HashMap<Usr, Description>, | ||
11 | } | ||
12 | |||
13 | pub(crate) struct EntitiesManagerComponents { | ||
14 | pub(crate) toplevel_entities: HashMap<Usr, Box<DynEntity>>, | ||
15 | pub(crate) descriptions: HashMap<Usr, Description>, | ||
16 | } | ||
17 | |||
18 | impl EntitiesManager { | ||
19 | pub fn new() -> Self { | ||
20 | EntitiesManager { | ||
21 | toplevel_entities: HashMap::new(), | ||
22 | descriptions: HashMap::new(), | ||
23 | } | ||
24 | } | ||
25 | |||
26 | pub fn insert(&mut self, usr: Usr, description: Description) { | ||
27 | info!("Found entity {:?}", description.name); | ||
28 | self.descriptions.insert(usr, description); | ||
29 | } | ||
30 | |||
31 | pub fn insert_toplevel(&mut self, usr: Usr, entity: Box<DynEntity>) { | ||
32 | self.toplevel_entities.insert(usr, entity); | ||
33 | } | ||
34 | |||
35 | pub fn decompose(self) -> EntitiesManagerComponents { | ||
36 | EntitiesManagerComponents { | ||
37 | toplevel_entities: self.toplevel_entities, | ||
38 | descriptions: self.descriptions, | ||
39 | } | ||
40 | } | ||
41 | } | ||
42 | |||
43 | #[derive(Debug, Clone, Default)] | ||
44 | pub(crate) struct Description { | ||
45 | pub(crate) name: String, | ||
46 | pub(crate) brief: String, | ||
47 | pub(crate) detailed: String, | ||
48 | } | ||
49 | |||
50 | impl Description { | ||
51 | pub(crate) fn with_name(name: String) -> Self { | ||
52 | Description { | ||
53 | name, | ||
54 | ..Default::default() | ||
55 | } | ||
56 | } | ||
57 | } | ||
58 | |||
59 | #[derive(Debug, Clone)] | ||
60 | pub(crate) struct Described<T> { | ||
61 | pub(crate) description: Description, | ||
62 | pub(crate) entity: T, | ||
63 | } | ||
64 | |||
65 | pub(crate) struct ChildGroup<'a> { | ||
66 | pub(crate) name: &'static str, | ||
67 | pub(crate) children: Vec<(&'a Usr, &'a DynEntity)>, | ||
68 | } | ||
69 | |||
70 | pub(crate) trait Entity { | ||
71 | fn kind_name(&self) -> &'static str; | ||
72 | fn embeddable_children(&self) -> Vec<ChildGroup> { | ||
73 | Vec::new() | ||
74 | } | ||
75 | fn separate_children(&self) -> Vec<ChildGroup> { | ||
76 | Vec::new() | ||
77 | } | ||
78 | } | ||
79 | |||
80 | #[derive(Debug, Clone)] | ||
81 | pub(crate) struct NameSpace { | ||
82 | // TODO: replace with Vec to keep order | ||
83 | pub(crate) members: HashMap<Usr, NameSpaceChild>, | ||
84 | } | ||
85 | |||
86 | #[derive(Debug, Clone)] | ||
87 | pub(crate) enum NameSpaceChild { | ||
88 | NameSpace(NameSpace), | ||
89 | Class(Class), | ||
90 | Variable(Variable), | ||
91 | Function(Function), | ||
92 | } | ||
93 | |||
94 | impl Entity for NameSpace { | ||
95 | fn kind_name(&self) -> &'static str { | ||
96 | "namespace" | ||
97 | } | ||
98 | |||
99 | fn embeddable_children(&self) -> Vec<ChildGroup> { | ||
100 | let mut variables = Vec::new(); | ||
101 | let mut functions = Vec::new(); | ||
102 | for (usr, member) in &self.members { | ||
103 | match member { | ||
104 | NameSpaceChild::NameSpace(_) => {} | ||
105 | NameSpaceChild::Class(_) => {} | ||
106 | NameSpaceChild::Variable(variable) => variables.push((usr, variable as &DynEntity)), | ||
107 | NameSpaceChild::Function(function) => functions.push((usr, function as &DynEntity)), | ||
108 | } | ||
109 | } | ||
110 | |||
111 | vec![ | ||
112 | ChildGroup { | ||
113 | name: "Variables", | ||
114 | children: variables, | ||
115 | }, | ||
116 | ChildGroup { | ||
117 | name: "Functions", | ||
118 | children: functions, | ||
119 | }, | ||
120 | ] | ||
121 | } | ||
122 | |||
123 | fn separate_children(&self) -> Vec<ChildGroup> { | ||
124 | let mut namespaces = Vec::new(); | ||
125 | let mut classes = Vec::new(); | ||
126 | for (usr, member) in &self.members { | ||
127 | match member { | ||
128 | NameSpaceChild::NameSpace(namespace) => { | ||
129 | namespaces.push((usr, namespace as &DynEntity)) | ||
130 | } | ||
131 | NameSpaceChild::Class(class) => classes.push((usr, class as &DynEntity)), | ||
132 | NameSpaceChild::Variable(_) => {} | ||
133 | NameSpaceChild::Function(_) => {} | ||
134 | } | ||
135 | } | ||
136 | |||
137 | vec![ | ||
138 | ChildGroup { | ||
139 | name: "Namespaces", | ||
140 | children: namespaces, | ||
141 | }, | ||
142 | ChildGroup { | ||
143 | name: "Classes", | ||
144 | children: classes, | ||
145 | }, | ||
146 | ] | ||
147 | } | ||
148 | } | ||
149 | |||
150 | #[derive(Debug, Clone)] | ||
151 | pub(crate) struct Variable { | ||
152 | pub(crate) r#type: String, | ||
153 | } | ||
154 | |||
155 | impl Entity for Variable { | ||
156 | fn kind_name(&self) -> &'static str { | ||
157 | "variable" | ||
158 | } | ||
159 | } | ||
160 | |||
161 | #[derive(Debug, Clone)] | ||
162 | pub(crate) struct Function { | ||
163 | pub(crate) arguments: Vec<FunctionArgument>, | ||
164 | pub(crate) return_type: String, | ||
165 | } | ||
166 | |||
167 | impl Entity for Function { | ||
168 | fn kind_name(&self) -> &'static str { | ||
169 | "function" | ||
170 | } | ||
171 | } | ||
172 | |||
173 | #[derive(Debug, Clone)] | ||
174 | pub(crate) struct FunctionArgument { | ||
175 | pub(crate) name: String, | ||
176 | pub(crate) r#type: String, | ||
177 | } | ||
178 | |||
179 | #[derive(Debug, Clone)] | ||
180 | pub(crate) struct Class { | ||
181 | //pub(crate) member_types: Vec<Usr>, | ||
182 | // TODO: replace with Vec to keep order | ||
183 | pub(crate) member_functions: HashMap<Usr, Function>, | ||
184 | pub(crate) member_variables: HashMap<Usr, Variable>, | ||
185 | } | ||
186 | |||
187 | impl Entity for Class { | ||
188 | fn kind_name(&self) -> &'static str { | ||
189 | "class" | ||
190 | } | ||
191 | |||
192 | fn embeddable_children(&self) -> Vec<ChildGroup> { | ||
193 | vec![ | ||
194 | ChildGroup { | ||
195 | name: "Functions", | ||
196 | children: self | ||
197 | .member_functions | ||
198 | .iter() | ||
199 | .map(|(usr, func)| (usr, func as &DynEntity)) | ||
200 | .collect(), | ||
201 | }, | ||
202 | ChildGroup { | ||
203 | name: "Variables", | ||
204 | children: self | ||
205 | .member_variables | ||
206 | .iter() | ||
207 | .map(|(usr, var)| (usr, var as &DynEntity)) | ||
208 | .collect(), | ||
209 | }, | ||
210 | ] | ||
211 | } | ||
212 | |||
213 | fn separate_children(&self) -> Vec<ChildGroup> { | ||
214 | Vec::new() | ||
215 | } | ||
216 | } | ||
diff --git a/src/generator/config.rs b/src/generator/config.rs new file mode 100644 index 0000000..43fee60 --- /dev/null +++ b/src/generator/config.rs | |||
@@ -0,0 +1,53 @@ | |||
1 | use serde_derive::{Deserialize, Serialize}; | ||
2 | use structopt::StructOpt; | ||
3 | |||
4 | use std::collections::{HashMap, HashSet}; | ||
5 | |||
6 | #[derive(Debug, Clone, Serialize)] | ||
7 | pub(crate) struct Config { | ||
8 | /// Tells us in which language, which entity types should inline which children entity type in | ||
9 | /// their documentation. | ||
10 | /// | ||
11 | /// e.g. in C++, classes should inline their children methods documentation. | ||
12 | pub(crate) inlines: HashMap<String, HashMap<String, HashSet<String>>>, | ||
13 | } | ||
14 | |||
15 | impl Config { | ||
16 | pub(crate) fn from_merge(_cli: ProvidedConfig, conf: ProvidedConfig) -> Self { | ||
17 | Self { | ||
18 | inlines: conf.inlines.unwrap_or_else(default_inlines), | ||
19 | } | ||
20 | } | ||
21 | } | ||
22 | |||
23 | fn default_inlines() -> HashMap<String, HashMap<String, HashSet<String>>> { | ||
24 | let mut clang = HashMap::new(); | ||
25 | let mut clang_inline_children = HashSet::new(); | ||
26 | // TODO: this is not great: no differences between variable/field for children, but differences | ||
27 | // as a parent... | ||
28 | clang_inline_children.insert(String::from("variable")); | ||
29 | //classes.insert(String::from("field")); | ||
30 | clang_inline_children.insert(String::from("function")); | ||
31 | //classes.insert(String::from("method")); | ||
32 | clang.insert(String::from("struct"), clang_inline_children.clone()); | ||
33 | clang.insert(String::from("class"), clang_inline_children.clone()); | ||
34 | clang.insert(String::from("namespace"), clang_inline_children); | ||
35 | |||
36 | let mut inlines = HashMap::new(); | ||
37 | inlines.insert(String::from("clang"), clang); | ||
38 | |||
39 | inlines | ||
40 | } | ||
41 | |||
42 | impl Default for Config { | ||
43 | fn default() -> Self { | ||
44 | Config::from_merge(ProvidedConfig::default(), ProvidedConfig::default()) | ||
45 | } | ||
46 | } | ||
47 | |||
48 | #[derive(Debug, Clone, Default, StructOpt, Deserialize, Serialize)] | ||
49 | #[serde(rename_all = "kebab-case")] | ||
50 | pub(crate) struct ProvidedConfig { | ||
51 | #[structopt(skip)] | ||
52 | pub(crate) inlines: Option<HashMap<String, HashMap<String, HashSet<String>>>>, | ||
53 | } | ||
diff --git a/src/generator.rs b/src/generator/mod.rs index 0fa5a94..b4e1c94 100644 --- a/src/generator.rs +++ b/src/generator/mod.rs | |||
@@ -1,25 +1,24 @@ | |||
1 | use crate::config::Config; | 1 | pub(crate) mod config; |
2 | use crate::entities::{ | 2 | mod pandoc; |
3 | Description, DynEntity, EntitiesManager, EntitiesManagerComponents, Usr, | 3 | |
4 | }; | 4 | use self::config::Config; |
5 | use crate::pandoc::into_pandoc; | 5 | //use crate::entities::{ |
6 | // Description, DynEntity, EntitiesManager, EntitiesManagerComponents, Usr, | ||
7 | //}; | ||
8 | use self::pandoc::into_pandoc; | ||
9 | use crate::types::Entity; | ||
6 | 10 | ||
7 | use anyhow::{ensure, Context, Result}; | 11 | use anyhow::{ensure, Context, Result}; |
8 | use rayon::Scope; | 12 | use rayon::Scope; |
9 | use thiserror::Error; | 13 | use thiserror::Error; |
10 | 14 | ||
11 | use std::collections::HashMap; | 15 | use std::collections::BTreeMap; |
12 | use std::io::Write; | 16 | use std::io::Write; |
13 | use std::path::Path; | 17 | use std::path::Path; |
14 | 18 | ||
15 | const DEFAULT_CSS: &[u8] = include_bytes!("../res/style.css"); | 19 | const DEFAULT_CSS: &[u8] = include_bytes!("../../res/style.css"); |
16 | |||
17 | pub(crate) fn generate(base_dir: &Path, manager: EntitiesManager, config: &Config) -> Result<()> { | ||
18 | let EntitiesManagerComponents { | ||
19 | toplevel_entities, | ||
20 | descriptions, | ||
21 | } = manager.decompose(); | ||
22 | 20 | ||
21 | pub(crate) fn generate(base_dir: &Path, entities: BTreeMap<String, Entity>, config: &Config) -> Result<()> { | ||
23 | let md_output_dir = base_dir.join("markdown"); | 22 | let md_output_dir = base_dir.join("markdown"); |
24 | let html_output_dir = base_dir.join("html"); | 23 | let html_output_dir = base_dir.join("html"); |
25 | 24 | ||
@@ -38,15 +37,15 @@ pub(crate) fn generate(base_dir: &Path, manager: EntitiesManager, config: &Confi | |||
38 | debug!("Generated temporary file with CSS at: {:?}", css_path); | 37 | debug!("Generated temporary file with CSS at: {:?}", css_path); |
39 | 38 | ||
40 | rayon::scope(|scope| { | 39 | rayon::scope(|scope| { |
41 | for (usr, entity) in &toplevel_entities { | 40 | for (id, entity) in entities { |
42 | generate_recursively( | 41 | generate_recursively( |
43 | &usr, | 42 | id, |
44 | entity.as_ref(), | 43 | entity, |
45 | scope, | 44 | scope, |
46 | &descriptions, | ||
47 | &md_output_dir, | 45 | &md_output_dir, |
48 | &html_output_dir, | 46 | &html_output_dir, |
49 | css_path, | 47 | css_path, |
48 | config, | ||
50 | ); | 49 | ); |
51 | } | 50 | } |
52 | }); | 51 | }); |
@@ -55,56 +54,54 @@ pub(crate) fn generate(base_dir: &Path, manager: EntitiesManager, config: &Confi | |||
55 | } | 54 | } |
56 | 55 | ||
57 | fn generate_recursively<'a>( | 56 | fn generate_recursively<'a>( |
58 | usr: &'a Usr, | 57 | id: String, |
59 | entity: &'a DynEntity, | 58 | entity: Entity, |
60 | pool: &Scope<'a>, | 59 | pool: &Scope<'a>, |
61 | descriptions: &'a HashMap<Usr, Description>, | ||
62 | md_output_dir: &'a Path, | 60 | md_output_dir: &'a Path, |
63 | html_output_dir: &'a Path, | 61 | html_output_dir: &'a Path, |
64 | css_path: &'a Path, | 62 | css_path: &'a Path, |
63 | config: &'a Config, | ||
65 | ) { | 64 | ) { |
66 | pool.spawn(move |pool| { | 65 | pool.spawn(move |pool| { |
67 | trace!("Trying to generate {}", usr.0); | 66 | trace!("Trying to generate {}", id); |
68 | 67 | ||
69 | let leftovers = generate_single( | 68 | let leftovers = generate_single( |
70 | &usr, | 69 | &id, |
71 | entity, | 70 | entity, |
72 | descriptions.get(&usr).unwrap(), | ||
73 | &descriptions, | ||
74 | &md_output_dir, | 71 | &md_output_dir, |
75 | &html_output_dir, | 72 | &html_output_dir, |
76 | &css_path, | 73 | &css_path, |
74 | config, | ||
77 | ) | 75 | ) |
78 | .unwrap(); | 76 | .unwrap(); |
79 | 77 | ||
80 | for (usr, entity) in leftovers { | 78 | for (id, entity) in leftovers { |
81 | generate_recursively( | 79 | generate_recursively( |
82 | usr, | 80 | id, |
83 | entity, | 81 | entity, |
84 | pool, | 82 | pool, |
85 | descriptions, | ||
86 | md_output_dir, | 83 | md_output_dir, |
87 | html_output_dir, | 84 | html_output_dir, |
88 | css_path, | 85 | css_path, |
86 | config, | ||
89 | ); | 87 | ); |
90 | } | 88 | } |
91 | }); | 89 | }); |
92 | } | 90 | } |
93 | 91 | ||
94 | fn generate_single<'e>( | 92 | fn generate_single( |
95 | usr: &Usr, | 93 | id: &str, |
96 | entity: &'e DynEntity, | 94 | entity: Entity, |
97 | description: &Description, | ||
98 | descriptions: &HashMap<Usr, Description>, | ||
99 | md_output_dir: impl AsRef<Path>, | 95 | md_output_dir: impl AsRef<Path>, |
100 | html_output_dir: impl AsRef<Path>, | 96 | html_output_dir: impl AsRef<Path>, |
101 | css_path: impl AsRef<Path>, | 97 | css_path: impl AsRef<Path>, |
102 | ) -> Result<HashMap<&'e Usr, &'e DynEntity>> { | 98 | config: &Config |
99 | ) -> Result<BTreeMap<String, Entity>> { | ||
103 | use std::io::prelude::*; | 100 | use std::io::prelude::*; |
104 | use std::process::{Command, Stdio}; | 101 | use std::process::{Command, Stdio}; |
105 | 102 | ||
106 | let md_output_file = md_output_dir.as_ref().join(&usr.0); | 103 | let md_output_file = md_output_dir.as_ref().join(id); |
107 | let html_output_file = html_output_dir.as_ref().join(&usr.0); | 104 | let html_output_file = html_output_dir.as_ref().join(id); |
108 | 105 | ||
109 | let mut command = Command::new("pandoc"); | 106 | let mut command = Command::new("pandoc"); |
110 | 107 | ||
@@ -127,7 +124,7 @@ fn generate_single<'e>( | |||
127 | .spawn() | 124 | .spawn() |
128 | .context("Failed to execute Pandoc command")?; | 125 | .context("Failed to execute Pandoc command")?; |
129 | 126 | ||
130 | let (pandoc_ast, leftovers) = into_pandoc(entity, description, descriptions); | 127 | let (pandoc_ast, leftovers) = into_pandoc(entity, config); |
131 | 128 | ||
132 | if log_enabled!(log::Level::Trace) { | 129 | if log_enabled!(log::Level::Trace) { |
133 | let json = | 130 | let json = |
@@ -168,7 +165,7 @@ fn generate_single<'e>( | |||
168 | .stdout(Stdio::piped()) | 165 | .stdout(Stdio::piped()) |
169 | .stderr(Stdio::piped()) | 166 | .stderr(Stdio::piped()) |
170 | .args(&[ | 167 | .args(&[ |
171 | "--from=markdown", | 168 | "--from=markdown-raw_tex", |
172 | "--to=html", | 169 | "--to=html", |
173 | "--css", | 170 | "--css", |
174 | css_path | 171 | css_path |
diff --git a/src/pandoc.rs b/src/generator/pandoc.rs index 8477f28..1753d34 100644 --- a/src/pandoc.rs +++ b/src/generator/pandoc.rs | |||
@@ -1,28 +1,26 @@ | |||
1 | use crate::entities::*; | 1 | use super::config::Config; |
2 | use crate::types::Entity; | ||
2 | 3 | ||
3 | use pandoc_types::definition::{Attr, Block, Inline, Meta, MetaValue, Pandoc}; | 4 | use pandoc_types::definition::{Attr, Block, Inline, Meta, MetaValue, Pandoc}; |
4 | 5 | ||
5 | use std::collections::HashMap; | 6 | use std::collections::BTreeMap; |
6 | 7 | ||
7 | pub(crate) fn into_pandoc<'e>( | 8 | pub(crate) fn into_pandoc( |
8 | entity: &'e dyn Entity, | 9 | entity: Entity, |
9 | description: &Description, | 10 | config: &Config, |
10 | descriptions: &HashMap<Usr, Description>, | 11 | ) -> (Pandoc, BTreeMap<String, Entity>) { |
11 | ) -> (Pandoc, HashMap<&'e Usr, &'e DynEntity>) { | ||
12 | let mut meta = Meta::null(); | 12 | let mut meta = Meta::null(); |
13 | 13 | ||
14 | let title = vec![Inline::Code(Attr::null(), description.name.clone())]; | 14 | let title = vec![Inline::Code(Attr::null(), entity.name.clone())]; |
15 | 15 | ||
16 | meta.0.insert( | 16 | meta.0 |
17 | "title".to_string(), | 17 | .insert("title".to_string(), MetaValue::MetaString(entity.name)); |
18 | MetaValue::MetaString(description.name.clone()), | ||
19 | ); | ||
20 | 18 | ||
21 | let mut content = Vec::new(); | 19 | let mut content = Vec::new(); |
22 | 20 | ||
23 | content.push(Block::Header(1, Attr::null(), title)); | 21 | content.push(Block::Header(1, Attr::null(), title)); |
24 | 22 | ||
25 | if !description.detailed.is_empty() { | 23 | if !entity.documentation.is_empty() { |
26 | content.push(Block::Header( | 24 | content.push(Block::Header( |
27 | 2, | 25 | 2, |
28 | Attr::null(), | 26 | Attr::null(), |
@@ -31,22 +29,35 @@ pub(crate) fn into_pandoc<'e>( | |||
31 | 29 | ||
32 | content.push(Block::Div( | 30 | content.push(Block::Div( |
33 | Attr(String::new(), vec![String::from("doc")], vec![]), | 31 | Attr(String::new(), vec![String::from("doc")], vec![]), |
34 | vec![raw_markdown(description.detailed.clone())], | 32 | vec![raw_markdown(entity.documentation.clone())], |
35 | )); | 33 | )); |
36 | } | 34 | } |
37 | 35 | ||
38 | let separate_children = entity.separate_children(); | 36 | let mut inline_children = BTreeMap::new(); |
39 | let embeddable_children = entity.embeddable_children(); | 37 | let mut separate_children = BTreeMap::new(); |
38 | |||
39 | let entity_kind = entity.kind; | ||
40 | for (children_kind, children) in entity.children { | ||
41 | if config | ||
42 | .inlines | ||
43 | .get(&entity.language) | ||
44 | .and_then(|lang_inlines| lang_inlines.get(&entity_kind)) | ||
45 | .map(|children_inlines| children_inlines.contains(&children_kind)) | ||
46 | == Some(true) | ||
47 | { | ||
48 | inline_children.insert(children_kind, children); | ||
49 | } else { | ||
50 | // By default, do not inline | ||
51 | separate_children.insert(children_kind, children); | ||
52 | } | ||
53 | } | ||
40 | 54 | ||
41 | for section in &separate_children { | 55 | for (section_name, children) in &separate_children { |
42 | if let Some(members_list) = member_list( | 56 | if let Some(members_list) = member_list(children) { |
43 | section.children.iter().map(|&(usr, _child)| usr), | ||
44 | descriptions, | ||
45 | ) { | ||
46 | content.push(Block::Header( | 57 | content.push(Block::Header( |
47 | 2, | 58 | 2, |
48 | Attr::null(), | 59 | Attr::null(), |
49 | vec![Inline::Str(String::from(section.name))], | 60 | vec![Inline::Str(String::from(section_name))], |
50 | )); | 61 | )); |
51 | 62 | ||
52 | content.push(members_list); | 63 | content.push(members_list); |
@@ -55,15 +66,12 @@ pub(crate) fn into_pandoc<'e>( | |||
55 | 66 | ||
56 | let mut embedded_documentation = Vec::new(); | 67 | let mut embedded_documentation = Vec::new(); |
57 | 68 | ||
58 | for section in &embeddable_children { | 69 | for (section_name, children) in inline_children { |
59 | if let Some(members_list) = member_list( | 70 | if let Some(members_list) = member_list(&children) { |
60 | section.children.iter().map(|&(usr, _child)| usr), | ||
61 | descriptions, | ||
62 | ) { | ||
63 | content.push(Block::Header( | 71 | content.push(Block::Header( |
64 | 2, | 72 | 2, |
65 | Attr::null(), | 73 | Attr::null(), |
66 | vec![Inline::Str(String::from(section.name))], | 74 | vec![Inline::Str(section_name.clone())], |
67 | )); | 75 | )); |
68 | 76 | ||
69 | content.push(members_list); | 77 | content.push(members_list); |
@@ -71,21 +79,19 @@ pub(crate) fn into_pandoc<'e>( | |||
71 | embedded_documentation.push(Block::Header( | 79 | embedded_documentation.push(Block::Header( |
72 | 2, | 80 | 2, |
73 | Attr::null(), | 81 | Attr::null(), |
74 | vec![Inline::Str(String::from(section.name) + " Documentation")], | 82 | vec![Inline::Str(section_name + " Documentation")], |
75 | )); | 83 | )); |
76 | 84 | ||
77 | for (usr, _child) in §ion.children { | 85 | for (_id, child) in children { |
78 | let child_doc = descriptions.get(usr).unwrap(); | ||
79 | |||
80 | embedded_documentation.push(Block::Header( | 86 | embedded_documentation.push(Block::Header( |
81 | 3, | 87 | 3, |
82 | Attr::null(), | 88 | Attr::null(), |
83 | vec![Inline::Code(Attr::null(), String::from(&child_doc.name))], | 89 | vec![Inline::Code(Attr::null(), child.name)], |
84 | )); | 90 | )); |
85 | 91 | ||
86 | embedded_documentation.push(Block::Div( | 92 | embedded_documentation.push(Block::Div( |
87 | Attr(String::new(), vec![String::from("doc")], vec![]), | 93 | Attr(String::new(), vec![String::from("doc")], vec![]), |
88 | vec![raw_markdown(child_doc.detailed.clone())], | 94 | vec![raw_markdown(child.documentation)], |
89 | )); | 95 | )); |
90 | } | 96 | } |
91 | } | 97 | } |
@@ -94,8 +100,8 @@ pub(crate) fn into_pandoc<'e>( | |||
94 | content.append(&mut embedded_documentation); | 100 | content.append(&mut embedded_documentation); |
95 | 101 | ||
96 | let leftovers = separate_children | 102 | let leftovers = separate_children |
97 | .iter() | 103 | .into_iter() |
98 | .map(|section| section.children.clone()) | 104 | .map(|(_section_name, children)| children) |
99 | .flatten() | 105 | .flatten() |
100 | .collect(); | 106 | .collect(); |
101 | 107 | ||
@@ -106,7 +112,7 @@ fn str_block(content: String) -> Block { | |||
106 | Block::Plain(vec![Inline::Str(content)]) | 112 | Block::Plain(vec![Inline::Str(content)]) |
107 | } | 113 | } |
108 | 114 | ||
109 | fn entity_link(usr: &Usr, name: String) -> Inline { | 115 | fn entity_link(id: &str, name: String) -> Inline { |
110 | use pandoc_types::definition::Target; | 116 | use pandoc_types::definition::Target; |
111 | use percent_encoding::{utf8_percent_encode, AsciiSet, CONTROLS}; | 117 | use percent_encoding::{utf8_percent_encode, AsciiSet, CONTROLS}; |
112 | 118 | ||
@@ -129,7 +135,7 @@ fn entity_link(usr: &Usr, name: String) -> Inline { | |||
129 | vec![Inline::Code(Attr::null(), name)], | 135 | vec![Inline::Code(Attr::null(), name)], |
130 | Target( | 136 | Target( |
131 | once("./") | 137 | once("./") |
132 | .chain(utf8_percent_encode(&usr.0, FRAGMENT)) | 138 | .chain(utf8_percent_encode(id, FRAGMENT)) |
133 | .collect(), | 139 | .collect(), |
134 | String::new(), | 140 | String::new(), |
135 | ), | 141 | ), |
@@ -141,20 +147,14 @@ fn raw_markdown(text: String) -> Block { | |||
141 | Block::RawBlock(Format(String::from("markdown")), text) | 147 | Block::RawBlock(Format(String::from("markdown")), text) |
142 | } | 148 | } |
143 | 149 | ||
144 | fn member_list<'a>( | 150 | fn member_list<'a>(members: impl IntoIterator<Item = (&'a String, &'a Entity)>) -> Option<Block> { |
145 | members: impl IntoIterator<Item = &'a Usr>, | ||
146 | descriptions: &HashMap<Usr, Description>, | ||
147 | ) -> Option<Block> { | ||
148 | let definitions: Vec<(Vec<Inline>, Vec<Vec<Block>>)> = members | 151 | let definitions: Vec<(Vec<Inline>, Vec<Vec<Block>>)> = members |
149 | .into_iter() | 152 | .into_iter() |
150 | .filter_map(|usr| { | 153 | .map(|(id, entity)| { |
151 | let name = &descriptions.get(usr)?.name; | 154 | ( |
152 | Some(( | 155 | vec![entity_link(id, entity.name.clone())], |
153 | vec![entity_link(usr, name.clone())], | 156 | vec![vec![str_block(entity.brief_description.clone())]], |
154 | vec![vec![str_block( | 157 | ) |
155 | descriptions.get(usr).unwrap().brief.clone(), | ||
156 | )]], | ||
157 | )) | ||
158 | }) | 158 | }) |
159 | .collect(); | 159 | .collect(); |
160 | 160 | ||
diff --git a/src/main.rs b/src/main.rs index e6e36d9..b42d73c 100644 --- a/src/main.rs +++ b/src/main.rs | |||
@@ -1,19 +1,16 @@ | |||
1 | #![warn(clippy::all)] | 1 | #![warn(clippy::all)] |
2 | 2 | ||
3 | //mod doxygen; | ||
4 | mod cli; | 3 | mod cli; |
5 | mod config; | 4 | mod config; |
6 | mod entities; | ||
7 | mod generator; | 5 | mod generator; |
8 | mod pandoc; | ||
9 | mod parsing; | 6 | mod parsing; |
7 | mod types; | ||
10 | 8 | ||
11 | #[macro_use] | 9 | #[macro_use] |
12 | extern crate log; | 10 | extern crate log; |
13 | 11 | ||
14 | use cli::{Command, ConfigCommand}; | 12 | use cli::{Command, ConfigCommand}; |
15 | use generator::generate; | 13 | use generator::generate; |
16 | use parsing::parse_file; | ||
17 | 14 | ||
18 | use anyhow::{Context, Result}; | 15 | use anyhow::{Context, Result}; |
19 | use codemap::CodeMap; | 16 | use codemap::CodeMap; |
@@ -86,14 +83,15 @@ fn start(codemap: &mut CodeMap) -> Result<()> { | |||
86 | .with_context(|| format!("Cannot change current directory to: {:?}", cli.directory))?; | 83 | .with_context(|| format!("Cannot change current directory to: {:?}", cli.directory))?; |
87 | 84 | ||
88 | match &cli.command { | 85 | match &cli.command { |
89 | Command::Generate { file } => { | 86 | Command::Generate { file: _ } => { |
90 | let file = file.clone(); | 87 | //let file = file.clone(); |
91 | let config = load_effective_config(cli, codemap)?; | 88 | let config = load_effective_config(cli, codemap)?; |
92 | 89 | ||
93 | let manager = parse_file(file, &config.extra_clang_args); | 90 | let entities = parsing::clang::parse_compile_commands(&config.clang, codemap)?; |
91 | //let manager = parse_file(file, &config.extra_clang_args); | ||
94 | 92 | ||
95 | let base_output_dir = std::path::Path::new("doc"); | 93 | let base_output_dir = std::path::Path::new("doc"); |
96 | generate(&base_output_dir, manager, &config)?; | 94 | generate(&base_output_dir, entities, &config.generator)?; |
97 | } | 95 | } |
98 | Command::Config { | 96 | Command::Config { |
99 | command: ConfigCommand::Default, | 97 | command: ConfigCommand::Default, |
@@ -105,7 +103,7 @@ fn start(codemap: &mut CodeMap) -> Result<()> { | |||
105 | } => { | 103 | } => { |
106 | print!( | 104 | print!( |
107 | "{}", | 105 | "{}", |
108 | toml::to_string_pretty(&config::load_config(".", codemap)?)? | 106 | toml::to_string_pretty(&load_effective_config(cli, codemap)?)? |
109 | ); | 107 | ); |
110 | } | 108 | } |
111 | } | 109 | } |
@@ -114,5 +112,6 @@ fn start(codemap: &mut CodeMap) -> Result<()> { | |||
114 | } | 112 | } |
115 | 113 | ||
116 | fn load_effective_config(cli: cli::Cli, codemap: &mut CodeMap) -> Result<config::Config> { | 114 | fn load_effective_config(cli: cli::Cli, codemap: &mut CodeMap) -> Result<config::Config> { |
117 | Ok(config::load_config(".", codemap)?.merge_cli(cli)) | 115 | let conf_file_conf = config::load_config(".", codemap)?; |
116 | Ok(config::Config::from_merge(cli, conf_file_conf)?) | ||
118 | } | 117 | } |
diff --git a/src/parsing.rs b/src/parsing.rs deleted file mode 100644 index d7aaa49..0000000 --- a/src/parsing.rs +++ /dev/null | |||
@@ -1,482 +0,0 @@ | |||
1 | use crate::entities::*; | ||
2 | |||
3 | use clang::{Clang, Index}; | ||
4 | use codemap::CodeMap; | ||
5 | |||
6 | use std::collections::HashMap; | ||
7 | use std::path::{Path, PathBuf}; | ||
8 | |||
9 | pub(crate) fn parse_file<T, S>(path: T, extra_args: &[S]) -> EntitiesManager | ||
10 | where | ||
11 | T: Into<PathBuf>, | ||
12 | T: AsRef<Path>, | ||
13 | T: ToString, | ||
14 | S: AsRef<str>, | ||
15 | S: std::fmt::Debug, | ||
16 | { | ||
17 | let mut codemap = CodeMap::new(); | ||
18 | let file_map = codemap.add_file(path.to_string(), std::fs::read_to_string(&path).unwrap()); | ||
19 | let file_span = file_map.span; | ||
20 | |||
21 | let clang = Clang::new().unwrap(); | ||
22 | let index = Index::new(&clang, true, false); | ||
23 | let mut parser = index.parser(path); | ||
24 | parser.skip_function_bodies(true); | ||
25 | |||
26 | parser.arguments(&extra_args); | ||
27 | |||
28 | if log_enabled!(log::Level::Debug) { | ||
29 | for extra_arg in extra_args { | ||
30 | debug!("Extra LibClang argument: {:?}", extra_arg); | ||
31 | } | ||
32 | } | ||
33 | |||
34 | let trans_unit = parser.parse().unwrap(); | ||
35 | |||
36 | let mut manager = EntitiesManager::new(); | ||
37 | |||
38 | trans_unit.get_entity().visit_children(|entity, _parent| { | ||
39 | if entity.is_in_system_header() { | ||
40 | trace!( | ||
41 | "Entity is in system header, skipping: USR = {:?}", | ||
42 | entity.get_display_name() | ||
43 | ); | ||
44 | return clang::EntityVisitResult::Continue; | ||
45 | } | ||
46 | |||
47 | // TODO: wrap this callback in another function so that we can use the | ||
48 | // "?" operator instead of all these `match`es | ||
49 | let usr = match entity.get_usr() { | ||
50 | Some(usr) => usr, | ||
51 | None => return clang::EntityVisitResult::Continue, | ||
52 | }; | ||
53 | trace!("Entity with USR = {:?}", usr); | ||
54 | |||
55 | let name = match entity.get_name() { | ||
56 | Some(name) => name, | ||
57 | None => return clang::EntityVisitResult::Continue, | ||
58 | }; | ||
59 | |||
60 | debug!("Parsing toplevel entity with name = {:?}", name); | ||
61 | |||
62 | let entity = entity.into_poseidoc_entity(&mut manager); | ||
63 | |||
64 | manager.insert_toplevel(usr.into(), entity); | ||
65 | |||
66 | clang::EntityVisitResult::Continue | ||
67 | }); | ||
68 | |||
69 | use codemap_diagnostic::{ColorConfig, Emitter}; | ||
70 | |||
71 | let mut emitter = Emitter::stderr(ColorConfig::Auto, Some(&codemap)); | ||
72 | |||
73 | for diagnostic in trans_unit.get_diagnostics().iter() { | ||
74 | let main_diag = match clang_diag_to_codemap_diag(&diagnostic, file_span) { | ||
75 | Some(diag) => diag, | ||
76 | None => continue, | ||
77 | }; | ||
78 | |||
79 | let sub_diags = diagnostic | ||
80 | .get_children() | ||
81 | .into_iter() | ||
82 | .filter_map(|diagnostic| clang_diag_to_codemap_diag(&diagnostic, file_span)); | ||
83 | |||
84 | let fix_it_diags = diagnostic | ||
85 | .get_fix_its() | ||
86 | .into_iter() | ||
87 | .map(|fix_it| clang_fix_it_to_codemap_diag(&fix_it, file_span)); | ||
88 | |||
89 | emitter.emit( | ||
90 | &std::iter::once(main_diag) | ||
91 | .chain(sub_diags) | ||
92 | .chain(fix_it_diags) | ||
93 | .collect::<Vec<_>>(), | ||
94 | ); | ||
95 | } | ||
96 | |||
97 | manager | ||
98 | } | ||
99 | |||
100 | fn clang_diag_to_codemap_diag( | ||
101 | diagnostic: &clang::diagnostic::Diagnostic, | ||
102 | file_span: codemap::Span, | ||
103 | ) -> Option<codemap_diagnostic::Diagnostic> { | ||
104 | use codemap_diagnostic::{Diagnostic, Level, SpanLabel, SpanStyle}; | ||
105 | |||
106 | let ranges = diagnostic.get_ranges(); | ||
107 | |||
108 | let (begin, end) = if ranges.is_empty() { | ||
109 | let location = diagnostic.get_location(); | ||
110 | |||
111 | ( | ||
112 | location.get_file_location().offset as u64, | ||
113 | location.get_file_location().offset as u64, | ||
114 | ) | ||
115 | } else { | ||
116 | let range = diagnostic.get_ranges()[0]; | ||
117 | if !range.is_in_main_file() { | ||
118 | warn!("Skipping diagnostic: {:?}", diagnostic); | ||
119 | return None; | ||
120 | } | ||
121 | ( | ||
122 | range.get_start().get_file_location().offset as u64, | ||
123 | range.get_end().get_file_location().offset as u64, | ||
124 | ) | ||
125 | }; | ||
126 | |||
127 | let label = SpanLabel { | ||
128 | span: file_span.subspan(begin, end), | ||
129 | label: None, | ||
130 | style: SpanStyle::Primary, | ||
131 | }; | ||
132 | |||
133 | Some(Diagnostic { | ||
134 | level: match diagnostic.get_severity() { | ||
135 | clang::diagnostic::Severity::Ignored => return None, | ||
136 | clang::diagnostic::Severity::Note => Level::Note, | ||
137 | clang::diagnostic::Severity::Warning => Level::Warning, | ||
138 | clang::diagnostic::Severity::Error => Level::Error, | ||
139 | clang::diagnostic::Severity::Fatal => Level::Error, | ||
140 | }, | ||
141 | message: diagnostic.get_text(), | ||
142 | //code: Some("-Werror=documentation".to_string()), | ||
143 | code: None, | ||
144 | spans: vec![label], | ||
145 | }) | ||
146 | } | ||
147 | |||
148 | fn clang_fix_it_to_codemap_diag( | ||
149 | fix_it: &clang::diagnostic::FixIt, | ||
150 | file_span: codemap::Span, | ||
151 | ) -> codemap_diagnostic::Diagnostic { | ||
152 | use clang::diagnostic::FixIt; | ||
153 | use codemap_diagnostic::{Diagnostic, Level, SpanLabel, SpanStyle}; | ||
154 | |||
155 | let label = match fix_it { | ||
156 | FixIt::Deletion(range) => { | ||
157 | let begin = range.get_start().get_file_location().offset as u64; | ||
158 | let end = range.get_end().get_file_location().offset as u64; | ||
159 | |||
160 | SpanLabel { | ||
161 | span: file_span.subspan(begin, end), | ||
162 | label: Some(String::from("Try deleting this")), | ||
163 | style: SpanStyle::Primary, | ||
164 | } | ||
165 | } | ||
166 | FixIt::Insertion(range, text) => { | ||
167 | let location = range.get_file_location().offset as u64; | ||
168 | |||
169 | SpanLabel { | ||
170 | span: file_span.subspan(location, location), | ||
171 | label: Some(format!("Try insterting {:?}", text)), | ||
172 | style: SpanStyle::Primary, | ||
173 | } | ||
174 | } | ||
175 | FixIt::Replacement(range, text) => { | ||
176 | let begin = range.get_start().get_file_location().offset as u64; | ||
177 | let end = range.get_end().get_file_location().offset as u64; | ||
178 | |||
179 | SpanLabel { | ||
180 | span: file_span.subspan(begin, end), | ||
181 | label: Some(format!("Try replacing this with {:?}", text)), | ||
182 | style: SpanStyle::Primary, | ||
183 | } | ||
184 | } | ||
185 | }; | ||
186 | |||
187 | Diagnostic { | ||
188 | level: Level::Help, | ||
189 | message: String::from("Possible fix"), | ||
190 | //code: Some("-Werror=documentation".to_string()), | ||
191 | code: None, | ||
192 | spans: vec![label], | ||
193 | } | ||
194 | } | ||
195 | |||
196 | pub(crate) struct Comment(String); | ||
197 | |||
198 | impl Comment { | ||
199 | // TODO: weirdness with emojis and line returns? | ||
200 | pub fn from_raw(raw: String) -> Self { | ||
201 | #[derive(Debug)] | ||
202 | enum CommentStyle { | ||
203 | // Comments of type `/**` or `/*!` | ||
204 | Starred, | ||
205 | // Comments of type `///` | ||
206 | SingleLine, | ||
207 | } | ||
208 | |||
209 | let mut chars = raw.chars(); | ||
210 | let style = match &chars.as_str()[..3] { | ||
211 | "/*!" | "/**" => CommentStyle::Starred, | ||
212 | "///" => CommentStyle::SingleLine, | ||
213 | _ => panic!("Comment is empty or doesn't start with either `///`, `/**`, or `/*!`"), | ||
214 | }; | ||
215 | |||
216 | chars.nth(2); | ||
217 | |||
218 | let mut result = String::new(); | ||
219 | |||
220 | 'parse_loop: loop { | ||
221 | let maybe_space = chars.next(); | ||
222 | let mut empty_line = false; | ||
223 | match maybe_space { | ||
224 | // TODO: Warn on empty comments | ||
225 | None => break, | ||
226 | Some(' ') => {} | ||
227 | Some('\n') => { | ||
228 | empty_line = true; | ||
229 | result.push('\n'); | ||
230 | } | ||
231 | Some(ch) => result.push(ch), | ||
232 | } | ||
233 | |||
234 | if !empty_line { | ||
235 | let rest = chars.as_str(); | ||
236 | match rest.find('\n') { | ||
237 | None => { | ||
238 | result.push_str(rest); | ||
239 | break; | ||
240 | } | ||
241 | Some(position) => { | ||
242 | result.push_str(&rest[..=position]); | ||
243 | chars.nth(position); | ||
244 | } | ||
245 | } | ||
246 | } | ||
247 | |||
248 | // Beginning of the line | ||
249 | let first_non_ws_ch = 'ws_loop: loop { | ||
250 | let maybe_whitespace = chars.next(); | ||
251 | match maybe_whitespace { | ||
252 | None => break 'parse_loop, | ||
253 | Some(ch) if ch.is_whitespace() => continue, | ||
254 | Some(ch) => break 'ws_loop ch, | ||
255 | } | ||
256 | }; | ||
257 | |||
258 | match style { | ||
259 | CommentStyle::Starred if first_non_ws_ch == '*' => { | ||
260 | if &chars.as_str()[..1] == "/" { | ||
261 | break; | ||
262 | } | ||
263 | } | ||
264 | CommentStyle::Starred => result.push(first_non_ws_ch), | ||
265 | CommentStyle::SingleLine => { | ||
266 | assert!(first_non_ws_ch == '/'); | ||
267 | let rest = chars.as_str(); | ||
268 | if &rest[..2] == "//" { | ||
269 | chars.nth(1); | ||
270 | } else if &rest[..1] == "/" { | ||
271 | chars.nth(0); | ||
272 | } else { | ||
273 | panic!("Could not parse comment"); | ||
274 | } | ||
275 | } | ||
276 | } | ||
277 | } | ||
278 | |||
279 | Self(result) | ||
280 | } | ||
281 | } | ||
282 | |||
283 | impl From<clang::Usr> for Usr { | ||
284 | fn from(usr: clang::Usr) -> Self { | ||
285 | Self(usr.0) | ||
286 | } | ||
287 | } | ||
288 | |||
289 | trait FromClangEntity { | ||
290 | /// Is responsible for inserting its documentation into the entities manager | ||
291 | fn from_clang_entity(entity: clang::Entity, manager: &mut EntitiesManager) -> Self; | ||
292 | } | ||
293 | |||
294 | trait IntoPoseidocEntity<T> { | ||
295 | /// Useful for the `clang_entity.into_poseidoc_entity(&mut manager)` syntax. Implement | ||
296 | /// `FromClangEntity` instead. | ||
297 | fn into_poseidoc_entity(self, manager: &mut EntitiesManager) -> T; | ||
298 | } | ||
299 | |||
300 | impl<T> IntoPoseidocEntity<T> for clang::Entity<'_> | ||
301 | where | ||
302 | T: FromClangEntity, | ||
303 | { | ||
304 | fn into_poseidoc_entity(self, manager: &mut EntitiesManager) -> T { | ||
305 | T::from_clang_entity(self, manager) | ||
306 | } | ||
307 | } | ||
308 | |||
309 | fn get_description(entity: &clang::Entity) -> Description { | ||
310 | let name = entity.get_display_name().unwrap(); | ||
311 | |||
312 | if let (Some(brief), Some(comment)) = (entity.get_comment_brief(), entity.get_comment()) { | ||
313 | Description { | ||
314 | name, | ||
315 | brief, | ||
316 | detailed: Comment::from_raw(comment).0, | ||
317 | } | ||
318 | } else { | ||
319 | Description::with_name(name) | ||
320 | } | ||
321 | } | ||
322 | |||
323 | impl FromClangEntity for Box<DynEntity> { | ||
324 | fn from_clang_entity(entity: clang::Entity, manager: &mut EntitiesManager) -> Self { | ||
325 | use clang::EntityKind; | ||
326 | |||
327 | match entity.get_kind() { | ||
328 | EntityKind::Namespace => Box::new(NameSpace::from_clang_entity(entity, manager)), | ||
329 | EntityKind::FieldDecl | EntityKind::VarDecl => { | ||
330 | Box::new(Variable::from_clang_entity(entity, manager)) | ||
331 | } | ||
332 | EntityKind::FunctionDecl | EntityKind::Method | EntityKind::FunctionTemplate => { | ||
333 | Box::new(Function::from_clang_entity(entity, manager)) | ||
334 | } | ||
335 | EntityKind::ClassDecl | EntityKind::StructDecl | EntityKind::ClassTemplate => { | ||
336 | Box::new(Class::from_clang_entity(entity, manager)) | ||
337 | } | ||
338 | _ => panic!("Unsupported entity: {:?}", entity), | ||
339 | } | ||
340 | } | ||
341 | } | ||
342 | |||
343 | impl FromClangEntity for NameSpace { | ||
344 | fn from_clang_entity(entity: clang::Entity, manager: &mut EntitiesManager) -> Self { | ||
345 | match entity.get_kind() { | ||
346 | clang::EntityKind::Namespace => {} | ||
347 | _ => panic!("Trying to parse a non-variable into a variable"), | ||
348 | } | ||
349 | debug!("Parsing NameSpace"); | ||
350 | |||
351 | let members = entity | ||
352 | .get_children() | ||
353 | .into_iter() | ||
354 | .map(|child| { | ||
355 | let usr = child.get_usr().unwrap().into(); | ||
356 | trace!("Namespace has member: {:?}", usr); | ||
357 | (usr, child.into_poseidoc_entity(manager)) | ||
358 | }) | ||
359 | .collect(); | ||
360 | |||
361 | manager.insert(entity.get_usr().unwrap().into(), get_description(&entity)); | ||
362 | |||
363 | NameSpace { members } | ||
364 | } | ||
365 | } | ||
366 | |||
367 | impl FromClangEntity for NameSpaceChild { | ||
368 | fn from_clang_entity(entity: clang::Entity, manager: &mut EntitiesManager) -> Self { | ||
369 | use clang::EntityKind; | ||
370 | |||
371 | match entity.get_kind() { | ||
372 | EntityKind::Namespace => { | ||
373 | NameSpaceChild::NameSpace(NameSpace::from_clang_entity(entity, manager)) | ||
374 | } | ||
375 | EntityKind::FieldDecl | EntityKind::VarDecl => { | ||
376 | NameSpaceChild::Variable(Variable::from_clang_entity(entity, manager)) | ||
377 | } | ||
378 | EntityKind::FunctionDecl | EntityKind::Method | EntityKind::FunctionTemplate => { | ||
379 | NameSpaceChild::Function(Function::from_clang_entity(entity, manager)) | ||
380 | } | ||
381 | EntityKind::ClassDecl | EntityKind::StructDecl | EntityKind::ClassTemplate => { | ||
382 | NameSpaceChild::Class(Class::from_clang_entity(entity, manager)) | ||
383 | } | ||
384 | _ => panic!("Unsupported entity: {:?}", entity), | ||
385 | } | ||
386 | } | ||
387 | } | ||
388 | |||
389 | impl FromClangEntity for Variable { | ||
390 | fn from_clang_entity(entity: clang::Entity, manager: &mut EntitiesManager) -> Self { | ||
391 | match entity.get_kind() { | ||
392 | clang::EntityKind::VarDecl | clang::EntityKind::FieldDecl => {} | ||
393 | _ => panic!("Trying to parse a non-variable into a variable"), | ||
394 | } | ||
395 | debug!("Parsing Variable"); | ||
396 | |||
397 | let r#type = entity.get_type().unwrap().get_display_name(); | ||
398 | trace!("Variable has type: {:?}", r#type); | ||
399 | |||
400 | manager.insert(entity.get_usr().unwrap().into(), get_description(&entity)); | ||
401 | |||
402 | Variable { r#type } | ||
403 | } | ||
404 | } | ||
405 | |||
406 | impl FromClangEntity for Function { | ||
407 | fn from_clang_entity(entity: clang::Entity, manager: &mut EntitiesManager) -> Self { | ||
408 | match entity.get_kind() { | ||
409 | clang::EntityKind::Method | ||
410 | | clang::EntityKind::Constructor | ||
411 | | clang::EntityKind::FunctionDecl | ||
412 | | clang::EntityKind::FunctionTemplate => {} | ||
413 | _ => panic!("Trying to parse a non-function into a function"), | ||
414 | } | ||
415 | debug!("Parsing Function"); | ||
416 | |||
417 | let return_type = entity.get_result_type().unwrap().get_display_name(); | ||
418 | trace!("Function has return type: {:?}", return_type); | ||
419 | let arguments = entity | ||
420 | .get_arguments() | ||
421 | .unwrap() | ||
422 | .into_iter() | ||
423 | .map(|arg| { | ||
424 | let name = arg.get_display_name().unwrap(); | ||
425 | let r#type = arg.get_type().unwrap().get_display_name(); | ||
426 | trace!("Function has argument {:?} of type {:?}", name, r#type); | ||
427 | FunctionArgument { name, r#type } | ||
428 | }) | ||
429 | .collect(); | ||
430 | |||
431 | manager.insert(entity.get_usr().unwrap().into(), get_description(&entity)); | ||
432 | |||
433 | Function { | ||
434 | arguments, | ||
435 | return_type, | ||
436 | } | ||
437 | } | ||
438 | } | ||
439 | |||
440 | impl FromClangEntity for Class { | ||
441 | fn from_clang_entity(entity: clang::Entity, manager: &mut EntitiesManager) -> Self { | ||
442 | match entity.get_kind() { | ||
443 | clang::EntityKind::ClassDecl | ||
444 | | clang::EntityKind::StructDecl | ||
445 | | clang::EntityKind::ClassTemplate => {} | ||
446 | _ => panic!("Trying to parse a non-class into a class"), | ||
447 | } | ||
448 | debug!("Parsing Class"); | ||
449 | |||
450 | let mut member_types = Vec::new(); | ||
451 | let mut member_functions = HashMap::new(); | ||
452 | let mut member_variables = HashMap::new(); | ||
453 | |||
454 | for child in entity.get_children() { | ||
455 | trace!("Class has child: {:?}", child); | ||
456 | |||
457 | let child_usr = child.get_usr().unwrap().into(); | ||
458 | |||
459 | use clang::EntityKind; | ||
460 | match child.get_kind() { | ||
461 | EntityKind::ClassDecl | EntityKind::StructDecl | EntityKind::TypeAliasDecl => { | ||
462 | member_types.push(child_usr); | ||
463 | } | ||
464 | EntityKind::Method | EntityKind::Constructor | EntityKind::FunctionDecl => { | ||
465 | member_functions.insert(child_usr, child.into_poseidoc_entity(manager)); | ||
466 | } | ||
467 | EntityKind::FieldDecl => { | ||
468 | member_variables.insert(child_usr, child.into_poseidoc_entity(manager)); | ||
469 | } | ||
470 | _ => trace!("Skipping child"), | ||
471 | } | ||
472 | } | ||
473 | |||
474 | manager.insert(entity.get_usr().unwrap().into(), get_description(&entity)); | ||
475 | |||
476 | Class { | ||
477 | //member_types, | ||
478 | member_functions, | ||
479 | member_variables, | ||
480 | } | ||
481 | } | ||
482 | } | ||
diff --git a/src/parsing/clang/config.rs b/src/parsing/clang/config.rs new file mode 100644 index 0000000..cb05876 --- /dev/null +++ b/src/parsing/clang/config.rs | |||
@@ -0,0 +1,53 @@ | |||
1 | use anyhow::Result; | ||
2 | use serde_derive::{Deserialize, Serialize}; | ||
3 | use structopt::StructOpt; | ||
4 | |||
5 | use std::path::PathBuf; | ||
6 | |||
7 | #[derive(Debug, Clone, StructOpt, Deserialize, Serialize)] | ||
8 | #[serde(rename_all = "kebab-case")] | ||
9 | pub(crate) struct Config { | ||
10 | pub(crate) compile_commands_location: PathBuf, | ||
11 | pub(crate) extra_args: Vec<String>, | ||
12 | } | ||
13 | |||
14 | #[derive(Debug, Clone, Default, StructOpt, Deserialize, Serialize)] | ||
15 | #[serde(rename_all = "kebab-case")] | ||
16 | pub(crate) struct ProvidedConfig { | ||
17 | #[structopt(long = "clang-compile-commands-location")] | ||
18 | pub(crate) compile_commands_location: Option<PathBuf>, | ||
19 | #[structopt(long = "clang-extra-args", number_of_values = 1)] | ||
20 | #[serde(default)] | ||
21 | pub(crate) extra_args: Vec<String>, | ||
22 | } | ||
23 | |||
24 | impl Default for Config { | ||
25 | fn default() -> Self { | ||
26 | Config::from_merge( | ||
27 | ProvidedConfig::default(), | ||
28 | ProvidedConfig::default(), | ||
29 | ) | ||
30 | // Currently errors out only on CLI parse fail with clang's extra args | ||
31 | .unwrap() | ||
32 | } | ||
33 | } | ||
34 | |||
35 | impl Config { | ||
36 | pub(crate) fn from_merge( | ||
37 | cli: ProvidedConfig, | ||
38 | mut config: ProvidedConfig, | ||
39 | ) -> Result<Self> { | ||
40 | let mut extra_args = Vec::new(); | ||
41 | for args in cli.extra_args { | ||
42 | extra_args.append(&mut ::shell_words::split(&args)?); | ||
43 | } | ||
44 | extra_args.append(&mut config.extra_args); | ||
45 | Ok(Self { | ||
46 | compile_commands_location: cli | ||
47 | .compile_commands_location | ||
48 | .or(config.compile_commands_location) | ||
49 | .unwrap_or_else(|| PathBuf::from(r".")), | ||
50 | extra_args, | ||
51 | }) | ||
52 | } | ||
53 | } | ||
diff --git a/src/parsing/clang/entities.rs b/src/parsing/clang/entities.rs new file mode 100644 index 0000000..a8062bb --- /dev/null +++ b/src/parsing/clang/entities.rs | |||
@@ -0,0 +1,369 @@ | |||
1 | use crate::types::Entity; | ||
2 | |||
3 | use clang::{EntityKind, Usr}; | ||
4 | use thiserror::Error; | ||
5 | |||
6 | use std::collections::BTreeMap; | ||
7 | use std::convert::TryFrom; | ||
8 | |||
9 | // TODO: factor out hardcoded strings | ||
10 | |||
11 | #[derive(Debug)] | ||
12 | pub(super) struct Children { | ||
13 | namespaces: Option<BTreeMap<Usr, Described<Namespace>>>, | ||
14 | variables: Option<BTreeMap<Usr, Described<Variable>>>, | ||
15 | structs: Option<BTreeMap<Usr, Described<Struct>>>, | ||
16 | functions: Option<BTreeMap<Usr, Described<Function>>>, | ||
17 | } | ||
18 | |||
19 | #[derive(Debug, Clone, PartialEq, Eq)] | ||
20 | pub(super) enum ClangEntityKind { | ||
21 | Namespace, | ||
22 | Variable(VariableKind), | ||
23 | Struct(StructKind), | ||
24 | Function(FunctionKind), | ||
25 | } | ||
26 | |||
27 | #[derive(Debug, Clone, PartialEq, Eq)] | ||
28 | pub(super) enum VariableKind { | ||
29 | Variable, | ||
30 | Field, | ||
31 | } | ||
32 | |||
33 | #[derive(Debug, Clone, PartialEq, Eq)] | ||
34 | pub(super) enum StructKind { | ||
35 | Struct, | ||
36 | Class, | ||
37 | } | ||
38 | |||
39 | #[derive(Debug, Clone, PartialEq, Eq)] | ||
40 | pub(super) enum FunctionKind { | ||
41 | Function, | ||
42 | Method, | ||
43 | } | ||
44 | |||
45 | #[derive(Debug, Error)] | ||
46 | #[error("Unsupported Clang entity kind: {:?}", _0)] | ||
47 | pub(super) struct TryFromEntityKindError(String); | ||
48 | |||
49 | impl TryFrom<EntityKind> for ClangEntityKind { | ||
50 | type Error = TryFromEntityKindError; | ||
51 | |||
52 | fn try_from(kind: EntityKind) -> Result<ClangEntityKind, TryFromEntityKindError> { | ||
53 | Ok(match kind { | ||
54 | EntityKind::Namespace => ClangEntityKind::Namespace, | ||
55 | EntityKind::VarDecl => ClangEntityKind::Variable(VariableKind::Variable), | ||
56 | EntityKind::FieldDecl => ClangEntityKind::Variable(VariableKind::Field), | ||
57 | EntityKind::FunctionDecl => ClangEntityKind::Function(FunctionKind::Function), | ||
58 | EntityKind::Method | EntityKind::Constructor => { | ||
59 | ClangEntityKind::Function(FunctionKind::Method) | ||
60 | } | ||
61 | EntityKind::StructDecl => ClangEntityKind::Struct(StructKind::Struct), | ||
62 | EntityKind::ClassDecl => ClangEntityKind::Struct(StructKind::Class), | ||
63 | kind => return Err(TryFromEntityKindError(format!("{:?}", kind))), | ||
64 | }) | ||
65 | } | ||
66 | } | ||
67 | |||
68 | pub(super) trait ClangEntity { | ||
69 | fn kind(&self) -> &'static str; | ||
70 | |||
71 | fn get_member_namespaces(&mut self) -> Option<&mut BTreeMap<Usr, Described<Namespace>>> { | ||
72 | None | ||
73 | } | ||
74 | fn get_member_variables(&mut self) -> Option<&mut BTreeMap<Usr, Described<Variable>>> { | ||
75 | None | ||
76 | } | ||
77 | fn get_member_structs(&mut self) -> Option<&mut BTreeMap<Usr, Described<Struct>>> { | ||
78 | None | ||
79 | } | ||
80 | fn get_member_functions(&mut self) -> Option<&mut BTreeMap<Usr, Described<Function>>> { | ||
81 | None | ||
82 | } | ||
83 | |||
84 | fn into_children(self) -> Children | ||
85 | where | ||
86 | Self: Sized, | ||
87 | { | ||
88 | Children { | ||
89 | namespaces: None, | ||
90 | variables: None, | ||
91 | structs: None, | ||
92 | functions: None, | ||
93 | } | ||
94 | } | ||
95 | } | ||
96 | |||
97 | /* | ||
98 | pub(super) trait FromNamespaceParent: ClangEntity + Sized { | ||
99 | fn from_namespace_parent<'a>( | ||
100 | parent: &'a mut Described<Namespace>, | ||
101 | name: &Usr, | ||
102 | ) -> Option<&'a mut Described<Self>>; | ||
103 | } | ||
104 | */ | ||
105 | |||
106 | pub(super) trait NamespaceParentManipulation<T: ClangEntity + Sized> { | ||
107 | fn get_members_mut(&mut self) -> Option<&mut BTreeMap<Usr, Described<T>>>; | ||
108 | } | ||
109 | |||
110 | impl<U> NamespaceParentManipulation<Namespace> for U | ||
111 | where | ||
112 | U: ClangEntity, | ||
113 | { | ||
114 | fn get_members_mut(&mut self) -> Option<&mut BTreeMap<Usr, Described<Namespace>>> { | ||
115 | self.get_member_namespaces() | ||
116 | } | ||
117 | } | ||
118 | |||
119 | impl<U> NamespaceParentManipulation<Variable> for U | ||
120 | where | ||
121 | U: ClangEntity, | ||
122 | { | ||
123 | fn get_members_mut(&mut self) -> Option<&mut BTreeMap<Usr, Described<Variable>>> { | ||
124 | self.get_member_variables() | ||
125 | } | ||
126 | } | ||
127 | |||
128 | impl<U> NamespaceParentManipulation<Function> for U | ||
129 | where | ||
130 | U: ClangEntity, | ||
131 | { | ||
132 | fn get_members_mut(&mut self) -> Option<&mut BTreeMap<Usr, Described<Function>>> { | ||
133 | self.get_member_functions() | ||
134 | } | ||
135 | } | ||
136 | |||
137 | impl<U> NamespaceParentManipulation<Struct> for U | ||
138 | where | ||
139 | U: ClangEntity, | ||
140 | { | ||
141 | fn get_members_mut(&mut self) -> Option<&mut BTreeMap<Usr, Described<Struct>>> { | ||
142 | self.get_member_structs() | ||
143 | } | ||
144 | } | ||
145 | |||
146 | impl<T: ClangEntity> From<Described<T>> for Entity { | ||
147 | fn from(entity: Described<T>) -> Self { | ||
148 | let mut children = BTreeMap::new(); | ||
149 | |||
150 | let kind = String::from(entity.entity.kind()); | ||
151 | let clang_children = entity.entity.into_children(); | ||
152 | |||
153 | if let Some(namespaces) = clang_children.namespaces { | ||
154 | children.insert( | ||
155 | String::from("namespace"), | ||
156 | namespaces | ||
157 | .into_iter() | ||
158 | .map(|(usr, namespace)| (usr.0, namespace.into())) | ||
159 | .collect(), | ||
160 | ); | ||
161 | } | ||
162 | |||
163 | if let Some(variables) = clang_children.variables { | ||
164 | children.insert( | ||
165 | String::from("variable"), | ||
166 | variables | ||
167 | .into_iter() | ||
168 | .map(|(usr, variable)| (usr.0, variable.into())) | ||
169 | .collect(), | ||
170 | ); | ||
171 | } | ||
172 | |||
173 | if let Some(structs) = clang_children.structs { | ||
174 | children.insert( | ||
175 | String::from("struct"), | ||
176 | structs | ||
177 | .into_iter() | ||
178 | .map(|(usr, the_struct)| (usr.0, the_struct.into())) | ||
179 | .collect(), | ||
180 | ); | ||
181 | } | ||
182 | |||
183 | if let Some(functions) = clang_children.functions { | ||
184 | children.insert( | ||
185 | String::from("function"), | ||
186 | functions | ||
187 | .into_iter() | ||
188 | .map(|(usr, function)| (usr.0, function.into())) | ||
189 | .collect(), | ||
190 | ); | ||
191 | } | ||
192 | |||
193 | Entity { | ||
194 | name: entity.description.name, | ||
195 | language: String::from("clang"), | ||
196 | kind, | ||
197 | brief_description: entity.description.brief, | ||
198 | documentation: entity.description.detailed, | ||
199 | children, | ||
200 | } | ||
201 | } | ||
202 | } | ||
203 | |||
204 | #[derive(Debug, Clone, Default)] | ||
205 | pub(super) struct Description { | ||
206 | pub(super) name: String, | ||
207 | pub(super) brief: String, | ||
208 | pub(super) detailed: String, | ||
209 | } | ||
210 | |||
211 | #[derive(Debug, Clone)] | ||
212 | pub(super) struct Described<T> { | ||
213 | pub(super) description: Description, | ||
214 | pub(super) entity: T, | ||
215 | } | ||
216 | |||
217 | #[derive(Debug, Clone)] | ||
218 | pub(super) struct Namespace { | ||
219 | pub(super) member_namespaces: BTreeMap<Usr, Described<Namespace>>, | ||
220 | pub(super) member_variables: BTreeMap<Usr, Described<Variable>>, | ||
221 | pub(super) member_structs: BTreeMap<Usr, Described<Struct>>, | ||
222 | pub(super) member_functions: BTreeMap<Usr, Described<Function>>, | ||
223 | } | ||
224 | |||
225 | impl ClangEntity for Namespace { | ||
226 | fn kind(&self) -> &'static str { | ||
227 | "namespace" | ||
228 | } | ||
229 | |||
230 | fn get_member_namespaces(&mut self) -> Option<&mut BTreeMap<Usr, Described<Namespace>>> { | ||
231 | Some(&mut self.member_namespaces) | ||
232 | } | ||
233 | fn get_member_variables(&mut self) -> Option<&mut BTreeMap<Usr, Described<Variable>>> { | ||
234 | Some(&mut self.member_variables) | ||
235 | } | ||
236 | fn get_member_structs(&mut self) -> Option<&mut BTreeMap<Usr, Described<Struct>>> { | ||
237 | Some(&mut self.member_structs) | ||
238 | } | ||
239 | fn get_member_functions(&mut self) -> Option<&mut BTreeMap<Usr, Described<Function>>> { | ||
240 | Some(&mut self.member_functions) | ||
241 | } | ||
242 | |||
243 | fn into_children(self) -> Children { | ||
244 | Children { | ||
245 | namespaces: Some(self.member_namespaces), | ||
246 | variables: Some(self.member_variables), | ||
247 | structs: Some(self.member_structs), | ||
248 | functions: Some(self.member_functions), | ||
249 | } | ||
250 | } | ||
251 | } | ||
252 | |||
253 | /* | ||
254 | impl FromNamespaceParent for Namespace { | ||
255 | fn from_namespace_parent<'a>( | ||
256 | parent: &'a mut Described<Namespace>, | ||
257 | name: &Usr, | ||
258 | ) -> Option<&'a mut Described<Self>> { | ||
259 | parent.entity.member_namespaces.get_mut(name) | ||
260 | } | ||
261 | } | ||
262 | */ | ||
263 | |||
264 | #[derive(Debug, Clone)] | ||
265 | pub(super) struct Variable { | ||
266 | pub(super) kind: VariableKind, | ||
267 | pub(super) r#type: String, | ||
268 | } | ||
269 | |||
270 | impl ClangEntity for Variable { | ||
271 | fn kind(&self) -> &'static str { | ||
272 | match self.kind { | ||
273 | VariableKind::Variable => "variable", | ||
274 | VariableKind::Field => "field", | ||
275 | } | ||
276 | } | ||
277 | } | ||
278 | |||
279 | /* | ||
280 | impl FromNamespaceParent for Variable { | ||
281 | fn from_namespace_parent<'a>( | ||
282 | parent: &'a mut Described<Namespace>, | ||
283 | name: &Usr, | ||
284 | ) -> Option<&'a mut Described<Self>> { | ||
285 | parent.entity.member_variables.get_mut(name) | ||
286 | } | ||
287 | } | ||
288 | */ | ||
289 | |||
290 | #[derive(Debug, Clone)] | ||
291 | pub(super) struct Struct { | ||
292 | //pub(super) member_types: Vec<Usr>, | ||
293 | pub(super) kind: StructKind, | ||
294 | pub(super) member_variables: BTreeMap<Usr, Described<Variable>>, | ||
295 | pub(super) member_structs: BTreeMap<Usr, Described<Struct>>, | ||
296 | pub(super) member_functions: BTreeMap<Usr, Described<Function>>, | ||
297 | } | ||
298 | |||
299 | impl ClangEntity for Struct { | ||
300 | fn kind(&self) -> &'static str { | ||
301 | match self.kind { | ||
302 | StructKind::Struct => "struct", | ||
303 | StructKind::Class => "class", | ||
304 | } | ||
305 | } | ||
306 | |||
307 | fn get_member_variables(&mut self) -> Option<&mut BTreeMap<Usr, Described<Variable>>> { | ||
308 | Some(&mut self.member_variables) | ||
309 | } | ||
310 | fn get_member_structs(&mut self) -> Option<&mut BTreeMap<Usr, Described<Struct>>> { | ||
311 | Some(&mut self.member_structs) | ||
312 | } | ||
313 | fn get_member_functions(&mut self) -> Option<&mut BTreeMap<Usr, Described<Function>>> { | ||
314 | Some(&mut self.member_functions) | ||
315 | } | ||
316 | |||
317 | fn into_children(self) -> Children { | ||
318 | Children { | ||
319 | namespaces: None, | ||
320 | variables: Some(self.member_variables), | ||
321 | structs: Some(self.member_structs), | ||
322 | functions: Some(self.member_functions), | ||
323 | } | ||
324 | } | ||
325 | } | ||
326 | |||
327 | /* | ||
328 | impl FromNamespaceParent for Struct { | ||
329 | fn from_namespace_parent<'a>( | ||
330 | parent: &'a mut Described<Namespace>, | ||
331 | name: &Usr, | ||
332 | ) -> Option<&'a mut Described<Self>> { | ||
333 | parent.entity.member_structs.get_mut(name) | ||
334 | } | ||
335 | } | ||
336 | */ | ||
337 | |||
338 | #[derive(Debug, Clone)] | ||
339 | pub(super) struct Function { | ||
340 | pub(super) kind: FunctionKind, | ||
341 | pub(super) arguments: Vec<FunctionArgument>, | ||
342 | pub(super) return_type: String, | ||
343 | } | ||
344 | |||
345 | impl ClangEntity for Function { | ||
346 | fn kind(&self) -> &'static str { | ||
347 | match self.kind { | ||
348 | FunctionKind::Function => "function", | ||
349 | FunctionKind::Method => "method", | ||
350 | } | ||
351 | } | ||
352 | } | ||
353 | |||
354 | /* | ||
355 | impl FromNamespaceParent for Function { | ||
356 | fn from_namespace_parent<'a>( | ||
357 | parent: &'a mut Described<Namespace>, | ||
358 | name: &Usr, | ||
359 | ) -> Option<&'a mut Described<Self>> { | ||
360 | parent.entity.member_functions.get_mut(name) | ||
361 | } | ||
362 | } | ||
363 | */ | ||
364 | |||
365 | #[derive(Debug, Clone)] | ||
366 | pub(super) struct FunctionArgument { | ||
367 | pub(super) name: String, | ||
368 | pub(super) r#type: String, | ||
369 | } | ||
diff --git a/src/parsing/clang/mod.rs b/src/parsing/clang/mod.rs new file mode 100644 index 0000000..047c49e --- /dev/null +++ b/src/parsing/clang/mod.rs | |||
@@ -0,0 +1,5 @@ | |||
1 | pub(crate) mod config; | ||
2 | mod entities; | ||
3 | mod parsing; | ||
4 | |||
5 | pub(crate) use parsing::parse_compile_commands; | ||
diff --git a/src/parsing/clang/parsing.rs b/src/parsing/clang/parsing.rs new file mode 100644 index 0000000..5359253 --- /dev/null +++ b/src/parsing/clang/parsing.rs | |||
@@ -0,0 +1,748 @@ | |||
1 | use super::config::Config; | ||
2 | use super::entities::*; | ||
3 | use crate::types::Entity; | ||
4 | |||
5 | use anyhow::{anyhow, Context, Error, Result}; | ||
6 | use clang::{Clang, CompilationDatabase, Index, TranslationUnit, Usr}; | ||
7 | use codemap::CodeMap; | ||
8 | use thiserror::Error; | ||
9 | |||
10 | use std::collections::BTreeMap; | ||
11 | use std::convert::{TryFrom, TryInto}; | ||
12 | use std::path::{Path, PathBuf}; | ||
13 | |||
14 | #[derive(Debug, Default)] | ||
15 | struct TopLevel { | ||
16 | namespaces: BTreeMap<Usr, Described<Namespace>>, | ||
17 | variables: BTreeMap<Usr, Described<Variable>>, | ||
18 | structs: BTreeMap<Usr, Described<Struct>>, | ||
19 | functions: BTreeMap<Usr, Described<Function>>, | ||
20 | } | ||
21 | |||
22 | /* | ||
23 | enum TopLevelEntry<'a, T> { | ||
24 | Vacant { | ||
25 | parent: &'a mut Described<Namespace>, | ||
26 | }, | ||
27 | /// Vacant, but no semantic parent | ||
28 | TopLevel, | ||
29 | Occupied { | ||
30 | entity: &'a mut Described<T>, | ||
31 | }, | ||
32 | Error, | ||
33 | } | ||
34 | */ | ||
35 | |||
36 | impl TopLevel { | ||
37 | // Somehow has a lifetime issue I can't get my head around | ||
38 | /* | ||
39 | fn entry<'a, T>(&'a mut self, path: &::clang::Entity) -> Result<TopLevelEntry<'a, T>> | ||
40 | where | ||
41 | T: ClangEntity + FromNamespaceParent + FromTopLevel, | ||
42 | { | ||
43 | let usr = path.get_usr().ok_or_else(|| anyhow!("no usr"))?; | ||
44 | if let Some(parent_path) = parent(&path) { | ||
45 | let parent_entry = self.entry::<Namespace>(&parent_path)?; | ||
46 | if let TopLevelEntry::Occupied { | ||
47 | entity: namespace_parent, | ||
48 | } = parent_entry | ||
49 | { | ||
50 | Ok(match T::from_namespace_parent(namespace_parent, &usr) { | ||
51 | None => TopLevelEntry::Vacant { | ||
52 | parent: namespace_parent, | ||
53 | }, | ||
54 | Some(entity) => TopLevelEntry::Occupied { entity }, | ||
55 | }) | ||
56 | } else { | ||
57 | panic!("Wut"); | ||
58 | } | ||
59 | } else { | ||
60 | Ok(match T::from_toplevel(self, &usr) { | ||
61 | Some(entity) => TopLevelEntry::Occupied { entity }, | ||
62 | None => TopLevelEntry::TopLevel, | ||
63 | }) | ||
64 | } | ||
65 | } | ||
66 | */ | ||
67 | |||
68 | fn get_entity_mut(&mut self, path: clang::Entity) -> Option<&mut dyn ClangEntity> { | ||
69 | let usr = path.get_usr()?; | ||
70 | if let Some(parent_path) = parent(path) { | ||
71 | let parent = self.get_entity_mut(parent_path)?; | ||
72 | Some(match path.get_kind().try_into().ok()? { | ||
73 | ClangEntityKind::Namespace => { | ||
74 | &mut parent.get_member_namespaces()?.get_mut(&usr)?.entity | ||
75 | } | ||
76 | ClangEntityKind::Variable(_) => { | ||
77 | &mut parent.get_member_variables()?.get_mut(&usr)?.entity | ||
78 | } | ||
79 | ClangEntityKind::Function(_) => { | ||
80 | &mut parent.get_member_functions()?.get_mut(&usr)?.entity | ||
81 | } | ||
82 | ClangEntityKind::Struct(_) => { | ||
83 | &mut parent.get_member_structs()?.get_mut(&usr)?.entity | ||
84 | } | ||
85 | }) | ||
86 | } else { | ||
87 | Some(match path.get_kind().try_into().ok()? { | ||
88 | ClangEntityKind::Namespace => &mut self.namespaces.get_mut(&usr)?.entity, | ||
89 | ClangEntityKind::Variable(_) => &mut self.variables.get_mut(&usr)?.entity, | ||
90 | ClangEntityKind::Struct(_) => &mut self.structs.get_mut(&usr)?.entity, | ||
91 | ClangEntityKind::Function(_) => &mut self.functions.get_mut(&usr)?.entity, | ||
92 | }) | ||
93 | } | ||
94 | } | ||
95 | |||
96 | fn get_namespace_mut(&mut self, path: clang::Entity) -> Option<&mut Described<Namespace>> { | ||
97 | let usr = path.get_usr()?; | ||
98 | |||
99 | if let Some(parent_path) = parent(path) { | ||
100 | let parent = self.get_entity_mut(parent_path)?; | ||
101 | parent.get_member_namespaces()?.get_mut(&usr) | ||
102 | } else { | ||
103 | self.namespaces.get_mut(&usr) | ||
104 | } | ||
105 | } | ||
106 | |||
107 | fn insert<T>(&mut self, path: clang::Entity, entity: Described<T>) -> Result<()> | ||
108 | where | ||
109 | T: ClangEntity + std::fmt::Debug, | ||
110 | Self: TopLevelManipulation<T>, | ||
111 | Namespace: NamespaceParentManipulation<T>, | ||
112 | { | ||
113 | let usr = path.get_usr().ok_or_else(|| anyhow!("no usr"))?; | ||
114 | if let Some(parent_path) = parent(path) { | ||
115 | if let Some(parent_namespace) = self.get_namespace_mut(parent_path) { | ||
116 | parent_namespace | ||
117 | .entity | ||
118 | .get_members_mut() | ||
119 | // Namespace should be able to contain every kind of entity | ||
120 | .unwrap() | ||
121 | .insert(usr, entity); | ||
122 | Ok(()) | ||
123 | } else { | ||
124 | Err(anyhow!("has parent but no parent in tree")) | ||
125 | } | ||
126 | } else { | ||
127 | self.insert_toplevel(usr, entity); | ||
128 | Ok(()) | ||
129 | } | ||
130 | } | ||
131 | } | ||
132 | |||
133 | // Like .get_semantic_parent(), but return none if the parent is the translation unit | ||
134 | fn parent(libclang_entity: clang::Entity) -> Option<clang::Entity> { | ||
135 | match libclang_entity.get_semantic_parent() { | ||
136 | Some(parent) => { | ||
137 | if parent.get_kind() != clang::EntityKind::TranslationUnit { | ||
138 | Some(parent) | ||
139 | } else { | ||
140 | None | ||
141 | } | ||
142 | } | ||
143 | None => { | ||
144 | warn!("get_semantic_parent() returned None"); | ||
145 | None | ||
146 | } | ||
147 | } | ||
148 | } | ||
149 | |||
150 | trait TopLevelManipulation<T: ClangEntity> { | ||
151 | fn insert_toplevel(&mut self, usr: Usr, entity: Described<T>); | ||
152 | } | ||
153 | |||
154 | impl TopLevelManipulation<Namespace> for TopLevel { | ||
155 | fn insert_toplevel(&mut self, usr: Usr, entity: Described<Namespace>) { | ||
156 | self.namespaces.insert(usr, entity); | ||
157 | } | ||
158 | } | ||
159 | |||
160 | impl TopLevelManipulation<Variable> for TopLevel { | ||
161 | fn insert_toplevel(&mut self, usr: Usr, entity: Described<Variable>) { | ||
162 | self.variables.insert(usr, entity); | ||
163 | } | ||
164 | } | ||
165 | |||
166 | impl TopLevelManipulation<Function> for TopLevel { | ||
167 | fn insert_toplevel(&mut self, usr: Usr, entity: Described<Function>) { | ||
168 | self.functions.insert(usr, entity); | ||
169 | } | ||
170 | } | ||
171 | |||
172 | impl TopLevelManipulation<Struct> for TopLevel { | ||
173 | fn insert_toplevel(&mut self, usr: Usr, entity: Described<Struct>) { | ||
174 | self.structs.insert(usr, entity); | ||
175 | } | ||
176 | } | ||
177 | |||
178 | /* | ||
179 | trait FromTopLevel: ClangEntity + Sized { | ||
180 | fn from_toplevel<'a>(toplevel: &'a mut TopLevel, usr: &Usr) -> Option<&'a mut Described<Self>>; | ||
181 | } | ||
182 | |||
183 | impl FromTopLevel for Namespace { | ||
184 | fn from_toplevel<'a>(toplevel: &'a mut TopLevel, usr: &Usr) -> Option<&'a mut Described<Self>> { | ||
185 | toplevel.namespaces.get_mut(usr) | ||
186 | } | ||
187 | } | ||
188 | |||
189 | impl FromTopLevel for Variable { | ||
190 | fn from_toplevel<'a>(toplevel: &'a mut TopLevel, usr: &Usr) -> Option<&'a mut Described<Self>> { | ||
191 | toplevel.variables.get_mut(usr) | ||
192 | } | ||
193 | } | ||
194 | |||
195 | impl FromTopLevel for Function { | ||
196 | fn from_toplevel<'a>(toplevel: &'a mut TopLevel, usr: &Usr) -> Option<&'a mut Described<Self>> { | ||
197 | toplevel.functions.get_mut(usr) | ||
198 | } | ||
199 | } | ||
200 | |||
201 | impl FromTopLevel for Struct { | ||
202 | fn from_toplevel<'a>(toplevel: &'a mut TopLevel, usr: &Usr) -> Option<&'a mut Described<Self>> { | ||
203 | toplevel.structs.get_mut(usr) | ||
204 | } | ||
205 | } | ||
206 | */ | ||
207 | |||
208 | pub(crate) fn parse_compile_commands( | ||
209 | config: &Config, | ||
210 | codemap: &mut CodeMap, | ||
211 | ) -> Result<BTreeMap<String, Entity>> { | ||
212 | let clang = Clang::new().unwrap(); | ||
213 | let index = Index::new( | ||
214 | &clang, /* exclude from pch = */ false, /* print diagnostics = */ false, | ||
215 | ); | ||
216 | |||
217 | debug!("Extra libclang argument: {:?}", config.extra_args); | ||
218 | |||
219 | debug!( | ||
220 | "Loading compile commands from: {:?}", | ||
221 | config.compile_commands_location | ||
222 | ); | ||
223 | let database = | ||
224 | CompilationDatabase::from_directory(&config.compile_commands_location).map_err(|()| { | ||
225 | CompileCommandsLoadError { | ||
226 | path: config.compile_commands_location.clone(), | ||
227 | } | ||
228 | })?; | ||
229 | |||
230 | let toplevel_directory = std::env::current_dir().context("Cannot read current directory")?; | ||
231 | |||
232 | let mut entities = TopLevel::default(); | ||
233 | |||
234 | for command in database.get_all_compile_commands().get_commands() { | ||
235 | let directory = command.get_directory(); | ||
236 | trace!("Changing directory to: {:?}", directory); | ||
237 | std::env::set_current_dir(&directory) | ||
238 | .with_context(|| format!("Cannot change current directory to: {:?}", directory))?; | ||
239 | |||
240 | let filename = command.get_filename(); | ||
241 | |||
242 | let file_map = codemap.add_file( | ||
243 | filename | ||
244 | .to_str() | ||
245 | .context("File is not valid UTF-8")? | ||
246 | .to_owned(), | ||
247 | std::fs::read_to_string(&filename) | ||
248 | .with_context(|| format!("Cannot readfile: {:?}", filename))?, | ||
249 | ); | ||
250 | |||
251 | trace!("Parsing file: {:?}", filename); | ||
252 | // The file name is passed as an argument in the compile commands | ||
253 | let mut parser = index.parser(""); | ||
254 | parser.skip_function_bodies(true); | ||
255 | |||
256 | let mut clang_arguments = command.get_arguments(); | ||
257 | clang_arguments.extend_from_slice(&config.extra_args); | ||
258 | trace!("Parsing with libclang arguments: {:?}", clang_arguments); | ||
259 | parser.arguments(&clang_arguments); | ||
260 | |||
261 | parse_unit( | ||
262 | &parser | ||
263 | .parse() | ||
264 | .with_context(|| format!("Could not parse file: {:?}", filename))?, | ||
265 | &mut entities, | ||
266 | &toplevel_directory, | ||
267 | file_map.span, | ||
268 | &codemap, | ||
269 | )?; | ||
270 | |||
271 | trace!("Changing directory to: {:?}", directory); | ||
272 | std::env::set_current_dir(&toplevel_directory).with_context(|| { | ||
273 | format!( | ||
274 | "Cannot change current directory to: {:?}", | ||
275 | toplevel_directory | ||
276 | ) | ||
277 | })?; | ||
278 | } | ||
279 | |||
280 | let normalized_entities = entities | ||
281 | .namespaces | ||
282 | .into_iter() | ||
283 | .map(|(usr, entity)| (usr.0, entity.into())) | ||
284 | .chain(entities.variables.into_iter().map(|(usr, entity)| (usr.0, entity.into()))) | ||
285 | .chain(entities.structs.into_iter().map(|(usr, entity)| (usr.0, entity.into()))) | ||
286 | .chain(entities.functions.into_iter().map(|(usr, entity)| (usr.0, entity.into()))) | ||
287 | .collect(); | ||
288 | |||
289 | Ok(normalized_entities) | ||
290 | } | ||
291 | |||
292 | /* | ||
293 | pub(crate) fn parse_file<T, S>(path: T, extra_args: &[S]) -> EntitiesManager | ||
294 | where | ||
295 | T: Into<PathBuf>, | ||
296 | T: AsRef<Path>, | ||
297 | T: ToString, | ||
298 | S: AsRef<str>, | ||
299 | S: std::fmt::Debug, | ||
300 | { | ||
301 | let mut codemap = CodeMap::new(); | ||
302 | let file_map = codemap.add_file(path.to_string(), std::fs::read_to_string(&path).unwrap()); | ||
303 | let file_span = file_map.span; | ||
304 | |||
305 | let clang = Clang::new().unwrap(); | ||
306 | let index = Index::new(&clang, true, false); | ||
307 | let mut parser = index.parser(path); | ||
308 | parser.skip_function_bodies(true); | ||
309 | |||
310 | parser.arguments(&extra_args); | ||
311 | |||
312 | if log_enabled!(log::Level::Debug) { | ||
313 | for extra_arg in extra_args { | ||
314 | debug!("Extra libclang argument: {:?}", extra_arg); | ||
315 | } | ||
316 | } | ||
317 | |||
318 | let trans_unit = parser.parse().unwrap(); | ||
319 | let mut entities = EntitiesManager::new(); | ||
320 | |||
321 | parse_unit( | ||
322 | &trans_unit, | ||
323 | &mut entities, | ||
324 | &std::env::current_dir().unwrap(), | ||
325 | file_span, | ||
326 | &codemap, | ||
327 | ) | ||
328 | .unwrap(); | ||
329 | |||
330 | entities | ||
331 | } | ||
332 | */ | ||
333 | |||
334 | fn parse_unit( | ||
335 | trans_unit: &TranslationUnit, | ||
336 | entities: &mut TopLevel, | ||
337 | base_dir: impl AsRef<Path>, | ||
338 | file_span: codemap::Span, | ||
339 | codemap: &CodeMap, | ||
340 | ) -> Result<()> { | ||
341 | trans_unit.get_entity().visit_children(|entity, _parent| { | ||
342 | if is_in_system_header(entity, &base_dir) { | ||
343 | trace!( | ||
344 | "Entity is in system header, skipping: USR = {:?}", | ||
345 | entity.get_display_name() | ||
346 | ); | ||
347 | return clang::EntityVisitResult::Continue; | ||
348 | } | ||
349 | |||
350 | // TODO: wrap this callback in another function so that we can use the | ||
351 | // "?" operator instead of all these `match`es | ||
352 | let usr = match entity.get_usr() { | ||
353 | Some(usr) => usr, | ||
354 | None => return clang::EntityVisitResult::Continue, | ||
355 | }; | ||
356 | trace!("Entity with USR = {:?}", usr); | ||
357 | debug!("Parsing toplevel entity: {:?}", entity); | ||
358 | |||
359 | add_entity(entity, entities, file_span, codemap) | ||
360 | }); | ||
361 | |||
362 | /* | ||
363 | use codemap_diagnostic::{ColorConfig, Emitter}; | ||
364 | |||
365 | let mut emitter = Emitter::stderr(ColorConfig::Auto, Some(&codemap)); | ||
366 | |||
367 | for diagnostic in trans_unit.get_diagnostics().iter() { | ||
368 | let main_diag = match clang_diag_to_codemap_diag(&diagnostic, file_span) { | ||
369 | Some(diag) => diag, | ||
370 | None => continue, | ||
371 | }; | ||
372 | |||
373 | let sub_diags = diagnostic | ||
374 | .get_children() | ||
375 | .into_iter() | ||
376 | .filter_map(|diagnostic| clang_diag_to_codemap_diag(&diagnostic, file_span)); | ||
377 | |||
378 | let fix_it_diags = diagnostic | ||
379 | .get_fix_its() | ||
380 | .into_iter() | ||
381 | .map(|fix_it| clang_fix_it_to_codemap_diag(&fix_it, file_span)); | ||
382 | |||
383 | emitter.emit( | ||
384 | &std::iter::once(main_diag) | ||
385 | .chain(sub_diags) | ||
386 | .chain(fix_it_diags) | ||
387 | .collect::<Vec<_>>(), | ||
388 | ); | ||
389 | } | ||
390 | */ | ||
391 | |||
392 | Ok(()) | ||
393 | } | ||
394 | |||
395 | fn is_in_system_header(entity: clang::Entity, base_dir: impl AsRef<Path>) -> bool { | ||
396 | if entity.is_in_system_header() { | ||
397 | true | ||
398 | } else if let Some(location) = entity.get_location() { | ||
399 | if let Some(file) = location.get_file_location().file { | ||
400 | !file | ||
401 | .get_path() | ||
402 | .canonicalize() | ||
403 | .unwrap() | ||
404 | .starts_with(base_dir) | ||
405 | } else { | ||
406 | // Not defined in a file? probably shouldn't document | ||
407 | true | ||
408 | } | ||
409 | } else { | ||
410 | // Not defined anywhere? probably shouldn't document | ||
411 | true | ||
412 | } | ||
413 | } | ||
414 | |||
415 | // Entries encountered in the toplevel lexical context | ||
416 | fn add_entity( | ||
417 | libclang_entity: clang::Entity, | ||
418 | toplevel: &mut TopLevel, | ||
419 | file_span: codemap::Span, | ||
420 | codemap: &CodeMap, | ||
421 | ) -> clang::EntityVisitResult { | ||
422 | if libclang_entity.get_usr().is_none() { | ||
423 | return clang::EntityVisitResult::Continue; | ||
424 | }; | ||
425 | |||
426 | let kind = match ClangEntityKind::try_from(libclang_entity.get_kind()) { | ||
427 | Ok(kind) => kind, | ||
428 | Err(err) => { | ||
429 | use codemap_diagnostic::{ | ||
430 | ColorConfig, Diagnostic, Emitter, Level, SpanLabel, SpanStyle, | ||
431 | }; | ||
432 | let spans = if let Some(range) = libclang_entity.get_range() { | ||
433 | // TODO: add every file parsed in this translation unit to the | ||
434 | // codemap, so we can properly report errors | ||
435 | if !range.is_in_main_file() { | ||
436 | vec![] | ||
437 | } else { | ||
438 | let begin = range.get_start().get_file_location().offset as u64; | ||
439 | let end = range.get_end().get_file_location().offset as u64; | ||
440 | |||
441 | vec![SpanLabel { | ||
442 | span: file_span.subspan(begin, end), | ||
443 | label: None, | ||
444 | style: SpanStyle::Primary, | ||
445 | }] | ||
446 | } | ||
447 | } else { | ||
448 | vec![] | ||
449 | }; | ||
450 | |||
451 | let diag = Diagnostic { | ||
452 | level: Level::Warning, | ||
453 | message: format!("{}", err), | ||
454 | code: None, | ||
455 | spans, | ||
456 | }; | ||
457 | |||
458 | let mut emitter = Emitter::stderr(ColorConfig::Auto, Some(codemap)); | ||
459 | emitter.emit(&[diag]); | ||
460 | |||
461 | return clang::EntityVisitResult::Continue; | ||
462 | } | ||
463 | }; | ||
464 | |||
465 | if let Some(in_tree_entity) = toplevel.get_entity_mut(libclang_entity) { | ||
466 | // if current.has_documentation && !tree.has_documentation { | ||
467 | // append_documentation | ||
468 | // } | ||
469 | } else if libclang_entity.is_definition() { | ||
470 | // TODO: This probably means that you can't put documentation in forward declarations. | ||
471 | // | ||
472 | // This seems restrictive, but since there can be multiple declarations but only one definition, | ||
473 | // you should probably put your documentation on the definition anyway? | ||
474 | // | ||
475 | // Also, skipping forward declarations allows us to not have to insert, then update the tree | ||
476 | // when we see the definition. | ||
477 | |||
478 | let result = match kind { | ||
479 | ClangEntityKind::Namespace => Described::<Namespace>::try_from(libclang_entity) | ||
480 | .and_then(|namespace| toplevel.insert(libclang_entity, namespace)), | ||
481 | ClangEntityKind::Variable(_) => Described::<Variable>::try_from(libclang_entity) | ||
482 | .and_then(|variable| toplevel.insert(libclang_entity, variable)), | ||
483 | ClangEntityKind::Struct(_) => Described::<Struct>::try_from(libclang_entity) | ||
484 | .and_then(|r#struct| toplevel.insert(libclang_entity, r#struct)), | ||
485 | ClangEntityKind::Function(_) => Described::<Function>::try_from(libclang_entity) | ||
486 | .and_then(|function| toplevel.insert(libclang_entity, function)), | ||
487 | }; | ||
488 | // TODO: check result | ||
489 | } | ||
490 | |||
491 | if kind == ClangEntityKind::Namespace { | ||
492 | // Recurse here since namespace definitions are allowed to change between translation units. | ||
493 | ::clang::EntityVisitResult::Recurse | ||
494 | } else { | ||
495 | ::clang::EntityVisitResult::Continue | ||
496 | } | ||
497 | } | ||
498 | |||
499 | impl<'a, T> TryFrom<clang::Entity<'a>> for Described<T> | ||
500 | where | ||
501 | T: TryFrom<clang::Entity<'a>, Error = Error>, | ||
502 | { | ||
503 | type Error = Error; | ||
504 | |||
505 | fn try_from(entity: clang::Entity<'a>) -> Result<Self, Error> { | ||
506 | Ok(Described::<T> { | ||
507 | description: get_description(entity)?, | ||
508 | entity: T::try_from(entity)?, | ||
509 | }) | ||
510 | } | ||
511 | } | ||
512 | |||
513 | impl<'a> TryFrom<clang::Entity<'a>> for Namespace { | ||
514 | type Error = Error; | ||
515 | |||
516 | fn try_from(entity: clang::Entity) -> Result<Self, Error> { | ||
517 | match entity.get_kind().try_into() { | ||
518 | Ok(ClangEntityKind::Namespace) => {} | ||
519 | _ => panic!("Trying to parse a non-variable into a variable"), | ||
520 | } | ||
521 | debug!("Parsing Namespace: {:?}", entity); | ||
522 | |||
523 | // Do not recurse here, but recurse in the main loop, since namespace | ||
524 | // definitions is allowed to change between translation units | ||
525 | |||
526 | Ok(Namespace { | ||
527 | member_namespaces: Default::default(), | ||
528 | member_variables: Default::default(), | ||
529 | member_structs: Default::default(), | ||
530 | member_functions: Default::default(), | ||
531 | }) | ||
532 | } | ||
533 | } | ||
534 | |||
535 | impl<'a> TryFrom<clang::Entity<'a>> for Variable { | ||
536 | type Error = Error; | ||
537 | |||
538 | fn try_from(entity: clang::Entity) -> Result<Self, Error> { | ||
539 | let variable_kind; | ||
540 | match entity.get_kind().try_into() { | ||
541 | Ok(ClangEntityKind::Variable(kind)) => { | ||
542 | variable_kind = kind; | ||
543 | } | ||
544 | _ => panic!("Trying to parse a non-variable into a variable"), | ||
545 | } | ||
546 | debug!("Parsing Variable: {:?}", entity); | ||
547 | |||
548 | let r#type = entity.get_type().unwrap().get_display_name(); | ||
549 | trace!("Variable has type: {:?}", r#type); | ||
550 | |||
551 | Ok(Variable { | ||
552 | r#type, | ||
553 | kind: variable_kind, | ||
554 | }) | ||
555 | } | ||
556 | } | ||
557 | |||
558 | impl<'a> TryFrom<clang::Entity<'a>> for Struct { | ||
559 | type Error = Error; | ||
560 | |||
561 | fn try_from(entity: clang::Entity) -> Result<Self, Error> { | ||
562 | let struct_kind; | ||
563 | match entity.get_kind().try_into() { | ||
564 | Ok(ClangEntityKind::Struct(kind)) => { | ||
565 | struct_kind = kind; | ||
566 | } | ||
567 | _ => panic!("Trying to parse a non-class into a class"), | ||
568 | } | ||
569 | debug!("Parsing Struct: {:?}", entity); | ||
570 | |||
571 | let mut member_variables = BTreeMap::new(); | ||
572 | let mut member_structs = BTreeMap::new(); | ||
573 | let mut member_functions = BTreeMap::new(); | ||
574 | |||
575 | for child in entity.get_children() { | ||
576 | trace!("Struct has child: {:?}", child); | ||
577 | |||
578 | match child.get_kind().try_into() { | ||
579 | Ok(ClangEntityKind::Variable(_)) => { | ||
580 | let child_usr = child.get_usr().ok_or_else(|| anyhow!("no usr"))?; | ||
581 | member_variables.insert(child_usr, Described::<Variable>::try_from(child)?); | ||
582 | } | ||
583 | Ok(ClangEntityKind::Struct(_)) => { | ||
584 | let child_usr: Usr = child.get_usr().ok_or_else(|| anyhow!("no usr"))?; | ||
585 | member_structs.insert(child_usr, Described::<Struct>::try_from(child)?); | ||
586 | } | ||
587 | Ok(ClangEntityKind::Function(_)) => { | ||
588 | let child_usr = child.get_usr().ok_or_else(|| anyhow!("no usr"))?; | ||
589 | member_functions.insert(child_usr, Described::<Function>::try_from(child)?); | ||
590 | } | ||
591 | _ => trace!("Skipping child"), | ||
592 | } | ||
593 | } | ||
594 | |||
595 | Ok(Struct { | ||
596 | kind: struct_kind, | ||
597 | member_functions, | ||
598 | member_structs, | ||
599 | member_variables, | ||
600 | }) | ||
601 | } | ||
602 | } | ||
603 | |||
604 | impl<'a> TryFrom<clang::Entity<'a>> for Function { | ||
605 | type Error = Error; | ||
606 | |||
607 | fn try_from(entity: clang::Entity) -> Result<Self, Error> { | ||
608 | let function_kind; | ||
609 | match entity.get_kind().try_into() { | ||
610 | Ok(ClangEntityKind::Function(kind)) => { | ||
611 | function_kind = kind; | ||
612 | } | ||
613 | _ => panic!("Trying to parse a non-function into a function"), | ||
614 | } | ||
615 | debug!("Parsing Function: {:?}", entity); | ||
616 | |||
617 | let return_type = entity.get_result_type().unwrap().get_display_name(); | ||
618 | trace!("Function has return type: {:?}", return_type); | ||
619 | let arguments = entity | ||
620 | .get_arguments() | ||
621 | .unwrap() | ||
622 | .into_iter() | ||
623 | .map(|arg| { | ||
624 | let name = arg | ||
625 | .get_display_name() | ||
626 | .unwrap_or_else(|| String::from("unnamed")); | ||
627 | let r#type = arg.get_type().unwrap().get_display_name(); | ||
628 | trace!("Function has argument {:?} of type {:?}", name, r#type); | ||
629 | FunctionArgument { name, r#type } | ||
630 | }) | ||
631 | .collect(); | ||
632 | |||
633 | Ok(Function { | ||
634 | kind: function_kind, | ||
635 | arguments, | ||
636 | return_type, | ||
637 | }) | ||
638 | } | ||
639 | } | ||
640 | |||
641 | fn get_description(entity: clang::Entity) -> Result<Description> { | ||
642 | let name = entity | ||
643 | .get_display_name() | ||
644 | .ok_or_else(|| anyhow!("Entity has no name: {:?}", entity))?; | ||
645 | |||
646 | // TODO: is that the best? | ||
647 | if let (Some(brief), Some(comment)) = (entity.get_comment_brief(), entity.get_comment()) { | ||
648 | Ok(Description { | ||
649 | name, | ||
650 | brief, | ||
651 | detailed: parse_comment(comment), | ||
652 | }) | ||
653 | } else { | ||
654 | Ok(Description { | ||
655 | name, | ||
656 | brief: String::new(), | ||
657 | detailed: String::new(), | ||
658 | }) | ||
659 | } | ||
660 | } | ||
661 | |||
662 | pub fn parse_comment(raw: String) -> String { | ||
663 | #[derive(Debug)] | ||
664 | enum CommentStyle { | ||
665 | // Comments of type `/**` or `/*!` | ||
666 | Starred, | ||
667 | // Comments of type `///` | ||
668 | SingleLine, | ||
669 | } | ||
670 | |||
671 | let mut chars = raw.chars(); | ||
672 | let style = match &chars.as_str()[..3] { | ||
673 | "/*!" | "/**" => CommentStyle::Starred, | ||
674 | "///" => CommentStyle::SingleLine, | ||
675 | _ => panic!("Comment is empty or doesn't start with either `///`, `/**`, or `/*!`"), | ||
676 | }; | ||
677 | |||
678 | chars.nth(2); | ||
679 | |||
680 | let mut result = String::new(); | ||
681 | |||
682 | 'parse_loop: loop { | ||
683 | let maybe_space = chars.next(); | ||
684 | let mut empty_line = false; | ||
685 | match maybe_space { | ||
686 | // TODO: Warn on empty comments | ||
687 | None => break, | ||
688 | Some(' ') => {} | ||
689 | Some('\n') => { | ||
690 | empty_line = true; | ||
691 | result.push('\n'); | ||
692 | } | ||
693 | Some(ch) => result.push(ch), | ||
694 | } | ||
695 | |||
696 | if !empty_line { | ||
697 | let rest = chars.as_str(); | ||
698 | match rest.find('\n') { | ||
699 | None => { | ||
700 | result.push_str(rest); | ||
701 | break; | ||
702 | } | ||
703 | Some(position) => { | ||
704 | result.push_str(&rest[..=position]); | ||
705 | chars.nth(position); | ||
706 | } | ||
707 | } | ||
708 | } | ||
709 | |||
710 | // Beginning of the line | ||
711 | let first_non_ws_ch = 'ws_loop: loop { | ||
712 | let maybe_whitespace = chars.next(); | ||
713 | match maybe_whitespace { | ||
714 | None => break 'parse_loop, | ||
715 | Some(ch) if ch.is_whitespace() => continue, | ||
716 | Some(ch) => break 'ws_loop ch, | ||
717 | } | ||
718 | }; | ||
719 | |||
720 | match style { | ||
721 | CommentStyle::Starred if first_non_ws_ch == '*' => { | ||
722 | if &chars.as_str()[..1] == "/" { | ||
723 | break; | ||
724 | } | ||
725 | } | ||
726 | CommentStyle::Starred => result.push(first_non_ws_ch), | ||
727 | CommentStyle::SingleLine => { | ||
728 | assert!(first_non_ws_ch == '/'); | ||
729 | let rest = chars.as_str(); | ||
730 | if &rest[..2] == "//" { | ||
731 | chars.nth(1); | ||
732 | } else if &rest[..1] == "/" { | ||
733 | chars.nth(0); | ||
734 | } else { | ||
735 | panic!("Could not parse comment"); | ||
736 | } | ||
737 | } | ||
738 | } | ||
739 | } | ||
740 | |||
741 | result | ||
742 | } | ||
743 | |||
744 | #[derive(Debug, Clone, Error)] | ||
745 | #[error("Failed to load 'compile_commands.json' at path: {:?}", path)] | ||
746 | pub(crate) struct CompileCommandsLoadError { | ||
747 | path: PathBuf, | ||
748 | } | ||
diff --git a/src/parsing/mod.rs b/src/parsing/mod.rs new file mode 100644 index 0000000..0736eee --- /dev/null +++ b/src/parsing/mod.rs | |||
@@ -0,0 +1 @@ | |||
pub(crate) mod clang; | |||
diff --git a/src/types.rs b/src/types.rs new file mode 100644 index 0000000..82f9c65 --- /dev/null +++ b/src/types.rs | |||
@@ -0,0 +1,22 @@ | |||
1 | use serde_derive::{Serialize, Deserialize}; | ||
2 | |||
3 | use std::collections::BTreeMap; | ||
4 | |||
5 | #[derive(Debug, Clone, Serialize, Deserialize)] | ||
6 | pub struct Poseidoc { | ||
7 | pub config: toml::Value, | ||
8 | pub entities: BTreeMap<String, Entity>, | ||
9 | } | ||
10 | |||
11 | #[derive(Debug, Clone, Serialize, Deserialize)] | ||
12 | pub struct Entity { | ||
13 | pub name: String, | ||
14 | //location: SourceLocation | ||
15 | pub language: String, | ||
16 | pub kind: String, | ||
17 | pub brief_description: String, | ||
18 | pub documentation: String, | ||
19 | pub children: BTreeMap<String, BTreeMap<String, Entity>>, | ||
20 | } | ||
21 | |||
22 | // TODO: use newtype for language, entity kind, entity ID | ||