diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index c3477af..936fe87 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -1,6 +1,7 @@ ignoreVCSIgnored(true) + ->exclude('Tests/Functional/Converter/Fixtures/Csv/') ->exclude('Tests/Functional/Converter/Fixtures/Xml/') ->in(realpath(__DIR__)) ; diff --git a/CHANGELOG.md b/CHANGELOG.md index 1fe0560..febbdba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.4.0 - 2023-11-07 + +### Added + +- Add command `convert-from-csv`. + ## v1.3.1 - 2023-08-10 ### Added diff --git a/Classes/Command/ConvertFromCsv.php b/Classes/Command/ConvertFromCsv.php new file mode 100644 index 0000000..1220ba2 --- /dev/null +++ b/Classes/Command/ConvertFromCsv.php @@ -0,0 +1,62 @@ + + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +namespace Codappix\Typo3PhpDatasets\Command; + +use Codappix\Typo3PhpDatasets\Converter\Csv; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +class ConvertFromCsv extends Command +{ + protected static $defaultName = 'convert-from-csv'; + protected static $defaultDescription = 'Converts CSV data-sets to PHP data-sets.'; + + protected function configure(): void + { + $this->addArgument('file', InputArgument::IS_ARRAY, 'The file(s) to convert.'); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $files = $input->getArgument('file'); + if (is_array($files) === false) { + $output->writeln('File needs to be an array.'); + return Command::INVALID; + } + + $converter = new Csv(); + foreach ($files as $file) { + try { + $converter->convert(realpath($file) ?: $file); + } catch (\Exception $e) { + $output->writeln($e->getMessage()); + return Command::INVALID; + } + } + + return Command::SUCCESS; + } +} diff --git a/Classes/Converter/Csv.php b/Classes/Converter/Csv.php new file mode 100644 index 0000000..7ef1cbe --- /dev/null +++ b/Classes/Converter/Csv.php @@ -0,0 +1,99 @@ + + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +namespace Codappix\Typo3PhpDatasets\Converter; + +use InvalidArgumentException; +use SplFileObject; + +class Csv implements Converter +{ + public function convert(string $fileName): string + { + if (file_exists($fileName) === false) { + throw new InvalidArgumentException('Given file "' . $fileName . '" does not exist.', 1681283739); + } + + $phpFileName = $this->getNewFileName($fileName); + $incomingFile = new SplFileObject($fileName); + + try { + file_put_contents($phpFileName, $this->buildContent($incomingFile)); + } catch (\Exception $e) { + throw new \Exception('Could not generate new file.', 1681287881, $e); + } + + return $phpFileName; + } + + private function getNewFileName(string $fileName): string + { + $file = new SplFileObject($fileName); + return str_replace( + $file->getBasename(), + $file->getBasename($file->getExtension()) . 'php', + $file->getRealPath() + ); + } + + private function buildContent(SplFileObject $incomingFile): string + { + $phpArray = []; + $tableName = ''; + $columns = []; + + $incomingFile->setFlags(SplFileObject::READ_CSV); + $incomingFile->setCsvControl(',', '"', '"'); + + foreach ($incomingFile as $line) { + if (is_array($line) && count($line) === 1 && is_null($line[0])) { + // End of file + break; + } + + if (is_array($line) && count($line) === 1 && is_string($line[0])) { + // Line is a new table, introducing also new columns o next row + $tableName = $line[0]; + $columns = []; + continue; + } + + if ($columns === [] && is_array($line)) { + $columns = array_slice($line, 1); + continue; + } + + if (is_array($line)) { + $values = array_slice($line, 1); + $phpArray[$tableName][] = array_combine($columns, $values); + } + } + + return implode(PHP_EOL, [ + ' + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +namespace Codappix\Typo3PhpDatasets\Tests\Functional\Converter; + +use Codappix\Typo3PhpDatasets\Converter\Csv; +use GlobIterator; +use InvalidArgumentException; +use PHPUnit\Framework\TestCase; + +/** + * @covers \Codappix\Typo3PhpDatasets\Converter\Csv + * @testdox The CSV converter + */ +class CsvTest extends TestCase +{ + protected function tearDown(): void + { + $filesToDelete = new GlobIterator(__DIR__ . '/Fixtures/Csv/*Incoming.php'); + foreach ($filesToDelete as $file) { + unlink((string)$file); + } + + parent::tearDown(); + } + + /** + * @test + */ + public function canBeCreated(): void + { + $subject = new Csv(); + + self::assertInstanceOf( + Csv::class, + $subject + ); + } + + /** + * @test + */ + public function throwsExceptionForNoneExistingFile(): void + { + $subject = new Csv(); + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionCode(1681283739); + $this->expectExceptionMessage('Given file "NoneExistingFile.csv" does not exist.'); + $subject->convert('NoneExistingFile.csv'); + } + + /** + * @test + * @dataProvider possibleCsvFiles + * @testdox Converts $_dataName CSV to PHP + */ + public function convertsCsvFileToPhpFile( + string $incomingCsvFile, + string $expectedResultFile + ): void { + $subject = new Csv(); + $result = $subject->convert($incomingCsvFile); + + self::assertFileEquals($expectedResultFile, $result); + } + + public static function possibleCsvFiles(): array + { + return [ + 'Simple' => [ + 'incomingCsvFile' => __DIR__ . '/Fixtures/Csv/SimpleIncoming.csv', + 'expectedResultFile' => __DIR__ . '/Fixtures/Csv/SimpleAssert.php', + ], + 'Multiple records in single table' => [ + 'incomingCsvFile' => __DIR__ . '/Fixtures/Csv/MultipleRecordsInSingleTableIncoming.csv', + 'expectedResultFile' => __DIR__ . '/Fixtures/Csv/MultipleRecordsInSingleTableAssert.php', + ], + 'Records in different tables' => [ + 'incomingCsvFile' => __DIR__ . '/Fixtures/Csv/RecordsInDifferentTablesIncoming.csv', + 'expectedResultFile' => __DIR__ . '/Fixtures/Csv/RecordsInDifferentTablesAssert.php', + ], + ]; + } +} diff --git a/Tests/Functional/Converter/Fixtures/Csv/MultipleRecordsInSingleTableAssert.php b/Tests/Functional/Converter/Fixtures/Csv/MultipleRecordsInSingleTableAssert.php new file mode 100644 index 0000000..d627869 --- /dev/null +++ b/Tests/Functional/Converter/Fixtures/Csv/MultipleRecordsInSingleTableAssert.php @@ -0,0 +1,21 @@ + + array ( + 0 => + array ( + 'uid' => '1', + 'pid' => '0', + 'title' => 'Page with uid 1 below 0', + 'deleted' => '0', + ), + 1 => + array ( + 'uid' => '2', + 'pid' => '1', + 'title' => 'Page with uid 2 below 1', + 'deleted' => '0', + ), + ), +); diff --git a/Tests/Functional/Converter/Fixtures/Csv/MultipleRecordsInSingleTableIncoming.csv b/Tests/Functional/Converter/Fixtures/Csv/MultipleRecordsInSingleTableIncoming.csv new file mode 100644 index 0000000..eca2f41 --- /dev/null +++ b/Tests/Functional/Converter/Fixtures/Csv/MultipleRecordsInSingleTableIncoming.csv @@ -0,0 +1,4 @@ +pages +,uid,pid,title,deleted +,1,0,Page with uid 1 below 0,0 +,2,1,Page with uid 2 below 1,0 diff --git a/Tests/Functional/Converter/Fixtures/Csv/RecordsInDifferentTablesAssert.php b/Tests/Functional/Converter/Fixtures/Csv/RecordsInDifferentTablesAssert.php new file mode 100644 index 0000000..bcd7071 --- /dev/null +++ b/Tests/Functional/Converter/Fixtures/Csv/RecordsInDifferentTablesAssert.php @@ -0,0 +1,24 @@ + + array ( + 0 => + array ( + 'uid' => '1', + 'pid' => '0', + 'title' => 'Page with uid 1 below 0', + 'deleted' => '0', + ), + ), + 'tt_content' => + array ( + 0 => + array ( + 'uid' => '1', + 'pid' => '1', + 'header' => 'Content with uid 1 on page 1', + 'deleted' => '0', + ), + ), +); diff --git a/Tests/Functional/Converter/Fixtures/Csv/RecordsInDifferentTablesIncoming.csv b/Tests/Functional/Converter/Fixtures/Csv/RecordsInDifferentTablesIncoming.csv new file mode 100644 index 0000000..cffb844 --- /dev/null +++ b/Tests/Functional/Converter/Fixtures/Csv/RecordsInDifferentTablesIncoming.csv @@ -0,0 +1,6 @@ +pages +,uid,pid,title,deleted +,1,0,Page with uid 1 below 0,0 +tt_content +,uid,pid,header,deleted +,1,1,Content with uid 1 on page 1,0 diff --git a/Tests/Functional/Converter/Fixtures/Csv/SimpleAssert.php b/Tests/Functional/Converter/Fixtures/Csv/SimpleAssert.php new file mode 100644 index 0000000..78d597e --- /dev/null +++ b/Tests/Functional/Converter/Fixtures/Csv/SimpleAssert.php @@ -0,0 +1,14 @@ + + array ( + 0 => + array ( + 'uid' => '1', + 'pid' => '0', + 'title' => 'Page with uid 1 below 0', + 'deleted' => '0', + ), + ), +); diff --git a/Tests/Functional/Converter/Fixtures/Csv/SimpleIncoming.csv b/Tests/Functional/Converter/Fixtures/Csv/SimpleIncoming.csv new file mode 100644 index 0000000..94baded --- /dev/null +++ b/Tests/Functional/Converter/Fixtures/Csv/SimpleIncoming.csv @@ -0,0 +1,3 @@ +pages +,uid,pid,title,deleted +,1,0,Page with uid 1 below 0,0 diff --git a/bin/typo3-php-datasets b/bin/typo3-php-datasets index c3829a1..a7f41b1 100755 --- a/bin/typo3-php-datasets +++ b/bin/typo3-php-datasets @@ -3,11 +3,13 @@ require $_composer_autoload_path ?? __DIR__ . '/../vendor/autoload.php'; +use Codappix\Typo3PhpDatasets\Command\ConvertFromCsv; use Codappix\Typo3PhpDatasets\Command\ConvertFromXml; use Symfony\Component\Console\Application; $application = new Application(); $application->add(new ConvertFromXml()); +$application->add(new ConvertFromCsv()); $application->run();