Skip to content

Commit

Permalink
implements 'yay://' stream wrapper #11
Browse files Browse the repository at this point in the history
  • Loading branch information
marcioAlmada committed Nov 28, 2015
1 parent 12df3c1 commit 62fdb71
Show file tree
Hide file tree
Showing 5 changed files with 240 additions and 0 deletions.
159 changes: 159 additions & 0 deletions src/StreamWrapper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
<?php declare(strict_types=1);

namespace Yay;

use function yay_parse;

use
SplFileObject,
SplFileInfo
;

final class StreamWrapper {
const
SCHEME = 'yay'
;

protected static
$registered = false
;

protected
/**
* @type SplFileInfo
*/
$fileMeta,
/**
* @type SplFileObject
*/
$file
;

static function register()
{
if (true === self::$registered) return;

if (@stream_wrapper_register(self::SCHEME, __CLASS__) === false)
throw new YayException(
'A handler has already been registered for the ' .
self::SCHEME . ' protocol.'
);

self::$registered = true;
}

static function unregister()
{
if (!self::$registered) {
if (in_array(self::SCHEME, stream_get_wrappers()))
throw new YayException(
'The URL wrapper for the protocol ' . self::SCHEME .
' was not registered with this version of YAY.'
);

return;
}

if (!@stream_wrapper_unregister(self::SCHEME))
throw new YayException(
'Failed to unregister the URL wrapper for the ' . self::SCHEME .
' protocol.'
);

self::$registered = false;
}

function stream_open(string $path, string $mode, int $flags, &$opened_path) : bool
{
$path = preg_replace('#^' . self::SCHEME . '://#', '', $path);

if (STREAM_USE_PATH & $flags && $path[0] !== '/')
$path = dirname(debug_backtrace()[0]['file']) . '/' . $path;

This comment has been minimized.

Copy link
@marcioAlmada

marcioAlmada Nov 28, 2015

Author Owner
  • Find a way to mimic relative path includes without debug_backtrace() 😭

$this->fileMeta = new SplFileInfo($path);

$opened_path = $path;

if (! $this->fileMeta->isReadable()) return false;

$this->file = $this->fileMeta->openFile($mode);

return true;
}

function stream_close(){}

function stream_read($lengh) : string {
return
! $this->file->eof()
? yay_parse($this->file->fread($lengh), $this->file->getPath())
: ''
;
}

function stream_eof() : bool { return $this->file->eof(); }

function stream_stat() : array
{
$stat =
$this->file ? [
'dev' => 0,
'ino' => 0,
'mode' => 'rb',
// 'mode' => $this->file->getMode(),
'nlink' => 0,
'uid' => $this->file->getOwner(),
'gid' => $this->file->getGroup(),
'rdev' => 0,
'size' => $this->file->getSize(),
'atime' => $this->file->getATime(),
'mtime' => $this->file->getMTime(),
'ctime' => $this->file->getCTime(),
'blksize' => -1,
'blocks' => -1
] : []
;

return array_merge(array_values($stat), $stat);
}

function url_stat() { $this->notImplemented(__FUNCTION__); }

function stream_write() { $this->notImplemented(__FUNCTION__); }

function stream_truncate() { $this->notImplemented(__FUNCTION__); }

function stream_metadata() { $this->notImplemented(__FUNCTION__); }

function stream_tell() { $this->notImplemented(__FUNCTION__); }

function stream_seek() { $this->notImplemented(__FUNCTION__); }

function stream_flush() { $this->notImplemented(__FUNCTION__); }

function stream_cast() { $this->notImplemented(__FUNCTION__); }

function stream_lock() { $this->notImplemented(__FUNCTION__); }

function stream_set_option() { $this->notImplemented(__FUNCTION__); }

function unlink() { $this->notImplemented(__FUNCTION__); }

function rename() { $this->notImplemented(__FUNCTION__); }

function mkdir() { $this->notImplemented(__FUNCTION__); }

function rmdir() { $this->notImplemented(__FUNCTION__); }

function dir_opendir() { $this->notImplemented(__FUNCTION__); }

function dir_readdir() { $this->notImplemented(__FUNCTION__); }

function dir_rewinddir() { $this->notImplemented(__FUNCTION__); }

function dir_closedir() { $this->notImplemented(__FUNCTION__); }

private function notImplemented(string $from) {
throw new YayException(__CLASS__ . "->{$from} is not implemented.");
}
}
60 changes: 60 additions & 0 deletions tests/StreamWrapperTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php declare(strict_types=1);

namespace Yay;

/**
* @group large
*/
class StreamWrapperTest extends \PHPUnit_Framework_TestCase {

const
FIXTURES_DIR = 'fixtures',
ABSOLUTE_FIXTURES_DIR = __DIR__ . '/' . self::FIXTURES_DIR
;

static function setupBeforeClass() { StreamWrapper::register(); }

function syntaxErrorProvider() {

$fixtures = [
['/syntax_error.php', "syntax error, unexpected '}'"],
['/preprocessor_error.php', "Undefined token type 'T_HAKUNAMATATA'"]
];

foreach ($fixtures as list($file, $message)) {
yield [self::FIXTURES_DIR . $file, $message];
yield [self::ABSOLUTE_FIXTURES_DIR . $file, $message];
}
}

/**
* @dataProvider syntaxErrorProvider
*/
function testStreamWrapperOnSyntaxError(string $file, string $error) {
$this->setExpectedException(\ParseError::class, $error);
include 'yay://' . $file;
}

function fixtureProvider() {
$fixtures = [
'/type_alias.php'
];

foreach ($fixtures as $file) {
yield [self::FIXTURES_DIR . $file];
yield [self::ABSOLUTE_FIXTURES_DIR . $file];
}
}

/**
* @dataProvider fixtureProvider
* @runInSeparateProcess
*/
function testStreamWrapperInclusion(string $file) {
include 'yay://' . $file;
$result = \Yay\Fixtures\test_type_alias(__FILE__);
$this->assertEquals('pass', $result);
}

static function tearDownAfterClass() { StreamWrapper::unregister(); }
}
3 changes: 3 additions & 0 deletions tests/fixtures/preprocessor_error.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<?php

macro { T_HAKUNAMATATA·foo } >> { };

This comment has been minimized.

Copy link
@haskellcamargo

haskellcamargo Feb 5, 2016

Collaborator

It means no worries!
Timão e Pumba

This comment has been minimized.

Copy link
@hikari-no-yume

hikari-no-yume Feb 5, 2016

Collaborator

T_PAAMAYIM_NEKUDOTAYIM

This comment has been minimized.

Copy link
@marcioAlmada

marcioAlmada Feb 5, 2016

Author Owner

This reminds me to have some conditional macro decl otherwise it will be impossible to write anything involving a token that exists only after a given PHP version. Ex:

macro ·if(version_compare(PHP_VERSION, '7.1.0', '>')) {
    T_SOME_PHP_SEVEN_DOT_ONE_TOKEN·foo
} >> {
    /*...*/
}

This comment has been minimized.

Copy link
@hikari-no-yume

hikari-no-yume Feb 6, 2016

Collaborator

Ooh, you could use this to pollyfill new features!

This comment has been minimized.

Copy link
@marcioAlmada

marcioAlmada Feb 6, 2016

Author Owner

Precisely. Some test files are half baked pollyfill implementations: primitive_union_return_types.phppt

There we go Conditional Macro Declaration.

1 change: 1 addition & 0 deletions tests/fixtures/syntax_error.php
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<?php { blah }
17 changes: 17 additions & 0 deletions tests/fixtures/type_alias.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php namespace Yay\Fixtures;

macro {
type T_STRING·newtype = T_STRING·basetype;
} >> {
macro {
\\(·either(instanceof, ·token(','), ·token('('), ·token(':'))·prec) T_STRING·newtype
} >> {
\\(·prec) T_STRING·basetype
}
}

type Path = string;

function test_type_alias(Path $p) : Path {
return 'pass';
}

0 comments on commit 62fdb71

Please sign in to comment.