Starting ModelCommand

This commit is contained in:
Amine Ben hammou 2015-09-23 01:49:46 +01:00
parent e41e7a97f3
commit 09071f4b53
29 changed files with 2814 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
/vendor/
/about/
*.sublime-*
tests/_output/*

View File

@ -1 +1,3 @@
# lumen-rest-generators
Restful resources generators for Lumen and Laravel 5.

21
composer.json Normal file
View 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

File diff suppressed because it is too large Load Diff

0
formats/fields.json Normal file
View File

View 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;
}

View 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];
}
}

View 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;
}
}

View 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);
}
}
}

View File

View File

View 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'))));
}
}

View File

View File

View File

View File

View 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');
}
}

View File

@ -0,0 +1,4 @@
<?php namespace Wn\Generators\Exceptions;
class ArgumentFormatException extends \Exception {}

View File

@ -0,0 +1,4 @@
<?php namespace Wn\Generators\Exceptions;
class ArgumentParserException extends \Exception {}

View File

@ -0,0 +1,4 @@
<?php namespace Wn\Generators\Exceptions;
class TemplateException extends \Exception {}

60
src/Template/Template.php Normal file
View 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;
}
}

View 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
View File

22
templates/migration.wnt Normal file
View 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}}');
}
}

View File

11
templates/model.wnt Normal file
View 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
View File

0
templates/seed.wnt Normal file
View File

0
templates/test.wnt Normal file
View File