mz_walkabout/
parse.rs

1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Use of this software is governed by the Business Source License
4// included in the LICENSE file.
5//
6// As of the Change Date specified in that file, in accordance with
7// the Business Source License, use of this software will be governed
8// by the Apache License, Version 2.0.
9
10//! A basic parser for Rust code.
11
12use std::fs;
13use std::path::Path;
14
15use anyhow::{Context, Result};
16use syn::{Data, DataEnum, DataStruct, DataUnion, DeriveInput, Item};
17
18/// Parses the module at `path` and any contained submodules.
19///
20/// Returns [`DeriveInput`]s representing all struct and enum items in the
21/// module. This is exactly what a custom derive procedural macro would see,
22/// except that we can present information for all types simultaneously.
23pub fn parse_mod<P>(path: P) -> Result<Vec<DeriveInput>>
24where
25    P: AsRef<Path>,
26{
27    let mut out = vec![];
28    collect_items(path, &mut out)?;
29    Ok(out)
30}
31
32fn collect_items<P>(path: P, out: &mut Vec<DeriveInput>) -> Result<()>
33where
34    P: AsRef<Path>,
35{
36    let path = path.as_ref();
37    let dir = path.parent().expect("missing parent directory");
38    let stem = path
39        .file_stem()
40        .expect("missing file stem")
41        .to_str()
42        .expect("file stem is not valid UTF-8");
43
44    let src =
45        fs::read_to_string(path).with_context(|| format!("Failed to read {}", path.display()))?;
46    let file =
47        syn::parse_file(&src).with_context(|| format!("Failed to parse {}", path.display()))?;
48
49    for item in file.items {
50        match item {
51            Item::Mod(item) if item.content.is_none() => {
52                let path = match stem {
53                    "mod" | "lib" => dir.join(format!("{}.rs", item.ident)),
54                    _ => dir.join(format!("{}/{}.rs", stem, item.ident)),
55                };
56                collect_items(path, out)?;
57            }
58            Item::Struct(item) => {
59                out.push(DeriveInput {
60                    ident: item.ident,
61                    vis: item.vis,
62                    attrs: item.attrs,
63                    generics: item.generics,
64                    data: Data::Struct(DataStruct {
65                        fields: item.fields,
66                        struct_token: item.struct_token,
67                        semi_token: item.semi_token,
68                    }),
69                });
70            }
71            Item::Enum(item) => {
72                out.push(DeriveInput {
73                    ident: item.ident,
74                    vis: item.vis,
75                    attrs: item.attrs,
76                    generics: item.generics,
77                    data: Data::Enum(DataEnum {
78                        enum_token: item.enum_token,
79                        brace_token: item.brace_token,
80                        variants: item.variants,
81                    }),
82                });
83            }
84            Item::Union(item) => {
85                out.push(DeriveInput {
86                    ident: item.ident,
87                    vis: item.vis,
88                    attrs: item.attrs,
89                    generics: item.generics,
90                    data: Data::Union(DataUnion {
91                        union_token: item.union_token,
92                        fields: item.fields,
93                    }),
94                });
95            }
96            _ => (),
97        }
98    }
99    Ok(())
100}