cxx_qt_gen/syntax/
qtfile.rs1use super::CxxQtItem;
7use proc_macro2::TokenStream;
8use quote::{ToTokens, TokenStreamExt};
9use syn::parse::{Parse, ParseStream};
10use syn::{AttrStyle, Attribute, Result};
11
12pub struct CxxQtFile {
14 pub attrs: Vec<Attribute>,
16 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
46pub 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 panic!("Failed to read file {:?}: {}", path.as_ref(), err);
51 });
52
53 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 type MyNumType = i32;
109 }
110
111 #[cxx::bridge]
112 mod ffi {}
113
114 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}