summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMinijackson <minijackson@riseup.net>2019-12-18 20:56:53 +0100
committerMinijackson <minijackson@riseup.net>2019-12-18 20:56:53 +0100
commitde896baff7e97fac4dde79078c9a2fa1c652576b (patch)
tree512b67b91d64e51d63f7ac5ff925a5c96d9aaf3c
parent860b73f1644ecd6548ae403ec483625fb7b625ea (diff)
downloadposeidoc-de896baff7e97fac4dde79078c9a2fa1c652576b.tar.gz
poseidoc-de896baff7e97fac4dde79078c9a2fa1c652576b.zip
Big refactoring
- entities should be more coherent when parsing multiple files - well defined, language agnostic entity tree - each module has its own configuration - less dead code
-rw-r--r--Cargo.toml2
-rw-r--r--shell.nix6
-rw-r--r--src/cli.rs7
-rw-r--r--src/config.rs89
-rw-r--r--src/doxygen.rs454
-rw-r--r--src/doxygen/builders.rs109
-rw-r--r--src/entities.rs216
-rw-r--r--src/generator/config.rs53
-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.rs19
-rw-r--r--src/parsing.rs482
-rw-r--r--src/parsing/clang/config.rs53
-rw-r--r--src/parsing/clang/entities.rs369
-rw-r--r--src/parsing/clang/mod.rs5
-rw-r--r--src/parsing/clang/parsing.rs748
-rw-r--r--src/parsing/mod.rs1
-rw-r--r--src/types.rs22
18 files changed, 1389 insertions, 1417 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 9572fa9..b01238f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -8,7 +8,7 @@ edition = "2018"
8 8
9[dependencies] 9[dependencies]
10anyhow = "1" 10anyhow = "1"
11clang = "0.23" 11clang = { version = "0.23", features = ["clang_8_0"] }
12codemap = "0.1" 12codemap = "0.1"
13codemap-diagnostic = "0.1" 13codemap-diagnostic = "0.1"
14log = "0.4" 14log = "0.4"
diff --git a/shell.nix b/shell.nix
index 5368dcd..5e370ca 100644
--- a/shell.nix
+++ b/shell.nix
@@ -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
diff --git a/src/cli.rs b/src/cli.rs
index 0a883af..ff25792 100644
--- a/src/cli.rs
+++ b/src/cli.rs
@@ -1,4 +1,4 @@
1use crate::config::Config; 1use crate::config::ProvidedConfig;
2 2
3use structopt::StructOpt; 3use 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 @@
1use crate::cli::Cli; 1use crate::cli::Cli;
2use crate::generator::config::{
3 Config as GeneratorConfig, ProvidedConfig as ProvidedGeneratorConfig,
4};
5use crate::parsing::clang::config::{Config as ClangConfig, ProvidedConfig as ProvidedClangConfig};
2 6
3use anyhow::{anyhow, Context, Result}; 7use anyhow::{anyhow, Context, Result};
4use codemap::CodeMap; 8use codemap::CodeMap;
@@ -12,71 +16,56 @@ use std::sync::Arc;
12 16
13pub(super) const DEFAULT_PROJECT_CONFIGURATION_FILE_NAME: &str = "poseidoc.toml"; 17pub(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)]
18pub(crate) struct Config { 20pub(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
29impl 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
48impl Default for Config { 26impl 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)] 36impl 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 {
62pub(crate) struct ClassConfig { 39 name: cli
63} 40 .common_options
64 41 .name
65impl 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
72impl 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 } 54pub(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
79pub(super) fn load_config(location: impl AsRef<Path>, codemap: &mut CodeMap) -> Result<Config> { 65pub(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
2mod builders;
3
4use xml::{
5 attribute::OwnedAttribute,
6 reader::{EventReader, ParserConfig, XmlEvent},
7};
8
9use std::collections::HashMap;
10use std::io::Read;
11
12// === Types ===
13
14#[derive(Debug, Clone)]
15pub(crate) enum DoxygenType {
16 NameSpace(NameSpace),
17 Class(Class),
18 Unhandled,
19}
20
21#[derive(Debug, Clone)]
22pub(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)]
30pub(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)]
37pub(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)]
57pub(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)]
68pub(crate) struct Description {
69 pub(crate) inner: Vec<DescriptionNode>,
70}
71
72impl Description {
73 fn new() -> Self {
74 Self { inner: Vec::new() }
75 }
76}
77
78#[derive(Debug, Clone)]
79pub(crate) enum DescriptionNode {
80 Text(String),
81 Title(String),
82 Para(Paragraph),
83 Sect1(String),
84 Internal(String),
85}
86
87#[derive(Debug, Clone)]
88pub(crate) struct Paragraph {
89 pub(crate) inner: Vec<ParagraphNode>,
90}
91
92#[derive(Debug, Clone)]
93pub(crate) enum ParagraphNode {
94 Text(String),
95 Ref(Ref),
96}
97
98#[derive(Debug, Clone)]
99pub(crate) struct Ref {
100 pub(crate) id: String,
101 pub(crate) kind: RefKind,
102 pub(crate) text: String,
103}
104
105#[derive(Debug, Clone)]
106pub(crate) enum RefKind {
107 Compound,
108 Member,
109}
110
111#[derive(Debug, Clone)]
112enum DoxygenTypeKind {
113 NameSpace,
114 Unhandled,
115}
116
117trait 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
130trait FromXML {
131 fn from_xml(attributes: Vec<OwnedAttribute>, event_reader: &mut EventReader<impl Read>)
132 -> Self;
133}
134
135impl 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
170impl 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
200impl 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
238pub(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
283fn 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`
344fn 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
354fn 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
373fn 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
384fn 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
408fn 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
432fn 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 @@
1use super::*;
2
3pub(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)]
12struct NameSpaceBuilder {
13 name: Option<String>,
14 members: Vec<String>,
15 brief_description: Option<Description>,
16 detailed_description: Option<Description>,
17}
18
19impl 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)]
53struct UnhandledBuilder;
54
55impl 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)]
69struct 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
77impl 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 @@
1use std::collections::HashMap;
2
3pub(crate) type DynEntity = dyn Entity + Sync + Send;
4
5#[derive(Debug, Clone, Hash, PartialEq, Eq)]
6pub(crate) struct Usr(pub(crate) String);
7
8pub(crate) struct EntitiesManager {
9 toplevel_entities: HashMap<Usr, Box<DynEntity>>,
10 descriptions: HashMap<Usr, Description>,
11}
12
13pub(crate) struct EntitiesManagerComponents {
14 pub(crate) toplevel_entities: HashMap<Usr, Box<DynEntity>>,
15 pub(crate) descriptions: HashMap<Usr, Description>,
16}
17
18impl 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)]
44pub(crate) struct Description {
45 pub(crate) name: String,
46 pub(crate) brief: String,
47 pub(crate) detailed: String,
48}
49
50impl 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)]
60pub(crate) struct Described<T> {
61 pub(crate) description: Description,
62 pub(crate) entity: T,
63}
64
65pub(crate) struct ChildGroup<'a> {
66 pub(crate) name: &'static str,
67 pub(crate) children: Vec<(&'a Usr, &'a DynEntity)>,
68}
69
70pub(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)]
81pub(crate) struct NameSpace {
82 // TODO: replace with Vec to keep order
83 pub(crate) members: HashMap<Usr, NameSpaceChild>,
84}
85
86#[derive(Debug, Clone)]
87pub(crate) enum NameSpaceChild {
88 NameSpace(NameSpace),
89 Class(Class),
90 Variable(Variable),
91 Function(Function),
92}
93
94impl 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)]
151pub(crate) struct Variable {
152 pub(crate) r#type: String,
153}
154
155impl Entity for Variable {
156 fn kind_name(&self) -> &'static str {
157 "variable"
158 }
159}
160
161#[derive(Debug, Clone)]
162pub(crate) struct Function {
163 pub(crate) arguments: Vec<FunctionArgument>,
164 pub(crate) return_type: String,
165}
166
167impl Entity for Function {
168 fn kind_name(&self) -> &'static str {
169 "function"
170 }
171}
172
173#[derive(Debug, Clone)]
174pub(crate) struct FunctionArgument {
175 pub(crate) name: String,
176 pub(crate) r#type: String,
177}
178
179#[derive(Debug, Clone)]
180pub(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
187impl 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 @@
1use serde_derive::{Deserialize, Serialize};
2use structopt::StructOpt;
3
4use std::collections::{HashMap, HashSet};
5
6#[derive(Debug, Clone, Serialize)]
7pub(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
15impl 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
23fn 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
42impl 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")]
50pub(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 @@
1use crate::config::Config; 1pub(crate) mod config;
2use crate::entities::{ 2mod pandoc;
3 Description, DynEntity, EntitiesManager, EntitiesManagerComponents, Usr, 3
4}; 4use self::config::Config;
5use crate::pandoc::into_pandoc; 5//use crate::entities::{
6// Description, DynEntity, EntitiesManager, EntitiesManagerComponents, Usr,
7//};
8use self::pandoc::into_pandoc;
9use crate::types::Entity;
6 10
7use anyhow::{ensure, Context, Result}; 11use anyhow::{ensure, Context, Result};
8use rayon::Scope; 12use rayon::Scope;
9use thiserror::Error; 13use thiserror::Error;
10 14
11use std::collections::HashMap; 15use std::collections::BTreeMap;
12use std::io::Write; 16use std::io::Write;
13use std::path::Path; 17use std::path::Path;
14 18
15const DEFAULT_CSS: &[u8] = include_bytes!("../res/style.css"); 19const DEFAULT_CSS: &[u8] = include_bytes!("../../res/style.css");
16
17pub(crate) fn generate(base_dir: &Path, manager: EntitiesManager, config: &Config) -> Result<()> {
18 let EntitiesManagerComponents {
19 toplevel_entities,
20 descriptions,
21 } = manager.decompose();
22 20
21pub(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
57fn generate_recursively<'a>( 56fn 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
94fn generate_single<'e>( 92fn 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 @@
1use crate::entities::*; 1use super::config::Config;
2use crate::types::Entity;
2 3
3use pandoc_types::definition::{Attr, Block, Inline, Meta, MetaValue, Pandoc}; 4use pandoc_types::definition::{Attr, Block, Inline, Meta, MetaValue, Pandoc};
4 5
5use std::collections::HashMap; 6use std::collections::BTreeMap;
6 7
7pub(crate) fn into_pandoc<'e>( 8pub(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 &section.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
109fn entity_link(usr: &Usr, name: String) -> Inline { 115fn 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
144fn member_list<'a>( 150fn 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;
4mod cli; 3mod cli;
5mod config; 4mod config;
6mod entities;
7mod generator; 5mod generator;
8mod pandoc;
9mod parsing; 6mod parsing;
7mod types;
10 8
11#[macro_use] 9#[macro_use]
12extern crate log; 10extern crate log;
13 11
14use cli::{Command, ConfigCommand}; 12use cli::{Command, ConfigCommand};
15use generator::generate; 13use generator::generate;
16use parsing::parse_file;
17 14
18use anyhow::{Context, Result}; 15use anyhow::{Context, Result};
19use codemap::CodeMap; 16use 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
116fn load_effective_config(cli: cli::Cli, codemap: &mut CodeMap) -> Result<config::Config> { 114fn 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 @@
1use crate::entities::*;
2
3use clang::{Clang, Index};
4use codemap::CodeMap;
5
6use std::collections::HashMap;
7use std::path::{Path, PathBuf};
8
9pub(crate) fn parse_file<T, S>(path: T, extra_args: &[S]) -> EntitiesManager
10where
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
100fn 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
148fn 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
196pub(crate) struct Comment(String);
197
198impl 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
283impl From<clang::Usr> for Usr {
284 fn from(usr: clang::Usr) -> Self {
285 Self(usr.0)
286 }
287}
288
289trait 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
294trait 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
300impl<T> IntoPoseidocEntity<T> for clang::Entity<'_>
301where
302 T: FromClangEntity,
303{
304 fn into_poseidoc_entity(self, manager: &mut EntitiesManager) -> T {
305 T::from_clang_entity(self, manager)
306 }
307}
308
309fn 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
323impl 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
343impl 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
367impl 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
389impl 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
406impl 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
440impl 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 @@
1use anyhow::Result;
2use serde_derive::{Deserialize, Serialize};
3use structopt::StructOpt;
4
5use std::path::PathBuf;
6
7#[derive(Debug, Clone, StructOpt, Deserialize, Serialize)]
8#[serde(rename_all = "kebab-case")]
9pub(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")]
16pub(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
24impl 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
35impl 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 @@
1use crate::types::Entity;
2
3use clang::{EntityKind, Usr};
4use thiserror::Error;
5
6use std::collections::BTreeMap;
7use std::convert::TryFrom;
8
9// TODO: factor out hardcoded strings
10
11#[derive(Debug)]
12pub(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)]
20pub(super) enum ClangEntityKind {
21 Namespace,
22 Variable(VariableKind),
23 Struct(StructKind),
24 Function(FunctionKind),
25}
26
27#[derive(Debug, Clone, PartialEq, Eq)]
28pub(super) enum VariableKind {
29 Variable,
30 Field,
31}
32
33#[derive(Debug, Clone, PartialEq, Eq)]
34pub(super) enum StructKind {
35 Struct,
36 Class,
37}
38
39#[derive(Debug, Clone, PartialEq, Eq)]
40pub(super) enum FunctionKind {
41 Function,
42 Method,
43}
44
45#[derive(Debug, Error)]
46#[error("Unsupported Clang entity kind: {:?}", _0)]
47pub(super) struct TryFromEntityKindError(String);
48
49impl 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
68pub(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/*
98pub(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
106pub(super) trait NamespaceParentManipulation<T: ClangEntity + Sized> {
107 fn get_members_mut(&mut self) -> Option<&mut BTreeMap<Usr, Described<T>>>;
108}
109
110impl<U> NamespaceParentManipulation<Namespace> for U
111where
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
119impl<U> NamespaceParentManipulation<Variable> for U
120where
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
128impl<U> NamespaceParentManipulation<Function> for U
129where
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
137impl<U> NamespaceParentManipulation<Struct> for U
138where
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
146impl<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)]
205pub(super) struct Description {
206 pub(super) name: String,
207 pub(super) brief: String,
208 pub(super) detailed: String,
209}
210
211#[derive(Debug, Clone)]
212pub(super) struct Described<T> {
213 pub(super) description: Description,
214 pub(super) entity: T,
215}
216
217#[derive(Debug, Clone)]
218pub(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
225impl 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/*
254impl 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)]
265pub(super) struct Variable {
266 pub(super) kind: VariableKind,
267 pub(super) r#type: String,
268}
269
270impl 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/*
280impl 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)]
291pub(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
299impl 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/*
328impl 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)]
339pub(super) struct Function {
340 pub(super) kind: FunctionKind,
341 pub(super) arguments: Vec<FunctionArgument>,
342 pub(super) return_type: String,
343}
344
345impl 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/*
355impl 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)]
366pub(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 @@
1pub(crate) mod config;
2mod entities;
3mod parsing;
4
5pub(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 @@
1use super::config::Config;
2use super::entities::*;
3use crate::types::Entity;
4
5use anyhow::{anyhow, Context, Error, Result};
6use clang::{Clang, CompilationDatabase, Index, TranslationUnit, Usr};
7use codemap::CodeMap;
8use thiserror::Error;
9
10use std::collections::BTreeMap;
11use std::convert::{TryFrom, TryInto};
12use std::path::{Path, PathBuf};
13
14#[derive(Debug, Default)]
15struct 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/*
23enum 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
36impl 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
134fn 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
150trait TopLevelManipulation<T: ClangEntity> {
151 fn insert_toplevel(&mut self, usr: Usr, entity: Described<T>);
152}
153
154impl TopLevelManipulation<Namespace> for TopLevel {
155 fn insert_toplevel(&mut self, usr: Usr, entity: Described<Namespace>) {
156 self.namespaces.insert(usr, entity);
157 }
158}
159
160impl TopLevelManipulation<Variable> for TopLevel {
161 fn insert_toplevel(&mut self, usr: Usr, entity: Described<Variable>) {
162 self.variables.insert(usr, entity);
163 }
164}
165
166impl TopLevelManipulation<Function> for TopLevel {
167 fn insert_toplevel(&mut self, usr: Usr, entity: Described<Function>) {
168 self.functions.insert(usr, entity);
169 }
170}
171
172impl 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/*
179trait FromTopLevel: ClangEntity + Sized {
180 fn from_toplevel<'a>(toplevel: &'a mut TopLevel, usr: &Usr) -> Option<&'a mut Described<Self>>;
181}
182
183impl 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
189impl 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
195impl 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
201impl 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
208pub(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/*
293pub(crate) fn parse_file<T, S>(path: T, extra_args: &[S]) -> EntitiesManager
294where
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
334fn 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
395fn 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
416fn 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
499impl<'a, T> TryFrom<clang::Entity<'a>> for Described<T>
500where
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
513impl<'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
535impl<'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
558impl<'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
604impl<'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
641fn 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
662pub 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)]
746pub(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 @@
1use serde_derive::{Serialize, Deserialize};
2
3use std::collections::BTreeMap;
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
6pub struct Poseidoc {
7 pub config: toml::Value,
8 pub entities: BTreeMap<String, Entity>,
9}
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
12pub 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