Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added .github/animation.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
.env
.php_cs.cache
.phpunit.result.cache
composer.lock
composer.lock
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# A2Workspace/Laravel-Stubs

<p align="center"><img src="/.github/animation.gif" alt="Laravel-Stubs demo"></p>

一個基於專案的程式模板注入器。

透過在專案中的 `resources/stubs` 目錄下,放置類別的模板文件,然後透過命令快速注入並生成。相比原生的 `artisan make:*` 命令可大大減少編寫時間,且模板檔案可隨版控被 git 紀錄。
Expand All @@ -20,8 +22,8 @@ composer require "a2workspace/laravel-stubs:*"

## Usage | 如何使用

現在你可以使用 `make:a..` [Artisan 命令](https://laravel.com/docs/9.x/artisan)來生成類別。該命令將會讀取 `resources/stubs` 下的目錄或 `.php` 檔案,將佔位符依照格式替換為給定的名稱,並依照類別名稱自動生成檔案到相對的路徑。
現在你可以使用 `make:a...` [Artisan 命令](https://laravel.com/docs/9.x/artisan)來生成類別。該命令將會讀取 `resources/stubs` 下的目錄或 `.php` 檔案,將佔位符依照格式替換為給定的名稱,並依照類別名稱自動生成檔案到相對的路徑。

```
php artisan make:a..
php artisan make:a...
```
13 changes: 8 additions & 5 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,19 @@
"Tests\\": "tests/"
}
},
"config": {
"platform": {
"ext-posix": "0"
}
},
"extra": {
"laravel": {
"providers": [
"A2Workspace\\Stubs\\ServiceProvider"
]
}
},
"scripts": {
"test": [
"vendor/bin/phpunit"
],
"test-coverage": [
"vendor/bin/phpunit --coverage-html coverage"
]
}
}
12 changes: 7 additions & 5 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@
stopOnFailure="false"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd"
>
<coverage>
<include>
<directory suffix=".php">src</directory>
</include>
</coverage>
<testsuites>
<testsuite name="Unit">
<directory suffix="Test.php">./tests/Unit</directory>
</testsuite>
<testsuite name="Feature">
<directory suffix="Test.php">./tests/Feature</directory>
<testsuite name="Package Test">
<directory suffix="Test.php">./tests/</directory>
</testsuite>
</testsuites>
<php>
Expand Down
32 changes: 22 additions & 10 deletions src/Commands/StubGeneratorCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use Symfony\Component\Finder\Finder;
use Symfony\Component\Finder\SplFileInfo;

#[AsCommand(name: 'make:a...')]
class StubGeneratorCommand extends Command
{
/**
Expand Down Expand Up @@ -45,7 +46,7 @@ class StubGeneratorCommand extends Command
/**
* @var string
*/
const REGEXP_NAMESPACE = '/^namespace (([A-Z][a-zA-Z]+)(\\\[A-Z][a-zA-Z]+)+);/m';
const REGEXP_NAMESPACE = '/^namespace (([A-Z][a-zA-Z]+)(\\\[A-Z][a-zA-Z]+)*);/m';

/**
* @var string
Expand Down Expand Up @@ -330,28 +331,39 @@ protected function getDestinationPath($built)
}

// 接著,我們嘗試比對 namespace。若符合則生成對應目錄的完整路徑。
// 目前支援 App\ 與 Tests\
$destinations = [
$this->laravel->getNamespace() => $this->laravel['path'],
'Tests\\' => $this->laravel->basePath('tests'),
'Database\\' => $this->laravel->basePath('database'),
'Database\\Factories' => $this->laravel->databasePath('factories'),
'Database\\Seeders' => $this->laravel->databasePath('seeders'),
];

foreach ($destinations as $rootNamespace => $destination) {
if (! Str::startsWith($namespace, $rootNamespace)) {
// 處理 namespace Test\Feature; 這種情形
if (Str::startsWith($namespace, $rootNamespace)) {
$relative = Str::replaceFirst($rootNamespace, '', $namespace);
$relative = str_replace('\\', '/', $relative);
}
// 處理 namespace Test; 這種情形
else if ($namespace === substr($rootNamespace, 0, -1)) {
$relative = '';
}
// 例外則跳過
else {
continue;
}

$relative = Str::replaceFirst($rootNamespace, '', $namespace);
$relative = str_replace('\\', '/', $relative);

$path = join('/', [
$path = [
$destination,
$relative,
"{$classname}.php"
]);
];

$path = join('/', $path);
$path = str_replace('//', '/', $path);
$path = str_replace('/', DIRECTORY_SEPARATOR, $path);

return str_replace('/', DIRECTORY_SEPARATOR, $path);
return $path;
}

return false;
Expand Down
189 changes: 189 additions & 0 deletions tests/CallArtisanCommandTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
<?php

namespace Tests;

use Illuminate\Testing\PendingCommand;
use Tests\TestCase;

class CallArtisanCommandTest extends TestCase
{
protected function setUp(): void
{
parent::setUp();

@unlink(app_path(static::resolvePath('Models/Category.php')));
@unlink(app_path(static::resolvePath('Http/Resources/CategoryResource.php')));
}

protected function tearDown(): void
{
parent::tearDown();

@unlink(app_path(static::resolvePath('Models/Category.php')));
@unlink(app_path(static::resolvePath('Http/Resources/CategoryResource.php')));
}

private function expectsCommandChoice(PendingCommand $command, $answer): PendingCommand
{
$options = [
'model.stub.php',
static::resolvePath('pack/'),
];

$command->expectsChoice(
'選擇要使用的 Stub 檔案',
$answer,
$options,
);

return $command;
}

// =========================================================================
// = Tests
// =========================================================================

public function test_call_artisan_command()
{
$expectedPutPath = app_path(static::resolvePath('Models/Category.php'));

$command = $this->artisan('make:a...');
$command = $this->expectsCommandChoice($command, 'model.stub.php');

$command->expectsQuestion('請輸入要注入的名稱', 'Category');

$command->expectsOutput(sprintf('已建立 "%s"', $expectedPutPath));
$command->assertExitCode(0);
$command->run();

$this->assertFileExists($expectedPutPath);
$this->assertFileEquals(
__DIR__ . '/fixtures/category_model.php',
$expectedPutPath,
);
}

public function test_call_artisan_command_in_already_exists_and_overwrite()
{
$expectedPutPath = app_path(static::resolvePath('Models/Category.php'));

file_put_contents($expectedPutPath, '__EMPTY__');

$command = $this->artisan('make:a...');
$command = $this->expectsCommandChoice($command, 'model.stub.php');

$command->expectsQuestion('請輸入要注入的名稱', 'Category');
$command->expectsConfirmation(
sprintf('%s 檔案已存在,是否要覆蓋?', $expectedPutPath),
'yes'
);

$command->expectsOutput(sprintf('已建立 "%s"', $expectedPutPath));
$command->assertExitCode(0);
$command->run();

$this->assertFileExists($expectedPutPath);
$this->assertFileEquals(
__DIR__ . '/fixtures/category_model.php',
$expectedPutPath,
);
}

public function test_call_artisan_command_in_already_exists_and_without_overwrite()
{
$expectedPutPath = app_path(static::resolvePath('Models/Category.php'));

file_put_contents($expectedPutPath, '__EMPTY__');

$command = $this->artisan('make:a...');
$command = $this->expectsCommandChoice($command, 'model.stub.php');

$command->expectsQuestion('請輸入要注入的名稱', 'Category');
$command->expectsConfirmation(
sprintf('%s 檔案已存在,是否要覆蓋?', $expectedPutPath),
'no'
);

$command->expectsOutput(
sprintf('略過處理 %s', $expectedPutPath)
);

$command->assertExitCode(0);
$command->run();

$this->assertFileExists($expectedPutPath);
$this->assertEquals(
'__EMPTY__',
file_get_contents($expectedPutPath),
);
}

public function test_call_artisan_command_with_filter()
{
$expectedPutPath = app_path(static::resolvePath('Models/Category.php'));

$command = $this->artisan('make:a...', [
'filter' => 'model',
]);

$command->expectsChoice(
'選擇要使用的 Stub 檔案',
'model.stub.php',
['model.stub.php'],
);

$command->expectsQuestion('請輸入要注入的名稱', 'Category');

$command->expectsOutput(sprintf('已建立 "%s"', $expectedPutPath));
$command->assertExitCode(0);
$command->run();

$this->assertFileExists($expectedPutPath);
$this->assertFileEquals(
__DIR__ . '/fixtures/category_model.php',
$expectedPutPath,
);
}

public function test_call_artisan_command_with_filter_and_not_found()
{
$command = $this->artisan('make:a...', [
'filter' => 'foobar',
]);

$command->expectsOutput('找不到符合的 Stub 檔案');
$command->assertExitCode(0);
$command->run();
}

public function test_call_artisan_command_with_directroy()
{
$expectedPutPath1 = app_path(static::resolvePath('Models/Category.php'));
$expectedPutPath2 = app_path(static::resolvePath('Http/Resources/CategoryResource.php'));

$command = $this->artisan('make:a...');
$command = $this->expectsCommandChoice(
$command,
static::resolvePath('pack/')
);

$command->expectsQuestion('請輸入要注入的名稱', 'Category');

$command->expectsOutput(sprintf('已建立 "%s"', $expectedPutPath1));
$command->expectsOutput(sprintf('已建立 "%s"', $expectedPutPath2));
$command->assertExitCode(0);
$command->run();

$this->assertFileExists($expectedPutPath1);
$this->assertFileEquals(
__DIR__ . '/fixtures/category_model.php',
$expectedPutPath1,
);

$this->assertFileExists($expectedPutPath2);
$this->assertFileEquals(
__DIR__ . '/fixtures/category_resource.php',
$expectedPutPath2,
);
}
}
18 changes: 0 additions & 18 deletions tests/Feature/ExampleTest.php

This file was deleted.

Loading