Skip to content

Commit d8f7f50

Browse files
committed
wip
1 parent 0dc99d1 commit d8f7f50

File tree

12 files changed

+531
-3
lines changed

12 files changed

+531
-3
lines changed

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@
1717
],
1818
"require": {
1919
"php": "^8.1",
20+
"illuminate/contracts": "^10.0",
2021
"spatie/laravel-package-tools": "^1.14.0",
21-
"illuminate/contracts": "^10.0"
22+
"symfony/finder": "^6.2"
2223
},
2324
"require-dev": {
2425
"laravel/pint": "^1.0",

routes/api.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@
88
Route::group(['middleware' => PreventRegularBrowserAccess::class], function () {
99
Route::post('_native/api/booted', NativeAppBootedController::class);
1010
Route::post('_native/api/events', DispatchEventFromAppController::class);
11-
});
11+
})->withoutMiddleware(\App\Http\Middleware\VerifyCsrfToken::class);
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
namespace Native\Laravel\Commands;
4+
5+
use Illuminate\Console\Command;
6+
use Symfony\Component\Finder\Finder;
7+
8+
class MinifyApplicationCommand extends Command
9+
{
10+
protected $signature = 'native:minify {app}';
11+
12+
public function handle()
13+
{
14+
$appPath = realpath($this->argument('app'));
15+
16+
if (! is_dir($appPath)) {
17+
$this->error('The app path is not a directory');
18+
return;
19+
}
20+
21+
$this->info('Minifying application…');
22+
23+
$compactor = new \Native\Laravel\Compactor\Php();
24+
25+
$phpFiles = Finder::create()
26+
->files()
27+
->name('*.php')
28+
->in($appPath);
29+
30+
foreach ($phpFiles as $phpFile) {
31+
$minifiedContent = $compactor->compact($phpFile->getRealPath(), $phpFile->getContents());
32+
file_put_contents($phpFile->getRealPath(), $minifiedContent);
33+
}
34+
}
35+
}

src/Compactor/Php.php

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
<?php
2+
3+
namespace Native\Laravel\Compactor;
4+
5+
use PhpToken;
6+
7+
class Php
8+
{
9+
public function canProcessFile(string $path): bool
10+
{
11+
return pathinfo($path, PATHINFO_EXTENSION) === 'php';
12+
}
13+
14+
public function compact(string $file, string $contents): string
15+
{
16+
if ($this->canProcessFile($file)) {
17+
return $this->compactContent($contents);
18+
}
19+
20+
$this->compactContent($contents);
21+
}
22+
23+
protected function compactContent(string $contents): string
24+
{
25+
$output = '';
26+
$tokens = PhpToken::tokenize($contents);
27+
$tokenCount = count($tokens);
28+
29+
for ($index = 0; $index < $tokenCount; ++$index) {
30+
$token = $tokens[$index];
31+
$tokenText = $token->text;
32+
33+
if ($token->is([T_COMMENT, T_DOC_COMMENT])) {
34+
if (str_starts_with($tokenText, '#[')) {
35+
// This is, in all likelihood, the start of a PHP >= 8.0 attribute.
36+
// Note: $tokens may be updated by reference as well!
37+
$retokenized = $this->retokenizeAttribute($tokens, $index);
38+
39+
if (null !== $retokenized) {
40+
array_splice($tokens, $index, 1, $retokenized);
41+
$tokenCount = count($tokens);
42+
}
43+
44+
$attributeCloser = self::findAttributeCloser($tokens, $index);
45+
46+
if (is_int($attributeCloser)) {
47+
$output .= '#[';
48+
} else {
49+
// Turns out this was not an attribute. Treat it as a plain comment.
50+
$output .= str_repeat("\n", mb_substr_count($tokenText, "\n"));
51+
}
52+
} elseif (str_contains($tokenText, '@')) {
53+
try {
54+
$output .= $this->compactAnnotations($tokenText);
55+
} catch (RuntimeException) {
56+
$output .= $tokenText;
57+
}
58+
} else {
59+
$output .= str_repeat("\n", mb_substr_count($tokenText, "\n"));
60+
}
61+
} elseif ($token->is(T_WHITESPACE)) {
62+
$whitespace = $tokenText;
63+
$previousIndex = ($index - 1);
64+
65+
// Handle whitespace potentially being split into two tokens after attribute retokenization.
66+
$nextToken = $tokens[$index + 1] ?? null;
67+
68+
if (null !== $nextToken
69+
&& $nextToken->is(T_WHITESPACE)
70+
) {
71+
$whitespace .= $nextToken->text;
72+
++$index;
73+
}
74+
75+
// reduce wide spaces
76+
$whitespace = preg_replace('{[ \t]+}', ' ', $whitespace);
77+
78+
// normalize newlines to \n
79+
$whitespace = preg_replace('{(?:\r\n|\r|\n)}', "\n", $whitespace);
80+
81+
// If the new line was split off from the whitespace token due to it being included in
82+
// the previous (comment) token (PHP < 8), remove leading spaces.
83+
84+
$previousToken = $tokens[$previousIndex];
85+
86+
if ($previousToken->is(T_COMMENT)
87+
&& str_contains($previousToken->text, "\n")
88+
) {
89+
$whitespace = ltrim($whitespace, ' ');
90+
}
91+
92+
// trim leading spaces
93+
$whitespace = preg_replace('{\n +}', "\n", $whitespace);
94+
95+
$output .= $whitespace;
96+
} else {
97+
$output .= $tokenText;
98+
}
99+
}
100+
101+
return $output;
102+
}
103+
104+
private function compactAnnotations(string $docblock): string
105+
{
106+
return $docblock;
107+
}
108+
109+
/**
110+
* @param list<PhpToken> $tokens
111+
*/
112+
private static function findAttributeCloser(array $tokens, int $opener): ?int
113+
{
114+
$tokenCount = count($tokens);
115+
$brackets = [$opener];
116+
$closer = null;
117+
118+
for ($i = ($opener + 1); $i < $tokenCount; ++$i) {
119+
$tokenText = $tokens[$i]->text;
120+
121+
// Allow for short arrays within attributes.
122+
if ('[' === $tokenText) {
123+
$brackets[] = $i;
124+
125+
continue;
126+
}
127+
128+
if (']' === $tokenText) {
129+
array_pop($brackets);
130+
131+
if (0 === count($brackets)) {
132+
$closer = $i;
133+
break;
134+
}
135+
}
136+
}
137+
138+
return $closer;
139+
}
140+
141+
/**
142+
* @param non-empty-list<PhpToken> $tokens
143+
*/
144+
private function retokenizeAttribute(array &$tokens, int $opener): ?array
145+
{
146+
Assert::keyExists($tokens, $opener);
147+
148+
/** @var PhpToken $token */
149+
$token = $tokens[$opener];
150+
$attributeBody = mb_substr($token->text, 2);
151+
$subTokens = PhpToken::tokenize('<?php '.$attributeBody);
152+
153+
// Replace the PHP open tag with the attribute opener as a simple token.
154+
array_splice($subTokens, 0, 1, ['#[']);
155+
156+
$closer = self::findAttributeCloser($subTokens, 0);
157+
158+
// Multi-line attribute or attribute containing something which looks like a PHP close tag.
159+
// Retokenize the rest of the file after the attribute opener.
160+
if (null === $closer) {
161+
foreach (array_slice($tokens, $opener + 1) as $token) {
162+
$attributeBody .= $token->text;
163+
}
164+
165+
$subTokens = PhpToken::tokenize('<?php '.$attributeBody);
166+
array_splice($subTokens, 0, 1, ['#[']);
167+
168+
$closer = self::findAttributeCloser($subTokens, 0);
169+
170+
if (null !== $closer) {
171+
array_splice(
172+
$tokens,
173+
$opener + 1,
174+
count($tokens),
175+
array_slice($subTokens, $closer + 1),
176+
);
177+
178+
$subTokens = array_slice($subTokens, 0, $closer + 1);
179+
}
180+
}
181+
182+
if (null === $closer) {
183+
return null;
184+
}
185+
186+
return $subTokens;
187+
}
188+
}

src/Enums/RolesEnum.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@
55
enum RolesEnum: string
66
{
77
case APP_MENU = 'appMenu';
8+
case QUIT = 'quit';
89
case TOGGLE_FULL_SCREEN = 'togglefullscreen';
910
}

src/Events/MenuBar/MenuBarClicked.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
namespace Native\Laravel\Events\MenuBar;
4+
5+
use Illuminate\Broadcasting\Channel;
6+
use Illuminate\Broadcasting\InteractsWithSockets;
7+
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
8+
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
9+
use Illuminate\Foundation\Events\Dispatchable;
10+
use Illuminate\Queue\SerializesModels;
11+
12+
class MenuBarClicked implements ShouldBroadcastNow
13+
{
14+
use Dispatchable, InteractsWithSockets, SerializesModels;
15+
16+
public function broadcastOn()
17+
{
18+
return [
19+
new Channel('nativephp')
20+
];
21+
}
22+
}

src/Facades/MenuBar.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
namespace Native\Laravel\Facades;
4+
5+
use Illuminate\Support\Facades\Facade;
6+
7+
class MenuBar extends Facade
8+
{
9+
protected static function getFacadeAccessor()
10+
{
11+
return \Native\Laravel\MenuBar::class;
12+
}
13+
}

src/Menu/Items/Quit.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
namespace Native\Laravel\Menu\Items;
4+
5+
use Native\Laravel\Contracts\MenuItem;
6+
7+
class Quit implements MenuItem
8+
{
9+
public function __construct(protected string $label)
10+
{
11+
12+
}
13+
14+
public function toArray(): array
15+
{
16+
return [
17+
'type' => 'quit',
18+
'label' => $this->label,
19+
];
20+
}
21+
}

src/Menu/Menu.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use Native\Laravel\Client\Client;
66
use Native\Laravel\Contracts\MenuItem;
77
use Native\Laravel\Enums\RolesEnum;
8+
use Native\Laravel\Menu\Items\Quit;
89
use Native\Laravel\Menu\Items\Event;
910
use Native\Laravel\Menu\Items\Link;
1011
use Native\Laravel\Menu\Items\Role;
@@ -52,6 +53,11 @@ public function separator(): static
5253
return $this->add(new Separator());
5354
}
5455

56+
public function quit(): static
57+
{
58+
return $this->add(new Role(RolesEnum::QUIT));
59+
}
60+
5561
public function event(string $event, string $text): self
5662
{
5763
return $this->add(new Event($event, $text));

0 commit comments

Comments
 (0)