cxx_qt_gen/syntax/
qtfile.rs

1// SPDX-FileCopyrightText: 2022 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
2// SPDX-FileContributor: Andrew Hayzen <andrew.hayzen@kdab.com>
3//
4// SPDX-License-Identifier: MIT OR Apache-2.0
5
6use super::CxxQtItem;
7use proc_macro2::TokenStream;
8use quote::{ToTokens, TokenStreamExt};
9use syn::parse::{Parse, ParseStream};
10use syn::{AttrStyle, Attribute, Result};
11
12/// Representation of a CxxQtFile as Syn items
13pub struct CxxQtFile {
14    /// A vector of [syn::Attribute] in the file
15    pub attrs: Vec<Attribute>,
16    /// A vector of [CxxQtItem] items in the file
17    pub items: Vec<CxxQtItem>,
18}
19
20impl Parse for CxxQtFile {
21    fn parse(input: ParseStream) -> Result<Self> {
22        Ok(CxxQtFile {
23            attrs: input.call(Attribute::parse_inner)?,
24            items: {
25                let mut items = Vec::new();
26                while !input.is_empty() {
27                    items.push(input.parse()?);
28                }
29                items
30            },
31        })
32    }
33}
34
35impl ToTokens for CxxQtFile {
36    fn to_tokens(&self, tokens: &mut TokenStream) {
37        tokens.append_all(
38            self.attrs
39                .iter()
40                .filter(|attr| matches!(attr.style, AttrStyle::Inner(_))),
41        );
42        tokens.append_all(&self.items);
43    }
44}
45
46/// Parse the given [std::path::Path] into a [CxxQtFile]
47pub fn parse_qt_file(path: impl AsRef<std::path::Path>) -> Result<CxxQtFile> {
48    let source = std::fs::read_to_string(path.as_ref()).unwrap_or_else(|err| {
49        // todo: fixme with a proper error propagation
50        panic!("Failed to read file {:?}: {}", path.as_ref(), err);
51    });
52
53    // We drop the shebang from the generated Rust code
54    if source.starts_with("#!") && !source.starts_with("#![") {
55        let shebang_end = source.find('\n').unwrap_or(source.len());
56        syn::parse_str(&source[shebang_end..])
57    } else {
58        syn::parse_str(&source)
59    }
60}
61
62#[cfg(test)]
63mod tests {
64    use super::*;
65    use crate::tests::assert_tokens_eq;
66    use crate::CxxQtItem::CxxQt;
67    use quote::quote;
68    use std::env;
69    use std::path::PathBuf;
70    use syn::parse_quote;
71
72    #[test]
73    fn test_parse_qt_file() {
74        let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
75
76        let qt_file = parse_qt_file(manifest_dir.join("test_inputs/properties.rs")).unwrap();
77
78        assert!(qt_file.attrs.is_empty());
79        assert_eq!(qt_file.items.len(), 1);
80        assert!(matches!(qt_file.items.first(), Some(CxxQt(_))))
81    }
82
83    #[test]
84    fn test_parse_qt_file_shebang_strip() {
85        let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
86
87        let qt_file = parse_qt_file(manifest_dir.join("test_inputs/shebang.rs")).unwrap();
88
89        assert!(qt_file.attrs.is_empty());
90        assert_eq!(qt_file.items.len(), 1);
91        assert!(matches!(qt_file.items.first(), Some(CxxQt(_))))
92    }
93
94    #[test]
95    #[should_panic]
96    fn test_parse_invalid_qt_file() {
97        let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
98
99        let _ = parse_qt_file(manifest_dir.join("not/real"));
100    }
101
102    #[test]
103    fn test_parse() {
104        let file: CxxQtFile = parse_quote! {
105           #[cxx_qt::bridge]
106           mod ffi {
107                //! inner doc comment
108                type MyNumType = i32;
109            }
110
111            #[cxx::bridge]
112            mod ffi {}
113
114            /// Outer doc comment
115            struct MyStruct {
116                name: &str
117            }
118        };
119        assert_tokens_eq(
120            &file,
121            quote! {
122            #[cxx_qt::bridge]
123            mod ffi {
124                 #![doc = r" inner doc comment"]
125                 type MyNumType = i32;
126             }
127
128             #[cxx::bridge]
129             mod ffi {}
130
131             #[doc = r" Outer doc comment"]
132             struct MyStruct {
133                 name: &str
134             }
135            },
136        )
137    }
138
139    #[test]
140    #[should_panic]
141    fn test_parse_invalid_mod() {
142        let _file: CxxQtFile = parse_quote! {
143           item: T
144        };
145    }
146}