1pub mod header;
7pub mod source;
8
9use crate::generator::cpp::{fragment::CppFragment, GeneratedCppBlocks};
10use clang_format::{clang_format_with_style, ClangFormatStyle};
11use header::write_cpp_header;
12use indoc::formatdoc;
13use source::write_cpp_source;
14
15pub fn namespaced(namespace: &str, cpp_code: &str) -> String {
17 if namespace.is_empty() {
18 cpp_code.to_owned()
19 } else {
20 formatdoc! {r#"
21 namespace {namespace} {{
22 {cpp_code}
23 }} // namespace {namespace}
24 "# }
25 }
26}
27
28pub fn write_cpp(generated: &GeneratedCppBlocks, include_path: &str) -> CppFragment {
30 let header = write_cpp_header(generated, include_path);
31 let source = write_cpp_source(generated, include_path);
32
33 CppFragment::Pair {
34 header: clang_format_with_style(&header, &ClangFormatStyle::File).unwrap_or(header),
35 source: clang_format_with_style(&source, &ClangFormatStyle::File).unwrap_or(source),
36 }
37}
38pub fn pair_as_header(pair: &CppFragment) -> Option<String> {
40 match pair {
41 CppFragment::Pair { header, source: _ } => Some(header.clone()),
42 CppFragment::Header(header) => Some(header.clone()),
43 CppFragment::Source(_) => None,
44 }
45}
46
47pub fn pair_as_source(pair: &CppFragment) -> Option<String> {
49 match pair {
50 CppFragment::Pair { header: _, source } => Some(source.clone()),
51 CppFragment::Header(_) => None,
52 CppFragment::Source(source) => Some(source.clone()),
53 }
54}
55
56pub fn extract_extern_qt(
57 generated: &GeneratedCppBlocks,
58 mut filter_fn: impl FnMut(&CppFragment) -> Option<String>,
59) -> String {
60 generated
61 .extern_cxx_qt
62 .iter()
63 .flat_map(|block| {
64 block
65 .fragments
66 .iter()
67 .filter_map(&mut filter_fn)
68 .collect::<Vec<String>>()
69 })
70 .collect::<Vec<String>>()
71 .join("\n")
72}
73
74#[cfg(test)]
75mod tests {
76 use std::collections::BTreeSet;
77
78 use super::*;
79
80 use crate::generator::cpp::property::tests::require_pair;
81 use crate::{
82 generator::cpp::qobject::{GeneratedCppQObject, GeneratedCppQObjectBlocks},
83 naming::Name,
84 tests::format_cpp,
85 };
86 use indoc::indoc;
87 use pretty_assertions::assert_str_eq;
88
89 pub fn create_generated_cpp() -> GeneratedCppBlocks {
90 create_generated_cpp_with_namespace(Some("cxx_qt::my_object"))
91 }
92
93 pub fn create_generated_cpp_with_namespace(namespace: Option<&str>) -> GeneratedCppBlocks {
95 GeneratedCppBlocks {
96 forward_declares: vec![],
97 includes: BTreeSet::default(),
98 extern_cxx_qt: vec![],
99 qobjects: vec![
100 GeneratedCppQObject {
101 name: if let Some(namespace) = namespace {
102 Name::mock_namespaced("MyObject", namespace)
103 } else {
104 Name::mock("MyObject")
105 },
106 rust_struct: Name::mock("MyObjectRust"),
107 namespace_internals: if let Some(namespace) = namespace {
108 format!("{namespace}::cxx_qt_my_object")
109 } else {
110 "cxx_qt_my_object".to_owned()
111 },
112 has_qobject_macro: true,
113 blocks: GeneratedCppQObjectBlocks {
114 base_classes: vec!["QStringListModel".to_owned()],
115 includes: {
116 let mut includes = BTreeSet::<String>::default();
117 includes.insert("#include <test>".to_owned());
118 includes
119 },
120 metaobjects: vec![
121 "Q_PROPERTY(int count READ count WRITE setCount NOTIFY countChanged)".to_owned(),
122 "Q_PROPERTY(bool longPropertyNameThatWrapsInClangFormat READ getToggle WRITE setToggle NOTIFY toggleChanged)"
123 .to_owned(),
124 ],
125 methods: vec![
126 CppFragment::Pair {
127 header: "int count() const;".to_owned(),
128 source: indoc! {r#"
129 int
130 MyObject::count() const
131 {
132 // getter
133 }
134 "#}
135 .to_owned(),
136 },
137 CppFragment::Pair {
138 header: "bool toggle() const;".to_owned(),
139 source: indoc! {r#"
140 bool
141 MyObject::toggle() const
142 {
143 // getter
144 }
145 "#}
146 .to_owned(),
147 },
148 CppFragment::Pair {
149 header: "Q_INVOKABLE void invokable();".to_owned(),
150 source: indoc! {r#"
151 void
152 MyObject::invokable()
153 {
154 // invokable method
155 }
156 "#}
157 .to_owned(),
158 },
159 CppFragment::Pair {
160 header: "void cppMethod();".to_owned(),
161 source: indoc! {r#"
162 void
163 MyObject::cppMethod()
164 {
165 // cpp method
166 }
167 "#}
168 .to_owned(),
169 },
170 CppFragment::Pair {
171 header: "Q_SLOT void setCount(int count);".to_owned(),
172 source: indoc! {r#"
173 void
174 MyObject::setCount(int count) const
175 {
176 // setter
177 }
178 "#}
179 .to_owned(),
180 },
181 CppFragment::Pair {
182 header: "Q_SLOT void setToggle(bool toggle);".to_owned(),
183 source: indoc! {r#"
184 void
185 MyObject::setToggle(bool toggle) const
186 {
187 // setter
188 }
189 "#}
190 .to_owned(),
191 },
192 CppFragment::Header (
193 "Q_SIGNAL void countChanged();".to_owned(),
194 ),
195 CppFragment::Header (
196 "Q_SIGNAL void toggleChanged();".to_owned(),
197 ),
198 ],
199 private_methods: vec![CppFragment::Pair{
200 header: "void privateMethod() const;".to_owned(),
201 source: indoc! {r#"
202 void MyObject::privateMethod() const {
203 // private method
204 }
205 "#}.to_owned(),
206 },
207 CppFragment::Pair{
208 header: "void privateMethod();".to_owned(),
209 source: indoc! {r#"
210 void MyObject::privateMethod()
211 {
212 // non-const private method
213 }
214 "#}.to_owned(),
215 }],
216 ..Default::default()
217 }
218 }
219 ],
220 }
221 }
222
223 pub fn create_generated_cpp_multi_qobjects() -> GeneratedCppBlocks {
225 GeneratedCppBlocks {
226 forward_declares: vec![],
227 includes: BTreeSet::default(),
228 extern_cxx_qt: vec![],
229 qobjects: vec![
230 GeneratedCppQObject {
231 name: Name::mock_namespaced("FirstObject", "cxx_qt"),
232 rust_struct: Name::mock("FirstObjectRust"),
233 namespace_internals: "cxx_qt::cxx_qt_first_object".to_owned(),
234 has_qobject_macro: true,
235 blocks: GeneratedCppQObjectBlocks {
236 base_classes: vec!["QStringListModel".to_owned()],
237 includes: {
238 let mut includes = BTreeSet::<String>::default();
239 includes.insert("#include <test>".to_owned());
240 includes
241 },
242 metaobjects: vec![
243 "Q_PROPERTY(int longPropertyNameThatWrapsInClangFormat READ count WRITE setCount NOTIFY countChanged)"
244 .to_owned(),
245 ],
246 methods: vec![CppFragment::Pair {
247 header: "int count() const;".to_owned(),
248 source: indoc! {r#"
249 int
250 FirstObject::count() const
251 {
252 // getter
253 }
254 "#}
255 .to_owned(),
256 },
257 CppFragment::Pair {
258 header: "Q_SLOT void setCount(int count);".to_owned(),
259 source: indoc! {r#"
260 void
261 FirstObject::setCount(int count) const
262 {
263 // setter
264 }
265 "#}
266 .to_owned(),
267 },
268 CppFragment::Header("Q_SIGNAL void countChanged();".to_owned()),
269 ],
270 ..Default::default()
271 }
272 },
273 GeneratedCppQObject {
274 name: Name::mock_namespaced("SecondObject", "cxx_qt"),
275 rust_struct: Name::mock("SecondObjectRust"),
276 namespace_internals: "cxx_qt::cxx_qt_second_object".to_owned(),
277 has_qobject_macro: true,
278 blocks: GeneratedCppQObjectBlocks {
279 base_classes: vec!["QStringListModel".to_owned()],
280 includes: {
281 let mut includes = BTreeSet::<String>::default();
282 includes.insert("#include <test>".to_owned());
283 includes
284 },
285 metaobjects: vec![
286 "Q_PROPERTY(int count READ count WRITE setCount NOTIFY countChanged)"
287 .to_owned(),
288 ],
289 methods: vec![CppFragment::Pair {
290 header: "int count() const;".to_owned(),
291 source: indoc! {r#"
292 int
293 SecondObject::count() const
294 {
295 // getter
296 }
297 "#}
298 .to_owned(),
299 },
300 CppFragment::Pair {
301 header: "Q_SLOT void setCount(int count);".to_owned(),
302 source: indoc! {r#"
303 void
304 SecondObject::setCount(int count) const
305 {
306 // setter
307 }
308 "#}
309 .to_owned(),
310 },
311 CppFragment::Header("Q_SIGNAL void countChanged();".to_owned()),
312 ],
313 private_methods: vec![
314 CppFragment::Pair{
315 header: "void privateMethod() const;".to_owned(),
316 source: indoc! {r#"
317 void SecondObject::privateMethod() const {
318 // private method
319 }
320 "#}.to_owned(),
321 }],
322 ..Default::default()
323 },
324 }
325 ]
326 }
327 }
328
329 pub fn create_generated_cpp_no_namespace() -> GeneratedCppBlocks {
331 create_generated_cpp_with_namespace(None)
332 }
333
334 pub fn expected_header() -> &'static str {
336 indoc! {r#"
337 #pragma once
338
339 #include <test>
340
341 namespace cxx_qt::my_object {
342 class MyObject;
343
344
345 } // namespace cxx_qt::my_object
346
347
348
349 #include "cxx-qt-gen/cxx_file_stem.cxx.h"
350
351
352
353 namespace cxx_qt::my_object {
354 class MyObject : public QStringListModel
355 {
356 Q_OBJECT
357 public:
358 Q_PROPERTY(int count READ count WRITE setCount NOTIFY countChanged)
359 Q_PROPERTY(bool longPropertyNameThatWrapsInClangFormat READ getToggle WRITE setToggle NOTIFY toggleChanged)
360
361 virtual ~MyObject() = default;
362
363 public:
364 int count() const;
365 bool toggle() const;
366 Q_INVOKABLE void invokable();
367 void cppMethod();
368 Q_SLOT void setCount(int count);
369 Q_SLOT void setToggle(bool toggle);
370 Q_SIGNAL void countChanged();
371 Q_SIGNAL void toggleChanged();
372
373 private:
374 void privateMethod() const;
375 void privateMethod();
376
377 };
378
379 static_assert(::std::is_base_of<QObject, MyObject>::value, "MyObject must inherit from QObject");
380 } // namespace cxx_qt::my_object
381
382
383
384 Q_DECLARE_METATYPE(cxx_qt::my_object::MyObject*)
385
386 "#}
387 }
388
389 pub fn expected_header_multi_qobjects() -> &'static str {
391 indoc! {r#"
392 #pragma once
393
394 #include <test>
395
396 namespace cxx_qt {
397 class FirstObject;
398
399
400 } // namespace cxx_qt
401
402
403
404 namespace cxx_qt {
405 class SecondObject;
406
407
408 } // namespace cxx_qt
409
410
411
412 #include "cxx-qt-gen/cxx_file_stem.cxx.h"
413
414
415
416 namespace cxx_qt {
417 class FirstObject : public QStringListModel
418 {
419 Q_OBJECT
420 public:
421 Q_PROPERTY(int longPropertyNameThatWrapsInClangFormat READ count WRITE setCount NOTIFY countChanged)
422
423 virtual ~FirstObject() = default;
424
425 public:
426 int count() const;
427 Q_SLOT void setCount(int count);
428 Q_SIGNAL void countChanged();
429
430
431 };
432
433 static_assert(::std::is_base_of<QObject, FirstObject>::value, "FirstObject must inherit from QObject");
434 } // namespace cxx_qt
435
436
437
438 Q_DECLARE_METATYPE(cxx_qt::FirstObject*)
439
440
441 namespace cxx_qt {
442 class SecondObject : public QStringListModel
443 {
444 Q_OBJECT
445 public:
446 Q_PROPERTY(int count READ count WRITE setCount NOTIFY countChanged)
447
448 virtual ~SecondObject() = default;
449
450 public:
451 int count() const;
452 Q_SLOT void setCount(int count);
453 Q_SIGNAL void countChanged();
454
455 private:
456 void privateMethod() const;
457
458 };
459
460 static_assert(::std::is_base_of<QObject, SecondObject>::value, "SecondObject must inherit from QObject");
461 } // namespace cxx_qt
462
463
464
465 Q_DECLARE_METATYPE(cxx_qt::SecondObject*)
466
467 "#}
468 }
469
470 pub fn expected_header_no_namespace() -> &'static str {
472 indoc! {r#"
473 #pragma once
474
475 #include <test>
476
477 class MyObject;
478
479
480
481
482 #include "cxx-qt-gen/cxx_file_stem.cxx.h"
483
484
485
486 class MyObject : public QStringListModel
487 {
488 Q_OBJECT
489 public:
490 Q_PROPERTY(int count READ count WRITE setCount NOTIFY countChanged)
491 Q_PROPERTY(bool longPropertyNameThatWrapsInClangFormat READ getToggle WRITE setToggle NOTIFY toggleChanged)
492
493 virtual ~MyObject() = default;
494
495 public:
496 int count() const;
497 bool toggle() const;
498 Q_INVOKABLE void invokable();
499 void cppMethod();
500 Q_SLOT void setCount(int count);
501 Q_SLOT void setToggle(bool toggle);
502 Q_SIGNAL void countChanged();
503 Q_SIGNAL void toggleChanged();
504
505 private:
506 void privateMethod() const;
507 void privateMethod();
508
509 };
510
511 static_assert(::std::is_base_of<QObject, MyObject>::value, "MyObject must inherit from QObject");
512
513
514 Q_DECLARE_METATYPE(MyObject*)
515
516 "#}
517 }
518
519 pub fn expected_source() -> &'static str {
521 indoc! {r#"
522 #include "cxx-qt-gen/cxx_file_stem.cxxqt.h"
523
524
525 namespace cxx_qt::my_object {
526 int
527 MyObject::count() const
528 {
529 // getter
530 }
531
532 bool
533 MyObject::toggle() const
534 {
535 // getter
536 }
537
538 void
539 MyObject::invokable()
540 {
541 // invokable method
542 }
543
544 void
545 MyObject::cppMethod()
546 {
547 // cpp method
548 }
549
550 void
551 MyObject::setCount(int count) const
552 {
553 // setter
554 }
555
556 void
557 MyObject::setToggle(bool toggle) const
558 {
559 // setter
560 }
561
562 void MyObject::privateMethod() const {
563 // private method
564 }
565
566 void MyObject::privateMethod()
567 {
568 // non-const private method
569 }
570
571 } // namespace cxx_qt::my_object
572
573 "#}
574 }
575
576 pub fn expected_source_multi_qobjects() -> &'static str {
578 indoc! {r#"
579 #include "cxx-qt-gen/cxx_file_stem.cxxqt.h"
580
581
582 namespace cxx_qt {
583 int
584 FirstObject::count() const
585 {
586 // getter
587 }
588
589 void
590 FirstObject::setCount(int count) const
591 {
592 // setter
593 }
594
595 } // namespace cxx_qt
596
597 namespace cxx_qt {
598 int
599 SecondObject::count() const
600 {
601 // getter
602 }
603
604 void
605 SecondObject::setCount(int count) const
606 {
607 // setter
608 }
609
610 void SecondObject::privateMethod() const {
611 // private method
612 }
613
614 } // namespace cxx_qt
615
616 "#}
617 }
618
619 pub fn expected_source_no_namespace() -> &'static str {
621 indoc! {r#"
622 #include "cxx-qt-gen/cxx_file_stem.cxxqt.h"
623
624
625 int
626 MyObject::count() const
627 {
628 // getter
629 }
630
631 bool
632 MyObject::toggle() const
633 {
634 // getter
635 }
636
637 void
638 MyObject::invokable()
639 {
640 // invokable method
641 }
642
643 void
644 MyObject::cppMethod()
645 {
646 // cpp method
647 }
648
649 void
650 MyObject::setCount(int count) const
651 {
652 // setter
653 }
654
655 void
656 MyObject::setToggle(bool toggle) const
657 {
658 // setter
659 }
660
661 void MyObject::privateMethod() const {
662 // private method
663 }
664
665 void MyObject::privateMethod()
666 {
667 // non-const private method
668 }
669
670 "#}
671 }
672
673 #[test]
674 fn namespacing() {
675 let cpp_code = "// My C++ Code\n // with multi-line";
676
677 let namespaced_code = namespaced("my_namespace", cpp_code);
678
679 assert_str_eq!(
680 indoc! {r#"
681 namespace my_namespace {
682 // My C++ Code
683 // with multi-line
684 } // namespace my_namespace
685 "#
686 },
687 namespaced_code
688 );
689 }
690
691 #[test]
692 fn namespacing_with_empty_namespace() {
693 let cpp_code = indoc! {r#"
694 // My C++ Code
695 "#};
696 let namespaced_code = namespaced("", cpp_code);
697 assert_str_eq!(cpp_code, namespaced_code);
698 }
699
700 #[test]
701 fn test_write_cpp() {
702 let generated = create_generated_cpp();
703 let (header, source) =
704 require_pair(&write_cpp(&generated, "cxx-qt-gen/cxx_file_stem")).unwrap();
705 assert_str_eq!(header, format_cpp(expected_header()));
706 assert_str_eq!(source, format_cpp(expected_source()));
707 }
708
709 #[test]
710 fn test_write_cpp_multi_qobjects() {
711 let generated = create_generated_cpp_multi_qobjects();
712 let (header, source) =
713 require_pair(&write_cpp(&generated, "cxx-qt-gen/cxx_file_stem")).unwrap();
714 assert_str_eq!(header, format_cpp(expected_header_multi_qobjects()));
715 assert_str_eq!(source, format_cpp(expected_source_multi_qobjects()));
716 }
717
718 #[test]
719 fn test_write_cpp_no_namespace() {
720 let generated = create_generated_cpp_no_namespace();
721 let (header, source) =
722 require_pair(&write_cpp(&generated, "cxx-qt-gen/cxx_file_stem")).unwrap();
723 assert_str_eq!(header, format_cpp(expected_header_no_namespace()));
724 assert_str_eq!(source, format_cpp(expected_source_no_namespace()));
725 }
726}