Skip to content

Commit

Permalink
feat(TextIterator): support SplFileObject, remove method setFlags(), …
Browse files Browse the repository at this point in the history
…by default trim line and skip empty lines
  • Loading branch information
h4kuna committed Jul 15, 2024
1 parent 7ad6cbe commit 4823101
Show file tree
Hide file tree
Showing 12 changed files with 174 additions and 140 deletions.
5 changes: 5 additions & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ parameters:
paths:
- src
- tests/src
ignoreErrors:
-
message: "#^Generator expects value type TValue of array\\<int, string\\|null\\>\\|string, array\\<int, string\\|null\\>\\|string given\\.$#"
count: 1
path: src/Iterators/TextIterator.php

includes:
- vendor/phpstan/phpstan-strict-rules/rules.neon
2 changes: 1 addition & 1 deletion src/Iterators/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ bar
joe";

$textIterator = new TextIterator($incomingString);
$textIterator->setFlags($textIterator::SKIP_EMPTY_LINE | $textIterator::TRIM_LINE);
// $textIterator->enableCsv();
foreach($textIterator as $line) {
echo $line;
}
Expand Down
157 changes: 70 additions & 87 deletions src/Iterators/TextIterator.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,137 +2,120 @@

namespace h4kuna\DataType\Iterators;

use Generator;
use h4kuna\DataType\Basic\BitwiseOperations;
use h4kuna\DataType\Exceptions\InvalidStateException;
use IteratorAggregate;
use Nette\Utils\Strings;
use SplFileObject;

/**
* Iterate via line
* @template TValue
* @extends \ArrayIterator<int, TValue>
* @template TValue of array<int, string|null>|string|non-empty-string
* @implements IteratorAggregate<int, TValue>
*/
class TextIterator extends \ArrayIterator
final class TextIterator implements IteratorAggregate
{
public const SKIP_EMPTY_LINE = 1048576; // 2^20
public const CSV_MODE = 2097152; // 2^21
public const SKIP_FIRST_LINE = 4194304; // 2^22
public const TRIM_LINE = 8388608; // 2^23
public const SKIP_EMPTY_AND_TRIM_LINE = self::TRIM_LINE | self::SKIP_EMPTY_LINE;

private string $_current = '';

private int $flags = 0;
public const NoSetup = 0;
public const KeepEmptyLine = 1;
public const CsvMode = 2;
public const SkipFirstLine = 4;
public const SkipTrimLine = 8;
private const KeyDelimiter = 'delimiter';
private const KeyEnclosure = 'enclosure';
private const KeyEscape = 'escape';

/** @var array{delimiter: string, enclosure: string, escape: string} */
private array $csv = [
'delimiter' => ',',
'enclosure' => '"',
'escape' => '\\',
self::KeyDelimiter => ',',
self::KeyEnclosure => '"',
self::KeyEscape => '\\',
];

/** @var array<scalar> */
private array|SplFileObject $data;


/**
* @param array<string|null>|string $text
* @param array<scalar>|string $text
* @param int $flags, trim line and skip empty line by default
*/
public function __construct(array|string $text)
public function __construct(
array|string|SplFileObject $text,
private int $flags = self::NoSetup
)
{
parent::__construct(is_array($text) ? $text : self::text2Array($text));
$this->data = is_string($text) ? self::text2Array($text) : $text;
}


/**
* Active csv parser.
* @return static<TValue>
*/
public function setCsv(string $delimiter = ',', string $enclosure = '"', string $escape = '\\'): static
public function enableCsv(string $delimiter = ',', string $enclosure = '"', string $escape = '\\'): self
{
$this->setFlags($this->getFlags() | self::SKIP_EMPTY_LINE | self::CSV_MODE | self::TRIM_LINE);
$this->flags |= self::CsvMode;
$this->csv = [
'delimiter' => $delimiter,
'enclosure' => $enclosure,
'escape' => $escape,
self::KeyDelimiter => $delimiter,
self::KeyEnclosure => $enclosure,
self::KeyEscape => $escape,
];

return $this;
}


/**
* @return array<string>
*/
private static function text2Array(string $text): array
{
$existsNewMethod = method_exists(Strings::class, 'unixNewLines'); // @phpstan-ignore-line
return explode("\n", $existsNewMethod
? Strings::unixNewLines($text)
: Strings::normalizeNewLines($text)
);
}


/**
* Change API behavior *****************************************************
* *************************************************************************
* @return Generator<TValue>
*/

public function setFlags(int $flags): void
{
parent::setFlags($flags);
$this->flags = $flags;
}


public function getFlags(): int
public function getIterator(): Generator
{
return parent::getFlags() | $this->flags;
}

$isTrimEnabled = BitwiseOperations::check($this->flags, self::SkipTrimLine) === false;
$skipEmptyLine = BitwiseOperations::check($this->flags, self::KeepEmptyLine) === false;
$isCsvMode = BitwiseOperations::check($this->flags, self::CsvMode);

/** @return TValue */
public function current(): mixed
{
$content = $this->_current;
if (BitwiseOperations::check($this->getFlags(), self::CSV_MODE)) {
$result = str_getcsv($content, $this->csv['delimiter'], $this->csv['enclosure'], $this->csv['escape']);

return $result;
if ($this->data instanceof SplFileObject) {
if (BitwiseOperations::check($this->data->getFlags(), SplFileObject::READ_CSV)) {
throw new InvalidStateException('SplFileObject is in csv read mode. Let\'s disable it and use self::enableCsv() instead.');
}
$first = 0;
} else {
$first = array_key_first($this->data);
}

return $content;
}


public function rewind(): void
{
parent::rewind();
if ($this->valid() && BitwiseOperations::check($this->getFlags(), self::SKIP_FIRST_LINE)) {
$this->next();
}
}
foreach ($this->data as $key => $row) {
if ($first === $key && BitwiseOperations::check($this->flags, self::SkipFirstLine)) {
continue;
}
assert(is_array($row) === false);

$strRow = (string) $row;

public function valid(): bool
{
$flags = $this->getFlags();
do {
if (parent::valid() === false) {
return false;
if ($isTrimEnabled) {
$strRow = trim($strRow);
}
$current = parent::current();
assert(is_string($current));
$this->_current = $current;
if (BitwiseOperations::check($flags, self::TRIM_LINE)) {
$this->_current = trim($this->_current);

if ($skipEmptyLine && $strRow === '') {
continue;
}
} while (BitwiseOperations::check($flags, self::SKIP_EMPTY_LINE) && $this->_current === '' && $this->moveInternalPointer());

return true;
yield $key => $isCsvMode
? str_getcsv($strRow, $this->csv[self::KeyDelimiter], $this->csv[self::KeyEnclosure], $this->csv[self::KeyEscape])
: $strRow;
}
}


private function moveInternalPointer(): bool
/**
* @return array<string>
*/
private static function text2Array(string $text): array
{
$this->next();

return true;
$existsNewMethod = method_exists(Strings::class, 'unixNewLines'); // @phpstan-ignore-line
return explode("\n", $existsNewMethod
? Strings::unixNewLines($text)
: Strings::normalizeNewLines($text)
);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ a:4:{i:0;s:6:"1Lorem";i:1;s:5:"ipsum";i:2;s:9:"dolor sit";i:3;s:7:"Windows";}
a:4:{i:0;s:5:"Lorem";i:1;s:5:"ipsum";i:2;s:9:"dolor sit";i:3;s:7:"Solaris";}
a:4:{i:0;s:5:"Lorem";i:1;s:5:"ipsum";i:2;s:9:"dolor sit";i:3;s:5:"Linux";}
a:4:{i:0;s:5:"Lorem";i:1;s:5:"ipsum";i:2;s:9:"dolor sit";i:3;s:3:"Mac";}
a:4:{i:0;s:5:"Lorem";i:1;s:5:"ipsum";i:2;s:9:"dolor sit";i:3;s:4:"amet";}
a:4:{i:0;s:5:"Lorem";i:1;s:5:"ipsum";i:2;s:9:"dolor sit";i:3;s:4:"amet";}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
a:4:{i:0;s:5:"Lorem";i:1;s:5:"ipsum";i:2;s:9:"dolor sit";i:3;s:7:"Solaris";}
a:4:{i:0;s:5:"Lorem";i:1;s:5:"ipsum";i:2;s:9:"dolor sit";i:3;s:5:"Linux";}
a:4:{i:0;s:5:"Lorem";i:1;s:5:"ipsum";i:2;s:9:"dolor sit";i:3;s:3:"Mac";}
a:4:{i:0;s:5:"Lorem";i:1;s:5:"ipsum";i:2;s:9:"dolor sit";i:3;s:4:"amet";}
a:4:{i:0;s:5:"Lorem";i:1;s:5:"ipsum";i:2;s:9:"dolor sit";i:3;s:4:"amet";}
4 changes: 4 additions & 0 deletions tests/Fixtures/fileObject.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
a:1:{i:0;s:30:"1Lorem;ipsum;dolor sit;Windows";}
a:1:{i:0;s:59:"Lorem;ipsum;dolor sit;Solaris Lorem;ipsum;dolor sit;Linux";}
a:1:{i:0;s:25:"Lorem;ipsum;dolor sit;Mac";}
a:1:{i:0;s:26:"Lorem;ipsum;dolor sit;amet";}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,7 @@
Lorem;ipsum;dolor sit;Solaris
Lorem;ipsum;dolor sit;Linux
Lorem;ipsum;dolor sit;Mac
Lorem;ipsum;dolor sit;amet


Lorem;ipsum;dolor sit;amet

Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@
Lorem;ipsum;dolor sit;Linux
Lorem;ipsum;dolor sit;Mac

Lorem;ipsum;dolor sit;amet

Lorem;ipsum;dolor sit;amet

9 changes: 3 additions & 6 deletions tests/Fixtures/noSetup.csv
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@

1Lorem;ipsum;dolor sit;Windows
Lorem;ipsum;dolor sit;Solaris
Lorem;ipsum;dolor sit;Linux
Lorem;ipsum;dolor sit;Mac


Lorem;ipsum;dolor sit;Solaris
Lorem;ipsum;dolor sit;Linux
Lorem;ipsum;dolor sit;Mac
Lorem;ipsum;dolor sit;amet
5 changes: 5 additions & 0 deletions tests/Fixtures/original.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
1Lorem;ipsum;dolor sit;Windows
Lorem;ipsum;dolor sit;Solaris Lorem;ipsum;dolor sit;Linux
Lorem;ipsum;dolor sit;Mac

Lorem;ipsum;dolor sit;amet
Expand Down
Loading

0 comments on commit 4823101

Please sign in to comment.