Skip to content

Commit

Permalink
introducing watch
Browse files Browse the repository at this point in the history
  • Loading branch information
henzeb committed Feb 25, 2024
1 parent ca6872a commit a69fbec
Show file tree
Hide file tree
Showing 6 changed files with 352 additions and 0 deletions.
28 changes: 28 additions & 0 deletions playground/watch.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

use function Laravel\Prompts\table;
use function Laravel\Prompts\watch;

require __DIR__ . '/../vendor/autoload.php';

watch(
function () {
static $iteration = 0;
static $items = [];

if (count($items) == 5) {
array_shift($items);
}

$items[] = [$iteration += 1, (new Datetime())->format(DateTime::RFC850)];

table(
[
'Iteration',
'DateTime'
],
$items
);
},
1,
);
3 changes: 3 additions & 0 deletions src/Concerns/Themes.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
use Laravel\Prompts\Themes\Default\SuggestPromptRenderer;
use Laravel\Prompts\Themes\Default\TableRenderer;
use Laravel\Prompts\Themes\Default\TextPromptRenderer;
use Laravel\Prompts\Themes\Default\WatchRenderer;
use Laravel\Prompts\Watch;

trait Themes
{
Expand Down Expand Up @@ -57,6 +59,7 @@ trait Themes
Note::class => NoteRenderer::class,
Table::class => TableRenderer::class,
Progress::class => ProgressRenderer::class,
Watch::class => WatchRenderer::class,
],
];

Expand Down
27 changes: 27 additions & 0 deletions src/Themes/Default/WatchRenderer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

namespace Laravel\Prompts\Themes\Default;

use Laravel\Prompts\Output\BufferedConsoleOutput;
use Laravel\Prompts\Watch;
use Symfony\Component\Console\Output\OutputInterface;

class WatchRenderer extends Renderer
{
/**
* buffers the output from a Buffered Prompt and flushes the output to Prompts
* in order to utilize Prompts neat way of updating lines
*/
public function __invoke(Watch $watch, OutputInterface $originalOutput): string
{
$bufferedOutput = new BufferedConsoleOutput();

$watch::setOutput($bufferedOutput);

($watch->watch)();

$watch::setOutput($originalOutput);

return $bufferedOutput->fetch();
}
}
140 changes: 140 additions & 0 deletions src/Watch.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
<?php

namespace Laravel\Prompts;

use Closure;
use Laravel\Prompts\Output\BufferedConsoleOutput;
use PHPUnit\Framework\Assert;
use RuntimeException;
use ValueError;

class Watch extends Prompt
{
/**
* How many times to fake an iteration
*/
protected static int $fakeTimes = 1;

/**
* count of faked iterations
*/
protected int $fakedTimes = 0;

/**
* Faking sleep or not
*/
protected static bool $fakeSleep = true;

/**
* the amount of seconds slept during intervals in total
*/
protected static int $sleptSeconds = 0;

public Closure $watch;
protected int $interval;

/**
* Create a new Watch instance.
*/
public function __construct(callable $watch, ?int $interval = 2)
{
static::$sleptSeconds = 0;
$this->watch = $watch(...);
$this->interval = $interval ?? 2;

if ($this->interval < 0) {
throw new ValueError('watch interval must be greater than or equal to 0');
}
}

/**
* displays the watched output and updates after the specified interval.
*/
public function display(): void
{
$faked = static::output() instanceof BufferedConsoleOutput;

while (!$faked || $this->fakedTimes < static::$fakeTimes) {

$this->render();

if ($faked) {
$this->fakedTimes++;

if ($this->fakedTimes >= static::$fakeTimes) {
static::$fakeSleep = true;
break;
}

if (static::$fakeSleep) {
static::$sleptSeconds += $this->interval;
continue;
}
}

sleep($this->interval);
}
}

/**
* overrides prompt so it will have the same behavior.
*/
public function prompt(): bool
{
$this->display();

return true;
}

protected function renderTheme(): string
{
$renderer = static::getRenderer();

return $renderer($this, static::output());
}

/**
* Get the value of the prompt.
*/
public function value(): bool
{
return true;
}

/**
* Tell Prompt how many iterations to fake
*/
public static function fakeTimes(int $times): void
{
if (!static::output() instanceof BufferedConsoleOutput) {
throw new RuntimeException('Prompt must be faked before faking iterations.');
}

static::$fakeTimes = $times;
}

/**
* Asserts the amount of seconds slept during intervals in total.
*/
public static function assertSecondsSleptBetweenIntervals(int $seconds): void
{
if (!static::output() instanceof BufferedConsoleOutput) {
throw new RuntimeException('Prompt must be faked before asserting.');
}

Assert::assertEquals($seconds, static::$sleptSeconds);
}

/**
* By default, when Prompt is faked, the intervals are faked..
* This tells Prompt to actually sleep between updates.
*/
public static function shouldNotFakeIntervalSleep(): void
{
if (!static::output() instanceof BufferedConsoleOutput) {
throw new RuntimeException('Not faking sleep makes no sense when not faking Prompt.');
}

static::$fakeSleep = false;
}
}
10 changes: 10 additions & 0 deletions src/helpers.php
Original file line number Diff line number Diff line change
Expand Up @@ -192,3 +192,13 @@ function progress(string $label, iterable|int $steps, ?Closure $callback = null,

return $progress;
}

/**
* Continuously updates output on each interval.
*
* @param callable(): void $watch
*/
function watch(callable $watch, ?int $interval = 2): void
{
(new Watch($watch, $interval))->display();
}
144 changes: 144 additions & 0 deletions tests/Feature/WatchTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
<?php

use Laravel\Prompts\Output\ConsoleOutput;
use Laravel\Prompts\Prompt;
use Laravel\Prompts\Watch;
use function Laravel\Prompts\note;
use function Laravel\Prompts\watch;

it('it should render', function () {
Prompt::fake();

watch(function () {
note('This should render');
});

Watch::assertSecondsSleptBetweenIntervals(0);

Prompt::assertOutputContains('This should render');
});

it('it should render callable array', function () {
Prompt::fake();

$watch = new class() {
public function watch(): void
{
note('This should render through array');
}
};

watch([$watch, 'watch']);

Watch::assertSecondsSleptBetweenIntervals(0);

Prompt::assertOutputContains('This should render through array');
});

it('it should render invokable', function () {
Prompt::fake();

$watch = new class() {
public function __invoke(): void
{
note('This should render through invokable');
}
};

watch($watch);

Watch::assertSecondsSleptBetweenIntervals(0);

Prompt::assertOutputContains('This should render through invokable');
});

it('it should render buffered', function () {
Prompt::fake();

watch(function () {

note('This should not render');

(fn() => Watch::output())
->bindTo(null, Watch::class)()->fetch();
});

Prompt::assertOutputDoesntContain('This should not render');
});

it('it should fake sleep when faking', function (
int $expected,
int $iteration,
int $interval = null
) {
Prompt::fake();

Watch::fakeTimes($iteration);

watch(function () {
}, $interval);

Watch::assertSecondsSleptBetweenIntervals($expected);
})->with(
[
['expected' => 2, 'iteration' => 2, 'interval' => 2],
['expected' => 3, 'iteration' => 2, 'interval' => 3],
['expected' => 6, 'iteration' => 3, 'interval' => 3],
['expected' => 4, 'iteration' => 3, 'interval' => null],
]
);

it('should throw exception with a negative interval ', function () {
Prompt::fake();

watch(fn() => null, -1);

})->throws(ValueError::class);

it('should sleep 2 seconds by default', function () {
Prompt::fake();

Watch::fakeTimes(2);

watch(function () {
});

Watch::assertSecondsSleptBetweenIntervals(2);
});

it('should actually sleep at intervals', function () {

Prompt::fake();

Watch::fakeTimes(2);

Watch::shouldNotFakeIntervalSleep();

$start = time();

watch(function () {
note('This should render');
}, 1);

$end = time();

expect($end - $start)->toBe(1);

Prompt::assertOutputContains('This should render');
});

it('should throw exception when invoking fakeTimes when not faked', function () {
(function () {
Prompt::$output = new ConsoleOutput();
})->bindTo(null, Prompt::class)();
Watch::fakeTimes(2);
})->throws(RuntimeException::class);

it('should throw exception when invoking assertSlept when not faked', function () {

(function () {
Prompt::$output = new ConsoleOutput();
})->bindTo(null, Prompt::class)();

Watch::assertSecondsSleptBetweenIntervals(2);
})->throws(RuntimeException::class);

0 comments on commit a69fbec

Please sign in to comment.