kotlin_poet_rs/spec/
file.rs

1use crate::io::RenderKotlin;
2use crate::spec::{Annotation, AnnotationTarget, Class, ClassLikeTypeName, CodeBlock, Comment, Function, Import, Package, Property, TypeAlias};
3use crate::tokens;
4
5#[derive(Debug, Clone)]
6enum KotlinFileNode {
7    Property(Property),
8    Function(Function),
9    TypeAlias(TypeAlias),
10    Class(Class),
11}
12
13/// Represents a Kotlin file.
14#[derive(Debug, Clone)]
15pub struct KotlinFile {
16    package: Option<Package>,
17    imports: Vec<Import>,
18    nodes: Vec<KotlinFileNode>,
19    annotations: Vec<Annotation>,
20    header_comments: Vec<Comment>,
21}
22
23impl KotlinFile {
24    /// Creates file in specified [package]
25    pub fn new<PackageLike: Into<Package>>(package: PackageLike) -> Self {
26        KotlinFile {
27            package: Some(package.into()),
28            imports: Vec::new(),
29            nodes: Vec::new(),
30            annotations: Vec::new(),
31            header_comments: Vec::new(),
32        }
33    }
34
35    /// Creates new file without package statement
36    pub fn root() -> Self {
37        KotlinFile {
38            package: None,
39            imports: Vec::new(),
40            nodes: Vec::new(),
41            annotations: Vec::new(),
42            header_comments: Vec::new(),
43        }
44    }
45
46    /// Adds new comment in first line.
47    ///
48    /// This method can be called multiple times to add multiple comments,
49    /// they will appear in order on enw lines.
50    pub fn header_comment<CommentLike: Into<Comment>>(mut self, comment: CommentLike) -> Self {
51        self.header_comments.push(comment.into());
52        self
53    }
54
55    /// Adds new import to the file.
56    pub fn import(mut self, import: Import) -> Self {
57        self.imports.push(import);
58        self
59    }
60
61    /// Adds new property to the file.
62    pub fn property(mut self, property: Property) -> Self {
63        self.nodes.push(KotlinFileNode::Property(property));
64        self
65    }
66
67    /// Adds new function to the file.
68    pub fn function(mut self, function: Function) -> Self {
69        self.nodes.push(KotlinFileNode::Function(function));
70        self
71    }
72
73    /// Adds new type alias to the file.
74    pub fn type_alias(mut self, type_alias: TypeAlias) -> Self {
75        self.nodes.push(KotlinFileNode::TypeAlias(type_alias));
76        self
77    }
78
79    /// Adds new class to the file.
80    pub fn class(mut self, class: Class) -> Self {
81        self.nodes.push(KotlinFileNode::Class(class));
82        self
83    }
84
85    /// Adds new annotation to the file.
86    /// Added annotation will be forced to have [AnnotationTarget::File] target.
87    pub fn annotation(mut self, annotation: Annotation) -> Self {
88        self.annotations.push(
89            annotation
90                .target(AnnotationTarget::File)
91        );
92        self
93    }
94}
95
96impl From<ClassLikeTypeName> for KotlinFile {
97    fn from(value: ClassLikeTypeName) -> Self {
98        let package = value.package;
99        KotlinFile::new(package)
100    }
101}
102
103impl RenderKotlin for KotlinFile {
104    fn render_into(&self, block: &mut CodeBlock) {
105        if !self.header_comments.is_empty() {
106            for comment in &self.header_comments {
107                block.push_renderable(comment);
108                block.push_new_line();
109            }
110            block.push_new_line()
111        }
112
113        for annotation in &self.annotations {
114            block.push_renderable(annotation);
115            block.push_new_line();
116        }
117        if !self.annotations.is_empty() {
118            block.push_new_line();
119        }
120
121        if let Some(package) = &self.package {
122            block.push_static_atom(tokens::keyword::PACKAGE);
123            block.push_space();
124            block.push_renderable(package);
125            block.push_new_line();
126        }
127
128        for import in &self.imports {
129            block.push_renderable(import);
130            block.push_new_line();
131        }
132
133        for node in &self.nodes {
134            match node {
135                KotlinFileNode::Property(property) => {
136                    block.push_new_line();
137                    block.push_renderable(property);
138                    block.push_new_line();
139                }
140                KotlinFileNode::Function(function) => {
141                    block.push_new_line();
142                    block.push_renderable(function);
143                    block.push_new_line();
144                }
145                KotlinFileNode::TypeAlias(type_alias) => {
146                    block.push_new_line();
147                    block.push_renderable(type_alias);
148                    block.push_new_line();
149                }
150                KotlinFileNode::Class(class) => {
151                    block.push_new_line();
152                    block.push_renderable(class);
153                    block.push_new_line();
154                }
155            }
156        }
157    }
158}
159
160#[cfg(test)]
161mod tests {
162    use crate::spec::{Comment, Function, KotlinFile};
163    use crate::io::RenderKotlin;
164
165    #[test]
166    fn test_root_file() {
167        let file = KotlinFile::root()
168            .function(Function::new("main"));
169
170        assert_eq!(
171            file.render_string(),
172            "public fun main(): kotlin.Unit",
173        )
174    }
175
176    #[test]
177    fn test_file_with_header_comments() {
178        let file = KotlinFile::new("com.example")
179            .header_comment(Comment::from("This is a header comment"))
180            .header_comment(Comment::from("This is another header comment"));
181
182        assert_eq!(
183            file.render_string(),
184            "// This is a header comment\n// This is another header comment\n\npackage com.example"
185        )
186    }
187}