stk2k/iostream

View on GitHub
src/string/StringInputStream.php

Summary

Maintainability
B
4 hrs
Test Coverage
<?php
declare(strict_types=1);

namespace stk2k\iostream\string;

use InvalidArgumentException;
use Generator;

use stk2k\iostream\BaseStream;
use stk2k\iostream\constants\SeekOrigin;
use stk2k\iostream\exception\IOException;
use stk2k\iostream\InputStreamInterface;
use stk2k\xstring\xStringBuffer;
use stk2k\xstring\xString;

class StringInputStream extends BaseStream implements InputStreamInterface
{
    /** @var xStringBuffer */
    private $source;

    /** @var int */
    private $pos = 0;

    /** @var int */
    private $length;

    /**
     * StringInputStream constructor.
     *
     * @param $source
     */
    public function __construct($source)
    {
        if(is_string($source)){
            $source = new xString($source);
        }
        if (!($source instanceof xString) && !($source instanceof xStringBuffer)){
            throw new InvalidArgumentException('Source must be instance of xString or xStringBuffer, but you passed: ' . get_class($source));
        }
        $this->source = $source instanceof xString ? new xStringBuffer($source) : $source;
        $this->length = $this->source->length();
    }

    /**
     * @param xStringBuffer $source
     *
     * @return self
     */
    protected function setSource(xStringBuffer $source) : self
    {
        $this->source = $source;
        return $this;
    }

    /**
     * @return xStringBuffer
     */
    protected function getSource() : xStringBuffer
    {
        return $this->source;
    }

    /**
     * @return Generator
     */
    public function getIterator(): Generator
    {
        foreach($this->source->substring($this->pos) as $c){
            yield $c;
        }
    }

    /**
     * Returns if this stream supports random acccess
     *
     * @return bool
     */
    public function seekable() : bool
    {
        return true;
    }

    /**
     * Returns position of current reading
     *
     * @return int
     */
    public function tell() : int
    {
        return $this->pos;
    }

    /**
     * Returns if this stream supports random acccess
     *
     * @param int $offset
     * @param string $seek_origin
     *
     * @return void
     *
     * @throws IOException
     */
    public function seek(int $offset, string $seek_origin) : void
    {
        switch($seek_origin)
        {
            case SeekOrigin::START:
                if ($offset < 0){
                    throw new IOException('Cannot seek before start position: ' . $offset);
                }
                if ($offset > $this->length - 1){
                    throw new IOException('Cannot seek after end position: ' . $offset);
                }
                $this->pos = $offset;
                break;

            case SeekOrigin::END:
                if ($offset > 0){
                    throw new IOException('Cannot seek after end position: ' . $offset);
                }
                if ($offset < -($this->length - 1)){
                    throw new IOException('Cannot seek before start position: ' . $offset);
                }
                $this->pos = $this->length - 1 + $offset;
                break;

            case SeekOrigin::CURRENT:
                if ($this->pos + $offset > $this->length - 1){
                    throw new IOException('Cannot seek after end position: ' . $offset);
                }
                if ($this->pos + $offset < 0){
                    throw new IOException('Cannot seek before start position: ' . $offset);
                }
                $this->pos += $offset;
                break;
        }
    }

    /**
     * Returns if this stream can be read
     *
     * @return bool
     */
    public function isReadable() : bool
    {
        return $this->pos < $this->length;
    }

    /**
     * @param int|null $length
     *
     * @return xString|null
     */
    public function read(int $length = null): ?string
    {
        if ($this->pos >= $this->length){
            return null;
        }
        if ($length > 0){
            $str = $this->source->substring($this->pos, $length)->value();
            $this->pos += $length;
        }
        else{
            $str = $this->source->substring($this->pos)->value();
            $this->pos = $this->length - 1;
        }
        return $str;
    }

    /**
     * Reads a line from steam. If the stream reached to the end, it returns EOF character('')
     *
     * @param int|null $length
     *
     * @return xString|null
     */
    public function readLine(int $length = null) : ?string
    {
        if ($this->pos >= $this->length){
            return null;
        }
        $str = '';
        $read = 0;
        foreach($this->source->substring($this->pos) as $c)
        {
            $this->pos ++;
            $read ++;
            if ($length > 0 && $read >= $length){
                break;
            }
            if ($c === "\n" || $c === "\r"){
                break;
            }
            else{
                $str .= $c;
            }
        }
        return $str;
    }

    /**
     * Reads multiple lines from steam. If the stream reached to the end, it returns EOF character('')
     * If the lines parameter is specified and its value is negative, this function tries to read all
     * lines from stream.
     *
     * @param int $lines
     *
     * @return array|null
     */
    public function readLines(int $lines = -1) : ?array
    {
        if ($this->pos >= $this->length){
            return null;
        }
        $lines = [];
        $str = '';
        $read = 0;
        foreach($this->source->substring($this->pos) as $c)
        {
            $this->pos ++;
            $read ++;
            if ($c === "\n" || $c === "\r"){
                $lines[] = $str;
                $str = '';
            }
            else{
                $str .= $c;
            }
        }
        if (!empty($str)){
            $lines[] = $str;
        }
        return $lines;
    }

}