kotlin_poet_rs/spec/
annotation.rs

1use crate::io::RenderKotlin;
2use crate::spec::{AnnotationTarget, Argument, ClassLikeTypeName, CodeBlock};
3use crate::tokens;
4
5/// Represents an annotation in Kotlin. Used for adding meta information for code entities.
6///
7/// [Official documentation reference](https://kotlinlang.org/docs/annotations.html)
8///
9/// # Examples
10///
11/// ```rust
12/// use std::str::FromStr;
13/// use kotlin_poet_rs::io::RenderKotlin;
14/// use kotlin_poet_rs::spec::{Annotation, Argument, ClassLikeTypeName, CodeBlock, Name, Package};
15///
16/// let annotation = Annotation::new(
17///     ClassLikeTypeName::top_level(
18///         Package::from_str("a.b.c").unwrap(),
19///         Name::from("MyAnnotation")
20///     )
21/// ).argument(
22///     Argument::new_named("value", CodeBlock::atom("1"))
23/// ).argument(
24///     Argument::new_named("name", CodeBlock::atom("\"name_value\""))
25/// );
26///
27/// assert_eq!(
28///     annotation.render_string(),
29///     "@a.b.c.MyAnnotation(value = 1, name = \"name_value\")"
30/// );
31/// ```
32#[derive(Debug, Clone)]
33pub struct Annotation {
34    type_name: ClassLikeTypeName,
35    arguments: Vec<Argument>,
36    target: Option<AnnotationTarget>,
37}
38
39#[derive(Debug, Clone)]
40pub(crate) enum  AnnotationSlotRenderMode { Vertical, Horizontal }
41
42#[derive(Debug, Clone)]
43pub(crate) struct AnnotationSlot {
44    inner: Vec<Annotation>,
45    render_mode: AnnotationSlotRenderMode
46}
47
48impl AnnotationSlot {
49
50    pub(crate) fn vertical() -> AnnotationSlot {
51        AnnotationSlot {
52            inner: Vec::new(),
53            render_mode: AnnotationSlotRenderMode::Vertical
54        }
55    }
56
57    pub(crate) fn horizontal() -> AnnotationSlot {
58        AnnotationSlot {
59            inner: Vec::new(),
60            render_mode: AnnotationSlotRenderMode::Horizontal
61        }
62    }
63
64    pub(crate) fn push(&mut self, new: Annotation) {
65        self.inner.push(new)
66    }
67}
68
69impl RenderKotlin for AnnotationSlot {
70    fn render_into(&self, block: &mut CodeBlock) {
71        for annotation in &self.inner {
72            block.push_renderable(annotation);
73            match self.render_mode {
74                AnnotationSlotRenderMode::Vertical => {
75                    block.push_new_line()
76                }
77                AnnotationSlotRenderMode::Horizontal => {
78                    block.push_space()
79                }
80            }
81        }
82    }
83}
84
85impl Annotation {
86    pub fn new<ClassLikeTypeNameLike: Into<ClassLikeTypeName>>(type_name: ClassLikeTypeNameLike) -> Self {
87        Annotation {
88            type_name: type_name.into(),
89            arguments: Vec::new(),
90            target: None,
91        }
92    }
93
94    pub fn argument(mut self, argument: Argument) -> Self {
95        self.arguments.push(argument);
96        self
97    }
98
99    pub fn target(mut self, target: AnnotationTarget) -> Self {
100        self.target = Some(target);
101        self
102    }
103}
104
105impl RenderKotlin for Annotation {
106    fn render_into(&self, block: &mut CodeBlock) {
107        block.push_static_atom(tokens::AT);
108        if let Some(target) = &self.target {
109            block.push_renderable(target);
110            block.push_static_atom(tokens::COLON);
111        }
112        block.push_renderable(&self.type_name);
113        block.push_round_brackets(|inner_code| {
114            inner_code.push_comma_separated(&self.arguments)
115        });
116    }
117}
118
119macro_rules! mixin_annotation_mutators {
120    () => {
121        /// Adds [Annotation] to this entity.
122        /// They will appear in order this method is called.
123        pub fn annotation(mut self, annotation: Annotation) -> Self {
124            self.annotation_slot.push(annotation);
125            self
126        }
127    };
128}
129
130pub(crate) use mixin_annotation_mutators;
131
132#[cfg(test)]
133mod tests {
134    use std::str::FromStr;
135    use crate::io::RenderKotlin;
136    use crate::spec::{Annotation, AnnotationTarget, Argument, ClassLikeTypeName, CodeBlock, Package};
137
138    #[test]
139    fn test_annotation() {
140        let annotation = Annotation::new(
141            ClassLikeTypeName::top_level(Package::from_str("a.b.c").unwrap(), "MyAnnotation")
142        ).argument(
143            Argument::new_named("value", CodeBlock::atom("1"))
144        ).argument(
145            Argument::new_named("name", CodeBlock::atom("\"name_value\""))
146        );
147
148        let code = annotation.render_string();
149
150        assert_eq!(
151            code,
152            "@a.b.c.MyAnnotation(value = 1, name = \"name_value\")"
153        );
154    }
155
156    #[test]
157    fn test_annotation_with_target() {
158        let annotation = Annotation::new(
159            ClassLikeTypeName::top_level(Package::from_str("a.b.c").unwrap(), "MyAnnotation")
160        ).argument(
161            Argument::new_named("value", CodeBlock::atom("1"))
162        ).argument(
163            Argument::new_named("name", CodeBlock::atom("\"name_value\""))
164        ).target(
165            AnnotationTarget::Field
166        );
167
168        let code = annotation.render_string();
169
170        assert_eq!(
171            code,
172            "@field:a.b.c.MyAnnotation(value = 1, name = \"name_value\")"
173        );
174    }
175}