diff --git a/.docs/README.md b/.docs/README.md index a2f5d4b..902eb41 100644 --- a/.docs/README.md +++ b/.docs/README.md @@ -5,14 +5,14 @@ ## Installation ```bash -composer require contributte/mate --dev +composer require contributte/crafter --dev ``` ## Quickstart -1. Create `.mate.neon` in your project root. +1. Create `crafter.neon` in your project root. -You can initialize it by running `vendor/bin/mate init`. Or you can create it manually. +You can initialize it by running `vendor/bin/crafter init`. Or you can create it manually. ```neon data: @@ -21,14 +21,14 @@ data: username: {type: string} email: {type: string} password: {type: string} - createdAt: {type: Nette\Utils\DateTime} - updatedAt: {type: Nette\Utils\DateTime} + createdAt: {type: datetime} + updatedAt: {type: datetime} ``` -2. Run `vendor/bin/mate` or `php mate.phar` in your project root. +2. Run `vendor/bin/crafter` or `php crafter.phar` in your project root. ``` -vendor/bin/mate craft +vendor/bin/crafter craft ``` ## Configuration @@ -37,39 +37,33 @@ Under construction. ## Usage -### `mate init` +### `crafter init` -Create `.mate.neon` in your project. +Create `crafter.neon` in your project. -### `mate craft` +### `crafter craft` -Generate files based on `.mate.neon`. +Generate files based on `crafter.neon`. -```bash -vendor/bin/mate craft --struct user -``` +You can define: + +- `--data|-k` - data structure key +- `--scope|-s` - scope of generation ```bash -vendor/bin/mate craft --struct user --crafter=entity -vendor/bin/mate craft --struct user --crafter=repository - -vendor/bin/mate craft --struct user --crafter=bus --mode=create -vendor/bin/mate craft --struct user --crafter=bus --mode=update -vendor/bin/mate craft --struct user --crafter=bus --mode=delete -vendor/bin/mate craft --struct user --crafter=bus --mode=list -vendor/bin/mate craft --struct user --crafter=bus --mode=get - -vendor/bin/mate craft --struct user --crafter=api --mode=create -vendor/bin/mate craft --struct user --crafter=api --mode=update -vendor/bin/mate craft --struct user --crafter=api --mode=delete -vendor/bin/mate craft --struct user --crafter=api --mode=list -vendor/bin/mate craft --struct user --crafter=api --mode=get +vendor/bin/crafter craft -k user +vendor/bin/crafter craft -k user -s database ``` -### `mate generate` +### `crafter generate` + +Generate whole project based on template. + +You can define: -Generate whole project based on `.mate.neon`. +- `--template|-t` - project template +- `--directory|-d` - output folder ```bash -vendor/bin/mate generate --template api +vendor/bin/crafter generate -t nella -d demo ``` diff --git a/README.md b/README.md index 91f6a0a..5b5b1c1 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,14 @@ -![](https://heatbadger.now.sh/github/readme/contributte/mate/) +![](https://heatbadger.now.sh/github/readme/contributte/crafter/)

- - - - + + + +

- - + + @@ -20,10 +20,10 @@ Website 🚀 contributte.org | Contact ## Usage -To install the latest version of `contributte/mate` use [Composer](https://getcomposer.org). +To install the latest version of `contributte/crafter` use [Composer](https://getcomposer.org). ``` -composer require contributte/mate +composer require contributte/crafter ``` ## Documentation diff --git a/bin/crafter b/bin/crafter new file mode 100755 index 0000000..be0df1d --- /dev/null +++ b/bin/crafter @@ -0,0 +1,4 @@ +#!/usr/bin/env php + Expect::string()->required(), - 'email' => Expect::string()->required(), - 'password' => Expect::string()->required(), - 'createdAt' => Expect::string()->required(), - 'updatedAt' => Expect::string()->required(), - 'roles' => Expect::string()->required(), - ])->castTo(self::class); - } - -} diff --git a/examples/d01/app/Api/User/Create/CreateUserResponse.php b/examples/d01/app/Api/User/Create/CreateUserResponse.php deleted file mode 100644 index 9866399..0000000 --- a/examples/d01/app/Api/User/Create/CreateUserResponse.php +++ /dev/null @@ -1,20 +0,0 @@ -payload = $dto; - - return $self; - } - -} diff --git a/examples/d01/app/Api/User/Delete/DeleteUserController.php b/examples/d01/app/Api/User/Delete/DeleteUserController.php deleted file mode 100644 index 8560754..0000000 --- a/examples/d01/app/Api/User/Delete/DeleteUserController.php +++ /dev/null @@ -1,28 +0,0 @@ -payload = $dto; - - return $self; - } - -} diff --git a/examples/d01/app/Api/User/List/ListUserController.php b/examples/d01/app/Api/User/List/ListUserController.php deleted file mode 100644 index a57daf3..0000000 --- a/examples/d01/app/Api/User/List/ListUserController.php +++ /dev/null @@ -1,28 +0,0 @@ - Expect::structure([ - 'username' => Expect::anyOf('asc', 'desc'), - ])->required(false)->castTo('array'), - 'q' => Expect::structure([ - 'username' => Expect::string(), - ])->required(false)->castTo('array'), - ])->castTo(self::class); - } - -} diff --git a/examples/d01/app/Api/User/List/ListUserResponse.php b/examples/d01/app/Api/User/List/ListUserResponse.php deleted file mode 100644 index 2996302..0000000 --- a/examples/d01/app/Api/User/List/ListUserResponse.php +++ /dev/null @@ -1,30 +0,0 @@ -entities as $entity) { - $payload[] = $entity->toArray(); - } - - $self->entities = $payload; - $self->count = $result->count; - $self->limit = $result->limit; - $self->page = $result->page; - - return $self; - } - -} diff --git a/examples/d01/app/Api/User/Update/UpdateUserController.php b/examples/d01/app/Api/User/Update/UpdateUserController.php deleted file mode 100644 index 9e06008..0000000 --- a/examples/d01/app/Api/User/Update/UpdateUserController.php +++ /dev/null @@ -1,28 +0,0 @@ - Expect::string(), - 'email' => Expect::string(), - 'password' => Expect::string(), - 'createdAt' => Expect::string(), - 'updatedAt' => Expect::string(), - 'roles' => Expect::string(), - ])->castTo(self::class); - } - -} diff --git a/examples/d01/app/Api/User/Update/UpdateUserResponse.php b/examples/d01/app/Api/User/Update/UpdateUserResponse.php deleted file mode 100644 index 1db51e0..0000000 --- a/examples/d01/app/Api/User/Update/UpdateUserResponse.php +++ /dev/null @@ -1,20 +0,0 @@ -payload = $dto; - - return $self; - } - -} diff --git a/examples/d01/app/Domain/User/CreateUserCommand.php b/examples/d01/app/Domain/User/CreateUserCommand.php deleted file mode 100644 index e904859..0000000 --- a/examples/d01/app/Domain/User/CreateUserCommand.php +++ /dev/null @@ -1,20 +0,0 @@ -username = $command->username; - $user->email = $command->email; - $user->password = $command->password; - $user->createdAt = $command->createdAt; - $user->updatedAt = $command->updatedAt; - $user->roles = $command->roles; - - try { - $this->em->persist($user); - $this->em->flush(); - } catch (UniqueConstraintViolationException $e) { - throw EntityExistsException::alreadyExists(User::class, $e); - } - - // $this->ed->dispatch(new UserCreatedEvent($user)); - - return Result::from($user); - } - -} diff --git a/examples/d01/app/Domain/User/Database/User.php b/examples/d01/app/Domain/User/Database/User.php deleted file mode 100644 index 816f04d..0000000 --- a/examples/d01/app/Domain/User/Database/User.php +++ /dev/null @@ -1,42 +0,0 @@ - - */ -class UserRepository extends AbstractRepository -{ - - public function __construct( - EntityManagerInterface $em - ) - { - parent::__construct($em->getRepository(User::class)); - } - -} diff --git a/examples/d01/app/Domain/User/DeleteUserCommand.php b/examples/d01/app/Domain/User/DeleteUserCommand.php deleted file mode 100644 index de3fca9..0000000 --- a/examples/d01/app/Domain/User/DeleteUserCommand.php +++ /dev/null @@ -1,15 +0,0 @@ -em->getRepository(User::class)->findOneBy(['id' => $command->id]); - - if ($user === null) { - throw EntityNotFoundException::notFoundByUiid(User::class, $command->id); - } - - if ($command->username !== null) { - $user->username = $command->username; - } - if ($command->email !== null) { - $user->email = $command->email; - } - if ($command->password !== null) { - $user->password = $command->password; - } - if ($command->createdAt !== null) { - $user->createdAt = $command->createdAt; - } - if ($command->updatedAt !== null) { - $user->updatedAt = $command->updatedAt; - } - if ($command->roles !== null) { - $user->roles = $command->roles; - } - - $this->em->persist($user); - $this->em->flush(); - - // $this->ed->dispatch(new UserUpdatedEvent($user)); - - return Result::from($user); - } - -} diff --git a/examples/d02/.mate.neon b/examples/d02/.mate.neon deleted file mode 100644 index 04bec7a..0000000 --- a/examples/d02/.mate.neon +++ /dev/null @@ -1,51 +0,0 @@ -app: - namespace: App - - craft: - # Database - - { category: database, type: entity, class: "App\Domain\${name|ucfirst}\Database\${name|ucfirst}", baseClass: App\Model\Database\Entity\AbstractEntity } - - { category: database, type: repository, class: "App\Domain\${name|ucfirst}\Database\${name|ucfirst}Repository", baseClass: App\Model\Database\Repository\AbstractRepository } - # Domain - - { category: domain, type: command, flavor: create, class: "App\Domain\${name|ucfirst}\Create${name|ucfirst}Command" } - - { category: domain, type: handler, flavor: create, class: "App\Domain\${name|ucfirst}\Create${name|ucfirst}Handler", dependencies: { em: Doctrine\ORM\EntityManagerInterface } } - - { category: domain, type: command, flavor: update, class: "App\Domain\${name|ucfirst}\Update${name|ucfirst}Command" } - - { category: domain, type: handler, flavor: update, class: "App\Domain\${name|ucfirst}\Update${name|ucfirst}Handler", dependencies: { em: Doctrine\ORM\EntityManagerInterface } } - - { category: domain, type: command, flavor: get, class: "App\Domain\${name|ucfirst}\Get${name|ucfirst}Command" } - - { category: domain, type: handler, flavor: get, class: "App\Domain\${name|ucfirst}\Get${name|ucfirst}Handler", dependencies: { em: Doctrine\ORM\EntityManagerInterface } } - - { category: domain, type: command, flavor: list, class: "App\Domain\${name|ucfirst}\List${name|ucfirst}Command" } - - { category: domain, type: handler, flavor: list, class: "App\Domain\${name|ucfirst}\List${name|ucfirst}Handler", dependencies: { em: Doctrine\ORM\EntityManagerInterface } } - - { category: domain, type: command, flavor: delete, class: "App\Domain\${name|ucfirst}\Delete${name|ucfirst}Command" } - - { category: domain, type: handler, flavor: delete, class: "App\Domain\${name|ucfirst}\Delete${name|ucfirst}Handler", dependencies: { em: Doctrine\ORM\EntityManagerInterface } } - # API (create) - - { category: api, type: controller, flavor: create, class: "App\Api\${name|ucfirst}\Create\Create${name|ucfirst}Controller" } - - { category: api, type: request, flavor: create, class: "App\Api\${name|ucfirst}\Create\Create${name|ucfirst}Request" } - - { category: api, type: request_body, flavor: create, class: "App\Api\${name|ucfirst}\Create\Create${name|ucfirst}Body" } - - { category: api, type: response, flavor: create, class: "App\Api\${name|ucfirst}\Create\Create${name|ucfirst}Response" } - # API (update) - - { category: api, type: controller, flavor: update, class: "App\Api\${name|ucfirst}\Update\Update${name|ucfirst}Controller" } - - { category: api, type: request, flavor: update, class: "App\Api\${name|ucfirst}\Update\Update${name|ucfirst}Request" } - - { category: api, type: request_body, flavor: update, class: "App\Api\${name|ucfirst}\Update\Update${name|ucfirst}Body" } - - { category: api, type: response, flavor: update, class: "App\Api\${name|ucfirst}\Update\Update${name|ucfirst}Response" } - # API (get) - - { category: api, type: controller, flavor: get, class: "App\Api\${name|ucfirst}\Get\Get${name|ucfirst}Controller" } - - { category: api, type: request, flavor: get, class: "App\Api\${name|ucfirst}\Get\Get${name|ucfirst}Request" } - - { category: api, type: response, flavor: get, class: "App\Api\${name|ucfirst}\Get\Get${name|ucfirst}Response" } - # API (list) - - { category: api, type: controller, flavor: list, class: "App\Api\${name|ucfirst}\List\List${name|ucfirst}Controller" } - - { category: api, type: request, flavor: list,class: "App\Api\${name|ucfirst}\List\List${name|ucfirst}Request" } - - { category: api, type: request_filter, flavor: list,class: "App\Api\${name|ucfirst}\List\List${name|ucfirst}Filter" } - - { category: api, type: response, flavor: list,class: "App\Api\${name|ucfirst}\List\List${name|ucfirst}Response" } - # API (delete) - - { category: api, type: controller, flavor: delete, class: "App\Api\${name|ucfirst}\Delete\Delete${name|ucfirst}Controller" } - - { category: api, type: response, flavor: delete,class: "App\Api\${name|ucfirst}\Delete\Delete${name|ucfirst}Response" } - -data: - user: - name: user - craft: [api, domain, database] - fields: - username: {type: string} - email: {type: string} - password: {type: string} - createdAt: {type: Nette\Utils\DateTime } - updatedAt: {type: Nette\Utils\DateTime} diff --git a/examples/d02/app/Domain/User/CreateUserCommand.php b/examples/d02/app/Domain/User/CreateUserCommand.php deleted file mode 100644 index c933f3f..0000000 --- a/examples/d02/app/Domain/User/CreateUserCommand.php +++ /dev/null @@ -1,17 +0,0 @@ -username, - email: $command->email, - password: $command->password, - createdAt: $command->createdAt, - updatedAt: $command->updatedAt, - ); - - - $this->em->persist($entity); - $this->em->flush(); - - - return $entity; - } -} diff --git a/examples/presets/custom-crafters/.gitignore b/examples/presets/custom-crafters/.gitignore new file mode 100644 index 0000000..0be5ce5 --- /dev/null +++ b/examples/presets/custom-crafters/.gitignore @@ -0,0 +1,3 @@ +* +!.gitignore +crafter.neon diff --git a/examples/d01/.mate.neon b/examples/presets/custom-crafters/crafter.neon similarity index 74% rename from examples/d01/.mate.neon rename to examples/presets/custom-crafters/crafter.neon index fc88379..aa49dc6 100644 --- a/examples/d01/.mate.neon +++ b/examples/presets/custom-crafters/crafter.neon @@ -1,10 +1,9 @@ -mate: - presets: [moderntv] +dir: app +presets: [fx] -app: - scopes: [database, bus, api] +crafters: [] # TODO -structs: +data: user: fields: username: {type: string} diff --git a/examples/presets/preset-default/.gitignore b/examples/presets/preset-default/.gitignore new file mode 100644 index 0000000..0be5ce5 --- /dev/null +++ b/examples/presets/preset-default/.gitignore @@ -0,0 +1,3 @@ +* +!.gitignore +crafter.neon diff --git a/examples/presets/preset-default/crafter.neon b/examples/presets/preset-default/crafter.neon new file mode 100644 index 0000000..bd2f6c2 --- /dev/null +++ b/examples/presets/preset-default/crafter.neon @@ -0,0 +1,8 @@ +dir: src +namespace: MyApp + +data: + user: + fields: + username: {type: string} + email: {type: string} diff --git a/examples/presets/preset-nette/.gitignore b/examples/presets/preset-nette/.gitignore new file mode 100644 index 0000000..0be5ce5 --- /dev/null +++ b/examples/presets/preset-nette/.gitignore @@ -0,0 +1,3 @@ +* +!.gitignore +crafter.neon diff --git a/examples/projects/nella/.gitignore b/examples/projects/nella/.gitignore new file mode 100644 index 0000000..0be5ce5 --- /dev/null +++ b/examples/projects/nella/.gitignore @@ -0,0 +1,3 @@ +* +!.gitignore +crafter.neon diff --git a/phpstan.neon b/phpstan.neon index 2c30fdf..a23661e 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -23,27 +23,26 @@ parameters: typeAliases: ConfigShape: """ array{ - app: array{ - appDir: string, - namespace: string, - scopes: array|null, - crafters: array|null, - baseClass: string|null - }>|null - }, - mate: array{ - presets: array|null - }, - structs: array|null, + preset: string|null, + template: string|null, + crafters: CraftersShape, + data: array|null - }>|null + nullable: bool|null, + }>, + vars: array|null, + }> } """ + CraftersShape: """ + array|null, + }> + """ diff --git a/resources/example/example1.neon b/resources/example/example1.neon new file mode 100644 index 0000000..7342cfe --- /dev/null +++ b/resources/example/example1.neon @@ -0,0 +1,28 @@ +# Schema version +version: 1 + +# Type of crafter +type: preset + +# Application directory +dir: src + +# Application namespace +namespace: MyApp + +# Global variables +vars: [] + +# List of crafters +crafters: [] + +# Data structures +data: + user: + fields: + username: {type: string} + email: {type: string} + password: {type: string} + createdAt: {type: Nette\Utils\DateTime} + updatedAt: {type: Nette\Utils\DateTime} + roles: {type: array} diff --git a/resources/presets/default/config.neon b/resources/presets/default/config.neon deleted file mode 100644 index d9342c3..0000000 --- a/resources/presets/default/config.neon +++ /dev/null @@ -1,5 +0,0 @@ -app: - namespace: App - appDir: app - - crafters: [] diff --git a/resources/presets/default/crafter.neon b/resources/presets/default/crafter.neon new file mode 100644 index 0000000..e554ec1 --- /dev/null +++ b/resources/presets/default/crafter.neon @@ -0,0 +1,5 @@ +# Schema version +version: 1 + +# List of crafters +crafters: [] diff --git a/resources/presets/fx/crafter.neon b/resources/presets/fx/crafter.neon new file mode 100644 index 0000000..02f44c0 --- /dev/null +++ b/resources/presets/fx/crafter.neon @@ -0,0 +1,220 @@ +# Schema version +version: 1 + +# List of crafters +crafters: + # Database ================================ + database_entity: + name: entity + scopes: [database] + crafter: + type: latte # explicit + template: %cwd%/templates/database/entity.latte + class: 'App\Domain\{$name|firstUpper}\Database\{$name|firstUpper}' + + database_repository: + name: repository + scopes: [database] + crafter: + # type: latte # autodetect by extension + template: %cwd%/templates/database/repository.latte + class: 'App\Domain\{$name|firstUpper}\Database\{$name|firstUpper}Repository' + + # Bus ==================================== + # Bus (create) + bus_command_create: + name: command@create + scopes: [bus] + crafter: + template: %cwd%/templates/bus/command_create.latte + class: 'App\Domain\{$name|firstUpper}\Create{$name|firstUpper}Command' + bus_handler_create: + name: handler@create + scopes: [bus] + crafter: + template: %cwd%/templates/bus/handler_create.latte + class: 'App\Domain\{$name|firstUpper}\Create{$name|firstUpper}Handler' + # Bus (update) + bus_command_update: + name: command@update + scopes: [bus] + crafter: + template: %cwd%/templates/bus/command_update.latte + class: 'App\Domain\{$name|firstUpper}\Update{$name|firstUpper}Command' + bus_handler_update: + name: handler@update + scopes: [bus] + crafter: + template: %cwd%/templates/bus/handler_update.latte + class: 'App\Domain\{$name|firstUpper}\Update{$name|firstUpper}Handler' + # Bus (get) + bus_command_get: + name: command@get + scopes: [bus] + crafter: + template: %cwd%/templates/bus/command_get.latte + class: 'App\Domain\{$name|firstUpper}\Get{$name|firstUpper}Command' + bus_handler_get: + name: handler@get + scopes: [bus] + crafter: + template: %cwd%/templates/bus/handler_get.latte + class: 'App\Domain\{$name|firstUpper}\Get{$name|firstUpper}Handler' + # Bus (list) + bus_command_list: + name: command@update + scopes: [bus] + crafter: + template: %cwd%/templates/bus/command_list.latte + class: 'App\Domain\{$name|firstUpper}\List{$name|firstUpper}Command' + bus_handler_list: + name: handler@create + scopes: [bus] + crafter: + template: %cwd%/templates/bus/handler_list.latte + class: 'App\Domain\{$name|firstUpper}\List{$name|firstUpper}Handler' + # Bus (delete) + bus_command_delete: + name: command@delete + scopes: [bus] + crafter: + template: %cwd%/templates/bus/command_delete.latte + class: 'App\Domain\{$name|firstUpper}\Delete{$name|firstUpper}Command' + bus_handler_delete: + name: handler@delete + scopes: [bus] + crafter: + template: %cwd%/templates/bus/handler_delete.latte + class: 'App\Domain\{$name|firstUpper}\Delete{$name|firstUpper}Handler' + + # API ==================================== + # API (create) + api_controller_create: + name: controller@create + scopes: [api] + crafter: + template: %cwd%/templates/api/controller_create.latte + class: 'App\Api\{$name|firstUpper}\Create\Create{$name|firstUpper}Controller' + + api_request_create: + name: request@create + scopes: [api] + crafter: + template: %cwd%/templates/api/request_create.latte + class: 'App\Api\{$name|firstUpper}\Create\Create{$name|firstUpper}Request' + + api_request_body_create: + name: request_body@create + scopes: [api] + crafter: + template: %cwd%/templates/api/request_body_create.latte + class: 'App\Api\{$name|firstUpper}\Create\Create{$name|firstUpper}RequestBody' + + api_response_create: + name: response@create + scopes: [api] + crafter: + template: %cwd%/templates/api/response_create.latte + class: 'App\Api\{$name|firstUpper}\Create\Create{$name|firstUpper}Response' + + # API (update) + api_controller_update: + name: controller@update + scopes: [api] + crafter: + template: %cwd%/templates/api/controller_update.latte + class: 'App\Api\{$name|firstUpper}\Update\Update{$name|firstUpper}Controller' + + api_request_update: + name: request@update + scopes: [api] + crafter: + template: %cwd%/templates/api/request_update.latte + class: 'App\Api\{$name|firstUpper}\Update\Update{$name|firstUpper}Request' + + api_request_body_update: + name: request_body@update + scopes: [api] + crafter: + template: %cwd%/templates/api/request_body_update.latte + class: 'App\Api\{$name|firstUpper}\Update\Update{$name|firstUpper}RequestBody' + + api_response_update: + name: response@update + scopes: [api] + crafter: + template: %cwd%/templates/api/response_update.latte + class: 'App\Api\{$name|firstUpper}\Update\Update{$name|firstUpper}Response' + + # API (get) + api_controller_get: + name: controller@get + scopes: [api] + crafter: + template: %cwd%/templates/api/controller_get.latte + class: 'App\Api\{$name|firstUpper}\Get\Get{$name|firstUpper}Controller' + + api_request_get: + name: request@get + scopes: [api] + crafter: + template: %cwd%/templates/api/request_get.latte + class: 'App\Api\{$name|firstUpper}\Get\Get{$name|firstUpper}Request' + + api_response_get: + name: response@get + scopes: [api] + crafter: + template: %cwd%/templates/api/response_get.latte + class: 'App\Api\{$name|firstUpper}\Get\Get{$name|firstUpper}Response' + + # API (list) + api_controller_list: + name: controller@list + scopes: [api] + crafter: + template: %cwd%/templates/api/controller_list.latte + class: 'App\Api\{$name|firstUpper}\List\List{$name|firstUpper}Controller' + + api_request_list: + name: request@list + scopes: [api] + crafter: + template: %cwd%/templates/api/request_list.latte + class: 'App\Api\{$name|firstUpper}\List\List{$name|firstUpper}Request' + + api_request_filter_list: + name: request_filter@list + scopes: [api] + crafter: + template: %cwd%/templates/api/request_filter_list.latte + class: 'App\Api\{$name|firstUpper}\List\List{$name|firstUpper}RequestFilter' + + api_response_list: + name: response@list + scopes: [api] + crafter: + template: %cwd%/templates/api/response_list.latte + class: 'App\Api\{$name|firstUpper}\List\List{$name|firstUpper}Response' + + # API (delete) + api_controller_delete: + name: controller@delete + scopes: [api] + crafter: + template: %cwd%/templates/api/controller_delete.latte + class: 'App\Api\{$name|firstUpper}\Delete\Delete{$name|firstUpper}Controller' + + api_request_delete: + name: request@delete + scopes: [api] + crafter: + template: %cwd%/templates/api/request_delete.latte + class: 'App\Api\{$name|firstUpper}\Delete\Delete{$name|firstUpper}Response' + + api_response_delete: + name: response@delete + scopes: [api] + crafter: + template: %cwd%/templates/api/response_delete.latte + class: 'App\Api\{$name|firstUpper}\Delete\Delete{$name|firstUpper}Response' diff --git a/resources/presets/moderntv/templates/api/controller_create.latte b/resources/presets/fx/templates/api/controller_create.latte similarity index 100% rename from resources/presets/moderntv/templates/api/controller_create.latte rename to resources/presets/fx/templates/api/controller_create.latte diff --git a/resources/presets/moderntv/templates/api/controller_delete.latte b/resources/presets/fx/templates/api/controller_delete.latte similarity index 100% rename from resources/presets/moderntv/templates/api/controller_delete.latte rename to resources/presets/fx/templates/api/controller_delete.latte diff --git a/resources/presets/moderntv/templates/api/controller_get.latte b/resources/presets/fx/templates/api/controller_get.latte similarity index 100% rename from resources/presets/moderntv/templates/api/controller_get.latte rename to resources/presets/fx/templates/api/controller_get.latte diff --git a/resources/presets/moderntv/templates/api/controller_list.latte b/resources/presets/fx/templates/api/controller_list.latte similarity index 100% rename from resources/presets/moderntv/templates/api/controller_list.latte rename to resources/presets/fx/templates/api/controller_list.latte diff --git a/resources/presets/moderntv/templates/api/controller_update.latte b/resources/presets/fx/templates/api/controller_update.latte similarity index 100% rename from resources/presets/moderntv/templates/api/controller_update.latte rename to resources/presets/fx/templates/api/controller_update.latte diff --git a/resources/presets/moderntv/templates/api/request_body_create.latte b/resources/presets/fx/templates/api/request_body_create.latte similarity index 100% rename from resources/presets/moderntv/templates/api/request_body_create.latte rename to resources/presets/fx/templates/api/request_body_create.latte diff --git a/resources/presets/moderntv/templates/api/request_body_update.latte b/resources/presets/fx/templates/api/request_body_update.latte similarity index 100% rename from resources/presets/moderntv/templates/api/request_body_update.latte rename to resources/presets/fx/templates/api/request_body_update.latte diff --git a/resources/presets/moderntv/templates/api/request_create.latte b/resources/presets/fx/templates/api/request_create.latte similarity index 100% rename from resources/presets/moderntv/templates/api/request_create.latte rename to resources/presets/fx/templates/api/request_create.latte diff --git a/resources/presets/moderntv/templates/api/request_delete.latte b/resources/presets/fx/templates/api/request_delete.latte similarity index 100% rename from resources/presets/moderntv/templates/api/request_delete.latte rename to resources/presets/fx/templates/api/request_delete.latte diff --git a/resources/presets/moderntv/templates/api/request_filter_list.latte b/resources/presets/fx/templates/api/request_filter_list.latte similarity index 100% rename from resources/presets/moderntv/templates/api/request_filter_list.latte rename to resources/presets/fx/templates/api/request_filter_list.latte diff --git a/resources/presets/moderntv/templates/api/request_get.latte b/resources/presets/fx/templates/api/request_get.latte similarity index 100% rename from resources/presets/moderntv/templates/api/request_get.latte rename to resources/presets/fx/templates/api/request_get.latte diff --git a/resources/presets/moderntv/templates/api/request_list.latte b/resources/presets/fx/templates/api/request_list.latte similarity index 100% rename from resources/presets/moderntv/templates/api/request_list.latte rename to resources/presets/fx/templates/api/request_list.latte diff --git a/resources/presets/moderntv/templates/api/request_update.latte b/resources/presets/fx/templates/api/request_update.latte similarity index 100% rename from resources/presets/moderntv/templates/api/request_update.latte rename to resources/presets/fx/templates/api/request_update.latte diff --git a/resources/presets/moderntv/templates/api/response_create.latte b/resources/presets/fx/templates/api/response_create.latte similarity index 100% rename from resources/presets/moderntv/templates/api/response_create.latte rename to resources/presets/fx/templates/api/response_create.latte diff --git a/resources/presets/moderntv/templates/api/response_delete.latte b/resources/presets/fx/templates/api/response_delete.latte similarity index 100% rename from resources/presets/moderntv/templates/api/response_delete.latte rename to resources/presets/fx/templates/api/response_delete.latte diff --git a/resources/presets/moderntv/templates/api/response_get.latte b/resources/presets/fx/templates/api/response_get.latte similarity index 100% rename from resources/presets/moderntv/templates/api/response_get.latte rename to resources/presets/fx/templates/api/response_get.latte diff --git a/resources/presets/moderntv/templates/api/response_list.latte b/resources/presets/fx/templates/api/response_list.latte similarity index 100% rename from resources/presets/moderntv/templates/api/response_list.latte rename to resources/presets/fx/templates/api/response_list.latte diff --git a/resources/presets/moderntv/templates/api/response_update.latte b/resources/presets/fx/templates/api/response_update.latte similarity index 100% rename from resources/presets/moderntv/templates/api/response_update.latte rename to resources/presets/fx/templates/api/response_update.latte diff --git a/resources/presets/moderntv/templates/bus/command_create.latte b/resources/presets/fx/templates/bus/command_create.latte similarity index 100% rename from resources/presets/moderntv/templates/bus/command_create.latte rename to resources/presets/fx/templates/bus/command_create.latte diff --git a/resources/presets/moderntv/templates/bus/command_delete.latte b/resources/presets/fx/templates/bus/command_delete.latte similarity index 100% rename from resources/presets/moderntv/templates/bus/command_delete.latte rename to resources/presets/fx/templates/bus/command_delete.latte diff --git a/resources/presets/moderntv/templates/bus/command_get.latte b/resources/presets/fx/templates/bus/command_get.latte similarity index 100% rename from resources/presets/moderntv/templates/bus/command_get.latte rename to resources/presets/fx/templates/bus/command_get.latte diff --git a/resources/presets/moderntv/templates/bus/command_list.latte b/resources/presets/fx/templates/bus/command_list.latte similarity index 100% rename from resources/presets/moderntv/templates/bus/command_list.latte rename to resources/presets/fx/templates/bus/command_list.latte diff --git a/resources/presets/moderntv/templates/bus/command_update.latte b/resources/presets/fx/templates/bus/command_update.latte similarity index 100% rename from resources/presets/moderntv/templates/bus/command_update.latte rename to resources/presets/fx/templates/bus/command_update.latte diff --git a/resources/presets/moderntv/templates/bus/handler_create.latte b/resources/presets/fx/templates/bus/handler_create.latte similarity index 100% rename from resources/presets/moderntv/templates/bus/handler_create.latte rename to resources/presets/fx/templates/bus/handler_create.latte diff --git a/resources/presets/moderntv/templates/bus/handler_delete.latte b/resources/presets/fx/templates/bus/handler_delete.latte similarity index 100% rename from resources/presets/moderntv/templates/bus/handler_delete.latte rename to resources/presets/fx/templates/bus/handler_delete.latte diff --git a/resources/presets/moderntv/templates/bus/handler_get.latte b/resources/presets/fx/templates/bus/handler_get.latte similarity index 100% rename from resources/presets/moderntv/templates/bus/handler_get.latte rename to resources/presets/fx/templates/bus/handler_get.latte diff --git a/resources/presets/moderntv/templates/bus/handler_list.latte b/resources/presets/fx/templates/bus/handler_list.latte similarity index 100% rename from resources/presets/moderntv/templates/bus/handler_list.latte rename to resources/presets/fx/templates/bus/handler_list.latte diff --git a/resources/presets/moderntv/templates/bus/handler_update.latte b/resources/presets/fx/templates/bus/handler_update.latte similarity index 100% rename from resources/presets/moderntv/templates/bus/handler_update.latte rename to resources/presets/fx/templates/bus/handler_update.latte diff --git a/resources/presets/moderntv/templates/database/entity.latte b/resources/presets/fx/templates/database/entity.latte similarity index 100% rename from resources/presets/moderntv/templates/database/entity.latte rename to resources/presets/fx/templates/database/entity.latte diff --git a/resources/presets/moderntv/templates/database/repository.latte b/resources/presets/fx/templates/database/repository.latte similarity index 100% rename from resources/presets/moderntv/templates/database/repository.latte rename to resources/presets/fx/templates/database/repository.latte diff --git a/resources/presets/moderntv/config.neon b/resources/presets/moderntv/config.neon deleted file mode 100644 index 3a10827..0000000 --- a/resources/presets/moderntv/config.neon +++ /dev/null @@ -1,228 +0,0 @@ -app: - namespace: App - appDir: app - - crafters: - # Database ================================ - database_entity: - crafter: entity - scopes: [database] - template: %presetDir%/templates/database/entity.latte - class: 'App\Domain\{$name|firstUpper}\Database\{$name|firstUpper}' - baseClass: App\Model\Database\Entity\AbstractEntity - - database_repository: - crafter: repository - scopes: [database] - template: %presetDir%/templates/database/repository.latte - class: 'App\Domain\{$name|firstUpper}\Database\{$name|firstUpper}Repository' - baseClass: App\Model\Database\Repository\AbstractRepository - - # Bus ==================================== - # Bus (create) - bus_command_create: - crafter: command - mode: create - scopes: [bus] - template: %presetDir%/templates/bus/command_create.latte - class: 'App\Domain\{$name|firstUpper}\Create{$name|firstUpper}Command' - bus_handler_create: - crafter: handler - mode: create - scopes: [bus] - template: %presetDir%/templates/bus/handler_create.latte - class: 'App\Domain\{$name|firstUpper}\Create{$name|firstUpper}Handler' - dependencies: - em: Doctrine\ORM\EntityManagerInterface - # Bus (update) - bus_command_update: - crafter: command - mode: update - scopes: [bus] - template: %presetDir%/templates/bus/command_update.latte - class: 'App\Domain\{$name|firstUpper}\Update{$name|firstUpper}Command' - bus_handler_update: - crafter: handler - mode: update - scopes: [bus] - template: %presetDir%/templates/bus/handler_update.latte - class: 'App\Domain\{$name|firstUpper}\Update{$name|firstUpper}Handler' - dependencies: - em: Doctrine\ORM\EntityManagerInterface - # Bus (get) - bus_command_get: - crafter: command - mode: get - scopes: [bus] - template: %presetDir%/templates/bus/command_get.latte - class: 'App\Domain\{$name|firstUpper}\Get{$name|firstUpper}Command' - bus_handler_get: - crafter: handler - mode: get - scopes: [bus] - template: %presetDir%/templates/bus/handler_get.latte - class: 'App\Domain\{$name|firstUpper}\Get{$name|firstUpper}Handler' - dependencies: - em: Doctrine\ORM\EntityManagerInterface - # Bus (list) - bus_command_list: - crafter: command - mode: update - scopes: [bus] - template: %presetDir%/templates/bus/command_list.latte - class: 'App\Domain\{$name|firstUpper}\List{$name|firstUpper}Command' - bus_handler_list: - crafter: handler - mode: create - scopes: [bus] - template: %presetDir%/templates/bus/handler_list.latte - class: 'App\Domain\{$name|firstUpper}\List{$name|firstUpper}Handler' - dependencies: - em: Doctrine\ORM\EntityManagerInterface - # Bus (delete) - bus_command_delete: - crafter: command - mode: delete - scopes: [bus] - template: %presetDir%/templates/bus/command_delete.latte - class: 'App\Domain\{$name|firstUpper}\Delete{$name|firstUpper}Command' - bus_handler_delete: - crafter: handler - mode: delete - scopes: [bus] - template: %presetDir%/templates/bus/handler_delete.latte - class: 'App\Domain\{$name|firstUpper}\Delete{$name|firstUpper}Handler' - dependencies: - em: Doctrine\ORM\EntityManagerInterface - - # API ==================================== - # API (create) - api_controller_create: - crafter: controller - mode: create - scopes: [api] - template: %presetDir%/templates/api/controller_create.latte - class: 'App\Api\{$name|firstUpper}\Create\Create{$name|firstUpper}Controller' - - api_request_create: - crafter: request - mode: create - scopes: [api] - template: %presetDir%/templates/api/request_create.latte - class: 'App\Api\{$name|firstUpper}\Create\Create{$name|firstUpper}Request' - - api_request_body_create: - crafter: request_body - mode: create - scopes: [api] - template: %presetDir%/templates/api/request_body_create.latte - class: 'App\Api\{$name|firstUpper}\Create\Create{$name|firstUpper}RequestBody' - - api_response_create: - crafter: response - mode: create - scopes: [api] - template: %presetDir%/templates/api/response_create.latte - class: 'App\Api\{$name|firstUpper}\Create\Create{$name|firstUpper}Response' - - # API (update) - api_controller_update: - crafter: controller - mode: update - scopes: [api] - template: %presetDir%/templates/api/controller_update.latte - class: 'App\Api\{$name|firstUpper}\Update\Update{$name|firstUpper}Controller' - - api_request_update: - crafter: request - mode: update - scopes: [api] - template: %presetDir%/templates/api/request_update.latte - class: 'App\Api\{$name|firstUpper}\Update\Update{$name|firstUpper}Request' - - api_request_body_update: - crafter: request_body - mode: update - scopes: [api] - template: %presetDir%/templates/api/request_body_update.latte - class: 'App\Api\{$name|firstUpper}\Update\Update{$name|firstUpper}RequestBody' - - api_response_update: - crafter: response - mode: update - scopes: [api] - template: %presetDir%/templates/api/response_update.latte - class: 'App\Api\{$name|firstUpper}\Update\Update{$name|firstUpper}Response' - - # API (get) - api_controller_get: - crafter: controller - mode: get - scopes: [api] - template: %presetDir%/templates/api/controller_get.latte - class: 'App\Api\{$name|firstUpper}\Get\Get{$name|firstUpper}Controller' - - api_request_get: - crafter: request - mode: get - scopes: [api] - template: %presetDir%/templates/api/request_get.latte - class: 'App\Api\{$name|firstUpper}\Get\Get{$name|firstUpper}Request' - - api_response_get: - crafter: response - mode: get - scopes: [api] - template: %presetDir%/templates/api/response_get.latte - class: 'App\Api\{$name|firstUpper}\Get\Get{$name|firstUpper}Response' - - # API (list) - api_controller_list: - crafter: controller - mode: list - scopes: [api] - template: %presetDir%/templates/api/controller_list.latte - class: 'App\Api\{$name|firstUpper}\List\List{$name|firstUpper}Controller' - - api_request_list: - crafter: request - mode: list - scopes: [api] - template: %presetDir%/templates/api/request_list.latte - class: 'App\Api\{$name|firstUpper}\List\List{$name|firstUpper}Request' - - api_request_filter_list: - crafter: request_filter - mode: list - scopes: [api] - template: %presetDir%/templates/api/request_filter_list.latte - class: 'App\Api\{$name|firstUpper}\List\List{$name|firstUpper}RequestFilter' - - api_response_list: - crafter: response - mode: list - scopes: [api] - template: %presetDir%/templates/api/response_list.latte - class: 'App\Api\{$name|firstUpper}\List\List{$name|firstUpper}Response' - - # API (delete) - api_controller_delete: - crafter: controller - mode: delete - scopes: [api] - template: %presetDir%/templates/api/controller_delete.latte - class: 'App\Api\{$name|firstUpper}\Delete\Delete{$name|firstUpper}Controller' - - api_request_delete: - crafter: request - mode: delete - scopes: [api] - template: %presetDir%/templates/api/request_delete.latte - class: 'App\Api\{$name|firstUpper}\Delete\Delete{$name|firstUpper}Response' - - api_response_delete: - crafter: response - mode: delete - scopes: [api] - template: %presetDir%/templates/api/response_delete.latte - class: 'App\Api\{$name|firstUpper}\Delete\Delete{$name|firstUpper}Response' diff --git a/resources/presets/nette/crafter.neon b/resources/presets/nette/crafter.neon new file mode 100644 index 0000000..3e0ae5f --- /dev/null +++ b/resources/presets/nette/crafter.neon @@ -0,0 +1,11 @@ +# Schema version +version: 1 + +# List of crafters +crafters: + presenter: + input: '%cwd%/templates/presenter/presenter.latte' + output: '/{$namespace}\UI\{$name|firstUpper}\{$name|firstUpper}Presenter.php' + presenter_template: + input: '%cwd%/templates/presenter/template.latte' + output: '/UI/{$name|firstUpper}/Templates/default.latte' diff --git a/resources/presets/nette/templates/presenter/presenter.latte b/resources/presets/nette/templates/presenter/presenter.latte new file mode 100644 index 0000000..b9631c5 --- /dev/null +++ b/resources/presets/nette/templates/presenter/presenter.latte @@ -0,0 +1,10 @@ +class->namespace}; + +use {$ctx->class->rootNamespace}\UI\BasePresenter; + +class {$ctx->class->className} extends BasePresenter +{ + +} diff --git a/resources/presets/nette/templates/presenter/template.latte b/resources/presets/nette/templates/presenter/template.latte new file mode 100644 index 0000000..4f46a35 --- /dev/null +++ b/resources/presets/nette/templates/presenter/template.latte @@ -0,0 +1,5 @@ +{block #content} + +

+ Hello! +
diff --git a/resources/presets/preset.dsl.neon b/resources/presets/preset.dsl.neon new file mode 100644 index 0000000..ebe8f8e --- /dev/null +++ b/resources/presets/preset.dsl.neon @@ -0,0 +1,21 @@ +# Schema version +version: 1 + +# List of crafters +crafters: + # Full syntax + acme: + id: acme@acme + crafter: + type: latte + template: %cwd%/templates/acme.latte + file: "{$namespace}\Acme\{$name|firstUpper}\Foo\{$name|firstUpper}.php" + vars: + some: variable + + # Compact syntax + acme2: + id: acme2@acme + crafter: + template: %cwd%/templates/acme.latte + file: "App\Acme\{$name|firstUpper}\Foo\{$name|firstUpper}.php" diff --git a/resources/projects/_defaults/LICENSE.latte b/resources/projects/_defaults/LICENSE.latte deleted file mode 100644 index 4337da9..0000000 --- a/resources/projects/_defaults/LICENSE.latte +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) {{=date('Y')} Contributte - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/resources/projects/_defaults/Makefile.latte b/resources/projects/_defaults/Makefile.latte deleted file mode 100644 index fbf63e6..0000000 --- a/resources/projects/_defaults/Makefile.latte +++ /dev/null @@ -1,56 +0,0 @@ -############################################################ -# PROJECT ################################################## -############################################################ -.PHONY: project install setup clean - -project: install setup - -install: -composer install - -setup: -mkdir -p var/tmp var/log -chmod +0777 var/tmp var/log - -clean: -find var/tmp -mindepth 1 ! -name '.gitignore' -type f,d -exec rm -rf {} + -find var/log -mindepth 1 ! -name '.gitignore' -type f,d -exec rm -rf {} + - -############################################################ -# DEVELOPMENT ############################################## -############################################################ -.PHONY: qa dev cs csf phpstan tests coverage dev build - -qa: cs phpstan - -cs: -vendor/bin/codesniffer app - -csf: -vendor/bin/codefixer app - -phpstan: -vendor/bin/phpstan analyse -c phpstan.neon --memory-limit=512M - -tests: -echo "NO TESTS" - -coverage: -echo "NO TESTS" - -dev: -NETTE_DEBUG=1 NETTE_ENV=dev php -S 0.0.0.0:8000 -t www - -build: -echo "OK" - -############################################################ -# DEPLOYMENT ############################################### -############################################################ -.PHONY: deploy - -deploy: -$(MAKE) clean -$(MAKE) project -$(MAKE) build -$(MAKE) clean diff --git a/resources/projects/_defaults/README.latte b/resources/projects/_defaults/README.latte deleted file mode 100644 index acef429..0000000 --- a/resources/projects/_defaults/README.latte +++ /dev/null @@ -1 +0,0 @@ -# Generated diff --git a/resources/projects/_defaults/github/codesniffer.latte b/resources/projects/_defaults/github/codesniffer.latte deleted file mode 100644 index dfc76ff..0000000 --- a/resources/projects/_defaults/github/codesniffer.latte +++ /dev/null @@ -1,15 +0,0 @@ -name: "Codesniffer" - -on: - pull_request: - - push: - branches: ["*"] - - schedule: - - cron: "0 8 * * 1" - -jobs: - codesniffer: - name: "Codesniffer" - uses: contributte/.github/.github/workflows/codesniffer.yml@v1 diff --git a/resources/projects/_defaults/github/dependabot.latte b/resources/projects/_defaults/github/dependabot.latte deleted file mode 100644 index 0f0dadb..0000000 --- a/resources/projects/_defaults/github/dependabot.latte +++ /dev/null @@ -1,9 +0,0 @@ -version: 2 -updates: - - package-ecosystem: composer - directory: "/" - schedule: - interval: daily - labels: - - "dependencies" - - "automerge" diff --git a/resources/projects/_defaults/github/kodiak.latte b/resources/projects/_defaults/github/kodiak.latte deleted file mode 100644 index 60c34b6..0000000 --- a/resources/projects/_defaults/github/kodiak.latte +++ /dev/null @@ -1,10 +0,0 @@ -version = 1 - -[merge] -automerge_label = "automerge" -blacklist_title_regex = "^WIP.*" -blacklist_labels = ["WIP"] -method = "rebase" -delete_branch_on_merge = true -notify_on_conflict = true -optimistic_updates = false diff --git a/resources/projects/_defaults/github/phpstan.latte b/resources/projects/_defaults/github/phpstan.latte deleted file mode 100644 index db3ad34..0000000 --- a/resources/projects/_defaults/github/phpstan.latte +++ /dev/null @@ -1,15 +0,0 @@ -name: "Phpstan" - -on: - pull_request: - - push: - branches: ["*"] - - schedule: - - cron: "0 8 * * 1" - -jobs: - phpstan: - name: "Phpstan" - uses: contributte/.github/.github/workflows/phpstan.yml@v1 diff --git a/resources/projects/_defaults/github/tests.latte b/resources/projects/_defaults/github/tests.latte deleted file mode 100644 index 5fa700c..0000000 --- a/resources/projects/_defaults/github/tests.latte +++ /dev/null @@ -1,23 +0,0 @@ -name: "Nette Tester" - -on: - pull_request: - - push: - branches: ["*"] - - schedule: - - cron: "0 8 * * 1" - -jobs: - test81: - name: "Nette Tester" - uses: contributte/.github/.github/workflows/nette-tester.yml@v1 - with: - php: "8.1" - - test80: - name: "Nette Tester" - uses: contributte/.github/.github/workflows/nette-tester.yml@v1 - with: - php: "8.0" diff --git a/resources/projects/_defaults/phpstan.latte b/resources/projects/_defaults/phpstan.latte deleted file mode 100644 index b5a5289..0000000 --- a/resources/projects/_defaults/phpstan.latte +++ /dev/null @@ -1,20 +0,0 @@ -includes: - - vendor/phpstan/phpstan-nette/extension.neon - - vendor/phpstan/phpstan-nette/rules.neon - - vendor/phpstan/phpstan-doctrine/extension.neon - - vendor/phpstan/phpstan-phpunit/rules.neon - -parameters: - level: 9 - phpVersion: 80000 - - scanDirectories: - - app - - fileExtensions: - - php - - paths: - - app - - ignoreErrors: diff --git a/resources/projects/_defaults/www/htaccess.latte b/resources/projects/_defaults/www/htaccess.latte deleted file mode 100644 index da941ae..0000000 --- a/resources/projects/_defaults/www/htaccess.latte +++ /dev/null @@ -1,31 +0,0 @@ -# Apache configuration file (see httpd.apache.org/docs/current/mod/quickreference.html) - -# disable directory listing - - Options -Indexes - - -# enable cool URL - - RewriteEngine On - # RewriteBase / - - # use HTTPS - # RewriteCond %{HTTPS} !on - # RewriteRule .? https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L] - - # prevents files starting with dot to be viewed by browser - RewriteRule /\.|^\.(?!well-known/) - [F] - - # front controller - RewriteCond %{REQUEST_FILENAME} !-f - RewriteCond %{REQUEST_FILENAME} !-d - RewriteRule !\.(pdf|js|ico|gif|jpg|png|css|rar|zip|tar\.gz|map)$ index.php [L] - - -# enable gzip compression - - - AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css application/javascript application/json application/xml image/svg+xml - - diff --git a/resources/projects/demo/app/UI/Home/HomePresenter.php b/resources/projects/demo/app/UI/Home/HomePresenter.php deleted file mode 100644 index fe6be79..0000000 --- a/resources/projects/demo/app/UI/Home/HomePresenter.php +++ /dev/null @@ -1,17 +0,0 @@ - + Hello! + diff --git a/resources/projects/nella/composer.json.latte b/resources/projects/nella/composer.json.latte new file mode 100644 index 0000000..12d8164 --- /dev/null +++ b/resources/projects/nella/composer.json.latte @@ -0,0 +1,32 @@ +{ + "name": "crafter/nella", + "type": "project", + "require": { + "php": ">=8.2", + "contributte/nella": "^0.3" + }, + "require-dev": { + "contributte/qa": "^0.3", + "contributte/dev": "^0.4", + "contributte/phpstan": "^0.1", + "contributte/tester": "^0.2" + }, + "autoload": { + "psr-4": { + "App\\": "app" + } + }, + "autoload-dev": { + "psr-4": { + "Tests\\": "tests" + } + }, + "prefer-stable": true, + "minimum-stability": "dev", + "config": { + "sort-packages": true, + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true + } + } +} diff --git a/resources/projects/demo/config/config.latte b/resources/projects/nella/config/config.neon.latte similarity index 72% rename from resources/projects/demo/config/config.latte rename to resources/projects/nella/config/config.neon.latte index d91c302..596e25b 100644 --- a/resources/projects/demo/config/config.latte +++ b/resources/projects/nella/config/config.neon.latte @@ -4,16 +4,13 @@ php: date.timezone: Europe/Prague # session.save_path: %tempDir%/session +# ====================================== +# Parameters =========================== +parameters: + # ====================================== # Extension ============================ extensions: - sentry: Contributte\Sentry\DI\SentryExtension - -sentry: - enable: %productionMode% - - client: - dsn: "https://FILLME@FILLME.ingest.sentry.io/1FILLME" # ====================================== # Services ============================= diff --git a/resources/projects/nella/config/local.neon.example.latte b/resources/projects/nella/config/local.neon.example.latte new file mode 100644 index 0000000..12b6ebf --- /dev/null +++ b/resources/projects/nella/config/local.neon.example.latte @@ -0,0 +1,3 @@ +parameters: + +services: diff --git a/resources/projects/nella/crafter.neon b/resources/projects/nella/crafter.neon new file mode 100644 index 0000000..186ff21 --- /dev/null +++ b/resources/projects/nella/crafter.neon @@ -0,0 +1,27 @@ +# Schema version +version: 1 + +# List of crafters +crafters: + # Root + editorconfig: { input: 'raw://%cwd%/.editorconfig.latte', output: 'raw:///.editorconfig' } + gitignore: { input: %cwd%/.gitignore.latte, output: 'raw:///.gitignore' } + composer: { input: %cwd%/composer.json.latte, output: 'raw:///composer.json' } + makefile: { input: %cwd%/Makefile.latte, output: 'raw:///Makefile' } + phpstan: { input: %cwd%/phpstan.neon.latte, output: 'raw:///phpstan.neon' } + readme: { input: %cwd%/README.md.latte, output: 'raw:///README.md' } + ruleset: { input: %cwd%/ruleset.xml.latte, output: 'raw:///ruleset.xml' } + # App + app_bootstrap: { input: %cwd%/app/Bootstrap.php.latte, output: 'raw:///app/Bootstrap.php' } + app_ui_basepresenter: { input: %cwd%/app/UI/BasePresenter.php.latte, output: 'raw:///app/UI/BasePresenter.php' } + app_ui_home_homepresenter: { input: %cwd%/app/UI/Home/HomePresenter.php.latte, output: 'raw:///app/UI/Home/HomePresenter.php' } + app_ui_home_template_default: { input: %cwd%/app/UI/Home/Templates/default.latte.latte, output: 'raw:///app/UI/Home/Templates/default.latte' } + app_ui_layout: { input: %cwd%/app/UI/@Templates/@layout.latte.latte, output: 'raw:///app/UI/@Templates/@layout.latte' } + # Config + config_config: { input: %cwd%/config/config.neon.latte, output: 'raw:///config/config.neon' } + config_local: { input: %cwd%/config/local.neon.example.latte, output: 'raw:///config/local.neon.example' } + # Var + var_log_gitignore: { input: %cwd%/var/log/.gitignore.latte, output: 'raw:///var/log/.gitignore' } + var_tmp_gitignore: { input: %cwd%/var/tmp/.gitignore.latte, output: 'raw:///var/tmp/.gitignore' } + # Www + www_index: { input: %cwd%/www/index.php.latte, output: 'raw:///www/index.php' } diff --git a/resources/projects/nella/phpstan.neon.latte b/resources/projects/nella/phpstan.neon.latte new file mode 100644 index 0000000..9b86943 --- /dev/null +++ b/resources/projects/nella/phpstan.neon.latte @@ -0,0 +1,16 @@ +includes: + - vendor/contributte/phpstan/phpstan.neon + +parameters: + level: 9 + phpVersion: 80200 + + tmpDir: %currentWorkingDirectory%/var/tmp/phpstan + + fileExtensions: + - php + - phpt + + paths: + - app + - config diff --git a/resources/projects/_defaults/ruleset.latte b/resources/projects/nella/ruleset.xml.latte similarity index 90% rename from resources/projects/_defaults/ruleset.latte rename to resources/projects/nella/ruleset.xml.latte index b076ae3..66ded9d 100644 --- a/resources/projects/_defaults/ruleset.latte +++ b/resources/projects/nella/ruleset.xml.latte @@ -9,6 +9,7 @@ + diff --git a/resources/projects/_defaults/var/gitignore.latte b/resources/projects/nella/var/log/.gitignore.latte similarity index 100% rename from resources/projects/_defaults/var/gitignore.latte rename to resources/projects/nella/var/log/.gitignore.latte diff --git a/resources/projects/nella/var/tmp/.gitignore.latte b/resources/projects/nella/var/tmp/.gitignore.latte new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/resources/projects/nella/var/tmp/.gitignore.latte @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/resources/projects/demo/www/index.php b/resources/projects/nella/www/index.php.latte similarity index 67% rename from resources/projects/demo/www/index.php rename to resources/projects/nella/www/index.php.latte index 401948b..0996575 100644 --- a/resources/projects/demo/www/index.php +++ b/resources/projects/nella/www/index.php.latte @@ -2,4 +2,4 @@ require __DIR__ . '/../vendor/autoload.php'; -\templates\demo\app\Bootstrap::run(); +App\Bootstrap::run(); diff --git a/resources/projects/project.dsl.neon b/resources/projects/project.dsl.neon new file mode 100644 index 0000000..d9908a4 --- /dev/null +++ b/resources/projects/project.dsl.neon @@ -0,0 +1,14 @@ +# Schema version +version: 1 + +# List of crafters +crafters: + # Full + file: + input: %cwd%/templates/database/entity.latte + output: 'App\Domain\{$name|firstUpper}\Database\{$name|firstUpper}' + vars: + some: variable + + # Compact + www_index: { input: %cwd%/www/index.php.latte, output: 'raw://www/index.php' } diff --git a/ruleset.xml b/ruleset.xml index ff48637..d613fd6 100644 --- a/ruleset.xml +++ b/ruleset.xml @@ -1,13 +1,15 @@ - + + + - + diff --git a/src/Bootstrap.php b/src/Bootstrap.php index ab1dad0..07dc07d 100644 --- a/src/Bootstrap.php +++ b/src/Bootstrap.php @@ -1,12 +1,16 @@ service(ConfigLoader::class, fn (): ConfigLoader => new ConfigLoader()); + + // Template $container->service(TemplateRenderer::class, fn (): TemplateRenderer => new TemplateRenderer()); - $container->service(CrafterWorker::class, fn (TemplateRenderer $templateRenderer): CrafterWorker => new CrafterWorker($templateRenderer)); - $application = new Application('Mate', '0.1.0'); + // Generator worker + $container->service(GeneratorWorker::class, fn (RawResolver $rawResolver): GeneratorWorker => new GeneratorWorker($rawResolver)); + $container->service(RawResolver::class, fn (TemplateRenderer $templateRenderer): RawResolver => new RawResolver($templateRenderer)); + + // Crafter worker + $container->service(CrafterWorker::class, fn (PhpResolver $phpResolver, LatteResolver $latteResolver): CrafterWorker => new CrafterWorker($phpResolver, $latteResolver)); + $container->service(PhpResolver::class, fn (TemplateRenderer $templateRenderer): PhpResolver => new PhpResolver($templateRenderer)); + $container->service(LatteResolver::class, fn (TemplateRenderer $templateRenderer): LatteResolver => new LatteResolver($templateRenderer)); + + // Symfony Console + $application = new Application('Crafter', 'magic'); $application->add($container->createInstance(CraftCommand::class)); + $application->add($container->createInstance(GenerateCommand::class)); return $application; } diff --git a/src/Command/BaseCommand.php b/src/Command/BaseCommand.php index 7391312..4579ba1 100644 --- a/src/Command/BaseCommand.php +++ b/src/Command/BaseCommand.php @@ -1,6 +1,6 @@ addOption('struct', 's', InputOption::VALUE_REQUIRED, 'Structure definition'); + $this->addOption('data', 'k', InputOption::VALUE_REQUIRED, 'Data key definition'); $this->addOption('scope', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Scope definition'); } @@ -43,11 +42,19 @@ protected function execute(InputInterface $input, OutputInterface $output): int $ui = (new SymfonyStyle($input, $output))->getErrorStyle(); try { - /** @var object{ struct: string, scope: string[] } $options */ - $options = (new Processor())->process(Expect::structure([ - 'struct' => Expect::mixed()->assert(fn ($v) => $v !== null && $v !== '', 'Option --struct|-s must be filled'), - 'scope' => Expect::arrayOf('string')->default([]), - ])->otherItems(), $input->getOptions()); + /** @var object{ data: string, scope: string[] } $options */ + $options = (new Processor())->process( + Expect::structure([ + 'data' => Expect::mixed()->assert(fn ($v) => $v !== null && $v !== '', 'Option --data|-k must be filled'), + 'scope' => Expect::arrayOf('string'), + ])->otherItems() + ->before(function (array $v) { + $v['scope'] = $v['scope'] === [] ? ['default'] : $v['scope']; + + return $v; + }), + $input->getOptions() + ); } catch (ValidationException $e) { foreach ($e->getMessageObjects() as $message) { $ui->error($message->variables['assertion']); @@ -57,31 +64,33 @@ protected function execute(InputInterface $input, OutputInterface $output): int } // Input - $struct = $options->struct; + $key = $options->data; $cwd = Safe::getcwd(); - $configFile = $cwd . '/.mate.neon'; + $scopes = $options->scope ?? ['default']; + $configFile = $cwd . '/crafter.neon'; // Config - $config = $this->configLoader->load($cwd, $cwd . '/.mate.neon'); + $config = (new ConfigLoader()) + ->withCwd($cwd) + ->withFile($configFile) + ->load(); // HUD $ui->title('Input'); $ui->table([], [ ['CWD', $cwd], ['Config', $configFile], - ['Struct', $struct], - ['Presets', implode(',', $config->mate->presets)], - ['Scopes', implode(',', $options->scope)], + ['Structure', $key], + ['Presets', $config->app->preset], + ['Scopes', implode(',', $scopes)], ]); // Context - $workerContextFactory = new WorkerContextFactory(); - $workerContextFactory->withScopes($options->scope); - $workerContext = $workerContextFactory->from($config); + $workerContext = CrafterContext::from($config, $scopes); // Worker validation - if (!$config->structs->has($struct)) { - $ui->error(sprintf('Unknown struct reference "%s" in .mate.neon', $struct)); + if (!$config->data->has($key)) { + $ui->error(sprintf('Unknown data reference "%s" in crafter.neon', $key)); return Command::FAILURE; } diff --git a/src/Command/GenerateCommand.php b/src/Command/GenerateCommand.php new file mode 100644 index 0000000..6bccb32 --- /dev/null +++ b/src/Command/GenerateCommand.php @@ -0,0 +1,107 @@ +addOption('template', 't', InputOption::VALUE_REQUIRED, 'Project template'); + $this->addOption('directory', 'd', InputOption::VALUE_REQUIRED, 'Output directory'); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $ui = (new SymfonyStyle($input, $output))->getErrorStyle(); + + try { + /** @var object{ template: string, directory: string } $options */ + $options = (new Processor())->process( + Expect::structure([ + 'template' => Expect::mixed()->assert(fn ($v) => $v !== null && $v !== '', 'Option --template|-t must be filled'), + 'directory' => Expect::mixed()->default('.')->assert(fn ($v) => $v !== null && $v !== '', 'Option --directory|-d must be filled'), + ])->otherItems(), + $input->getOptions() + ); + } catch (ValidationException $e) { + foreach ($e->getMessageObjects() as $message) { + $ui->error($message->variables['assertion']); + } + + return Command::FAILURE; + } + + // Input + $template = $options->template; + $cwd = Safe::getcwd(); + + // Config + $config = (new ConfigLoader()) + ->withCwd($cwd) + ->withConfig([ + 'template' => $template, + ]) + ->load(); + + // HUD + $ui->title('Input'); + $ui->table([], [ + ['CWD', $cwd], + ['Template', $template], + ]); + + // Context + $generatorContext = GeneratorContext::from($config, dir: $options->directory); + + // Worker + $result = $this->generatorWorker->execute($generatorContext, $this->createLogger($output)); + + // HUD + $ui->title('Crafted'); + + foreach ($result->items as $item) { + if ($item['state'] === 'skipped') { + continue; + } + + $ui->block($item['crafter']); + $ui->table([], [ + ['Input', $item['input']], + ['Output', $item['output']], + ['Note', $item['note']], + ]); + } + + $ui->info(sprintf('Crafted: %d files', count(Arrays::filter($result->items, fn ($item) => $item['state'] === 'crafted')))); + + return Command::SUCCESS; + } + +} diff --git a/src/Command/InitCommand.php b/src/Command/InitCommand.php index c238d05..cd142aa 100644 --- a/src/Command/InitCommand.php +++ b/src/Command/InitCommand.php @@ -1,6 +1,6 @@ $vars * @param array $scopes + * @param array $crafters */ public function __construct( - public string $appDir, + public string $version, + public string $dir, public string $namespace, - public array $scopes, - public CraftersConfig $crafters, + public array $vars, + public string|null $preset, + public string|null $template, + public array $crafters, + public array $scopes = [], ) { } diff --git a/src/Config/CrafterConfig.php b/src/Config/CrafterConfig.php index f887108..0ee7a6b 100644 --- a/src/Config/CrafterConfig.php +++ b/src/Config/CrafterConfig.php @@ -1,20 +1,22 @@ $scopes + * @param array $vars */ public function __construct( - public string $crafter, - public string $template, - public string $class, - public string|null $mode = null, - public array $scopes = [], - public string|null $baseClass = null, + public string $id, + public string $name, + public string|null $mode, + public array $scopes, + public CrafterFileConfig $input, + public CrafterFileConfig $output, + public array $vars, ) { } diff --git a/src/Config/CrafterFileConfig.php b/src/Config/CrafterFileConfig.php new file mode 100644 index 0000000..a8f123b --- /dev/null +++ b/src/Config/CrafterFileConfig.php @@ -0,0 +1,19 @@ + $vars + */ + public function __construct( + public string $resolver, + public string $path, + public array $vars, + ) + { + } + +} diff --git a/src/Config/CraftersConfig.php b/src/Config/CraftersConfig.php deleted file mode 100644 index 6b5e20b..0000000 --- a/src/Config/CraftersConfig.php +++ /dev/null @@ -1,17 +0,0 @@ - $items - */ - public function __construct( - public array $items, - ) - { - } - -} diff --git a/src/Config/InputConfig.php b/src/Config/CraftingConfig.php similarity index 53% rename from src/Config/InputConfig.php rename to src/Config/CraftingConfig.php index 63b5343..bcda469 100644 --- a/src/Config/InputConfig.php +++ b/src/Config/CraftingConfig.php @@ -1,15 +1,14 @@ $items + * @param array $items */ public function __construct( public array $items, diff --git a/src/Config/DataObjectConfig.php b/src/Config/DataObjectConfig.php new file mode 100644 index 0000000..ffdebb2 --- /dev/null +++ b/src/Config/DataObjectConfig.php @@ -0,0 +1,20 @@ + $vars + */ + public function __construct( + public string $name, + public array $fields, + public array $vars, + ) + { + } + +} diff --git a/src/Config/StructFieldConfig.php b/src/Config/DataObjectField.php similarity index 70% rename from src/Config/StructFieldConfig.php rename to src/Config/DataObjectField.php index f8dbf94..955a320 100644 --- a/src/Config/StructFieldConfig.php +++ b/src/Config/DataObjectField.php @@ -1,8 +1,8 @@ */ + private array $configs = []; + + /** @var array */ + private array $files = []; + + public function __construct() + { + // No-op + } + + public function withCwd(string $cwd): self + { + $this->cwd = $cwd; + + return $this; + } + + /** + * @param array $config + */ + public function withConfig(array $config): self + { + $this->configs[] = $config; + + return $this; + } + + public function withFile(string $file): self + { + $this->files[] = $file; + + return $this; + } + + public function load(): CraftingConfig + { + if ($this->cwd === null) { + throw new LogicalException('Missing cwd'); + } + + // Defaults + $defaults = [ + 'version' => '1', + 'dir' => 'src', + 'namespace' => 'App', + 'preset' => 'default', + 'vars' => [], + 'crafters' => [], + 'data' => [], + ]; + + // Merge current with defaults + $current = $this->mergeConfig([], $defaults); // @phpstan-ignore-line + + // (1) Take file configs + foreach ($this->files as $file) { + // Get file config + $fileConfig = $this->getFileConfig($file); + + // Merge current with file config + $current = $this->mergeConfig($fileConfig, $current); + } + + // (2) Take array configs + foreach ($this->configs as $config) { + // Merge current with user config + $current = $this->mergeConfig($config, $current); // @phpstan-ignore-line + } + + // Process config (presets, templates) + $processed = $this->processConfig($current); + + // Create crafting config + return $this->createConfig($this->cwd, $processed); + } + + /** + * @phpstan-param ConfigShape $left + * @phpstan-param ConfigShape $right + * @phpstan-return ConfigShape + */ + public function mergeConfig(array $left, array $right): array { - /** @phpstan-var ConfigShape $config */ - $config = $this->loadFile($file); + /** @var ConfigShape $return */ + $return = Helpers::merge($left, $right); - // Merge presets - $presets = $config['mate']['presets'] ?? ['default']; + return $return; + } - foreach ($presets as $preset) { - $presetDir = IO::realpath(__DIR__ . '/../../../resources/presets/' . $preset); - $presetFile = $this->loadFile($presetDir . '/config.neon'); + /** + * @phpstan-param ConfigShape $config + * @phpstan-return ConfigShape + */ + public function processConfig(array $config): array + { + // Process preset + $preset = $config['preset'] ?? null; + + if ($preset !== null) { + $presetDir = __DIR__ . '/../../../resources/presets/' . $preset; + + // Skip non-existing presets + if (!is_dir($presetDir)) { + throw new RuntimeException(sprintf('Preset template "%s" not found on path "%s"', $preset, $presetDir)); + } + + $presetFile = $this->getPresetConfig($presetDir . '/crafter.neon'); // Expand variables - $presetConfig = DIHelpers::expand($presetFile, ['presetDir' => $presetDir], true); + /** @var array{crafter: CraftersShape} $presetConfig */ + $presetConfig = DIHelpers::expand($presetFile, ['cwd' => $presetDir], true); - /** @phpstan-var ConfigShape $config */ - $config = SchemaHelpers::merge($presetConfig, $config); + // Merge user defined crafters and template crafters + $config['crafters'] = Arrays::mergeTree($config['crafters'] ?? [], $presetConfig['crafters'] ?? []); // @phpstan-ignore-line } - return new InputConfig( - mate: new MateConfig( - presets: $presets, - ), + // Process template + $template = $config['template'] ?? null; + + if ($template !== null) { + $projectDir = __DIR__ . '/../../../resources/projects/' . $template; + + // Skip non-existing presets + if (!is_dir($projectDir)) { + throw new RuntimeException(sprintf('Project template "%s" not found on path "%s"', $template, $projectDir)); + } + + $projectFile = $this->getProjectConfig($projectDir . '/crafter.neon'); + + // Expand variables + /** @var array{crafter: CraftersShape} $projectConfig */ + $projectConfig = DIHelpers::expand($projectFile, ['cwd' => $projectDir], true); + + // Merge user defined crafters and template crafters + $config['crafters'] = Arrays::mergeTree($config['crafters'] ?? [], $projectConfig['crafters'] ?? []); // @phpstan-ignore-line + } + + return $config; + } + + /** + * @phpstan-param ConfigShape $config + */ + private function createConfig(string $cwd, array $config): CraftingConfig + { + return new CraftingConfig( process: new ProcessConfig( cwd: $cwd, ), app: new AppConfig( - appDir: $config['app']['appDir'], - namespace: $config['app']['namespace'], - scopes: $config['app']['scopes'] ?? [], - crafters: new CraftersConfig( - items: Arrays::map( - $config['app']['crafters'] ?? [], - fn (array $crafter): CrafterConfig => new CrafterConfig( - crafter: $crafter['crafter'], - template: $crafter['template'], - class: $crafter['class'], - mode: $crafter['mode'] ?? null, - scopes: $crafter['scopes'] ?? [], - baseClass: $crafter['baseClass'] ?? null - ) - ) + version: $config['version'], + dir: $config['dir'], + namespace: $config['namespace'], + vars: $config['vars'] ?? [], + preset: $config['preset'] ?? null, + template: $config['template'] ?? null, + crafters: Arrays::map( + $config['crafters'], + function (array $crafter, string $crafterId): CrafterConfig { + // Parse crafter id + // E.q.: presenter:create@db+admin -> [presenter:create, db+admin] + $matches = explode('@', $crafterId); + + // Parse crafter mode + // E.q.: presenter:create -> [presenter, create] + $matches2 = explode(':', $matches[0]); + + // Name + // E.q.: [presenter, create] -> presenter + $name = $matches2[0]; + + // Mode + // E.q.: presenter:create -> php + $mode = $matches2[1] ?? null; + + // Scopes + // E.q.: db+admin -> [db, admin] + // E.q.: null -> [default] + $scopes = explode('+', $matches[1] ?? 'default'); + + // Input (@regex https://regex101.com/r/1zInYi/1) + // E.q.: presenter/presenter.latte [resolver: latte, path: presenter/presenter.latte] + // E.q.: raw://presenter/presenter.latte [resolver: raw, path: presenter/presenter.latte] + $inputMatch = Strings::match($crafter['input'], '#^(?:(?P\w+):\/\/)?(?P.+)$#') ?? throw new LogicalException(sprintf('Invalid input format "%s"', $crafter['input'])); + $input = new CrafterFileConfig( + resolver: Validators::empty($inputMatch['schema']) ? Files::extension($crafter['input']) : $inputMatch['schema'], + path: $inputMatch['path'], + vars: [] + ); + + // Output (@regex https://regex101.com/r/1zInYi/1) + // E.q.: presenter/presenter.latte [resolver: latte, path: presenter/presenter.latte] + // E.q.: raw://presenter/presenter.latte [resolver: raw, path: presenter/presenter.latte] + $outputMatch = Strings::match($crafter['output'], '#^(?:(?P\w+):\/\/)?(?P.+)$#') ?? throw new LogicalException(sprintf('Invalid output format "%s"', $crafter['output'])); + $output = new CrafterFileConfig( + resolver: Validators::empty($outputMatch['schema']) ? Files::extension($crafter['output']) : $outputMatch['schema'], + path: $outputMatch['path'], + vars: [] + ); + + return new CrafterConfig( + id: $crafterId, + name: $name, + mode: $mode, + scopes: $scopes, + input: $input, + output: $output, + vars: $crafter['vars'] ?? [] + ); + } ) ), - structs: new StructsConfig( + data: new DataConfig( items: Arrays::map( - $config['structs'] ?? [], - fn (array $struct, string $structKey): StructConfig => new StructConfig( - name: $structKey, + $config['data'], + fn (array $data, string $dataKey): DataObjectConfig => new DataObjectConfig( + name: $dataKey, fields: Arrays::map( - $struct['fields'] ?? [], - fn (array $field, string $fieldKey): StructFieldConfig => new StructFieldConfig( + $data['fields'], + fn (array $field, string $fieldKey): DataObjectField => new DataObjectField( name: $fieldKey, type: $field['type'], nullable: $field['nullable'] ?? false ) - ) + ), + vars: $data['vars'] ?? [] ) ) ) @@ -87,14 +264,108 @@ class: $crafter['class'], /** * @phpstan-return ConfigShape */ - private function loadFile(string $file): array + private function getFileConfig(string $file): array + { + // Read file + $file = FileSystem::read($file); + + /** @var mixed $config */ + $config = Neon::decode($file); + + // Validate + $schema = Expect::structure([ + 'version' => Expect::anyOf('1')->before(fn (mixed $v) => is_scalar($v) ? strval($v) : $v)->default('1'), + 'dir' => Expect::string()->required(), + 'namespace' => Expect::string()->default('App'), + 'vars' => Expect::arrayOf('string')->default([]), + 'preset' => Expect::string(), + 'template' => Expect::string(), + 'crafters' => Expect::arrayOf( + Expect::structure([ + 'input' => Expect::string()->required(), + 'output' => Expect::string()->required(), + 'vars' => Expect::arrayOf('string')->default([]), + ])->castTo('array'), + Expect::string() + ), + 'data' => Expect::arrayOf( + Expect::structure([ + 'fields' => Expect::arrayOf( + Expect::structure([ + 'type' => Expect::string()->required(), + 'nullable' => Expect::bool()->default(false), + ])->castTo('array'), + Expect::string() + ), + 'vars' => Expect::arrayOf('string')->default([]), + ])->castTo('array'), + Expect::string() + ), + ])->castTo('array'); + + /** @var ConfigShape $config */ + $config = (new Processor())->process($schema, $config); + + return $config; + } + + /** + * @return array + */ + private function getPresetConfig(string $file): array { // Read file $file = FileSystem::read($file); - /** @phpstan-var ConfigShape $config */ + /** @var mixed $config */ $config = Neon::decode($file); + // Validate + $schema = Expect::structure([ + 'version' => Expect::anyOf('1')->before(fn (mixed $v) => is_scalar($v) ? strval($v) : $v)->default('1'), + 'crafters' => Expect::arrayOf( + Expect::structure([ + 'input' => Expect::string()->required(), + 'output' => Expect::string()->required(), + 'vars' => Expect::arrayOf('string')->default([]), + ])->castTo('array'), + Expect::string() + ), + ])->castTo('array'); + + /** @var array $config */ + $config = (new Processor())->process($schema, $config); + + return $config; + } + + /** + * @return array + */ + private function getProjectConfig(string $file): array + { + // Read file + $file = FileSystem::read($file); + + /** @var mixed $config */ + $config = Neon::decode($file); + + // Validate + $schema = Expect::structure([ + 'version' => Expect::anyOf('1')->before(fn (mixed $v) => is_scalar($v) ? strval($v) : $v)->default('1'), + 'crafters' => Expect::arrayOf( + Expect::structure([ + 'input' => Expect::string()->required(), + 'output' => Expect::string()->required(), + 'vars' => Expect::arrayOf('string')->default([]), + ])->castTo('array'), + Expect::string() + ), + ])->castTo('array'); + + /** @var array $config */ + $config = (new Processor())->process($schema, $config); + return $config; } diff --git a/src/Config/MateConfig.php b/src/Config/MateConfig.php deleted file mode 100644 index 9bf4df6..0000000 --- a/src/Config/MateConfig.php +++ /dev/null @@ -1,17 +0,0 @@ - $presets - */ - public function __construct( - public array $presets, - ) - { - } - -} diff --git a/src/Config/ProcessConfig.php b/src/Config/ProcessConfig.php index 5ff120b..017b879 100644 --- a/src/Config/ProcessConfig.php +++ b/src/Config/ProcessConfig.php @@ -1,6 +1,6 @@ mate->app->scopes === []) { - throw new LogicalException('No scope defined'); - } - - $result = new CrafterResult(); - - foreach ($workerContext->mate->app->crafters->items as $crafterKey => $crafter) { - // Skip crafter if not in scope - if (array_intersect($crafter->scopes, $workerContext->mate->app->scopes) === []) { - $result->add( - crafter: $crafterKey, - state: 'skipped', - note: 'Scope mismatch', - ); - - continue; - } - - foreach ($workerContext->mate->structs->items as $struct) { - // Prepare context - $resolvedClass = $this->templateRenderer->render($crafter->class, ['name' => $struct->name]); - $resolvedClassName = Classes::getClassName($resolvedClass); - $resolvedNamespace = Classes::getNamespace($resolvedClass); - $resolvedFilename = Strings::replace($resolvedClass, '#\\\#', '/'); - - $context = new TemplateContext( - class: new ClassContext( - class: $resolvedClass, - className: $resolvedClassName, - namespace: $resolvedNamespace, - extends: $crafter->baseClass, - ), - helper: new HelperContext( - structVar: sprintf('$%s', $struct->name), - entityClassName: sprintf('%s', ucfirst($struct->name)), - dtoClassName: sprintf('%sDto', ucfirst($struct->name)), - ), - struct: new StructContext( - name: $struct->name, - fields: Arrays::map( - $struct->fields, - fn (StructFieldConfig $field): StructFieldContext => new StructFieldContext( - name: $field->name, - type: $field->type, - nullable: $field->nullable, - ) - ) - ) - ); - - // Craft (input & output) - $intputFile = IO::realpath($crafter->template); - $outputFile = $workerContext->mate->process->cwd . '/' . lcfirst($resolvedFilename) . '.php'; - - $outputContent = $this->templateRenderer->renderFile( - file: $intputFile, - params: ['ctx' => $context] - ); - - FileSystem::write($outputFile, $outputContent); - - $result->add( - crafter: $crafterKey, - state: 'crafted', - input: $intputFile, - output: $outputFile, - ); - } - } - - return $result; - } - -} diff --git a/src/Crafter/Worker/WorkerContext.php b/src/Crafter/Worker/WorkerContext.php deleted file mode 100644 index 6863d7a..0000000 --- a/src/Crafter/Worker/WorkerContext.php +++ /dev/null @@ -1,16 +0,0 @@ - */ - private array $scopes = []; - - /** - * @param array $scopes - */ - public function withScopes(array $scopes): self - { - $this->scopes = $scopes; - - return $this; - } - - public function from(InputConfig $mateConfig): WorkerContext - { - if ($this->scopes !== []) { - $mateConfig->app->scopes = $this->scopes; - } - - return new WorkerContext( - mate: $mateConfig, - ); - } - -} diff --git a/src/DI/BetterContainer.php b/src/DI/BetterContainer.php index 5820510..da33e2a 100644 --- a/src/DI/BetterContainer.php +++ b/src/DI/BetterContainer.php @@ -1,6 +1,6 @@ $params */ - public function renderFile(string $file, array $params = []): string + public function renderPhp(string $file, array $params = []): string { return $this->render(FileSystem::read($file), $params); } + /** + * @param array $params + */ + public function renderLatte(string $file, array $params = []): string + { + return $this->render('{syntax double}' . FileSystem::read($file), $params); + } + } diff --git a/src/Utils/Classes.php b/src/Utils/Classes.php index fc78af4..96cbf89 100644 --- a/src/Utils/Classes.php +++ b/src/Utils/Classes.php @@ -1,6 +1,6 @@ true, + $value === '' => true, + default => false, + }; + } + +} diff --git a/src/Worker/Crafter/CrafterContext.php b/src/Worker/Crafter/CrafterContext.php new file mode 100644 index 0000000..ad4ddfd --- /dev/null +++ b/src/Worker/Crafter/CrafterContext.php @@ -0,0 +1,33 @@ + $scopes + */ + public static function from( + CraftingConfig $craftingConfig, + array $scopes = [] + ): self + { + if ($scopes !== []) { + $craftingConfig->app->scopes = $scopes; + } + + return new self( + craftingConfig: $craftingConfig, + ); + } + +} diff --git a/src/Crafter/Worker/CrafterResult.php b/src/Worker/Crafter/CrafterResult.php similarity index 91% rename from src/Crafter/Worker/CrafterResult.php rename to src/Worker/Crafter/CrafterResult.php index 5c00f2b..41f47af 100644 --- a/src/Crafter/Worker/CrafterResult.php +++ b/src/Worker/Crafter/CrafterResult.php @@ -1,6 +1,6 @@ craftingConfig->app->crafters as $crafterKey => $crafter) { + // Skip crafter if not in scope + if (array_intersect($crafter->scopes, $workerContext->craftingConfig->app->scopes) === []) { + $result->add( + crafter: $crafterKey, + state: 'skipped', + note: 'Scope mismatch', + ); + + continue; + } + + foreach ($workerContext->craftingConfig->data->items as $structure) { + match ($crafter->output->resolver) { + 'php' => $this->phpResolver->resolve($workerContext, $crafter, $structure, $result), + 'latte' => $this->latteResolver->resolve($workerContext, $crafter, $structure, $result), + default => throw new LogicalException(sprintf('Unknown template resolver "%s"', $crafter->output->resolver)), + }; + } + } + + return $result; + } + +} diff --git a/src/Worker/Crafter/Resolver/Latte/HelperContext.php b/src/Worker/Crafter/Resolver/Latte/HelperContext.php new file mode 100644 index 0000000..ad31d50 --- /dev/null +++ b/src/Worker/Crafter/Resolver/Latte/HelperContext.php @@ -0,0 +1,14 @@ +templateRenderer->render($crafterConfig->output->path, ['name' => $structureConfig->name]); + $resolvedFilename = Strings::replace($resolvedFile, '#\\\#', '/'); + + $context = new TemplateContext( + helper: new HelperContext( + structVar: sprintf('$%s', $structureConfig->name), + ), + struct: new StructContext( + name: $structureConfig->name, + fields: Arrays::map( + $structureConfig->fields, + fn (DataObjectField $field): StructFieldContext => new StructFieldContext( + name: $field->name, + type: $field->type, + nullable: $field->nullable, + ) + ) + ) + ); + + // Craft (input & output) + $intputFile = IO::realpath($crafterConfig->input->path); + $outputFile = $crafterContext->craftingConfig->process->cwd + . '/' . $crafterContext->craftingConfig->app->dir + . '/' . $resolvedFilename; + + $outputContent = $this->templateRenderer->renderLatte( + file: $intputFile, + params: ['ctx' => $context] + ); + + FileSystem::write($outputFile, $outputContent); + + $crafterResult->add( + crafter: $crafterConfig->id, + state: 'crafted', + input: $intputFile, + output: $outputFile, + ); + } + +} diff --git a/src/Config/StructConfig.php b/src/Worker/Crafter/Resolver/Latte/StructContext.php similarity index 51% rename from src/Config/StructConfig.php rename to src/Worker/Crafter/Resolver/Latte/StructContext.php index 476b331..b6298ef 100644 --- a/src/Config/StructConfig.php +++ b/src/Worker/Crafter/Resolver/Latte/StructContext.php @@ -1,12 +1,12 @@ MyApp\UI\HomepagePresenter.php + $resolvedFile = $this->templateRenderer->render($crafterConfig->output->path, [ + 'namespace' => $crafterContext->craftingConfig->app->namespace, + 'name' => $structureConfig->name, + ]); + + // Resolve classname + // E.q. MyApp\UI\HomepagePresenter.php -> MyApp\\UI\\HomepagePresenter + $resolvedClassName = Strings::replace($resolvedFile, '#\.php$#', ''); + $resolvedClassName = Classes::getClassName($resolvedClassName); + + // Resolve namespace + // E.q. MyApp\UI\HomepagePresenter -> MyApp\UI + $resolvedNamespace = Classes::getNamespace($resolvedFile); + + // Resolve namespace + // E.q. MyApp\UI\HomepagePresenter -> MyApp + $resolvedRootNamespace = Classes::getRootNamespace($resolvedFile); + + // Resolve filename + // E.q. MyApp\UI\HomepagePresenter.php -> UI/HomepagePresenter + $resolvedFilename = Strings::replace($resolvedFile, '#\\\#', '/'); + $resolvedFilename = Strings::replace($resolvedFilename, '#^' . $crafterContext->craftingConfig->app->namespace . '/#', ''); + + $context = new TemplateContext( + class: new ClassContext( + className: $resolvedClassName, + namespace: $resolvedNamespace, + rootNamespace: $resolvedRootNamespace, + ), + helper: new HelperContext( + structVar: sprintf('$%s', $structureConfig->name), + entityClassName: sprintf('%s', ucfirst($structureConfig->name)), + dtoClassName: sprintf('%sDto', ucfirst($structureConfig->name)), + ), + struct: new StructContext( + name: $structureConfig->name, + fields: Arrays::map( + $structureConfig->fields, + fn (DataObjectField $field): StructFieldContext => new StructFieldContext( + name: $field->name, + type: $field->type, + nullable: $field->nullable, + ) + ) + ) + ); + + // Craft (input & output) + $intputFile = IO::realpath($crafterConfig->input->path); + $outputFile = $crafterContext->craftingConfig->process->cwd + . '/' . $crafterContext->craftingConfig->app->dir + . '/' . $resolvedFilename; + + $outputContent = $this->templateRenderer->renderPhp( + file: $intputFile, + params: ['ctx' => $context] + ); + + FileSystem::write($outputFile, $outputContent); + + $crafterResult->add( + crafter: $crafterConfig->id, + state: 'crafted', + input: $intputFile, + output: $outputFile, + ); + } + +} diff --git a/src/Crafter/Template/StructContext.php b/src/Worker/Crafter/Resolver/Php/StructContext.php similarity index 77% rename from src/Crafter/Template/StructContext.php rename to src/Worker/Crafter/Resolver/Php/StructContext.php index 72e482f..2be627e 100644 --- a/src/Crafter/Template/StructContext.php +++ b/src/Worker/Crafter/Resolver/Php/StructContext.php @@ -1,6 +1,6 @@ app->dir = $dir; + } + + return new self( + craftingConfig: $craftingConfig, + ); + } + +} diff --git a/src/Worker/Generator/GeneratorResult.php b/src/Worker/Generator/GeneratorResult.php new file mode 100644 index 0000000..10dac32 --- /dev/null +++ b/src/Worker/Generator/GeneratorResult.php @@ -0,0 +1,28 @@ + */ + public array $items = []; + + public function add( + string $crafter, + string $state, + string|null $note = null, + string|null $input = null, + string|null $output = null, + ): void + { + $this->items[] = [ + 'crafter' => $crafter, + 'state' => $state, + 'note' => $note, + 'input' => $input, + 'output' => $output, + ]; + } + +} diff --git a/src/Worker/Generator/GeneratorWorker.php b/src/Worker/Generator/GeneratorWorker.php new file mode 100644 index 0000000..d02bfed --- /dev/null +++ b/src/Worker/Generator/GeneratorWorker.php @@ -0,0 +1,32 @@ +craftingConfig->app->crafters as $crafter) { + match ($crafter->output->resolver) { + 'raw' => $this->rawResolver->resolve($generatorContext, $crafter, $result), + default => throw new LogicalException(sprintf('Unknown template resolver "%s"', $crafter->output->resolver)), + }; + } + + return $result; + } + +} diff --git a/src/Worker/Generator/Resolver/Raw/RawResolver.php b/src/Worker/Generator/Resolver/Raw/RawResolver.php new file mode 100644 index 0000000..0a275de --- /dev/null +++ b/src/Worker/Generator/Resolver/Raw/RawResolver.php @@ -0,0 +1,51 @@ +templateRenderer->render($crafterConfig->output->path); + $resolvedFilename = Strings::replace($resolvedFile, '#\\\#', '/'); + + // Craft (input & output) + $intputFile = IO::realpath($crafterConfig->input->path); + $outputFile = $generatorContext->craftingConfig->process->cwd + . '/' . $generatorContext->craftingConfig->app->dir + . '/' . $resolvedFilename; + + $outputContent = FileSystem::read($intputFile); + + FileSystem::write($outputFile, $outputContent); + + $generatorResult->add( + crafter: $crafterConfig->id, + state: 'crafted', + input: $intputFile, + output: $outputFile, + ); + } + +} diff --git a/src/Worker/Generator/Resolver/ResolverInterface.php b/src/Worker/Generator/Resolver/ResolverInterface.php new file mode 100644 index 0000000..3c4bc87 --- /dev/null +++ b/src/Worker/Generator/Resolver/ResolverInterface.php @@ -0,0 +1,18 @@ +in(__DIR__ . '/__files__') as $file) { + $case = Neon::decodeFile($file->getRealPath()); + + $loader = (new ConfigLoader()) + ->withCwd(__DIR__) + ->withConfig($case['input']) + ->load(); + + Assert::match( + Json::encode($case['output'], pretty: true), + Json::encode($loader, pretty: true), + ); + } +}); diff --git a/tests/Cases/Config/Loader/__files__/config1.neon b/tests/Cases/Config/Loader/__files__/config1.neon new file mode 100644 index 0000000..c5394a1 --- /dev/null +++ b/tests/Cases/Config/Loader/__files__/config1.neon @@ -0,0 +1,69 @@ +input: + dir: src + namespace: MyApp + preset: nette + + data: + user: + fields: + username: {type: string} + email: {type: string} + +output: + process: + cwd: %a%/tests/Cases/Config/Loader + app: + version: "1" + dir: src + namespace: MyApp + vars: [] + preset: nette + template: null + crafters: + presenter: + id: presenter + name: presenter + mode: null + scopes: [ + default + ] + input: + resolver: latte + path: "%a%/resources/presets/nette/templates/presenter/presenter.latte" + vars: [] + output: + resolver: php + path: "/{$namespace}\\UI\\{$name|firstUpper}\\{$name|firstUpper}Presenter.php" + vars: [] + vars: [] + presenter_template: + id: presenter_template + name: presenter_template + mode: null + scopes: [ + default + ] + input: + resolver: latte + path: "%a%/resources/presets/nette/templates/presenter/template.latte" + vars: [] + output: + resolver: latte + path: "/UI/{$name|firstUpper}/Templates/default.latte" + vars: [] + vars: [] + scopes: [] + data: + items: + user: + name: user + fields: + username: + name: username + type: string + nullable: false + email: + name: email + type: string + nullable: false + vars: [] diff --git a/tests/Cases/Mate.phpt b/tests/Cases/Mate.phpt index 8e111af..871706e 100644 --- a/tests/Cases/Mate.phpt +++ b/tests/Cases/Mate.phpt @@ -1,6 +1,6 @@