ryo_query_language/
formatter.rs1use quote::ToTokens;
7use ryo_source::pure::{
8 PureAttrMeta, PureAttribute, PureEnum, PureFn, PureItem, PureStruct, PureTrait, ToSyn,
9 ToSynError,
10};
11
12pub struct SourceFormatter;
14
15impl SourceFormatter {
16 fn format_item(item: syn::Item) -> String {
18 let file = syn::File {
19 shebang: None,
20 attrs: vec![],
21 items: vec![item],
22 };
23 prettyplease::unparse(&file)
24 }
25
26 fn format_signature(sig: &syn::Signature) -> String {
28 sig.to_token_stream().to_string()
29 }
30
31 pub fn format_fn_full(f: &PureFn) -> Result<String, ToSynError> {
33 Ok(Self::format_item(syn::Item::Fn(f.to_syn()?)))
34 }
35
36 pub fn format_fn_signature(f: &PureFn) -> Result<String, ToSynError> {
38 Ok(Self::format_signature(&f.to_syn()?.sig))
39 }
40
41 pub fn format_struct(s: &PureStruct) -> Result<String, ToSynError> {
43 Ok(Self::format_item(syn::Item::Struct(s.to_syn()?)))
44 }
45
46 pub fn format_enum(e: &PureEnum) -> Result<String, ToSynError> {
48 Ok(Self::format_item(syn::Item::Enum(e.to_syn()?)))
49 }
50
51 pub fn format_trait(t: &PureTrait) -> Result<String, ToSynError> {
53 Ok(Self::format_item(syn::Item::Trait(t.to_syn()?)))
54 }
55
56 pub fn format_item_source(item: &PureItem) -> Result<String, ToSynError> {
58 Ok(Self::format_item(item.to_syn()?))
59 }
60
61 pub fn extract_doc(attrs: &[PureAttribute]) -> Option<String> {
63 let docs: Vec<String> = attrs
64 .iter()
65 .filter(|a| a.path == "doc")
66 .filter_map(|a| {
67 if let PureAttrMeta::NameValue(val) = &a.meta {
68 Some(val.trim_matches('"').to_string())
69 } else {
70 None
71 }
72 })
73 .collect();
74
75 if docs.is_empty() {
76 None
77 } else {
78 Some(docs.join("\n"))
79 }
80 }
81
82 pub fn extract_spec(attrs: &[PureAttribute]) -> Option<String> {
84 let specs: Vec<String> = attrs
85 .iter()
86 .filter(|a| a.path == "spec")
87 .map(|a| format!("#[spec({:?})]", a.meta))
88 .collect();
89
90 if specs.is_empty() {
91 None
92 } else {
93 Some(specs.join("\n"))
94 }
95 }
96
97 pub fn extract_doc_and_spec(attrs: &[PureAttribute]) -> Option<String> {
99 let mut lines = Vec::new();
100
101 if let Some(spec) = Self::extract_spec(attrs) {
103 lines.push(spec);
104 }
105
106 if let Some(doc) = Self::extract_doc(attrs) {
108 lines.push(doc);
109 }
110
111 if lines.is_empty() {
112 None
113 } else {
114 Some(lines.join("\n"))
115 }
116 }
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122 use ryo_source::pure::{PureBlock, PureFields, PureGenerics, PureVis};
123
124 #[test]
125 fn test_format_fn_signature() {
126 let f = PureFn {
127 name: "hello".to_string(),
128 vis: PureVis::Public,
129 is_async: false,
130 is_const: false,
131 is_unsafe: false,
132 is_async_inferred: false,
133 abi: None,
134 generics: PureGenerics::default(),
135 params: vec![],
136 ret: None,
137 body: PureBlock::default(),
138 attrs: vec![],
139 };
140 let sig = SourceFormatter::format_fn_signature(&f).unwrap();
141 assert!(sig.contains("fn hello"));
142 }
143
144 #[test]
145 fn test_format_struct() {
146 let s = PureStruct {
147 name: "MyStruct".to_string(),
148 vis: PureVis::Public,
149 generics: PureGenerics::default(),
150 fields: PureFields::Unit,
151 attrs: vec![],
152 };
153 let source = SourceFormatter::format_struct(&s).unwrap();
154 assert!(source.contains("pub struct MyStruct"));
155 }
156}