mirror of
https://github.com/ZorgCC/lumen-generators.git
synced 2024-12-26 14:05:29 +03:00
Starting ModelCommand
This commit is contained in:
parent
e41e7a97f3
commit
09071f4b53
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
/vendor/
|
||||
/about/
|
||||
*.sublime-*
|
||||
|
||||
tests/_output/*
|
@ -1 +1,3 @@
|
||||
# lumen-rest-generators
|
||||
|
||||
Restful resources generators for Lumen and Laravel 5.
|
21
composer.json
Normal file
21
composer.json
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "wn/lumen-rest-generators",
|
||||
"description": "Restful resources generators for Lumen and Laravel 5.",
|
||||
"keywords": ["lumen", "laravel", "rest", "api", "generators"],
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Amine Ben hammou",
|
||||
"email": "webneat@gmail.com"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.4.0",
|
||||
"illuminate/console": "^5.1"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Wn\\Generators\\": "src/"
|
||||
}
|
||||
}
|
||||
}
|
2177
composer.lock
generated
Normal file
2177
composer.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
0
formats/fields.json
Normal file
0
formats/fields.json
Normal file
43
src/Argument/ArgumentFormat.php
Normal file
43
src/Argument/ArgumentFormat.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php namespace Wn\Generators\Argument;
|
||||
|
||||
|
||||
class ArgumentFormat {
|
||||
|
||||
/**
|
||||
* Argument name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* One of the types: array, object, string, boolean, number
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $type;
|
||||
|
||||
/**
|
||||
* The default value
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $default;
|
||||
|
||||
/**
|
||||
* The separator, by default ":" for object and "," for array.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $separator;
|
||||
|
||||
/**
|
||||
* Specify the format of fields for object
|
||||
* [ field_name => Format, field_name => Format, ... ]
|
||||
* Specify the format of an element of array
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
public $format;
|
||||
|
||||
}
|
142
src/Argument/ArgumentFormatLoader.php
Normal file
142
src/Argument/ArgumentFormatLoader.php
Normal file
@ -0,0 +1,142 @@
|
||||
<?php namespace Wn\Generators\Argument;
|
||||
|
||||
|
||||
class ArgumentFormatLoader {
|
||||
|
||||
protected $fs;
|
||||
|
||||
protected $loaded;
|
||||
|
||||
public function __construct(Filesystem $fs)
|
||||
{
|
||||
$this->fs = $fs;
|
||||
$this->loaded = [];
|
||||
}
|
||||
|
||||
public function load($name)
|
||||
{
|
||||
if(! isset($this->loaded[$name])){
|
||||
$path = __DIR__ . "/../../formats/{$name}.json";
|
||||
$json = "";
|
||||
try {
|
||||
$json = json_decode($this->fs->get($path));
|
||||
} catch(\Exception $e) {
|
||||
throw new ArgumentFormatException("Unable to read the file '{$path}'");
|
||||
}
|
||||
if (json_last_error() !== JSON_ERROR_NONE){
|
||||
throw new ArgumentFormatException("Error while parsing the JSON file '{$path}'");
|
||||
}
|
||||
$this->loaded[$name] = $this->buildFormat($json);
|
||||
}
|
||||
return $this->loaded[$name];
|
||||
}
|
||||
|
||||
protected function buildFormat($obj)
|
||||
{
|
||||
return $this->fillFirstLevel($obj);
|
||||
}
|
||||
|
||||
protected function fillFirstLevel($obj)
|
||||
{
|
||||
return $this->fill($obj, true);
|
||||
}
|
||||
|
||||
protected function fill($obj, $firstLevel = false)
|
||||
{
|
||||
if (is_string($obj)) {
|
||||
return $this->fillString($obj, $firstLevel);
|
||||
} else {
|
||||
return $this->fillObject($obj, $firstLevel);
|
||||
}
|
||||
}
|
||||
|
||||
protected function fillString($string, $firstLevel = false)
|
||||
{
|
||||
list($name, $type, $isArray) = $this->parseName($string);
|
||||
|
||||
$format = new ArgumentFormat;
|
||||
$format->name = $name;
|
||||
|
||||
if ($isArray) {
|
||||
$format->type = 'array';
|
||||
$subFormat = new Format;
|
||||
$subFormat->type = $type ?: 'string';
|
||||
$format->format = $subFormat;
|
||||
} else {
|
||||
$format->type = 'string';
|
||||
}
|
||||
|
||||
if ($firstLevel) {
|
||||
if ($format->type === 'object') {
|
||||
$format->separator = ':';
|
||||
} elseif ($format->type === 'array') {
|
||||
$format->separator = ',';
|
||||
}
|
||||
}
|
||||
|
||||
return $format;
|
||||
}
|
||||
|
||||
protected function fillObject($obj, $firstLevel=false)
|
||||
{
|
||||
$format = new ArgumentFormat;
|
||||
|
||||
// Resolve type from name
|
||||
if (isset($obj->name)) {
|
||||
list($name, $type) = $this->parseName($obj->name);
|
||||
$format->name = $name;
|
||||
$format->type = $type;
|
||||
}
|
||||
|
||||
// Fill type if set
|
||||
if (isset($obj->type)) {
|
||||
$format->type = $obj->type;
|
||||
}
|
||||
|
||||
// Fill default if set
|
||||
if (isset($obj->default)) {
|
||||
$format->default = $obj->default;
|
||||
}
|
||||
|
||||
// Set separator, default to ':' for objects and ',' for arrays in first level
|
||||
if (in_array($format->type, ['object', 'array'])) {
|
||||
if (isset($obj->separator)) {
|
||||
$format->separator = $obj->separator;
|
||||
} elseif ($firstLevel) {
|
||||
$format->separator = ($format->type === 'object') ? ':':',';
|
||||
}
|
||||
}
|
||||
|
||||
// Build format recursively
|
||||
if (isset($obj->fields)) {
|
||||
if ($firstLevel) {
|
||||
if (is_array($obj->fields)) {
|
||||
$format->format = array_map([$this, 'fillFirstLevel'], $obj->fields);
|
||||
} else {
|
||||
$format->format = $this->fill($obj->fields, true);
|
||||
}
|
||||
} else {
|
||||
if (is_array($obj->fields)) {
|
||||
$format->format = array_map([$this, 'fill'], $obj->fields);
|
||||
} else {
|
||||
$format->format = $this->fill($obj->fields);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $format;
|
||||
}
|
||||
|
||||
protected function parseName($name)
|
||||
{
|
||||
$pattern = '/^(?P<attr>\w+)(\[(?P<type>\w+)?\])?/';
|
||||
preg_match($pattern, $name, $matches);
|
||||
|
||||
$attr = $matches['attr'];
|
||||
$type = empty($matches['type']) ? null : $matches['type'];
|
||||
$isArray = (@$matches[2][0] === '[');
|
||||
|
||||
return [$attr, $type, $isArray];
|
||||
}
|
||||
|
||||
}
|
114
src/Argument/ArgumentParser.php
Normal file
114
src/Argument/ArgumentParser.php
Normal file
@ -0,0 +1,114 @@
|
||||
<?php namespace Wn\Generators\Argument;
|
||||
|
||||
use Wn\Generators\Argument\ArgumentFormat;
|
||||
|
||||
|
||||
class ArgumentParser {
|
||||
|
||||
protected $format;
|
||||
|
||||
public function __construct(ArgumentFormat $format)
|
||||
{
|
||||
$this->format = $format;
|
||||
}
|
||||
|
||||
public function parse($args)
|
||||
{
|
||||
return $this->parseToken($args, $this->format);
|
||||
}
|
||||
|
||||
protected function parseToken($token, ArgumentFormat $format){
|
||||
switch($format->type) {
|
||||
case 'string':
|
||||
return $token;
|
||||
case 'number':
|
||||
return $this->parseNumber($token);
|
||||
case 'boolean':
|
||||
return $this->parseBoolean($token, $format->name);
|
||||
case 'array':
|
||||
return $this->parseArray($token, $format->separator, $format->format);
|
||||
case 'object':
|
||||
return $this->parseObject($token, $format->separator, $format->format);
|
||||
default:
|
||||
throw new ArgumentParserException("Unknown format type: '{$format->type}'");
|
||||
}
|
||||
}
|
||||
|
||||
protected function parseNumber($token)
|
||||
{
|
||||
if(! is_numeric($token)) {
|
||||
throw new ArgumentParserException("Unable to parse '{$token}' as number");
|
||||
}
|
||||
return $token + 0;
|
||||
}
|
||||
|
||||
protected function parseBoolean($token, $name)
|
||||
{
|
||||
if(in_array($token, ['yes', 'true', '1', $name])) {
|
||||
return true;
|
||||
} else if(in_array($token, ['no', 'false', '0', "!{$name}"])){
|
||||
return false;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected function parseArray($token, $separator, Format $format)
|
||||
{
|
||||
$result = [];
|
||||
$tokens = explode($separator, $token);
|
||||
foreach($tokens as $value) {
|
||||
array_push($result, $this->parseToken($value, $format));
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
protected function parseObject($token, $separator, $fields)
|
||||
{
|
||||
$result = [];
|
||||
$tokens = explode($separator, $token);
|
||||
$tokensNumber = count($tokens);
|
||||
|
||||
$requiredFieldsIndexes = [];
|
||||
$optionalFieldsIndexes = [];
|
||||
foreach($fields as $index => $format) {
|
||||
if($format->default === null) {
|
||||
array_push($requiredFieldsIndexes, $index);
|
||||
} else {
|
||||
array_push($optionalFieldsIndexes, $index);
|
||||
}
|
||||
}
|
||||
$requiredFieldsIndexesNumber = count($requiredFieldsIndexes);
|
||||
|
||||
if($tokensNumber < $requiredFieldsIndexesNumber) {
|
||||
$requiredFields = array_map(function($index) use ($fields) {
|
||||
return $fields[$index]->name;
|
||||
}, $requiredFieldsIndexes);
|
||||
$requiredFields = implode($separator, $requiredFields);
|
||||
throw new ArgumentParserException("Required field missing: {$tokensNumber} given "
|
||||
. "({$token}) but {$requiredFieldsIndexesNumber} required ({$requiredFields})");
|
||||
}
|
||||
|
||||
$givenOptionalFieldsIndexes = array_slice(
|
||||
$optionalFieldsIndexes, 0, $tokensNumber - $requiredFieldsIndexesNumber);
|
||||
$notPresentFieldsIndexes = array_slice(
|
||||
$optionalFieldsIndexes, $tokensNumber - $requiredFieldsIndexesNumber);
|
||||
$givenFieldsIndexes = array_merge($requiredFieldsIndexes, $givenOptionalFieldsIndexes);
|
||||
sort($givenFieldsIndexes);
|
||||
|
||||
// Fill the given fields
|
||||
for($i = 0; $i < $tokensNumber; $i ++) {
|
||||
$fieldFormat = $fields[$givenFieldsIndexes[$i]];
|
||||
$result[$fieldFormat->name] = $this->parseToken($tokens[$i], $fieldFormat);
|
||||
}
|
||||
|
||||
// Fill other fields with default values
|
||||
foreach($notPresentFieldsIndexes as $index) {
|
||||
$fieldFormat = $fields[$index];
|
||||
$result[$fieldFormat->name] = $fieldFormat->default;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
}
|
47
src/Commands/BaseCommand.php
Normal file
47
src/Commands/BaseCommand.php
Normal file
@ -0,0 +1,47 @@
|
||||
<?php namespace Wn\Generators\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Contracts\Filesystem\Filesystem;
|
||||
use Wn\Generators\Argument\ArgumentFormatLoader;
|
||||
use Wn\Generators\Argument\ArgumentParser;
|
||||
use Wn\Generators\Template\TemplateLoader;
|
||||
|
||||
|
||||
class BaseCommand extends Command {
|
||||
|
||||
protected $fs;
|
||||
protected $templates;
|
||||
|
||||
public function __construct(Filesystem $fs)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->fs = $fs;
|
||||
$this->templates = new TemplateLoader($fs);
|
||||
$this->argumentFormatLoader = new ArgumentFormatLoader($fs);
|
||||
}
|
||||
|
||||
protected function getTemplate($name)
|
||||
{
|
||||
return $this->templates->load($name);
|
||||
}
|
||||
|
||||
protected function getArgumentParser($name){
|
||||
$format = $this->argumentFormatLoader->load($name);
|
||||
return new ArgumentParser($format);
|
||||
}
|
||||
|
||||
protected function save($content, $path)
|
||||
{
|
||||
$this->makeDirectory($path);
|
||||
$this->fs->put($path, $content);
|
||||
}
|
||||
|
||||
protected function makeDirectory($path)
|
||||
{
|
||||
if (!$this->fs->isDirectory(dirname($path))) {
|
||||
$this->fs->makeDirectory(dirname($path), 0777, true, true);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
0
src/Commands/ControllerCommand.php
Normal file
0
src/Commands/ControllerCommand.php
Normal file
0
src/Commands/MigrationCommand.php
Normal file
0
src/Commands/MigrationCommand.php
Normal file
51
src/Commands/ModelCommand.php
Normal file
51
src/Commands/ModelCommand.php
Normal file
@ -0,0 +1,51 @@
|
||||
<?php namespace Wn\Generators\Commands;
|
||||
|
||||
|
||||
class ModelCommand extends BaseCommand {
|
||||
|
||||
protected $signature = 'wn:model
|
||||
{name : Name of the model}
|
||||
{--fillable= : the fillable fields of the model}
|
||||
{--dates= : date fields of the model}
|
||||
{--path=app : where to store the model php file}';
|
||||
|
||||
protected $description = 'Generates a model class for a RESTfull resource';
|
||||
|
||||
protected $fields = [];
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$name = $this->argument('name');
|
||||
$path = $this->option('path');
|
||||
|
||||
$content = $this->getTemplate('model')
|
||||
->with([
|
||||
'name' => $name,
|
||||
'namespace' => $this->getNamespace(),
|
||||
'fillable' => $this->getAsArrayFields('fillable'),
|
||||
'dates' => $this->getAsArrayFields('dates')
|
||||
])
|
||||
->get();
|
||||
|
||||
$this->save($content, "./{$path}/{$name}.php");
|
||||
|
||||
$this->info("Model {$name} Generated !");
|
||||
}
|
||||
|
||||
protected function getAsArrayFields($arg, $isOption = true)
|
||||
{
|
||||
$arg = ($isOption) ? $this->option($arg) : $this->argument($arg);
|
||||
if(is_string($arg)){
|
||||
$arg = explode(',', $arg);
|
||||
}
|
||||
return implode(', ', array_map(function($item){
|
||||
return '"' . $item . '"';
|
||||
}, $arg));
|
||||
}
|
||||
|
||||
protected function getNamespace()
|
||||
{
|
||||
return str_replace(' ', '\\', ucwords(str_replace('/', ' ', $this->option('path'))));
|
||||
}
|
||||
|
||||
}
|
0
src/Commands/ResourceCommand.php
Normal file
0
src/Commands/ResourceCommand.php
Normal file
0
src/Commands/RouteCommand.php
Normal file
0
src/Commands/RouteCommand.php
Normal file
0
src/Commands/SeedCommand.php
Normal file
0
src/Commands/SeedCommand.php
Normal file
0
src/Commands/TestCommand.php
Normal file
0
src/Commands/TestCommand.php
Normal file
76
src/CommandsServiceProvider.php
Normal file
76
src/CommandsServiceProvider.php
Normal file
@ -0,0 +1,76 @@
|
||||
<?php namespace Wn\Generators;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class CommandsServiceProvider extends ServiceProvider
|
||||
{
|
||||
|
||||
public function register()
|
||||
{
|
||||
// $this->register
|
||||
$this->registerModelCommand();
|
||||
// $this->registerControllerCommand();
|
||||
// $this->registerMigrationCommand();
|
||||
// $this->registerSeedCommand();
|
||||
// $this->registerRouteCommand();
|
||||
// $this->registerTestCommand();
|
||||
// $this->registerResourceCommand();
|
||||
}
|
||||
|
||||
protected function registerModelCommand(){
|
||||
$this->app->singleton('command.wn.model', function($app){
|
||||
return $app['Wn\Generators\Commands\ModelCommand'];
|
||||
});
|
||||
$this->commands('command.wn.model');
|
||||
|
||||
}
|
||||
|
||||
protected function registerControllerCommand(){
|
||||
$this->app->singleton('command.wn.controller', function($app){
|
||||
return $app['Wn\Generators\Commands\ControllerCommand'];
|
||||
});
|
||||
$this->commands('command.wn.controller');
|
||||
|
||||
}
|
||||
|
||||
protected function registerMigrationCommand(){
|
||||
$this->app->singleton('command.wn.migration', function($app){
|
||||
return $app['Wn\Generators\Commands\MigrationCommand'];
|
||||
});
|
||||
$this->commands('command.wn.migration');
|
||||
|
||||
}
|
||||
|
||||
protected function registerSeedCommand(){
|
||||
$this->app->singleton('command.wn.seed', function($app){
|
||||
return $app['Wn\Generators\Commands\SeedCommand'];
|
||||
});
|
||||
$this->commands('command.wn.seed');
|
||||
|
||||
}
|
||||
|
||||
protected function registerRouteCommand(){
|
||||
$this->app->singleton('command.wn.route', function($app){
|
||||
return $app['Wn\Generators\Commands\RouteCommand'];
|
||||
});
|
||||
$this->commands('command.wn.route');
|
||||
|
||||
}
|
||||
|
||||
protected function registerTestCommand(){
|
||||
$this->app->singleton('command.wn.test', function($app){
|
||||
return $app['Wn\Generators\Commands\TestCommand'];
|
||||
});
|
||||
$this->commands('command.wn.test');
|
||||
|
||||
}
|
||||
|
||||
protected function registerResourceCommand(){
|
||||
$this->app->singleton('command.wn.resource', function($app){
|
||||
return $app['Wn\Generators\Commands\ResourceCommand'];
|
||||
});
|
||||
$this->commands('command.wn.resource');
|
||||
|
||||
}
|
||||
|
||||
}
|
4
src/Exceptions/ArgumentFormatException.php
Normal file
4
src/Exceptions/ArgumentFormatException.php
Normal file
@ -0,0 +1,4 @@
|
||||
<?php namespace Wn\Generators\Exceptions;
|
||||
|
||||
|
||||
class ArgumentFormatException extends \Exception {}
|
4
src/Exceptions/ArgumentParserException.php
Normal file
4
src/Exceptions/ArgumentParserException.php
Normal file
@ -0,0 +1,4 @@
|
||||
<?php namespace Wn\Generators\Exceptions;
|
||||
|
||||
|
||||
class ArgumentParserException extends \Exception {}
|
4
src/Exceptions/TemplateException.php
Normal file
4
src/Exceptions/TemplateException.php
Normal file
@ -0,0 +1,4 @@
|
||||
<?php namespace Wn\Generators\Exceptions;
|
||||
|
||||
|
||||
class TemplateException extends \Exception {}
|
60
src/Template/Template.php
Normal file
60
src/Template/Template.php
Normal file
@ -0,0 +1,60 @@
|
||||
<?php namespace Wn\Generators\Template;
|
||||
|
||||
|
||||
class Template {
|
||||
|
||||
protected $loader;
|
||||
|
||||
protected $text;
|
||||
|
||||
protected $data;
|
||||
|
||||
protected $compiled;
|
||||
|
||||
protected $dirty;
|
||||
|
||||
public function __construct(TemplateLoader $loader, $text)
|
||||
{
|
||||
$this->loader = $loader;
|
||||
$this->text = $text;
|
||||
$this->compiled = '';
|
||||
$this->data = [];
|
||||
$this->dirty = false;
|
||||
}
|
||||
|
||||
public function clean()
|
||||
{
|
||||
$this->data = [];
|
||||
$this->dirty = false;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function with($data = [])
|
||||
{
|
||||
if($data)
|
||||
$this->dirty = true;
|
||||
foreach ($data as $key => $value) {
|
||||
$this->data[$key] = $value;
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function get()
|
||||
{
|
||||
if($this->dirty){
|
||||
$this->compile();
|
||||
$this->dirty = false;
|
||||
}
|
||||
return $this->compiled;
|
||||
}
|
||||
|
||||
public function compile()
|
||||
{
|
||||
$this->compiled = $this->text;
|
||||
foreach($this->data as $key => $value){
|
||||
$this->compiled = str_replace('{{' . $key . '}}', $value, $this->compiled);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
31
src/Template/TemplateLoader.php
Normal file
31
src/Template/TemplateLoader.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php namespace Wn\Generators\Template;
|
||||
|
||||
use Wn\Generators\Exceptions\TemplateException;
|
||||
|
||||
|
||||
class TemplateLoader {
|
||||
|
||||
protected $fs;
|
||||
|
||||
protected $loaded;
|
||||
|
||||
public function __construct(Filesystem $fs)
|
||||
{
|
||||
$this->fs = $fs;
|
||||
$this->loaded = [];
|
||||
}
|
||||
|
||||
public function load($name)
|
||||
{
|
||||
if(! isset($this->loaded[$name])){
|
||||
$path = __DIR__ . "/../../templates/{$name}.wnt";
|
||||
try {
|
||||
$this->loaded[$name] = $this->fs->get($path);
|
||||
} catch(\Exception $e) {
|
||||
throw new TemplateException("Unable to read the file '{$path}'");
|
||||
}
|
||||
}
|
||||
return new Template($this, $this->loaded[$name]);
|
||||
}
|
||||
|
||||
}
|
0
templates/controller.wnt
Normal file
0
templates/controller.wnt
Normal file
22
templates/migration.wnt
Normal file
22
templates/migration.wnt
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class {{name}}Migration extends Migration
|
||||
{
|
||||
|
||||
public function up()
|
||||
{
|
||||
Schema::create('{{table}}', function(Blueprint $table) {
|
||||
$table->increments('id');
|
||||
{{fields}}
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
Schema::drop('{{table}}');
|
||||
}
|
||||
}
|
0
templates/migration/schema-field.wnt
Normal file
0
templates/migration/schema-field.wnt
Normal file
11
templates/model.wnt
Normal file
11
templates/model.wnt
Normal file
@ -0,0 +1,11 @@
|
||||
<?php namespace {{namespace}};
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class {{name}} extends Model {
|
||||
|
||||
protected $fillable = [{{fillable}}];
|
||||
|
||||
protected $dates = [{{dates}}];
|
||||
|
||||
}
|
0
templates/route.wnt
Normal file
0
templates/route.wnt
Normal file
0
templates/seed.wnt
Normal file
0
templates/seed.wnt
Normal file
0
templates/test.wnt
Normal file
0
templates/test.wnt
Normal file
Loading…
Reference in New Issue
Block a user