├── .github ├── FUNDING.yml └── workflows │ └── tests.yml ├── .gitignore ├── screenshots ├── php.png └── rust.png ├── examples ├── simple.php ├── simple.rs └── class.rs ├── Justfile ├── Cargo.toml ├── src ├── modifiers.rs ├── attribute.rs ├── lib.rs ├── data_type.rs ├── interface.rs ├── comment.rs ├── function.rs ├── enum_case.rs ├── trait.rs ├── literal.rs ├── parameter.rs ├── body.rs ├── method.rs ├── class.rs ├── enum.rs ├── file.rs ├── usage.rs ├── constant.rs └── property.rs ├── LICENSE-MIT ├── README.md ├── tests ├── complete.php └── complete.rs └── LICENSE-APACHE /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: azjezz 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | -------------------------------------------------------------------------------- /screenshots/php.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/php-rust-tools/codegen/HEAD/screenshots/php.png -------------------------------------------------------------------------------- /screenshots/rust.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/php-rust-tools/codegen/HEAD/screenshots/rust.png -------------------------------------------------------------------------------- /examples/simple.php: -------------------------------------------------------------------------------- 1 | is_float($arg) ? number_format($arg, 2) : $arg, 20 | array_filter($args, static fn ($arg) => $arg !== null) 21 | )); 22 | } 23 | -------------------------------------------------------------------------------- /Justfile: -------------------------------------------------------------------------------- 1 | default: 2 | @just --list 3 | 4 | # build the library 5 | build: 6 | cargo build 7 | 8 | # regenerate schema 9 | update-examples: 10 | rm examples/simple.php examples/complete.php 11 | cargo run --example simple >> examples/simple.php 12 | cargo run --example complete >> examples/complete.php 13 | 14 | # detect linting problems. 15 | lint: 16 | cargo fmt --all -- --check 17 | cargo clippy 18 | 19 | # fix linting problems. 20 | fix: 21 | cargo fmt 22 | cargo clippy --fix --allow-dirty --allow-staged 23 | 24 | test: 25 | cargo test --all 26 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "php_codegen" 3 | version = "0.4.0" 4 | edition = "2021" 5 | description = "Generate PHP code from Rust using a fluent API 🐘 🦀" 6 | readme = "README.md" 7 | repository = "https://github.com/php-rust-tools/codegen" 8 | documentation = "https://docs.rs/php_codegen" 9 | homepage = "https://github.com/php-rust-tools/codegen" 10 | exclude = ["/.github/*"] 11 | authors = ["Saif Eddin Gmati "] 12 | license = "MIT OR Apache-2.0" 13 | keywords = ["php", "codegen", "code-generation", "php-rust-tools"] 14 | 15 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 16 | 17 | [dependencies] 18 | 19 | [dev-dependencies] 20 | pretty_assertions = "1.4.0" 21 | -------------------------------------------------------------------------------- /src/modifiers.rs: -------------------------------------------------------------------------------- 1 | use crate::Generator; 2 | use crate::Indentation; 3 | 4 | #[derive(Debug)] 5 | pub enum Modifier { 6 | Abstract, 7 | Final, 8 | Readonly, 9 | Static, 10 | } 11 | 12 | #[derive(Debug)] 13 | pub enum VisibilityModifier { 14 | Public, 15 | Protected, 16 | Private, 17 | } 18 | 19 | impl Generator for Modifier { 20 | fn generate(&self, _: Indentation, _: usize) -> String { 21 | match self { 22 | Modifier::Abstract => "abstract".to_string(), 23 | Modifier::Final => "final".to_string(), 24 | Modifier::Readonly => "readonly".to_string(), 25 | Modifier::Static => "static".to_string(), 26 | } 27 | } 28 | } 29 | 30 | impl Generator for VisibilityModifier { 31 | fn generate(&self, _: Indentation, _: usize) -> String { 32 | match self { 33 | VisibilityModifier::Public => "public".to_string(), 34 | VisibilityModifier::Protected => "protected".to_string(), 35 | VisibilityModifier::Private => "private".to_string(), 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022-2023 Saif Eddin Gmati 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PHP-Codegen 2 | 3 | Generate PHP code from Rust using a fluent API 🐘 🦀 4 | 5 | [![Actions Status](https://github.com/php-rust-tools/codegen/workflows/ci/badge.svg)](https://github.com/php-rust-tools/codegen/actions) 6 | [![Crates.io](https://img.shields.io/crates/v/php_codegen.svg)](https://crates.io/crates/php_codegen) 7 | [![Docs](https://docs.rs/php_codegen/badge.svg)](https://docs.rs/php_codegen/latest/php_codegen/) 8 | 9 | Rust | PHP 10 | :----------------------------------------------:|:----------------------------------------------: 11 | [![](screenshots/rust.png)](examples/simple.rs) | [![](screenshots/php.png)](examples/simple.php) 12 | 13 | 14 | ## Usage 15 | 16 | To bring this crate into your repository, either add `php_codegen` to your `Cargo.toml`, or run `cargo add php_codegen`. 17 | 18 | See the [examples](examples) directory for more examples. 19 | 20 | ## License 21 | 22 | Licensed under either of 23 | 24 | * Apache License, Version 2.0 25 | ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 26 | * MIT license 27 | ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 28 | 29 | at your option. 30 | 31 | ## Contribution 32 | 33 | Unless you explicitly state otherwise, any contribution intentionally submitted 34 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 35 | dual licensed as above, without any additional terms or conditions. 36 | -------------------------------------------------------------------------------- /src/attribute.rs: -------------------------------------------------------------------------------- 1 | use crate::Generator; 2 | use crate::Indentation; 3 | 4 | #[derive(Debug)] 5 | pub struct AttributeGroup { 6 | pub members: Vec<(String, Option)>, 7 | } 8 | 9 | impl AttributeGroup { 10 | pub fn new() -> Self { 11 | Self { members: vec![] } 12 | } 13 | 14 | pub fn add(mut self, name: T, arguments: Option) -> Self { 15 | self.members.push(( 16 | name.to_string(), 17 | arguments.map(|arguments| arguments.to_string()), 18 | )); 19 | 20 | self 21 | } 22 | } 23 | 24 | impl Generator for AttributeGroup { 25 | fn generate(&self, indentation: Indentation, level: usize) -> String { 26 | let mut result = String::new(); 27 | 28 | result.push_str(&indentation.indent("#[", level)); 29 | result.push_str( 30 | &self 31 | .members 32 | .iter() 33 | .map(|(name, arguments)| { 34 | if let Some(arguments) = arguments { 35 | format!("{}({})", name, arguments) 36 | } else { 37 | name.to_string() 38 | } 39 | }) 40 | .collect::>() 41 | .join(", "), 42 | ); 43 | result.push_str("]\n"); 44 | 45 | result 46 | } 47 | } 48 | 49 | impl Default for AttributeGroup { 50 | fn default() -> Self { 51 | Self::new() 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod attribute; 2 | pub mod body; 3 | pub mod class; 4 | pub mod comment; 5 | pub mod constant; 6 | pub mod data_type; 7 | pub mod r#enum; 8 | pub mod enum_case; 9 | pub mod file; 10 | pub mod function; 11 | pub mod interface; 12 | pub mod literal; 13 | pub mod method; 14 | pub mod modifiers; 15 | pub mod parameter; 16 | pub mod property; 17 | pub mod r#trait; 18 | pub mod usage; 19 | 20 | #[derive(Debug, PartialEq, Eq, PartialOrd, Clone, Copy)] 21 | pub enum Indentation { 22 | Spaces(usize), 23 | Tabs(usize), 24 | } 25 | 26 | impl Indentation { 27 | pub fn value(&self, level: usize) -> String { 28 | self.to_string().repeat(level) 29 | } 30 | 31 | pub fn indent(&self, code: T, level: usize) -> String { 32 | let indentation = self.value(level); 33 | 34 | code.to_string() 35 | .lines() 36 | .map(|line| format!("{}{}", indentation, line.trim_end())) 37 | .collect::>() 38 | .join("\n") 39 | } 40 | } 41 | 42 | impl std::fmt::Display for Indentation { 43 | fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { 44 | match self { 45 | Indentation::Spaces(count) => write!(f, "{:1$}", " ", count), 46 | Indentation::Tabs(count) => write!(f, "{:1$}", "\t", count), 47 | } 48 | } 49 | } 50 | 51 | impl Default for Indentation { 52 | fn default() -> Self { 53 | Indentation::Spaces(4) 54 | } 55 | } 56 | 57 | pub trait Generator { 58 | fn generate(&self, indentation: Indentation, level: usize) -> String; 59 | } 60 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | 8 | jobs: 9 | ci: 10 | name: ci 11 | runs-on: ${{ matrix.os }} 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | rust: 16 | - 'stable' 17 | - 'nightly' 18 | os: 19 | - 'ubuntu-latest' 20 | 21 | steps: 22 | - name: checkout 23 | uses: actions/checkout@v3 24 | 25 | - name: install rust 26 | uses: actions-rs/toolchain@v1 27 | with: 28 | toolchain: ${{ matrix.rust }} 29 | override: true 30 | components: rustfmt, clippy 31 | 32 | - name: cache 33 | id: cache 34 | uses: actions/cache@v3 35 | with: 36 | path: | 37 | ~/.cargo/bin/ 38 | ~/.cargo/registry/index/ 39 | ~/.cargo/registry/cache/ 40 | ~/.cargo/git/db/ 41 | target/ 42 | key: rust-${{ inputs.rust }}-os-${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 43 | 44 | - name: check 45 | uses: actions-rs/cargo@v1 46 | with: 47 | command: check 48 | 49 | - name: fmt 50 | if: matrix.rust == 'stable' 51 | uses: actions-rs/cargo@v1 52 | with: 53 | command: fmt 54 | args: --all --check 55 | 56 | - name: clippy 57 | if: matrix.rust == 'stable' 58 | uses: actions-rs/cargo@v1 59 | with: 60 | command: clippy 61 | 62 | - name: test 63 | uses: actions-rs/cargo@v1 64 | with: 65 | command: test 66 | args: -r --all 67 | -------------------------------------------------------------------------------- /examples/simple.rs: -------------------------------------------------------------------------------- 1 | use php_codegen::comment::Document; 2 | use php_codegen::data_type::DataType; 3 | use php_codegen::file::File; 4 | use php_codegen::function::Function; 5 | use php_codegen::parameter::Parameter; 6 | 7 | fn main() { 8 | let file = File::new() 9 | .namespaced("App") 10 | .declare("strict_types", 1) 11 | .function( 12 | Function::new("format") 13 | .document( 14 | Document::new() 15 | .text("Format a string with the given arguments using sprintf.") 16 | .empty_line() 17 | .tag("param", "non-empty-string $template") 18 | .empty_line() 19 | .simple_tag("pure"), 20 | ) 21 | .parameter(Parameter::new("template").typed(DataType::String)) 22 | .parameter( 23 | Parameter::new("args") 24 | .variadic() 25 | .typed(DataType::Union(vec![ 26 | DataType::Integer, 27 | DataType::Float, 28 | DataType::String, 29 | DataType::Null, 30 | ])), 31 | ) 32 | .returns(DataType::String) 33 | .body(vec![ 34 | "return sprintf($template, ...array_map(", 35 | " static fn ($arg) => is_float($arg) ? number_format($arg, 2) : $arg,", 36 | " array_filter($args, static fn ($arg) => $arg !== null)", 37 | "));", 38 | ]), 39 | ); 40 | 41 | print!("{file}"); 42 | } 43 | -------------------------------------------------------------------------------- /src/data_type.rs: -------------------------------------------------------------------------------- 1 | use crate::Generator; 2 | use crate::Indentation; 3 | 4 | #[derive(Debug)] 5 | pub enum DataType { 6 | Named(String), 7 | Nullable(Box), 8 | Union(Vec), 9 | Intersection(Vec), 10 | Void, 11 | Null, 12 | True, 13 | False, 14 | Never, 15 | Float, 16 | Boolean, 17 | Integer, 18 | String, 19 | Array, 20 | Object, 21 | Mixed, 22 | Callable, 23 | Iterable, 24 | StaticReference, 25 | SelfReference, 26 | ParentReference, 27 | } 28 | 29 | impl Generator for DataType { 30 | fn generate(&self, _indentation: Indentation, _level: usize) -> String { 31 | match self { 32 | DataType::Named(name) => name.to_string(), 33 | DataType::Nullable(inner) => { 34 | format!("null|{}", inner.generate(_indentation, _level)) 35 | } 36 | DataType::Union(inner) => inner 37 | .iter() 38 | .map(|t| t.generate(_indentation, _level)) 39 | .collect::>() 40 | .join("|"), 41 | DataType::Intersection(inner) => inner 42 | .iter() 43 | .map(|t| t.generate(_indentation, _level)) 44 | .collect::>() 45 | .join("&"), 46 | DataType::Null => "null".to_string(), 47 | DataType::True => "true".to_string(), 48 | DataType::False => "false".to_string(), 49 | DataType::Float => "float".to_string(), 50 | DataType::Boolean => "bool".to_string(), 51 | DataType::Integer => "int".to_string(), 52 | DataType::String => "string".to_string(), 53 | DataType::Array => "array".to_string(), 54 | DataType::Object => "object".to_string(), 55 | DataType::Mixed => "mixed".to_string(), 56 | DataType::Callable => "callable".to_string(), 57 | DataType::Iterable => "iterable".to_string(), 58 | DataType::StaticReference => "static".to_string(), 59 | DataType::SelfReference => "self".to_string(), 60 | DataType::ParentReference => "parent".to_string(), 61 | DataType::Void => "void".to_string(), 62 | DataType::Never => "never".to_string(), 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/interface.rs: -------------------------------------------------------------------------------- 1 | use crate::attribute::AttributeGroup; 2 | use crate::comment::Document; 3 | use crate::method::Method; 4 | use crate::Generator; 5 | use crate::Indentation; 6 | 7 | #[derive(Debug)] 8 | pub struct Interface { 9 | pub documentation: Option, 10 | pub attributes: Vec, 11 | pub name: String, 12 | pub extends: Option, 13 | pub methods: Vec, 14 | } 15 | 16 | impl Interface { 17 | pub fn new(name: T) -> Self { 18 | Self { 19 | documentation: None, 20 | attributes: vec![], 21 | name: name.to_string(), 22 | extends: None, 23 | methods: vec![], 24 | } 25 | } 26 | 27 | pub fn document(mut self, documentation: Document) -> Self { 28 | self.documentation = Some(documentation); 29 | 30 | self 31 | } 32 | 33 | pub fn attributes(mut self, attributes: AttributeGroup) -> Self { 34 | self.attributes.push(attributes); 35 | 36 | self 37 | } 38 | 39 | pub fn extends(mut self, extends: T) -> Self { 40 | self.extends = Some(extends.to_string()); 41 | 42 | self 43 | } 44 | 45 | pub fn method(mut self, method: Method) -> Self { 46 | self.methods.push(method.public()); 47 | 48 | self 49 | } 50 | } 51 | 52 | impl Generator for Interface { 53 | fn generate(&self, indentation: Indentation, level: usize) -> String { 54 | let mut code = String::new(); 55 | 56 | if let Some(documentation) = &self.documentation { 57 | code.push_str(&documentation.generate(indentation, level)); 58 | } 59 | 60 | for attribute in &self.attributes { 61 | code.push_str(&attribute.generate(indentation, level)); 62 | } 63 | 64 | code.push_str(&format!("interface {}", self.name)); 65 | 66 | if let Some(extends) = &self.extends { 67 | code.push_str(&format!(" extends {}", extends)); 68 | } 69 | 70 | code.push_str("\n{\n"); 71 | 72 | code.push_str(self.methods.generate(indentation, level + 1).as_str()); 73 | 74 | code = code.trim_end().to_string(); 75 | code.push_str("\n}\n"); 76 | 77 | code 78 | } 79 | } 80 | 81 | impl Generator for Vec { 82 | fn generate(&self, indentation: Indentation, level: usize) -> String { 83 | let mut code = String::new(); 84 | if self.is_empty() { 85 | return code; 86 | } 87 | 88 | for interface in self { 89 | code.push_str(interface.generate(indentation, level).as_str()); 90 | code.push('\n'); 91 | } 92 | 93 | code 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/comment.rs: -------------------------------------------------------------------------------- 1 | use crate::Generator; 2 | use crate::Indentation; 3 | 4 | #[derive(Debug)] 5 | pub enum Element { 6 | Tag(String, String), 7 | Text(String), 8 | EmptyLine, 9 | } 10 | 11 | #[derive(Debug)] 12 | pub struct Document { 13 | pub elements: Vec, 14 | } 15 | 16 | impl Document { 17 | pub fn new() -> Self { 18 | Self { elements: vec![] } 19 | } 20 | 21 | pub fn add_element(mut self, element: Element) -> Self { 22 | self.elements.push(element); 23 | 24 | self 25 | } 26 | 27 | pub fn empty_line(mut self) -> Self { 28 | self.elements.push(Element::EmptyLine); 29 | 30 | self 31 | } 32 | 33 | pub fn tag(mut self, tag: T, description: T) -> Self { 34 | self.elements 35 | .push(Element::Tag(tag.to_string(), description.to_string())); 36 | 37 | self 38 | } 39 | 40 | pub fn simple_tag(mut self, tag: T) -> Self { 41 | self.elements 42 | .push(Element::Tag(tag.to_string(), String::new())); 43 | 44 | self 45 | } 46 | 47 | pub fn text(mut self, text: T) -> Self { 48 | self.elements 49 | .push(Element::Text(format!("{}\n", text.to_string()))); 50 | 51 | self 52 | } 53 | } 54 | 55 | impl Generator for Document { 56 | fn generate(&self, indentation: Indentation, level: usize) -> String { 57 | let mut code = String::new(); 58 | 59 | code.push_str(&format!("{}/**\n", indentation.value(level))); 60 | 61 | for element in &self.elements { 62 | let element = element.generate(indentation, level); 63 | if element.is_empty() { 64 | code.push_str(&format!("{} *\n", indentation.value(level))); 65 | 66 | continue; 67 | } 68 | 69 | for line in element.lines() { 70 | code.push_str(&format!( 71 | "{} * {}\n", 72 | indentation.value(level), 73 | line.trim_end() 74 | )); 75 | } 76 | } 77 | 78 | code.push_str(&format!("{} */\n", indentation.value(level))); 79 | 80 | code 81 | } 82 | } 83 | 84 | impl Generator for Element { 85 | fn generate(&self, _: Indentation, _: usize) -> String { 86 | match self { 87 | Element::Tag(tag, description) => { 88 | if description.is_empty() { 89 | format!("@{}", tag) 90 | } else { 91 | format!("@{} {}", tag, description) 92 | } 93 | } 94 | Element::Text(text) => text.to_string(), 95 | Element::EmptyLine => String::new(), 96 | } 97 | } 98 | } 99 | 100 | impl Default for Document { 101 | fn default() -> Self { 102 | Self::new() 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/function.rs: -------------------------------------------------------------------------------- 1 | use crate::attribute::AttributeGroup; 2 | use crate::body::Body; 3 | use crate::comment::Document; 4 | use crate::data_type::DataType; 5 | use crate::parameter::Parameter; 6 | use crate::Generator; 7 | use crate::Indentation; 8 | 9 | #[derive(Debug)] 10 | pub struct Function { 11 | pub documentation: Option, 12 | pub attributes: Vec, 13 | pub name: String, 14 | pub parameters: Vec, 15 | pub return_type: Option, 16 | pub body: Body, 17 | } 18 | 19 | impl Function { 20 | pub fn new(name: T) -> Self { 21 | Self { 22 | name: name.to_string(), 23 | parameters: vec![], 24 | return_type: None, 25 | body: Body::new().with_semicolon_for_empty(false), 26 | attributes: vec![], 27 | documentation: None, 28 | } 29 | } 30 | 31 | pub fn document(mut self, documentation: Document) -> Self { 32 | self.documentation = Some(documentation); 33 | 34 | self 35 | } 36 | 37 | pub fn attributes(mut self, attributes: AttributeGroup) -> Self { 38 | self.attributes.push(attributes); 39 | 40 | self 41 | } 42 | 43 | pub fn parameter(mut self, parameter: Parameter) -> Self { 44 | self.parameters.push(parameter); 45 | 46 | self 47 | } 48 | 49 | pub fn returns(mut self, return_type: DataType) -> Self { 50 | self.return_type = Some(return_type); 51 | 52 | self 53 | } 54 | 55 | pub fn body>(mut self, body: T) -> Self { 56 | self.body = body.into(); 57 | self.body = self.body.with_semicolon_for_empty(false); 58 | 59 | self 60 | } 61 | } 62 | 63 | impl Generator for Function { 64 | fn generate(&self, indentation: Indentation, level: usize) -> String { 65 | let mut code = String::new(); 66 | 67 | if let Some(document) = &self.documentation { 68 | code.push_str(document.generate(indentation, level).as_str()); 69 | } 70 | 71 | for attribute in &self.attributes { 72 | code.push_str(attribute.generate(indentation, level).as_str()); 73 | } 74 | 75 | code.push_str(format!("function {}", self.name).as_str()); 76 | code.push_str(self.parameters.generate(indentation, level).as_str()); 77 | 78 | if let Some(return_type) = &self.return_type { 79 | code.push_str(format!(": {}", return_type.generate(indentation, level)).as_str()); 80 | } 81 | 82 | code.push_str(self.body.generate(indentation, level).as_str()); 83 | 84 | code 85 | } 86 | } 87 | 88 | impl Generator for Vec { 89 | fn generate(&self, indentation: Indentation, level: usize) -> String { 90 | let mut code = String::new(); 91 | if self.is_empty() { 92 | return code; 93 | } 94 | 95 | for function in self { 96 | code.push_str(function.generate(indentation, level).as_str()); 97 | code.push('\n'); 98 | } 99 | 100 | code 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/enum_case.rs: -------------------------------------------------------------------------------- 1 | use crate::attribute::AttributeGroup; 2 | use crate::comment::Document; 3 | use crate::literal::Value; 4 | use crate::Generator; 5 | use crate::Indentation; 6 | 7 | #[derive(Debug)] 8 | pub struct EnumCase { 9 | pub documentation: Option, 10 | pub attributes: Vec, 11 | pub name: String, 12 | pub value: Option, 13 | } 14 | 15 | impl EnumCase { 16 | pub fn new(name: T) -> Self { 17 | Self { 18 | documentation: None, 19 | attributes: vec![], 20 | name: name.to_string(), 21 | value: None, 22 | } 23 | } 24 | 25 | pub fn document(mut self, documentation: Document) -> Self { 26 | self.documentation = Some(documentation); 27 | 28 | self 29 | } 30 | 31 | pub fn attributes(mut self, attributes: AttributeGroup) -> Self { 32 | self.attributes.push(attributes); 33 | 34 | self 35 | } 36 | 37 | pub fn valued>(mut self, value: T) -> Self { 38 | self.value = Some(value.into()); 39 | 40 | self 41 | } 42 | } 43 | 44 | impl Generator for EnumCase { 45 | fn generate(&self, indentation: Indentation, level: usize) -> String { 46 | let mut code = String::new(); 47 | 48 | if let Some(document) = &self.documentation { 49 | code.push_str(&document.generate(indentation, level)); 50 | } 51 | 52 | for attribute in &self.attributes { 53 | code.push_str(&attribute.generate(indentation, level)); 54 | } 55 | 56 | code.push_str(&indentation.indent(format!("case {}", self.name), level)); 57 | 58 | if let Some(value) = &self.value { 59 | code.push_str(&format!(" = {};\n", value.generate(indentation, level))); 60 | } else { 61 | code.push_str(";\n"); 62 | } 63 | 64 | code 65 | } 66 | } 67 | 68 | impl Generator for Vec { 69 | fn generate(&self, indentation: Indentation, level: usize) -> String { 70 | let mut code = String::new(); 71 | if self.is_empty() { 72 | return code; 73 | } 74 | 75 | for case in self { 76 | code.push_str(case.generate(indentation, level).as_str()); 77 | code.push('\n'); 78 | } 79 | 80 | code 81 | } 82 | } 83 | 84 | impl> From<(T, Tv)> for EnumCase { 85 | fn from((name, value): (T, Tv)) -> Self { 86 | Self { 87 | documentation: None, 88 | attributes: vec![], 89 | name: name.to_string(), 90 | value: Some(value.into()), 91 | } 92 | } 93 | } 94 | 95 | impl From for EnumCase { 96 | fn from(name: String) -> Self { 97 | Self { 98 | documentation: None, 99 | attributes: vec![], 100 | name, 101 | value: None, 102 | } 103 | } 104 | } 105 | 106 | impl From<&str> for EnumCase { 107 | fn from(name: &str) -> Self { 108 | Self { 109 | documentation: None, 110 | attributes: vec![], 111 | name: name.to_string(), 112 | value: None, 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /examples/class.rs: -------------------------------------------------------------------------------- 1 | use php_codegen::class::Class; 2 | use php_codegen::data_type::DataType; 3 | use php_codegen::file::File; 4 | use php_codegen::literal::Value; 5 | use php_codegen::modifiers::VisibilityModifier; 6 | use php_codegen::property::Property; 7 | use php_codegen::property::PropertyHook; 8 | use php_codegen::property::PropertySetHookParameter; 9 | 10 | fn main() { 11 | let file = File::new() 12 | .namespaced("App") 13 | .declare("strict_types", 1) 14 | // A class that uses property hooks 15 | .class( 16 | Class::new("SimpleUser") 17 | .property( 18 | Property::new("firstName") 19 | .typed(DataType::String) 20 | .visibility(VisibilityModifier::Private) 21 | .default(Value::String("Jane".to_string())), 22 | ) 23 | .property( 24 | Property::new("lastName") 25 | .typed(DataType::String) 26 | .visibility(VisibilityModifier::Private) 27 | .default(Value::String("Doe".to_string())), 28 | ) 29 | .property( 30 | Property::new("fullname") 31 | .typed(DataType::String) 32 | .visibility(VisibilityModifier::Public) 33 | .hook(PropertyHook::Get( 34 | false, 35 | vec!["return $this->firstName . ' ' . $this->lastName;"].into(), 36 | )) 37 | .hook(PropertyHook::Set( 38 | Some( 39 | PropertySetHookParameter::new("$fullname").typed(DataType::String), 40 | ), 41 | vec![ 42 | "[$first, $last] = explode(' ', $fullname);", 43 | "$this->firstName = $first;", 44 | "$this->lastName = $last;", 45 | ] 46 | .into(), 47 | )), 48 | ), 49 | ) 50 | .class( 51 | Class::new("SimpleUser2") 52 | .property( 53 | Property::new("firstName") 54 | .typed(DataType::String) 55 | .visibility(VisibilityModifier::Private) 56 | .default(Value::String("Jane".to_string())), 57 | ) 58 | .property( 59 | Property::new("lastName") 60 | .typed(DataType::String) 61 | .visibility(VisibilityModifier::Private) 62 | .default(Value::String("Doe".to_string())), 63 | ) 64 | .property( 65 | Property::new("fullname") 66 | .typed(DataType::String) 67 | .visibility(VisibilityModifier::Public) 68 | .hook(PropertyHook::Get( 69 | false, 70 | vec!["return $this->firstName . ' ' . $this->lastName;"].into(), 71 | )), 72 | ), 73 | ); 74 | 75 | print!("{file}"); 76 | } 77 | -------------------------------------------------------------------------------- /src/trait.rs: -------------------------------------------------------------------------------- 1 | use crate::attribute::AttributeGroup; 2 | use crate::comment::Document; 3 | use crate::constant::ClassConstant; 4 | use crate::method::Method; 5 | use crate::property::Property; 6 | use crate::usage::Usage; 7 | use crate::Generator; 8 | use crate::Indentation; 9 | 10 | #[derive(Debug)] 11 | pub struct Trait { 12 | pub documentation: Option, 13 | pub attributes: Vec, 14 | pub name: String, 15 | pub usages: Vec, 16 | pub constants: Vec, 17 | pub properties: Vec, 18 | pub methods: Vec, 19 | } 20 | 21 | impl Trait { 22 | pub fn new(name: T) -> Self { 23 | Self { 24 | documentation: None, 25 | attributes: vec![], 26 | name: name.to_string(), 27 | usages: vec![], 28 | constants: vec![], 29 | properties: vec![], 30 | methods: vec![], 31 | } 32 | } 33 | 34 | pub fn document(mut self, documentation: Document) -> Self { 35 | self.documentation = Some(documentation); 36 | 37 | self 38 | } 39 | 40 | pub fn attributes(mut self, attributes: AttributeGroup) -> Self { 41 | self.attributes.push(attributes); 42 | 43 | self 44 | } 45 | 46 | pub fn using>(mut self, usage: T) -> Self { 47 | self.usages.push(usage.into()); 48 | 49 | self 50 | } 51 | 52 | pub fn constant>(mut self, constant: T) -> Self { 53 | self.constants.push(constant.into()); 54 | 55 | self 56 | } 57 | 58 | pub fn property(mut self, property: Property) -> Self { 59 | self.properties.push(property); 60 | 61 | self 62 | } 63 | 64 | pub fn method(mut self, method: Method) -> Self { 65 | self.methods.push(method); 66 | 67 | self 68 | } 69 | } 70 | 71 | impl Generator for Trait { 72 | fn generate(&self, indentation: Indentation, level: usize) -> String { 73 | let mut code = String::new(); 74 | 75 | if let Some(documentation) = &self.documentation { 76 | code.push_str(documentation.generate(indentation, level).as_str()); 77 | } 78 | 79 | for attribute in &self.attributes { 80 | code.push_str(attribute.generate(indentation, level).as_str()); 81 | } 82 | 83 | code.push_str(format!("trait {}", self.name).as_str()); 84 | 85 | code.push_str("\n{\n"); 86 | 87 | code.push_str(self.usages.generate(indentation, level + 1).as_str()); 88 | code.push_str(self.constants.generate(indentation, level + 1).as_str()); 89 | code.push_str(self.properties.generate(indentation, level + 1).as_str()); 90 | code.push_str(self.methods.generate(indentation, level + 1).as_str()); 91 | 92 | code = code.trim_end().to_string(); 93 | code.push_str("\n}\n"); 94 | 95 | code 96 | } 97 | } 98 | 99 | impl Generator for Vec { 100 | fn generate(&self, indentation: Indentation, level: usize) -> String { 101 | let mut code = String::new(); 102 | if self.is_empty() { 103 | return code; 104 | } 105 | 106 | for r#trait in self { 107 | code.push_str(r#trait.generate(indentation, level).as_str()); 108 | code.push('\n'); 109 | } 110 | 111 | code 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/literal.rs: -------------------------------------------------------------------------------- 1 | use crate::Generator; 2 | use crate::Indentation; 3 | 4 | #[derive(Debug)] 5 | pub enum Value { 6 | Null, 7 | True, 8 | False, 9 | Float(f64), 10 | Integer(i64), 11 | String(String), 12 | Literal(String), 13 | List(Vec), 14 | HashMap(Vec<(Value, Value)>), 15 | } 16 | 17 | impl From for Value { 18 | fn from(value: bool) -> Self { 19 | if value { 20 | Value::True 21 | } else { 22 | Value::False 23 | } 24 | } 25 | } 26 | 27 | impl From for Value { 28 | fn from(value: f64) -> Self { 29 | Value::Float(value) 30 | } 31 | } 32 | 33 | impl From for Value { 34 | fn from(value: i64) -> Self { 35 | Value::Integer(value) 36 | } 37 | } 38 | 39 | impl From for Value { 40 | fn from(value: String) -> Self { 41 | Value::String(value) 42 | } 43 | } 44 | 45 | impl From<&str> for Value { 46 | fn from(value: &str) -> Self { 47 | Value::String(value.to_string()) 48 | } 49 | } 50 | 51 | impl From<()> for Value { 52 | fn from(_: ()) -> Self { 53 | Value::Null 54 | } 55 | } 56 | 57 | impl> From> for Value { 58 | fn from(value: Vec) -> Self { 59 | Value::List(value.into_iter().map(|value| value.into()).collect()) 60 | } 61 | } 62 | 63 | impl> From> for Value { 64 | fn from(value: Vec<(T, T)>) -> Self { 65 | Value::HashMap( 66 | value 67 | .into_iter() 68 | .map(|(key, value)| (key.into(), value.into())) 69 | .collect(), 70 | ) 71 | } 72 | } 73 | 74 | impl Generator for Value { 75 | fn generate(&self, _identation: Indentation, _level: usize) -> String { 76 | match self { 77 | Value::Null => "null".to_string(), 78 | Value::True => "true".to_string(), 79 | Value::False => "false".to_string(), 80 | Value::Integer(value) => value.to_string(), 81 | Value::String(value) => format!("\"{}\"", value), 82 | Value::Float(value) => value.to_string(), 83 | Value::Literal(value) => value.to_string(), 84 | Value::List(values) => { 85 | let mut result = String::new(); 86 | 87 | result.push('['); 88 | result.push_str( 89 | &values 90 | .iter() 91 | .map(|value| value.generate(_identation, _level)) 92 | .collect::>() 93 | .join(", "), 94 | ); 95 | result.push(']'); 96 | 97 | result 98 | } 99 | Value::HashMap(values) => { 100 | let mut result = String::new(); 101 | 102 | result.push('['); 103 | result.push_str( 104 | &values 105 | .iter() 106 | .map(|(key, value)| { 107 | format!( 108 | "{} => {}", 109 | key.generate(_identation, _level), 110 | value.generate(_identation, _level) 111 | ) 112 | }) 113 | .collect::>() 114 | .join(", "), 115 | ); 116 | result.push(']'); 117 | 118 | result 119 | } 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/parameter.rs: -------------------------------------------------------------------------------- 1 | use crate::attribute::AttributeGroup; 2 | use crate::data_type::DataType; 3 | use crate::literal::Value; 4 | use crate::modifiers::Modifier; 5 | use crate::modifiers::VisibilityModifier; 6 | use crate::Generator; 7 | use crate::Indentation; 8 | 9 | #[derive(Debug)] 10 | pub struct Parameter { 11 | pub attributes: Vec, 12 | pub name: String, 13 | pub data_type: Option, 14 | pub default: Option, 15 | pub modifiers: Vec, 16 | pub visibility: Option, 17 | pub variadic: bool, 18 | } 19 | 20 | impl Parameter { 21 | pub fn new(name: T) -> Self { 22 | Self { 23 | name: name.to_string(), 24 | data_type: None, 25 | default: None, 26 | modifiers: vec![], 27 | attributes: vec![], 28 | visibility: None, 29 | variadic: false, 30 | } 31 | } 32 | 33 | pub fn attributes(mut self, attributes: AttributeGroup) -> Self { 34 | self.attributes.push(attributes); 35 | 36 | self 37 | } 38 | 39 | pub fn typed(mut self, data_type: DataType) -> Self { 40 | self.data_type = Some(data_type); 41 | 42 | self 43 | } 44 | 45 | pub fn variadic(mut self) -> Self { 46 | self.variadic = true; 47 | 48 | self 49 | } 50 | 51 | pub fn default>(mut self, default: T) -> Self { 52 | self.default = Some(default.into()); 53 | 54 | self 55 | } 56 | 57 | pub fn modifier(mut self, modifier: Modifier) -> Self { 58 | self.modifiers.push(modifier); 59 | 60 | self 61 | } 62 | 63 | pub fn public(mut self) -> Self { 64 | self.visibility = Some(VisibilityModifier::Public); 65 | 66 | self 67 | } 68 | 69 | pub fn protected(mut self) -> Self { 70 | self.visibility = Some(VisibilityModifier::Protected); 71 | 72 | self 73 | } 74 | 75 | pub fn private(mut self) -> Self { 76 | self.visibility = Some(VisibilityModifier::Private); 77 | 78 | self 79 | } 80 | 81 | pub fn visibility(mut self, visibility: VisibilityModifier) -> Self { 82 | self.visibility = Some(visibility); 83 | 84 | self 85 | } 86 | } 87 | 88 | impl Generator for Parameter { 89 | fn generate(&self, indentation: Indentation, level: usize) -> String { 90 | let mut code = String::new(); 91 | 92 | for attribute in &self.attributes { 93 | code.push_str(&attribute.generate(indentation, level)); 94 | } 95 | 96 | code.push_str(&indentation.value(level)); 97 | 98 | if let Some(visibility) = &self.visibility { 99 | code.push_str(&format!("{} ", visibility.generate(indentation, level))); 100 | } 101 | 102 | for modifier in &self.modifiers { 103 | code.push_str(&format!("{} ", modifier.generate(indentation, level))); 104 | } 105 | 106 | if let Some(data_type) = &self.data_type { 107 | code.push_str(&format!("{} ", data_type.generate(indentation, level))); 108 | } 109 | 110 | if self.variadic { 111 | code.push_str("..."); 112 | } 113 | 114 | code.push_str(&format!("${}", &self.name)); 115 | 116 | if let Some(default) = &self.default { 117 | code.push_str(&format!(" = {}", default.generate(indentation, level))); 118 | } 119 | 120 | code 121 | } 122 | } 123 | 124 | impl Generator for Vec { 125 | fn generate(&self, indentation: Indentation, level: usize) -> String { 126 | let mut code = String::new(); 127 | 128 | if self.is_empty() { 129 | code.push_str("()"); 130 | } else { 131 | code.push_str("(\n"); 132 | code.push_str( 133 | &self 134 | .iter() 135 | .map(|parameter| parameter.generate(indentation, level + 1)) 136 | .collect::>() 137 | .join(",\n"), 138 | ); 139 | 140 | code.push_str(",\n"); 141 | code.push_str(&indentation.indent(")", level)); 142 | } 143 | 144 | code 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/body.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Debug; 2 | 3 | use crate::Generator; 4 | use crate::Indentation; 5 | 6 | pub struct Body { 7 | pub factory: Option String>>, 8 | pub semicolon_for_empty: bool, 9 | } 10 | 11 | impl Body { 12 | pub fn new() -> Self { 13 | Self { 14 | factory: None, 15 | semicolon_for_empty: true, 16 | } 17 | } 18 | 19 | pub fn with_factory String + 'static>(factory: T) -> Self { 20 | Self { 21 | factory: Some(Box::new(factory)), 22 | semicolon_for_empty: true, 23 | } 24 | } 25 | 26 | pub fn with_semicolon_for_empty(mut self, semicolon_for_empty: bool) -> Self { 27 | self.semicolon_for_empty = semicolon_for_empty; 28 | 29 | self 30 | } 31 | } 32 | 33 | impl Debug for Body { 34 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 35 | f.debug_struct("Body") 36 | .field("factory:is-some", &self.factory.is_some()) 37 | .field("semicolon_for_empty", &self.semicolon_for_empty) 38 | .finish() 39 | } 40 | } 41 | 42 | impl Generator for Body { 43 | fn generate(&self, indentation: Indentation, level: usize) -> String { 44 | match self.factory { 45 | Some(ref factory) => { 46 | let mut code = String::new(); 47 | 48 | code.push_str(" {"); 49 | code.push('\n'); 50 | code.push_str(&factory(indentation, level + 1)); 51 | code.push('\n'); 52 | code.push_str(&indentation.indent("}", level)); 53 | code.push('\n'); 54 | 55 | code 56 | } 57 | None => { 58 | let mut code = String::new(); 59 | 60 | if self.semicolon_for_empty { 61 | code.push(';'); 62 | } else { 63 | code.push_str(" {}"); 64 | } 65 | 66 | code.push('\n'); 67 | 68 | code 69 | } 70 | } 71 | } 72 | } 73 | 74 | impl From> for Body { 75 | fn from(body: Vec) -> Self { 76 | let body = body 77 | .iter() 78 | .map(|line| line.to_string()) 79 | .collect::>(); 80 | 81 | Self { 82 | factory: Some(Box::new(move |indentation, level| { 83 | let body = body.clone(); 84 | 85 | body.iter() 86 | .map(|line| indentation.indent(line.to_string(), level)) 87 | .collect::>() 88 | .join("\n") 89 | })), 90 | semicolon_for_empty: true, 91 | } 92 | } 93 | } 94 | 95 | impl From for Body { 96 | fn from(body: String) -> Self { 97 | Self { 98 | factory: Some(Box::new(move |indentation, level| { 99 | let body = body.clone(); 100 | 101 | indentation.indent(body, level) 102 | })), 103 | semicolon_for_empty: true, 104 | } 105 | } 106 | } 107 | 108 | impl From<&str> for Body { 109 | fn from(body: &str) -> Self { 110 | body.to_string().into() 111 | } 112 | } 113 | 114 | impl String + 'static> From for Body { 115 | fn from(factory: T) -> Self { 116 | Self { 117 | factory: Some(Box::new(factory)), 118 | semicolon_for_empty: true, 119 | } 120 | } 121 | } 122 | 123 | impl From String>>> for Body { 124 | fn from(factory: Option String>>) -> Self { 125 | Self { 126 | factory, 127 | semicolon_for_empty: true, 128 | } 129 | } 130 | } 131 | 132 | impl From> for Body { 133 | fn from(body: Option) -> Self { 134 | match body { 135 | Some(body) => body.into(), 136 | None => Self { 137 | factory: None, 138 | semicolon_for_empty: true, 139 | }, 140 | } 141 | } 142 | } 143 | 144 | impl Default for Body { 145 | fn default() -> Self { 146 | Self { 147 | factory: None, 148 | semicolon_for_empty: true, 149 | } 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/method.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Debug; 2 | 3 | use crate::attribute::AttributeGroup; 4 | use crate::body::Body; 5 | use crate::comment::Document; 6 | use crate::data_type::DataType; 7 | use crate::modifiers::Modifier; 8 | use crate::modifiers::VisibilityModifier; 9 | use crate::parameter::Parameter; 10 | use crate::Generator; 11 | use crate::Indentation; 12 | 13 | #[derive(Debug)] 14 | pub struct Method { 15 | pub documentation: Option, 16 | pub attributes: Vec, 17 | pub name: String, 18 | pub parameters: Vec, 19 | pub return_type: Option, 20 | pub body: Body, 21 | pub modifiers: Vec, 22 | pub visibility: Option, 23 | } 24 | 25 | impl Method { 26 | pub fn new(name: T) -> Self { 27 | Self { 28 | name: name.to_string(), 29 | parameters: vec![], 30 | return_type: None, 31 | body: Body::default(), 32 | modifiers: vec![], 33 | attributes: vec![], 34 | documentation: None, 35 | visibility: None, 36 | } 37 | } 38 | 39 | pub fn document(mut self, documentation: Document) -> Self { 40 | self.documentation = Some(documentation); 41 | 42 | self 43 | } 44 | 45 | pub fn attributes(mut self, attributes: AttributeGroup) -> Self { 46 | self.attributes.push(attributes); 47 | 48 | self 49 | } 50 | 51 | pub fn public(mut self) -> Self { 52 | self.visibility = Some(VisibilityModifier::Public); 53 | 54 | self 55 | } 56 | 57 | pub fn protected(mut self) -> Self { 58 | self.visibility = Some(VisibilityModifier::Protected); 59 | 60 | self 61 | } 62 | 63 | pub fn private(mut self) -> Self { 64 | self.visibility = Some(VisibilityModifier::Private); 65 | 66 | self 67 | } 68 | 69 | pub fn visibility(mut self, visibility: VisibilityModifier) -> Self { 70 | self.visibility = Some(visibility); 71 | 72 | self 73 | } 74 | 75 | pub fn modifier(mut self, modifier: Modifier) -> Self { 76 | self.modifiers.push(modifier); 77 | 78 | self 79 | } 80 | 81 | pub fn parameter(mut self, parameter: Parameter) -> Self { 82 | self.parameters.push(parameter); 83 | 84 | self 85 | } 86 | 87 | pub fn returns(mut self, return_type: DataType) -> Self { 88 | self.return_type = Some(return_type); 89 | 90 | self 91 | } 92 | 93 | pub fn body>(mut self, body: T) -> Self { 94 | self.body = body.into().with_semicolon_for_empty(true); 95 | 96 | self 97 | } 98 | } 99 | 100 | impl Generator for Method { 101 | fn generate(&self, indentation: Indentation, level: usize) -> String { 102 | let mut code = String::new(); 103 | 104 | if let Some(document) = &self.documentation { 105 | code.push_str(&document.generate(indentation, level)); 106 | } 107 | 108 | for attribute in &self.attributes { 109 | code.push_str(&attribute.generate(indentation, level)); 110 | } 111 | 112 | code.push_str(&indentation.value(level)); 113 | if let Some(visibility) = &self.visibility { 114 | code.push_str(format!("{} ", visibility.generate(indentation, level)).as_str()); 115 | } 116 | 117 | if !self.modifiers.is_empty() { 118 | code.push_str( 119 | &self 120 | .modifiers 121 | .iter() 122 | .map(|modifier| modifier.generate(indentation, level)) 123 | .collect::>() 124 | .join(" "), 125 | ); 126 | 127 | code.push(' '); 128 | } 129 | 130 | code.push_str(format!("function {}", self.name).as_str()); 131 | code.push_str(&self.parameters.generate(indentation, level)); 132 | 133 | if let Some(return_type) = &self.return_type { 134 | code.push_str(format!(": {}", return_type.generate(indentation, level)).as_str()); 135 | } 136 | 137 | code.push_str(&self.body.generate(indentation, level)); 138 | 139 | code 140 | } 141 | } 142 | 143 | impl Generator for Vec { 144 | fn generate(&self, indentation: Indentation, level: usize) -> String { 145 | let mut code = String::new(); 146 | if self.is_empty() { 147 | return code; 148 | } 149 | 150 | for method in self { 151 | code.push_str(method.generate(indentation, level).as_str()); 152 | code.push('\n'); 153 | } 154 | 155 | code 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/class.rs: -------------------------------------------------------------------------------- 1 | use crate::attribute::AttributeGroup; 2 | use crate::comment::Document; 3 | use crate::constant::ClassConstant; 4 | use crate::method::Method; 5 | use crate::modifiers::Modifier; 6 | use crate::property::Property; 7 | use crate::usage::Usage; 8 | use crate::Generator; 9 | use crate::Indentation; 10 | 11 | #[derive(Debug)] 12 | pub struct Class { 13 | pub documentation: Option, 14 | pub attributes: Vec, 15 | pub modifiers: Vec, 16 | pub name: String, 17 | pub extends: Option, 18 | pub implements: Vec, 19 | pub usages: Vec, 20 | pub constants: Vec, 21 | pub properties: Vec, 22 | pub methods: Vec, 23 | } 24 | 25 | impl Class { 26 | pub fn new(name: T) -> Self { 27 | Self { 28 | documentation: None, 29 | attributes: vec![], 30 | modifiers: vec![], 31 | name: name.to_string(), 32 | extends: None, 33 | implements: vec![], 34 | usages: vec![], 35 | constants: vec![], 36 | properties: vec![], 37 | methods: vec![], 38 | } 39 | } 40 | 41 | pub fn document(mut self, documentation: Document) -> Self { 42 | self.documentation = Some(documentation); 43 | 44 | self 45 | } 46 | 47 | pub fn attributes(mut self, attributes: AttributeGroup) -> Self { 48 | self.attributes.push(attributes); 49 | 50 | self 51 | } 52 | 53 | pub fn modifier(mut self, modifier: Modifier) -> Self { 54 | self.modifiers.push(modifier); 55 | 56 | self 57 | } 58 | 59 | pub fn extends(mut self, extends: T) -> Self { 60 | self.extends = Some(extends.to_string()); 61 | 62 | self 63 | } 64 | 65 | pub fn implements(mut self, implements: T) -> Self { 66 | self.implements.push(implements.to_string()); 67 | 68 | self 69 | } 70 | 71 | pub fn using>(mut self, usage: T) -> Self { 72 | self.usages.push(usage.into()); 73 | 74 | self 75 | } 76 | 77 | pub fn constant>(mut self, constant: T) -> Self { 78 | self.constants.push(constant.into()); 79 | 80 | self 81 | } 82 | 83 | pub fn property(mut self, property: Property) -> Self { 84 | self.properties.push(property); 85 | 86 | self 87 | } 88 | 89 | pub fn method(mut self, method: Method) -> Self { 90 | self.methods.push(method); 91 | 92 | self 93 | } 94 | } 95 | 96 | impl Generator for Class { 97 | fn generate(&self, indentation: Indentation, level: usize) -> String { 98 | let mut code = String::new(); 99 | 100 | if let Some(documentation) = &self.documentation { 101 | code.push_str(&documentation.generate(indentation, level)); 102 | } 103 | 104 | for attribute in &self.attributes { 105 | code.push_str(&attribute.generate(indentation, level)); 106 | } 107 | 108 | for modifier in &self.modifiers { 109 | code.push_str(&format!("{} ", modifier.generate(indentation, level))); 110 | } 111 | 112 | code.push_str(&format!("class {}", self.name)); 113 | 114 | if let Some(extends) = &self.extends { 115 | code.push_str(&format!(" extends {}", extends)); 116 | } 117 | 118 | if !self.implements.is_empty() { 119 | code.push_str(&format!( 120 | " implements {}", 121 | self.implements 122 | .iter() 123 | .map(|implement| implement.to_string()) 124 | .collect::>() 125 | .join(", ") 126 | )); 127 | } 128 | 129 | code.push_str("\n{\n"); 130 | 131 | code.push_str(self.usages.generate(indentation, level + 1).as_str()); 132 | code.push_str(self.constants.generate(indentation, level + 1).as_str()); 133 | code.push_str(self.properties.generate(indentation, level + 1).as_str()); 134 | code.push_str(self.methods.generate(indentation, level + 1).as_str()); 135 | 136 | code = code.trim_end().to_string(); 137 | code.push_str("\n}\n"); 138 | 139 | code 140 | } 141 | } 142 | 143 | impl Generator for Vec { 144 | fn generate(&self, indentation: Indentation, level: usize) -> String { 145 | let mut code = String::new(); 146 | if self.is_empty() { 147 | return code; 148 | } 149 | 150 | for class in self { 151 | code.push_str(class.generate(indentation, level).as_str()); 152 | code.push('\n'); 153 | } 154 | 155 | code 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/enum.rs: -------------------------------------------------------------------------------- 1 | use crate::attribute::AttributeGroup; 2 | use crate::comment::Document; 3 | use crate::constant::ClassConstant; 4 | use crate::enum_case::EnumCase; 5 | use crate::method::Method; 6 | use crate::usage::Usage; 7 | use crate::Generator; 8 | use crate::Indentation; 9 | 10 | #[derive(Debug)] 11 | pub enum EnumBackingType { 12 | Int, 13 | String, 14 | } 15 | 16 | #[derive(Debug)] 17 | pub struct Enum { 18 | pub documentation: Option, 19 | pub attributes: Vec, 20 | pub name: String, 21 | pub backing_type: Option, 22 | pub implements: Vec, 23 | pub usages: Vec, 24 | pub constants: Vec, 25 | pub cases: Vec, 26 | pub methods: Vec, 27 | } 28 | 29 | impl Enum { 30 | pub fn new(name: T) -> Self { 31 | Self { 32 | documentation: None, 33 | attributes: vec![], 34 | name: name.to_string(), 35 | backing_type: None, 36 | implements: vec![], 37 | usages: vec![], 38 | constants: vec![], 39 | cases: vec![], 40 | methods: vec![], 41 | } 42 | } 43 | 44 | pub fn document(mut self, documentation: Document) -> Self { 45 | self.documentation = Some(documentation); 46 | 47 | self 48 | } 49 | 50 | pub fn attributes(mut self, attributes: AttributeGroup) -> Self { 51 | self.attributes.push(attributes); 52 | 53 | self 54 | } 55 | 56 | pub fn implements(mut self, implements: T) -> Self { 57 | self.implements.push(implements.to_string()); 58 | 59 | self 60 | } 61 | 62 | pub fn int_backed(mut self) -> Self { 63 | self.backing_type = Some(EnumBackingType::Int); 64 | 65 | self 66 | } 67 | 68 | pub fn string_backed(mut self) -> Self { 69 | self.backing_type = Some(EnumBackingType::String); 70 | 71 | self 72 | } 73 | 74 | pub fn backed(mut self, backing_type: EnumBackingType) -> Self { 75 | self.backing_type = Some(backing_type); 76 | 77 | self 78 | } 79 | 80 | pub fn using>(mut self, usage: T) -> Self { 81 | self.usages.push(usage.into()); 82 | 83 | self 84 | } 85 | 86 | pub fn constant>(mut self, constant: T) -> Self { 87 | self.constants.push(constant.into()); 88 | 89 | self 90 | } 91 | 92 | pub fn case>(mut self, case: T) -> Self { 93 | self.cases.push(case.into()); 94 | 95 | self 96 | } 97 | 98 | pub fn method(mut self, method: Method) -> Self { 99 | self.methods.push(method); 100 | 101 | self 102 | } 103 | } 104 | 105 | impl Generator for Enum { 106 | fn generate(&self, indentation: Indentation, level: usize) -> String { 107 | let mut code = String::new(); 108 | 109 | if let Some(documentation) = &self.documentation { 110 | code.push_str(&documentation.generate(indentation, level)); 111 | } 112 | 113 | for attribute in &self.attributes { 114 | code.push_str(&attribute.generate(indentation, level)); 115 | } 116 | 117 | code.push_str(&format!("enum {}", self.name)); 118 | 119 | if let Some(backing_type) = &self.backing_type { 120 | code.push_str(&format!( 121 | ": {}", 122 | match backing_type { 123 | EnumBackingType::Int => "int", 124 | EnumBackingType::String => "string", 125 | } 126 | )); 127 | } 128 | 129 | if !self.implements.is_empty() { 130 | code.push_str(&format!( 131 | " implements {}", 132 | self.implements 133 | .iter() 134 | .map(|implement| implement.to_string()) 135 | .collect::>() 136 | .join(", ") 137 | )); 138 | } 139 | 140 | code.push_str("\n{\n"); 141 | 142 | code.push_str(self.usages.generate(indentation, level + 1).as_str()); 143 | code.push_str(self.constants.generate(indentation, level + 1).as_str()); 144 | code.push_str(self.cases.generate(indentation, level + 1).as_str()); 145 | code.push_str(self.methods.generate(indentation, level + 1).as_str()); 146 | 147 | code = code.trim_end().to_string(); 148 | code.push_str("\n}\n"); 149 | 150 | code 151 | } 152 | } 153 | 154 | impl Generator for Vec { 155 | fn generate(&self, indentation: Indentation, level: usize) -> String { 156 | let mut code = String::new(); 157 | if self.is_empty() { 158 | return code; 159 | } 160 | 161 | for r#enum in self { 162 | code.push_str(r#enum.generate(indentation, level).as_str()); 163 | code.push('\n'); 164 | } 165 | 166 | code 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /src/file.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | 3 | use crate::class::Class; 4 | use crate::constant::Constant; 5 | use crate::function::Function; 6 | use crate::interface::Interface; 7 | use crate::literal::Value; 8 | use crate::r#enum::Enum; 9 | use crate::r#trait::Trait; 10 | use crate::Generator; 11 | use crate::Indentation; 12 | 13 | #[derive(Debug)] 14 | pub struct File { 15 | pub namespace: Option, 16 | pub declares: Vec<(String, Value)>, 17 | pub uses: Vec, 18 | pub constant_uses: Vec, 19 | pub function_uses: Vec, 20 | pub functions: Vec, 21 | pub constants: Vec, 22 | pub classes: Vec, 23 | pub traits: Vec, 24 | pub enums: Vec, 25 | pub interfaces: Vec, 26 | } 27 | 28 | impl File { 29 | pub fn new() -> Self { 30 | Self { 31 | namespace: None, 32 | declares: vec![], 33 | uses: vec![], 34 | constant_uses: vec![], 35 | function_uses: vec![], 36 | constants: vec![], 37 | functions: vec![], 38 | classes: vec![], 39 | traits: vec![], 40 | enums: vec![], 41 | interfaces: vec![], 42 | } 43 | } 44 | 45 | pub fn declare>(mut self, name: T, value: Tv) -> Self { 46 | self.declares.push((name.to_string(), value.into())); 47 | 48 | self 49 | } 50 | 51 | pub fn namespaced(mut self, namespace: T) -> Self { 52 | self.namespace = Some(namespace.to_string()); 53 | 54 | self 55 | } 56 | 57 | pub fn uses(mut self, symbol: T) -> Self { 58 | self.uses.push(symbol.to_string()); 59 | 60 | self 61 | } 62 | 63 | pub fn uses_constant(mut self, constant: T) -> Self { 64 | self.constant_uses.push(constant.to_string()); 65 | 66 | self 67 | } 68 | 69 | pub fn uses_function(mut self, function: T) -> Self { 70 | self.function_uses.push(function.to_string()); 71 | 72 | self 73 | } 74 | 75 | pub fn constant>(mut self, constant: T) -> Self { 76 | self.constants.push(constant.into()); 77 | 78 | self 79 | } 80 | 81 | pub fn function(mut self, function: Function) -> Self { 82 | self.functions.push(function); 83 | 84 | self 85 | } 86 | 87 | pub fn class(mut self, class: Class) -> Self { 88 | self.classes.push(class); 89 | 90 | self 91 | } 92 | 93 | pub fn r#trait(mut self, r#trait: Trait) -> Self { 94 | self.traits.push(r#trait); 95 | 96 | self 97 | } 98 | 99 | pub fn r#enum(mut self, r#enum: Enum) -> Self { 100 | self.enums.push(r#enum); 101 | 102 | self 103 | } 104 | 105 | pub fn interface(mut self, interface: Interface) -> Self { 106 | self.interfaces.push(interface); 107 | 108 | self 109 | } 110 | } 111 | 112 | impl Generator for File { 113 | fn generate(&self, indentation: Indentation, level: usize) -> String { 114 | let mut code = String::new(); 115 | 116 | code.push_str(") -> std::fmt::Result { 178 | write!(f, "{}", self.generate(Indentation::default(), 0)) 179 | } 180 | } 181 | 182 | impl Default for File { 183 | fn default() -> Self { 184 | Self::new() 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /src/usage.rs: -------------------------------------------------------------------------------- 1 | use crate::modifiers::VisibilityModifier; 2 | use crate::Generator; 3 | use crate::Indentation; 4 | 5 | #[derive(Debug)] 6 | pub struct Usage { 7 | pub traits: Vec, 8 | pub adaptations: Vec, 9 | } 10 | 11 | #[derive(Debug)] 12 | pub enum UsageAdaptation { 13 | Alias { 14 | method: String, 15 | alias: String, 16 | visibility: Option, 17 | }, 18 | Visibility { 19 | method: String, 20 | visibility: VisibilityModifier, 21 | }, 22 | Precedence { 23 | method: String, 24 | insteadof: Vec, 25 | }, 26 | } 27 | 28 | impl Usage { 29 | pub fn new(traits: Vec) -> Self { 30 | Self { 31 | traits, 32 | adaptations: vec![], 33 | } 34 | } 35 | 36 | pub fn with(mut self, r#trait: T) -> Self { 37 | self.traits.push(r#trait.to_string()); 38 | 39 | self 40 | } 41 | 42 | pub fn alias( 43 | mut self, 44 | method: T, 45 | alias: T, 46 | visibility: VisibilityModifier, 47 | ) -> Self { 48 | self.adaptations.push(UsageAdaptation::Alias { 49 | method: method.to_string(), 50 | alias: alias.to_string(), 51 | visibility: Some(visibility), 52 | }); 53 | 54 | self 55 | } 56 | 57 | pub fn rename(mut self, method: T, alias: T) -> Self { 58 | self.adaptations.push(UsageAdaptation::Alias { 59 | method: method.to_string(), 60 | alias: alias.to_string(), 61 | visibility: None, 62 | }); 63 | 64 | self 65 | } 66 | 67 | pub fn public(mut self, method: T) -> Self { 68 | self.adaptations.push(UsageAdaptation::Visibility { 69 | method: method.to_string(), 70 | visibility: VisibilityModifier::Public, 71 | }); 72 | 73 | self 74 | } 75 | 76 | pub fn protected(mut self, method: T) -> Self { 77 | self.adaptations.push(UsageAdaptation::Visibility { 78 | method: method.to_string(), 79 | visibility: VisibilityModifier::Protected, 80 | }); 81 | 82 | self 83 | } 84 | 85 | pub fn private(mut self, method: T) -> Self { 86 | self.adaptations.push(UsageAdaptation::Visibility { 87 | method: method.to_string(), 88 | visibility: VisibilityModifier::Private, 89 | }); 90 | 91 | self 92 | } 93 | 94 | pub fn visibility(mut self, method: T, visibility: VisibilityModifier) -> Self { 95 | self.adaptations.push(UsageAdaptation::Visibility { 96 | method: method.to_string(), 97 | visibility, 98 | }); 99 | 100 | self 101 | } 102 | 103 | pub fn precede(mut self, method: T, insteadof: Vec) -> Self { 104 | self.adaptations.push(UsageAdaptation::Precedence { 105 | method: method.to_string(), 106 | insteadof: insteadof 107 | .into_iter() 108 | .map(|insteadof| insteadof.to_string()) 109 | .collect(), 110 | }); 111 | 112 | self 113 | } 114 | } 115 | 116 | impl Generator for Usage { 117 | fn generate(&self, indentation: Indentation, level: usize) -> String { 118 | let mut code = indentation.indent("use", level); 119 | 120 | code.push(' '); 121 | code.push_str(&self.traits.join(", ")); 122 | 123 | if !self.adaptations.is_empty() { 124 | code.push_str(" {\n"); 125 | for adaptation in &self.adaptations { 126 | let adaptation = match adaptation { 127 | UsageAdaptation::Alias { 128 | method, 129 | alias, 130 | visibility, 131 | } => { 132 | let alias = if let Some(visibility) = visibility { 133 | format!("{} {}", visibility.generate(indentation, level), alias) 134 | } else { 135 | alias.to_string() 136 | }; 137 | 138 | indentation.indent(format!("{} as {}", method, alias), level + 1) 139 | } 140 | UsageAdaptation::Visibility { method, visibility } => indentation.indent( 141 | format!("{} as {}", method, visibility.generate(indentation, level)), 142 | level + 1, 143 | ), 144 | UsageAdaptation::Precedence { method, insteadof } => indentation.indent( 145 | format!("{} insteadof {}", method, insteadof.join(", ")), 146 | level + 1, 147 | ), 148 | }; 149 | 150 | code.push_str(&format!("{};\n", adaptation)); 151 | } 152 | 153 | code.push_str(&format!("{}}}\n", indentation.value(level))); 154 | } else { 155 | code.push_str(";\n"); 156 | } 157 | 158 | code 159 | } 160 | } 161 | 162 | impl Generator for Vec { 163 | fn generate(&self, indentation: Indentation, level: usize) -> String { 164 | let mut code = String::new(); 165 | if self.is_empty() { 166 | return code; 167 | } 168 | 169 | for usage in self { 170 | code.push_str(usage.generate(indentation, level).as_str()); 171 | } 172 | 173 | code.push('\n'); 174 | 175 | code 176 | } 177 | } 178 | 179 | impl From for Usage { 180 | fn from(r#trait: String) -> Self { 181 | Self::new(vec![r#trait]) 182 | } 183 | } 184 | 185 | impl From<&str> for Usage { 186 | fn from(r#trait: &str) -> Self { 187 | Self::new(vec![r#trait.to_string()]) 188 | } 189 | } 190 | 191 | impl From> for Usage { 192 | fn from(traits: Vec) -> Self { 193 | traits 194 | .into_iter() 195 | .fold(Self::new(vec![]), |usage, r#trait| usage.with(r#trait)) 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /src/constant.rs: -------------------------------------------------------------------------------- 1 | use crate::attribute::AttributeGroup; 2 | use crate::comment::Document; 3 | use crate::data_type::DataType; 4 | use crate::literal::Value; 5 | use crate::modifiers::Modifier; 6 | use crate::modifiers::VisibilityModifier; 7 | use crate::Generator; 8 | use crate::Indentation; 9 | 10 | #[derive(Debug)] 11 | pub struct Constant { 12 | pub documentation: Option, 13 | pub name: String, 14 | pub value: Value, 15 | } 16 | 17 | impl Constant { 18 | pub fn new(name: T) -> Self { 19 | Self { 20 | documentation: None, 21 | name: name.to_string(), 22 | value: Value::Null, 23 | } 24 | } 25 | 26 | pub fn document(mut self, documentation: Document) -> Self { 27 | self.documentation = Some(documentation); 28 | 29 | self 30 | } 31 | 32 | pub fn valued>(mut self, value: T) -> Self { 33 | self.value = value.into(); 34 | 35 | self 36 | } 37 | } 38 | 39 | impl Generator for Constant { 40 | fn generate(&self, indentation: Indentation, level: usize) -> String { 41 | let mut output = String::new(); 42 | 43 | if let Some(documentation) = &self.documentation { 44 | output.push_str(&documentation.generate(indentation, level)); 45 | } 46 | 47 | output.push_str(&format!( 48 | "const {} = {};\n", 49 | self.name, 50 | self.value.generate(indentation, level) 51 | )); 52 | 53 | output 54 | } 55 | } 56 | 57 | impl Generator for Vec { 58 | fn generate(&self, indentation: Indentation, level: usize) -> String { 59 | let mut code = String::new(); 60 | if self.is_empty() { 61 | return code; 62 | } 63 | 64 | for constant in self { 65 | code.push_str(constant.generate(indentation, level).as_str()); 66 | code.push('\n'); 67 | } 68 | 69 | code 70 | } 71 | } 72 | 73 | impl> From<(T, Tv)> for Constant { 74 | fn from((name, value): (T, Tv)) -> Self { 75 | Self { 76 | documentation: None, 77 | name: name.to_string(), 78 | value: value.into(), 79 | } 80 | } 81 | } 82 | 83 | #[derive(Debug)] 84 | pub struct ClassConstant { 85 | pub documentation: Option, 86 | pub attributes: Vec, 87 | pub visibility: Option, 88 | pub modifiers: Vec, 89 | pub data_type: Option, 90 | pub name: String, 91 | pub value: Value, 92 | } 93 | 94 | impl ClassConstant { 95 | pub fn new(name: T) -> Self { 96 | Self { 97 | documentation: None, 98 | attributes: vec![], 99 | visibility: None, 100 | modifiers: vec![], 101 | data_type: None, 102 | name: name.to_string(), 103 | value: Value::Null, 104 | } 105 | } 106 | 107 | pub fn document(mut self, documentation: Document) -> Self { 108 | self.documentation = Some(documentation); 109 | 110 | self 111 | } 112 | 113 | pub fn attributes(mut self, attributes: AttributeGroup) -> Self { 114 | self.attributes.push(attributes); 115 | 116 | self 117 | } 118 | 119 | pub fn public(mut self) -> Self { 120 | self.visibility = Some(VisibilityModifier::Public); 121 | 122 | self 123 | } 124 | 125 | pub fn protected(mut self) -> Self { 126 | self.visibility = Some(VisibilityModifier::Protected); 127 | 128 | self 129 | } 130 | 131 | pub fn private(mut self) -> Self { 132 | self.visibility = Some(VisibilityModifier::Private); 133 | 134 | self 135 | } 136 | 137 | pub fn visibility(mut self, visibility: VisibilityModifier) -> Self { 138 | self.visibility = Some(visibility); 139 | 140 | self 141 | } 142 | 143 | pub fn modifier(mut self, modifier: Modifier) -> Self { 144 | self.modifiers.push(modifier); 145 | 146 | self 147 | } 148 | 149 | pub fn typed>(mut self, data_type: T) -> Self { 150 | self.data_type = Some(data_type.into()); 151 | 152 | self 153 | } 154 | 155 | pub fn valued>(mut self, value: T) -> Self { 156 | self.value = value.into(); 157 | 158 | self 159 | } 160 | } 161 | 162 | impl Generator for ClassConstant { 163 | fn generate(&self, indentation: Indentation, level: usize) -> String { 164 | let mut code = String::new(); 165 | 166 | if let Some(document) = &self.documentation { 167 | code.push_str(&document.generate(indentation, level)); 168 | } 169 | 170 | for attribute in &self.attributes { 171 | code.push_str(&attribute.generate(indentation, level)); 172 | } 173 | 174 | code.push_str(&indentation.value(level)); 175 | 176 | if let Some(visibility) = &self.visibility { 177 | code.push_str(&format!("{} ", visibility.generate(indentation, level))); 178 | } 179 | 180 | for modifier in &self.modifiers { 181 | code.push_str(&format!("{} ", modifier.generate(indentation, level))); 182 | } 183 | 184 | if let Some(data_type) = &self.data_type { 185 | code.push_str(&format!( 186 | "const {} {} = {};\n", 187 | data_type.generate(indentation, level), 188 | self.name, 189 | self.value.generate(indentation, level) 190 | )); 191 | } else { 192 | code.push_str(&format!( 193 | "const {} = {};\n", 194 | self.name, 195 | self.value.generate(indentation, level) 196 | )); 197 | } 198 | 199 | code 200 | } 201 | } 202 | 203 | impl Generator for Vec { 204 | fn generate(&self, indentation: Indentation, level: usize) -> String { 205 | let mut code = String::new(); 206 | if self.is_empty() { 207 | return code; 208 | } 209 | 210 | for constant in self { 211 | code.push_str(constant.generate(indentation, level).as_str()); 212 | code.push('\n'); 213 | } 214 | 215 | code 216 | } 217 | } 218 | 219 | impl> From<(T, Tv)> for ClassConstant { 220 | fn from((name, value): (T, Tv)) -> Self { 221 | Self { 222 | documentation: None, 223 | attributes: vec![], 224 | visibility: None, 225 | modifiers: vec![], 226 | data_type: None, 227 | name: name.to_string(), 228 | value: value.into(), 229 | } 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /tests/complete.php: -------------------------------------------------------------------------------- 1 | is_float($arg) ? number_format($arg, 2) : $arg, 60 | array_filter($args, static fn ($arg) => $arg !== null) 61 | )); 62 | } 63 | 64 | /** 65 | * This is an example class. 66 | * 67 | * @immutable 68 | */ 69 | abstract class Example extends Foo\Bar\Baz implements Foo\Bar\BazInterface 70 | { 71 | use A; 72 | use B, C; 73 | use D, E, F, G { 74 | E::bar as baz; 75 | D::foo as public bar; 76 | E::qux as public; 77 | D::format as protected; 78 | D::d as private; 79 | D::drop insteadof E; 80 | G::something insteadof E, F, D; 81 | E::e as protected; 82 | } 83 | 84 | final const A = "Hello World!"; 85 | 86 | protected const B = null; 87 | 88 | private const C = 1; 89 | 90 | public const D = false; 91 | 92 | public const bool E = false; 93 | 94 | private string $foo; 95 | protected string $bar; 96 | public string|int $baz = "Hello World!"; 97 | /** 98 | * This is a simple hello function. 99 | * 100 | * @param non-empty-string $firstname 101 | * 102 | * @return string 103 | * 104 | * @pure 105 | */ 106 | #[Qux(foo: 1, bar: 2), Qux(foo: 1, bar: 2)] 107 | function hello( 108 | string $firstname, 109 | string $lastname = Qux::Foo, 110 | ): string { 111 | return 'Hello ' . $firstname . ' ' . $lastname . '!'; 112 | } 113 | 114 | /** 115 | * This is a simple x function. 116 | * 117 | * @pure 118 | */ 119 | #[Foo(foo: 1, bar: 2), Bar(foo: 1, bar: 2)] 120 | #[Baz, Qux] 121 | public function x(): mixed { 122 | return 'Hello!'; 123 | } 124 | 125 | /** 126 | * This is a simple poop function. 127 | */ 128 | public abstract function poop(): void; 129 | 130 | /** 131 | * This is a simple echo function. 132 | */ 133 | public final function helloWorld(): void { 134 | echo 'Hello World!'; 135 | } 136 | } 137 | 138 | class SimpleUser 139 | { 140 | private string $firstName = "Jane"; 141 | private string $lastName = "Doe"; 142 | public string $fullname { 143 | get { 144 | return $this->firstName . ' ' . $this->lastName; 145 | } 146 | set (string $fullname) { 147 | [$first, $last] = explode(' ', $fullname); 148 | $this->firstName = $first; 149 | $this->lastName = $last; 150 | } 151 | } 152 | } 153 | 154 | class SimpleUser2 155 | { 156 | private string $firstName = "Jane"; 157 | private string $lastName = "Doe"; 158 | public string $fullname { 159 | get { 160 | return $this->firstName . ' ' . $this->lastName; 161 | } 162 | } 163 | } 164 | 165 | /** 166 | * This is an example trait. 167 | */ 168 | trait ExampleTrait 169 | { 170 | use A; 171 | use B, C; 172 | use D, E, F, G { 173 | E::bar as baz; 174 | D::foo as public bar; 175 | E::qux as public; 176 | D::format as protected; 177 | D::d as private; 178 | D::drop insteadof E; 179 | G::something insteadof E, F, D; 180 | E::e as protected; 181 | } 182 | 183 | private string $foo; 184 | protected string $bar; 185 | public string|int $baz = "Hello World!"; 186 | /** 187 | * This is a simple hello function. 188 | * 189 | * @param non-empty-string $firstname 190 | * 191 | * @return string 192 | * 193 | * @pure 194 | */ 195 | #[Qux(foo: 1, bar: 2), Qux(foo: 1, bar: 2)] 196 | function hello( 197 | string $firstname, 198 | string $lastname = Qux::Foo, 199 | ): string { 200 | return 'Hello ' . $firstname . ' ' . $lastname . '!'; 201 | } 202 | 203 | /** 204 | * This is a simple x function. 205 | * 206 | * @pure 207 | */ 208 | #[Foo(foo: 1, bar: 2), Bar(foo: 1, bar: 2)] 209 | #[Baz, Qux] 210 | public function x(): mixed { 211 | return 'Hello!'; 212 | } 213 | 214 | /** 215 | * This is a simple poop function. 216 | */ 217 | public abstract function poop(): void; 218 | 219 | /** 220 | * This is a simple echo function. 221 | */ 222 | public final function helloWorld(): void { 223 | echo 'Hello World!'; 224 | } 225 | } 226 | 227 | /** 228 | * This is an example unit enum. 229 | */ 230 | enum ExampleUnitEnum 231 | { 232 | use A; 233 | use B, C; 234 | use D, E, F, G { 235 | E::bar as baz; 236 | D::foo as public bar; 237 | E::qux as public; 238 | D::format as protected; 239 | D::d as private; 240 | D::drop insteadof E; 241 | G::something insteadof E, F, D; 242 | E::e as protected; 243 | } 244 | 245 | /** 246 | * This is a foo case. 247 | */ 248 | case Foo; 249 | 250 | /** 251 | * This is a bar case. 252 | */ 253 | case Bar; 254 | 255 | case Baz; 256 | } 257 | 258 | /** 259 | * This is an example string backed enum. 260 | */ 261 | enum ExampleStringBackEnum: string 262 | { 263 | /** 264 | * This is a foo case. 265 | */ 266 | case Foo = "foo value"; 267 | 268 | /** 269 | * This is a bar case. 270 | */ 271 | case Bar = "bar value"; 272 | 273 | case Baz = "baz value"; 274 | } 275 | 276 | /** 277 | * This is a simple formatter interface. 278 | * 279 | * @immutable 280 | */ 281 | #[Foo(foo: 1, bar: 2), Bar(foo: 1, bar: 2)] 282 | interface Formatter extends Qux 283 | { 284 | public function format( 285 | string $template, 286 | int|float|string|null ...$args, 287 | ): string; 288 | } 289 | -------------------------------------------------------------------------------- /src/property.rs: -------------------------------------------------------------------------------- 1 | use crate::attribute::AttributeGroup; 2 | use crate::body::Body; 3 | use crate::comment::Document; 4 | use crate::data_type::DataType; 5 | use crate::literal::Value; 6 | use crate::modifiers::Modifier; 7 | use crate::modifiers::VisibilityModifier; 8 | use crate::Generator; 9 | use crate::Indentation; 10 | 11 | #[derive(Debug)] 12 | pub struct PropertySetHookParameter { 13 | pub data_type: Option, 14 | pub name: String, 15 | } 16 | 17 | #[derive(Debug)] 18 | pub enum PropertyHook { 19 | Get(bool, Body), 20 | Set(Option, Body), 21 | } 22 | 23 | #[derive(Debug)] 24 | pub struct Property { 25 | pub documentation: Option, 26 | pub attributes: Vec, 27 | pub visibility: Option, 28 | pub modifiers: Vec, 29 | pub data_type: Option, 30 | pub name: String, 31 | pub default: Option, 32 | pub hooks: Vec, 33 | } 34 | 35 | impl PropertySetHookParameter { 36 | pub fn new(name: T) -> Self { 37 | Self { 38 | name: name.to_string(), 39 | data_type: None, 40 | } 41 | } 42 | 43 | pub fn typed(mut self, data_type: DataType) -> Self { 44 | self.data_type = Some(data_type); 45 | 46 | self 47 | } 48 | } 49 | 50 | impl Property { 51 | pub fn new(name: T) -> Self { 52 | Self { 53 | name: name.to_string(), 54 | data_type: None, 55 | default: None, 56 | modifiers: vec![], 57 | attributes: vec![], 58 | visibility: None, 59 | documentation: None, 60 | hooks: vec![], 61 | } 62 | } 63 | 64 | pub fn document(mut self, documentation: Document) -> Self { 65 | self.documentation = Some(documentation); 66 | 67 | self 68 | } 69 | 70 | pub fn attributes(mut self, attributes: AttributeGroup) -> Self { 71 | self.attributes.push(attributes); 72 | 73 | self 74 | } 75 | 76 | pub fn typed(mut self, data_type: DataType) -> Self { 77 | self.data_type = Some(data_type); 78 | 79 | self 80 | } 81 | 82 | pub fn default>(mut self, default: T) -> Self { 83 | self.default = Some(default.into()); 84 | 85 | self 86 | } 87 | 88 | pub fn modifier(mut self, modifier: Modifier) -> Self { 89 | self.modifiers.push(modifier); 90 | 91 | self 92 | } 93 | 94 | pub fn public(mut self) -> Self { 95 | self.visibility = Some(VisibilityModifier::Public); 96 | 97 | self 98 | } 99 | 100 | pub fn protected(mut self) -> Self { 101 | self.visibility = Some(VisibilityModifier::Protected); 102 | 103 | self 104 | } 105 | 106 | pub fn private(mut self) -> Self { 107 | self.visibility = Some(VisibilityModifier::Private); 108 | 109 | self 110 | } 111 | 112 | pub fn visibility(mut self, visibility: VisibilityModifier) -> Self { 113 | self.visibility = Some(visibility); 114 | 115 | self 116 | } 117 | 118 | pub fn hook(mut self, hook: PropertyHook) -> Self { 119 | self.hooks.push(hook); 120 | 121 | self 122 | } 123 | } 124 | 125 | impl Generator for PropertySetHookParameter { 126 | fn generate(&self, indentation: Indentation, level: usize) -> String { 127 | let mut code = String::new(); 128 | 129 | if let Some(data_type) = &self.data_type { 130 | code.push_str(&format!("{} ", data_type.generate(indentation, level))); 131 | } 132 | 133 | code.push_str(&self.name); 134 | 135 | code 136 | } 137 | } 138 | 139 | impl Generator for PropertyHook { 140 | fn generate(&self, indentation: Indentation, level: usize) -> String { 141 | match self { 142 | PropertyHook::Get(by_reference, body) => { 143 | let mut code = String::new(); 144 | 145 | code.push_str(&indentation.value(level + 1)); 146 | if *by_reference { 147 | code.push_str("&get"); 148 | } else { 149 | code.push_str("get"); 150 | } 151 | 152 | code.push_str(&body.generate(indentation, level + 1)); 153 | 154 | code 155 | } 156 | PropertyHook::Set(parameter, body) => { 157 | let mut code = String::new(); 158 | 159 | code.push_str(&indentation.value(level + 1)); 160 | code.push_str("set"); 161 | 162 | if let Some(parameter) = parameter { 163 | code.push_str(" ("); 164 | code.push_str(¶meter.generate(indentation, level + 1)); 165 | code.push(')'); 166 | } 167 | 168 | code.push_str(&body.generate(indentation, level + 1)); 169 | 170 | code 171 | } 172 | } 173 | } 174 | } 175 | 176 | impl Generator for Vec { 177 | fn generate(&self, indentation: Indentation, level: usize) -> String { 178 | if self.is_empty() { 179 | return String::from(";"); 180 | } 181 | 182 | let hooks = self 183 | .iter() 184 | .map(|hook| hook.generate(indentation, level)) 185 | .collect::>() 186 | .join(""); 187 | 188 | format!(" {{\n{}{}}}", hooks, indentation.value(level)) 189 | } 190 | } 191 | 192 | impl Generator for Property { 193 | fn generate(&self, indentation: Indentation, level: usize) -> String { 194 | let mut code = indentation.value(level); 195 | 196 | if let Some(document) = &self.documentation { 197 | code.push_str(&document.generate(indentation, level)); 198 | } 199 | 200 | if !self.attributes.is_empty() { 201 | code.push_str( 202 | &self 203 | .attributes 204 | .iter() 205 | .map(|attributes| attributes.generate(indentation, level)) 206 | .collect::>() 207 | .join("\n"), 208 | ); 209 | 210 | code.push('\n'); 211 | } 212 | 213 | if let Some(visibility) = &self.visibility { 214 | code.push_str(&format!("{} ", visibility.generate(indentation, level))); 215 | } 216 | 217 | for modifier in &self.modifiers { 218 | code.push_str(&format!("{} ", modifier.generate(indentation, level))); 219 | } 220 | 221 | if let Some(data_type) = &self.data_type { 222 | code.push_str(&format!("{} ", data_type.generate(indentation, level))); 223 | } 224 | 225 | code.push_str(&format!("${}", &self.name)); 226 | 227 | if let Some(default) = &self.default { 228 | code.push_str(&format!(" = {}", default.generate(indentation, level))); 229 | } 230 | 231 | code.push_str(&self.hooks.generate(indentation, level)); 232 | 233 | code 234 | } 235 | } 236 | 237 | impl Generator for Vec { 238 | fn generate(&self, indentation: Indentation, level: usize) -> String { 239 | let mut code = String::new(); 240 | if self.is_empty() { 241 | return code; 242 | } 243 | 244 | for property in self.iter() { 245 | code.push_str(property.generate(indentation, level).as_str()); 246 | code.push('\n'); 247 | } 248 | 249 | code 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | -------------------------------------------------------------------------------- /tests/complete.rs: -------------------------------------------------------------------------------- 1 | use pretty_assertions::assert_eq; 2 | 3 | use php_codegen::attribute::AttributeGroup; 4 | use php_codegen::class::Class; 5 | use php_codegen::comment::Document; 6 | use php_codegen::constant::ClassConstant; 7 | use php_codegen::constant::Constant; 8 | use php_codegen::data_type::DataType; 9 | use php_codegen::enum_case::EnumCase; 10 | use php_codegen::file::File; 11 | use php_codegen::function::Function; 12 | use php_codegen::interface::Interface; 13 | use php_codegen::literal::Value; 14 | use php_codegen::method::Method; 15 | use php_codegen::modifiers::Modifier; 16 | use php_codegen::modifiers::VisibilityModifier; 17 | use php_codegen::parameter::Parameter; 18 | use php_codegen::property::Property; 19 | use php_codegen::property::PropertyHook; 20 | use php_codegen::property::PropertySetHookParameter; 21 | use php_codegen::r#enum::Enum; 22 | use php_codegen::r#trait::Trait; 23 | use php_codegen::usage::Usage; 24 | use php_codegen::Indentation; 25 | 26 | #[test] 27 | fn test_code_generation() { 28 | let file = File::new() 29 | .namespaced("Foo") 30 | .declare("strict_types", 1) 31 | .uses("Foo\\Bar\\Baz as Qux") 32 | .uses("Throwable") 33 | .uses("DateTime") 34 | .uses("DateTimeImmutable") 35 | .uses_function("strlen") 36 | .uses_function("array_map") 37 | .uses_function("array_filter") 38 | .uses_constant("Foo\\HELLO") 39 | .uses_constant("Foo\\HEY") 40 | .constant(Constant::new("A").valued("Hello World!")) 41 | .constant(Constant::new("B").valued(())) 42 | .constant(Constant::new("C").valued(1)) 43 | .constant(Constant::new("D").valued(false)) 44 | .constant(Constant::new("E")) 45 | .constant(("F", 213.412)) 46 | .constant(("G", vec![1, 25])) 47 | .function( 48 | Function::new("hello") 49 | .document( 50 | Document::new() 51 | .text("This is a simple hello function.") 52 | .empty_line() 53 | .tag("param", "non-empty-string $firstname") 54 | .empty_line() 55 | .tag("return", "string") 56 | .empty_line() 57 | .simple_tag("pure"), 58 | ) 59 | .attributes(AttributeGroup::new().add("Qux", Some("foo: 1, bar: 2"))) 60 | .parameter( 61 | Parameter::new("firstname") 62 | .typed(DataType::String) 63 | .attributes( 64 | AttributeGroup::new() 65 | .add("Validation\\NotBlank", None) 66 | .add("Validation\\Length", Some("min: 2, max: 10")), 67 | ), 68 | ) 69 | .parameter( 70 | Parameter::new("lastname") 71 | .typed(DataType::String) 72 | .default(Value::Literal("Qux::Foo".to_string())), 73 | ) 74 | .returns(DataType::String) 75 | .body("return 'Hello ' . $firstname . ' ' . $lastname . '!';"), 76 | ) 77 | .function(Function::new("nothing").returns(DataType::Void)) 78 | .function( 79 | Function::new("format") 80 | .parameter(Parameter::new("template").typed(DataType::String)) 81 | .parameter( 82 | Parameter::new("args") 83 | .variadic() 84 | .typed(DataType::Union(vec![ 85 | DataType::Integer, 86 | DataType::Float, 87 | DataType::String, 88 | DataType::Null, 89 | ])), 90 | ) 91 | .returns(DataType::String) 92 | .body(vec![ 93 | "return sprintf($template, ...array_map(", 94 | " static fn ($arg) => is_float($arg) ? number_format($arg, 2) : $arg,", 95 | " array_filter($args, static fn ($arg) => $arg !== null)", 96 | "));", 97 | ]), 98 | ) 99 | .class( 100 | Class::new("Example") 101 | .extends("Foo\\Bar\\Baz") 102 | .implements("Foo\\Bar\\BazInterface") 103 | .document( 104 | Document::new() 105 | .text("This is an example class.") 106 | .empty_line() 107 | .simple_tag("immutable"), 108 | ) 109 | .modifier(Modifier::Abstract) 110 | .using("A") 111 | .using(vec!["B", "C"]) 112 | .using( 113 | Usage::new(vec![ 114 | "D".to_string(), 115 | "E".to_string(), 116 | "F".to_string(), 117 | "G".to_string(), 118 | ]) 119 | .rename("E::bar", "baz") 120 | .alias("D::foo", "bar", VisibilityModifier::Public) 121 | .public("E::qux") 122 | .protected("D::format") 123 | .private("D::d") 124 | .precede("D::drop", vec!["E"]) 125 | .precede("G::something", vec!["E", "F", "D"]) 126 | .visibility("E::e", VisibilityModifier::Protected), 127 | ) 128 | .constant( 129 | ClassConstant::new("A") 130 | .valued("Hello World!") 131 | .modifier(Modifier::Final), 132 | ) 133 | .constant(ClassConstant::new("B").valued(()).protected()) 134 | .constant(ClassConstant::new("C").valued(1).private()) 135 | .constant(ClassConstant::new("D").valued(false).public()) 136 | .constant( 137 | ClassConstant::new("E") 138 | .typed(DataType::Boolean) 139 | .valued(false) 140 | .public(), 141 | ) 142 | .property(Property::new("foo").typed(DataType::String).private()) 143 | .property(Property::new("bar").typed(DataType::String).protected()) 144 | .property( 145 | Property::new("baz") 146 | .typed(DataType::Union(vec![DataType::String, DataType::Integer])) 147 | .public() 148 | .default("Hello World!"), 149 | ) 150 | .method( 151 | Method::new("hello") 152 | .returns(DataType::String) 153 | .parameter(Parameter::new("firstname").typed(DataType::String)) 154 | .parameter( 155 | Parameter::new("lastname") 156 | .typed(DataType::String) 157 | .default(Value::Literal("Qux::Foo".to_string())), 158 | ) 159 | .body("return 'Hello ' . $firstname . ' ' . $lastname . '!';") 160 | .attributes( 161 | AttributeGroup::new() 162 | .add("Qux", Some("foo: 1, bar: 2")) 163 | .add("Qux", Some("foo: 1, bar: 2")), 164 | ) 165 | .document( 166 | Document::new() 167 | .text("This is a simple hello function.") 168 | .empty_line() 169 | .tag("param", "non-empty-string $firstname") 170 | .empty_line() 171 | .tag("return", "string") 172 | .empty_line() 173 | .simple_tag("pure"), 174 | ), 175 | ) 176 | .method( 177 | Method::new("x") 178 | .public() 179 | .returns(DataType::Mixed) 180 | .body("return 'Hello!';") 181 | .attributes( 182 | AttributeGroup::new() 183 | .add("Foo", Some("foo: 1, bar: 2")) 184 | .add("Bar", Some("foo: 1, bar: 2")), 185 | ) 186 | .attributes(AttributeGroup::new().add("Baz", None).add("Qux", None)) 187 | .document( 188 | Document::new() 189 | .text("This is a simple x function.") 190 | .empty_line() 191 | .simple_tag("pure"), 192 | ), 193 | ) 194 | .method( 195 | Method::new("poop") 196 | .public() 197 | .modifier(Modifier::Abstract) 198 | .returns(DataType::Void) 199 | .document(Document::new().text("This is a simple poop function.")), 200 | ) 201 | .method( 202 | Method::new("helloWorld") 203 | .public() 204 | .modifier(Modifier::Final) 205 | .returns(DataType::Void) 206 | .document(Document::new().text("This is a simple echo function.")) 207 | .body(|indentation: Indentation, level| { 208 | indentation.indent("echo 'Hello World!';", level) 209 | }), 210 | ), 211 | ) 212 | .class( 213 | Class::new("SimpleUser") 214 | .property( 215 | Property::new("firstName") 216 | .typed(DataType::String) 217 | .visibility(VisibilityModifier::Private) 218 | .default(Value::String("Jane".to_string())), 219 | ) 220 | .property( 221 | Property::new("lastName") 222 | .typed(DataType::String) 223 | .visibility(VisibilityModifier::Private) 224 | .default(Value::String("Doe".to_string())), 225 | ) 226 | .property( 227 | Property::new("fullname") 228 | .typed(DataType::String) 229 | .visibility(VisibilityModifier::Public) 230 | .hook(PropertyHook::Get( 231 | false, 232 | vec!["return $this->firstName . ' ' . $this->lastName;"].into(), 233 | )) 234 | .hook(PropertyHook::Set( 235 | Some( 236 | PropertySetHookParameter::new("$fullname").typed(DataType::String), 237 | ), 238 | vec![ 239 | "[$first, $last] = explode(' ', $fullname);", 240 | "$this->firstName = $first;", 241 | "$this->lastName = $last;", 242 | ] 243 | .into(), 244 | )), 245 | ), 246 | ) 247 | .class( 248 | Class::new("SimpleUser2") 249 | .property( 250 | Property::new("firstName") 251 | .typed(DataType::String) 252 | .visibility(VisibilityModifier::Private) 253 | .default(Value::String("Jane".to_string())), 254 | ) 255 | .property( 256 | Property::new("lastName") 257 | .typed(DataType::String) 258 | .visibility(VisibilityModifier::Private) 259 | .default(Value::String("Doe".to_string())), 260 | ) 261 | .property( 262 | Property::new("fullname") 263 | .typed(DataType::String) 264 | .visibility(VisibilityModifier::Public) 265 | .hook(PropertyHook::Get( 266 | false, 267 | vec!["return $this->firstName . ' ' . $this->lastName;"].into(), 268 | )), 269 | ), 270 | ) 271 | .r#trait( 272 | Trait::new("ExampleTrait") 273 | .document(Document::new().text("This is an example trait.")) 274 | .using("A") 275 | .using(vec!["B", "C"]) 276 | .using( 277 | Usage::new(vec![ 278 | "D".to_string(), 279 | "E".to_string(), 280 | "F".to_string(), 281 | "G".to_string(), 282 | ]) 283 | .rename("E::bar", "baz") 284 | .alias("D::foo", "bar", VisibilityModifier::Public) 285 | .public("E::qux") 286 | .protected("D::format") 287 | .private("D::d") 288 | .precede("D::drop", vec!["E"]) 289 | .precede("G::something", vec!["E", "F", "D"]) 290 | .visibility("E::e", VisibilityModifier::Protected), 291 | ) 292 | .property(Property::new("foo").typed(DataType::String).private()) 293 | .property(Property::new("bar").typed(DataType::String).protected()) 294 | .property( 295 | Property::new("baz") 296 | .typed(DataType::Union(vec![DataType::String, DataType::Integer])) 297 | .public() 298 | .default("Hello World!"), 299 | ) 300 | .method( 301 | Method::new("hello") 302 | .returns(DataType::String) 303 | .parameter(Parameter::new("firstname").typed(DataType::String)) 304 | .parameter( 305 | Parameter::new("lastname") 306 | .typed(DataType::String) 307 | .default(Value::Literal("Qux::Foo".to_string())), 308 | ) 309 | .body("return 'Hello ' . $firstname . ' ' . $lastname . '!';") 310 | .attributes( 311 | AttributeGroup::new() 312 | .add("Qux", Some("foo: 1, bar: 2")) 313 | .add("Qux", Some("foo: 1, bar: 2")), 314 | ) 315 | .document( 316 | Document::new() 317 | .text("This is a simple hello function.") 318 | .empty_line() 319 | .tag("param", "non-empty-string $firstname") 320 | .empty_line() 321 | .tag("return", "string") 322 | .empty_line() 323 | .simple_tag("pure"), 324 | ), 325 | ) 326 | .method( 327 | Method::new("x") 328 | .public() 329 | .returns(DataType::Mixed) 330 | .body("return 'Hello!';") 331 | .attributes( 332 | AttributeGroup::new() 333 | .add("Foo", Some("foo: 1, bar: 2")) 334 | .add("Bar", Some("foo: 1, bar: 2")), 335 | ) 336 | .attributes(AttributeGroup::new().add("Baz", None).add("Qux", None)) 337 | .document( 338 | Document::new() 339 | .text("This is a simple x function.") 340 | .empty_line() 341 | .simple_tag("pure"), 342 | ), 343 | ) 344 | .method( 345 | Method::new("poop") 346 | .public() 347 | .modifier(Modifier::Abstract) 348 | .returns(DataType::Void) 349 | .document(Document::new().text("This is a simple poop function.")), 350 | ) 351 | .method( 352 | Method::new("helloWorld") 353 | .public() 354 | .modifier(Modifier::Final) 355 | .returns(DataType::Void) 356 | .document(Document::new().text("This is a simple echo function.")) 357 | .body(|indentation: Indentation, level| { 358 | indentation.indent("echo 'Hello World!';", level) 359 | }), 360 | ), 361 | ) 362 | .r#enum( 363 | Enum::new("ExampleUnitEnum") 364 | .document(Document::new().text("This is an example unit enum.")) 365 | .using("A") 366 | .using(vec!["B", "C"]) 367 | .using( 368 | Usage::new(vec![ 369 | "D".to_string(), 370 | "E".to_string(), 371 | "F".to_string(), 372 | "G".to_string(), 373 | ]) 374 | .rename("E::bar", "baz") 375 | .alias("D::foo", "bar", VisibilityModifier::Public) 376 | .public("E::qux") 377 | .protected("D::format") 378 | .private("D::d") 379 | .precede("D::drop", vec!["E"]) 380 | .precede("G::something", vec!["E", "F", "D"]) 381 | .visibility("E::e", VisibilityModifier::Protected), 382 | ) 383 | .case(EnumCase::new("Foo").document(Document::new().text("This is a foo case."))) 384 | .case(EnumCase::new("Bar").document(Document::new().text("This is a bar case."))) 385 | .case("Baz"), 386 | ) 387 | .r#enum( 388 | Enum::new("ExampleStringBackEnum") 389 | .document(Document::new().text("This is an example string backed enum.")) 390 | .string_backed() 391 | .case( 392 | EnumCase::new("Foo") 393 | .document(Document::new().text("This is a foo case.")) 394 | .valued("foo value"), 395 | ) 396 | .case( 397 | EnumCase::new("Bar") 398 | .document(Document::new().text("This is a bar case.")) 399 | .valued("bar value"), 400 | ) 401 | .case(("Baz", "baz value")), 402 | ) 403 | .interface( 404 | Interface::new("Formatter") 405 | .document( 406 | Document::new() 407 | .text("This is a simple formatter interface.") 408 | .empty_line() 409 | .simple_tag("immutable"), 410 | ) 411 | .attributes( 412 | AttributeGroup::new() 413 | .add("Foo", Some("foo: 1, bar: 2")) 414 | .add("Bar", Some("foo: 1, bar: 2")), 415 | ) 416 | .extends("Foo") 417 | .extends("Bar") 418 | .extends("Qux") 419 | .method( 420 | Method::new("format") 421 | .parameter(Parameter::new("template").typed(DataType::String)) 422 | .parameter( 423 | Parameter::new("args") 424 | .variadic() 425 | .typed(DataType::Union(vec![ 426 | DataType::Integer, 427 | DataType::Float, 428 | DataType::String, 429 | DataType::Null, 430 | ])), 431 | ) 432 | .returns(DataType::String), 433 | ), 434 | ); 435 | 436 | assert_eq!(include_str!("complete.php"), file.to_string(),); 437 | } 438 | --------------------------------------------------------------------------------