PK \GM'19 .travis.ymlnu W+A language: php
php:
- 5.4
- 5.5
- 5.6
- hhvm
before_script:
- composer self-update
- composer install --no-interaction --prefer-source --dev
script: vendor/bin/phpunit
PK \GMѝ>L Makefilenu W+A all: clean coverage
release: tag
git push origin --tags
tag:
chag tag --sign --debug CHANGELOG.rst
test:
vendor/bin/phpunit
coverage:
vendor/bin/phpunit --coverage-html=artifacts/coverage
view-coverage:
open artifacts/coverage/index.html
clean:
rm -rf artifacts/*
PK \GM:[F; ;
.gitignorenu W+A .idea
.DS_STORE
coverage
phpunit.xml
composer.lock
vendor/
PK \GM=*~ ~
CHANGELOG.rstnu W+A =========
Changelog
=========
1.5.1 (2014-09-10)
------------------
* Stream metadata is grabbed from the underlying stream each time
``getMetadata`` is called rather than returning a value from a cache.
* Properly closing all underlying streams when AppendStream is closed.
* Seek functions no longer throw exceptions.
* LazyOpenStream now correctly returns the underlying stream resource when
detached.
1.5.0 (2014-08-07)
------------------
* Added ``Stream\safe_open`` to open stream resources and throw exceptions
instead of raising errors.
1.4.0 (2014-07-19)
------------------
* Added a LazyOpenStream
1.3.0 (2014-07-15)
------------------
* Added an AppendStream to stream over multiple stream one after the other.
1.2.0 (2014-07-15)
------------------
* Updated the ``detach()`` method to return the underlying stream resource or
``null`` if it does not wrap a resource.
* Multiple fixes for how streams behave when the underlying resource is
detached
* Do not clear statcache when a stream does not have a 'uri'
* Added a fix to LimitStream
* Added a condition to ensure that functions.php can be required multiple times
PK \GMx}ތ src/CachingStream.phpnu W+A remoteStream = $stream;
$this->stream = $target ?: new Stream(fopen('php://temp', 'r+'));
}
public function getSize()
{
return max($this->stream->getSize(), $this->remoteStream->getSize());
}
/**
* {@inheritdoc}
* @throws \RuntimeException When seeking with SEEK_END or when seeking
* past the total size of the buffer stream
*/
public function seek($offset, $whence = SEEK_SET)
{
if ($whence == SEEK_SET) {
$byte = $offset;
} elseif ($whence == SEEK_CUR) {
$byte = $offset + $this->tell();
} else {
return false;
}
// You cannot skip ahead past where you've read from the remote stream
if ($byte > $this->stream->getSize()) {
throw new \RuntimeException(sprintf('Cannot seek to byte %d when '
. ' the buffered stream only contains %d bytes',
$byte, $this->stream->getSize()));
}
return $this->stream->seek($byte);
}
public function read($length)
{
// Perform a regular read on any previously read data from the buffer
$data = $this->stream->read($length);
$remaining = $length - strlen($data);
// More data was requested so read from the remote stream
if ($remaining) {
// If data was written to the buffer in a position that would have
// been filled from the remote stream, then we must skip bytes on
// the remote stream to emulate overwriting bytes from that
// position. This mimics the behavior of other PHP stream wrappers.
$remoteData = $this->remoteStream->read(
$remaining + $this->skipReadBytes
);
if ($this->skipReadBytes) {
$len = strlen($remoteData);
$remoteData = substr($remoteData, $this->skipReadBytes);
$this->skipReadBytes = max(0, $this->skipReadBytes - $len);
}
$data .= $remoteData;
$this->stream->write($remoteData);
}
return $data;
}
public function write($string)
{
// When appending to the end of the currently read stream, you'll want
// to skip bytes from being read from the remote stream to emulate
// other stream wrappers. Basically replacing bytes of data of a fixed
// length.
$overflow = (strlen($string) + $this->tell()) - $this->remoteStream->tell();
if ($overflow > 0) {
$this->skipReadBytes += $overflow;
}
return $this->stream->write($string);
}
public function eof()
{
return $this->stream->eof() && $this->remoteStream->eof();
}
/**
* Close both the remote stream and buffer stream
*/
public function close()
{
$this->remoteStream->close() && $this->stream->close();
}
}
PK \GMc} src/LimitStream.phpnu W+A stream = $stream;
$this->setLimit($limit);
$this->setOffset($offset);
}
public function eof()
{
if ($this->limit == -1) {
return $this->stream->eof();
}
$tell = $this->stream->tell();
return $tell === false ||
($tell >= $this->offset + $this->limit) ||
$this->stream->eof();
}
/**
* Returns the size of the limited subset of data
* {@inheritdoc}
*/
public function getSize()
{
if (null === ($length = $this->stream->getSize())) {
return null;
} elseif ($this->limit == -1) {
return $length - $this->offset;
} else {
return min($this->limit, $length - $this->offset);
}
}
/**
* Allow for a bounded seek on the read limited stream
* {@inheritdoc}
*/
public function seek($offset, $whence = SEEK_SET)
{
if ($whence != SEEK_SET) {
return false;
}
if ($offset < $this->offset) {
$offset = $this->offset;
}
if ($this->limit !== -1 && $offset > ($this->offset + $this->limit)) {
$offset = $this->offset + $this->limit;
}
return $this->stream->seek($offset);
}
/**
* Give a relative tell()
* {@inheritdoc}
*/
public function tell()
{
return $this->stream->tell() - $this->offset;
}
/**
* Set the offset to start limiting from
*
* @param int $offset Offset to seek to and begin byte limiting from
*
* @return self
* @throws \RuntimeException
*/
public function setOffset($offset)
{
$this->offset = $offset;
$current = $this->stream->tell();
if ($current !== $offset) {
// If the stream cannot seek to the offset position, then read to it
if (!$this->stream->seek($offset)) {
if ($current > $offset) {
throw new \RuntimeException("Cannot seek to stream offset {$offset}");
} else {
$this->stream->read($offset - $current);
}
}
}
return $this;
}
/**
* Set the limit of bytes that the decorator allows to be read from the
* stream.
*
* @param int $limit Number of bytes to allow to be read from the stream.
* Use -1 for no limit.
* @return self
*/
public function setLimit($limit)
{
$this->limit = $limit;
return $this;
}
public function read($length)
{
if ($this->limit == -1) {
return $this->stream->read($length);
}
// Check if the current position is less than the total allowed
// bytes + original offset
$remaining = ($this->offset + $this->limit) - $this->stream->tell();
if ($remaining > 0) {
// Only return the amount of requested data, ensuring that the byte
// limit is not exceeded
return $this->stream->read(min($remaining, $length));
} else {
return false;
}
}
}
PK \GM[G G src/functions.phpnu W+A eof()) {
$buf = $stream->read(1048576);
if ($buf === '' || $buf === false) {
break;
}
$buffer .= $buf;
}
} else {
$len = 0;
while (!$stream->eof() && $len < $maxLen) {
$buf = $stream->read($maxLen - $len);
if ($buf === '' || $buf === false) {
break;
}
$buffer .= $buf;
$len = strlen($buffer);
}
}
return $buffer;
}
/**
* Copy the contents of a stream into another stream until the given number
* of bytes have been read.
*
* @param StreamInterface $source Stream to read from
* @param StreamInterface $dest Stream to write to
* @param int $maxLen Maximum number of bytes to read. Pass -1
* to read the entire stream.
*/
function copy_to_stream(
StreamInterface $source,
StreamInterface $dest,
$maxLen = -1
) {
if ($maxLen === -1) {
while (!$source->eof()) {
if (!$dest->write($source->read(1048576))) {
break;
}
}
return;
}
$bytes = 0;
while (!$source->eof()) {
$buf = $source->read($maxLen - $bytes);
if (!($len = strlen($buf))) {
break;
}
$bytes += $len;
$dest->write($buf);
if ($bytes == $maxLen) {
break;
}
}
}
/**
* Calculate a hash of a Stream
*
* @param StreamInterface $stream Stream to calculate the hash for
* @param string $algo Hash algorithm (e.g. md5, crc32, etc)
* @param bool $rawOutput Whether or not to use raw output
*
* @return bool|string Returns false on failure or a hash string on success
*/
function hash(
StreamInterface $stream,
$algo,
$rawOutput = false
) {
$pos = $stream->tell();
if (!$stream->seek(0)) {
return false;
}
$ctx = hash_init($algo);
while (!$stream->eof()) {
hash_update($ctx, $stream->read(1048576));
}
$out = hash_final($ctx, (bool) $rawOutput);
$stream->seek($pos);
return $out;
}
/**
* Read a line from the stream up to the maximum allowed buffer length
*
* @param StreamInterface $stream Stream to read from
* @param int $maxLength Maximum buffer length
*
* @return string|bool
*/
function read_line(StreamInterface $stream, $maxLength = null)
{
$buffer = '';
$size = 0;
while (!$stream->eof()) {
if (false === ($byte = $stream->read(1))) {
return $buffer;
}
$buffer .= $byte;
// Break when a new line is found or the max length - 1 is reached
if ($byte == PHP_EOL || ++$size == $maxLength - 1) {
break;
}
}
return $buffer;
}
/**
* Safely opens a PHP stream resource using a filename.
*
* When fopen fails, PHP normally raises a warning. This function adds an
* error handler that checks for errors and throws an exception instead.
*
* @param string $filename File to open
* @param string $mode Mode used to open the file
*
* @return resource
* @throws \RuntimeException if the file cannot be opened
*/
function safe_open($filename, $mode)
{
$ex = null;
set_error_handler(function () use ($filename, $mode, &$ex) {
$ex = new \RuntimeException(sprintf(
'Unable to open %s using mode %s: %s',
$filename,
$mode,
func_get_args()[1]
));
});
$handle = fopen($filename, $mode);
restore_error_handler();
if ($ex) {
/** @var $ex \RuntimeException */
throw $ex;
}
return $handle;
}
}
PK \GMp p src/NoSeekStream.phpnu W+A [
'r' => true, 'w+' => true, 'r+' => true, 'x+' => true, 'c+' => true,
'rb' => true, 'w+b' => true, 'r+b' => true, 'x+b' => true,
'c+b' => true, 'rt' => true, 'w+t' => true, 'r+t' => true,
'x+t' => true, 'c+t' => true, 'a+' => true
],
'write' => [
'w' => true, 'w+' => true, 'rw' => true, 'r+' => true, 'x+' => true,
'c+' => true, 'wb' => true, 'w+b' => true, 'r+b' => true,
'x+b' => true, 'c+b' => true, 'w+t' => true, 'r+t' => true,
'x+t' => true, 'c+t' => true, 'a' => true, 'a+' => true
]
];
/**
* Create a new stream based on the input type
*
* @param resource|string|StreamInterface $resource Entity body data
* @param int $size Size of the data contained in the resource
*
* @return StreamInterface
* @throws \InvalidArgumentException if the $resource arg is not valid.
*/
public static function factory($resource = '', $size = null)
{
return create($resource, $size);
}
/**
* @param resource $stream Stream resource to wrap
* @param int $size Size of the stream in bytes. Only pass if the
* size cannot be obtained from the stream.
*
* @throws \InvalidArgumentException if the stream is not a stream resource
*/
public function __construct($stream, $size = null)
{
if (!is_resource($stream)) {
throw new \InvalidArgumentException('Stream must be a resource');
}
$this->size = $size;
$this->stream = $stream;
$meta = stream_get_meta_data($this->stream);
$this->seekable = $meta['seekable'];
$this->readable = isset(self::$readWriteHash['read'][$meta['mode']]);
$this->writable = isset(self::$readWriteHash['write'][$meta['mode']]);
$this->uri = isset($meta['uri']) ? $meta['uri'] : null;
}
/**
* Closes the stream when the destructed
*/
public function __destruct()
{
$this->close();
}
public function __toString()
{
if (!$this->stream) {
return '';
}
$this->seek(0);
return (string) stream_get_contents($this->stream);
}
public function getContents($maxLength = -1)
{
return $this->stream
? stream_get_contents($this->stream, $maxLength)
: '';
}
public function close()
{
if (is_resource($this->stream)) {
fclose($this->stream);
}
$this->detach();
}
public function detach()
{
$result = $this->stream;
$this->stream = $this->size = $this->uri = null;
$this->readable = $this->writable = $this->seekable = false;
return $result;
}
public function getSize()
{
if ($this->size !== null) {
return $this->size;
}
if (!$this->stream) {
return null;
}
// Clear the stat cache if the stream has a URI
if ($this->uri) {
clearstatcache(true, $this->uri);
}
$stats = fstat($this->stream);
if (isset($stats['size'])) {
$this->size = $stats['size'];
return $this->size;
}
return null;
}
public function isReadable()
{
return $this->readable;
}
public function isWritable()
{
return $this->writable;
}
public function isSeekable()
{
return $this->seekable;
}
public function eof()
{
return $this->stream && feof($this->stream);
}
public function tell()
{
return $this->stream ? ftell($this->stream) : false;
}
public function setSize($size)
{
$this->size = $size;
return $this;
}
public function seek($offset, $whence = SEEK_SET)
{
return $this->seekable
? fseek($this->stream, $offset, $whence) === 0
: false;
}
public function read($length)
{
return $this->readable ? fread($this->stream, $length) : '';
}
public function write($string)
{
// We can't know the size after writing anything
$this->size = null;
return $this->writable ? fwrite($this->stream, $string) : false;
}
/**
* Get stream metadata as an associative array or retrieve a specific key.
*
* The keys returned are identical to the keys returned from PHP's
* stream_get_meta_data() function.
*
* @param string $key Specific metadata to retrieve.
*
* @return array|mixed|null Returns an associative array if no key is
* no key is provided. Returns a specific key
* value if a key is provided and the value is
* found, or null if the key is not found.
* @see http://php.net/manual/en/function.stream-get-meta-data.php
*/
public function getMetadata($key = null)
{
$meta = $this->stream ? stream_get_meta_data($this->stream) : [];
return !$key ? $meta : (isset($meta[$key]) ? $meta[$key] : null);
}
}
PK \GMK src/StreamDecoratorTrait.phpnu W+A stream = $stream;
}
public function __toString()
{
try {
$this->seek(0);
return $this->getContents();
} catch (\Exception $e) {
// Really, PHP? https://bugs.php.net/bug.php?id=53648
trigger_error('StreamDecorator::__toString exception: '
. (string) $e, E_USER_ERROR);
return '';
}
}
public function getContents($maxLength = -1)
{
return copy_to_string($this, $maxLength);
}
/**
* Allow decorators to implement custom methods
*
* @param string $method Missing method name
* @param array $args Method arguments
*
* @return mixed
*/
public function __call($method, array $args)
{
$result = call_user_func_array(array($this->stream, $method), $args);
// Always return the wrapped object if the result is a return $this
return $result === $this->stream ? $this : $result;
}
public function close()
{
$this->stream->close();
}
public function getMetadata($key = null)
{
return $this->stream instanceof MetadataStreamInterface
? $this->stream->getMetadata($key)
: null;
}
public function detach()
{
return $this->stream->detach();
}
public function getSize()
{
return $this->stream->getSize();
}
public function eof()
{
return $this->stream->eof();
}
public function tell()
{
return $this->stream->tell();
}
public function isReadable()
{
return $this->stream->isReadable();
}
public function isWritable()
{
return $this->stream->isWritable();
}
public function isSeekable()
{
return $this->stream->isSeekable();
}
public function seek($offset, $whence = SEEK_SET)
{
return $this->stream->seek($offset, $whence);
}
public function read($length)
{
return $this->stream->read($length);
}
public function write($string)
{
return $this->stream->write($string);
}
}
PK \GM=c src/AppendStream.phpnu W+A addStream($stream);
}
}
public function __toString()
{
try {
$this->seek(0);
return $this->getContents();
} catch (\Exception $e) {
return '';
}
}
/**
* Add a stream to the AppendStream
*
* @param StreamInterface $stream Stream to append. Must be readable.
*
* @throws \InvalidArgumentException if the stream is not readable
*/
public function addStream(StreamInterface $stream)
{
if (!$stream->isReadable()) {
throw new \InvalidArgumentException('Each stream must be readable');
}
// The stream is only seekable if all streams are seekable
if (!$stream->isSeekable()) {
$this->seekable = false;
}
$this->streams[] = $stream;
}
public function getContents($maxLength = -1)
{
return copy_to_string($this, $maxLength);
}
/**
* Closes each attached stream.
*
* {@inheritdoc}
*/
public function close()
{
$this->pos = $this->current = 0;
foreach ($this->streams as $stream) {
$stream->close();
}
$this->streams = [];
}
/**
* Detaches each attached stream
*
* {@inheritdoc}
*/
public function detach()
{
$this->close();
}
public function tell()
{
return $this->pos;
}
/**
* Tries to calculate the size by adding the size of each stream.
*
* If any of the streams do not return a valid number, then the size of the
* append stream cannot be determined and null is returned.
*
* {@inheritdoc}
*/
public function getSize()
{
$size = 0;
foreach ($this->streams as $stream) {
$s = $stream->getSize();
if ($s === null) {
return null;
}
$size += $s;
}
return $size;
}
public function eof()
{
return !$this->streams ||
($this->current >= count($this->streams) - 1 &&
$this->streams[$this->current]->eof());
}
/**
* Attempts to seek to the given position. Only supports SEEK_SET.
*
* {@inheritdoc}
*/
public function seek($offset, $whence = SEEK_SET)
{
if (!$this->seekable || $whence !== SEEK_SET) {
return false;
}
$success = true;
$this->pos = $this->current = 0;
// Rewind each stream
foreach ($this->streams as $stream) {
if (!$stream->seek(0)) {
$success = false;
}
}
if (!$success) {
return false;
}
// Seek to the actual position by reading from each stream
while ($this->pos < $offset && !$this->eof()) {
$this->read(min(8096, $offset - $this->pos));
}
return $this->pos == $offset;
}
/**
* Reads from all of the appended streams until the length is met or EOF.
*
* {@inheritdoc}
*/
public function read($length)
{
$buffer = '';
$total = count($this->streams) - 1;
$remaining = $length;
while ($remaining > 0) {
// Progress to the next stream if needed.
if ($this->streams[$this->current]->eof()) {
if ($this->current == $total) {
break;
}
$this->current++;
}
$buffer .= $this->streams[$this->current]->read($remaining);
$remaining = $length - strlen($buffer);
}
$this->pos += strlen($buffer);
return $buffer;
}
public function isReadable()
{
return true;
}
public function isWritable()
{
return false;
}
public function isSeekable()
{
return $this->seekable;
}
public function write($string)
{
return false;
}
}
PK \GM=>
>
src/GuzzleStreamWrapper.phpnu W+A isReadable()) {
$mode = $stream->isWritable() ? 'r+' : 'r';
} elseif ($stream->isWritable()) {
$mode = 'w';
} else {
throw new \InvalidArgumentException('The stream must be readable, '
. 'writable, or both.');
}
return fopen('guzzle://stream', $mode, null, stream_context_create([
'guzzle' => ['stream' => $stream]
]));
}
public function stream_open($path, $mode, $options, &$opened_path)
{
$options = stream_context_get_options($this->context);
if (!isset($options['guzzle']['stream'])) {
return false;
}
$this->mode = $mode;
$this->stream = $options['guzzle']['stream'];
return true;
}
public function stream_read($count)
{
return $this->stream->read($count);
}
public function stream_write($data)
{
return (int) $this->stream->write($data);
}
public function stream_tell()
{
return $this->stream->tell();
}
public function stream_eof()
{
return $this->stream->eof();
}
public function stream_seek($offset, $whence)
{
return $this->stream->seek($offset, $whence);
}
public function stream_stat()
{
static $modeMap = [
'r' => 33060,
'r+' => 33206,
'w' => 33188
];
return [
'dev' => 0,
'ino' => 0,
'mode' => $modeMap[$this->mode],
'nlink' => 0,
'uid' => 0,
'gid' => 0,
'rdev' => 0,
'size' => $this->stream->getSize() ?: 0,
'atime' => 0,
'mtime' => 0,
'ctime' => 0,
'blksize' => 0,
'blocks' => 0
];
}
}
PK \GM4vo src/StreamInterface.phpnu W+A filename = $filename;
$this->mode = $mode;
}
public function __toString()
{
try {
return (string) $this->getStream();
} catch (\Exception $e) {
return '';
}
}
private function getStream()
{
if (!$this->stream) {
$this->stream = create(safe_open($this->filename, $this->mode));
}
return $this->stream;
}
public function getContents($maxLength = -1)
{
return copy_to_string($this->getStream(), $maxLength);
}
public function close()
{
if ($this->stream) {
$this->stream->close();
}
}
public function detach()
{
$stream = $this->getStream();
$result = $stream->detach();
$this->close();
return $result;
}
public function tell()
{
return $this->stream ? $this->stream->tell() : 0;
}
public function getSize()
{
return $this->getStream()->getSize();
}
public function eof()
{
return $this->getStream()->eof();
}
public function seek($offset, $whence = SEEK_SET)
{
return $this->getStream()->seek($offset, $whence);
}
public function read($length)
{
return $this->getStream()->read($length);
}
public function isReadable()
{
return $this->getStream()->isReadable();
}
public function isWritable()
{
return $this->getStream()->isWritable();
}
public function isSeekable()
{
return $this->getStream()->isSeekable();
}
public function write($string)
{
return $this->getStream()->write($string);
}
public function getMetadata($key = null)
{
return $this->getStream()->getMetadata($key);
}
}
PK \GMs|uc c phpunit.xml.distnu W+A
tests
src
PK \GMC tests/NoSeekStreamTest.phpnu W+A getMockBuilder('GuzzleHttp\Stream\StreamInterface')
->setMethods(['isSeekable', 'seek'])
->getMockForAbstractClass();
$s->expects($this->never())->method('seek');
$s->expects($this->never())->method('isSeekable');
$wrapped = new NoSeekStream($s);
$this->assertFalse($wrapped->isSeekable());
$this->assertFalse($wrapped->seek(2));
}
public function testHandlesClose()
{
$s = Stream::factory('foo');
$wrapped = new NoSeekStream($s);
$wrapped->close();
$this->assertFalse($wrapped->write('foo'));
}
}
PK \GM)% % tests/functionsTest.phpnu W+A assertEquals('foobaz', Stream\copy_to_string($s));
$s->seek(0);
$this->assertEquals('foo', Stream\copy_to_string($s, 3));
$this->assertEquals('baz', Stream\copy_to_string($s, 3));
$this->assertEquals('', Stream\copy_to_string($s));
}
public function testCopiesToStream()
{
$s1 = Stream\create('foobaz');
$s2 = Stream\create('');
Stream\copy_to_stream($s1, $s2);
$this->assertEquals('foobaz', (string) $s2);
$s2 = Stream\create('');
$s1->seek(0);
Stream\copy_to_stream($s1, $s2, 3);
$this->assertEquals('foo', (string) $s2);
Stream\copy_to_stream($s1, $s2, 3);
$this->assertEquals('foobaz', (string) $s2);
}
public function testReadsLines()
{
$s = Stream\create("foo\nbaz\nbar");
$this->assertEquals("foo\n", Stream\read_line($s));
$this->assertEquals("baz\n", Stream\read_line($s));
$this->assertEquals("bar", Stream\read_line($s));
}
public function testReadsLinesUpToMaxLength()
{
$s = Stream\create("12345\n");
$this->assertEquals("123", Stream\read_line($s, 4));
$this->assertEquals("45\n", Stream\read_line($s));
}
public function testReadsLineUntilFalseReturnedFromRead()
{
$s = $this->getMockBuilder('GuzzleHttp\Stream\Stream')
->setMethods(['read', 'eof'])
->disableOriginalConstructor()
->getMock();
$s->expects($this->exactly(2))
->method('read')
->will($this->returnCallback(function () {
static $c = false;
if ($c) {
return false;
}
$c = true;
return 'h';
}));
$s->expects($this->exactly(2))
->method('eof')
->will($this->returnValue(false));
$this->assertEquals("h", Stream\read_line($s));
}
public function testCalculatesHash()
{
$s = Stream\create('foobazbar');
$this->assertEquals(md5('foobazbar'), Stream\hash($s, 'md5'));
}
public function testCalculatesHashSeeksToOriginalPosition()
{
$s = Stream\create('foobazbar');
$s->seek(4);
$this->assertEquals(md5('foobazbar'), Stream\hash($s, 'md5'));
$this->assertEquals(4, $s->tell());
}
public function testFactoryCreatesFromEmptyString()
{
$s = Stream\create();
$this->assertInstanceOf('GuzzleHttp\Stream\Stream', $s);
}
public function testFactoryCreatesFromResource()
{
$r = fopen(__FILE__, 'r');
$s = Stream\create($r);
$this->assertInstanceOf('GuzzleHttp\Stream\Stream', $s);
$this->assertSame(file_get_contents(__FILE__), (string) $s);
}
public function testFactoryCreatesFromObjectWithToString()
{
$r = new HasToString();
$s = Stream\create($r);
$this->assertInstanceOf('GuzzleHttp\Stream\Stream', $s);
$this->assertEquals('foo', (string) $s);
}
public function testCreatePassesThrough()
{
$s = Stream\create('foo');
$this->assertSame($s, Stream\create($s));
}
/**
* @expectedException \InvalidArgumentException
*/
public function testThrowsExceptionForUnknown()
{
Stream\create(new \stdClass());
}
public function testOpensFilesSuccessfully()
{
$r = Stream\safe_open(__FILE__, 'r');
$this->assertInternalType('resource', $r);
fclose($r);
}
/**
* @expectedException \RuntimeException
* @expectedExceptionMessage Unable to open /path/to/does/not/exist using mode r
*/
public function testThrowsExceptionNotWarning()
{
Stream\safe_open('/path/to/does/not/exist', 'r');
}
}
class HasToString
{
public function __toString() {
return 'foo';
}
}
PK \GMӴi i ! tests/GuzzleStreamWrapperTest.phpnu W+A assertSame('foo', fread($handle, 3));
$this->assertSame(3, ftell($handle));
$this->assertSame(3, fwrite($handle, 'bar'));
$this->assertSame(0, fseek($handle, 0));
$this->assertSame('foobar', fread($handle, 6));
$this->assertTrue(feof($handle));
// This fails on HHVM for some reason
if (!defined('HHVM_VERSION')) {
$this->assertEquals([
'dev' => 0,
'ino' => 0,
'mode' => 33206,
'nlink' => 0,
'uid' => 0,
'gid' => 0,
'rdev' => 0,
'size' => 6,
'atime' => 0,
'mtime' => 0,
'ctime' => 0,
'blksize' => 0,
'blocks' => 0,
0 => 0,
1 => 0,
2 => 33206,
3 => 0,
4 => 0,
5 => 0,
6 => 0,
7 => 6,
8 => 0,
9 => 0,
10 => 0,
11 => 0,
12 => 0,
], fstat($handle));
}
$this->assertTrue(fclose($handle));
$this->assertSame('foobar', (string) $stream);
}
/**
* @expectedException \InvalidArgumentException
*/
public function testValidatesStream()
{
$stream = $this->getMockBuilder('GuzzleHttp\Stream\StreamInterface')
->setMethods(['isReadable', 'isWritable'])
->getMockForAbstractClass();
$stream->expects($this->once())
->method('isReadable')
->will($this->returnValue(false));
$stream->expects($this->once())
->method('isWritable')
->will($this->returnValue(false));
GuzzleStreamWrapper::getResource($stream);
}
/**
* @expectedException \PHPUnit_Framework_Error_Warning
*/
public function testReturnsFalseWhenStreamDoesNotExist()
{
fopen('guzzle://foo', 'r');
}
public function testCanOpenReadonlyStream()
{
$stream = $this->getMockBuilder('GuzzleHttp\Stream\StreamInterface')
->setMethods(['isReadable', 'isWritable'])
->getMockForAbstractClass();
$stream->expects($this->once())
->method('isReadable')
->will($this->returnValue(false));
$stream->expects($this->once())
->method('isWritable')
->will($this->returnValue(true));
$r = GuzzleStreamWrapper::getResource($stream);
$this->assertInternalType('resource', $r);
fclose($r);
}
}
PK \GM0
" tests/StreamDecoratorTraitTest.phpnu W+A c = fopen('php://temp', 'r+');
fwrite($this->c, 'foo');
fseek($this->c, 0);
$this->a = Stream::factory($this->c);
$this->b = new Str($this->a);
}
public function testCatchesExceptionsWhenCastingToString()
{
$s = $this->getMockBuilder('GuzzleHttp\Stream\StreamInterface')
->setMethods(['read'])
->getMockForAbstractClass();
$s->expects($this->once())
->method('read')
->will($this->throwException(new \Exception('foo')));
$msg = '';
set_error_handler(function ($errNo, $str) use (&$msg) { $msg = $str; });
echo new Str($s);
restore_error_handler();
$this->assertContains('foo', $msg);
}
public function testToString()
{
$this->assertEquals('foo', (string) $this->b);
}
public function testHasSize()
{
$this->assertEquals(3, $this->b->getSize());
$this->assertSame($this->b, $this->b->setSize(2));
$this->assertEquals(2, $this->b->getSize());
}
public function testReads()
{
$this->assertEquals('foo', $this->b->read(10));
}
public function testCheckMethods()
{
$this->assertEquals($this->a->isReadable(), $this->b->isReadable());
$this->assertEquals($this->a->isWritable(), $this->b->isWritable());
$this->assertEquals($this->a->isSeekable(), $this->b->isSeekable());
}
public function testSeeksAndTells()
{
$this->assertTrue($this->b->seek(1));
$this->assertEquals(1, $this->a->tell());
$this->assertEquals(1, $this->b->tell());
$this->assertTrue($this->b->seek(0));
$this->assertEquals(0, $this->a->tell());
$this->assertEquals(0, $this->b->tell());
$this->assertTrue($this->b->seek(0, SEEK_END));
$this->assertEquals(3, $this->a->tell());
$this->assertEquals(3, $this->b->tell());
}
public function testGetsContents()
{
$this->assertEquals('foo', $this->b->getContents());
$this->assertEquals('', $this->b->getContents());
$this->b->seek(1);
$this->assertEquals('o', $this->b->getContents(1));
$this->assertEquals('', $this->b->getContents(0));
}
public function testCloses()
{
$this->b->close();
$this->assertFalse(is_resource($this->c));
}
public function testDetaches()
{
$this->b->detach();
$this->assertFalse($this->b->isReadable());
}
public function testWrapsMetadata()
{
$this->assertSame($this->b->getMetadata(), $this->a->getMetadata());
$this->assertSame($this->b->getMetadata('uri'), $this->a->getMetadata('uri'));
}
public function testWrapsWrites()
{
$this->b->seek(0, SEEK_END);
$this->b->write('foo');
$this->assertEquals('foofoo', (string) $this->a);
}
}
PK \GMjt+ tests/StreamTest.phpnu W+A assertTrue($stream->isReadable());
$this->assertTrue($stream->isWritable());
$this->assertTrue($stream->isSeekable());
$this->assertEquals('php://temp', $stream->getMetadata('uri'));
$this->assertInternalType('array', $stream->getMetadata());
$this->assertEquals(4, $stream->getSize());
$this->assertFalse($stream->eof());
$stream->close();
}
public function testStreamClosesHandleOnDestruct()
{
$handle = fopen('php://temp', 'r');
$stream = new Stream($handle);
unset($stream);
$this->assertFalse(is_resource($handle));
}
public function testConvertsToString()
{
$handle = fopen('php://temp', 'w+');
fwrite($handle, 'data');
$stream = new Stream($handle);
$this->assertEquals('data', (string) $stream);
$this->assertEquals('data', (string) $stream);
$stream->close();
}
public function testGetsContents()
{
$handle = fopen('php://temp', 'w+');
fwrite($handle, 'data');
$stream = new Stream($handle);
$this->assertEquals('', $stream->getContents());
$stream->seek(0);
$this->assertEquals('data', $stream->getContents());
$this->assertEquals('', $stream->getContents());
$stream->seek(0);
$this->assertEquals('da', $stream->getContents(2));
$stream->close();
}
public function testChecksEof()
{
$handle = fopen('php://temp', 'w+');
fwrite($handle, 'data');
$stream = new Stream($handle);
$this->assertFalse($stream->eof());
$stream->read(4);
$this->assertTrue($stream->eof());
$stream->close();
}
public function testAllowsSettingManualSize()
{
$handle = fopen('php://temp', 'w+');
fwrite($handle, 'data');
$stream = new Stream($handle);
$stream->setSize(10);
$this->assertEquals(10, $stream->getSize());
$stream->close();
}
public function testGetSize()
{
$size = filesize(__FILE__);
$handle = fopen(__FILE__, 'r');
$stream = new Stream($handle);
$this->assertEquals($size, $stream->getSize());
// Load from cache
$this->assertEquals($size, $stream->getSize());
$stream->close();
}
public function testEnsuresSizeIsConsistent()
{
$h = fopen('php://temp', 'w+');
$this->assertEquals(3, fwrite($h, 'foo'));
$stream = new Stream($h);
$this->assertEquals(3, $stream->getSize());
$this->assertEquals(4, $stream->write('test'));
$this->assertEquals(7, $stream->getSize());
$this->assertEquals(7, $stream->getSize());
$stream->close();
}
public function testProvidesStreamPosition()
{
$handle = fopen('php://temp', 'w+');
$stream = new Stream($handle);
$this->assertEquals(0, $stream->tell());
$stream->write('foo');
$this->assertEquals(3, $stream->tell());
$stream->seek(1);
$this->assertEquals(1, $stream->tell());
$this->assertSame(ftell($handle), $stream->tell());
$stream->close();
}
public function testKeepsPositionOfResource()
{
$h = fopen(__FILE__, 'r');
fseek($h, 10);
$stream = Stream::factory($h);
$this->assertEquals(10, $stream->tell());
$stream->close();
}
public function testCanDetachStream()
{
$r = fopen('php://temp', 'w+');
$stream = new Stream($r);
$this->assertTrue($stream->isReadable());
$this->assertSame($r, $stream->detach());
$this->assertNull($stream->detach());
$this->assertFalse($stream->isReadable());
$this->assertSame('', $stream->read(10));
$this->assertFalse($stream->isWritable());
$this->assertFalse($stream->write('foo'));
$this->assertFalse($stream->isSeekable());
$this->assertFalse($stream->seek(10));
$this->assertFalse($stream->tell());
$this->assertFalse($stream->eof());
$this->assertNull($stream->getSize());
$this->assertSame('', (string) $stream);
$this->assertSame('', $stream->getContents());
$stream->close();
}
public function testCloseClearProperties()
{
$handle = fopen('php://temp', 'r+');
$stream = new Stream($handle);
$stream->close();
$this->assertEmpty($stream->getMetadata());
$this->assertFalse($stream->isSeekable());
$this->assertFalse($stream->isReadable());
$this->assertFalse($stream->isWritable());
$this->assertNull($stream->getSize());
}
public function testCreatesWithFactory()
{
$stream = Stream::factory('foo');
$this->assertInstanceOf('GuzzleHttp\Stream\Stream', $stream);
$this->assertEquals('foo', $stream->getContents());
$stream->close();
}
}
PK \GMBh% tests/LazyOpenStreamTest.phpnu W+A fname = tempnam('/tmp', 'tfile');
if (file_exists($this->fname)) {
unlink($this->fname);
}
}
public function tearDown()
{
if (file_exists($this->fname)) {
unlink($this->fname);
}
}
public function testOpensLazily()
{
$l = new LazyOpenStream($this->fname, 'w+');
$l->write('foo');
$this->assertInternalType('array', $l->getMetadata());
$this->assertFileExists($this->fname);
$this->assertEquals('foo', file_get_contents($this->fname));
}
public function testProxiesToFile()
{
file_put_contents($this->fname, 'foo');
$l = new LazyOpenStream($this->fname, 'r');
$this->assertEquals('foo', $l->read(4));
$this->assertTrue($l->eof());
$this->assertEquals(3, $l->tell());
$this->assertTrue($l->isReadable());
$this->assertTrue($l->isSeekable());
$this->assertFalse($l->isWritable());
$l->seek(1);
$this->assertEquals('oo', $l->getContents());
$this->assertEquals('foo', (string) $l);
$this->assertEquals(3, $l->getSize());
$this->assertInternalType('array', $l->getMetadata());
$l->close();
}
public function testDetachesUnderlyingStream()
{
file_put_contents($this->fname, 'foo');
$l = new LazyOpenStream($this->fname, 'r');
$r = $l->detach();
$this->assertInternalType('resource', $r);
fclose($r);
}
}
PK \GMDe e tests/AppendStreamTest.phpnu W+A getMockBuilder('GuzzleHttp\Stream\StreamInterface')
->setMethods(['isReadable'])
->getMockForAbstractClass();
$s->expects($this->once())
->method('isReadable')
->will($this->returnValue(false));
$a->addStream($s);
}
public function testValidatesSeekType()
{
$a = new AppendStream();
$this->assertFalse($a->seek(100, SEEK_CUR));
}
public function testTriesToRewindOnSeek()
{
$a = new AppendStream();
$s = $this->getMockBuilder('GuzzleHttp\Stream\StreamInterface')
->setMethods(['isReadable', 'seek', 'isSeekable'])
->getMockForAbstractClass();
$s->expects($this->once())
->method('isReadable')
->will($this->returnValue(true));
$s->expects($this->once())
->method('isSeekable')
->will($this->returnValue(true));
$s->expects($this->once())
->method('seek')
->will($this->returnValue(false));
$a->addStream($s);
$this->assertFalse($a->seek(10));
}
public function testSeeksToPositionByReading()
{
$a = new AppendStream([
Stream::factory('foo'),
Stream::factory('bar'),
Stream::factory('baz'),
]);
$this->assertTrue($a->seek(3));
$this->assertEquals(3, $a->tell());
$this->assertEquals('bar', $a->read(3));
$a->seek(6);
$this->assertEquals(6, $a->tell());
$this->assertEquals('baz', $a->read(3));
}
public function testDetachesEachStream()
{
$s1 = Stream::factory('foo');
$s2 = Stream::factory('foo');
$a = new AppendStream([$s1, $s2]);
$this->assertSame('foofoo', (string) $a);
$a->detach();
$this->assertSame('', (string) $a);
$this->assertSame(0, $a->getSize());
}
public function testClosesEachStream()
{
$s1 = Stream::factory('foo');
$a = new AppendStream([$s1]);
$a->close();
$this->assertSame('', (string) $a);
}
public function testIsNotWritable()
{
$a = new AppendStream([Stream::factory('foo')]);
$this->assertFalse($a->isWritable());
$this->assertTrue($a->isSeekable());
$this->assertTrue($a->isReadable());
$this->assertFalse($a->write('foo'));
}
public function testDoesNotNeedStreams()
{
$a = new AppendStream();
$this->assertEquals('', (string) $a);
}
public function testCanReadFromMultipleStreams()
{
$a = new AppendStream([
Stream::factory('foo'),
Stream::factory('bar'),
Stream::factory('baz'),
]);
$this->assertFalse($a->eof());
$this->assertSame(0, $a->tell());
$this->assertEquals('foo', $a->read(3));
$this->assertEquals('bar', $a->read(3));
$this->assertEquals('baz', $a->read(3));
$this->assertTrue($a->eof());
$this->assertSame(9, $a->tell());
$this->assertEquals('foobarbaz', (string) $a);
}
public function testCanDetermineSizeFromMultipleStreams()
{
$a = new AppendStream([
Stream::factory('foo'),
Stream::factory('bar')
]);
$this->assertEquals(6, $a->getSize());
$s = $this->getMockBuilder('GuzzleHttp\Stream\StreamInterface')
->setMethods(['isSeekable', 'isReadable'])
->getMockForAbstractClass();
$s->expects($this->once())
->method('isSeekable')
->will($this->returnValue(null));
$s->expects($this->once())
->method('isReadable')
->will($this->returnValue(true));
$a->addStream($s);
$this->assertNull($a->getSize());
}
public function testCatchesExceptionsWhenCastingToString()
{
$s = $this->getMockBuilder('GuzzleHttp\Stream\StreamInterface')
->setMethods(['read', 'isReadable', 'eof'])
->getMockForAbstractClass();
$s->expects($this->once())
->method('read')
->will($this->throwException(new \RuntimeException('foo')));
$s->expects($this->once())
->method('isReadable')
->will($this->returnValue(true));
$s->expects($this->any())
->method('eof')
->will($this->returnValue(false));
$a = new AppendStream([$s]);
$this->assertFalse($a->eof());
$this->assertSame('', (string) $a);
}
}
PK \GM
tests/LimitStreamTest.phpnu W+A decorated = Stream::factory(fopen(__FILE__, 'r'));
$this->body = new LimitStream($this->decorated, 10, 3);
}
public function testReturnsSubset()
{
$body = new LimitStream(Stream::factory('foo'), -1, 1);
$this->assertEquals('oo', (string) $body);
$this->assertTrue($body->eof());
$body->seek(0);
$this->assertFalse($body->eof());
$this->assertEquals('oo', $body->read(100));
$this->assertTrue($body->eof());
}
public function testReturnsSubsetWhenCastToString()
{
$body = Stream::factory('foo_baz_bar');
$limited = new LimitStream($body, 3, 4);
$this->assertEquals('baz', (string) $limited);
}
public function testReturnsSubsetOfEmptyBodyWhenCastToString()
{
$body = Stream::factory('');
$limited = new LimitStream($body, 0, 10);
$this->assertEquals('', (string) $limited);
}
public function testSeeksWhenConstructed()
{
$this->assertEquals(0, $this->body->tell());
$this->assertEquals(3, $this->decorated->tell());
}
public function testAllowsBoundedSeek()
{
$this->body->seek(100);
$this->assertEquals(13, $this->decorated->tell());
$this->body->seek(0);
$this->assertEquals(0, $this->body->tell());
$this->assertEquals(3, $this->decorated->tell());
$this->assertEquals(false, $this->body->seek(1000, SEEK_END));
}
public function testReadsOnlySubsetOfData()
{
$data = $this->body->read(100);
$this->assertEquals(10, strlen($data));
$this->assertFalse($this->body->read(1000));
$this->body->setOffset(10);
$newData = $this->body->read(100);
$this->assertEquals(10, strlen($newData));
$this->assertNotSame($data, $newData);
}
public function testClaimsConsumedWhenReadLimitIsReached()
{
$this->assertFalse($this->body->eof());
$this->body->read(1000);
$this->assertTrue($this->body->eof());
}
public function testContentLengthIsBounded()
{
$this->assertEquals(10, $this->body->getSize());
}
public function testGetContentsIsBasedOnSubset()
{
$body = new LimitStream(Stream::factory('foobazbar'), 3, 3);
$this->assertEquals('baz', $body->getContents());
}
}
PK \GM1!9 9 tests/CachingStreamTest.phpnu W+A decorated = Stream::factory('testing');
$this->body = new CachingStream($this->decorated);
}
public function tearDown()
{
$this->decorated->close();
$this->body->close();
}
public function testUsesRemoteSizeIfPossible()
{
$body = Stream::factory('test');
$caching = new CachingStream($body);
$this->assertEquals(4, $caching->getSize());
}
/**
* @expectedException \RuntimeException
* @expectedExceptionMessage Cannot seek to byte 10
*/
public function testCannotSeekPastWhatHasBeenRead()
{
$this->body->seek(10);
}
public function testCannotUseSeekEnd()
{
$this->assertFalse($this->body->seek(2, SEEK_END));
}
public function testRewindUsesSeek()
{
$a = Stream::factory('foo');
$d = $this->getMockBuilder('GuzzleHttp\Stream\CachingStream')
->setMethods(array('seek'))
->setConstructorArgs(array($a))
->getMock();
$d->expects($this->once())
->method('seek')
->with(0)
->will($this->returnValue(true));
$d->seek(0);
}
public function testCanSeekToReadBytes()
{
$this->assertEquals('te', $this->body->read(2));
$this->body->seek(0);
$this->assertEquals('test', $this->body->read(4));
$this->assertEquals(4, $this->body->tell());
$this->body->seek(2);
$this->assertEquals(2, $this->body->tell());
$this->body->seek(2, SEEK_CUR);
$this->assertEquals(4, $this->body->tell());
$this->assertEquals('ing', $this->body->read(3));
}
public function testWritesToBufferStream()
{
$this->body->read(2);
$this->body->write('hi');
$this->body->seek(0);
$this->assertEquals('tehiing', (string) $this->body);
}
public function testSkipsOverwrittenBytes()
{
$decorated = Stream::factory(
implode("\n", array_map(function ($n) {
return str_pad($n, 4, '0', STR_PAD_LEFT);
}, range(0, 25))),
true
);
$body = new CachingStream($decorated);
$this->assertEquals("0000\n", \GuzzleHttp\Stream\read_line($body));
$this->assertEquals("0001\n", \GuzzleHttp\Stream\read_line($body));
// Write over part of the body yet to be read, so skip some bytes
$this->assertEquals(5, $body->write("TEST\n"));
$this->assertEquals(5, $this->readAttribute($body, 'skipReadBytes'));
// Read, which skips bytes, then reads
$this->assertEquals("0003\n", \GuzzleHttp\Stream\read_line($body));
$this->assertEquals(0, $this->readAttribute($body, 'skipReadBytes'));
$this->assertEquals("0004\n", \GuzzleHttp\Stream\read_line($body));
$this->assertEquals("0005\n", \GuzzleHttp\Stream\read_line($body));
// Overwrite part of the cached body (so don't skip any bytes)
$body->seek(5);
$this->assertEquals(5, $body->write("ABCD\n"));
$this->assertEquals(0, $this->readAttribute($body, 'skipReadBytes'));
$this->assertEquals("TEST\n", \GuzzleHttp\Stream\read_line($body));
$this->assertEquals("0003\n", \GuzzleHttp\Stream\read_line($body));
$this->assertEquals("0004\n", \GuzzleHttp\Stream\read_line($body));
$this->assertEquals("0005\n", \GuzzleHttp\Stream\read_line($body));
$this->assertEquals("0006\n", \GuzzleHttp\Stream\read_line($body));
$this->assertEquals(5, $body->write("1234\n"));
$this->assertEquals(5, $this->readAttribute($body, 'skipReadBytes'));
// Seek to 0 and ensure the overwritten bit is replaced
$body->seek(0);
$this->assertEquals("0000\nABCD\nTEST\n0003\n0004\n0005\n0006\n1234\n0008\n0009\n", $body->read(50));
// Ensure that casting it to a string does not include the bit that was overwritten
$this->assertContains("0000\nABCD\nTEST\n0003\n0004\n0005\n0006\n1234\n0008\n0009\n", (string) $body);
}
public function testClosesBothStreams()
{
$s = fopen('php://temp', 'r');
$a = Stream::factory($s);
$d = new CachingStream($a);
$d->close();
$this->assertFalse(is_resource($s));
}
}
PK \GMXW W LICENSEnu W+A Copyright (c) 2014 Michael Dowling, https://github.com/mtdowling
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
PK \GMvq
README.rstnu W+A ==============
Guzzle Streams
==============
Provides a simple abstraction over streams of data.
This library is used in `Guzzle 4 `_ and is
an implementation of the proposed `PSR-7 stream interface `_.
Installation
============
This package can be installed easily using `Composer `_.
Simply add the following to the composer.json file at the root of your project:
.. code-block:: javascript
{
"require": {
"guzzlehttp/streams": "~1.0"
}
}
Then install your dependencies using ``composer.phar install``.
Documentation
=============
The documentation for this package can be found on the main Guzzle website at
http://docs.guzzlephp.org/en/guzzle4/streams.html.
Testing
=======
This library is tested using PHPUnit. You'll need to install the dependencies
using `Composer `_ then run ``make test``.
PK \GMazO*
composer.jsonnu W+A {
"name": "guzzlehttp/streams",
"description": "Provides a simple abstraction over streams of data (Guzzle 4+)",
"homepage": "http://guzzlephp.org/",
"keywords": ["stream", "guzzle"],
"license": "MIT",
"authors": [
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
}
],
"require": {
"php": ">=5.4.0"
},
"require-dev": {
"phpunit/phpunit": "~4.0"
},
"autoload": {
"psr-4": { "GuzzleHttp\\Stream\\": "src/" },
"files": ["src/functions.php"]
},
"extra": {
"branch-alias": {
"dev-master": "1.5-dev"
}
}
}
PK \GM'19 .travis.ymlnu W+A PK \GMѝ>L Makefilenu W+A PK \GM:[F; ;
= .gitignorenu W+A PK \GM=*~ ~
CHANGELOG.rstnu W+A PK \GMx}ތ m src/CachingStream.phpnu W+A PK \GMc} > src/LimitStream.phpnu W+A PK \GM[G G D&