Skip to content
Draft
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
55 changes: 55 additions & 0 deletions app/Commands/DefaultCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

namespace App\Commands;

use App\Actions\FixCode;
use LaravelZero\Framework\Commands\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Throwable;

class DefaultCommand extends Command
{
Expand Down Expand Up @@ -49,6 +51,7 @@ protected function configure()
new InputOption('cache-file', '', InputArgument::OPTIONAL, 'The path to the cache file'),
new InputOption('parallel', 'p', InputOption::VALUE_NONE, 'Runs the linter in parallel (Experimental)'),
new InputOption('max-processes', null, InputOption::VALUE_REQUIRED, 'The number of processes to spawn when using parallel execution'),
new InputOption('stdin-filename', null, InputOption::VALUE_REQUIRED, 'Provide file path context for stdin input'),
],
);
}
Expand All @@ -62,8 +65,60 @@ protected function configure()
*/
public function handle($fixCode, $elaborateSummary)
{
if ($this->hasStdinInput()) {
return $this->fixStdinInput($fixCode);
}

[$totalFiles, $changes] = $fixCode->execute();

return $elaborateSummary->execute($totalFiles, $changes);
}

/**
* Fix the code sent to Pint on stdin and output to stdout.
*
* The stdin-filename option provides file path context for error messages.
* Falls back to 'stdin.php' if not provided.
*/
protected function fixStdinInput(FixCode $fixCode): int
{
$contextPath = $this->option('stdin-filename') ?: 'stdin.php';
$tempFile = sys_get_temp_dir().DIRECTORY_SEPARATOR.'pint_stdin_'.uniqid().'.php';

$this->input->setArgument('path', [$tempFile]);
$this->input->setOption('format', 'json');

try {
file_put_contents($tempFile, stream_get_contents(STDIN));
$fixCode->execute();
fwrite(STDOUT, file_get_contents($tempFile));

return self::SUCCESS;
} catch (Throwable $e) {
fwrite(STDERR, "pint: error processing {$contextPath}: {$e->getMessage()}\n");

return self::FAILURE;
} finally {
if (file_exists($tempFile)) {
@unlink($tempFile);
}
}
}

/**
* Determine if there is input available on stdin.
*
* Stdin mode is triggered by either:
* - Passing '-' as the path argument (Unix convention like Black, cat)
* - Providing the --stdin-filename option (editor-friendly like Prettier)
*/
protected function hasStdinInput(): bool
{
$paths = $this->argument('path');

$hasStdinPlaceholder = ! empty($paths) && $paths[0] === '__STDIN_PLACEHOLDER__';
$hasStdinFilename = ! empty($this->option('stdin-filename'));

return $hasStdinPlaceholder || $hasStdinFilename;
}
}
21 changes: 21 additions & 0 deletions pint
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,27 @@ $app = require_once __DIR__.'/bootstrap/app.php';

$kernel = $app->make(Illuminate\Contracts\Console\Kernel::class);

/*
|--------------------------------------------------------------------------
| Handle Stdin Mode
|--------------------------------------------------------------------------
|
| When using '-' to indicate stdin input (following Unix convention like
| Black, cat, etc.), Symfony Console's ArgvInput parser fails because it
| treats '-' as a malformed option. We work around this by replacing '-'
| with a placeholder before the input is parsed. The DefaultCommand then
| detects this placeholder to enable stdin mode.
|
*/

if (isset($_SERVER['argv'])) {
$stdinIndex = array_search('-', $_SERVER['argv'], true);

if ($stdinIndex !== false) {
$_SERVER['argv'][$stdinIndex] = '__STDIN_PLACEHOLDER__';
}
}

$status = $kernel->handle(
$input = new Symfony\Component\Console\Input\ArgvInput,
new Symfony\Component\Console\Output\ConsoleOutput
Expand Down
134 changes: 134 additions & 0 deletions tests/Feature/StdinTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
<?php

use Illuminate\Support\Facades\Process;

it('formats code from stdin', function (string $input, ?string $expected) {
$result = Process::input($input)
->run('php pint - --stdin-filename=app/Test.php')
->throw();

expect($result)
->output()
->toBe($expected ?? $input)
->errorOutput()
->toBe('');
})->with([
'basic array and conditional' => [
<<<'PHP'
<?php
$array = array("a","b");
if($condition==true){
echo "test";
}
PHP
,
<<<'PHP'
<?php

$array = ['a', 'b'];
if ($condition == true) {
echo 'test';
}

PHP
,
],
'class with method' => [
<<<'PHP'
<?php
class Test{
public function method(){
return array("key"=>"value");
}
}
PHP
,
<<<'PHP'
<?php

class Test
{
public function method()
{
return ['key' => 'value'];
}
}

PHP
,
],
'already formatted code' => [
<<<'PHP'
<?php

class AlreadyFormatted
{
public function method()
{
return ['key' => 'value'];
}
}

PHP
,
null,
],
]);

it('formats code from stdin without filename', function () {
$input = <<<'PHP'
<?php
$array = array("a","b");
PHP;

$expected = <<<'PHP'
<?php

$array = ['a', 'b'];

PHP;

$result = Process::input($input)->run('php pint -')->throw();

expect($result)->output()->toBe($expected)->errorOutput()->toBe('');
});

it('uses stdin-filename for context', function () {
$input = <<<'PHP'
<?php
$array = array("test");
PHP;

$expected = <<<'PHP'
<?php

$array = ['test'];

PHP;

$result = Process::input($input)
->run('php pint - --stdin-filename=app/Models/User.php')
->throw();

expect($result)->output()->toBe($expected)->errorOutput()->toBe('');
});

it('formats code from stdin using only stdin-filename option', function () {
$input = <<<'PHP'
<?php
$array = array("foo","bar");
PHP;

$expected = <<<'PHP'
<?php

$array = ['foo', 'bar'];

PHP;

$result = Process::input($input)
->run('php pint --stdin-filename=app/Models/Example.php')
->throw();

expect($result)->output()->toBe($expected)->errorOutput()->toBe('');
});